Skip to content

Turing runs m8n8k4 with F = 23 (not 24); kind::f8f6f4 reads E2M1's low padding bits as mantissa (RTX 2060 / RTX 5070 Ti measurements) #19

Closed

Description

@PabloSoage

Thanks for MMA-Sim. We have been checking it against our own GPUs with an independent harness, and found two places where hardware and MMA-Sim (commit 54294d4) disagree. Neither instruction has a hardware kernel in mmasim-kernels, which is probably why they slipped through. Everything else we measured matched bit for bit. Details and minimal cases below.

Setup. Our harness is a raw-bit mma.sync kernel written independently of mmasim-kernels: one independent tile per warp (per quad-pair for m8n8k4), with fragment layouts from the PTX ISA. Inputs:

  • half of the tiles are uniform random words;
  • half have operands within a few binades of 1, so that every fractional bit of the fused sum shows;
  • the bits a format does not use (the low 13 bits of TF32, the padding around FP4/FP6) are random on purpose;
  • a set of directed cases.

GPUs and software:

  • RTX 2060 (sm_75), driver 610.62;
  • RTX 5070 Ti (GB203, sm_120a), driver 616.64;
  • A100-PCIE-40GB (sm_80), driver 580.178.04;
  • CUDA 12.9.

1. Turing executes Volta's m8n8k4 with F = 23, not 24

MMA("Turing", "m8n8k4.*") applies Turing's parameters (F = 24; e_zero −21 / −132). On an RTX 2060 the native HMMA.884 (it is native: cuobjdump -sass shows HMMA.884.F32.F32.STEP0..3) behaves like Volta's sum width.

Statistics:

  • Against MMA-Sim: m8n8k4.f32.f16.f16.f32 differs in 49,758 of 262,784 results, and m8n8k4.f32.f16.f16.f16 in 47,429 of 262,720. All differences are ±1 ulp and spread evenly over quad-pairs, rows and columns.
  • With F = 23: a T-FDPA with L = 4, F = 23 and RZ gives 0 mismatches on all three m8n8k4 variants. No other F in 22..26, and no L = 2 chaining, does.
  • Turing's own instructions agree: m16n8k8 (f32 and f16 accumulators) matches MMA-Sim exactly, with 0 mismatches in about 1.05 M results.

Minimal case, the discrepancy example from your Table 6:

  • inputs: a = (−2^13, −0.5, −0.25, −0.125) f16, b = (2^10, 1, 1, 1) f16, c = 2^23 f32, m8n8k4.f32.f16.f16.f32;
  • MMA-Sim (Turing): 0xbf000000 (−0.5);
  • RTX 2060: 0x0, Volta's answer.

Zero accumulator with C in f16. We probed single products a·b with C = +0 (f16) and product exponents from −14 to −28. The significand 2047·1025 ends in binary 1111, so any bits lost in alignment would show. No bits are lost at any exponent. So with F = 23, every e_zero ≤ −25 is consistent: a zero f16 C does not affect the alignment. MMA-Sim's −21 truncates tiny products. Example, m8n8k4.f32.f16.f16.f16, c = +0 (f16):

a (f16) b (f16) MMA-Sim (Turing) RTX 2060
0x07ff 0x0401 0x32000fe0 0x32000ffc (exact)
0x0bff 0x0401 0x32800ff0 0x32800ffc (exact)

Ampere, for comparison. On sm_80, ptxas lowers m8n8k4 to FFMA/FADD/FMUL with no HMMA at all. An A100 then computes (((p0+p1)+p2)+p3)+c in f32 with round-to-nearest-even: 0 mismatches on 233,452 finite f32-output results. So m8n8k4 is a tensor-core instruction only on Volta and Turing. That matches MMA-Sim not listing it for Ampere, but it may be worth a note. The eight Ampere mma.sync instructions MMA-Sim models match on the A100 with 0 mismatches.

2. kind::f8f6f4 reads the low padding bits of an E2M1 byte as mantissa bits

The PTX ISA says only that ".e2m1 resides in central 4 bits of the 8-bit container with padding in the upper 2 bits and lower 2 bits of the container". It does not say whether the padding is ignored. MMA-Sim's decode_uint8_to_fp6_fp4 drops bits 1:0. On an RTX 5070 Ti, the byte is read as E2M3 in bits 5:0: bits 1:0 act as two more mantissa bits, and bits 7:6 are ignored. With zero padding both readings agree, which is presumably the common case.

One-hot probe with m16n8k32.f32.e2m1.e4m3.f32 (mma.sync.aligned.m16n8k32.row.col.kind::f8f6f4): one non-zero byte in A, B = 1.0 (e4m3 0x38), C = 0. 512 tiles per row of the table, every (row, k) position:

A byte MMA-Sim RTX 5070 Ti reading as E2M3
0x08 1.0 1.0 (512/512 positions) 1.0
0x02 0.0 0.25 0.25
0xC9 1.0 1.125 1.125
0xF2 −2.0 −2.5 −2.5

With E2M1 operands decoded as E2M3 from bits 5:0 and everything else as in your T-FDPA (F = 25, L = 32), we get 0 mismatches on all 18 kind::f8f6f4 instructions involving e2m1, f32 and f16 accumulators. That is 14.2 M results across two runs with different seeds, random padding included. On the 10 of these instructions that MMA-Sim models, its current decoding mismatches 58–96 % of the results of the first run.

3. A data point for the e3m2 / e2m3 TODO

For kind::f8f6f4 with e3m2 and e2m3 (in bits 5:0, bits 7:6 random), we used T-FDPA with F = 25 and L = 32, as for the other RTX Blackwell 8-bit formats. It gives 0 mismatches on all 24 combinations without e2m1 (e3m2/e2m3 with each other and with e4m3/e5m2, f32 and f16 accumulators), 18.9 M results over two runs. So adding them to rtx_blackwell_mma_shape_and_type with the existing model looks safe.

Happy to share the raw inputs and outputs (case files, GPU words and SHA-256) or to run specific probes on these GPUs if useful.

Activity

  1. PabloSoage commented on Oct 20, 2026

    @PabloSoage
    Author

    A follow-up on point 2: the same E2M1 reading applies to the block-scaled instruction kind::mxf8f6f4.block_scale on the same RTX 5070 Ti (driver 616.64, CUDA 12.9, sm_120a).

    Setup. As before, our harness is independent of mmasim-kernels:

    • each lane's scale-a-data / scale-b-data word and the four selectors are passed raw, per tile;
    • the selectors are random valid values, not only 0;
    • the bytes and lanes that hold no scale factor are random garbage;
    • 2,048 tiles per instruction. A third of the tiles are fully raw (overflow, underflow and NaN scales included), a third have raw elements with scales near 1, and a third have everything near 1. A few directed cases are added.

    Results with E2M1 padding. With E2M1 decoded as E2M3 from bits 5:0 and everything else as in your ST-FDPA (F = 25, L = 32, RZ, e_zero = −133), all 5 mxf8f6f4 instructions with an e2m1 operand give 0 mismatches. With MMA-Sim's current decoding (decode_uint8_to_fp6_fp4, bits 5:2), the same results mismatch:

    instruction (m16n8k32.block32…ue8m0) results MMA-Sim vs GPU
    f32.e2m1.e2m1.f32 262,784 179,911
    f32.e2m1.e4m3.f32 262,784 160,486
    f32.e4m3.e2m1.f32 262,784 160,609
    f32.e2m1.e5m2.f32 262,784 123,940
    f32.e5m2.e2m1.f32 262,784 123,940

    What is not affected. Our integer re-implementation with your published parameters gives exactly MMA-Sim's bits on all 12 block-scale instructions you model, so these differences come from the E2M1 decoding alone. Against the GPU, MMA-Sim gives 0 mismatches on:

    • the 4 FP8 pairs of mxf8f6f4;
    • the 3 m16n8k64 instructions that compile (mxf4 2X, mxf4nvf4 2X ue8m0, mxf4nvf4 4X ue4m3). There, kind::mxf4/mxf4nvf4 have no padding: E2M1 is a bare nibble.

    A few other data points, in case they are useful:

    • Scale-factor placement: we probed every valid {byte-id, thread-id} and every block, with a distinct value in each lane and byte. SF_A[r][j] is read from lane 4·(r mod 8) + 2·thread-id-a + ⌊r/8⌋, byte byte-id-a + j; SF_B[j][n] from lane 4n + thread-id-b, byte byte-id-b + j. This matches your LOAD_SFA_*/LOAD_SFB_* macros for selector 0.
    • Overflow: with large scales, overflow gives +/−inf (thousands of results), never the largest finite value. So your double-to-float cast for RZ is what the hardware does.
    • The ue4m3 sign bit is ignored, as in truncate_e4m3_to_ue4m3: scale 0xB8 acts as 1.0, and 0xFF is NaN like 0x7F.
    • kind::mxf4 equals kind::mxf4nvf4.scale_vec::2X: on identical inputs the GPU outputs are bit-identical, confirming the comment in mma_sm120a.cu.
    • e3m2 / e2m3 with block scaling: the 16 mxf8f6f4 pairs with an FP6 operand match ST-FDPA with F = 25 (0 mismatches, 262,784 results each). This adds to the e3m2/e2m3 TODO data point above.
    • ptxas rejections (CUDA 12.9): mxf4nvf4.block_scale.scale_vec::4X…ue8m0 is rejected, although the PTX ISA's table of valid combinations lists it, so we could not measure it. scale_vec::2X with ue4m3 is rejected too, which matches that table. So two entries of rtx_blackwell_mma_block_scale_shape_and_type cannot be built with mma.sync on this toolchain: m16n8k64.block16…ue8m0 and m16n8k64.block32…ue4m3.
  2. peichenxie commented on Oct 21, 2026

    @peichenxie
    Collaborator

    Thank you for your feedback! I have added support for these instructions in PR #20 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions