RINS heuristic - #527
Conversation
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
cpp/src/mip/diversity/lns/rins.cu (1)
156-156: Use consistent integer type forn_to_fix.The calculation mixes
size_t,f_t,int, andi_t, which can cause narrowing conversions and type inconsistency.Apply this diff to use
i_tconsistently:- i_t n_to_fix = std::max((int)(vars_to_fix.size() * rins_ratio), 0); + i_t n_to_fix = std::max<i_t>(static_cast<i_t>(vars_to_fix.size() * rins_ratio), i_t{0});cpp/src/mip/diversity/lns/rins.cuh (1)
98-98: Initialize seed member variable.
seedis declared without initialization. Althoughenable()assigns it a value (line 82 in the .cu file), accessing it beforeenable()would be undefined behavior.Apply this diff to initialize at declaration:
- i_t seed; + i_t seed{0};
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
cpp/src/dual_simplex/logger.hpp(3 hunks)cpp/src/mip/diversity/diversity_manager.cu(3 hunks)cpp/src/mip/diversity/lns/rins.cu(1 hunks)cpp/src/mip/diversity/lns/rins.cuh(1 hunks)cpp/src/mip/diversity/population.cuh(3 hunks)cpp/src/mip/feasibility_jump/fj_cpu.cu(1 hunks)cpp/src/mip/feasibility_jump/fj_cpu.cuh(2 hunks)cpp/src/mip/local_search/local_search.cu(1 hunks)cpp/src/mip/problem/problem.cu(5 hunks)cpp/src/mip/solver.cu(5 hunks)cpp/src/mip/solver_context.cuh(2 hunks)cpp/src/mip/utilities/cpu_worker_thread.cuh(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- cpp/src/mip/diversity/population.cuh
- cpp/src/mip/problem/problem.cu
- cpp/src/mip/feasibility_jump/fj_cpu.cuh
- cpp/src/dual_simplex/logger.hpp
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/src/mip/diversity/lns/rins.cu
🧬 Code graph analysis (3)
cpp/src/mip/solver.cu (1)
cpp/src/dual_simplex/branch_and_bound.hpp (3)
solution(132-132)solution(145-145)solution(217-217)
cpp/src/mip/feasibility_jump/fj_cpu.cu (1)
cpp/src/mip/diversity/lns/rins.cu (2)
run_worker(44-48)run_worker(44-44)
cpp/src/mip/diversity/lns/rins.cu (2)
cpp/src/mip/feasibility_jump/fj_cpu.cu (2)
run_worker(1064-1068)run_worker(1064-1064)cpp/src/mip/solver.cu (8)
solution(74-78)solution(74-74)solution(80-85)solution(80-82)solution(87-90)solution(87-87)dm(92-92)dm(120-120)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: wheel-build-cuopt-sh-client / 13.0.1, 3.10, amd64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.10, arm64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.11, arm64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.11, amd64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.12, arm64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.12, amd64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.13, arm64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.10, amd64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.13, amd64, rockylinux8
- GitHub Check: checks / check-style
🔇 Additional comments (4)
cpp/src/mip/diversity/diversity_manager.cu (1)
420-422: RINS activation timing looks good.Enabling RINS right after skipping the FJ-only fast path keeps the heuristics wiring straightforward and avoids double toggles later.
cpp/src/mip/feasibility_jump/fj_cpu.cu (1)
1058-1086: Thread shutdown path looks solid.The new destructor/on_start/on_terminate plumbing cleanly signals
fj_cpu->haltedand prevents leaks.cpp/src/mip/local_search/local_search.cu (1)
149-152: Good call on using request_termination().Switching the scratch threads to the shared termination helper keeps the lifecycle consistent with the new base class.
cpp/src/mip/utilities/cpu_worker_thread.cuh (1)
27-140: Reusable worker scaffold appreciated.The base class encapsulates the start/stop/join logic cleanly and gives derived classes well-defined hooks.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
cpp/src/mip/presolve/trivial_presolve.cuh (1)
105-113: Resize operation is correct.The
var_flagsresize follows the same pattern as the other vector resizes and is placed appropriately. One minor observation: the resize ordering (bounds → types → var_flags → binary → obj) differs slightly from the iterator declaration order (bounds → types → binary → obj → var_flags), though this doesn't affect correctness.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
cpp/src/mip/presolve/trivial_presolve.cuh(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.13, arm64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.12, arm64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.11, arm64, rockylinux8
🔇 Additional comments (1)
cpp/src/mip/presolve/trivial_presolve.cuh (1)
80-104: LGTM! Consistent variable cleanup pattern.The addition of
var_flagsremoval follows the same pattern as the other variable-related vector cleanups. The formatting alignment also improves readability across all the removal operations.
…re freauent external solution population update checks
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
cpp/src/mip/diversity/diversity_manager.cu (2)
251-260: Consider eliminating the redundant call.The call to
add_external_solutions_to_population()appears twice: once inside the if-block (line 255) and once unconditionally after it (line 258). While the early-exit optimization inadd_external_solutions_to_population()(line 193 in population.cu) makes the second call a no-op when executed twice, removing line 255 would simplify the logic without changing behavior.Apply this diff to eliminate the redundancy:
if (population.preempt_heuristic_solver_.load()) { if (population.current_size() == 0) { population.allocate_solutions(); } - population.add_external_solutions_to_population(); return true; } population.add_external_solutions_to_population(); return false;
422-423: Add idempotency guard torins.enable()or document single-call assumption.The enable() function creates new thread and problem resources without checking if already enabled. While unique_ptr prevents leaks, calling enable() multiple times during an active solve could disrupt the previously spawned thread. Add a guard (
if (enabled) return;at the start) or document that run_solver() is called only once per solver instance.Note: The original concern about resource leaks is unfounded (unique_ptr handles cleanup), but the missing idempotency guard is valid.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
cpp/src/mip/diversity/diversity_manager.cu(4 hunks)cpp/src/mip/diversity/lns/rins.cu(1 hunks)cpp/src/mip/diversity/population.cu(9 hunks)cpp/src/mip/diversity/population.cuh(3 hunks)cpp/src/mip/local_search/local_search.cu(9 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-22T14:25:22.899Z
Learnt from: aliceb-nv
Repo: NVIDIA/cuopt PR: 527
File: cpp/src/mip/diversity/lns/rins.cu:167-175
Timestamp: 2025-10-22T14:25:22.899Z
Learning: In MIP (Mixed Integer Programming) problems in the cuOPT codebase, `n_integer_vars == 0` is impossible by definition—MIP problems must have at least one integer variable. If there are no integer variables, it would be a pure Linear Programming (LP) problem, not a MIP problem.
Applied to files:
cpp/src/mip/diversity/lns/rins.cu
🧬 Code graph analysis (2)
cpp/src/mip/diversity/lns/rins.cu (2)
cpp/src/mip/feasibility_jump/fj_cpu.cu (2)
run_worker(1064-1068)run_worker(1064-1064)cpp/src/mip/solver.cu (8)
solution(74-78)solution(74-74)solution(80-85)solution(80-82)solution(87-90)solution(87-87)dm(92-92)dm(120-120)
cpp/src/mip/local_search/local_search.cu (1)
cpp/src/mip/solver.cu (6)
solution(74-78)solution(74-74)solution(80-85)solution(80-82)solution(87-90)solution(87-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.13, arm64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.12, arm64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.13, amd64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.10, amd64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.12, amd64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.11, amd64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.10, arm64, rockylinux8
- GitHub Check: wheel-build-cuopt-mps-parser / 13.0.1, 3.11, arm64, rockylinux8
- GitHub Check: wheel-build-cuopt-sh-client / 13.0.1, 3.10, amd64, rockylinux8
🔇 Additional comments (22)
cpp/src/mip/diversity/diversity_manager.cu (2)
60-60: LGTM! RINS member initialization follows existing patterns.The initialization of the
rinsmember in the constructor is consistent with other member initializations (e.g.,lson line 59).
738-738: LGTM! Improved error handling for device setting.Wrapping
cudaSetDevicewithRAFT_CUDA_TRYensures proper error handling and matches the error-handling patterns used elsewhere in the codebase.cpp/src/mip/diversity/population.cu (6)
167-175: LGTM! Queue capping prevents CPUFJ solution flooding.The logic correctly identifies and removes the worst (maximum objective) solution from the CPUFJ queue when it exceeds 10 entries, preventing unbounded growth while retaining the best solutions.
192-193: LGTM! Effective optimization to reduce lock contention.The early-exit check using the atomic
solutions_in_external_queue_flag avoids acquiring the write lock when the external queue is empty, improving performance under contention.
215-215: LGTM! Wait time tracking improves observability.The longest wait time tracking provides useful debugging information about queue latency. The logic correctly initializes the timer in
external_solution_t(population.cuh line 200), tracks the maximum across all queued solutions, and logs it when solutions are consumed.Also applies to: 228-228, 258-261
401-401: LGTM! Mutex protects population from concurrent RINS access.The
write_mutexlock guards against race conditions when RINS (running on a separate thread) attempts to read the population while the main thread is modifying it. The use ofrecursive_mutexallows the same thread to acquire the lock multiple times if needed.
571-571: LGTM! Mutex guards concurrent modifications to indices.The lock protects the
indicesvector during quality updates and sorting, preventing data races when RINS reads the population on another thread.
670-671: LGTM! Proper synchronization for population reads and modifications.Both
population_to_vectorandhalve_the_populationcorrectly acquire the write lock to prevent race conditions:
population_to_vectorprotects the read operation with an early exit optimization for empty populationshalve_the_populationprotects extensive modifications (clearing and re-adding solutions)Also applies to: 693-693
cpp/src/mip/diversity/population.cuh (4)
38-38: LGTM! RINS origin properly integrated.The new
RINSenum value and its string mapping follow the existing pattern for solution origins.Also applies to: 45-45
66-66: LGTM! Guards prevent undefined behavior on empty populations.The empty checks prevent:
- Integer underflow in
current_size()whenindices.size()is 0- Out-of-bounds access in
is_feasible()whensolutionsis emptyThese guards are essential for robustness during initialization.
Also applies to: 69-69
196-207: LGTM! Timer enables queue latency tracking.The
timermember tracks how long solutions wait in the external queue. Initializing to infinity serves as a clear sentinel value, and the elapsed time is logged for observability (population.cu line 260).
213-213: LGTM! Synchronization primitives enable thread-safe RINS integration.
write_mutex: A recursive mutex that protects population data structures from concurrent modificationssolutions_in_external_queue_: An atomic flag enabling lock-free early-exit optimization when the external queue is emptyBoth are essential for safe multi-threaded operation with RINS.
Also applies to: 217-217
cpp/src/mip/local_search/local_search.cu (2)
149-151: LGTM! Cleaner termination pattern.The change from
kill_cpu_solver()torequest_termination()aligns with modern thread management patterns and is likely part of the CPU worker thread refactor mentioned in the PR summary.
466-466: LGTM! Frequent integration ensures external solutions are not lost.The strategic placement of
add_external_solutions_to_population()calls:
- Before preemption checks to enable responsive control flow
- Throughout long-running FP/FJ loops to prevent solution loss
- After potential improvements to capture all progress
This addresses the concern raised in past reviews about FP loops running too long without integrating external solutions from CPUFJ, B&B, and now RINS.
Based on learnings
Also applies to: 476-476, 586-586, 620-620, 674-674, 680-680, 705-705, 760-760, 781-781
cpp/src/mip/diversity/lns/rins.cu (8)
27-35: LGTM! Constructor properly initializes all members.The initialization of
time_limitfromsettings.default_time_limit(line 34) addresses the past review concern about usingtime_limitbefore initialization. All members are now properly initialized before use.
38-48: LGTM! Clean lifecycle management.The destructor properly requests termination, and
run_worker()provides a clean thread entry point for the RINS logic.
51-75: LGTM! Well-structured callback gating logic.The callbacks correctly implement the RINS triggering logic:
new_best_incumbent_callback: Updates the improvement trackernode_callback:
- Multi-level gating (enabled check, node cadence, improvement frequency)
- Opportunistic lock-free check (line 67) to reduce contention
- Proper locking for state modification (lines 68-73)
The atomic counters provide sufficient memory ordering guarantees as noted in past reviews.
89-120: LGTM! Thorough setup and validation.The setup logic correctly:
- Sets device affinity on first call (line 91)
- Validates problem dimensions and handle consistency (lines 95-102)
- Safely copies the best feasible solution under lock (line 107)
The assertions provide strong guarantees about preconditions.
127-178: LGTM! Variable fixing logic correctly implements RINS neighborhood.The logic properly:
- Identifies variables where incumbent and LP solution agree (lines 127-137)
- Validates fractional ratio and aborts if too few candidates (lines 138-144)
- Randomly selects subset to fix based on fixrate (lines 146-155)
- Validates all selected variables are integers (lines 157-163)
Minor note: Line 153 mixes
intandi_ttypes, but the defensivemax(., 0)prevents negative values, so impact is minimal.Based on learnings
180-205: LGTM! Proper subproblem construction and preprocessing.The logic correctly:
- Creates fixed-variable subproblem (line 182)
- Constructs solution in fixed space (lines 187-193)
- Optionally adds objective cut to focus search (lines 195-200)
- Applies trivial presolve to tighten the subproblem (lines 202-205)
This follows standard RINS methodology.
257-262: Verify assertion safety: B&B objective vs solution callback.The assertion on line 261 assumes that if
branch_and_bound_solution.objectiveis not NaN, then the solution callback was invoked at least once (makingrins_solution_queue.size() > 0).Please confirm that the B&B solver guarantees the solution callback is invoked for every feasible solution before returning a non-NaN objective. If the callback can be skipped in some edge cases (e.g., initial guess is optimal), the assertion could fail.
207-327: LGTM! Sophisticated parallel search with adaptive parameters.The implementation effectively:
- Runs B&B and FJ in parallel to explore the RINS neighborhood (lines 209-255)
- Uses a "Goldilocks" strategy to adaptively tune fixrate and time_limit based on sub-MIP outcome (lines 263-282)
- Thoroughly processes and validates all solutions before adding to population (lines 294-323)
- Properly tracks statistics for tuning (lines 325-326)
The parallel approach maximizes the value of each RINS run.
akifcorduk
left a comment
There was a problem hiding this comment.
Thanks for the great work!
|
/merge |
This PR adds support for the RINS heuristic, which is run every N nodes to search for feasible solutions in a neighborhood computed from the equal integers between the best integer incumbent and the node's LP optimal.
RINS provides a ~1% improvement over the main branch:
~15.5%vs~14.3-14.7%.This PR also fixes a bug in the CPUFJ external solution bookkeeping.
closes #531
Summary by CodeRabbit
New Features
Bug Fixes