Python api performance improvement - #1615
Conversation
Reduce model refresh and solution-population overhead independently of solver session persistence. Signed-off-by: Ishika Roy <iroy@ipp1-3302.aselab.nvidia.com>
Reuse cached model structures for value-only changes and avoid redundant solution invalidation across batched updates.
📝 WalkthroughWalkthroughChangesCache-aware linear programming and benchmark
Estimated code review effort: 4 (Complex) | ~60 minutes Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
script_perf_eval.py (1)
210-218: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAvoid materializing the dense diagonal matrix.
np.diag(info["D_diag"])allocates an n×n dense array (n≈5000 ⇒ ~200 MB) on every objective evaluation just to computex @ D @ x. Use the elementwise form.♻️ Elementwise diagonal quadratic term
y = info["F"].T @ x_np z = np.abs(x_np - info["x0"]) - d_matrix = np.diag(info["D_diag"]) return ( -info["mu"] @ x_np + info["gamma"] - * (x_np @ d_matrix @ x_np + y @ info["Omega"] @ y) + * (x_np @ (info["D_diag"] * x_np) + y @ info["Omega"] @ y) + info["tc_rate"] * np.sum(z) )🤖 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 `@script_perf_eval.py` around lines 210 - 218, Update the objective calculation around the `d_matrix` expression to avoid constructing `np.diag(info["D_diag"])`; compute the diagonal quadratic term elementwise as the sum of `info["D_diag"]` multiplied by `x_np` squared, while preserving the existing objective value and remaining terms.
🤖 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 `@python/cuopt/cuopt/linear_programming/problem.py`:
- Around line 2450-2457: Update Problem.solve to accept the session argument
used by script_perf_eval.py and pass it through to solver.Solve, preserving
existing settings behavior; alternatively, remove the session keyword from that
caller so the signatures remain aligned.
In `@script_perf_eval.py`:
- Line 778: Update the objective-record comparison loop over
baseline["objective_records"] and session["objective_records"] to use strict zip
semantics, ensuring differing record counts raise an error instead of silently
truncating. Preserve the existing per-record comparison logic.
- Around line 110-130: Update _capture_solver_output to drain the stderr pipe
concurrently while the yielded solve runs, using a reader thread or equivalent
that continuously consumes and stores output. Ensure cleanup restores fd 2,
waits for the reader to finish, closes descriptors, and preserves captured
output forwarding to sys.stderr.
- Around line 385-386: Initialize prob._session before the session-handling
logic in the relevant baseline/cold-session flow, ensuring it exists before
session_after_cold or any other read. Preserve the existing use_session behavior
that clears the session when enabled, and use a safe default of None for
uninitialized sessions.
- Around line 481-482: Update the Problem.solve invocation in the
_capture_solver_output block to pass only settings, removing the conditional
session keyword argument. Preserve the surrounding solver-output capture and
solution assignment behavior.
---
Nitpick comments:
In `@script_perf_eval.py`:
- Around line 210-218: Update the objective calculation around the `d_matrix`
expression to avoid constructing `np.diag(info["D_diag"])`; compute the diagonal
quadratic term elementwise as the sum of `info["D_diag"]` multiplied by `x_np`
squared, while preserving the existing objective value and remaining terms.
🪄 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: c3b79d8a-3433-4ba3-bd5c-d4c71b4790c7
📒 Files selected for processing (2)
python/cuopt/cuopt/linear_programming/problem.pyscript_perf_eval.py
|
/ok to test 6834882 |
|
/ok to test a2ac39a |
|
@coderabiitai review |
CI Test Summary8 failed · 1 passed · 4 skipped
|
| self.var_type = None | ||
| self._index_to_var_cache = None | ||
| self._constraint_csr_scipy = None | ||
| self._constraint_index_to_csr_row = None |
There was a problem hiding this comment.
Consider refactoring so that the constraint index is always the same as the csr row index?
| count=m, | ||
| ) | ||
| self.row_sense = np.asarray( | ||
| [constr.Sense for constr in linear_constrs], dtype="S1" |
There was a problem hiding this comment.
Why np.fromiter above and np.asarray here?
| [constr.Sense for constr in linear_constrs], dtype="S1" | ||
| ) | ||
| self.row_names = [ | ||
| constr.ConstraintName or "R" + str(constr.index) |
There was a problem hiding this comment.
Do we need to generate row names if they're not provided?
| # otherwise leave Slack as NaN (same outcome as unset Values). | ||
| slacks = None | ||
| if len(primal_sol) == len(self.vars): | ||
| A = self._constraint_csr_scipy_matrix() |
There was a problem hiding this comment.
This looks like the only place in the code where _constraint_csr_scipy_matrix is used. Are we keeping a duplicate copy of the matrix in memory just to compute slack values? We should be sensitive to changes in peak memory usage. An extra copy of the constraint matrix isn't cheap.
Description
The 100ms shave off helps in consecutive solves of portfolio problems which solve in 300-500ms.
Issue
Checklist