You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Float8E4M3: fn (float8_e4m3fn) is now the DEFAULT overflow convention - 4.14.0-local.2
Correctness over compatibility (lib <4mo, no external users): the cast operator
and the IR-level convert now use the fn convention - finite overflow AND +-Inf
-> NaN - making Float8E4M3 bit-exact to PyTorch/JAX/ml_dtypes float8_e4m3fn, the
dtype it is named after. Was: saturate to +-448. Since the change is at the IR
convert, the cast, PrecisionConvert, and the generic INumber<T> path are all
consistently fn.
Changed the E4M3 overflow branch in the managed conversion AND all 6 backend
emitters (PTX/OpenCL/WGSL/GLSL/Wasm bit-manip): e>8 -> NaN, and the post-round
carry reaching the 0x7F slot -> NaN (check the FULL result, not the masked low 7
bits, so the 0x80 carry is caught). 449..464 round DOWN to 448 under both
conventions; only |x|>464 differs (fn: NaN, saturating: +-448).
Saturating is now opt-in via FromSingleSaturating / FromSingle(x, saturate:true):
finite overflow -> +-448, +-Inf -> NaN. Built from a BIT-level finite check
(exponent != all-ones, NOT a float compare vs MaxValue - that is unreliable for
+-Inf on WebGL) + the fn cast + a >464 redirect, so it transpiles with no
per-backend codegen.
Gates: fp8-oracle cast/FromSingleFn/FromSingleSaturating all 1099/1099 vs the
reference (decode 0/256, E5M2 723/723); fp8-verify desktop 24/24; PMT
Float8E4M3_FromSingleFn_OverflowToNaN 9/0 all lanes (cast fn + saturating, incl
the WebGL Inf fix); no regression: PrecisionConvert 37/0, Fp8Radix 44/0. Forks 2.0.29.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
## 4.14.0-local.2 (2026-06-17) - Float8E4M3 is now bit-exact to float8_e4m3fn (overflow → NaN), saturating opt-in
6
6
7
-
Additive new API on `Float8E4M3`(forks bump to `2.0.28`). No change to any existing behavior - the cast operator is unchanged.
7
+
`Float8E4M3`float→fp8 conversion changed from saturating to the `fn` (`float8_e4m3fn`) convention as the DEFAULT, matching the dtype it is named after. Forks bump to `2.0.29`.
8
8
9
-
-**Validated `Float8E4M3` / `Float8E5M2` conversions against the `ml_dtypes` reference** (the impl PyTorch / JAX `float8_e4m3fn` / `float8_e5m2` share) with a new evidence harness `DemoConsole -- fp8-oracle` (generators in `_research/fp8_oracle/`). Result: decode is bit-exact (0/256) and encode rounding/subnormal is bit-exact (0 divergences across 1099 / 723 probes) for both types. The **only** divergence was E4M3 finite overflow: ILGPU saturated to ±448 (the NVIDIA Transformer Engine / OCP saturating cast), whereas the dtype literally named `e4m3fn` overflows to **NaN**. Both are real-world conventions; they agree everywhere except `|x| > 464` (the region that rounds up past the 448 slot).
10
-
-**Made the overflow convention selectable.** The bare cast `(Float8E4M3)x`, `Float8E4M3.FromSingleSaturating(x)`, and `FromSingle(x, saturate: true)` keep the **saturating** behavior (finite overflow → ±448, ±Inf → NaN). New **`Float8E4M3.FromSingleFn(x)`** / `FromSingle(x, saturate: false)` use the **fn** convention (finite overflow AND ±Inf → NaN), **bit-exact to PyTorch / JAX / ml_dtypes `float8_e4m3fn`** - use it for reference-matching ML (loading/comparing FP8 checkpoints). `FromSingleFn` is composed only of existing intrinsics (compare + the saturating cast + Neg + cast-of-NaN), so it transpiles with **no per-backend conversion codegen** and is bit-exact on all 6 backends.
11
-
- Gates: `DemoConsole -- fp8-oracle` (managed `FromSingleFn` 1099/1099 vs `float8_e4m3fn`; saturating cast's 62 overflow points reported as the documented convention) + `fp8-verify` desktop kernel (`FromSingleFn` 24/24 bit-exact on CPU/OpenCL/CUDA) + **PMT `Float8E4M3_FromSingleFn_OverflowToNaN` 9/0 across all backend lanes** (CPU/CUDA/OpenCL/WebGPU/WebGPU-NoSubgroups/WebGL/Wasm). No regression to existing FP8/bf16/Half gates. `Float8E5M2` already matched its reference (overflow → ±Inf); its canonical NaN byte is `0x7F` (ml_dtypes uses `0x7E` - both valid NaN patterns).
9
+
-**Validated `Float8E4M3` / `Float8E5M2` conversions against the `ml_dtypes` reference** (the impl PyTorch / JAX `float8_e4m3fn` / `float8_e5m2` share) with a new evidence harness `DemoConsole -- fp8-oracle` (generators in `_research/fp8_oracle/`). Decode is bit-exact (0/256) and encode rounding/subnormal bit-exact (0 divergences across 1099 / 723 probes) for both. The one prior divergence was E4M3 finite overflow: ILGPU **saturated to ±448** while `e4m3fn`**overflows to NaN**. The two agree everywhere except `|x| > 464` (the region that rounds up past the 448 slot; `449..464` round **down** to 448 under both).
10
+
-**Default flipped to `fn` (overflow → NaN), made selectable.** The cast operator `(Float8E4M3)x`, `Float8E4M3.FromSingleFn(x)`, and `FromSingle(x, saturate: false)` now map finite overflow AND ±Inf → NaN - **bit-exact to PyTorch / JAX / ml_dtypes `float8_e4m3fn`**. The change is at the **IR-level convert**, so every float→fp8 path (the cast, `PrecisionConvert`, the generic `INumber<T>` path) is consistently `fn`. This required updating the E4M3 overflow branch in the managed conversion AND all 6 backend emitters (PTX / OpenCL / WGSL / GLSL / Wasm bit-manip): `e>8` → NaN, and the post-round carry that reaches the `0x7F` slot → NaN (checking the full result, not the masked low 7 bits, so the `0x80` carry is caught).
11
+
-**Saturating is now opt-in.**`Float8E4M3.FromSingleSaturating(x)` / `FromSingle(x, saturate: true)` clamp finite overflow to ±448 (±Inf → NaN) - the NVIDIA Transformer Engine saturating cast / OCP mode. It is composed of existing intrinsics (a **bit-level** finite check - exponent ≠ all-ones, NOT a float compare against `MaxValue`, which is unreliable for ±Inf on WebGL - plus the fn cast and a `>464` redirect), so it transpiles with no per-backend codegen and is bit-exact on all 6 backends.
12
+
- Gates: `DemoConsole -- fp8-oracle` (cast operator / `FromSingleFn` / `FromSingleSaturating` all 1099/1099 vs the reference; decode 0/256; E5M2 723/723) + `fp8-verify` desktop kernels (`FromSingleFn` 24/24 bit-exact CPU/OpenCL/CUDA) + **PMT `Float8E4M3_FromSingleFn_OverflowToNaN` 9/0 all backend lanes** (cast operator fn + `FromSingleSaturating`, incl WebGPU/WebGL/Wasm) + no regression: `PMT_FILTER=PrecisionConvert` 37/0, `PMT_FILTER=Fp8Radix` 44/0. `Float8E5M2` already matched its reference (overflow → ±Inf); its canonical NaN byte is `0x7F` (ml_dtypes uses `0x7E` - both valid NaN patterns).
13
+
-_Supersedes the unreleased local.1 (which had kept the cast operator saturating); local.1 was never promoted._
12
14
13
15
## 4.13.2 (2026-06-16) - Packaging fix (no code changes)
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -147,7 +147,7 @@ If total > 10: `InvalidOperationException` at dispatch time (v4.9.1+). Before v4
147
147
148
148
`ArrayView<byte>`, `ArrayView<sbyte>`, `ArrayView<short>`, `ArrayView<ushort>`, `ArrayView<Half>` (ILGPU.Half), `ArrayView<BFloat16>` (ILGPU.BFloat16) supported on all 6 backends.
149
149
150
-
**Use `ILGPU.Half`, NOT `System.Half`** in kernel signatures. Implicit conversion operators exist for interop. Same for **`ILGPU.BFloat16`** (the "brain float": top 16 bits of an fp32, so fp32's full dynamic range - the ML-weights trade vs `Half`) and the two FP8 types **`ILGPU.Float8E4M3`** (forward/inference, no Inf; **selectable overflow**: the cast/`FromSingleSaturating` clamps to ±448 = NVIDIA TE/OCP,`FromSingleFn` → NaN = bit-exact PyTorch/JAX `float8_e4m3fn`) + **`ILGPU.Float8E5M2`** (backward/gradient, IEEE Inf/NaN). bf16/FP8 detail: [Docs/data-type-support.md](Docs/data-type-support.md). On CUDA bf16 + FP8 use an f32-register-compute model (no native PTX bf16/fp8 arithmetic); the load/store conversion is **PORTABLE bit-manipulation (basic integer ops on every CUDA arch incl. pre-Ampere)** - 4.13.0+ replaced the sm_80-only `cvt.*.bf16` shortcut that broke on older cards. The browser/OpenCL/Wasm backends emulate the same exact conversion, byte-identical to CUDA.
150
+
**Use `ILGPU.Half`, NOT `System.Half`** in kernel signatures. Implicit conversion operators exist for interop. Same for **`ILGPU.BFloat16`** (the "brain float": top 16 bits of an fp32, so fp32's full dynamic range - the ML-weights trade vs `Half`) and the two FP8 types **`ILGPU.Float8E4M3`** (forward/inference, no Inf; **selectable overflow, default = fn**: the cast operator / `FromSingleFn` map overflow → NaN = bit-exact PyTorch/JAX `float8_e4m3fn`; `FromSingleSaturating` clamps to ±448 = NVIDIA TE/OCP) + **`ILGPU.Float8E5M2`** (backward/gradient, IEEE Inf/NaN). bf16/FP8 detail: [Docs/data-type-support.md](Docs/data-type-support.md). On CUDA bf16 + FP8 use an f32-register-compute model (no native PTX bf16/fp8 arithmetic); the load/store conversion is **PORTABLE bit-manipulation (basic integer ops on every CUDA arch incl. pre-Ampere)** - 4.13.0+ replaced the sm_80-only `cvt.*.bf16` shortcut that broke on older cards. The browser/OpenCL/Wasm backends emulate the same exact conversion, byte-identical to CUDA.
151
151
152
152
**Per-backend implementation:**
153
153
-**WebGPU:** Packed into `array<atomic<u32>>`. Load via atomicLoad + shift + mask. Store via atomicAnd + atomicOr (thread-safe sub-word writes). Float16 load/store calls `_f16_to_f32` / `_f32_to_f16` helpers from `WGSLEmulationLibrary.F16Functions` when `!shader-f16`; native WGSL `f16` type otherwise. `WebGPUBackend.ForceEmulatedF16` test flag forces the emulation path for verification.
Copy file name to clipboardExpand all lines: Docs/data-type-support.md
+15-13Lines changed: 15 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -187,22 +187,24 @@ backend** (CPU-verified idempotence 0/256 for all representable values).
187
187
|**CUDA**| f32-register model. The FP8<->f32 conversion is **inline PTX bit-manipulation** (branchless `setp`/`selp`, unrolled normalize) using only basic integer ops - FP8 has no portable native PTX cvt (`cvt.*.e4m3` is sm_89/Hopper only), so this works on every CUDA arch. Load = `ld.global.u8` + convert; store = convert + `st.global.u8`. |
188
188
|**CPU**| Native - the managed `Float8E4M3`/`Float8E5M2` structs run directly. |
189
189
190
-
> **Convention note (E4M3 overflow) - SELECTABLE.**E4M3 has two real-world overflow conventions and
191
-
> both are exposed; the conversion is otherwise **bit-exact** to the `ml_dtypes` reference (the impl
0 commit comments