Repository navigation
test: Add real-GPU tests for std bit, packing, trunc and faceForward … - #3286
mishablablabla wants to merge 2 commits into
Conversation
|
pkg.pr.new packages benchmark commit |
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues remain for the added GPU builtin tests and supporting test configuration.
No open issues 🎉
What changed in this PR
Adds real-GPU coverage for TypeGPU standard-library builtins without CPU implementations, using reference calculations to check device results.
Changes:
- Adds 16 tests covering bit counting, bit extraction/insertion, packed dot products,
trunc, andfaceForward. - Introduces a standalone GPU test configuration, separate from unit tests.
- Adds a package-level
test:gpucommand.
| File | Description |
|---|---|
packages/typegpu/vitest.gpu.config.mts |
Configures GPU test discovery, TypeGPU transforms, aliases, and timeouts. |
packages/typegpu/tests/gpu/numeric.gpu.ts |
Adds GPU execution helpers and numeric builtin tests with reference expectations. |
packages/typegpu/package.json |
Adds the test:gpu script. |
Review effort: Balanced
There was a problem hiding this comment.
✅ No new issues found.
( ◦°^°◦) 🏴☠️ Ahoy! I read the whole diff end to end, ran the type-check, linter, formatter and test collection, and cross-checked every reference against the WGSL spec — all shipshape, so I gave her the green light.
Reviewed changes
- Real-GPU suite for
stdnumeric builtins —packages/typegpu/tests/gpu/numeric.gpu.tsadds 16gpuIttests:countLeadingZeros(u32/i32/vec3u),countTrailingZeros(u32/i32),firstLeadingBit(u32/i32),extractBits/insertBits(unsigned zero-extend and signed sign-extend/insert),dot4U8Packed,trunc(f32/vec3f) andfaceForward(vec2f/vec3f). Each case compares GPU output against a reference transcribed from the WGSL spec, driven by themapOnGpuhelper (one guarded thread per element). - Standalone GPU vitest config —
packages/typegpu/vitest.gpu.config.mtsmirrors the@typegpu/algorithmconfig (tests/**/*.gpu.tsinclude, 60s timeouts,unplugin-typegpuplugin plus built aliases), keeping these files out of the defaulttest:unitinclude. test:gpuscript — added topackages/typegpu/package.json, so rootpnpm test:gpu(and the manualtest-gpu.ymlworkflow) now runs this package too.
I verified tsc --noEmit on the package passes, vitest list collects all 16 tests, and oxfmt --check / oxlint --type-aware are clean. I also cross-checked every reference against WGSL §17.5 (firstLeadingBit unsigned 0xFFFFFFFF for zero and signed -1 for 0/-1; extractBits c == 0 → 0 with the right extension; insertBits c == 0 → e; dot4U8Packed; faceForward), and all match. The tests genuinely assert GPU output — the output buffer starts zeroed while the expected values are nonzero, so a no-op dispatch would fail.
ℹ️ These tests only run in the manual GPU workflow
The new suite is reachable through pnpm test:gpu, which currently runs only in the workflow_dispatch-only test-gpu.yml (Lavapipe), not in a required PR check — so it will not guard against std regressions in ordinary CI. That is the existing design for real-GPU tests, so no change is requested; noting it only so the coverage boundary is explicit.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Bundle size comparison (
|
| 🟢 Decreased | ➖ Unchanged | 🔴 Increased | ❔ Unknown |
|---|---|---|---|
| 0 | 355 | 0 | 0 |
No notable changes.
If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.
Resolution Time Benchmark---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Random Branching (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [1.01, 2.16, 4.37, 7.27, 8.62, 13.71, 26.02, 28.40]
line [1.11, 2.26, 4.96, 6.81, 7.99, 12.29, 27.42, 27.42]
line [1.02, 2.04, 4.59, 7.08, 9.35, 13.35, 28.35, 26.67]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Linear Recursion (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.32, 0.51, 0.70, 0.83, 1.12, 1.19, 1.49, 1.62]
line [0.36, 0.59, 0.76, 0.87, 1.19, 1.30, 1.54, 1.72]
line [0.33, 0.51, 0.69, 0.83, 1.11, 1.26, 1.50, 1.67]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Full Tree (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.95, 2.45, 3.88, 7.67, 14.08, 28.20, 60.28, 122.02]
line [0.83, 2.38, 3.92, 7.92, 13.61, 27.42, 60.16, 120.63]
line [1.00, 2.39, 3.79, 7.71, 13.56, 29.21, 60.32, 122.23]
|
There was a problem hiding this comment.
ℹ️ No critical issues — one low-stakes note inline.
( ◦°^°◦) 🏴☠️ Ahoy! I re-scanned the new commits end to end, typechecked the package, collected the 60 GPU tests, and cross-checked every new reference against the WGSL spec — all sound, with one wee note on the frexp zero case.
Reviewed changes
Reviewed the delta since the prior Pullfrog review (d77dccd3), where this was a single-file suite of 16 tests.
- Split the suite into focused files plus a shared helper —
utils.tsnow holdsmapOnGpu/noNegativeZero/U32_MAX/I32_MIN;numeric.gpu.tskeeps the bit-count / extract / insert / packing /trunc/faceForwardcases, and two new files group the added builtins. - Added
bits.gpu.ts—countOneBits,firstTrailingBit,reverseBits(u32 / i32 / vec) anddot4I8Packed. - Added
float.gpu.ts—determinant(mat2/3/4),refract,ldexp,frexp,modf,quantizeToF16. - Added
dispatch.gpu.ts— assertsdispatchThreadsruns every requested thread exactly once (and no extra) for 0D/1D/2D/3D sizes straddling the 256 / 16×16 / 8×8×4 workgroup sizes, plus a re-dispatch with a changing size.
I re-ran tsc --noEmit (passes) and vitest list (collects all 60 GPU tests) on the head commit, and an independent pass cross-checked every new reference against WGSL §17.5 — including firstTrailingBit/firstLeadingBit returning T(-1) for zero and the quantizeToF16 bracket being exactly the two spec-allowed neighbouring values.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
| }), | ||
| ); | ||
| // and it recombines to the input | ||
| result.forEach((r, i) => expect(r.fract * 2 ** r.exp).toBe(Math.fround(inputs[i] as number))); |
There was a problem hiding this comment.
toBe uses Object.is, so a -0 fraction fails this check even though the assertion just above deliberately normalizes zero signs through noNegativeZero. WGSL does not pin the sign of frexp(0)'s fraction (nor its exponent), so a backend returning -0 would fail here while the other assertions pass — wrap the recombined value in noNegativeZero (already imported) to stay consistent.

Summary
stdbuiltins that have no CPU implementation (bit counting,extractBits/insertBits,dot4U8Packed,trunc,faceForward).test:gpuscript and config topackages/typegpu.Validation