Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8de5112
feat(mip): bound presolve by rounds, badge size and work units
akifcorduk Jul 29, 2026
a74d486
fix(mip): scale the probing budget with the candidate count
akifcorduk Jul 30, 2026
ca84fec
feat(mip): select presolve budget points with CUOPT_CONFIG_ID
akifcorduk Jul 30, 2026
0360daa
fix(mip): keep a wall ceiling on Papilo and probing
akifcorduk Jul 30, 2026
568da86
feat(mip): bound presolve by cost proxies so it concludes before its …
akifcorduk Jul 30, 2026
e322616
cuopt-skill-evolution: add stage-budget and regression-attribution me…
akifcorduk Jul 30, 2026
ee2bf78
Loosen presolve limits that cost reduction without saving needed time
akifcorduk Jul 30, 2026
3a2ba29
Correct the bind-detection advice in the stage budgets reference
akifcorduk Jul 30, 2026
4d0c6a5
Bound the exempt Papilo badge and cut probing coverage to a quarter
akifcorduk Jul 31, 2026
9ede431
Keep probing inside the presolve share of the solve
akifcorduk Jul 31, 2026
182b4a6
Pair the sweep ids so each setting gets two repeats
akifcorduk Jul 31, 2026
e3261f1
Sweep the probing work ceiling with the wall cap removed
akifcorduk Jul 31, 2026
4f71e4a
Make the probing work ceiling the bound and demote the wall cap
akifcorduk Aug 3, 2026
0b77cd1
Report a too-dense ADAT as a capacity failure instead of crashing
akifcorduk Aug 3, 2026
cdeee69
Merge branch 'main' of github.com:NVIDIA/cuopt into presolve_work_units
akifcorduk Aug 4, 2026
837a37e
Keep the measured presolve rule and drop the sweep scaffolding
akifcorduk Aug 4, 2026
1bdb2b4
Let the work ceiling be the only bound on presolve
akifcorduk Aug 4, 2026
ee34659
Drop the +25% probing arm now that it measured neutral
akifcorduk Aug 4, 2026
d9573ba
Drop the presolve telemetry to DEBUG and trim its comments
akifcorduk Aug 4, 2026
ec3aded
fix ai comments
akifcorduk Aug 4, 2026
7cd4fcf
Merge branch 'main' of github.com:NVIDIA/cuopt into presolve_work_units
akifcorduk Aug 10, 2026
9fd5e1e
remove leftover file
akifcorduk Aug 10, 2026
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
5 changes: 3 additions & 2 deletions cpp/include/cuopt/mathematical_optimization/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@

#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_PRESOLVE_MAX_ROUNDS "mip_hyper_heuristic_presolve_max_rounds"
#define CUOPT_MIP_HYPER_HEURISTIC_PAPILO_PROBING_MAX_BADGESIZE \
"mip_hyper_heuristic_papilo_probing_max_badgesize"
#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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ namespace cuopt::mathematical_optimization {
*/
template <typename i_t, typename f_t>
struct mip_heuristics_hyper_params_t {
i_t population_size = 32; // max solutions in pool
i_t num_cpufj_threads = 8; // parallel CPU FJ climbers
f_t presolve_time_ratio = 0.1; // fraction of total time for presolve
f_t presolve_max_time = 60.0; // hard cap on presolve seconds
i_t population_size = 32; // max solutions in pool
i_t num_cpufj_threads = 8; // parallel CPU FJ climbers

// Presolve budgeting. Both are derived from the problem's dimensions and structure by default
// (see presolve_budget_policy.hpp); a negative value asks for that rule, and any other value
// overrides it, where <=0 removes the cap entirely.
i_t presolve_max_rounds = -1; // Papilo presolve rounds cap
i_t papilo_probing_max_badgesize = -1; // ceiling on Papilo's probing.minbadgesize

f_t root_lp_time_ratio = 0.1; // fraction of total time for root LP
f_t root_lp_max_time = 15.0; // hard cap on root LP seconds
f_t rins_time_limit = 3.0; // per-call RINS sub-MIP time
Expand Down
5 changes: 4 additions & 1 deletion cpp/src/barrier/barrier.cu
Original file line number Diff line number Diff line change
Expand Up @@ -4530,7 +4530,10 @@ lp_status_t barrier_solver_t<i_t, f_t>::solve(f_t start_time, lp_solution_t<i_t,
} catch (const raft::cuda_error& e) {
settings.log.printf("Error in barrier_solver_t: %s\n", e.what());
return lp_status_t::NUMERICAL_ISSUES;
} catch (const rmm::out_of_memory& e) {
} catch (const std::bad_alloc& e) {
// Covers rmm::out_of_memory and any other allocation failure. The barrier sizes its normal
// equations from the problem, so a shape it cannot hold is a property of the input rather
// than a defect, and the solvers running concurrently with it are unaffected.
settings.log.printf("Out of memory in barrier_solver_t: %s\n", e.what());
return lp_status_t::NUMERICAL_ISSUES;
}
Expand Down
14 changes: 14 additions & 0 deletions cpp/src/barrier/sparse_matrix_kernels.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
#include <barrier/cusparse_info.hpp>
#include <barrier/device_sparse_matrix.cuh>

#include <rmm/error.hpp>

#include <limits>
#include <string>

namespace cuopt::mathematical_optimization::barrier {

template <typename i_t, typename f_t>
Expand Down Expand Up @@ -140,6 +145,15 @@ void multiply_kernels(raft::handle_t const* handle,
int64_t ADAT_num_rows, ADAT_num_cols, ADAT_nnz1;
RAFT_CUSPARSE_TRY(
cusparseSpMatGetSize(cusparse_data.matADAT_descr, &ADAT_num_rows, &ADAT_num_cols, &ADAT_nnz1));
// cuSPARSE sizes the product in 64 bits while the CSR arrays are indexed by i_t; narrowing would
// reach RMM as a negative count and surface as an unrelated device_uvector overflow.
if (ADAT_nnz1 > std::numeric_limits<i_t>::max()) {
throw rmm::out_of_memory(
"ADAT needs " + std::to_string(ADAT_nnz1) + " nonzeros over " +
std::to_string(ADAT_num_rows) + " rows, past the " +
std::to_string(std::numeric_limits<i_t>::max()) +
" its index type can address: the normal equations are too dense for this problem");
}
ADAT.resize_to_nnz(ADAT_nnz1, handle->get_stream());

thrust::fill(rmm::exec_policy(handle->get_stream()), ADAT.x.begin(), ADAT.x.end(), 0.0);
Expand Down
17 changes: 11 additions & 6 deletions cpp/src/grpc/codegen/field_registry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -755,12 +755,9 @@ mip_settings:
field_num: 36
type: int32
optional: true
- presolve_time_ratio:
field_num: 37
optional: true
- presolve_max_time:
field_num: 38
optional: true
# 37 and 38 held presolve_time_ratio and presolve_max_time, removed when
# presolve stopped taking a wall budget. Do not reuse: an older client
# still sends them on those numbers.
- root_lp_time_ratio:
field_num: 39
optional: true
Expand Down Expand Up @@ -805,6 +802,14 @@ mip_settings:
- related_vars_time_limit:
field_num: 51
optional: true
- presolve_max_rounds:
field_num: 53
type: int32
optional: true
- papilo_probing_max_badgesize:
field_num: 54
type: int32
optional: true
Comment thread
akifcorduk marked this conversation as resolved.

# ─────────────────────────────────────────────────────────────────────────────
# Optimization Problem (cpu_optimization_problem_t)
Expand Down
4 changes: 2 additions & 2 deletions cpp/src/grpc/codegen/generated/cuopt_remote_data.proto
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,6 @@ message MIPSolverSettings {
optional double semi_continuous_big_m = 34;
optional int32 population_size = 35;
optional int32 num_cpufj_threads = 36;
optional double presolve_time_ratio = 37;
optional double presolve_max_time = 38;
optional double root_lp_time_ratio = 39;
optional double root_lp_max_time = 40;
optional double rins_time_limit = 41;
Expand All @@ -250,6 +248,8 @@ message MIPSolverSettings {
optional double relaxed_lp_time_limit = 50;
optional double related_vars_time_limit = 51;
optional int32 zero_half_cuts = 52;
optional int32 presolve_max_rounds = 53;
optional int32 papilo_probing_max_badgesize = 54;
}

message PDLPWarmStartData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@
pb_settings->set_semi_continuous_big_m(settings.semi_continuous_big_m);
pb_settings->set_population_size(settings.heuristic_params.population_size);
pb_settings->set_num_cpufj_threads(settings.heuristic_params.num_cpufj_threads);
pb_settings->set_presolve_time_ratio(settings.heuristic_params.presolve_time_ratio);
pb_settings->set_presolve_max_time(settings.heuristic_params.presolve_max_time);
pb_settings->set_root_lp_time_ratio(settings.heuristic_params.root_lp_time_ratio);
pb_settings->set_root_lp_max_time(settings.heuristic_params.root_lp_max_time);
pb_settings->set_rins_time_limit(settings.heuristic_params.rins_time_limit);
Expand All @@ -58,3 +56,5 @@
pb_settings->set_cycle_detection_length(settings.heuristic_params.cycle_detection_length);
pb_settings->set_relaxed_lp_time_limit(settings.heuristic_params.relaxed_lp_time_limit);
pb_settings->set_related_vars_time_limit(settings.heuristic_params.related_vars_time_limit);
pb_settings->set_presolve_max_rounds(settings.heuristic_params.presolve_max_rounds);
pb_settings->set_papilo_probing_max_badgesize(settings.heuristic_params.papilo_probing_max_badgesize);
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,6 @@
if (pb_settings.has_num_cpufj_threads()) {
settings.heuristic_params.num_cpufj_threads = pb_settings.num_cpufj_threads();
}
if (pb_settings.has_presolve_time_ratio()) {
settings.heuristic_params.presolve_time_ratio = pb_settings.presolve_time_ratio();
}
if (pb_settings.has_presolve_max_time()) {
settings.heuristic_params.presolve_max_time = pb_settings.presolve_max_time();
}
if (pb_settings.has_root_lp_time_ratio()) {
settings.heuristic_params.root_lp_time_ratio = pb_settings.root_lp_time_ratio();
}
Expand Down Expand Up @@ -152,3 +146,9 @@
if (pb_settings.has_related_vars_time_limit()) {
settings.heuristic_params.related_vars_time_limit = pb_settings.related_vars_time_limit();
}
if (pb_settings.has_presolve_max_rounds()) {
settings.heuristic_params.presolve_max_rounds = pb_settings.presolve_max_rounds();
}
if (pb_settings.has_papilo_probing_max_badgesize()) {
settings.heuristic_params.papilo_probing_max_badgesize = pb_settings.papilo_probing_max_badgesize();
}
4 changes: 2 additions & 2 deletions cpp/src/math_optimization/solver_settings.cu
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ solver_settings_t<i_t, f_t>::solver_settings_t() : pdlp_settings(), mip_settings
{CUOPT_MIP_CUT_MIN_ORTHOGONALITY, &mip_settings.cut_min_orthogonality, f_t(0.0), f_t(1.0), f_t(0.5)},
{CUOPT_BARRIER_STEP_SCALE, &pdlp_settings.barrier_step_scale, f_t(0.5), f_t(0.9999), f_t(0.9)},
// MIP heuristic hyper-parameters (hidden from default --help: name contains "hyper_")
{CUOPT_MIP_HYPER_HEURISTIC_PRESOLVE_TIME_RATIO, &mip_settings.heuristic_params.presolve_time_ratio, f_t(0.0), f_t(1.0), f_t(0.1), "fraction of total time for presolve"},
{CUOPT_MIP_HYPER_HEURISTIC_PRESOLVE_MAX_TIME, &mip_settings.heuristic_params.presolve_max_time, f_t(0.0), std::numeric_limits<f_t>::infinity(), f_t(60.0), "hard cap on presolve seconds"},
{CUOPT_MIP_HYPER_HEURISTIC_ROOT_LP_TIME_RATIO, &mip_settings.heuristic_params.root_lp_time_ratio, f_t(0.0), f_t(1.0), f_t(0.1), "fraction of total time for root LP"},
{CUOPT_MIP_HYPER_HEURISTIC_ROOT_LP_MAX_TIME, &mip_settings.heuristic_params.root_lp_max_time, f_t(0.0), std::numeric_limits<f_t>::infinity(), f_t(15.0), "hard cap on root LP seconds"},
{CUOPT_MIP_HYPER_HEURISTIC_RINS_TIME_LIMIT, &mip_settings.heuristic_params.rins_time_limit, f_t(0.0), std::numeric_limits<f_t>::infinity(), f_t(3.0), "per-call RINS sub-MIP time"},
Expand Down Expand Up @@ -169,6 +167,8 @@ solver_settings_t<i_t, f_t>::solver_settings_t() : pdlp_settings(), mip_settings
// MIP heuristic hyper-parameters (hidden from default --help: name contains "hyper_")
{CUOPT_MIP_HYPER_HEURISTIC_POPULATION_SIZE, &mip_settings.heuristic_params.population_size, 1, std::numeric_limits<i_t>::max(), 32, "max solutions in pool"},
{CUOPT_MIP_HYPER_HEURISTIC_NUM_CPUFJ_THREADS, &mip_settings.heuristic_params.num_cpufj_threads, 0, std::numeric_limits<i_t>::max(), 8, "parallel CPU FJ climbers"},
{CUOPT_MIP_HYPER_HEURISTIC_PRESOLVE_MAX_ROUNDS, &mip_settings.heuristic_params.presolve_max_rounds, -1, std::numeric_limits<i_t>::max(), -1, "Papilo presolve rounds cap (<0 derives it from the problem, 0 keeps Papilo default)"},
{CUOPT_MIP_HYPER_HEURISTIC_PAPILO_PROBING_MAX_BADGESIZE, &mip_settings.heuristic_params.papilo_probing_max_badgesize, -1, std::numeric_limits<i_t>::max(), -1, "ceiling on Papilo probing.minbadgesize (<0 derives it from the problem, 0 leaves it uncapped)"},
{CUOPT_MIP_HYPER_HEURISTIC_STAGNATION_TRIGGER, &mip_settings.heuristic_params.stagnation_trigger, 1, std::numeric_limits<i_t>::max(), 3, "FP loops w/o improvement before recombination"},
{CUOPT_MIP_HYPER_HEURISTIC_MAX_ITERS_WITHOUT_IMPROVEMENT, &mip_settings.heuristic_params.max_iterations_without_improvement, 1, std::numeric_limits<i_t>::max(), 8, "diversity step depth after stagnation"},
{CUOPT_MIP_HYPER_HEURISTIC_N_OF_MINIMUMS_FOR_EXIT, &mip_settings.heuristic_params.n_of_minimums_for_exit, 1, std::numeric_limits<i_t>::max(), 7000, "FJ baseline local-minima exit threshold"},
Expand Down
2 changes: 0 additions & 2 deletions cpp/src/mip_heuristics/diversity/diversity_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
namespace cuopt::mathematical_optimization::mip {

struct diversity_config_t {
double time_ratio_of_probing_cache = 0.1;
double max_time_on_probing = 60.0;
int max_var_diff = 256;
double default_time_limit = 10.;
int initial_island_size = 3;
Expand Down
28 changes: 19 additions & 9 deletions cpp/src/mip_heuristics/diversity/diversity_manager.cu
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

#include <utilities/scope_guard.hpp>

#include <chrono>
#include <cmath>
#include <limits>
#include <memory>
#include <numeric>

Expand Down Expand Up @@ -294,24 +297,31 @@ bool diversity_manager_t<i_t, f_t>::run_presolve(f_t time_limit, timer_t global_
if (termination_criterion_t::NO_UPDATE != term_crit) {
ls.constraint_prop.bounds_update.set_updated_bounds(*problem_ptr);
}
bool run_probing_cache = !fj_only_run;
// Don't run probing cache in deterministic mode yet as neither B&B nor CPUFJ need it
// and it doesn't make use of work units yet
if (context.settings.determinism_mode == CUOPT_MODE_DETERMINISTIC) { run_probing_cache = false; }
const auto& hp = context.settings.heuristic_params;
const auto probing_features = probing_presolve_features(*problem_ptr);
const auto probing_budget = evaluate_presolve_budget(hp, probing_features);
bool run_probing_cache = !fj_only_run;
// Allow the user to disable the probing-cache step of cuOpt's internal presolve
// independently of the higher-level presolver setting.
if (!context.settings.probing) {
CUOPT_LOG_INFO("Probing-cache step disabled via %s=false", CUOPT_MIP_PROBING);
run_probing_cache = false;
}
if (run_probing_cache) {
// Run probing cache before trivial presolve to discover variable implications
const f_t max_time_on_probing = diversity_config.max_time_on_probing;
f_t time_for_probing_cache = std::min(max_time_on_probing, time_limit);
log_presolve_budget("PROBING", probing_features, probing_budget);
f_t time_for_probing_cache = std::min(time_limit, (f_t)global_timer.remaining_time());
timer_t probing_timer{time_for_probing_cache};
[[maybe_unused]] const auto probing_t0 = std::chrono::steady_clock::now();
// this function computes probing cache, finds singletons, substitutions and changes the problem
bool problem_is_infeasible =
compute_probing_cache(ls.constraint_prop.bounds_update, *problem_ptr, probing_timer);
bool problem_is_infeasible = compute_probing_cache(ls.constraint_prop.bounds_update,
*problem_ptr,
probing_timer,
probing_budget.probing_work_limit,
(size_t)probing_budget.probing_step_size);
problem_ptr->handle_ptr->sync_stream();
CUOPT_LOG_DEBUG(
"PRESOLVE_PROBING_WALL wall=%.3f",
std::chrono::duration<double>(std::chrono::steady_clock::now() - probing_t0).count());
if (problem_is_infeasible) { return false; }
}
const bool remap_cache_ids = true;
Expand Down
1 change: 1 addition & 0 deletions cpp/src/mip_heuristics/presolve/multi_probe.cu
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ termination_criterion_t multi_probe_t<i_t, f_t>::bound_update_loop(problem_t<i_t
iter_0 += !skip_0;
iter_1 += !skip_1;
}
if (local_iter_accumulator != nullptr) { *local_iter_accumulator += (double)(iter_0 + iter_1); }
handle_ptr->sync_stream();
if (compute_stats) {
upd_0.init_changed_constraints(handle_ptr);
Expand Down
2 changes: 2 additions & 0 deletions cpp/src/mip_heuristics/presolve/multi_probe.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class multi_probe_t {
bool skip_0;
bool skip_1;
settings_t settings;
// Per-task iteration count; a shared counter would race and make the budget nondeterministic.
double* local_iter_accumulator = nullptr;
bool compute_stats = true;
bool init_changed_constraints = true;
i_t infeas_constraints_count_0 = 0;
Expand Down
Loading
Loading