Sparse sampling performance benchmarks and parallelism - #196
Merged
Conversation
rileyjmurray
commented
Aug 23, 2026
rileyjmurray
left a comment
Contributor
Author
There was a problem hiding this comment.
Reviewing this PR has me thinking about what the safe_int_product helper function buys us.
rileyjmurray
commented
Aug 23, 2026
rileyjmurray
commented
Aug 23, 2026
rileyjmurray
commented
Aug 23, 2026
rileyjmurray
left a comment
Contributor
Author
There was a problem hiding this comment.
Leaving a comment to confirm that I've reviewed all tests.
…ty submatrices. Update offset_and_ldim to always return ldim >= 1, for compatibility with the BLAS standard. Plenty of new tests.
rileyjmurray
commented
Aug 23, 2026
rileyjmurray
left a comment
Contributor
Author
There was a problem hiding this comment.
Leaving tons of comments to prove that I actually reviewed each file. Some comments request changes.
rileyjmurray
marked this pull request as ready for review
August 24, 2026 05:04
mmelnich
added a commit
that referenced
this pull request
Aug 24, 2026
Post-#196, left_spmm passes the caller's beta straight to MKL (which fuses it) and applies lascl only before the hand kernels; the old pre-scale-then-beta=1 pattern was removed there. spsymm now follows the same contract in both Cases: MKL and mkl_spgemm_to_dense receive beta (in Case D this also restores the alpha=1/beta=0 direct-write fast path, which the pre-applied beta=1 was defeating), and the dispatcher's lascl moved to just before the pure-accumulator fallbacks. Also per the #196 contract: empty products (a zero dimension, alpha == 0, or a structurally empty operand) now leave beta*Y at the dispatcher, since MKL rejects some valid empty sparse matrices at handle creation; without the guards, spsymm threw on inputs left_spmm handles gracefully. The public dispatch docstring keeps the contract; MKL routing mechanics moved to a plain comment and DevNotes.
mmelnich
added a commit
that referenced
this pull request
Aug 24, 2026
…c register Window bounds now go through validate_submat_dims (the overflow-safe validator #196 centralized; the addition-form checks it replaced could overflow and accept negative dims), and the manual layout-flip ternaries go through flipped_layout, including one site where a local variable shadowed that helper's name. The spsymm kernels and the *_to_dense templates use the SignedInteger concept like every neighboring kernel. Documentation register fixes: internal helpers (the fallback kernels, lsksy3/rsksy3/lsksys/rsksys) drop their Doxygen-visible /// blocks for plain comments; the public spsymm convenience wrapper's math renders through the \math alias; all references to a non-repository planning file are gone (the four-case design lives in sparse_data/DevNotes.md); require_symmetric's docstring describes the current legacy-overload relationship instead of a phase history. Symmetric<SpMat> and as_symmetric gain their own directives in the web API reference. sksy.hh includes <vector> and <type_traits> directly.
mmelnich
added a commit
that referenced
this pull request
Aug 24, 2026
…se D pairings Test-case names follow the snake_case behavior-phrase convention (STYLE_GUIDE 'Other files'). New coverage: empty sparse operands leave beta*Y in both spsymm overloads (the #196 contract the previous commit adopted); the three previously untested sparse-times-sparse format pairings (CSR-COO, COO-CSC, CSC-COO) and a non-CSR side=Right cell, completing the 3x3 grid the docs claim. The remaining std::mt19937 use carries its stated reason per CONTRIBUTING.
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.
The following is written by a robot. PR changes were reviewed by me (Riley) extensively over the course of several rounds. The agent-driven work was by GPT 5.6 Sol. I ran adversarial review through Fable 5 at high effort.
This PR began as an effort to bring sparse sketching-operator generation in line with dense generation: SASOs and LASOs should use the ambient OpenMP configuration without making their samples depend on the number of threads. It now resolves #139 and includes a broader cleanup of sampling benchmarks, dimension validation, empty-submatrix behavior, and sparse matrix multiplication edge cases.
Sampling semantics
Sparse sampling assigns randomness to logical major-axis vectors rather than to physical threads. If the first requested vector starts at counter
c, then vectoristarts atc + i * vec_nnzand writes to a deterministic lane. The returned state advances bynum_major_axis_vectors * vec_nnz, independent of the number of active threads or the OpenMP schedule.Changing the OpenMP thread count does not change
vals,rows,cols,nnz, or the returnedRNGState. This applies to full and submatrix requests, tall and wide operators, and both choices of major axis. A build without OpenMP retains the serial behavior.No execution-policy or thread-count argument was added. RandBLAS consults the ambient OpenMP configuration and applies an internal policy that may select fewer threads when there is too little work to amortize parallel startup or per-thread workspace.
Main changes
Parallel SASO sampling
repeated_fisher_yatesnow processes major-axis vectors in parallel. Thevec_nnz == 1path reduces to batched uniform sampling and does not allocate permutation workspace. Forvec_nnz > 1, every active thread owns a restored permutation of lengthdim_majorand a pivot array of lengthvec_nnz. This keeps the sampling work atO(num_major_axis_vectors * vec_nnz), but increases permutation workspace fromO(dim_major)toO(T * dim_major)forTactive threads.The same OpenMP implementation handles both the single-threaded and multithreaded cases. The sparse-operator path asks
repeated_fisher_yatesto sort each completed major-axis vector before its sampling lane ends, while the public support-only function preserves the original unsorted order. This removes a separate sorting pass without changing its output.Parallel LASO sampling
LASO vectors are sampled with replacement into fixed-width lanes. Each thread uses private hash maps to merge duplicate locations, sorts the surviving entries within its vector, and records the survivor count. A serial pass then packs those lanes in increasing major-axis-vector order. Every destination is at or before its source, so the pack is safe in place and preserves canonical COO order without allocating another
O(nnz)buffer.Allocation failures during parallel merging are captured inside the OpenMP region and rethrown after the team exits. I deliberately left a parallel prefix-sum and scatter out of this PR. The serial pack is simpler, preserves the existing representation, and should only be replaced if end-to-end measurements show that it is material.
Benchmark and testing infrastructure
This PR adds
saso_sampling_performance, following the conventions insketch_general_performance.cc. It comparesrepeated_fisher_yateswith five plausible C++ implementations:std::sampleover an iota-filled vector;npopulation;std::shufflefollowed by the firstkentries;std::unordered_set.The natural comparison uses
std::mt19937_64for those implementations and Philox for RandBLAS. A second comparison adapts Philox to the standard URBG interface. That adapter uses one 64-bit result per counter by default, matching RandBLAS' sampling stream, and can optionally consume both 64-bit results from each Philox counter.The baseline samplers now live in
RandBLAS/testing/samplers.hh; future discrete-uniform baseline samplers have a common home there. Shared benchmark parsing and OpenMP-setting helpers live inRandBLAS/testing/benchmarking.hh. Thestd::samplebaseline uses an ordinary iota-filled vector rather than a ranges sentinel pair, which keeps the code portable to MSVC.Comparison mode forces RandBLAS to one OpenMP thread because each competing implementation owns one serial engine. Scaling mode times RandBLAS alone and reports both requested and effective thread counts.
sketch_general_performancealso has a separate sparse-sampling scaling section alongside its existing sparse-operator application measurements.Correctness and portability hardening
The implementation now checks products that determine matrix sizes, output capacities, workspace lengths, and pointer offsets before using them. Those checks cover dense and sparse operator generation. They are intentionally about addressable matrix data; wrap-around of the extended-width unsigned CBRNG counter remains accepted behavior.
Submatrix bounds checking is centralized in
validate_submat_dimsand reused by dense sketching, sparse sketching, and sparse matrix operations. Boundary windows with a zero dimension are valid.offset_and_ldimstill reports a leading dimension of at least one for such matrices, as required by BLAS conventions, and sparse submatrix materialization can now return an empty COO matrix.randblas_get_thread_numandrandblas_get_num_threadsprovide the OpenMP queries RandBLAS needs while returning serial answers when OpenMP is absent. This lets the sampling and sparse-matrix kernels use one code path in both builds.A small layout-aware
lasclutility now centralizes in-place dense matrix scaling. This helper was adapted from Max Melnichenko's work on theadd-symm-kernelsbranch. The MKL sparse wrapper uses it when an empty contraction dimension or an all-zero sparse operand leavesbeta * C; the RandBLAS sparse dispatcher uses the same operation before its own accumulation kernels. This avoids depending on MKL's treatment of sparse empty-matrix edge cases.Performance
I ran the required SASO sweep on an Apple M3 MacBook Air with Clang 19.1.3 and OpenMP 5.1. The problem size was
dim_major = 2000,num_major_axis_vectors = 100000, with 10 trials for eachvec_nnz.vec_nnzEvery row passed the exact output/state check. I did not observe a one-thread regression relative to the pre-implementation baseline; the final medians were lower for all five values of
vec_nnz.I also ran the sampling section of
sketch_general_performancefor both major-axis orientations atvec_nnzin{1, 4, 16}. The 8-thread minimum-time speedups ranged from 2.12x to 4.19x, and every configuration passed its exact output/state check.Tests and documentation
The tests compare exact sparse buffers and returned states across thread counts. They cover the specialized and general Fisher-Yates paths; full, strict-submatrix, and empty-boundary requests; tall and wide SASOs and LASOs; nondefault value and index types; thread-policy boundaries; overflow checks; and validation errors that must be reported before parallel execution.
Separate tests exercise the baseline samplers, their Philox adapter, the benchmark parser, dense and sparse submatrix validation, BLAS-compatible leading dimensions, and the sparse multiplication cases that leave
beta * C. The more specialized tests now include comments explaining the behavior under test and how the chosen setup exposes it.The documentation describes logical-vector counter partitioning, SASO workspace costs, LASO packing, accepted CBRNG wrap-around, and the distinction between random-stream keys and physical thread assignment. The testing developer notes document the shared sampler and benchmark helpers. The style guide was also clarified while bringing the changed files into compliance.
Size of the change
The table below groups the
origin/main...HEADdiff into nonoverlapping directories. The full PR changes 34 files, with+3,057 / -297lines.RandBLAS/(top level)RandBLAS/sparse_data/RandBLAS/testing/examples/(top level)examples/simple-kernel-benchmarks/examples/sparse-low-rank-approx/test/(top level)test/basic_rng/test/datastructures/test/linops/test/meta/rtd/