#[unroll] over the clone budget leaves an array-indexing loop rolled and moves the array to .local, with no way to raise the budget
Maintainers usually reply within 2 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 45/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- compilers, rust
- Domain
- compilers, performance
Research direction
Start in mir-transforms/src/unroll.rs and inspect MAX_CLONED_OPS and the unroll decision; the issue also cites kernel instances in crates/speakrs-cuda-kernels/src/segdense.rs. Reproduce the reported array placement and clone-budget warning with the provided accumulator pattern on sm_80, then decide which proposed behavior to implement. Done means a chosen override or diagnostic is implemented and covered by a regression test for the over-budget loop.
Written by the indexing model from the issue text.
Description
What problem does this solve?
When #[unroll] on the loop that selects an element of a local array goes over the 65536-operation clone budget (MAX_CLONED_OPS in mir-transforms/src/unroll.rs), the loop stays rolled. The whole array then moves to .local memory. The only signal is a warning, and there's no way to raise the budget for that loop.
The budget counts the outer loop's body after its inner #[unroll] loops have already been expanded. So a typical tensor-core accumulator pattern goes over budget at modest sizes:
#[kernel]
pub fn acc_tiles(a: *const f32, b: *const f32, out: *mut f32) {
let tid = thread::threadIdx_x() as usize;
let mut acc = [[0.0f32; 8]; 32];
let mut t = 0usize;
#[unroll]
while t < 32 {
let mut j = 0usize;
#[unroll]
while j < J {
let mut n = 0usize;
#[unroll]
while n < 8 {
let x = unsafe { *a.add(tid + (j * 32 + t) * 128) };
let y = unsafe { *b.add(tid + (j * 8 + n) * 128) };
acc[t][n] += x * y;
n += 1;
}
j += 1;
}
t += 1;
}
// store acc with the same unrolled t/n loops
}
On main (bafffe45), sm_80, ptxas 13.0.88 -v:
| J | warning | acc |
registers | stack / spill |
|---|---|---|---|---|
| 1 | none | registers | 72 | 0 / 0 |
| 3 | none | registers | 168 | 0 / 0 |
| 8 | not unrolled (152672 ops) | .local, 792 LDL/STL |
96 | 1024 B / 0 |
| 32 | not unrolled (609632 ops) | .local, 944 LDL/STL |
255 | 1184 B / 160 B |
warning: #[unroll] requested but the loop was not unrolled: unrolling would clone 152672 operations
(32 copies x 4771 operations); the safety limit is 65536 cloned operations
At J = 8, ptxas still has registers to spare, but the 1 KiB accumulator array sits in .local. The inner loops unroll correctly; only the loop that selects acc[t] stays rolled. In speakrs, this decided several kernel shapes (crates/speakrs-cuda-kernels/src/segdense.rs, the spk_segdense_conv1_b32_x3 and spk_segdense_embed_b32_x3 instances). We split work across more warps or kernels so the unrolled body fits, even where a larger tile per warp would have been faster.
Proposed behavior
Either or both:
- A per-loop override, such as
#[unroll(full)]or#[unroll(max_ops = N)], that unrolls even past the default budget. That's like CUDA C's#pragma unroll, which has no size limit. - A more precise diagnostic: name the local array that will move to
.localbecause the loop stays rolled. Optionally, make it an error when the loop index selects an aggregate local, since the result is rarely what the author wants.
Alternatives considered
Restructuring kernels so the expanded body fits, which is what we do now.
Related: #1346 (for range loops not unrolled, same .local consequence), #1375 and #1468 (loop metadata and #[nounroll]).
- Dominant language
- Rust
- Stars
- 3.7k
- Forks
- 312
- Avg merge
- 6d 5h
- Merged PRs (30d)
- 68
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from NVIDIA/cuda-rust
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
NVIDIA/cuda-rust#1568 · 1 comment ·
Maintainers usually reply within 2 days
-
[BUG]: The doc of trait `LoadTileLike` is incorrectPossibly taken @akhil9tiet claimed this 6 days ago. Openbug
Difficulty 1/5 Under an hour Newbie friendliness 72/100
NVIDIA/cuda-rust#1523 · 2 comments ·
Maintainers usually reply within 2 days
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 2 days
-
Target feature scan splits LLVM IR on ';', so conjunctive predicates match across the whole module (integer redux + any f32 reads as ReduxF32)Possibly taken @vyncint claimed this 33 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
NVIDIA/cuda-rust#1303 · 1 comment ·
Maintainers usually reply within 2 days
All issues in NVIDIA/cuda-rust
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
git-ai-project/git-ai#2434 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
peteonrails/voxtype#909 ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
kucherenko/jscpd#1239 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 69/100
alchemyplatform/rundler#1358 ·
Maintainers usually reply within 3 days