Skip to content

Fix/dx12 shader f16 - #4

Merged
dafioram merged 12 commits into
mainfrom
fix/dx12-shader-f16
Nov 5, 2026
Merged

dafioram merged 12 commits into
mainfrom
fix/dx12-shader-f16

Conversation

@dafioram

@dafioram dafioram commented Nov 5, 2026

Copy link
Copy Markdown
Owner

No description provided.

dafioram and others added 12 commits November 3, 2026 14:04
On DX12, wgpu advertises SHADER_F16 only with shader model 6.2 or newer and
native 16-bit shader support. FXC cannot compile for shader model 6.2, so
without dxcompiler.dll the FP16 matmul is skipped. The skip message now says
so when the run is DX12 and no DXC is found. With DXC loaded, it says the
driver reports neither shader model 6.2 nor native 16-bit support.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
Runs the FP16 matmul on the software D3D12 device twice: with FXC, then with
the dxcompiler.dll found in the Windows SDK. It only runs when this file
changes, and it is not part of the build. Drop it once the question is
answered.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
With dxcompiler.dll present, wgpu drops a DX12 adapter whose driver lacks
shader model 6.0, rather than falling back to FXC. The DXC note now says so,
the README warns about it, and --list prints the DX12 compiler in use, so a
run with and without the DLLs can be compared directly. Removes the FP16
experiment workflow, which has answered its question.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
A WARP matmul check failed with 243 of 256 samples wrong and a max error of
59.9, while uncut and llvmpipe runs pass. The log only said how many were
wrong, not what the GPU computed instead.

When a sampled entry is wrong, the first three are now logged with what the
GPU value matches: one K block of the kernel (16 terms) left out or counted
twice, a prefix of K summed alone, a never-written zero, or no simple partial
sum. Passing runs print nothing extra. A unit test covers the matches.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
The WARP run failed 243 of 256 samples, and the three logged entries were
exactly zero, so most of C was never written even though the GPU ran the full
matmul time with no reported error. The count of wrong samples cannot show
which tiles are missing.

On a failed check, the log now prints one character per 64x64 tile of C, from
one sampled entry per tile: '.' never written, 'o' matches the CPU, 'x' wrong.
Whole skipped tile rows or columns will be visible at a glance. Passing runs
print nothing extra. A unit test covers the map.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
The WARP tile map showed only the bottom 64-row band written correctly; the
other 31 bands were never written. Each dispatch still took its full time, so
every dispatch was computing the same band, the last one. The 32 per-band
uniforms were each set with queue.write_buffer before the first submit, so
they appear to have resolved to the last band's offset.

Each uniform is now created already holding its contents, so no write is
pending. The values are the same; only the write path changes. This is not
yet confirmed on WARP: the next WARP run's tile map should be all 'o'.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
The WARP run still wrote only the bottom 64-row band after the uniform change,
and the log does not show which build produced it. To separate per-band
dispatches from the kernel and copy path, a failed FP32 check now runs the whole
matmul once as a single dispatch with a freshly made row offset of 0, then
checks it again and prints a tile map. Failure-only and diagnostic; the
reported result is still the first check. The matmul keeps its A and B buffers
so the retry can bind them.

If the retry passes on WARP, the per-band dispatches are the fault; if it fails
the same way, the kernel or the copy back is.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
On the WARP device, one dispatch per 64-row band wrote only the last band; the
other 31 bands stayed zero. A retry run as one dispatch over all rows checked
0 of 256 bad. Writing the band uniforms at creation did not change the result,
so the fault is in the per-band dispatches themselves, not the kernel or the
copy back. The root cause is not identified.

Software adapters now plan one dispatch over all tile rows. Real GPUs keep the
watchdog-sized bands. The WARP and llvmpipe paths are not yet confirmed on
Windows; the llvmpipe run passes here.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
The WARP run with one dispatch over all rows still wrote only the bottom tile
row, even though the plan now covers all rows in one dispatch. A retry that
makes a fresh bind group just before a single dispatch checked 0 of 256 bad, so
the bind groups made at setup are the fault. Their row offsets read the last
band's value on WARP.

Each band now gets its bind group right before its dispatch, the same as the
passing retry. The setup-time bind groups are gone. Locally, the llvmpipe matmul
passes both with the software plan and with a forced multi-band plan (32 bind
groups), so the new path is exercised. Not yet confirmed on WARP or on a real
GPU's bands.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
The WARP smoke step ran matmul at N=256, which passed on every commit and so
never reached the N=2048 failure that the software adapter hit. It now runs at
N=2048 so CI catches a broken multi-band matmul on the software device.

Build artifacts for Linux and Windows now include README.md alongside the
binaries.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
The planner shrinks N when its probe is slow, and the smaller run still prints
PASS, so the N=2048 step could pass without testing N=2048. The control run
did exactly that (N=1024).

The step now fails if the output contains "auto-reduced", or if no PASS line
names N=2048 for the matmul. The CLI's exit code is still checked first.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
The planner shrinks N when its probe says a band would take over 0.2 s, to
avoid GPU watchdog resets. On the CI runner's WARP the probe measured 2.30
GFLOPS, which is below the 2.68 needed to keep N=2048, so the run became
N=1024 and the new gate failed the step.

A software adapter has no such watchdog. The 32-row single dispatch ran at
N=2048 on the 10.0.19041 WARP without a device loss, so the shrink is skipped
for CPU devices. GPUs keep it.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DS52oJk4dJpQPT7u2QpTr8
@dafioram
dafioram merged commit 092cd33 into main Nov 5, 2026
4 checks passed
@dafioram
dafioram deleted the fix/dx12-shader-f16 branch November 5, 2026 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant