Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
42 changes: 29 additions & 13 deletions benchmarks/linear_programming/cuopt/run_mip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@

#include "initial_problem_check.hpp"

#include <mip_heuristics/heuristics_hyper_params_loader.hpp>

void merge_result_files(const std::string& out_dir,
const std::string& final_result_file,
int n_gpus,
Expand Down Expand Up @@ -150,7 +152,8 @@ int run_single_file(std::string file_path,
int reliability_branching,
double time_limit,
double work_limit,
bool deterministic)
bool deterministic,
const std::string& heuristic_config_file = "")
{
const raft::handle_t handle_{};
cuopt::linear_programming::mip_solver_settings_t<int, double> settings;
Expand Down Expand Up @@ -215,6 +218,10 @@ int run_single_file(std::string file_path,
settings.reliability_branching = reliability_branching;
settings.clique_cuts = -1;
settings.seed = 42;
if (!heuristic_config_file.empty()) {
cuopt::linear_programming::fill_mip_heuristics_hyper_params(heuristic_config_file,
settings.heuristic_params);
}
cuopt::linear_programming::benchmark_info_t benchmark_info;
settings.benchmark_info_ptr = &benchmark_info;
auto start_run_solver = std::chrono::high_resolution_clock::now();
Expand Down Expand Up @@ -270,7 +277,8 @@ void run_single_file_mp(std::string file_path,
int reliability_branching,
double time_limit,
double work_limit,
bool deterministic)
bool deterministic,
const std::string& heuristic_config_file = "")
{
std::cout << "running file " << file_path << " on gpu : " << device << std::endl;
auto memory_resource = make_async();
Expand All @@ -288,7 +296,8 @@ void run_single_file_mp(std::string file_path,
reliability_branching,
time_limit,
work_limit,
deterministic);
deterministic,
heuristic_config_file);
// this is a bad design to communicate the result but better than adding complexity of IPC or
// pipes
exit(sol_found);
Expand Down Expand Up @@ -386,6 +395,10 @@ int main(int argc, char* argv[])
.default_value(false)
.implicit_value(true);

program.add_argument("--mip-heuristic-config")
.help("path to MIP heuristic hyper-parameters config file (key = value format)")
.default_value(std::string(""));

// Parse arguments
try {
program.parse_args(argc, argv);
Expand All @@ -409,14 +422,15 @@ int main(int argc, char* argv[])
std::string result_file;
int batch_num = -1;

bool heuristics_only = program.get<std::string>("--heuristics-only")[0] == 't';
int num_cpu_threads = program.get<int>("--num-cpu-threads");
bool write_log_file = program.get<std::string>("--write-log-file")[0] == 't';
bool log_to_console = program.get<std::string>("--log-to-console")[0] == 't';
double memory_limit = program.get<double>("--memory-limit");
bool track_allocations = program.get<std::string>("--track-allocations")[0] == 't';
int reliability_branching = program.get<int>("--reliability-branching");
bool deterministic = program.get<bool>("--determinism");
bool heuristics_only = program.get<std::string>("--heuristics-only")[0] == 't';
int num_cpu_threads = program.get<int>("--num-cpu-threads");
bool write_log_file = program.get<std::string>("--write-log-file")[0] == 't';
bool log_to_console = program.get<std::string>("--log-to-console")[0] == 't';
double memory_limit = program.get<double>("--memory-limit");
bool track_allocations = program.get<std::string>("--track-allocations")[0] == 't';
int reliability_branching = program.get<int>("--reliability-branching");
bool deterministic = program.get<bool>("--determinism");
std::string heuristic_config_file = program.get<std::string>("--mip-heuristic-config");

if (num_cpu_threads < 0) {
num_cpu_threads = omp_get_max_threads() / n_gpus;
Expand Down Expand Up @@ -516,7 +530,8 @@ int main(int argc, char* argv[])
reliability_branching,
time_limit,
work_limit,
deterministic);
deterministic,
heuristic_config_file);
} else if (sys_pid < 0) {
std::cerr << "Fork failed!" << std::endl;
exit(1);
Expand Down Expand Up @@ -559,7 +574,8 @@ int main(int argc, char* argv[])
reliability_branching,
time_limit,
work_limit,
deterministic);
deterministic,
heuristic_config_file);
}

return 0;
Expand Down
31 changes: 29 additions & 2 deletions cpp/cuopt_cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <vector>

#include <math_optimization/solution_reader.hpp>
#include <mip_heuristics/heuristics_hyper_params_loader.hpp>

#include <cuopt/version_config.hpp>

Expand Down Expand Up @@ -90,7 +91,8 @@ inline cuopt::init_logger_t dummy_logger(
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)
const std::map<std::string, std::string>& settings_strings,
const std::string& heuristic_config_file = "")
{
cuopt::linear_programming::solver_settings_t<int, double> settings;

Expand Down Expand Up @@ -177,6 +179,10 @@ int run_single_file(const std::string& file_path,
try {
if (is_mip) {
auto& mip_settings = settings.get_mip_settings();
if (!heuristic_config_file.empty()) {
cuopt::linear_programming::fill_mip_heuristics_hyper_params(heuristic_config_file,
mip_settings.heuristic_params);
}
auto solution = cuopt::linear_programming::solve_mip(problem_interface.get(), mip_settings);
} else {
auto& lp_settings = settings.get_pdlp_settings();
Expand Down Expand Up @@ -258,6 +264,16 @@ int set_cuda_module_loading(int argc, char* argv[])
*/
int main(int argc, char* argv[])
{
// Handle --dump-mip-heuristic-config before argparse so no other args are required
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--dump-mip-heuristic-config" && i + 1 < argc) {
cuopt::linear_programming::mip_heuristics_hyper_params_t defaults;
bool ok = cuopt::linear_programming::dump_mip_heuristics_hyper_params(argv[i + 1], defaults);
return ok ? 0 : 1;
}
}

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

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

program.add_argument("--mip-heuristic-config")
.help("path to MIP heuristic hyper-parameters config file (key = value format)")
.default_value(std::string(""));

program.add_argument("--dump-mip-heuristic-config")
.help("write default MIP heuristic hyper-parameters to the given file and exit")
.default_value(std::string(""));

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

// Register --pdlp-precision with string-to-int mapping so that it flows
Expand Down Expand Up @@ -392,5 +416,8 @@ int main(int argc, char* argv[])
RAFT_CUDA_TRY(cudaSetDevice(0));
}

return run_single_file(file_name, initial_solution_file, solve_relaxation, settings_strings);
const auto heuristic_config = program.get<std::string>("--mip-heuristic-config");

return run_single_file(
file_name, initial_solution_file, solve_relaxation, settings_strings, heuristic_config);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* 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.
* Loadable via fill_mip_heuristics_hyper_params(); dumpable via
* dump_mip_heuristics_hyper_params().
*/
// TODO: unify field declarations, loader tables, and dump logic via X macros
// so that adding a parameter is a single-line change.
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 @@ -134,6 +135,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
31 changes: 27 additions & 4 deletions cpp/src/mip_heuristics/diversity/diversity_manager.cu
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,33 @@ size_t sub_mip_recombiner_config_t::max_n_of_vars_from_other =
template <typename i_t, typename f_t>
std::vector<recombiner_enum_t> recombiner_t<i_t, f_t>::enabled_recombiners;

namespace {
diversity_config_t make_diversity_config(const mip_heuristics_hyper_params_t& hp)
{
diversity_config_t c;
c.max_solutions = hp.population_size;
Comment thread
aliceb-nv marked this conversation as resolved.
Outdated
c.time_ratio_on_init_lp = hp.root_lp_time_ratio;
c.max_time_on_lp = hp.root_lp_max_time;
c.initial_infeasibility_weight = hp.initial_infeasibility_weight;
return c;
}

rins_settings_t make_rins_settings(const mip_heuristics_hyper_params_t& hp)
{
rins_settings_t s;
s.default_fixrate = hp.rins_fix_rate;
s.default_time_limit = hp.rins_time_limit;
s.max_time_limit = hp.rins_max_time_limit;
return s;
}
} // namespace

template <typename i_t, typename f_t>
diversity_manager_t<i_t, f_t>::diversity_manager_t(mip_solver_context_t<i_t, f_t>& context_)
: context(context_),
branch_and_bound_ptr(nullptr),
problem_ptr(context.problem_ptr),
diversity_config(),
diversity_config(make_diversity_config(context_.settings.heuristic_params)),
population("population",
context,
*this,
Expand All @@ -54,7 +75,7 @@ diversity_manager_t<i_t, f_t>::diversity_manager_t(mip_solver_context_t<i_t, f_t
lp_dual_optimal_solution(context.problem_ptr->n_constraints,
context.problem_ptr->handle_ptr->get_stream()),
ls(context, lp_optimal_solution),
rins(context, *this),
rins(context, *this, make_rins_settings(context_.settings.heuristic_params)),
timer(diversity_config.default_time_limit),
bound_prop_recombiner(context,
context.problem_ptr->n_variables,
Expand Down Expand Up @@ -206,7 +227,8 @@ bool diversity_manager_t<i_t, f_t>::run_presolve(f_t time_limit, timer_t global_
compute_probing_cache(ls.constraint_prop.bounds_update, *problem_ptr, probing_timer);
if (problem_is_infeasible) { return false; }
}
const bool remap_cache_ids = true;
const bool remap_cache_ids = true;
problem_ptr->related_vars_time_limit = context.settings.heuristic_params.related_vars_time_limit;
if (!global_timer.check_time_limit()) { trivial_presolve(*problem_ptr, remap_cache_ids); }
if (!problem_ptr->empty && !check_bounds_sanity(*problem_ptr)) { return false; }
// if (!presolve_timer.check_time_limit() && !context.settings.heuristics_only &&
Expand Down Expand Up @@ -394,7 +416,8 @@ solution_t<i_t, f_t> diversity_manager_t<i_t, f_t>::run_solver()
problem_ptr->check_problem_representation(true);
// have the structure ready for reusing later
problem_ptr->compute_integer_fixed_problem();
recombiner_t<i_t, f_t>::init_enabled_recombiners(*problem_ptr);
recombiner_t<i_t, f_t>::init_enabled_recombiners(
*problem_ptr, context.settings.heuristic_params.enabled_recombiners);
mab_recombiner.resize_mab_arm_stats(recombiner_t<i_t, f_t>::enabled_recombiners.size());
// test problem is not ii
cuopt_func_call(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,14 @@ class recombiner_t {
"vars_to_fix should be sorted!");
}

static void init_enabled_recombiners(const problem_t<i_t, f_t>& problem)
static void init_enabled_recombiners(const problem_t<i_t, f_t>& problem,
int user_enabled_mask = -1)
{
std::unordered_set<recombiner_enum_t> enabled_recombiners;
for (auto recombiner : recombiner_types) {
if (user_enabled_mask >= 0 && !(user_enabled_mask & (1 << (uint32_t)recombiner))) {
continue;
}
enabled_recombiners.insert(recombiner);
}
if (problem.expensive_to_fix_vars) {
Expand Down
Loading
Loading