Repository navigation
Fix zero-result sign, f16 zero anchor and e2m1 decoding on Hopper and Blackwell - #18
Merged
Peichen Xie (peichenxie) merged 4 commits intoOct 16, 2026
Merged
Conversation
A nonzero negative sum below the smallest output step came back as -0 (0x8000 for f16, 0x80000000 for f32). H200 and B300 return +0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Simon Veitner <simp.veitner@gmail.com>
A zero f16 C anchors the fused sum at 2^-21 on B300 and B200 for mma.sync and tcgen05.mma, and on H200 for wgmma. The simulator used -22 for Blackwell mma.sync and -133 for wgmma and tcgen05.mma. Hopper mma.sync already uses -21. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Simon Veitner <simp.veitner@gmail.com>
tcgen05.mma kind::f8f6f4 and kind::mxf8f6f4 accept e2m1, but the simulator raised "Unsupported dtype: torch.uint8". Decode one e2m1 per uint8 from bits 5:2 of the 8-bit container and keep its subnormal at exponent 0. B300 and B200 return 0x33000000 for 0.5 * 0.5 with C = -(0.25 - 2^-26), which this now matches. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Simon Veitner <simp.veitner@gmail.com>
simveit
force-pushed
the
sim-corrections
branch
from
October 14, 2026 09:58
24f2315 to
866bdda
Compare
Contributor
Author
|
@microsoft-github-policy-service agree |
Collaborator
|
Thank you for your PR! I can confirm Bullets 1 and 2. I also tested e_zero on RTX Blackwell, and the results were -21/-133 as well. For Bullet 3, could you add support for FP6 and include the corresponding instructions in src/mmasim_kernels? That way, the existing test scripts can test these instructions. |
RTX Blackwell anchors a zero f16 C at 2^-21 like Hopper and Blackwell, so mma.sync now uses e_zero = -21 (f16) / -133 (f32) on all three. tcgen05.mma kind::f8f6f4 and kind::mxf8f6f4 now accept e3m2 and e2m3, held one per uint8 in bits 5:0 (e2m1 stays in bits 5:2). The ISA classes keep the PTX type names, and the FDPA ops decode uint8 operands with the matching subnormal exponent (e3m2 -2, e2m3 0, e2m1 0). New tcgen05 kernels cover m64n8k32 f32/f16 and m128n8k32.block32 for e3m2, e2m3 and e2m1, so test_nv_blackwell.py tests them. The load macros pack 16 elements into 12 (FP6) or 8 (FP4) bytes plus zero padding, as PTX ISA 9.7.17.10.4.4 requires. All 31 Blackwell instructions match B300 bit for bit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Simon Veitner <simp.veitner@gmail.com>
Contributor
Author
included the new instructions :) tried to align with existing conventions, please lmk if there is something more i can change |
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.
Before: in three corner cases, MMA-Sim returns different bits from the hardware on Hopper (H200), Blackwell (B300, B200) and RTX Blackwell. First, when a nonzero sum rounds to zero, the simulator returns −0, but the GPUs return +0. Second, with a zero f16 accumulator, the simulator anchors the fused sum at 2^-22 (Blackwell and RTX Blackwell
mma.sync) or 2^-133 (wgmma,tcgen05.mma), while the GPUs anchor it at 2^-21. Hoppermma.syncalready uses 2^-21 since #17. Third,tcgen05.mma kind::f8f6f4andkind::mxf8f6f4with e2m1 (FP4) operands crash withUnsupported dtype: torch.uint8, and the simulator does not accept FP6 (e3m2, e2m3) operands at all. Random-input tests do not hit the first two cases.After: all three cases match the hardware bit for bit, and FP6 and FP4 work in
kind::f8f6f4andkind::mxf8f6f4. New kernels insrc/mmasim_kernelslettest_nv_blackwell.pytest these instructions. Every other result is unchanged.How:
fdpa.py:ldexp_and_normalizereturns +0 for every NVIDIA rounding mode. The AMDRNE-FP32path is unchanged.sim.py:MMAon Blackwell and RTX Blackwell,WGMMAandTCGen05MMAusee_zero = -21for an f16 C, like HopperMMAafter Release v2.0.3 #17. f32 keeps -133. Peichen Xie (@peichenxie) measured -21/-133 on RTX Blackwell.isa_*.py,helper.pyandfdpa.py: the ISA classes keep the PTX type names (a_ptx_type,b_ptx_type), because e3m2, e2m3 and e2m1 are all held one per uint8.MMA_T_FDPAandMMA_ST_FDPAdecode FP6 from bits 5:0 and e2m1 from bits 5:2 of that byte (PTX ISA §9.7.15.5.14, §9.7.17.10.4.3), with subnormal exponents -2 (e3m2), 0 (e2m3) and 0 (e2m1). Packed mxf4/nvf4 is unchanged.tcgen05mma.h,tcgen05mma_sm100a.cuandblackwell.py: 9 newtcgen05.mmakernels for e3m2, e2m3 and e2m1:kind::f8f6f4m64n8k32 with f32 and f16 output, andkind::mxf8f6f4m128n8k32.block32. They take the same one-value-per-byte input as the simulator. NewLOAD_*_FP6andLOAD_*_FP4macros pack each 16 elements into 12 or 8 bytes plus zero padding (PTX ISA §9.7.17.10.4.4). On B300 the packing is little-endian: FP6 element j sits at bits 6j..6j+5, and FP4 is low nibble first.tests/directed/test_nv_directed.pychecks these cases against the hardware bits and runs on CPU. It includes one e3m2 case that checks the -2 subnormal exponent and that bits 7:6 are ignored.Reproducers
Both reproducers use main's own
mmasim_kernelswrappers. Each prints the GPU bits next to the simulator bits for a single dot product.1. A zero result is +0
A negative sum below the smallest output step rounds to zero.
wgmma/mmabf16 → f320x00x800000000x0wgmma/mmaf16 → f160x00x80000x0tcgen05/mmaf16 → f160x00x80000x0tcgen05/mmabf16 → f320x00x800000000x02. A zero f16 accumulator anchors at 2^-21
With C = 0, the f16 output rounds at a tie. A term at 2^-47 breaks the tie only if the anchor is 2^-22 or lower.
wgmmam64n8k160x00x10x0tcgen05m64n8k16,mmam16n8k16 / m16n8k80x40x30x43. e2m1 operands in
kind::f8f6f4e2m1 code 1 is the subnormal 0.5. The product 0.5 · 0.5 lands on C = -(0.25 - 2^-26), so only the lowest bit of C survives. If the subnormal were normalized instead, the result would be
0x32800000. Main'sblackwell.pyhas no e2m1 kernel, so this script builds one from main'stcgen05mma.h: it reuses the e4m3 m64n8k32 kernel with format code 5.repro_d3.py
0x33000000Unsupported dtype: torch.uint80x330000000x33000000Unsupported dtype: torch.uint80x33000000Hardware
hopper.pyunchanged.blackwell.pywas compiled with-code=sm_103a.blackwell.pyunchanged.Each GPU ran 3 fresh processes per simulator. The outputs were bit-identical across runs.
tests/equivalence/test_nv_hopper.pypasses with this PR on H200, andtest_nv_blackwell.pypasses on B300 and B200, both before and after the e2m1 commit.The +0 rule also covers Volta through Ada and RTX Blackwell, which were not measured. A fixed-point sum carries no sign, so I expect the same behaviour there.
The final commit, which adds FP6, the new kernels and the RTX Blackwell change, was tested on B300 only.
test_nv_blackwell.pynow covers 31 instructions, including the 9 new ones, and all of them match bit for bit.This PR was prepared with AI assistance (Claude Code). I reviewed the changes and the hardware results.
🤖 Generated with Claude Code