pub enum ScaledFormat {
F8E4M3,
F8E5M2,
F8E4M3Fnuz,
F8E5M2Fnuz,
F6E2M3,
F6E3M2,
F4E2M1,
Custom {
exp_bits: u8,
mant_bits: u8,
bias: i8,
},
}Expand description
Element format for a native low-precision tensor-core GEMM operand
(see crate::op::Op::ScaledMatMul).
Distinct from QuantScheme: a QuantScheme is block-scaled storage
that the CPU/GPU decodes to f32 before a normal sgemm. A ScaledFormat
is the raw element encoding hardware tensor cores consume directly, with
f32 accumulation — the whole point of FP8/FP6/FP4 on Hopper / Ada /
Blackwell / CDNA3 / CDNA4. The per-block/per-tensor scale layout is
orthogonal and lives in ScaleLayout.
Operands flow through the graph as DType::U8 byte buffers (one code per
byte on the CPU oracle; packed on GPU); the format is carried on the op,
not the dtype, so no DType variant is needed.
Variants§
F8E4M3
OCP FP8 E4M3 — bias 7, no infinities, only S.1111.111 is NaN.
Max ±448. Hopper / Ada / Blackwell; weights + forward activations.
F8E5M2
OCP FP8 E5M2 — bias 15, IEEE-like inf/NaN at max exponent. Max ±57344. Wider range — the gradient format.
F8E4M3Fnuz
AMD “FNUZ” FP8 E4M3 — bias 8, single NaN at 0x80, no inf, no −0.
Max ±240. CDNA3 / MI300.
F8E5M2Fnuz
AMD “FNUZ” FP8 E5M2 — bias 16, single NaN at 0x80, no inf, no −0.
Max ±57344. CDNA3 / MI300.
F6E2M3
MX FP6 E2M3 — bias 1, all 64 codes finite. Max ±7.5. Blackwell / CDNA4.
F6E3M2
MX FP6 E3M2 — bias 3, all 64 codes finite. Max ±28. Blackwell / CDNA4.
F4E2M1
FP4 E2M1 — all 16 codes finite. Max ±6. Blackwell / CDNA4.
Shared by NVFP4 and MXFP4 — the ScaleLayout tells them apart.
Custom
Arbitrary sub-byte minifloat parameterized by exponent / mantissa width
— the fNeXmY family (N = 1 + exp_bits + mant_bits total bits).
All-finite (no infinities, no NaN, no AMD FNUZ), IEEE-style gradual
underflow (subnormals when exp == 0), sign in bit exp_bits + mant_bits, one code per byte. The whole code must fit in a byte, so
1 + exp_bits + mant_bits <= 8 and exp_bits >= 1.
This is a research / experimental encoding: it has no hardware
tensor-core GEMM, so it always runs the decode-and-accumulate path (CPU
oracle + generic GPU kernel). Build one with ScaledFormat::custom
(IEEE bias 2^(exp_bits-1) - 1) or ScaledFormat::custom_with_bias,
or parse a name via "f4e3m0".parse().
Example — f4e3m0 (exp_bits: 3, mant_bits: 0, bias: 3): the 16 codes
are ±0 and ±{0.25, 0.5, 1, 2, 4, 8, 16} — a signed power-of-two
(near-logarithmic) 4-bit format.
Implementations§
Source§impl ScaledFormat
impl ScaledFormat
Sourcepub const NAMED: [ScaledFormat; 7]
pub const NAMED: [ScaledFormat; 7]
The seven named hardware element formats, for enumeration / format sweeps.
Sourcepub const fn custom(exp_bits: u8, mant_bits: u8) -> Self
pub const fn custom(exp_bits: u8, mant_bits: u8) -> Self
Build an arbitrary all-finite minifloat with exp_bits exponent and
mant_bits mantissa bits, using the IEEE-style bias 2^(exp_bits-1) - 1
— the convention every named rlx format follows (E4M3 → 7, E5M2 → 15,
E2M1 → 1, …). The whole code must fit in a byte. const — usable in
const contexts and pattern-free construction.
Panics if exp_bits == 0 or 1 + exp_bits + mant_bits > 8.
use rlx_ir::ScaledFormat;
const F4E3M0: ScaledFormat = ScaledFormat::custom(3, 0);
assert_eq!(F4E3M0.to_string(), "f4e3m0");
assert_eq!(F4E3M0.exp_bits(), 3);
assert_eq!(F4E3M0.mant_bits(), 0);
// Nearest representable value ("quantize" a single f32):
assert_eq!(F4E3M0.quantize(1.4), 1.0);
assert_eq!(F4E3M0.max_finite(), 16.0);Sourcepub const fn custom_with_bias(exp_bits: u8, mant_bits: u8, bias: i8) -> Self
pub const fn custom_with_bias(exp_bits: u8, mant_bits: u8, bias: i8) -> Self
Like custom but with an explicit exponent bias
(for formats that don’t use the IEEE 2^(e-1)-1 convention). const.
Sourcepub const fn mant_bits(self) -> u32
pub const fn mant_bits(self) -> u32
Mantissa-field bit count (Y in fNeXmY; 0 for a pure power-of-two
format such as f4e3m0).
Sourcepub const fn is_custom(self) -> bool
pub const fn is_custom(self) -> bool
True for a parameterized Custom format (no hardware
tensor core — always runs the decode path).
Sourcepub const fn has_inf(self) -> bool
pub const fn has_inf(self) -> bool
True for IEEE-like formats carrying inf / NaN at the max exponent (OCP E5M2 only — E4M3 is finite-with-one-NaN, FP6/FP4 are all-finite).
Sourcepub fn max_finite(self) -> f32
pub fn max_finite(self) -> f32
Largest finite magnitude representable (used as the amax divisor when computing a per-tensor / per-block scale).
Sourcepub fn decode(self, code: u8) -> f32
pub fn decode(self, code: u8) -> f32
Decode one packed code to f32 (bit-exact; ±inf / NaN for the formats
that encode them). Method form of crate::lowp_codec::decode.
Sourcepub fn encode(self, x: f32) -> u8
pub fn encode(self, x: f32) -> u8
Encode an f32 to its nearest representable code (round-half-to-even,
saturating overflow / ±inf to ±max_finite, NaN → 0). Method form of
crate::lowp_codec::encode.
Sourcepub fn quantize(self, x: f32) -> f32
pub fn quantize(self, x: f32) -> f32
Round-trip x through the format — the nearest value it can represent.
Handy for eyeballing a format’s resolution without building a graph
(e.g. ScaledFormat::custom(3, 0).quantize(1.4) == 1.0).
Sourcepub fn representable_values(self) -> Vec<f32>
pub fn representable_values(self) -> Vec<f32>
Every finite value the format represents, ascending and deduplicated —
the format’s grid. Useful for inspecting a research minifloat, e.g.
ScaledFormat::custom(3, 0).representable_values() yields
[-16, -8, -4, -2, -1, -0.5, -0.25, 0, 0.25, 0.5, 1, 2, 4, 8, 16].
Sourcepub const fn kernel_id(self) -> u32
pub const fn kernel_id(self) -> u32
The GPU dispatch word passed to scaled_lowp_general.cu (rlx_decode_lowp).
For the seven named formats this is a small stable id matching the
kernel’s switch: e4m3=0, e5m2=1, e4m3fnuz=2, e5m2fnuz=3, e2m3=4,
e3m2=5, e2m1=6.
For a Custom format there is no fixed id, so this
returns a packed field descriptor with the top bit set as a
sentinel: 0x8000_0000 | exp_bits | mant_bits<<4 | (bias as u8)<<8.
The kernel detects the sentinel bit and decodes generically from the
unpacked (exp_bits, mant_bits, bias) — so a new format needs no kernel
edit. The named ids stay in 0..=6 (top bit clear), so the existing
hardware switch path is unchanged.
Sourcepub const fn is_native_fp8(self) -> bool
pub const fn is_native_fp8(self) -> bool
True for the OCP FP8 variants that the native cublasLt / hipBLASLt FP8 GEMM accepts (per-tensor only). Other formats use the decode fallback.
Trait Implementations§
Source§impl Clone for ScaledFormat
impl Clone for ScaledFormat
Source§fn clone(&self) -> ScaledFormat
fn clone(&self) -> ScaledFormat
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl Copy for ScaledFormat
Source§impl Debug for ScaledFormat
impl Debug for ScaledFormat
Source§impl<'de> Deserialize<'de> for ScaledFormat
impl<'de> Deserialize<'de> for ScaledFormat
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl Display for ScaledFormat
impl Display for ScaledFormat
impl Eq for ScaledFormat
Source§impl FromStr for ScaledFormat
impl FromStr for ScaledFormat
Source§fn from_str(s: &str) -> Result<Self, Self::Err>
fn from_str(s: &str) -> Result<Self, Self::Err>
Parse a format name. The seven named formats ("f8e4m3", "f4e2m1",
the …fnuz variants, …) keep their exact hardware semantics (FNUZ,
inf/NaN); any other fNeXmY string becomes an all-finite
Custom with the IEEE bias. N must equal 1 + X + Y
and the code must fit in a byte (N <= 8, X >= 1).