Test: run the shaders on a real GPU, not just their references - #30
Open
m96-chan wants to merge 1 commit into
Open
Test: run the shaders on a real GPU, not just their references#30m96-chan wants to merge 1 commit into
m96-chan wants to merge 1 commit into
Conversation
Twelve shaders, eighteen test files, and nothing executed WGSL: `requestAdapter` and `createShaderModule` appeared in zero of them. Every test exercised a TypeScript reference, so the shader and the reference were two transcriptions of the same intent with nothing holding them together. One name admitted it — refTernaryGemvShaderLogic. The kernels are not in doubt; the model runs in a browser and produces coherent text, which is a stronger signal than any unit test. What was missing is per-kernel verification, and the reason to want it now is fusion: "still coherent" cannot tell a correct fusion from a subtly wrong one. Seven shaders are covered — rmsnorm, activation, elementwise, softmax, rope, quantize, dequantize — with D chosen to span the 256-wide workgroup: below it, exactly on it, and not a multiple of it. The five left out (attention, embedding, f32_matmul, ternary_gemm, ternary_gemv) need packed I2_S and embedding tables built first. Two findings came out of it. The eps in rmsnorm was untested: on ordinary input sumSq/D dwarfs it, so removing it entirely still passed. There is now an all-but-zero row where it is the only thing preventing a division by zero. And rope cannot be checked to f32 precision. This GPU's sin and cos carry up to 1.86e-4 of absolute error — three orders of magnitude worse than f32 epsilon. `pow` was measured separately and agrees to 2.8e-7, so the transcendentals are the whole of it. The tolerance says so and records the number rather than being quietly widened. Kept out of the default suite. CI is ubuntu-latest with no GPU, and the suite that gates pull requests should be one that can run there. It also skips rather than fails when no adapter is present. Two things about the environment, since both cost time: navigator.gpu is absent on about:blank because WebGPU needs a secure context, so the page is served from 127.0.0.1 — with that, headless works, which is usually what "WebGPU is unavailable headless" turns out to mean. And Playwright's bundled Chromium does not ship WebGPU; the system Chrome does. Closes #29
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.
Closes #29
What was missing
Twelve shaders, eighteen test files, and nothing executed WGSL —
requestAdapterandcreateShaderModuleappear in zero of them. Every test exercises a TypeScript reference, so the shader and the reference were two transcriptions of the same intent with nothing holding them together. One name admits it:refTernaryGemvShaderLogic.The kernels are not in doubt. The model runs in a browser and produces coherent text, which is a stronger signal than any unit test. What was missing is per-kernel verification, and the reason to want it now is fusion —
activation→elementwise(multiply)inFFN.forwardGatedis two dispatches where one would do, andBitLinear.forwardruns RMSNorm and Quantize separately despite a comment calling them fused. "Still coherent" cannot tell a correct fusion from a subtly wrong one.What this covers
Seven shaders —
rmsnorm,activation,elementwise,softmax,rope,quantize,dequantize— withDchosen to span the 256-wide workgroup: below it, exactly on it, and not a multiple of it, since the strided loop and the reduction each depend on which.Five are left:
attention,embedding,f32_matmul,ternary_gemm,ternary_gemv. They need packed I2_S data and embedding tables constructed first, which is its own piece of work rather than a footnote to this one.Two findings
epsin rmsnorm was untested. Removing it entirely still passed — on ordinary inputsumSq/Ddwarfs it. There is now an all-but-zero row where it is the only thing standing betweeninverseSqrtand a division by zero.ropecannot be checked to f32 precision, and the reason is the hardware. Measured on this GPU:sin/cospow(measured separately)Three orders of magnitude worse than f32, and RoPE calls both per element — which fully accounts for its 3.37e-4. The shader and the reference compute the same expression; I checked. So the tolerance is loosened on measurement, with the number recorded in the test, rather than quietly widened until it went green.
Worth knowing generally: no shader using
sin,cosorexpcan be validated tighter than this.Proven to catch damage
Mutating
rmsnorm.wgslto drop theweightmultiply fails 4 tests. Dropping a reduction barrier fails too. A harness that only ever passes would be worse than none.Kept out of
npm testCI is
ubuntu-latestwith no GPU, and the suite that gates pull requests should be one that can run there. Separate config, separate script (npm run test:gpu), excluded from the default include. It also skips rather than fails when no adapter is present, so it does not punish a contributor without one.Two environment notes, since both cost time
navigator.gpuis absent onabout:blank. WebGPU needs a secure context, so the page is served from127.0.0.1. With that, headless works — which is usually what "WebGPU is unavailable headless" turns out to mean.CHROME_PATHoverrides the default/opt/google/chrome/chrome.Next
Fusing
activation+elementwise(multiply)into one dispatch, and RMSNorm + Quantize inBitLinear. Both can now be shown to match what they replace.