Intermediate and advanced workshop validation¶
Executed 2026-09-23 and last re-executed 2026-09-30 on the NVIDIA RTX 4080 SUPER under Ubuntu WSL2. The second review made kernel sizes and strides runtime arguments in every workshop solution, and the fourth review strengthened the fused-GEMM checker. The 2026-09-30 run repeats every check and measurement after the improvements to the portfolio kernels, which the workshop runner shares. Python 3.12.13, PyTorch 2.11.0+cu130, Triton 3.6.0, driver 595.97. This records execution of the separate reference solutions, not learner completion.
Correctness and review¶
| Check | Observed result | Evidence |
|---|---|---|
| Online normalizer, standard library only | 11 named checks passed under python -S |
CPU execution; also repeated at the start of checks.log |
| Five GPU reference implementations | 122 named checks passed, actual GPU execution | checks.log, 133 including the CPU workshop |
| Learning checker regression suite | 15 tests passed under python -S, including six workshop-runner tests |
checker-tests.log |
| Portfolio regressions | 48 of 49 passed; the two-GPU test skipped, and the interpreter-only class, skipped at setup, runs separately (5 passed) | portfolio-regression.log |
| All benchmark variants | Correctness gate passed before timing | Errors and raw samples in the JSON below |
| Static checks | Ruff passed across src, tests, scripts, examples and learning | Local Ruff execution |
| Provenance | Reports use schema version 2: the measured solutions and their shared dependencies match the current sources, checked by tests/test_docs.py; unused learner exercises are not fingerprinted. All measurements re-run on 2026-09-30 |
source_sha256 in the raw reports |
Checks cover ragged dimensions, empty outputs, all supported dtypes, selected strided/broadcast layouts, option variants, invalid metadata, input preservation, fresh outputs, and non-default streams. Backward is checked against FP64 autograd and a central finite difference. Attention is checked against dense FP64 math, including future-value perturbations that must not affect earlier causal outputs.
The pre-implementation review was followed by this assistant self-review; it was not an independent human review.
| Review concern | Resolution / remaining scope |
|---|---|
| Learners could accidentally validate answers as their own | Starter paths remain separate; solution mode is explicitly labeled; TODO/unavailable outcomes return nonzero |
| Intermediate-only selection could pass without running anything | Empty CPU selection returns 3 with instructions to include GPU |
| Both operands in backward may have different strides | Separate y/g strides; both-strided and 8-warp case added |
| Fused GEMM can round too early or mis-handle K=0 | Epilogue acts on FP32 accumulator; FP64 oracle; zero-K and ragged final-group cases. The checker first accepted an FP16 accumulator; an exact FP16 case (2048 + 1 + 1 = 2050 only with one rounding) and a K = 1024 case now reject it, and a GPU test checks that |
| Empty online states can produce NaN | Explicit identity handling, including empty+empty |
| Split softmax could race across stages | Ordered launches on the current stream; private per-call scratch; no global spin barriers |
| Causal attention can read future values or mishandle a changing maximum | Key mask includes diagonal; both numerator and denominator rescaled; perturbation and large-logit cases |
| Timing could reward a numerically different baseline | Baseline contracts recorded explicitly, particularly the FP32 GEMM epilogue comparison |
| Simplified attention could be mistaken for production FlashAttention | Single-head bounded scope, approximate PV precision, no backward/dropout/batching, full future-tile traversal documented |
No new Compute Sanitizer or hardware-counter trace was collected for these workshops. The original native CUDA sanitizer results apply only to that earlier implementation. Correctness comparisons and non-default-stream checks are not a proof that every possible execution is race-free. The available single GPU does not validate multi-GPU behavior.
Measured examples¶
Raw data: graph-fp16.json, events-fp16.json. Readable command output: graph.log, events.log. Each run used FP16 inputs, 7 samples of 20 calls, five warmup calls per variant, and randomized variant order within each sample. Compilation happened before timing, and the GPU was warmed before each case. Both runs covered ten operation/shape cases and 38 variant measurements each (76 total), with every individual sample preserved.
The table selects the same candidate configuration in both timing modes. Latency is the median in microseconds; speedup is baseline/candidate. GEMM shape order is M,N,K; attention is N,D and causal=True. Strided inputs are transposes.
| Operator / shape | Candidate | Graph baseline / candidate (us) | Graph ratio | Event ratio |
|---|---|---|---|---|
| Strided softmax, 256x257 | direct_4w | 3.774 / 2.304 | 1.64x | 0.62x |
| Strided softmax, 1024x1024 | direct_4w | 17.357 / 14.434 | 1.20x | 0.86x |
| Softmax VJP, 256x1025 | fused_vjp | 12.698 / 1.792 | 7.09x | 2.19x |
| Softmax VJP, 1024x4097 | fused_vjp | 123.187 / 13.414 | 9.18x | 2.21x |
| GEMM+bias+ReLU, 127x65x33 | group_4 | 11.264 / 2.304 | 4.89x | 2.74x |
| GEMM+bias+ReLU, 512x512x512 | group_4 | 28.774 / 6.502 | 4.43x | 2.06x |
| Split softmax, 4x32769 | chunk_1024 | 8.074 / 4.915 | 1.64x | 0.14x |
| Split softmax, 64x131072 | chunk_4096 | 23.501 / 18.586 | 1.26x | 0.27x |
| Attention, 129x32 | stream_bn64 | 6.758 / 3.366 | 2.01x | 0.72x |
| Attention, 512x64 | stream_bn64 | 8.499 / 8.235 | 1.03x | 1.01x |
Interpretation matters:
- Strides: direct loads beat copy-then-kernel in the graph runs on these two shapes. The copy is included in timing. This does not prove a universal layout rule.
- Backward: the baseline is an eager FP32 VJP expression including casts and several launches. It is not PyTorch's complete training step or a compiled VJP.
- GEMM: the baseline performs FP32 GEMM/bias/ReLU and then casts, with TF32 disabled. The candidate uses half-precision dot inputs and an FP32 epilogue. These ratios do not establish a win over fused half-precision cuBLASLt. GROUP=1/4/8 were nearly tied at 512-cubed; the dispersion does not support a cache-efficiency claim or a convincing winner among those group sizes.
- Split softmax: chunk_256 at 64x131072 took 49.152 us in graph mode, 0.48x the PyTorch baseline. More programs were not automatically better. In event timing all three split candidates lost on both shapes; Python dispatch and three launches change the result materially.
- Attention: the primary baseline is PyTorch SDPA with automatic backend selection. BN=64 won the small graph case (2.01x) but did not establish a win on the larger case; its 1.03x result is close enough to treat as roughly tied. BN=32 was clearly slower in the larger graph case at 0.76x. In event timing BN=64 measured 0.72x and 1.01x here; earlier runs measured 0.32x and 1.00x (2026-09-25), then 1.09x and 1.37x, and 0.65x and 0.83x (2026-09-26), and 0.77x and 0.94x earlier on 2026-09-30, before the GPU warm-up existed. Event ratios that include Python dispatch of several launches move with host load and do not rank these implementations.
These are local microbenchmarks on a desktop GPU, not isolated-server guarantees or model-level latency results. Min/max dispersion is recorded. No clock lock, peak-memory measurement, DRAM-traffic counter or SDPA-backend trace is claimed. The attention score-storage figures in the lessons are analytic calculations.
Reproduce¶
From the repo root in the configured environment (or with PYTHONPATH=src):
python -S -m learning.workshops.check online_normalizer --solution
python -S -m unittest discover -s learning/tests -v
python -m learning.workshops.check all --include-gpu --solution
KERNEL_REQUIRE_GPU=1 python -m unittest discover -s tests -v
python -m learning.workshops.benchmark --solution --timing graph --output results/local/workshops-graph.json
python -m learning.workshops.benchmark --solution --timing events --output results/local/workshops-events.json
Remove --solution to check or benchmark your own exercise files. Keep new results
under results/local/ until you intentionally add your own documented experiment.