Repository navigation
Conversation
nihalpasham
left a comment
There was a problem hiding this comment.
The integer reduction fast path is useful, but the cfg floor still needs to be part of target selection before this lands.
resolve_sm_floor is only called by cuda-device/build.rs. With no environment target, it advertises 80 while an sm_75-feature module selects sm_75. With a detected sm_120 hint and WGMMA, it advertises 120 while the backend selects sm_90a. I reproduced both through the actual resolver; the lower-bound assertion fails.
Please carry one resolved floor into native PTX and NVVM selection, handle incompatible architecture families explicitly, and add cross-stage regressions for these cases and invalid/blank inputs. The current tests only verify the new helper's own table.
This partially addresses #811; #1304 remains a prerequisite.
58b0d79 to
70ede7a
Compare
|
You were right that the lower bound did not hold, and chasing it turned up that the rule itself — the middle row of the table on #811 — is the unsound part, not just my wiring of it. Why a detected device cannot raise the floor
So the rule is now asymmetric, and that is what makes the assertion hold:
The hint says nothing about how high selection goes. It does bound how low, because a device that can run the kernel is selected as-is. That direction matters: Carried into both selectorsBoth Your two cases, now: The regression you asked for is the property itself, not those three rows: a matrix over explicit × hint × features asserting One behaviour change, stated because it reaches builds that never use this cfgWith nothing pinned, a module whose features ask only for sm_75 is now built for sm_80. That is the documented Ampere+ minimum and the same default a featureless module already got, and HardwareA10G, compute capability 8.6. Per-kernel PTX at both floors, unchanged from the last revision:
Run on the A10G with nothing pinned (hint sm_86, floor 80, fast path live): all four checks correct. 1218 codegen tests, plus |
70ede7a to
08d95ff
Compare
nihalpasham
left a comment
There was a problem hiding this comment.
The earlier floor-contract blocker is fixed. Both selectors now use the shared floor: no-env ldmatrix stays at least sm_80, and an sm_120 hint with WGMMA advertises floor 80 while selecting sm_90a. I shortened the comments and corrected the outdated cfg and reduction-hook descriptions.
At db5c23f8, all 1,394 tests/doctests across cuda-device, cuda-target-spec and cuda-oxide-codegen passed, along with strict Clippy and formatting. B200 runs of warp_reduce_redux passed for sm_80 and sm_75 through LLVM NVPTX. The sm_80 fast path also passed memcheck and synccheck with 0 errors; float and sub-warp controls passed in both builds. No new performance measurement was made.
This still depends on #1304, whose current detector misses valid float-redux asm and misreads comments/ordinary symbols. Please fix those lexical cases there and update this stack before landing. The remaining request is the scanner prerequisite, not the repaired floor invariant.
One minor distinction: malformed hints are ignored by the shared floor resolver and PTX selector, but the existing NVVM selector rejects them. The floor guarantee is still fail-closed.
db5c23f to
96ea0b5
Compare
|
Thanks for the B200 runs and the memcheck/synccheck pass — that is evidence I could not have produced on an A10G. Rebased onto the updated #1304 ( On the minor distinction you noted: you are right, and I had it wrong in two places. My last comment here said a malformed hint "is ignored by both", and the test comment said "both stages ignore it" — true of the floor resolver and the PTX selector, not of the NVVM selector, which rejects it. One small commit on top, Across the stack: Stack is now |
nihalpasham
left a comment
There was a problem hiding this comment.
Checked 96ea0b56: the floor logic, reduction hooks and all five documentation-cleanup files are unchanged from the tested db5c23f8. The new test correctly pins NVVM's rejection of malformed hints beside PTX's fall-through.
All 1,395 tests/doctests across cuda-device, cuda-target-spec and cuda-oxide-codegen pass from an isolated build directory, along with strict Clippy and formatting. The earlier B200 sm_80/sm_75 runs and fast-path memcheck/synccheck covered the unchanged reduction implementation; they were not repeated for this scanner rebase.
The original four #1304 cases are fixed, but its PTX scan still misses legal whitespace/comment-separated modifiers and mistakes a quoted .file path for an instruction. Those cases are accepted by LLVM and ptxas, and the actual detector chooses the wrong targets. Please address that remaining prerequisite and update this stack. The floor invariant itself remains clear.
96ea0b5 to
cf4c55c
Compare
|
Rebased onto #1304's
|
NVIDIA#1305 selects the reduction form at compile time from the target floor, so the comparison has to be two builds of one source rather than two kernels in one binary. This adds that harness, plus the control the comparison needs. - `api_chain` / `api_independent` drive `warp_reduce::<u32, _>`: one is a dependency chain (reduction latency), the other four unrelated reductions per iteration (throughput). - `shuffle_chain` / `shuffle_independent` spell the butterfly out with `WarpCollective::shfl_xor`, so at `sm_80` the pair isolates the reduction from the rest of the build: same target, same compiler, same binary. - 128 threads, 40,000 iterations, 15 interleaved samples and 3 warm-up rounds per arm, medians reported per reduction, and the first iterations checked against a host simulation so a folded-away loop cannot pass as a timing. Measured on an A100-SXM4-40GB (cc 8.0, driver 595.91.07). At `sm_80`, where the API takes `redux.sync.add.s32` and the control keeps its five `shfl.sync.bfly.b32` + `add.s32`: | workload | redux ns/reduction | butterfly ns/reduction | ratio | |---|---|---|---| | dependent chain | 8.4 - 10.8 | 27.2 - 35.0 | 3.24x | | four independent reductions | 2.9 - 3.7 | 9.4 - 12.1 | 3.24 - 3.27x | Three batches agree on the ratio to the printed digit. Built at `sm_75` the two arms are the same code and time identically, which is the control for the harness itself. Needs NVIDIA#1305 for the `sm_80` arm to be a `redux.sync` build; without it both arms are the butterfly. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
Results for the four items in the cuda-oxide A100 execution pack (NVIDIA#399, NVIDIA#811 / NVIDIA#1305, NVIDIA#1235, NVIDIA#1328), with the raw logs behind every number. - A1: NVIDIA#399 closure audit (`CLOSURE_COMMIT: UNKNOWN`) and the test-only regression that covers the iterator form and its negative control. - A2: native A100 validation of NVIDIA#1305 at cf4c55c - correctness, sanitizers, sm_75 fallback, and the paired measurement (3.24x for a dependent chain, 3.24-3.27x for four independent reductions). - A3: NVIDIA#1235 merged-state and coverage audit. Existing oracle covers 2 of 12 combinations; G1/G2/G3 unmet, so no transform is proposed. - A4: NVIDIA#1328 - the reported reproducer is already fixed by NVIDIA#1264, the equal-width arm was still wrong, falsification and device evidence. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
NVIDIA#1305 selects the reduction form at compile time from the target floor, so the comparison has to be two builds of one source rather than two kernels in one binary. This adds that harness, plus the control the comparison needs. - `api_chain` / `api_independent` drive `warp_reduce::<u32, _>`: one is a dependency chain (reduction latency), the other four unrelated reductions per iteration (throughput). - `shuffle_chain` / `shuffle_independent` spell the butterfly out with `WarpCollective::shfl_xor`, so at `sm_80` the pair isolates the reduction from the rest of the build: same target, same compiler, same binary. - 128 threads, 40,000 iterations, 15 interleaved samples and 3 warm-up rounds per arm, medians reported per reduction, and the first iterations checked against a host simulation so a folded-away loop cannot pass as a timing. Measured on an A100-SXM4-40GB (cc 8.0, driver 595.91.07). At `sm_80`, where the API takes `redux.sync.add.s32` and the control keeps its five `shfl.sync.bfly.b32` + `add.s32`: | workload | redux ns/reduction | butterfly ns/reduction | ratio | |---|---|---|---| | dependent chain | 8.4 - 10.8 | 27.2 - 35.0 | 3.24x | | four independent reductions | 2.9 - 3.7 | 9.4 - 12.1 | 3.24 - 3.27x | Three batches agree on the ratio to the printed digit. Built at `sm_75` the two arms are the same code and time identically, which is the control for the harness itself. Needs NVIDIA#1305 for the `sm_80` arm to be a `redux.sync` build; without it both arms are the butterfly. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
Results for the four items in the cuda-oxide A100 execution pack (NVIDIA#399, NVIDIA#811 / NVIDIA#1305, NVIDIA#1235, NVIDIA#1328), with the raw logs behind every number. - A1: NVIDIA#399 closure audit (`CLOSURE_COMMIT: UNKNOWN`) and the test-only regression that covers the iterator form and its negative control. - A2: native A100 validation of NVIDIA#1305 at cf4c55c - correctness, sanitizers, sm_75 fallback, and the paired measurement (3.24x for a dependent chain, 3.24-3.27x for four independent reductions). - A3: NVIDIA#1235 merged-state and coverage audit. Existing oracle covers 2 of 12 combinations; G1/G2/G3 unmet, so no transform is proposed. - A4: NVIDIA#1328 - the reported reproducer is already fixed by NVIDIA#1264, the equal-width arm was still wrong, falsification and device evidence. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
NVIDIA#1305 chooses the reduction form from the target floor at compile time, so the two forms cannot be compared inside one binary: the comparison has to be two builds of one source. This adds that harness and the control it needs. - `api_chain` / `api_independent` drive `warp_reduce::<u32, _>`. One is a dependency chain, the other four unrelated reductions per iteration. - `shuffle_chain` / `shuffle_independent` spell the butterfly out with `WarpCollective::shfl_xor`, so on one target the pair isolates the reduction from the rest of the build: same compiler, same binary. - Correctness is a per-warp host simulation of the real data flow, not a comparison between the two arms. Every reduction reads the previous round's whole warp and only then broadcasts, so no lane observes a value another lane already updated. Both arms are checked against that oracle at iterations 0, 1, 2, 3, 8 and 17, on every lane and every field. - The independent arms publish four values per lane instead of folding them into one, so a fault in a single operation is locatable. - Every arm starts from the bitwise inverse of its oracle, so a value the kernel never writes cannot read back as a correct answer. On a tree without NVIDIA#1305 both arms are the butterfly and the harness still runs: the API is then measured as the fallback it compiles to, not as a fast path. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de> The figure the harness reports is a warp reduction's amortised cost over a long kernel, timed with a host wall clock around launch plus stream synchronisation. That keeps launch and host overhead small per reduction without removing them, and one warp reduction is one collective - not one instruction, and not 32 independent results. The output now says so: ``` timer_kind=host_wall_clock_kernel_batch iterations=40000 grid_blocks=1 warps_per_block=4 amortized_ns_per_warp_reduction = kernel_ms * 1e6 / (iterations * blocks * warps * R) ``` `R` is 1 for the chain arms and 4 for the independent ones, and it is printed per row. The arms are sampled round-robin inside one pass so a clock or power drift lands on all of them. A scaling check runs the chain arms at four iteration counts and warns when the time does not grow with the iteration count, which is what a loop the compiler removed would look like. On the dependency tree the four points are 0.048/0.145, 0.221/0.704, 0.866/2.800 and 1.727/5.595 ms. No performance threshold is asserted: the harness reports, it does not gate. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
A reusable harness for the pair NVIDIA#1305 selects at compile time. Four arms: API and hand-written butterfly, each as a dependency chain and as four unrelated reductions per iteration. Correctness is a per-warp host simulation of the real data flow, at iterations 0, 1, 2, 3, 8 and 17, on every lane and field; the independent arms publish four values per lane instead of one merge, and every arm starts from the bitwise inverse of its oracle. Timing is reported as `amortized_ns_per_warp_reduction` with `timer_kind` and `R` stated, and a scaling check warns when the time does not grow with the iteration count. On NVIDIA#1305's dependency tree at sm_80: 8.41 ns against 27.19 ns for the chain, and 2.90 against 9.01 ns for the independent mix. On a tree without NVIDIA#1305 both arms are the butterfly and the harness reports the fallback rather than a fast path. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
…nearby text `contains_redux_f32_features` asked whether `redux.sync` and `.f32` both appeared in one `;`-delimited piece of the LLVM IR. `;` starts a comment in IR, so that piece is usually the whole module: a module reducing integers with any unrelated `.f32` was pushed from sm_80 to sm_100a. The integer `warp_reduce` fast path in NVIDIA#811 is such a module. The predicate now decides on tokens. The IR is lexed with quoted text consumed whole and comments dropped, and two things count: * a global symbol, quoting decoded, named `llvm.nvvm.redux.sync.` followed by a float operation -- the ones beginning with `f`, since the float callees carry no type suffix; * a `redux` opcode whose modifiers, in an inline-asm template, start with `.sync` and include `.f32`. The template is the string after `LLParser`'s `asm [sideeffect] [alignstack] [inteldialect] [unwind]`, decoded as `llvm::UnEscapeLexed` does, then lexed as PTX: whitespace and `//` and `/* */` comments separate tokens without ending a modifier run, a number consumes its whole run, and a string literal -- in which a backslash is literal -- is skipped as data. So text that only mentions the instruction or the callee -- in a comment, a data or metadata string, a `.file` path, or another symbol's quoted name -- no longer counts, and the quoted, escaped, `unwind`, spaced and comment-separated spellings of a real one do. Thirty-one controls across four tests, each a line from a module `llvm-as` from the pinned toolchain accepts. All but one also lower through `llc`; the exception is a global whose quoted name contains a space and `@`, which is valid IR that NVPTX cannot emit, and which tests the lexer rather than lowering. The PTX controls are also checked with ptxas, where the float cases assemble at sm_100a and are refused at sm_80 as `redux.f32`. Twelve mutations of the lexers each fail a test. The float intrinsic spelling was never matched by the old predicate, but that is not a live mis-target: those are catalog intrinsics, and `select_target_with_generated` takes sm_100a from their recorded requirements regardless -- `redux_f32` selects sm_100a with nothing pinned under either version. Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
…reduce Device code had no way to ask what target it was being compiled for, so `warp_reduce` could not take the one-instruction `redux.sync` form even when the build was certain to be Ampere or newer: a full warp always paid five shuffles plus five combines. Measured on an A10G over 1M threads reducing 64 times each, that is 159.4 us per launch against 33.6 us, 4.74x, for bit-identical results. A cfg is the mechanism that works here. It is decided before rustc runs, so rustc drops the branch that does not apply and the requirement scan never sees an instruction the target cannot run -- unlike a `__CUDA_ARCH__`-style intrinsic folded at lowering, where the scan reads `redux.sync` out of the dead branch and raises the module floor before anything folds. `cuda-device` gains a build script that resolves the floor and emits `cuda_oxide_sm_at_least = "XY"` for every rung at or below it. `cfg` compares strings rather than numbers, so the rungs are cumulative and device code tests the one its instruction needs. The floor rule lives in `cuda-target-spec`, shared with the backend rather than restated, and follows the backend's own precedence: `CUDA_OXIDE_TARGET`, else `CUDA_OXIDE_DEVICE_ARCH`, else sm_80. Because both sides read the same two variables through the same function, the cfg floor and the selected target cannot disagree. The hint is read with the `DeviceArch` grammar the backend validates it with; one that does not parse is left out, because the backend rejects it before selecting a target. A named target that does not parse fails the build instead of falling back: guessing high emits instructions the target cannot run, guessing low silently drops the faster form. Rust has no specialization, so the reduction reaches a form that exists for only some `(Op, T)` pairs through a provided `ReduceOp::reduce_full_warp` hook, `None` by default. Nine integer pairs override it under the Ampere rung; `f32` keeps the butterfly, since there is no `f32` `redux` before sm_100. Only `N == 32` consults it, leaving the sub-warp path and its out-of-tile lane substitution untouched. One behaviour change: with no explicit target the floor is sm_80, so an integer `warp_reduce` now emits `redux.sync` and its module requires Ampere. Building with `--arch sm_75` keeps the portable form. The `warp_reduce` rustdoc and the warp-level-programming chapter both said this selection did not happen and pointed at this issue; both now describe what it does. Adding the build-dependency puts the first `cuda-device` -> `cuda-target-spec` edge in the graph, and `cuda-device` is in every kernel crate's. So 234 example locks plus the `cuda-macros` device-only test lock each gain that one line; `check-example-license-policy.sh` and `check-device-only-build.sh` fail on a stale lock, so the churn is mandatory rather than cosmetic. It is exactly one added line per file and no version moves. A build-dependency reaches only the host side of the build, so no kernel graph changes. The floor is a lower bound on target selection, and the backend now enforces it. It did not before: `resolve_sm_floor` was consulted only by the build script, so with nothing pinned the ladder advertised sm_80 while a module whose features need only sm_75 -- `ldmatrix` alone -- was built for sm_75, below the capability device code had been compiled against. Both resolvers, native PTX and NVVM, now raise such a selection to the floor, which every architecture at or above it can host. A floor that cannot host the module at all is reported rather than resolved to something else; no path reaches that today, and it is there so a future floor cannot select an architecture family silently. The hint rule is the other half, and it is a correction to the design sketched on the issue. A detected device cannot raise the floor. `CUDA_OXIDE_DEVICE_ARCH` is advisory by contract -- the backend "builds for the detected GPU only when that GPU can actually run the kernel", and otherwise for the arch the kernel requires -- so it says nothing about how high selection will go: a detected sm_120 with a WGMMA module lands on sm_90a, and a floor of 120 taken from it would be a bound the backend is documented to break. What the hint does bound is how low selection may go, since a device that can run the kernel is selected as-is. So it lowers the floor and never raises it: running on Turing still produces sm_75 rather than PTX that device cannot load, and device code is told it has only the rungs that PTX will have. One behaviour change falls out, stated because it reaches builds that never use this cfg: with nothing pinned, a module whose features ask only for sm_75 is now built for sm_80. That is the documented Ampere+ minimum and the same default a featureless module already got; `--arch sm_75` still selects sm_75. Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
…controls
`warp_reduce` now takes `redux.sync` for the nine integer pairs at the Ampere
floor and the shuffle butterfly below it, but no example demonstrated either:
the only caller of the generic API is `coop_groups_demo`, which also uses
thread-block clusters and so cannot be built below sm_90 at all. There was
nowhere to read the PTX difference, and nothing that would notice if the fast
path started answering differently from the tree it replaces.
This example uses one warp and nothing else, so it builds at both floors:
cargo oxide build warp_reduce_redux --arch sm_80 redux.sync, no shfl
cargo oxide build warp_reduce_redux --arch sm_75 shfl butterfly
Four kernels, three of them controls. The `u32` kernel takes every supported
op, with inputs that pin the whole warp: a distinct bit per lane, so `or` and
`xor` are `0xFFFFFFFF` and `and` over the complements is 0 -- a reduction that
dropped lanes could not produce those. The `i32` kernel straddles zero,
because signedness is what a single-instruction form gets wrong silently:
`redux.sync.min.s32` and `.u32` disagree on exactly those inputs.
The two controls are the cases that must *not* move. `f32` has no `redux`
before sm_100, so a hook that claimed one would name an instruction that does
not exist. A `WarpTile<16>` must reduce within its own tile: the fast path is
gated on `N == 32`, and if that gate broke, both tiles would answer 496
instead of 120, which the expected values catch.
Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
Signed-off-by: nihalpasham <nihalp@nvidia.com>
Since NVIDIA#1256 the backend validates CUDA_OXIDE_DEVICE_ARCH once, at its input boundary, and hands selection a DeviceArch: a hint that does not parse is an error before either backend chooses a target. The three readings of a malformed hint this stack used to pin -- the floor resolver and the PTX selector ignoring it, the NVVM selector rejecting it -- no longer exist, so that test was dropped rather than carried forward. What the floor depends on now is narrower. cuda-device's build script reads the same variable with `parse::<DeviceArch>().ok()`, so its floor matches selection exactly when the boundary accepts the values that parse and rejects the rest. The new test asks both about the same inputs -- valid, blank, padded, bare, too short, virtual and family spellings -- and fails if they disagree. Making the boundary trim its input fails it on `" sm_86"`, which the build script would leave out while the backend selected it. The resolver's doc no longer says malformed hints are ignored, since the hint now reaches it parsed. Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
cf4c55c to
ab10984
Compare
|
Rebased onto the mono-repo What changed Outside the lock files, this PR's own diff differs from
Unchanged: the floor rule, the reduction hooks and Verification on
With With ptxas 13.3, the first PTX assembles at sm_80 and the second at sm_75. The first, retargeted to sm_75, is refused with Two statements in the description now predate the head, and since it is your text I have left it to you: the malformed-hint regression it describes is the dropped |
What this adds
Full-warp integer
warp_reducecalls useredux.syncwhen the target floor is sm_80 or newer. Floats, smaller tiles and lower floors retain the shuffle implementation.The device build script and both backend selectors share the floor rule: an explicit target sets it; an advisory device hint can only lower the default sm_80 floor. Automatic fallback targets are raised to that floor or rejected if their feature requirements conflict. A numeric floor does not promise architecture-family-specific instructions.
Verification
At
96ea0b56, 1,395 tests/doctests across cuda-device, cuda-target-spec and cuda-oxide-codegen pass from an isolated build directory, along with strict Clippy and formatting. The new malformed-hint regression checks all three stages: the floor resolver and PTX selector ignore the hint; NVVM rejects it.Earlier B200 runs at
db5c23f8passed for sm_80 and sm_75 through LLVM NVPTX, covering nine integer operation/type pairs plus float and sub-warp controls. The sm_80 run also passed memcheck and synccheck with 0 errors. The floor/reduction implementation and maintainer documentation fixes are unchanged in the new stack. GPU tests were not repeated after the scanner rebase; no new speedup measurement was made.Remaining prerequisite
The original four #1304 lexical failures are fixed. Its updated PTX scan still misses valid whitespace/comment-separated opcode modifiers and treats a quoted
.filepath as an instruction. Those cases must be fixed before this PR lands. The earlier floor-selection counterexamples remain resolved.Partially addresses #811. Depends on #1304.