Repository navigation
perf(cute): speed up the CuTe verifier's provenance analysis - #1648
Open
juliankang4 wants to merge 4 commits into
Open
juliankang4 wants to merge 4 commits into
juliankang4 wants to merge 4 commits into
Conversation
`OrdinaryFacts` kept one flat map keyed by (value, field path), so `entries(value)` scanned every key in the map. The ordinary plumbing and the poison pass call it for every cast, aggregate, store, load and block argument of a scope, which made each sweep quadratic in the scope's size. Key the facts by SSA value first and by field path second. `entries`, `at_path` and `copy_entries` now touch only the named carrier, and `copy_entries` still forwards a snapshot of the source taken before the copy. The shared-MMA use closure walks the nested map and tests scope membership through a set instead of a linear search. Signed-off-by: juliankang4 <200967947+juliankang4@users.noreply.github.com>
…tions The scheduler, pipeline and shared-MMA stories built their provenance facts for every scope, including every function with no CuTe operation at all. Each analysis seeds every integer value of the scope, so this cost was paid for every ordinary kernel. Return before building the facts when the scope holds no operation of the story's family. Every check in those stories starts from such an operation, so a scope without one was already accepted after the analysis; consumer-only (malformed) stories still reach their checks because their operations belong to the family. Signed-off-by: juliankang4 <200967947+juliankang4@users.noreply.github.com>
Seeding gives every factless integer value an origin that names the value itself (`Unknown`, and `Scalar` for shared MMA). Propagation merges those origins through block arguments and closed local cells, so a value's set grew with the length of every chain that reaches it, and each sweep of the fixed point copied the grown sets: verification time grew with the cube of the scope size. Consumers read a set only through its size, its sole origin, or whether it holds a given semantic origin, and they skip opaque origins when walking a set. Two opaque origins therefore report a merge exactly as any larger number does. `insert_path` now drops a third one, through a `CarrierFact` trait that each family's fact type implements. Path presence and semantic origins are unchanged, so every accept or reject decision and the diagnostic each consumer reports stay the same. Tests cover the cap, the snapshot copy, a scheduler state carried through 48 blocks and cells, and the rejection wording for cursors that merge one to eight unrelated values. Signed-off-by: juliankang4 <200967947+juliankang4@users.noreply.github.com>
Every load and store that forwards provenance through a closed local cell called `local_cell_is_closed`, which walks all uses of the cell and its aliases. A cell with N accesses therefore cost N walks of N uses on every fixed-point sweep, so verification time grew with the square of the number of accesses to one cell. The verifier does not change the IR it reads, so a cell's closedness is fixed for the whole analysis. `LocalCells` walks the alias graph of each `mir.alloca` in the scope once, and the scheduler, pipeline and shared-MMA fact builders, the shared-MMA use closure and the epilogue plumbing check look cells up there. A cell allocated outside the scope is still walked on demand, so every answer is the one the walk gives. The ordinary plumbing and the poison pass also read each incoming edge's successor operands once per block instead of once per block argument. A new test compares the table with the alias walk for closed, aliased and escaped cells, with and without their allocations in the scope. Signed-off-by: juliankang4 <200967947+juliankang4@users.noreply.github.com>
4 of 5 tasks
Contributor
|
Tested on bloomery, a crate of large SIMT kernels with no CuTe operation, on its own box. Cold release build of one binary: 9df9e7c alone did not finish in 1700 s; with this PR, 480 s (388 s at 0143af5, before #1598). The PTX of 809 kernel entries is identical to a build that skips the verifier in scopes without CuTe operations. A block-chain probe with no CuTe operation verifies in 0.006 s at 800 blocks and 0.050 s at 8000. |
midagedev
added a commit
to midagedev/bloomery
that referenced
this pull request
Oct 26, 2026
…used by name without it; host flags generated per triple tools/oxide-backend.sh applies tools/oxide-backend/cuda-rust-1648.patch to a copy of the pin's checkout, builds the backend and records the patch's sha256 in PROVENANCE; tools/oxide-host.sh checks it before every cargo oxide build and writes .cargo/cuda-oxide.toml's flags from .cargo/config.toml's table for the target triple, with the llc major check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
midagedev
added a commit
to midagedev/bloomery
that referenced
this pull request
Oct 26, 2026
…used by name without it; host flags generated per triple tools/oxide-backend.sh applies tools/oxide-backend/cuda-rust-1648.patch to a copy of the pin's checkout, builds the backend and records the patch's sha256 in PROVENANCE; tools/oxide-host.sh checks it before every cargo oxide build and writes .cargo/cuda-oxide.toml's flags from .cargo/config.toml's table for the target triple, with the llc major check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
Summary
Fixes #1647. The CuTe semantic verifier added in #1598 takes time that grows roughly with the cube of a function's size, and it runs on every module, so large kernel crates no longer finish building. This change indexes the provenance facts by carrier, skips a story's analysis in scopes that have none of its operations, stops opaque origins from piling up in fact sets, and checks each local cell's aliases once per analysis. The accept and reject rules and the diagnostic each consumer reports stay the same. On the inputs measured below, verification time now grows linearly.
Changes
OrdinaryFactskeys its facts by SSA value first and field path second, soentries,at_pathandcopy_entriestouch one carrier instead of scanning the whole map.copy_entriesstill forwards a snapshot taken before the copy. The shared-MMA use closure walks the nested map and checks scope membership through a set.scope_has_anyincfg.rs). Every check in those stories starts from such an operation, so these scopes were already accepted after the analysis.insert_pathkeeps at most two opaque origins (Unknown, andScalarfor shared MMA) per field path, through a smallCarrierFacttrait. Consumers read a set only through its size, its sole element, or whether it holds a given semantic origin, and they skip opaque origins when they walk one:scheduler_root,pipeline_state_rootandone_mma_factrejectlen() != 1,require_same_mma_scalarcomparesScalaridentity only when the set has one element, the advance and accumulator propagation andverify_mma_use_closureskip opaque facts, and the poison pass only asks whether a path exists. A capped set is a subset of the uncapped one with the same semantic origins,min(2, n)opaque origins and the same paths, so each of those reads gives the same answer.LocalCellswalks the alias graph of everymir.allocain the scope once, because the verifier does not change the IR it reads. The fact builders, the shared-MMA use closure and the epilogue plumbing check look cells up there instead of callinglocal_cell_is_closedfor every access on every sweep. A cell allocated outside the scope is still walked on demand. The plumbing and the poison pass also read each incoming edge's successor operands once per block instead of once per block argument.OrdinaryFacts, the cell table against the alias walk for closed, aliased and escaped cells with and without their allocations in the scope, a scheduler state carried through 48 blocks and closed cells, and the exact rejection text for a scheduler cursor that has no origin or merges one to eight unrelated values. The two story tests also pass against the verifier at9df9e7ce.When an input has more than one defect, the verifier still reports whichever one it meets first in hash-map order, as it did before this change.
Testing
cargo test -p dialect-cute --all-targetson9df9e7ceplus this branch, with the pinned 1.99.1 toolchain:93 passed; 0 failedin the library tests, and theglobal_store,public_pathsandsm100_attentionintegration tests pass.cargo clippy -p dialect-cute --all-targets -- -D warningspasses, andrustfmt --checkpasses on the changed files. Each of the first two commits also passes the existing 90 library tests on its own.Verification time on one function with one scheduler story and N ordinary blocks (one scheduler story whose entry reaches its loop through N ordinary blocks, each with a block argument, a closed local cell, a store and a load; debug build, Apple M4 Pro):
Verification time with N store/load pairs on one closed cell (the same story with the N pairs in its entry block; same machine):
Cold
cargo oxide build --arch sm_86 -- --release -j 12of a 358-kernel crate without CuTe operations, RTX 5090, Ubuntu 26.04.1, CUDA 13.3:0143af5c(parent of #1598)9df9e7ce9df9e7cewith the first three commitsThe last commit only changes code that runs after a story finds one of its operations, which this crate never reaches. The remaining difference from
0143af5cis outside the verifier and is not addressed here.On this branch, RTX 5090 with driver 595.99.02, CUDA 13.3, LLVM 21.1.8 and the pinned 1.99.1 toolchain:
just -f cuda-oxide/Justfile checkpasses (exit 0)cargo oxide run vecaddpasses:✓ SUCCESS: All 1024 elements correct!Checklist
git commit -s)