Skip to content
Open
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
83 changes: 56 additions & 27 deletions cpp/cuopt_cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,25 +85,13 @@ inline cuopt::init_logger_t dummy_logger(
* @brief Run a single file
* @param file_path Path to the MPS format input file containing the optimization problem
* @param initial_solution_file Path to initial solution file in SOL format
* @param settings_strings Map of solver parameters
* @param settings Merged solver settings (config file loaded in main, then CLI overrides applied)
*/
int run_single_file(const std::string& file_path,
const std::string& initial_solution_file,
bool solve_relaxation,
const std::map<std::string, std::string>& settings_strings)
cuopt::linear_programming::solver_settings_t<int, double>& settings)
{
cuopt::linear_programming::solver_settings_t<int, double> settings;

try {
for (auto& [key, val] : settings_strings) {
settings.set_parameter_from_string(key, val);
}
} catch (const std::exception& e) {
auto log = dummy_logger(settings);
CUOPT_LOG_ERROR("Error: %s", e.what());
return -1;
}

std::string base_filename = file_path.substr(file_path.find_last_of("/\\") + 1);

constexpr bool input_mps_strict = false;
Expand Down Expand Up @@ -259,6 +247,21 @@ int set_cuda_module_loading(int argc, char* argv[])
*/
int main(int argc, char* argv[])
{
// Handle dump flags before argparse so no other args are required
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--dump-hyper-params") {
cuopt::linear_programming::solver_settings_t<int, double> settings;
settings.dump_parameters_to_file("/dev/stdout", true);
return 0;
}
if (arg == "--dump-params") {
cuopt::linear_programming::solver_settings_t<int, double> settings;
settings.dump_parameters_to_file("/dev/stdout", false);
return 0;
}
}

if (set_cuda_module_loading(argc, argv) != 0) { return 1; }

// Get the version string from the version_config.hpp file
Expand Down Expand Up @@ -287,6 +290,20 @@ int main(int argc, char* argv[])
.default_value(true)
.implicit_value(true);

program.add_argument("--params-file")
.help("path to parameter config file (key = value format, supports all parameters)")
.default_value(std::string(""));

program.add_argument("--dump-hyper-params")
.help("print hyper-parameters only in config file format and exit")
.default_value(false)
.implicit_value(true);

program.add_argument("--dump-params")
.help("print all parameters in config file format and exit")
.default_value(false)
.implicit_value(true);

std::map<std::string, std::string> arg_name_to_param_name;

// Register --pdlp-precision with string-to-int mapping so that it flows
Expand All @@ -312,37 +329,39 @@ int main(int argc, char* argv[])
std::string arg_name = param_name_to_arg_name(param.param_name);
// handle duplicate parameters appearing in MIP and LP settings
if (arg_name_to_param_name.count(arg_name) == 0) {
program.add_argument(arg_name.c_str()).default_value(param.default_value);
auto& arg = program.add_argument(arg_name.c_str()).default_value(param.default_value);
if (param.param_name.find("hyper_") != std::string::npos) { arg.hidden(); }
arg_name_to_param_name[arg_name] = param.param_name;
}
}

for (auto& param : double_params) {
std::string arg_name = param_name_to_arg_name(param.param_name);
// handle duplicate parameters appearing in MIP and LP settings
if (arg_name_to_param_name.count(arg_name) == 0) {
program.add_argument(arg_name.c_str()).default_value(param.default_value);
auto& arg = program.add_argument(arg_name.c_str()).default_value(param.default_value);
if (param.param_name.find("hyper_") != std::string::npos) { arg.hidden(); }
arg_name_to_param_name[arg_name] = param.param_name;
}
}

for (auto& param : bool_params) {
std::string arg_name = param_name_to_arg_name(param.param_name);
if (arg_name_to_param_name.count(arg_name) == 0) {
program.add_argument(arg_name.c_str()).default_value(param.default_value);
auto& arg = program.add_argument(arg_name.c_str()).default_value(param.default_value);
if (param.param_name.find("hyper_") != std::string::npos) { arg.hidden(); }
arg_name_to_param_name[arg_name] = param.param_name;
}
}

for (auto& param : string_params) {
std::string arg_name = param_name_to_arg_name(param.param_name);
// handle duplicate parameters appearing in MIP and LP settings
if (arg_name_to_param_name.count(arg_name) == 0) {
program.add_argument(arg_name.c_str()).default_value(param.default_value);
auto& arg = program.add_argument(arg_name.c_str()).default_value(param.default_value);
if (param.param_name.find("hyper_") != std::string::npos) { arg.hidden(); }
arg_name_to_param_name[arg_name] = param.param_name;
}
} // done with solver settings
}
}
} // done with solver settings

// Parse arguments
try {
Expand Down Expand Up @@ -374,16 +393,26 @@ int main(int argc, char* argv[])

const auto initial_solution_file = program.get<std::string>("--initial-solution");
const auto solve_relaxation = program.get<bool>("--relaxation");
const auto params_file = program.get<std::string>("--params-file");

cuopt::linear_programming::solver_settings_t<int, double> settings;
try {
if (!params_file.empty()) { settings.load_parameters_from_file(params_file); }
for (auto& [key, val] : settings_strings) {
settings.set_parameter_from_string(key, val);
}
} catch (const std::exception& e) {
auto log = dummy_logger(settings);
CUOPT_LOG_ERROR("Error: %s", e.what());
return -1;
}

// Only initialize CUDA resources if using GPU memory backend (not remote execution)
auto memory_backend = cuopt::linear_programming::get_memory_backend_type();
std::vector<std::shared_ptr<rmm::mr::device_memory_resource>> memory_resources;

if (memory_backend == cuopt::linear_programming::memory_backend_t::GPU) {
// All arguments are parsed as string, default values are parsed as int if unused.
const auto num_gpus = program.is_used("--num-gpus")
? std::stoi(program.get<std::string>("--num-gpus"))
: program.get<int>("--num-gpus");
const int num_gpus = settings.get_parameter<int>(CUOPT_NUM_GPUS);

for (int i = 0; i < std::min(raft::device_setter::get_device_count(), num_gpus); ++i) {
RAFT_CUDA_TRY(cudaSetDevice(i));
Expand All @@ -393,5 +422,5 @@ int main(int argc, char* argv[])
RAFT_CUDA_TRY(cudaSetDevice(0));
}

return run_single_file(file_name, initial_solution_file, solve_relaxation, settings_strings);
return run_single_file(file_name, initial_solution_file, solve_relaxation, settings);
}
23 changes: 23 additions & 0 deletions cpp/include/cuopt/linear_programming/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,29 @@
#define CUOPT_RANDOM_SEED "random_seed"
#define CUOPT_PDLP_PRECISION "pdlp_precision"

#define CUOPT_MIP_HYPER_HEURISTIC_POPULATION_SIZE "mip_hyper_heuristic_population_size"
#define CUOPT_MIP_HYPER_HEURISTIC_NUM_CPUFJ_THREADS "mip_hyper_heuristic_num_cpufj_threads"
#define CUOPT_MIP_HYPER_HEURISTIC_PRESOLVE_TIME_RATIO "mip_hyper_heuristic_presolve_time_ratio"
#define CUOPT_MIP_HYPER_HEURISTIC_PRESOLVE_MAX_TIME "mip_hyper_heuristic_presolve_max_time"
#define CUOPT_MIP_HYPER_HEURISTIC_ROOT_LP_TIME_RATIO "mip_hyper_heuristic_root_lp_time_ratio"
#define CUOPT_MIP_HYPER_HEURISTIC_ROOT_LP_MAX_TIME "mip_hyper_heuristic_root_lp_max_time"
#define CUOPT_MIP_HYPER_HEURISTIC_RINS_TIME_LIMIT "mip_hyper_heuristic_rins_time_limit"
#define CUOPT_MIP_HYPER_HEURISTIC_RINS_MAX_TIME_LIMIT "mip_hyper_heuristic_rins_max_time_limit"
#define CUOPT_MIP_HYPER_HEURISTIC_RINS_FIX_RATE "mip_hyper_heuristic_rins_fix_rate"
#define CUOPT_MIP_HYPER_HEURISTIC_STAGNATION_TRIGGER "mip_hyper_heuristic_stagnation_trigger"
#define CUOPT_MIP_HYPER_HEURISTIC_MAX_ITERS_WITHOUT_IMPROVEMENT \
"mip_hyper_heuristic_max_iterations_without_improvement"
#define CUOPT_MIP_HYPER_HEURISTIC_INITIAL_INFEASIBILITY_WEIGHT \
"mip_hyper_heuristic_initial_infeasibility_weight"
#define CUOPT_MIP_HYPER_HEURISTIC_N_OF_MINIMUMS_FOR_EXIT \
"mip_hyper_heuristic_n_of_minimums_for_exit"
#define CUOPT_MIP_HYPER_HEURISTIC_ENABLED_RECOMBINERS "mip_hyper_heuristic_enabled_recombiners"
#define CUOPT_MIP_HYPER_HEURISTIC_CYCLE_DETECTION_LENGTH \
"mip_hyper_heuristic_cycle_detection_length"
#define CUOPT_MIP_HYPER_HEURISTIC_RELAXED_LP_TIME_LIMIT "mip_hyper_heuristic_relaxed_lp_time_limit"
#define CUOPT_MIP_HYPER_HEURISTIC_RELATED_VARS_TIME_LIMIT \
"mip_hyper_heuristic_related_vars_time_limit"

/* @brief MIP determinism mode constants */
#define CUOPT_MODE_OPPORTUNISTIC 0
#define CUOPT_MODE_DETERMINISTIC 1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */

#pragma once

namespace cuopt::linear_programming {

/**
* @brief Tuning knobs for MIP GPU heuristics.
*
* All fields carry their actual defaults. A config file only needs to list
* the knobs being changed; omitted keys keep the values shown here.
* These are registered in the unified parameter framework via solver_settings_t
* and can be loaded from a config file with load_parameters_from_file().
*/
struct mip_heuristics_hyper_params_t {
int population_size = 32; // max solutions in pool
int num_cpufj_threads = 8; // parallel CPU FJ climbers
double presolve_time_ratio = 0.1; // fraction of total time for presolve
double presolve_max_time = 60.0; // hard cap on presolve seconds
double root_lp_time_ratio = 0.1; // fraction of total time for root LP
double root_lp_max_time = 15.0; // hard cap on root LP seconds
double rins_time_limit = 3.0; // per-call RINS sub-MIP time
double rins_max_time_limit = 20.0; // ceiling for RINS adaptive time budget
double rins_fix_rate = 0.5; // RINS variable fix rate
int stagnation_trigger = 3; // FP loops w/o improvement before recombination
int max_iterations_without_improvement = 8; // diversity step depth after stagnation
double initial_infeasibility_weight = 1000.0; // constraint violation penalty seed
int n_of_minimums_for_exit = 7000; // FJ baseline local-minima exit threshold
int enabled_recombiners = 15; // bitmask: 1=BP 2=FP 4=LS 8=SubMIP
int cycle_detection_length = 30; // FP assignment cycle ring buffer
double relaxed_lp_time_limit = 1.0; // base relaxed LP time cap in heuristics
double related_vars_time_limit = 30.0; // time for related-variable structure build
};

} // namespace cuopt::linear_programming
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <vector>

#include <cuopt/linear_programming/constants.h>
#include <cuopt/linear_programming/mip/heuristics_hyper_params.hpp>
#include <cuopt/linear_programming/pdlp/pdlp_hyper_params.cuh>
#include <cuopt/linear_programming/utilities/internals.hpp>

Expand Down Expand Up @@ -137,6 +138,8 @@ class mip_solver_settings_t {
// TODO check with Akif and Alice
pdlp_hyper_params::pdlp_hyper_params_t hyper_params;

mip_heuristics_hyper_params_t heuristic_params;

private:
std::vector<internals::base_solution_callback_t*> mip_callbacks_;

Expand Down
3 changes: 3 additions & 0 deletions cpp/include/cuopt/linear_programming/solver_settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ class solver_settings_t {
const std::vector<parameter_info_t<std::string>>& get_string_parameters() const;
const std::vector<std::string> get_parameter_names() const;

void load_parameters_from_file(const std::string& path);
bool dump_parameters_to_file(const std::string& path, bool hyperparameters_only = true) const;

private:
pdlp_solver_settings_t<i_t, f_t> pdlp_settings;
mip_solver_settings_t<i_t, f_t> mip_settings;
Expand Down
24 changes: 18 additions & 6 deletions cpp/include/cuopt/linear_programming/utilities/internals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,37 +79,49 @@ class base_solution_t {

template <typename T>
struct parameter_info_t {
parameter_info_t(std::string_view param_name, T* value, T min, T max, T def)
: param_name(param_name), value_ptr(value), min_value(min), max_value(max), default_value(def)
parameter_info_t(
std::string_view param_name, T* value, T min, T max, T def, const char* description = "")
: param_name(param_name),
value_ptr(value),
min_value(min),
max_value(max),
default_value(def),
description(description)
{
}
std::string param_name;
T* value_ptr;
T min_value;
T max_value;
T default_value;
const char* description;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about adding the description for the purpose of cuopt_cli --help, however, I decided against it in favor of making the names themselves self explanatory. Further more the the macros are defined as strings, we can probably use them as descriptions. Wdyt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point :) I was however a bit worried about the UX - some of these hyperparameters are named after internal implementation details which aren't immediately obvious or clear; this is why I thought a description field could be relevant to help guide tuning efforts (e.g. what results to expect from this "knob")

};

template <>
struct parameter_info_t<bool> {
parameter_info_t(std::string_view name, bool* value, bool def)
: param_name(name), value_ptr(value), default_value(def)
parameter_info_t(std::string_view name, bool* value, bool def, const char* description = "")
: param_name(name), value_ptr(value), default_value(def), description(description)
{
}
std::string param_name;
bool* value_ptr;
bool default_value;
const char* description;
};

template <>
struct parameter_info_t<std::string> {
parameter_info_t(std::string_view name, std::string* value, std::string def)
: param_name(name), value_ptr(value), default_value(def)
parameter_info_t(std::string_view name,
std::string* value,
std::string def,
const char* description = "")
: param_name(name), value_ptr(value), default_value(def), description(description)
{
}
std::string param_name;
std::string* value_ptr;
std::string default_value;
const char* description;
};

/**
Expand Down
Loading
Loading