Skip to content

Commit 48c9dc0

Browse files
LostBeardclaude
andcommitted
FP4 (Float4E2M1) browser backends (WGSL/GLSL/Wasm) + host scalar-param fix - FP4 now on ALL 6 backends - 4.14.0-local.5
Completes Float4E2M1 (OCP E2M1FN, the NVFP4/MXFP4 element format) across every backend. The convert/decode is now BIT-EXACT to ml_dtypes.float4_e2m1fn on CPU, CUDA, OpenCL, WebGPU, WebGL, and Wasm. Four-package bundle: forks 2.0.31 -> 2.0.32, SpawnDev.ILGPU 4.14.0-local.4 -> local.5. Browser convert codegen (mirrors the FP8 WGSL/GLSL/Wasm paths; the _e2m1<->f32 helpers are line-for-line ports of the CPU/OpenCL/CUDA-verified managed conversion, byte-identical across all 6): - WebGPU/WGSL: _e2m1_to_f32 / _f32_to_e2m1 in WGSLEmulationLibrary (minimal-trim gated) + type map (f32) + FloatAsInt/IntAsFloat + const + every sub-word classification/scalar/load/store/LEA site. - WebGL/GLSL: _e2m1_to_f32 / _f32_to_e2m1 in GLSLEmulationLibrary + type map + FloatAsInt/IntAsFloat + const + body-struct/param/load(texelFetch)/store(TF) sites. - Wasm: EmitFP4ToF32 / EmitF32ToFP4 inline bytecode + load (i32.load8_u) / store (i32.store8) / const + the struct-wrapper mis-lowering store guard. Host sub-word SCALAR-param fix (Rule 2a - this was a latent FP8 gap too; FP8 only had buffer round-trip tests, never a scalar-param test): - A by-value 1-byte float scalar (scale/bias in the generic relu kernel) fell through the host scalar marshaling with no FP8/FP4 case: Wasm struct-serialized it (kernel read garbage -> "got 2"), WebGL never sent the uniform (arrived as 0). Added FP8 (E4M3/E5M2) + FP4 to the host widen-to-f32 path next to Half/bf16 in WasmAccelerator + WebGLAccelerator (the f32-register model; the scalar param is declared f32/float on these backends, so pass the widened value, lossless). Capability + tests: - AcceleratorRequirements.RequiresFloat4E2M1 + HasFloat4() (always-true no-op filter like bf16/FP8). - PMT BackendTestBase.GenericPrecision: PrecisionConvert_Float4E2M1_RoundTripBitExact, GenericPrecision_Float4E2M1_RunsAndMatchesCpu (FP4-exact scale=1.5/bias=0.5 scalar params), Float4E2M1_FloatToFP4_RneSaturateNaN (decode/overflow/NaN bit-exact vs managed). - AcceleratorRequirementsTests.Satisfies_LowPrecisionFloats extended with FP4. Gates: PMT Float4E2M1 23/0 ALL 6 backends; GenericPrecision (Half/bf16/FP4 scalar relu) 30/0; AcceleratorRequirements 19/0/1; no regression (PrecisionConvert_Float8 16/0, desktop fp8-verify 257/257 + fp4-verify CPU/OpenCL/CUDA bit-exact, bf16-f16-oracle 65536/65536). NEXT (separate commit): FP4 radix-sort keys, then INT4, then MXFP4/NF4 dequant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1f29845 commit 48c9dc0

20 files changed

Lines changed: 891 additions & 21 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
This file tracks notable changes per release. The README's "Recent Highlights" section links here for the full version history.
44

5+
## 4.14.0-local.5 (2026-06-17) - New 4-bit float type `Float4E2M1` (NVFP4/MXFP4 element format) on all 6 backends + a latent low-precision store-widening bug fix
6+
7+
Adds `ILGPU.Float4E2M1`, the OCP **E2M1FN** 4-bit float (the element format of NVFP4 / MXFP4): 1 sign / 2 exp / 1 mantissa, bias 1, **16 finite codes (no Inf, no NaN)**, magnitudes `{0,.5,1,1.5,2,3,4,6}`, max 6, finite overflow + ±Inf saturate to ±6, NaN→-0. 1-byte storage (value in the low nibble), f32-register compute. Forks bump to `2.0.32`. Bit-exact to `ml_dtypes.float4_e2m1fn` (PyTorch/JAX share it).
8+
9+
- **First-class IR primitive on all 6 backends** (CPU, CUDA, OpenCL, WebGPU, WebGL, Wasm), mirroring the FP8 wiring: `BasicValueType.Float4E2M1` + `ArithmeticBasicValueType.Float4E2M1` (appended at the end - all existing ordinals + positional type tables unchanged), `INumber<Float4E2M1>` (E2M1 has neither Inf nor NaN), const-fold, `Interop.FloatAsInt`, and `AcceleratorRequirements.RequiresFloat4E2M1` (always-true no-op filter like bf16/FP8). The float↔FP4 conversion is **portable bit-manipulation** on every backend (no native cvt → works on every CUDA arch incl. pre-Ampere 1080/2060), byte-identical across all 6.
10+
- **Fixed a real latent low-precision store bug (Rule 2a).** On PTX the "low-precision value stored to a wider buffer" guard (`floatBuf[i] = (float)lowpBuf[i]`) covered only bf16, so an FP8 **or** FP4 decode-to-float emitted `st.b8` into the 4-byte slot → read back ~0. Found via the FP4 decode kernel + a PTX dump; generalized the guard to bf16 + Float8E4M3 + Float8E5M2 + Float4E2M1. FP8 decode-to-float was silently broken (untested) before this.
11+
- **Fixed FP8/FP4 by-value sub-word SCALAR params on Wasm + WebGL.** A by-value 1-byte float scalar (e.g. `scale`/`bias` in a generic relu kernel) fell through the host scalar-marshaling switch (no FP8/FP4 case) → struct-serialized (Wasm: garbage) / uniform never sent (WebGL: arrived as 0). Added FP8 + FP4 to the host widen-to-f32 path next to Half/bf16 (the f32-register model). This was a latent FP8 gap too - FP8 only had buffer round-trip tests, never a scalar-param test.
12+
- Gates: PMT `Float4E2M1` (round-trip + generic relu with FP4 scalar params + float→FP4 RNE/saturate/NaN) **23/0 all 6 backends**; `GenericPrecision` (Half/bf16/FP4 scalar relu) 30/0; `AcceleratorRequirements` 19/0/1; no regression (`PrecisionConvert_Float8` 16/0, desktop `fp8-verify` 257/257, `bf16-f16-oracle` 65536/65536). New harness `DemoConsole -- fp4-verify` (CPU/OpenCL/CUDA bit-exact: convert 24/24, decode 16/16, relu 256/256). Radix-sort keys for `Float4E2M1` follow in a later release.
13+
514
## 4.14.0-local.4 (2026-06-17) - Low-precision data-type parity to 100% (selectable saturating cast + complete radix grid; fixes a WebGL FP8-struct-field bug)
615

716
Closes the last parity gaps across the four low-precision float types (`Half`, `BFloat16`, `Float8E4M3`, `Float8E5M2`) so data-type support is feature-complete with no lingering items. Forks bump to `2.0.31`.

ILGPU.Algorithms/ILGPU.Algorithms.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
SpawnDev.ILGPU.Fork* PackageReference Versions inside SpawnDev.ILGPU.csproj.
1313
Run `_check-fork-version-sync.bat` at repo root. See the banner comment in
1414
SpawnDev.ILGPU.csproj for the full procedure. -->
15-
<Version>2.0.31</Version>
15+
<Version>2.0.32</Version>
1616
<IsPackable>true</IsPackable>
1717
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1818
</PropertyGroup>

ILGPU/ILGPU.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
check on push. Skipping (b) means consumers ship rc.N still pulling old Fork
1717
transitively and the fix is invisible. See the banner comment in
1818
SpawnDev.ILGPU.csproj for the full procedure. -->
19-
<Version>2.0.31</Version>
19+
<Version>2.0.32</Version>
2020
<IsPackable>true</IsPackable>
2121
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
2222
</PropertyGroup>

SpawnDev.ILGPU.Demo.Shared/UnitTests/BackendTestBase.GenericPrecision.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ private async Task RunFP8RoundTrip<T>(Func<float, T> toT, Func<T, float> toF)
8484
=> await RunTest(async accelerator =>
8585
await RunPrecisionRoundTripCore<T>(accelerator, toT, toF));
8686

87+
// FP4 (Float4E2M1) round-trip on all 6 backends (1-byte storage, value in the low nibble,
88+
// f32-register compute, portable conversion incl. CUDA). Bit-exact vs the concrete (T)(float)x.
89+
[TestMethod]
90+
public async Task PrecisionConvert_Float4E2M1_RoundTripBitExact() =>
91+
await RunTest(async accelerator =>
92+
await RunPrecisionRoundTripCore<global::ILGPU.Float4E2M1>(accelerator,
93+
v => (global::ILGPU.Float4E2M1)v, v => (float)v));
94+
8795
private async Task RunPrecisionRoundTrip<T>(Func<float, T> toT, Func<T, float> toF)
8896
where T : unmanaged, INumber<T>
8997
=> await RunTest(async accelerator =>
@@ -409,5 +417,94 @@ public async Task Float8E4M3_FromSingleFn_OverflowToNaN() => await RunTest(async
409417
$"want 0x{expSat[i]:X2} (saturating: finite overflow -> +-448, +-Inf -> NaN).");
410418
}
411419
});
420+
421+
// FP4 (Float4E2M1) generic INumber relu kernel on all 6 backends - load/store, in-kernel
422+
// arithmetic, FP4 const, compare+select, AND by-value FP4 sub-word scalar params (scale/bias).
423+
// scale=1.5 / bias=0.5 are EXACTLY representable in E2M1 (so the FP4-rounded scalar params match
424+
// their float values); the reference rounds per the f32-register model (round once at the end).
425+
// E2M1 has 1 mantissa bit so per-op (CPU) vs round-once (GPU) differ by <=1 step - 0.55 rel tol.
426+
[TestMethod]
427+
public async Task GenericPrecision_Float4E2M1_RunsAndMatchesCpu() => await RunTest(async accelerator =>
428+
{
429+
const int n = 256;
430+
var st = (global::ILGPU.Float4E2M1)1.5f;
431+
var bt = (global::ILGPU.Float4E2M1)0.5f;
432+
float sf = (float)st, bf = (float)bt;
433+
var x = new global::ILGPU.Float4E2M1[n];
434+
var expected = new float[n];
435+
for (int i = 0; i < n; i++)
436+
{
437+
// Cover all 16 input codes repeatedly (low nibble), so every representable FP4 input is hit.
438+
byte raw = (byte)(i & 0x0F);
439+
x[i] = System.Runtime.CompilerServices.Unsafe.As<byte, global::ILGPU.Float4E2M1>(ref raw);
440+
float vf = (float)x[i] * sf + bf;
441+
expected[i] = vf > 0f ? vf : 0f;
442+
}
443+
444+
using var inBuf = accelerator.Allocate1D(x);
445+
using var outBuf = accelerator.Allocate1D<global::ILGPU.Float4E2M1>(n);
446+
var k = accelerator.LoadAutoGroupedStreamKernel<Index1D,
447+
ArrayView1D<global::ILGPU.Float4E2M1, Stride1D.Dense>,
448+
ArrayView1D<global::ILGPU.Float4E2M1, Stride1D.Dense>,
449+
global::ILGPU.Float4E2M1, global::ILGPU.Float4E2M1>(FusedReluGeneric<global::ILGPU.Float4E2M1>);
450+
k(n, inBuf.View, outBuf.View, st, bt);
451+
await accelerator.SynchronizeAsync();
452+
var got = await outBuf.CopyToHostAsync<global::ILGPU.Float4E2M1>();
453+
454+
for (int i = 0; i < n; i++)
455+
{
456+
float g = (float)got[i];
457+
float tol = MathF.Max(MathF.Abs(expected[i]), 1f) * 0.55f;
458+
if (MathF.Abs(g - expected[i]) > tol)
459+
throw new Exception(
460+
$"Generic Float4E2M1 kernel @{i} ({BackendName}): in {(float)x[i]} -> got {g}, want " +
461+
$"{expected[i]} (tol {tol}) - FP4 by-value sub-word scalar params + arith must transpile.");
462+
}
463+
});
464+
465+
// FP4 (Float4E2M1) float->FP4 convert (E2M1FN, the NVFP4/MXFP4 element format): RNE among the 16
466+
// codes; finite overflow AND +-Inf saturate to +-6 (0x7/0xF); NaN -> -0 (0x8). Bit-exact vs the
467+
// managed conversion (CPU-verified bit-exact to ml_dtypes.float4_e2m1fn). Proves every backend's
468+
// f32->FP4 emitter matches - the overflow-saturate + NaN->-0 branches are the point.
469+
private static void Float4ConvertKernel(Index1D i,
470+
ArrayView1D<float, Stride1D.Dense> x, ArrayView1D<global::ILGPU.Float4E2M1, Stride1D.Dense> y) =>
471+
y[i] = (global::ILGPU.Float4E2M1)x[i];
472+
473+
[TestMethod]
474+
public async Task Float4E2M1_FloatToFP4_RneSaturateNaN() => await RunTest(async accelerator =>
475+
{
476+
float[] inputs =
477+
{
478+
0f, 0.5f, 1f, 1.5f, 2f, 3f, 4f, 6f, // exact magnitudes
479+
0.25f, 0.7f, 1.25f, 2.5f, 5f, 0.75f, // RNE (0.25->0, 2.5->2, 5->4 ties-to-even)
480+
7f, 100f, 1e30f, float.PositiveInfinity, // overflow + +Inf -> +6
481+
-0.5f, -1.5f, -6f, -7f, -100f, float.NegativeInfinity, // negatives + -Inf -> -6
482+
float.NaN, -0f, // NaN -> 0x8 (-0); -0 -> 0x8
483+
};
484+
int n = inputs.Length;
485+
var expected = new byte[n];
486+
for (int i = 0; i < n; i++)
487+
{
488+
var v = (global::ILGPU.Float4E2M1)inputs[i]; // managed = oracle-proven reference
489+
expected[i] = (byte)(System.Runtime.CompilerServices.Unsafe.As<global::ILGPU.Float4E2M1, byte>(ref v) & 0x0F);
490+
}
491+
492+
using var inBuf = accelerator.Allocate1D(inputs);
493+
using var outBuf = accelerator.Allocate1D<global::ILGPU.Float4E2M1>(n);
494+
var k = accelerator.LoadAutoGroupedStreamKernel<Index1D,
495+
ArrayView1D<float, Stride1D.Dense>, ArrayView1D<global::ILGPU.Float4E2M1, Stride1D.Dense>>(Float4ConvertKernel);
496+
k(n, inBuf.View, outBuf.View);
497+
await accelerator.SynchronizeAsync();
498+
var got = await outBuf.CopyToHostAsync<global::ILGPU.Float4E2M1>();
499+
500+
for (int i = 0; i < n; i++)
501+
{
502+
byte g = (byte)(System.Runtime.CompilerServices.Unsafe.As<global::ILGPU.Float4E2M1, byte>(ref got[i]) & 0x0F);
503+
if (g != expected[i])
504+
throw new Exception(
505+
$"float->Float4E2M1 kernel @{i} ({BackendName}): input {inputs[i]} -> got 0x{g:X1}, " +
506+
$"want 0x{expected[i]:X1} (E2M1FN: RNE, overflow/+-Inf -> +-6, NaN -> 0x8).");
507+
}
508+
});
412509
}
413510
}

SpawnDev.ILGPU.DemoConsole/AcceleratorRequirementsTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,22 @@ public Task Satisfies_Atomics_PassesOnCpu()
5959
public Task Satisfies_LowPrecisionFloats_AllDevicesPass()
6060
{
6161
using var context = Context.CreateDefault();
62-
// bf16 + both FP8 formats are supported (always emulated) on every backend, so these are
62+
// bf16 + both FP8 formats + FP4 are supported (always emulated) on every backend, so these are
6363
// no-op documentation filters - every device must satisfy them, including combined.
6464
var req = new AcceleratorRequirements
6565
{
6666
RequiresFloat16 = true,
6767
RequiresBFloat16 = true,
6868
RequiresFloat8E4M3 = true,
6969
RequiresFloat8E5M2 = true,
70+
RequiresFloat4E2M1 = true,
7071
};
7172
foreach (var device in context.Devices)
7273
{
7374
if (!device.Satisfies(req))
7475
throw new Exception(
7576
$"Device {device.AcceleratorType} (name={device.Name}) failed the low-precision-float " +
76-
$"requirements - Half/bf16/FP8 are supported on every backend and must never filter.");
77+
$"requirements - Half/bf16/FP8/FP4 are supported on every backend and must never filter.");
7778
}
7879
return Task.CompletedTask;
7980
}

SpawnDev.ILGPU/AcceleratorRequirements.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ public sealed class AcceleratorRequirements
9696
/// </summary>
9797
public bool RequiresFloat8E5M2 { get; init; }
9898

99+
/// <summary>
100+
/// Kernel uses the 4-bit float <c>ILGPU.Float4E2M1</c> (E2M1FN: 1/2/1, bias 1, no Inf/NaN, 16 finite
101+
/// codes {0,.5,1,1.5,2,3,4,6}, saturates to +-6 - the NVFP4/MXFP4 element format). Every backend
102+
/// supports it - always emulated (1-byte storage with the value in the low nibble, f32-register
103+
/// compute, portable conversion on every backend incl. CUDA). Like <see cref="RequiresBFloat16"/>
104+
/// this is a no-op documentation filter - it never rules out a backend.
105+
/// </summary>
106+
public bool RequiresFloat4E2M1 { get; init; }
107+
99108
/// <summary>
100109
/// Kernel uses Float64 (<c>double</c>). True is compatible with every backend - WebGPU
101110
/// and WebGL run Float64 through Dekker emulation (see <c>CLAUDE.md</c>). Use
@@ -252,6 +261,7 @@ public static bool Satisfies(this Device device, AcceleratorRequirements require
252261
if (requirements.RequiresBFloat16 && !HasBFloat16(device)) return false;
253262
if (requirements.RequiresFloat8E4M3 && !HasFloat8(device)) return false;
254263
if (requirements.RequiresFloat8E5M2 && !HasFloat8(device)) return false;
264+
if (requirements.RequiresFloat4E2M1 && !HasFloat4(device)) return false;
255265
if (requirements.RequiresFloat64 && !HasFloat64(device)) return false;
256266
if (requirements.RequiresFloat64Native && !HasFloat64Native(device)) return false;
257267
if (requirements.RequiresFloat64Strict && !HasFloat64Strict(device)) return false;
@@ -307,6 +317,10 @@ private static bool HasFloat16(Device device)
307317
// f32-register compute, portable conversion on every backend incl. CUDA). Always true.
308318
private static bool HasFloat8(Device device) => true;
309319

320+
// Every backend supports FP4 (Float4E2M1) - always emulated (1-byte storage, value in the low
321+
// nibble, f32-register compute, portable conversion on every backend incl. CUDA). Always true.
322+
private static bool HasFloat4(Device device) => true;
323+
310324
private static bool HasFloat16Native(Device device)
311325
{
312326
// Native-or-nothing: CUDA (SM_53+), OpenCL w/ cl_khr_fp16. WebGPU with shader-f16
@@ -388,6 +402,7 @@ public static string Describe(this AcceleratorRequirements r)
388402
if (r.RequiresBFloat16) flags.Add("BFloat16");
389403
if (r.RequiresFloat8E4M3) flags.Add("Float8E4M3");
390404
if (r.RequiresFloat8E5M2) flags.Add("Float8E5M2");
405+
if (r.RequiresFloat4E2M1) flags.Add("Float4E2M1");
391406
if (r.RequiresFloat64) flags.Add("Float64");
392407
if (r.RequiresFloat64Native) flags.Add("Float64Native");
393408
if (r.RequiresFloat64Strict) flags.Add("Float64Strict");

SpawnDev.ILGPU/SpawnDev.ILGPU.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
7-
<Version>4.14.0-local.4</Version>
7+
<Version>4.14.0-local.5</Version>
88
<!-- Brief current-version highlights only. Full per-version history with code samples lives in CHANGELOG.md (linked from the README). -->
99
<PackageReleaseNotes>4.14.0 brings the four low-precision float types (Half, BFloat16, Float8E4M3, Float8E5M2) to feature-complete parity. A selectable saturating cast (FromSingle(x, saturate) / FromSingleSaturating) is available on every type for the NVIDIA-TE/OCP clamp-instead-of-overflow mode, and radix-sort is complete for all four (keys + key/value pairs, ascending + descending) on all 6 backends. It also fixes low-precision CONVERSION CORRECTNESS against the references. (1) Half (float-&gt;half) is now IEEE round-to-nearest-even on every backend (CPU + WebGPU + WebGL + Wasm), bit-exact to numpy.float16 / PyTorch / CUDA / OpenCL - it previously truncated toward zero and flushed subnormals to zero (diverging from numpy AND from CUDA/OpenCL). (2) Float8E4M3 is now bit-exact to PyTorch/JAX/ml_dtypes float8_e4m3fn: the cast + IR convert use the fn convention (finite overflow AND +-Inf -&gt; NaN; was saturate to +-448); saturating is opt-in via Float8E4M3.FromSingleSaturating. BFloat16 was already bit-exact to ml_dtypes.bfloat16 (verified). All validated exhaustively against ml_dtypes/numpy oracles + cross-backend PMT gates. 4.13.2 is a packaging fix over 4.13.1: removes stray Wasm/repro JSON files that the Razor SDK swept into the package, and bundles the precompiled-shaders precompiler tool (tools/) that 4.13.0/4.13.1 were missing. The 4.13.x line brings full low-precision floating-point support across ALL 6 backends (CPU, OpenCL, WebGPU, WebGL, Wasm, CUDA): Half, BFloat16, and FP8 (Float8E4M3 + Float8E5M2) - including FP8 radix-sort keys (4.13.1) - plus generic INumber&lt;T&gt; mixed-precision kernels, PrecisionConvert, and bf16/FP8 portability to pre-Ampere CUDA cards (GTX 1080 / RTX 2060). Full per-version history with code samples: CHANGELOG.md at https://github.com/LostBeard/SpawnDev.ILGPU/blob/master/CHANGELOG.md</PackageReleaseNotes>
1010
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@@ -65,8 +65,8 @@
6565
<ProjectReference Include="..\ILGPU.Algorithms\ILGPU.Algorithms.csproj" />
6666
</ItemGroup>
6767
<ItemGroup Condition="!Exists('$(MSBuildThisFileDirectory)..\ILGPU\ILGPU.csproj')">
68-
<PackageReference Include="SpawnDev.ILGPU.Fork" Version="2.0.31" />
69-
<PackageReference Include="SpawnDev.ILGPU.Algorithms.Fork" Version="2.0.31" />
68+
<PackageReference Include="SpawnDev.ILGPU.Fork" Version="2.0.32" />
69+
<PackageReference Include="SpawnDev.ILGPU.Algorithms.Fork" Version="2.0.32" />
7070
</ItemGroup>
7171

7272
<ItemGroup>

SpawnDev.ILGPU/Wasm/Backend/WasmCodeGenerator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ protected static byte GetWasmTypeFromIR(TypeNode type)
299299
BasicValueType.BFloat16 => WasmOpCodes.F32, // bf16 promoted to f32 for compute
300300
BasicValueType.Float8E4M3 => WasmOpCodes.F32, // FP8 promoted to f32 for compute
301301
BasicValueType.Float8E5M2 => WasmOpCodes.F32,
302+
BasicValueType.Float4E2M1 => WasmOpCodes.F32, // FP4 promoted to f32 for compute
302303
BasicValueType.Float32 => WasmOpCodes.F32,
303304
BasicValueType.Float64 => WasmOpCodes.F64,
304305
_ => WasmOpCodes.I32,
@@ -324,6 +325,7 @@ protected static int GetElementSize(BasicValueType type)
324325
BasicValueType.BFloat16 => 2, // bf16: 2-byte storage
325326
BasicValueType.Float8E4M3 => 1, // FP8: 1-byte storage
326327
BasicValueType.Float8E5M2 => 1,
328+
BasicValueType.Float4E2M1 => 1, // FP4: 1-byte storage (4-bit value in low nibble)
327329
BasicValueType.Float32 => 4,
328330
BasicValueType.Float64 => 8,
329331
_ => 4,
@@ -1343,6 +1345,8 @@ public virtual void GenerateCode(PrimitiveValue value)
13431345
? (float)value.Float8E4M3Value
13441346
: value.BasicValueType == BasicValueType.Float8E5M2
13451347
? (float)value.Float8E5M2Value
1348+
: value.BasicValueType == BasicValueType.Float4E2M1
1349+
? (float)value.Float4E2M1Value
13461350
: value.Float32Value;
13471351
WasmModuleBuilder.EmitF32Const(Code, f32Const);
13481352
break;

0 commit comments

Comments
 (0)