Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/generators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,8 @@ GeneratorParams::GeneratorParams(const Config& config)
}

GeneratorParams::GeneratorParams(const Model& model)
: config{*model.config_.get()},
: model_{model.shared_from_this()},
config{*model_->config_.get()},
use_graph_capture{IsGraphCaptureEnabled(model.config_->model.decoder.session_options)},
use_multi_profile{IsMultiProfileEnabled(model.config_->model.decoder.session_options)},
p_device{model.p_device_scoring_} {
Expand Down
5 changes: 4 additions & 1 deletion src/generators.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ struct GeneratorParams : std::enable_shared_from_this<GeneratorParams>, LeakChec
GeneratorParams(const Config& config); // This constructor is only used for internal generator benchmarks
GeneratorParams(const Model& model);

const Config& config; // The model outlives the GeneratorParams
// Co-owns the model so the aliased Config below cannot be freed while this
// params object is alive. Null for the benchmark-only Config constructor.
std::shared_ptr<const Model> model_;
const Config& config; // Aliases model-owned Config; kept alive by model_
Config::Search search{config.search}; // Copy of the search parameters from the config

// Query the params to get the value set for a param
Expand Down
28 changes: 28 additions & 0 deletions test/c_api_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,34 @@ TEST(CAPITests, TopKTopPExceedsVocabSizeThrows) {
}
}

// Regression test: a GeneratorParams created from a model must keep that model
// alive, so destroying the model handle before creating the generator does not
// cause a use-after-free (GeneratorParams aliases the model-owned Config, and
// Generator::Generator calls model.shared_from_this()).
TEST(CAPITests, CreateGeneratorAfterDestroyModel) {
OgaModel* model = nullptr;
ASSERT_EQ(OgaCreateModel(PHI2_PATH, &model), nullptr);
ASSERT_NE(model, nullptr);

OgaGeneratorParams* params = nullptr;
ASSERT_EQ(OgaCreateGeneratorParams(model, &params), nullptr);
ASSERT_NE(params, nullptr);

// Drop the external reference to the model by destroying its handle. Because
// params co-owns the underlying Model (and its Config) via shared ownership,
// the object itself stays alive, so dereferencing the raw model pointer below
// remains valid. This does NOT imply the handle is generally usable after
// OgaDestroyModel; it is valid here only because another owner keeps it alive.
OgaDestroyModel(model);

OgaGenerator* generator = nullptr;
ASSERT_EQ(OgaCreateGenerator(model, params, &generator), nullptr);
ASSERT_NE(generator, nullptr);

OgaDestroyGenerator(generator);
OgaDestroyGeneratorParams(params);
}

TEST(CAPITests, AdaptersTest) {
#ifdef USE_CUDA
using OutputType = Ort::Float16_t;
Expand Down
Loading