For the complete documentation index, see llms.txt. Markdown versions of all pages are available by appending .md to any URL (e.g. /max/get-started.md).
Mojo function
decode_e2m1_to_f32_inject
def decode_e2m1_to_f32_inject[width: SIMDLength, //](nibble: SIMD[DType.uint16, width]) -> SIMD[DType.float32, width]
Decodes E2M1 nibbles to float32 by exponent injection (Preston's trick).
A branch-free alternative to decode_e2m1_to_f32 with NO select: inject
the 3 value bits (e1 e0 m0) and the sign directly into a float32 bit
pattern at a SHIFTED exponent position, then renormalize with a single
* 2^126 multiply (a power-of-two scale, hence exact). The result is
bit-identical to decode_e2m1_to_f32 / E2M1_TO_FLOAT32[nibble] for all
16 values on a denormal-honoring target (verified host-side, including +-0
and the signed values).
!!! warning "Wrong on flush-to-zero (FTZ) GPUs, including Apple M5"
This trick relies on the +-0.5 E2M1 values (E == 0, m == 1)
producing a denormal float32 intermediate (0x00400000) that
survives until the * 2^126 renormalizes it. On a GPU that flushes
denormals to zero on arithmetic inputs -- the Apple M5 does -- that
intermediate is zeroed and +-0.5 decodes to +-0.0 (verified
on-device: nibbles 1 and 9 mismatch, every other value is exact). Use
decode_e2m1_to_f32 (which builds the normalized value directly, no
denormal intermediate) on Apple GPU and any FTZ target. This function is
retained only for non-FTZ targets and as documentation of why the
injection trick does not port to M5.
Construction (float32 s | 8-bit exp | 23-bit mantissa): place (e1 e0 m0)
at bits 22..24 and the sign at bit 31, leaving a denormalized/small float
whose significand encodes the value; * 2^126 shifts those bits into the
proper exponent range, reproducing 2^(E-1) * (1 + m/2) for E >= 1 and
0.5 * m for E == 0 exactly (the same value the table holds). The
E == 0, m == 1 case is the lone denormal intermediate (see the warning).
NOTE on folding: 2^126 CANNOT be folded into the per-block FP8 scale --
scale * 2^126 overflows float32 for any scale >= 4.0 (fp8_e4m3 scales
routinely exceed this), which corrupts the result. The * 2^126 must be
applied to the injected value FIRST (where the product is <= 6.0, no
overflow), then the caller multiplies by the block scale. So this is NOT a
multiply saved over decode_e2m1_to_f32 -- it trades the select + the
(E+126)<<23 | m<<22 assembly for an extra * 2^126; whether it is faster
is a per-target measurement (on Apple M5 it is moot -- it is wrong, see the
warning -- and the dequant cost there is the scale LOAD, not the arith).
Parameters:
- width (
SIMDLength): SIMD width (lane count) of the nibble vector.
Args:
- nibble (
SIMD[DType.uint16, width]): One E2M1 nibble per lane in the low 4 bits (0..15).
Returns:
SIMD[DType.float32, width]: The decoded values as SIMD[DType.float32, width], bit-identical to
E2M1_TO_FLOAT32[nibble].
Was this page helpful?
Thank you! We'll create more content like this.
Thank you for helping us improve!