Repository navigation
Make block_left_multiply_fill_values linear in the multiply-adds - #127
Open
Transurgeon wants to merge 3 commits into
Open
Transurgeon wants to merge 3 commits into
Transurgeon wants to merge 3 commits into
Conversation
The value fill took a merge-based sparse dot of a whole row of A for every entry of C, so its cost was O(nnz(C) * row length): quadratic for a long dense row. A cvxpy objective c @ x with c dense of length n (CSR path) took 3.2 s at n = 1e5, 49 s at 4e5, and an extrapolated 8.5 h at the cvxpy benchmark suite's n = 1e7, against 7 s for cvxpy's own backends. The symbolic counterpart was already made output-driven in c549c64; the fill was not. The fill is now Gustavson's numeric phase through A's CSC mirror: per block of a column of C, scatter A[:, c] * J[c, j] into an accumulator over A's rows, then gather. sparse_matrix reuses its version-guarded CSC cache and keeps the accumulator, so a constant A converts once. Terms are added in the same order as the merge-based dot, so values are bit-identical; the new test checks that against the old kernel on random shapes, densities and block counts, including the dense-row case. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
csr_to_csc_alloc builds the structure only, so the test compared both kernels on uninitialised J values (Valgrind: conditional jump on uninitialised value in the memcmp). Fill them with csr_to_csc_fill_values. The test now fails on a 1e-15 relative perturbation of the kernel. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drop the copy of the old merge kernel and the random comparison. Three small cases in the style of the existing tests, with the expected values worked out in comments: - two blocks, with an entry of C that sums several products; - a dense row (the c @ x gradient that made the merge kernel quadratic); - the sparse_matrix fill, which keeps A's CSC mirror: refilled with new J values, then with new A values after matrix_values_changed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Transurgeon
commented
Oct 6, 2026
Comment on lines
+40
to
+41
| /* Accumulator (csr->m doubles) of block_left_mult_values; lazily allocated. */ | ||
| double *bl_acc; |
Member
Author
There was a problem hiding this comment.
is this necessary to add to every sparse matrix? Also where is it lazily allocated?
Transurgeon
commented
Oct 6, 2026
Comment on lines
+274
to
+275
| /* One-off convenience form: builds A's CSC mirror per call. Callers that | ||
| fill repeatedly (sparse_matrix) keep the mirror and the accumulator. */ |
Member
Author
There was a problem hiding this comment.
what do you mean by one-off convenience form?
Transurgeon
commented
Oct 6, 2026
| CSC_matrix *A_csc = csr_to_csc_alloc(A, iwork); | ||
| csr_to_csc_fill_values(A, A_csc, iwork); | ||
| double *acc = (double *) sp_malloc((A->m > 0 ? A->m : 1) * sizeof(double)); | ||
| block_left_multiply_fill_values_csc(A_csc, J, C, acc); |
Member
Author
There was a problem hiding this comment.
can't we just replace the original content of block_left_multiply_fill_values? instead of creating a new method?
Member
Author
|
@dance858 I think we should focus on this PR first. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
block_left_multiply_fill_valuescomputed each entry ofCas a merge-based sparse dot of a whole row of A with the matching block of a column ofJ. The cost isO(nnz(C) · row length), which is quadratic for a long dense row.c @ xwithcdense of length n, on the CSR path (cvxpy,DIFFENGINEwith constants forced to CSR), one first canonicalization on an Apple M2:permuted_dense)The symbolic counterpart (
block_left_multiply_fill_sparsity) was already made output-driven in c549c64. The value fill was not.Fix
The fill is now Gustavson's numeric phase through A's CSC mirror. For each block of a column of
C, it scattersA[:, c] * J[c, j]into an accumulator over A's rows, then gathers into the block's entries ofC. The cost is now the number of multiply-adds.block_left_multiply_fill_values_csc(A_csc, J, C, acc)is the new kernel.accholdsA_csc->mdoubles and needs no initialization.block_left_multiply_fill_values(A, J, C)keeps its signature. It builds the CSnvenience for one-off callers.sparse_matrix'sblock_left_mult_valuesreuses the existing version-guardedcsc_cache(throughrefresh_csc_values) and a lazily allocated accumulator (bl_acc). A constantAtherefore converts once. Sparseleft_matmulmatrices are always constant, since sparse parameters are rejected, but the version guard would reconvert if that changed.Each row's terms are still added in increasing column order of A, the same order avalues are bit-identical.
Tests
tests/utils/test_linalg_sparse_matmuls.h:test_block_left_multiply_values_two_blocks: two blocks, with an entry ofCthat sums several products.test_block_left_multiply_values_dense_row: thec @ xgradient that made the merge kernel quadratic.test_sparse_matrix_block_left_mult_values_refill: thesparse_matrixpath w refilled with newJvalues, then with newAvalues aftermatrix_values_changed.all_tests: 464/464 pass, also under-fsanitize=address,undefined. A 1e-6 perturbation of the kernel output makes the suite fail.(P, c, A, b)match the previous engine (64f7432), both with default routing and with every constant forced to CSR. The exception is SimpleLPBenchmark on the CSR path, which previously never finished; it now matches the dense path's output exactly.clang-format -style=file: no replacements on the changed files.🤖 Generated with Claude Code