Skip to content

AISPARSE-687: clamp grid.x in the dense conversion paths and add grid-stride loops - #11830

Merged
kliegeois merged 16 commits into
developfrom
users/kliegeois/aisparse-687-dense-conv-clamp
Oct 8, 2026
Merged

kliegeois merged 16 commits into
developfrom
users/kliegeois/aisparse-687-dense-conv-clamp

Conversation

@kliegeois

Copy link
Copy Markdown
Contributor

JIRA ID: AISPARSE-687

What

Clamps grid.x against handle->properties.maxGridSize[0] in the dense conversion launches and adds a grid-stride loop to every kernel behind them, so a clamped grid still covers the whole matrix.

Library changes, all under library/src/conversion/:

Launch site Kernel Grid-stride added over
rocsparse_dense2bell.cpp:66 dense2bell_nnz_kernel block rows
rocsparse_dense2bell.cpp:169 dense2bell_fill_kernel block rows
rocsparse_dense2csx.hpp:62,:83 dense2csr_kernel rows
rocsparse_dense2csx.hpp:110,:131 dense2csc_kernel columns
rocsparse_csx2dense.hpp:60,:84 csr2dense_kernel rows
rocsparse_csx2dense.hpp:114,:138 csc2dense_kernel columns

Both dense2bell kernels now take mb so they know the block-row bound; dense2bell_fill_kernel resets its shared s_slot accumulator per block-row and barriers before the reset so the stride loop is reentrant.

rocsparse_csx2dense.hpp also computed the block count into a rocsparse_int local before handing it to dim3, truncating one step earlier than the other files on a 64-bit index instantiation. That intermediate is gone, so the width is int64_t end to end.

All new ceiling divisions use the overflow-safe (count - 1) / block_size + 1 form.

dense2bell.h:121-128 (dense2bell_max_nnzb_part1_kernel) already had a fixed grid with a correct stride and is untouched. dense2bell_nnz_kernel already looped over columns internally; only its block-row dimension needed work.

Why

Only dim3-narrowing plus a missing loop, so an undersized grid does not fault — it silently leaves trailing rows or columns unconverted.

The two halves of this are very far apart in reachability, and the P2 rests entirely on the BELL half:

  • dense2bell is reachable on current hardware. It launches one thread block per block-row, so the divisor is 1 and the grid overflows for mb above 2,147,483,647. With an ELL block size of 1 that is m above 2,147,483,648, roughly 17 GB of row pointers, which fits on a single MI300X several times over. After this change the block-row count is bounded only by int64_t.
  • dense2csx and csx2dense are not reachable on current hardware. They tile 16 rows or columns per block, so they only overflow above m (or n) of 34,359,738,368 — about 275 GB of row pointers. Fixed here for consistency and to keep the whole family auditable, not because it is currently exploitable.

The reachable entry points are rocsparse_dense_to_sparse and rocsparse_sparse_to_dense with rocsparse_indextype_i64 descriptors, and for BELL rocsparse_dense_to_sparse regardless of the build, since mat_A->rows is int64_t there. The legacy rocsparse_Xdense2csr / rocsparse_Xcsr2dense entry points take rocsparse_int and cannot reach either threshold.

rocsparse_coo2dense.cpp

JIRA flagged bringing the non-AoS coo2dense in line with its already-clamped AoS sibling as a reasonable addition if cheap. It was, so it is included: the launch now clamps and coo2dense_kernel grid-strides, mirroring coo2dense_aos_kernel.

While doing that, both coo2dense kernels now compute the stride in int64_t. The AoS kernel used const auto NUM_THREADS = hipGridDim_x * BLOCKSIZE, which is uint32_t * uint32_t: at nnz >= 2^32 the product wraps to 0 and the grid-stride loop never advances. That is a hang, not a wrong answer, and it is reachable on a 192 GB part, so it is fixed here rather than left in the file being used as the reference pattern.

Testing

New dense_to_sparse_bell_extra nightly test (testing_dense_to_sparse_bell_extra_687), registered in test_dense_to_sparse_bell.yaml with device_memory_gb: 44. It drives rocsparse_dense_to_sparse with ell_block_size = 1 and m = maxGridSize[0] + 1, so the block-row count exceeds the grid limit by exactly one block-row.

The test asserts values, not just launch success. The dense matrix is zero apart from its very last row, so the converted matrix has one occupied ELL slot, and that slot lives in the trailing block-row that an unclamped or non-striding grid does not reach. It checks ell_cols, and the column index and value of both the leading (padded) and the trailing (occupied) block-row. The column-index array is pre-filled with a poison pattern so an unvisited block-row cannot be mistaken for either an occupied or a padded slot.

On a 15 GB gfx1201 the test auto-skips with SKIPPING TEST: Insufficient memory / Required device: 44 GB (available: 15 GB), which is the suite's memory guard behaving correctly.

Regression coverage for the ordinary-sized path, on gfx1201:

  • 7239 tests pass across *dense_to_sparse*, *sparse_to_dense*, *dense2csr*, *dense2csc*, *csr2dense*, *csc2dense*, *coo2dense*, *dense2coo*, *ell2dense*, *prune_dense2csr*, *sparse_to_sparse*, *spmm_bell*.
  • To exercise the new stride loops on hardware that cannot reach the real thresholds, every clamp was temporarily replaced with a literal 3, forcing all six kernels to cover their matrices with a 3-block grid. 5696 tests from the same filter pass in that configuration, including value comparisons against the host references. Reverted before commit.

The dense conversion launches narrow a 64-bit dimension into dim3 without
clamping against handle->properties.maxGridSize[0], and none of the kernels
loops, so an undersized grid silently leaves trailing rows or columns
unconverted.

dense2bell is the reachable case: it launches one block per block-row, so the
divisor is one and the grid overflows for mb above 2147483647.

Clamp every grid against maxGridSize[0], add a grid-stride loop over block
rows, rows or columns to dense2bell_nnz_kernel, dense2bell_fill_kernel,
dense2csr_kernel, dense2csc_kernel, csr2dense_kernel and csc2dense_kernel,
and drop the intermediate rocsparse_int in rocsparse_csx2dense.hpp so the
width is preserved end to end.

Bring rocsparse_coo2dense.cpp in line with its already clamped AoS sibling,
and compute the grid-stride in int64_t in both coo2dense kernels.

JIRA ID: AISPARSE-687

Co-authored-by: Cursor <cursoragent@cursor.com>
@therock-pr-bot

therock-pr-bot Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

✅ All Checks Passed — Ready for Review

Check Status Details
📝 PR Description ✅ Pass —
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ✅ Pass —
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —
🤖 therock-pr-bot ✅ Pass —

🎉 All checks passed! This PR is ready for review.

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

@therock-pr-bot

therock-pr-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

🎉 All checks passed! This PR is ready for review.

@kliegeois kliegeois changed the title Clamp grid.x in the dense conversion paths and add grid-stride loops AISPARSE-687: clamp grid.x in the dense conversion paths and add grid-stride loops Sep 8, 2026
@codecov

codecov Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.26882% with 23 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...rse/library/src/conversion/rocsparse_csx2dense.hpp 50.00% 10 Missing ⚠️
...rse/library/src/conversion/rocsparse_dense2csx.hpp 50.00% 8 Missing ⚠️
...rocsparse/library/src/conversion/rocsparse_nnz.cpp 94.12% 0 Missing and 2 partials ⚠️
...rse/library/src/conversion/rocsparse_coo2dense.cpp 90.00% 0 Missing and 1 partial ⚠️
...library/src/conversion/rocsparse_coo2dense_aos.cpp 0.00% 1 Missing ⚠️
...ibrary/src/conversion/rocsparse_csx2dense_impl.hpp 87.50% 0 Missing and 1 partial ⚠️

❌ Your project check has failed because the head coverage (74.63%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #11830      +/-   ##
===========================================
- Coverage    71.94%   69.69%   -2.25%     
===========================================
  Files         2866     2934      +68     
  Lines       473102   482508    +9406     
  Branches     69892    69491     -401     
===========================================
- Hits        340347   336239    -4108     
- Misses      109153   122038   +12885     
- Partials     23602    24231     +629     
Flag Coverage Δ *Carryforward flag
TensileLite-CPP 48.80% <ø> (-0.53%) ⬇️ Carriedforward from 6026214
TensileLite-Unit 76.64% <ø> (-6.65%) ⬇️ Carriedforward from 6026214
hipBLAS 90.62% <ø> (ø) Carriedforward from 6026214
hipBLASLt 35.29% <ø> (-3.62%) ⬇️ Carriedforward from 6026214
hipCUB 82.68% <ø> (ø) Carriedforward from 6026214
hipDNN 86.98% <ø> (+0.33%) ⬆️ Carriedforward from 6026214
hipFFT 54.81% <ø> (+11.38%) ⬆️ Carriedforward from 6026214
hipRAND 76.12% <ø> (ø) Carriedforward from 6026214
hipSOLVER 68.96% <ø> (ø) Carriedforward from 6026214
hipSPARSE 86.99% <ø> (ø) Carriedforward from 6026214
rocBLAS 48.31% <ø> (+0.06%) ⬆️ Carriedforward from 6026214
rocFFT 46.27% <ø> (-1.34%) ⬇️ Carriedforward from 6026214
rocRAND 57.42% <ø> (-0.03%) ⬇️ Carriedforward from 6026214
rocSOLVER 76.83% <ø> (+<0.01%) ⬆️ Carriedforward from 6026214
rocSPARSE 74.63% <75.27%> (-<0.01%) ⬇️
rocThrust 78.34% <ø> (-14.81%) ⬇️ Carriedforward from 6026214

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...se/library/src/conversion/rocsparse_dense2bell.cpp 86.46% <100.00%> (+0.29%) ⬆️
...rse/library/src/conversion/rocsparse_coo2dense.cpp 93.83% <90.00%> (+0.58%) ⬆️
...library/src/conversion/rocsparse_coo2dense_aos.cpp 0.00% <0.00%> (ø)
...ibrary/src/conversion/rocsparse_csx2dense_impl.hpp 86.67% <87.50%> (+0.58%) ⬆️
...rocsparse/library/src/conversion/rocsparse_nnz.cpp 79.72% <94.12%> (+0.49%) ⬆️
...rse/library/src/conversion/rocsparse_dense2csx.hpp 50.86% <50.00%> (-0.10%) ⬇️
...rse/library/src/conversion/rocsparse_csx2dense.hpp 51.26% <50.00%> (-0.14%) ⬇️

... and 360 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

AISPARSE-687: address review feedback by turning
testing_dense_to_sparse_bell_extra_687 into a template parameterised on
the index type I and the value type T, and having the non-template
testing_dense_to_sparse_bell_extra wrapper instantiate it at
<int32_t, float>. The hardcoded "using I = int32_t; using T = float;"
aliases in the function body are dropped. No behavioural change.

Co-authored-by: Cursor <cursoragent@cursor.com>

@kliegeois kliegeois left a comment

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.

Self-review after the #12617 rebase: I went through this PR for the issues reviewers raised on the other large-grid PRs. 2 items below that I will address.

Size m from min(maxGridSize[0], (2^32 - 1) / 256) + 1 so the test runs in about 320 MB,
and pass get_grid_size_x straight to dim3 instead of through num_blocks_x.
@ntrost57

ntrost57 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

changelog entry missing

…-687)

rocsparse_dense_to_sparse to CSR, CSC and COO counts the non-zeros with
nnz_kernel_row or nnz_kernel_col before converting. nnz_kernel_col
launched one 256-thread block per column and nnz_kernel_row one block per
256 rows, both unclamped and without a grid-stride loop, so a large
enough matrix went past the 32-bit dispatch limit.

Clamp both launches with rocsparse::get_grid_size_x and select the kernel
variant through rocsparse::dispatch_grid_stride_x (identical to the
AISPARSE-677 helper): the grid-stride variant runs only when the clamp
binds, and the unclamped launch keeps the straight-line body.

Add forced-clamp unit tests that drive rocsparse_dense_to_sparse (CSR and
CSC, both dense orders) and rocsparse_scoo2dense with maxGridSize[0]
shrunk to 1, 3 and 7 blocks.
…2dense counts (AISPARSE-687)

coo2dense_aos clamped grid.x to maxGridSize[0] only, which still lets
grid.x * 512 pass 2^32 work-items. Use rocsparse::get_grid_size_x like
the SoA variant.

coo2dense formed m * n in the index type for the null check on A and
called valset_2d with the index type, so with 32-bit indices the zero
fill bound overflowed once m * n passed 2^31. Form the count in 64 bits
and call valset_2d with 64-bit extents, as coo2dense_aos already does.
…(AISPARSE-687)

Give block-row 0 an occupied slot so that block 0 enters the trailing
block-row with s_slot == 1 unless the fill kernel resets it, and make the
trailing block-row the only one with two slots so ell_cols depends on the
nnz kernel reaching it. Zero the analysis buffer so an unvisited block-row
counts as empty, return early if ell_cols is wrong since every buffer is
sized from it, and allocate one spare block-row so a stale slot cannot
write past the arrays.
…AISPARSE-687)

The non-strided nnz_kernel_row formed the row tile index in the index
type J, so bid * DIM_X * 4 could wrap negative for int32 m. Pass the
block index as int64_t, as the grid-stride branch already does.

Size the row grid as (m - 1) / (NNZ_DIM_X * 4) + 1 for every value
type. Each block covers NNZ_DIM_X * 4 rows regardless of T, so the
double complex case launched 4x the blocks it needed; the surplus
blocks did no work but were the ones that reached the wrap.
…E-687)

csx2dense formed m * n in the index type for the null check on A and
called valset_2d with offset-typed extents, so with 32-bit offsets the
zero fill bound overflowed once m * n passed 2^31. Form the count in
64 bits and call valset_2d with 64-bit extents, as coo2dense does.
…-687)

Attach the Blocked ELL fix to rocsparse_dense_to_sparse, which is where
dense2bell is reached, and limit the zero fill claim to what this
change covers: dense matrices with 2^31 to 2^32 - 1 elements and 32-bit
indices.
@kliegeois
kliegeois requested a review from a team as a code owner October 5, 2026 21:34
@kliegeois
kliegeois requested a review from ntrost57 October 6, 2026 14:41
kliegeois and others added 3 commits October 7, 2026 13:02
…SE-687)

get_grid_size_x returns uint32_t and dim3 takes 32-bit dimensions, so
holding the clamped block count in int64_t only widened it to narrow it
again.
…lamp

# Conflicts:
#	projects/rocsparse/CHANGELOG.md
@kliegeois
kliegeois enabled auto-merge (squash) October 8, 2026 04:20
@kliegeois
kliegeois merged commit 2b99ef9 into develop Oct 8, 2026
115 of 116 checks passed
@kliegeois
kliegeois deleted the users/kliegeois/aisparse-687-dense-conv-clamp branch October 8, 2026 09:13
@assistant-librarian

Copy link
Copy Markdown
Contributor

TheRock Submodule Bump Activity

Newest first

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants