Skip to content

Commit ae99d04

Browse files
authored
feat: Load new model version should not reload loaded existing model version(s) (#388)
* Do not reload unmodified loaded model version * Track model directory timestamps more granularly to detect updates to model version files * Rename model config util config change require reload function * Re-organize ModelTimestamp() and throw exception * Revert "Re-organize ModelTimestamp() and throw exception" This reverts commit 41e57e5. * Break constructor into multiple functions * Comment on should raise an exception when failed to create timestamp
1 parent 9598a80 commit ae99d04

File tree

6 files changed

+204
-80
lines changed

6 files changed

+204
-80
lines changed

src/model_config_utils.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2432,14 +2432,16 @@ TritonToDataType(const TRITONSERVER_DataType dtype)
24322432
}
24332433

24342434
bool
2435-
EquivalentInNonInstanceGroupConfig(
2435+
ConfigChangeRequiresReload(
24362436
const inference::ModelConfig& old_config,
24372437
const inference::ModelConfig& new_config)
24382438
{
24392439
::google::protobuf::util::MessageDifferencer pb_diff;
24402440
pb_diff.IgnoreField(
24412441
old_config.descriptor()->FindFieldByLowercaseName("instance_group"));
2442-
return pb_diff.Compare(old_config, new_config);
2442+
pb_diff.IgnoreField(
2443+
old_config.descriptor()->FindFieldByLowercaseName("version_policy"));
2444+
return !pb_diff.Compare(old_config, new_config);
24432445
}
24442446

24452447
bool

src/model_config_utils.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,12 +288,14 @@ TRITONSERVER_DataType DataTypeToTriton(const inference::DataType dtype);
288288
/// \return The data type.
289289
inference::DataType TritonToDataType(const TRITONSERVER_DataType dtype);
290290

291-
/// Check if non instance group settings on the model configs are equivalent.
291+
/// Check if non instance group and non version policy settings on the model
292+
/// configs are equivalent.
292293
/// \param old_config The old model config.
293294
/// \param new_config The new model config.
294-
/// \return True if the model configs are equivalent in all non instance group
295-
/// settings. False if they differ in non instance group settings.
296-
bool EquivalentInNonInstanceGroupConfig(
295+
/// \return False if the model configs are equivalent in all non instance group
296+
/// and non version policy settings. True if they differ in non instance group
297+
/// and non version policy settings.
298+
bool ConfigChangeRequiresReload(
297299
const inference::ModelConfig& old_config,
298300
const inference::ModelConfig& new_config);
299301

src/model_repository_manager/model_lifecycle.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ Status
434434
ModelLifeCycle::AsyncLoad(
435435
const ModelIdentifier& model_id, const std::string& model_path,
436436
const inference::ModelConfig& model_config, const bool is_config_provided,
437-
const bool is_model_file_updated,
437+
const ModelTimestamp& prev_timestamp, const ModelTimestamp& curr_timestamp,
438438
const std::shared_ptr<TritonRepoAgentModelList>& agent_model_list,
439439
std::function<void(Status)>&& OnComplete)
440440
{
@@ -488,8 +488,9 @@ ModelLifeCycle::AsyncLoad(
488488
if (serving_model->state_ == ModelReadyState::READY) {
489489
// The model is currently being served. Check if the model load could
490490
// be completed with a simple config update.
491-
if (!is_model_file_updated && !serving_model->is_ensemble_ &&
492-
EquivalentInNonInstanceGroupConfig(
491+
if (!serving_model->is_ensemble_ &&
492+
!prev_timestamp.IsModelVersionModified(curr_timestamp, version) &&
493+
!ConfigChangeRequiresReload(
493494
serving_model->model_config_, model_config)) {
494495
// Update the model
495496
model_info = serving_model.get();

src/model_repository_manager/model_lifecycle.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
// Copyright 2022-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
//
33
// Redistribution and use in source and binary forms, with or without
44
// modification, are permitted provided that the following conditions
@@ -162,6 +162,7 @@ class TritonRepoAgentModelList {
162162
};
163163

164164
class InferenceServer;
165+
class ModelTimestamp;
165166

166167
class ModelLifeCycle {
167168
public:
@@ -183,7 +184,8 @@ class ModelLifeCycle {
183184
Status AsyncLoad(
184185
const ModelIdentifier& model_id, const std::string& model_path,
185186
const inference::ModelConfig& model_config, const bool is_config_provided,
186-
const bool is_model_file_updated,
187+
const ModelTimestamp& prev_timestamp,
188+
const ModelTimestamp& curr_timestamp,
187189
const std::shared_ptr<TritonRepoAgentModelList>& agent_model_list,
188190
std::function<void(Status)>&& OnComplete);
189191

src/model_repository_manager/model_repository_manager.cc

Lines changed: 149 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ CreateAgentModelListWithLoadAction(
267267
// Return the latest modification time in ns for all files/folders starting
268268
// from the path. The modification time will be 0 if there is an error.
269269
int64_t
270-
GetModifiedTime(const std::string& path)
270+
GetPathModifiedTime(const std::string& path)
271271
{
272272
// If there is an error in any step the fall-back default
273273
// modification time is 0. This means that in error cases 'path'
@@ -306,84 +306,173 @@ GetModifiedTime(const std::string& path)
306306

307307
for (const auto& child : contents) {
308308
const auto full_path = JoinPath({path, child});
309-
mtime = std::max(mtime, GetModifiedTime(full_path));
309+
mtime = std::max(mtime, GetPathModifiedTime(full_path));
310310
}
311311

312312
return mtime;
313313
}
314-
// Return the latest modification time in ns for '<config.pbtxt, model files>'
315-
// in a model directory path. The time for "config.pbtxt" will be 0 if not
316-
// found at "[model_dir_path]/config.pbtxt" or "[model_dir_path]/configs/
317-
// <custom-config-name>.pbtxt" if "--model-config-name" is set. The time for
318-
// "model files" includes the time for 'model_dir_path'.
319-
std::pair<int64_t, int64_t>
320-
GetDetailedModifiedTime(
314+
315+
} // namespace
316+
317+
ModelTimestamp::ModelTimestamp(
321318
const std::string& model_dir_path, const std::string& model_config_path)
322319
{
323-
// Check if 'model_dir_path' is a directory.
320+
// DLIS-7221: Raise an exception when failed to create timestamp
321+
bool init_success =
322+
ModelDirectoryPathIsValid(model_dir_path) &&
323+
ReadModelDirectoryTimestamp(model_dir_path, model_config_path) &&
324+
ReadModelDirectoryContentTimestamps(model_dir_path, model_config_path);
325+
if (!init_success) {
326+
// Same as calling the default constructor. All timestamps are presumed to
327+
// be 0 when comparing.
328+
model_timestamps_.clear();
329+
model_config_content_name_.clear();
330+
}
331+
}
332+
333+
bool
334+
ModelTimestamp::ModelDirectoryPathIsValid(const std::string& path) const
335+
{
324336
bool is_dir;
325-
Status status = IsDirectory(model_dir_path, &is_dir);
337+
Status status = IsDirectory(path, &is_dir);
326338
if (!status.IsOk()) {
327-
LOG_ERROR << "Failed to determine modification time for '" << model_dir_path
339+
LOG_ERROR << "Failed to determine modification time for '" << path
328340
<< "': " << status.AsString();
329-
return std::make_pair(0, 0);
341+
return false;
330342
}
331343
if (!is_dir) {
332-
LOG_ERROR << "Failed to determine modification time for '" << model_dir_path
344+
LOG_ERROR << "Failed to determine modification time for '" << path
333345
<< "': Model directory path is not a directory";
334-
return std::make_pair(0, 0);
346+
return false;
335347
}
348+
return true;
349+
}
336350

337-
std::pair<int64_t, int64_t> mtime(0, 0); // <config.pbtxt, model files>
338-
339-
// Populate 'model files' time to the model directory time
340-
status = FileModificationTime(model_dir_path, &mtime.second);
351+
bool
352+
ModelTimestamp::ReadModelDirectoryTimestamp(
353+
const std::string& model_dir_path, const std::string& model_config_path)
354+
{
355+
int64_t model_dir_time = 0;
356+
Status status = FileModificationTime(model_dir_path, &model_dir_time);
341357
if (!status.IsOk()) {
342358
LOG_ERROR << "Failed to determine modification time for '" << model_dir_path
343359
<< "': " << status.AsString();
344-
return std::make_pair(0, 0);
360+
return false;
345361
}
362+
model_timestamps_.emplace("", model_dir_time);
346363

347-
// Get files/folders in model_dir_path.
348-
std::set<std::string> contents;
349-
status = GetDirectoryContents(model_dir_path, &contents);
364+
return true;
365+
}
366+
367+
bool
368+
ModelTimestamp::ReadModelDirectoryContentTimestamps(
369+
const std::string& model_dir_path, const std::string& model_config_path)
370+
{
371+
std::set<std::string> dir_contents;
372+
Status status = GetDirectoryContents(model_dir_path, &dir_contents);
350373
if (!status.IsOk()) {
351374
LOG_ERROR << "Failed to determine modification time for '" << model_dir_path
352375
<< "': " << status.AsString();
353-
return std::make_pair(0, 0);
354-
}
355-
// Get latest modification time for each files/folders, and place it at the
356-
// correct category.
357-
for (const auto& child : contents) {
358-
const auto full_path = JoinPath({model_dir_path, child});
359-
if (full_path == model_config_path) {
360-
// config.pbtxt or customized config file in configs folder
361-
mtime.first = GetModifiedTime(full_path);
362-
} else {
363-
// model files
364-
mtime.second = std::max(mtime.second, GetModifiedTime(full_path));
376+
return false;
377+
}
378+
for (const auto& content_name : dir_contents) {
379+
const auto content_path = JoinPath({model_dir_path, content_name});
380+
bool is_model_config = model_config_path.rfind(content_path, 0) == 0;
381+
if (is_model_config) {
382+
if (!model_config_content_name_.empty()) {
383+
LOG_ERROR << "Failed to determine modification time for '"
384+
<< model_dir_path << "': Duplicate model config is detected";
385+
return false;
386+
}
387+
model_config_content_name_ = content_name;
365388
}
389+
model_timestamps_.emplace(content_name, GetPathModifiedTime(content_path));
366390
}
367391

368-
return mtime;
392+
return true;
369393
}
370-
// Return true if any file in the 'model_dir_path' has been modified more
371-
// recently than 'last_ns', which represents last modified time for
372-
// '<config.pbtxt, model files>'. Update the 'last_ns' to the most-recent
373-
// modified time.
394+
374395
bool
375-
IsModified(
376-
const std::string& model_dir_path, const std::string& model_config_path,
377-
std::pair<int64_t, int64_t>* last_ns)
396+
ModelTimestamp::IsModified(const ModelTimestamp& new_timestamp) const
378397
{
379-
auto new_ns = GetDetailedModifiedTime(model_dir_path, model_config_path);
380-
bool modified = std::max(new_ns.first, new_ns.second) >
381-
std::max(last_ns->first, last_ns->second);
382-
last_ns->swap(new_ns);
383-
return modified;
398+
int64_t old_modified_time = GetModifiedTime();
399+
int64_t new_modified_time = new_timestamp.GetModifiedTime();
400+
return new_modified_time > old_modified_time;
384401
}
385402

386-
} // namespace
403+
bool
404+
ModelTimestamp::IsModelVersionModified(
405+
const ModelTimestamp& new_timestamp, const int64_t version) const
406+
{
407+
int64_t old_modified_time = std::max(
408+
GetModelVersionModifiedTime(version),
409+
GetNonModelConfigNorVersionNorDirModifiedTime());
410+
int64_t new_modified_time = std::max(
411+
new_timestamp.GetModelVersionModifiedTime(version),
412+
new_timestamp.GetNonModelConfigNorVersionNorDirModifiedTime());
413+
return new_modified_time > old_modified_time;
414+
}
415+
416+
int64_t
417+
ModelTimestamp::GetModifiedTime() const
418+
{
419+
int64_t modified_time = 0;
420+
for (const auto& pair : model_timestamps_) {
421+
const int64_t time = pair.second;
422+
modified_time = std::max(modified_time, time);
423+
}
424+
return modified_time;
425+
}
426+
427+
int64_t
428+
ModelTimestamp::GetModelVersionModifiedTime(const int64_t version) const
429+
{
430+
int64_t modified_time = 0;
431+
auto itr = model_timestamps_.find(std::to_string(version));
432+
if (itr != model_timestamps_.end()) {
433+
modified_time = itr->second;
434+
}
435+
return modified_time;
436+
}
437+
438+
int64_t
439+
ModelTimestamp::GetNonModelConfigNorVersionNorDirModifiedTime() const
440+
{
441+
// Get modified time excluding time from model config, model version
442+
// directory(s) and model directory.
443+
int64_t modified_time = 0;
444+
for (const auto& pair : model_timestamps_) {
445+
const std::string& content_name = pair.first;
446+
bool found_non_digit_in_content_name = false;
447+
for (const char& c : content_name) {
448+
if (std::isdigit(c) == 0) {
449+
found_non_digit_in_content_name = true;
450+
break;
451+
}
452+
}
453+
// All model version directory(s) will not 'found_non_digit_in_content_name'
454+
// as they are all digit(s).
455+
// Model directory name will not 'found_non_digit_in_content_name' as it is
456+
// empty.
457+
if (found_non_digit_in_content_name &&
458+
content_name != model_config_content_name_) {
459+
const int64_t time = pair.second;
460+
modified_time = std::max(modified_time, time);
461+
}
462+
}
463+
return modified_time;
464+
}
465+
466+
void
467+
ModelTimestamp::SetModelConfigModifiedTime(const int64_t time_ns)
468+
{
469+
if (model_config_content_name_.empty()) {
470+
LOG_ERROR << "Failed to set config modification time: "
471+
"model_config_content_name_ is empty";
472+
return;
473+
}
474+
model_timestamps_[model_config_content_name_] = time_ns;
475+
}
387476

388477
ModelRepositoryManager::ModelRepositoryManager(
389478
const std::set<std::string>& repository_paths, const bool autofill,
@@ -624,7 +713,7 @@ ModelRepositoryManager::LoadModelByDependency(
624713
auto status = model_life_cycle_->AsyncLoad(
625714
valid_model->model_id_, itr->second->model_path_,
626715
valid_model->model_config_, itr->second->is_config_provided_,
627-
itr->second->mtime_nsec_.second > itr->second->prev_mtime_ns_.second,
716+
itr->second->prev_mtime_ns_, itr->second->mtime_nsec_,
628717
itr->second->agent_model_list_, [model_state](Status load_status) {
629718
model_state->status_ = load_status;
630719
model_state->ready_.set_value();
@@ -1406,7 +1495,7 @@ ModelRepositoryManager::InitializeModelInfo(
14061495
if (iitr != infos_.end()) {
14071496
linfo->prev_mtime_ns_ = iitr->second->mtime_nsec_;
14081497
} else {
1409-
linfo->prev_mtime_ns_ = std::make_pair(0, 0);
1498+
linfo->prev_mtime_ns_ = ModelTimestamp();
14101499
}
14111500

14121501
// Set 'mtime_nsec_' and override 'model_path_' if current path is empty
@@ -1435,7 +1524,7 @@ ModelRepositoryManager::InitializeModelInfo(
14351524
// For file override, set 'mtime_nsec_' to minimum value so that
14361525
// the next load without override will trigger re-load to undo
14371526
// the override while the local files may still be unchanged.
1438-
linfo->mtime_nsec_ = std::make_pair(0, 0);
1527+
linfo->mtime_nsec_ = ModelTimestamp();
14391528
linfo->model_path_ = location;
14401529
linfo->model_config_path_ = JoinPath({location, kModelConfigPbTxt});
14411530
linfo->agent_model_list_.reset(new TritonRepoAgentModelList());
@@ -1445,14 +1534,16 @@ ModelRepositoryManager::InitializeModelInfo(
14451534
GetModelConfigFullPath(linfo->model_path_, model_config_name_);
14461535
// Model is not loaded.
14471536
if (iitr == infos_.end()) {
1448-
linfo->mtime_nsec_ = GetDetailedModifiedTime(
1449-
linfo->model_path_, linfo->model_config_path_);
1537+
linfo->mtime_nsec_ =
1538+
ModelTimestamp(linfo->model_path_, linfo->model_config_path_);
14501539
} else {
14511540
// Check the current timestamps to determine if model actually has been
14521541
// modified
14531542
linfo->mtime_nsec_ = linfo->prev_mtime_ns_;
1454-
unmodified = !IsModified(
1455-
linfo->model_path_, linfo->model_config_path_, &linfo->mtime_nsec_);
1543+
ModelTimestamp new_mtime_ns =
1544+
ModelTimestamp(linfo->model_path_, linfo->model_config_path_);
1545+
unmodified = !linfo->mtime_nsec_.IsModified(new_mtime_ns);
1546+
linfo->mtime_nsec_ = new_mtime_ns;
14561547
}
14571548
}
14581549

@@ -1467,9 +1558,9 @@ ModelRepositoryManager::InitializeModelInfo(
14671558
// the override while the local files may still be unchanged.
14681559
auto time_since_epoch =
14691560
std::chrono::system_clock::now().time_since_epoch();
1470-
linfo->mtime_nsec_.first =
1561+
linfo->mtime_nsec_.SetModelConfigModifiedTime(
14711562
std::chrono::duration_cast<std::chrono::nanoseconds>(time_since_epoch)
1472-
.count();
1563+
.count());
14731564
unmodified = false;
14741565

14751566
const std::string& override_config = override_parameter->ValueString();

0 commit comments

Comments
 (0)