Skip to content

Verification

Each translated instruction has to prove it behaves like the real chip. The strongest proof is running the instruction on a real Ampere card and comparing every output byte. For example, one instruction runs on a rented RTX 3090 with known inputs. The translated version must then give the same bits on Intel and on lavapipe.

Which checks exist?

flowchart TB
    T1["1. spirv-val<br/>Every Vulkan shader we write is well formed"] --> T2["2. nvdisasm<br/>Every hand-built instruction word is the one we meant"]
    T2 --> T3["3. silicon captures<br/>Results match a real RTX 3090 or A6000"]
    T3 --> T4["4. compiled programs<br/>Real CUDA programs give the right output end to end"]

The checks, from cheapest to most convincing.

A handler, the code that translates one instruction family, is not done until it passes every check that applies. It must pass on Intel and on lavapipe, Mesa's CPU-based Vulkan driver.

Oracle and digest tests select exact mode explicitly. The normal emulator defaults to speed hacks; KOKOROMI_EXACT=1 or SpeedHacks::none() disables them.

Check How Where
spirv-val, the Khronos SPIR-V checker The harness pipes each module through it, skipped when it is not installed core/src/tests/gpu/harness.rs
nvdisasm, NVIDIA's disassembler fixtures_match_nvdisasm disassembles the test words and compares the text core/src/tests/gpu_test.rs
Silicon Scoreboard tests replay recorded inputs and compare registers, shared memory, global memory and image bytes core/src/tests/gpu/fixtures.rs, core/tests/fixtures
Programs Kernels built with nvcc, NVIDIA's CUDA compiler, run through the translator and must match the card's output or its hash core/src/tests/gpu/program.rs, cubin tests

How are silicon captures made?

A capture patches one instruction word into a small compiled CUDA program, the seed kernel. It runs that program on an NVIDIA Ampere card through the CUDA driver and reads the registers and memory back. The capture machine uses the NVIDIA driver for exact SASS. The word is built from the instruction table and checked with nvdisasm before the run.

The result is stored as a fixture: the word, the inputs, the outputs, and whether the result was really observed. Observed results are never typed in by hand.

Session Card What it added
1 RTX 3090 Single-instruction integer cases
Warp RTX A6000 Votes, shuffles, reductions, shared memory, barriers
3 RTX 3090 fp32, fp16, fp64, conversions, predicates, warp, textures
4 RTX 3090 Whole programs, DSETP, HSETP2, atomics, textures
5 RTX 3090 Observers, local memory, TMML, SUATOM, tensors, the driver's constant buffer 0 values
Fixture file Captures
sm86-thread.jsonl 136
sm86-predicate.jsonl 209
sm86-warp.jsonl 220
sm86-texture.jsonl 37
sm86-observer.jsonl 31
sm86-tensor.jsonl 15

What if a result is allowed to differ?

Each allowed mismatch is listed by fixture id, with its reason, for every device or for lavapipe only. Any other mismatch fails the test. See ADR-0008.

List Applies to Examples
KNOWN_FAILURES Every device MUFU.TANH is a hardware approximation; SUATOM's thread order is up to the device
LLVMPIPE_FAILURES lavapipe only f64 round-to-even truncates; an infinite unnormalized coordinate reads texel 0

What did each check catch?

Bug Caught by
EXIT emitted OpKill, which compute shaders may not use spirv-val
Vector types read type id 0, OpMemberName in the wrong section spirv-val
MOV immediate keyed on 0x1a02, which is really MOV from cx[UR]; the real key is 0x802 nvdisasm
Test words carried guard P0 instead of PT nvdisasm
Drivers folded x + -x to zero, Intel flushed tiny f16 and f64 values fp32 tests, then silicon fp16 and fp64 cases
NaN results: fp32 and fp16 are canonical, fp64 keeps the first NaN's payload Silicon
HMMA summed in standard float order instead of the truncating group sum Silicon tensor captures
SHFL with a clamp of 15 was read as a half-warp segment; it clamps the whole warp Silicon, a later session
LDG/STG with a memory descriptor added the descriptor to the address Compiled program (nvcc mul-add)
I2F to f32 ignored directed rounding CUDA sample (Mandelbrot)
IADD3 ignored the negate bit on cbuf and uniform operands CUDA sample (Mandelbrot)
IMAD.HI added Rc as 32 bits instead of the 64-bit pair CUDA sample (Mandelbrot)
Narrow stores raced as a plain read-modify-write Review; now a compare-exchange loop
HFMA2 rounded twice through native f16 fma on lavapipe Review; now one rounding from a round-to-odd f32 sum
BAR.RED wrote its result to Rb instead of B2R.RESULT Divergence work

The lesson from the CUDA sample rows: whole programs find bugs that single-instruction captures miss. They hit operand flags, rounding modes and register pairs together.

How are speed and CI checked?

core/examples/perf.rs prints latency percentiles for translate, dispatch, draw, the demo frames and the pushbuffer triangle. scripts/profile.sh records it with perf and writes a report and a flamegraph.

CI runner GPU tests
Ubuntu Run on lavapipe, then print the perf baseline
Windows Skipped, no Vulkan device
macOS Skipped, no Vulkan device

Verified by: this page is the index of checks; each check's tests are listed above.

Sources: core/src/tests/gpu/{harness,fixtures,program}.rs, core/src/tests/gpu_test.rs, core/tests/fixtures/README, core/examples/perf.rs, scripts/profile.sh, .github/workflows/ci.yml.