#machine-learning #quantization #fp4 #mxfp4

float4

MXFP4-compatible 4-bit floating point types and block formats for Rust

1 unstable release

Uses new Rust 2024

0.1.0 Aug 4, 2025

#494 in Encoding

Download history 161/week @ 2025-08-04 702/week @ 2025-08-11 2026/week @ 2025-08-18 2395/week @ 2025-08-25 2615/week @ 2025-09-01 2741/week @ 2025-09-08 2595/week @ 2025-09-15 2326/week @ 2025-09-22 1872/week @ 2025-09-29 1194/week @ 2025-10-06 2440/week @ 2025-10-13 2390/week @ 2025-10-20 3771/week @ 2025-10-27 4169/week @ 2025-11-03 3983/week @ 2025-11-10

14,388 downloads per month
Used in 65 crates (3 directly)

MIT license

51KB
481 lines

float4

MXFP4-compatible 4-bit floating point types and block formats for Rust.

This crate provides low-precision floating-point types following the OCP MX specification, designed for efficient storage and computation in machine learning applications where extreme quantization is beneficial.

Available Types

  • F4E2M1: 4-bit floating-point with 2 exponent bits and 1 mantissa bit
  • E8M0: 8-bit scale factor representing powers of two (2^-127 to 2^127)
  • MXFP4Block: Block format storing 32 F4E2M1 values with a shared E8M0 scale

Features

  • Extreme compression: 4× smaller than f32 with MXFP4Block format
  • IEEE 754 compliant rounding: Round-to-nearest-even for F4E2M1
  • Power-of-two scales: E8M0 provides exact scaling without rounding errors
  • Efficient block storage: Pack multiple values with shared scale factor
  • Comprehensive API: Conversions, constants, and trait implementations

Quick Start

Add this to your Cargo.toml:

[dependencies]
float4 = "0.1"

Example Usage

use float4::F4E2M1;

// Create from f64
let a = F4E2M1::from_f64(1.5);
assert_eq!(a.to_f64(), 1.5);

// Create from raw bits
let b = F4E2M1::from_bits(0x3); // 0b0011 = 1.5
assert_eq!(b.to_f64(), 1.5);

// Arithmetic operations (via f64 conversion)
let x = F4E2M1::from_f64(2.0);
let y = F4E2M1::from_f64(3.0);
let sum = F4E2M1::from_f64(x.to_f64() + y.to_f64());
assert_eq!(sum.to_f64(), 5.0); // May round to nearest representable value

// Constants
assert_eq!(F4E2M1::MAX.to_f64(), 6.0);
assert_eq!(F4E2M1::MIN.to_f64(), -6.0);
assert_eq!(F4E2M1::EPSILON.to_f64(), 0.5);

Block Format Example

use float4::{F4E2M1, E8M0, MXFP4Block};

// Original data
let data = vec![1.5, -2.0, 0.5, 3.0, 1.0, -0.5];

// Compute scale factor (rounds up to power of 2)
let scale = E8M0::from_f32_slice(&data);
assert_eq!(scale.to_f64(), 4.0); // 3.0 rounds up to 4.0

// Quantize to F4E2M1
let mut quantized = [F4E2M1::from_f64(0.0); 32];
for (i, &value) in data.iter().enumerate() {
    quantized[i] = F4E2M1::from_f64(value as f64 / scale.to_f64());
}

// Pack into block (17 bytes for 32 values vs 128 bytes for f32)
let block = MXFP4Block::from_f32_slice(quantized, scale);

// Retrieve values
let restored = block.to_f32_array();
assert_eq!(restored[0], 1.5);
assert_eq!(restored[1], -2.0);

E8M0 Scale Factors

The E8M0 type represents scale factors as exact powers of two:

use float4::E8M0;

// Exact powers of two are preserved
let scale = E8M0::from(4.0);
assert_eq!(scale.to_f64(), 4.0);

// Non-powers round UP to next power of two
let scale = E8M0::from(3.0);
assert_eq!(scale.to_f64(), 4.0);  // 3.0 → 4.0

let scale = E8M0::from(5.0);
assert_eq!(scale.to_f64(), 8.0);  // 5.0 → 8.0

// Computing scale from data
let data = [1.5, -2.0, 0.5, 3.0];
let scale = E8M0::from_f32_slice(&data);
assert_eq!(scale.to_f64(), 4.0);  // max(|data|) = 3.0 → 4.0

Key characteristics:

  • Range: 2^-127 to 2^127
  • Always rounds UP (toward positive infinity)
  • No rounding errors when scaling by powers of two
  • Ideal for block quantization schemes

Representable Values

F4E2M1 can exactly represent 16 distinct values:

Value Bit Pattern Type
0.0 0000 Zero
0.5 0001 Subnormal
1.0 0010 Normal
1.5 0011 Normal
2.0 0100 Normal
3.0 0101 Normal
4.0 0110 Normal
6.0 0111 Normal
-0.0 1000 Negative zero
-0.5 1001 Subnormal
-1.0 1010 Normal
-1.5 1011 Normal
-2.0 1100 Normal
-3.0 1101 Normal
-4.0 1110 Normal
-6.0 1111 Normal

Special Values

Unlike standard floating point formats, F4E2M1 has no representation for infinity or NaN. These values saturate to the maximum representable value:

use float4::F4E2M1;

assert_eq!(F4E2M1::from_f64(f64::INFINITY).to_f64(), 6.0);
assert_eq!(F4E2M1::from_f64(f64::NEG_INFINITY).to_f64(), -6.0);
assert_eq!(F4E2M1::from_f64(f64::NAN).to_f64(), 6.0);

No runtime deps