Skip to content

Reduce concurrent LP host memory footprint - #1640

Open
hlinsen wants to merge 4 commits into
NVIDIA:mainfrom
hlinsen:reduce-cpu-mem-footprint
Open

Reduce concurrent LP host memory footprint#1640
hlinsen wants to merge 4 commits into
NVIDIA:mainfrom
hlinsen:reduce-cpu-mem-footprint

Conversation

@hlinsen

@hlinsen hlinsen commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
  • Release parsed MPS/QPS host buffers after GPU population completes.
  • Remove redundant full-size problem, name, and solution copies from concurrent LP solves.
  • Share the CPU problem between Barrier and dual simplex while preserving Barrier’s independent RAFT handle and CUDA stream.
  • Convert only the winning solver result and release losing solver storage before copying the solution back to the GPU.
  • Report both total and available host RAM in version information.

On B200, mcf_2500_100_500 peak memory dropped from 168.12 GiB on main to 143.73 GiB with this change, a 24.39 GiB (14.5%) reduction

Signed-off-by: Hugo Linsenmaier <hlinsenmaier@gmail.com>
@hlinsen hlinsen added this to the 26.08 milestone Jul 30, 2026
@hlinsen
hlinsen requested a review from a team as a code owner July 30, 2026 14:24
@hlinsen hlinsen added the non-breaking Introduces a non-breaking change label Jul 30, 2026
@hlinsen
hlinsen requested review from Kh4ster and chris-maes July 30, 2026 14:25
@hlinsen hlinsen added the improvement Improves an existing functionality label Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The PR updates GPU-backed MPS adoption, propagates RAFT handles through barrier and concurrent LP solving, manages presolved problem lifetime with std::optional, and reports total and available host memory.

Changes

Solver and runtime updates

Layer / File(s) Summary
GPU MPS adoption cleanup
cpp/include/cuopt/mathematical_optimization/optimization_problem_utils.hpp, cpp/tests/linear_programming/unit_tests/solution_interface_test.cu
GPU adoption synchronizes transfers before releasing parsed host storage. A test verifies that the source model is consumed.
Barrier handle propagation
cpp/src/dual_simplex/solve.hpp, cpp/src/dual_simplex/solve.cpp
Barrier solving accepts an explicit RAFT handle and uses it for LP construction and presolve. The existing forwarding overload remains available.
Concurrent solver execution
cpp/src/pdlp/translate.hpp, cpp/src/pdlp/solve.cu
Concurrent barrier and dual-simplex execution use task-specific handles, optionally omit name copies, avoid shape resizing, and convert only the selected result.
Presolve problem lifetime
cpp/src/pdlp/solve.cu
solve_lp stores the original or reduced problem in std::optional before invoking the selected method.
Host memory reporting
cpp/src/utilities/version_info.cpp, cpp/tests/utilities/test_cli.cpp
Version output reports available and total RAM in GiB. CLI assertions check the updated RAM label.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: mlubin, chris-maes, kh4ster

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: reducing host memory usage during concurrent LP solves.
Description check ✅ Passed The description directly explains the memory reductions, solver changes, host RAM reporting, and measured improvement.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/pdlp/solve.cu (1)

1612-1804: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Add a concurrent-solve regression test.

This task-local handle and winner-only conversion path has no supplied coverage. Add a small deterministic Concurrent LP test that validates feasibility/objective and returned primal/dual vector shapes regardless of which solver wins.

As per coding guidelines, “Contributors must add unit tests for code changes”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/pdlp/solve.cu` around lines 1612 - 1804, Add a small deterministic
Concurrent LP regression test covering the concurrent solve flow around
dispatch_concurrent_solvers and the winner-only conversion branches. Configure
the test so either solver may win, then assert the returned solution is
feasible, has the expected objective, and has primal and dual vectors sized to
the problem’s variables and constraints.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/tests/linear_programming/unit_tests/solution_interface_test.cu`:
- Around line 518-534: Extend TEST_F(SolutionInterfaceTest,
gpu_adopt_consumes_mps_data_model) to snapshot representative objective and CSR
payload vectors from model before std::move, then retrieve the corresponding
vectors from problem after adopt_from_mps_data_model and compare their values,
while preserving the existing dimension, nnz, and moved-from assertions.

In `@cpp/tests/utilities/test_cli.cpp`:
- Around line 111-112: Strengthen the RAM assertions in the CLI test by parsing
the numeric values from the “RAM: ...” output, validating the expected GiB
format, and asserting total is positive while available is within 0 through
total. Keep the existing label checks, and use the parsed values rather than
only checking string presence.

---

Outside diff comments:
In `@cpp/src/pdlp/solve.cu`:
- Around line 1612-1804: Add a small deterministic Concurrent LP regression test
covering the concurrent solve flow around dispatch_concurrent_solvers and the
winner-only conversion branches. Configure the test so either solver may win,
then assert the returned solution is feasible, has the expected objective, and
has primal and dual vectors sized to the problem’s variables and constraints.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: fff71895-38a7-4921-9ced-edd9b14c57cb

📥 Commits

Reviewing files that changed from the base of the PR and between 5c08da4 and b010468.

📒 Files selected for processing (8)
  • cpp/include/cuopt/mathematical_optimization/optimization_problem_utils.hpp
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/solve.hpp
  • cpp/src/pdlp/solve.cu
  • cpp/src/pdlp/translate.hpp
  • cpp/src/utilities/version_info.cpp
  • cpp/tests/linear_programming/unit_tests/solution_interface_test.cu
  • cpp/tests/utilities/test_cli.cpp

Comment on lines +518 to +534
TEST_F(SolutionInterfaceTest, gpu_adopt_consumes_mps_data_model)
{
auto model = io::read_mps<int, double>(lp_file_);
const auto n_variables = model.get_n_variables();
const auto n_constraints = model.get_n_constraints();
const auto nnz = model.get_nnz();

raft::handle_t handle;
optimization_problem_t<int, double> problem(&handle);
adopt_from_mps_data_model(&problem, std::move(model));

EXPECT_EQ(model.get_n_variables(), 0);
EXPECT_EQ(model.get_n_constraints(), 0);
EXPECT_EQ(problem.get_n_variables(), n_variables);
EXPECT_EQ(problem.get_n_constraints(), n_constraints);
EXPECT_EQ(problem.get_nnz(), nnz);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert copied payload values as well.

This passes even if objective or CSR values are corrupted after model is cleared. Snapshot representative vectors before the move, then copy them back from problem and compare them after adoption.

As per path instructions, C++ tests should focus on “Numerical correctness validation (not just ‘runs without error’)”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/tests/linear_programming/unit_tests/solution_interface_test.cu` around
lines 518 - 534, Extend TEST_F(SolutionInterfaceTest,
gpu_adopt_consumes_mps_data_model) to snapshot representative objective and CSR
payload vectors from model before std::move, then retrieve the corresponding
vectors from problem after adopt_from_mps_data_model and compare their values,
while preserving the existing dimension, nnz, and moved-from assertions.

Source: Path instructions

Comment thread cpp/tests/utilities/test_cli.cpp Outdated
Comment on lines +111 to +112
EXPECT_TRUE(output.find("GiB total") != std::string::npos);
EXPECT_TRUE(output.find("GiB available") != std::string::npos);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the reported RAM values, not only the labels.

These checks pass even if the parser emits 0.00 GiB values. Parse the RAM: ... fields and verify the format plus sane bounds such as total > 0 and 0 <= available <= total.

As per path instructions, C++ utility tests should validate numerical correctness, not only that the command runs.

Suggested assertion shape
-  EXPECT_TRUE(output.find("GiB total") != std::string::npos);
-  EXPECT_TRUE(output.find("GiB available") != std::string::npos);
+  std::smatch match;
+  ASSERT_TRUE(std::regex_search(
+    output,
+    match,
+    std::regex(R"(RAM: ([0-9]+\.[0-9]{2}) GiB total, ([0-9]+\.[0-9]{2}) GiB available)")));
+  const auto total     = std::stod(match[1].str());
+  const auto available = std::stod(match[2].str());
+  EXPECT_GT(total, 0.0);
+  EXPECT_GE(available, 0.0);
+  EXPECT_LE(available, total);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
EXPECT_TRUE(output.find("GiB total") != std::string::npos);
EXPECT_TRUE(output.find("GiB available") != std::string::npos);
std::smatch match;
ASSERT_TRUE(std::regex_search(
output,
match,
std::regex(R"(RAM: ([0-9]+\.[0-9]{2}) GiB total, ([0-9]+\.[0-9]{2}) GiB available)")));
const auto total = std::stod(match[1].str());
const auto available = std::stod(match[2].str());
EXPECT_GT(total, 0.0);
EXPECT_GE(available, 0.0);
EXPECT_LE(available, total);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/tests/utilities/test_cli.cpp` around lines 111 - 112, Strengthen the RAM
assertions in the CLI test by parsing the numeric values from the “RAM: ...”
output, validating the expected GiB format, and asserting total is positive
while available is within 0 through total. Keep the existing label checks, and
use the parsed values rather than only checking string presence.

Source: Path instructions

Comment thread cpp/src/utilities/version_info.cpp Outdated
f_t start_time,
lp_solution_t<i_t, f_t>& solution)
lp_solution_t<i_t, f_t>& solution,
const raft::handle_t* handle_ptr)

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.

Why does passing in the handle_ptr (rather than getting it from the user_problem) help reduce memory?

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.

This is refactoring that allows us to get rid of this copy:

  auto barrier_problem       = dual_simplex_problem;
  barrier_problem.handle_ptr = barrier_handle_ptr.get();

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.

where is that in the code? I see barrier_lp but that is after column scaling?

Comment thread cpp/src/dual_simplex/solve.cpp
Comment thread cpp/src/pdlp/solve.cu
Comment thread cpp/src/pdlp/solve.cu Outdated
Comment thread cpp/src/pdlp/solve.cu Outdated
return;
}
populate_from_mps_data_model(problem, data_model);
if (auto* gpu_problem = dynamic_cast<optimization_problem_t<i_t, f_t>*>(problem)) {

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 assume the = is intentional and that you didn't want == but you might want move that out of the if to be clear.

auto* gpu_problem = dynamic_cast<optimization_problem_t<i_t, f_t>*>(problem);
if (gpu_problem) {
 ...
}

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

CI Test Summary

✅ All 31 test job(s) passed.

Signed-off-by: Hugo Linsenmaier <hlinsenmaier@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/pdlp/solve.cu (1)

526-538: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Size the Barrier result before uncrush.

solution starts as (0, 0), but the concurrent external-handle path skips the only resize. The Barrier solve then writes through uncrush_primal_solution/uncrush_dual_solution; unlike solve_linear_program at cpp/src/dual_simplex/solve.cpp Line 702, this path does not size the output first. Resize immediately before uncrushing in the Barrier implementation, and add a GoogleTest regression for this path.

As per coding guidelines, contributors must add unit tests for code changes using GoogleTest examples under cpp/src/tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/pdlp/solve.cu` around lines 526 - 538, Resize the Barrier solver’s
output solution immediately before uncrushing, covering the concurrent
external-handle path where the existing handle_ptr == nullptr branch does not
run; update the relevant Barrier implementation around
uncrush_primal_solution/uncrush_dual_solution while preserving existing sizing
behavior. Add a GoogleTest regression under cpp/src/tests that exercises this
external-handle path and verifies the result is sized and completes
successfully.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cpp/src/pdlp/solve.cu`:
- Around line 526-538: Resize the Barrier solver’s output solution immediately
before uncrushing, covering the concurrent external-handle path where the
existing handle_ptr == nullptr branch does not run; update the relevant Barrier
implementation around uncrush_primal_solution/uncrush_dual_solution while
preserving existing sizing behavior. Add a GoogleTest regression under
cpp/src/tests that exercises this external-handle path and verifies the result
is sized and completes successfully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: cc938589-351b-425e-bf36-b88c3f7813ed

📥 Commits

Reviewing files that changed from the base of the PR and between b010468 and 3747823.

📒 Files selected for processing (3)
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/pdlp/solve.cu
  • cpp/src/utilities/version_info.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • cpp/src/utilities/version_info.cpp

Signed-off-by: Hugo Linsenmaier <hlinsenmaier@gmail.com>
Comment thread cpp/src/pdlp/solve.cu Outdated
Signed-off-by: Hugo Linsenmaier <hlinsenmaier@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/pdlp/solve.cu (1)

1604-1609: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Restore device 1 before destroying barrier_handle_ptr.

barrier_handle_ptr creates CUDA library resources on device 1, but reset() runs on the dispatching thread after the device scope ends. Wrap the reset in raft::device_setter(1) for multi-GPU mode and add a regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/pdlp/solve.cu` around lines 1604 - 1609, Update the cleanup following
run_barrier_thread so barrier_handle_ptr.reset() executes within
raft::device_setter(1) when multi-GPU mode is active, ensuring device 1 is
restored before destroying its CUDA resources. Preserve the existing cleanup
behavior for non-multi-GPU mode and add a regression test covering destruction
after the device scope ends.

Source: MCP tools

🧹 Nitpick comments (1)
cpp/src/pdlp/solve.cu (1)

1693-1718: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add regression coverage for concurrent result selection.

Existing tests cover the dual-simplex winner only. Add deterministic GoogleTest coverage for the Barrier winner, PDLP winner, ConcurrentLimit fallback, and inside_mip with Barrier disabled.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/pdlp/solve.cu` around lines 1693 - 1718, Add deterministic GoogleTest
regression cases for concurrent result selection, extending the existing
dual-simplex winner coverage to verify Barrier selection, PDLP selection,
ConcurrentLimit fallback, and inside_mip behavior when Barrier is disabled.
Reuse the existing concurrent-solve test fixtures and assert each case returns
the expected solver result without introducing unrelated test changes.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cpp/src/pdlp/solve.cu`:
- Around line 1604-1609: Update the cleanup following run_barrier_thread so
barrier_handle_ptr.reset() executes within raft::device_setter(1) when multi-GPU
mode is active, ensuring device 1 is restored before destroying its CUDA
resources. Preserve the existing cleanup behavior for non-multi-GPU mode and add
a regression test covering destruction after the device scope ends.

---

Nitpick comments:
In `@cpp/src/pdlp/solve.cu`:
- Around line 1693-1718: Add deterministic GoogleTest regression cases for
concurrent result selection, extending the existing dual-simplex winner coverage
to verify Barrier selection, PDLP selection, ConcurrentLimit fallback, and
inside_mip behavior when Barrier is disabled. Reuse the existing
concurrent-solve test fixtures and assert each case returns the expected solver
result without introducing unrelated test changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4ea47c27-05ce-477b-9f2b-5715a89772b5

📥 Commits

Reviewing files that changed from the base of the PR and between e2834e0 and 3f20c16.

📒 Files selected for processing (1)
  • cpp/src/pdlp/solve.cu

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improves an existing functionality non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants