phanerozoic commited on
Commit
75066be
·
verified ·
1 Parent(s): 2a71500

Card: standardized form with hero

Browse files
Files changed (2) hide show
  1. CARD.md +63 -78
  2. README.md +63 -78
CARD.md CHANGED
@@ -5,15 +5,27 @@ license: apache-2.0
5
 
6
  # fp4-train
7
 
8
- fp4-train is an NVFP4 quantizer with stochastic rounding and error feedback, the
9
- primitives for stable 4-bit training, loadable through `kernels`. Low-bit
10
- training is limited less by the matmul than by the quantizer: round-to-nearest is
11
- biased, so the quantization error correlates with the signal and the optimizer
12
- drifts. This kernel quantizes to NVFP4 (e2m1 values with an e4m3 scale per
13
- 16-element block) with stochastic rounding, so the quantization is unbiased, and
14
- carries a per-element error-feedback residual so sub-ULP information accumulates
15
- instead of vanishing. The packed output is in the two-codes-per-byte layout the
16
- Blackwell FP4 tensor cores consume.
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  ## Usage
19
 
@@ -23,28 +35,19 @@ from kernels import get_kernel
23
 
24
  fp4 = get_kernel("phanerozoic/fp4-train", version=1, trust_remote_code=True)
25
 
26
- # NVFP4-stored-weight training: the weight lives in 4-bit and is updated in place.
27
- # Error feedback carries the sub-ULP part of each update, so round-to-nearest no
28
- # longer stalls; this is the regime where SR/EF matter.
29
  ef = fp4.ErrorFeedback(stochastic=False) # RTN + error feedback
30
- W = W0.clone() # the NVFP4 weight (bf16 values)
31
  for step in range(num_steps):
32
- grad = compute_grad(W) # your backward
33
- W = ef.qdq("layer0.weight", W - lr * grad, step=step) # apply the update in NVFP4
34
- # stochastic=True instead gives unbiased stochastic rounding (a noisier estimator)
35
 
36
  # quantization primitives
37
- packed, scale = fp4.quantize(W, stochastic=True, seed=0, step=global_step) # NVFP4 + e4m3
38
  Wq = fp4.dequantize(packed, scale, *W.shape)
39
 
40
- # quantized-operand linear (full-precision master weights kept by the caller)
41
- x = torch.randn(8, 512, 2048, device="cuda")
42
- Wm = torch.randn(6144, 2048, device="cuda", requires_grad=True)
43
- y = fp4.fp4_linear(x, Wm, stochastic=True, step=global_step) # NVFP4 operands, STE grad
44
-
45
  # native NVFP4 tensor-core matmul on Blackwell (a @ b^T, K a multiple of 16)
46
- a = torch.randn(2048, 4096, device="cuda"); b = torch.randn(4096, 4096, device="cuda")
47
- y = fp4.fp4_mm(a, b) # hardware FP4 path, bf16 out
48
  ```
49
 
50
  `version` selects the release branch; `trust_remote_code` is required by
@@ -57,79 +60,61 @@ y = fp4.fp4_mm(a, b) # hardware FP4 pat
57
  | `quantize(x, stochastic, residual, seed, step)` | NVFP4 quantize; error feedback if `residual` is a `[M,K]` f32 tensor (updated in place) |
58
  | `dequantize(packed, scale, M, K)` | NVFP4 -> bf16 |
59
  | `qdq(x, stochastic, residual, seed, step)` | quantize then dequantize (the value the tensor cores use) |
60
- | `fp4_linear(x, weight, stochastic, seed, step)` | training-ready quantized-operand linear (straight-through gradient) |
61
  | `fp4_matmul(a, b, stochastic, seed, step)` | `a @ b^T` FP4-operand product, dequant + bf16 accumulate (portable, cc 8.0+) |
62
  | `fp4_mm(a, b, stochastic, seed, step)` | `a @ b^T` on the Blackwell NVFP4 tensor cores (native `_scaled_mm`, ~10x faster) |
63
  | `to_fp4_operands(x, stochastic, seed, step)` | packed fp4 + e4m3 scale as the torch FP4 dtypes for `_scaled_mm` |
64
  | `ErrorFeedback` | per-tensor residual state for weight quantization |
65
 
66
- ## How it works
67
 
68
- Each 16-element block gets a scale of `block_absmax / 6` encoded to e4m3 (so the
69
- largest element maps near the e2m1 maximum of 6). Every element is divided by the
70
- decoded scale and encoded to the e2m1 grid `{0, .5, 1, 1.5, 2, 3, 4, 6}` with a
71
- sign bit. Rounding is stochastic: for a value between two grid points, the higher
72
- is chosen with probability equal to the fractional distance, using a counter-based
73
- (seed, step, global-index) RNG, so the expected quantized value equals the input.
74
- With error feedback, the per-element residual `x - dequant(quantize(x))` is
75
- carried and added into the next quantization, so the time-averaged quantized
76
- value equals the true value even under round-to-nearest. Codes are packed two per
77
- byte; the block scales are e4m3, the layout NVFP4 tensor cores read.
 
78
 
79
  ## Correctness
80
 
81
  Measured on L4, H200, and RTX PRO 6000 through `get_kernel`:
82
 
83
- - **Exact encoding.** The quantize/dequantize matches an independent reference
84
- implementation to 0 (`max abs diff = 0`).
85
- - **Stochastic rounding is unbiased.** For a value 2.3 sitting between the grid
86
- points 2 and 3, the stochastic-rounding mean is 2.300 against a true 2.3, where
87
- round-to-nearest is biased to 2.000.
88
- - **Error feedback cancels the drift.** With round-to-nearest plus error
89
- feedback the time-averaged quantized value is 2.297, tracking the true 2.3,
90
- where plain round-to-nearest stays stuck at 2.000.
91
- - **Round-to-nearest stalls low-bit training; stochastic rounding and error
92
- feedback do not.** With the weights *stored* in NVFP4 and updated in place, so
93
- each step's update is smaller than a quantization step, round-to-nearest
94
- discards every update and training stalls, while stochastic rounding (unbiased)
95
- and error feedback (residual-carrying) apply the updates and recover
96
- near-bf16 loss. A pure accumulation of a sub-ULP increment reaches a true 1.50
97
- under SR (1.50) and EF (1.50) while RTN stays at the starting 0.50. A linear
98
- model trained with NVFP4-stored weights reaches MSE 1.0 (EF) and 13 (SR) against
99
- a bf16 0.0, where RTN sits at 77; a small MLP reaches loss 0.159 (EF) and 0.171
100
- (SR) against a bf16 0.158, where RTN stalls at 1.03. Error feedback recovers
101
- essentially the full-precision loss; stochastic rounding, being a noisier
102
- estimator, trails it but still beats RTN by a wide margin.
103
-
104
- ## Scope
105
-
106
- The verified contribution is the quantizer that makes 4-bit training stable:
107
- correct NVFP4 encoding, unbiased stochastic rounding, and error feedback that
108
- preserves sub-ULP information. Their value shows up when the low precision is in
109
- the training state itself, meaning weights stored and updated in NVFP4, where the
110
- per-step update is below a quantization step: there round-to-nearest stalls and
111
- stochastic rounding or error feedback recover near-bf16 loss, as measured above.
112
- When instead only the forward matmul operands are quantized and a full-precision
113
- master weight is kept, the choice of rounding is second order, since the master
114
- carries the sub-ULP information; that regime does not need this kernel's rounding
115
- modes and is not where the gain is. Two matmuls are bundled: `fp4_matmul` takes a
116
- numerically exact FP4 path (dequantize, bf16 accumulate) that is portable to every
117
- compute-capability-8.0+ GPU, and `fp4_mm` runs the operands on the Blackwell NVFP4
118
- tensor cores by swizzling the e4m3 block scales into the layout `torch._scaled_mm`
119
- consumes. On an RTX PRO 6000 the hardware path returns a bit-identical result
120
- (relative error 0) and is about 10-14x faster than the dequantize path and 1.5-1.7x
121
- faster than a bf16 matmul.
122
 
123
  ## Requirements and limits
124
 
125
- - NVIDIA GPU with compute capability 8.0+ (e4m3 scale via software conversion).
 
126
  - `K` a multiple of 16; float32 or bfloat16 input.
 
 
 
 
 
 
127
 
128
  ## References
129
 
130
  NVFP4 and OCP microscaling (MXFP) formats; Gupta et al., "Deep Learning with
131
- Limited Numerical Precision" (stochastic rounding, 2015); Seide et al., "1-bit
132
- SGD" (error feedback, 2014); low-precision training with unbiased quantization.
133
 
134
  ## License
135
 
 
5
 
6
  # fp4-train
7
 
8
+ fp4-train is an NVFP4 quantizer with stochastic rounding and error feedback,
9
+ the primitives for stable 4-bit training, loadable through `kernels`. The
10
+ reference baseline is an independent NVFP4 encoding implementation, matched
11
+ to zero error, with fp32 training as the convergence reference.
12
+
13
+ Training with weights stored in 4-bit fails under ordinary rounding: once the
14
+ per-step update is smaller than a quantization step, round-to-nearest
15
+ discards every update and the model stops learning. This kernel quantizes to
16
+ NVFP4 (e2m1 values with an e4m3 scale per 16-element block) with stochastic
17
+ rounding, so quantization is unbiased and sub-step updates accumulate in
18
+ expectation, and it carries a per-element error-feedback residual so the
19
+ dropped fraction of every update is banked instead of lost. The packed output
20
+ is the layout Blackwell FP4 tensor cores consume.
21
+
22
+ ![Four training curves: round-to-nearest flatlines while stochastic rounding and error feedback descend toward the fp32 reference, above an e2m1 grid where a value hops between neighbors](https://huggingface.co/kernels/phanerozoic/fp4-train/resolve/main/media/hero.gif)
23
+
24
+ *Weights stored in NVFP4 and updated in place, 500 steps of sub-step updates:
25
+ round-to-nearest never leaves its starting loss (6.56); stochastic rounding
26
+ reaches 1.80, error feedback 0.77, against the fp32 reference at 0.02. Below,
27
+ quantizing 2.3 on the e2m1 grid hops between 2 and 3 with the running mean at
28
+ 2.26.*
29
 
30
  ## Usage
31
 
 
35
 
36
  fp4 = get_kernel("phanerozoic/fp4-train", version=1, trust_remote_code=True)
37
 
38
+ # NVFP4-stored-weight training: the weight lives in 4-bit, updated in place
 
 
39
  ef = fp4.ErrorFeedback(stochastic=False) # RTN + error feedback
40
+ W = W0.clone()
41
  for step in range(num_steps):
42
+ grad = compute_grad(W)
43
+ W = ef.qdq("layer0.weight", W - lr * grad, step=step)
 
44
 
45
  # quantization primitives
46
+ packed, scale = fp4.quantize(W, stochastic=True, seed=0, step=global_step)
47
  Wq = fp4.dequantize(packed, scale, *W.shape)
48
 
 
 
 
 
 
49
  # native NVFP4 tensor-core matmul on Blackwell (a @ b^T, K a multiple of 16)
50
+ y = fp4.fp4_mm(a, b)
 
51
  ```
52
 
53
  `version` selects the release branch; `trust_remote_code` is required by
 
60
  | `quantize(x, stochastic, residual, seed, step)` | NVFP4 quantize; error feedback if `residual` is a `[M,K]` f32 tensor (updated in place) |
61
  | `dequantize(packed, scale, M, K)` | NVFP4 -> bf16 |
62
  | `qdq(x, stochastic, residual, seed, step)` | quantize then dequantize (the value the tensor cores use) |
63
+ | `fp4_linear(x, weight, stochastic, seed, step)` | quantized-operand linear (straight-through gradient) |
64
  | `fp4_matmul(a, b, stochastic, seed, step)` | `a @ b^T` FP4-operand product, dequant + bf16 accumulate (portable, cc 8.0+) |
65
  | `fp4_mm(a, b, stochastic, seed, step)` | `a @ b^T` on the Blackwell NVFP4 tensor cores (native `_scaled_mm`, ~10x faster) |
66
  | `to_fp4_operands(x, stochastic, seed, step)` | packed fp4 + e4m3 scale as the torch FP4 dtypes for `_scaled_mm` |
67
  | `ErrorFeedback` | per-tensor residual state for weight quantization |
68
 
69
+ ## Method
70
 
71
+ Each 16-element block gets a scale of `block_absmax / 6` encoded to e4m3, so
72
+ the largest element maps near the e2m1 maximum. Every element is divided by
73
+ the decoded scale and encoded to the e2m1 grid `{0, .5, 1, 1.5, 2, 3, 4, 6}`
74
+ with a sign bit. Rounding is stochastic: for a value between two grid points,
75
+ the higher is chosen with probability equal to the fractional distance, using
76
+ a counter-based `(seed, step, global-index)` RNG, so the expected quantized
77
+ value equals the input. With error feedback, the residual
78
+ `x - dequant(quantize(x))` is carried and added into the next quantization,
79
+ so the time-averaged value equals the true value even under round-to-nearest.
80
+ Codes pack two per byte; block scales are e4m3, the layout NVFP4 tensor cores
81
+ read.
82
 
83
  ## Correctness
84
 
85
  Measured on L4, H200, and RTX PRO 6000 through `get_kernel`:
86
 
87
+ - Exact encoding: quantize/dequantize matches an independent reference to 0
88
+ (`max abs diff = 0`).
89
+ - Stochastic rounding is unbiased: for 2.3 between grid points 2 and 3, the
90
+ SR mean is 2.300 where round-to-nearest is biased to 2.000.
91
+ - Error feedback cancels the drift: RTN plus error feedback time-averages to
92
+ 2.297.
93
+ - With NVFP4-stored weights and sub-step updates, RTN stalls at the starting
94
+ loss while SR and EF recover near-full-precision loss: a linear model
95
+ reaches MSE 1.0 (EF) and 13 (SR) against bf16 0.0 with RTN at 77; a small
96
+ MLP reaches 0.159 (EF) and 0.171 (SR) against bf16 0.158 with RTN at 1.03.
97
+ - On an RTX PRO 6000 the hardware `fp4_mm` path returns a bit-identical
98
+ result to the dequantize path (relative error 0) and is about 10-14x
99
+ faster, and 1.5-1.7x faster than a bf16 matmul.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  ## Requirements and limits
102
 
103
+ - NVIDIA GPU with compute capability 8.0+ (e4m3 scale via software
104
+ conversion); `fp4_mm` requires Blackwell.
105
  - `K` a multiple of 16; float32 or bfloat16 input.
106
+ - The rounding modes matter when the low precision is in the training state
107
+ itself (weights stored and updated in NVFP4). With a full-precision master
108
+ weight, the master carries the sub-ULP information and the choice of
109
+ rounding is second order.
110
+ - Blocks whose absmax falls below the e4m3 scale range quantize to zero; keep
111
+ stored-weight scales inside the representable range.
112
 
113
  ## References
114
 
115
  NVFP4 and OCP microscaling (MXFP) formats; Gupta et al., "Deep Learning with
116
+ Limited Numerical Precision" (stochastic rounding, 2015); Seide et al.,
117
+ "1-bit SGD" (error feedback, 2014).
118
 
119
  ## License
120
 
README.md CHANGED
@@ -5,15 +5,27 @@ license: apache-2.0
5
 
6
  # fp4-train
7
 
8
- fp4-train is an NVFP4 quantizer with stochastic rounding and error feedback, the
9
- primitives for stable 4-bit training, loadable through `kernels`. Low-bit
10
- training is limited less by the matmul than by the quantizer: round-to-nearest is
11
- biased, so the quantization error correlates with the signal and the optimizer
12
- drifts. This kernel quantizes to NVFP4 (e2m1 values with an e4m3 scale per
13
- 16-element block) with stochastic rounding, so the quantization is unbiased, and
14
- carries a per-element error-feedback residual so sub-ULP information accumulates
15
- instead of vanishing. The packed output is in the two-codes-per-byte layout the
16
- Blackwell FP4 tensor cores consume.
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  ## Usage
19
 
@@ -23,28 +35,19 @@ from kernels import get_kernel
23
 
24
  fp4 = get_kernel("phanerozoic/fp4-train", version=1, trust_remote_code=True)
25
 
26
- # NVFP4-stored-weight training: the weight lives in 4-bit and is updated in place.
27
- # Error feedback carries the sub-ULP part of each update, so round-to-nearest no
28
- # longer stalls; this is the regime where SR/EF matter.
29
  ef = fp4.ErrorFeedback(stochastic=False) # RTN + error feedback
30
- W = W0.clone() # the NVFP4 weight (bf16 values)
31
  for step in range(num_steps):
32
- grad = compute_grad(W) # your backward
33
- W = ef.qdq("layer0.weight", W - lr * grad, step=step) # apply the update in NVFP4
34
- # stochastic=True instead gives unbiased stochastic rounding (a noisier estimator)
35
 
36
  # quantization primitives
37
- packed, scale = fp4.quantize(W, stochastic=True, seed=0, step=global_step) # NVFP4 + e4m3
38
  Wq = fp4.dequantize(packed, scale, *W.shape)
39
 
40
- # quantized-operand linear (full-precision master weights kept by the caller)
41
- x = torch.randn(8, 512, 2048, device="cuda")
42
- Wm = torch.randn(6144, 2048, device="cuda", requires_grad=True)
43
- y = fp4.fp4_linear(x, Wm, stochastic=True, step=global_step) # NVFP4 operands, STE grad
44
-
45
  # native NVFP4 tensor-core matmul on Blackwell (a @ b^T, K a multiple of 16)
46
- a = torch.randn(2048, 4096, device="cuda"); b = torch.randn(4096, 4096, device="cuda")
47
- y = fp4.fp4_mm(a, b) # hardware FP4 path, bf16 out
48
  ```
49
 
50
  `version` selects the release branch; `trust_remote_code` is required by
@@ -57,79 +60,61 @@ y = fp4.fp4_mm(a, b) # hardware FP4 pat
57
  | `quantize(x, stochastic, residual, seed, step)` | NVFP4 quantize; error feedback if `residual` is a `[M,K]` f32 tensor (updated in place) |
58
  | `dequantize(packed, scale, M, K)` | NVFP4 -> bf16 |
59
  | `qdq(x, stochastic, residual, seed, step)` | quantize then dequantize (the value the tensor cores use) |
60
- | `fp4_linear(x, weight, stochastic, seed, step)` | training-ready quantized-operand linear (straight-through gradient) |
61
  | `fp4_matmul(a, b, stochastic, seed, step)` | `a @ b^T` FP4-operand product, dequant + bf16 accumulate (portable, cc 8.0+) |
62
  | `fp4_mm(a, b, stochastic, seed, step)` | `a @ b^T` on the Blackwell NVFP4 tensor cores (native `_scaled_mm`, ~10x faster) |
63
  | `to_fp4_operands(x, stochastic, seed, step)` | packed fp4 + e4m3 scale as the torch FP4 dtypes for `_scaled_mm` |
64
  | `ErrorFeedback` | per-tensor residual state for weight quantization |
65
 
66
- ## How it works
67
 
68
- Each 16-element block gets a scale of `block_absmax / 6` encoded to e4m3 (so the
69
- largest element maps near the e2m1 maximum of 6). Every element is divided by the
70
- decoded scale and encoded to the e2m1 grid `{0, .5, 1, 1.5, 2, 3, 4, 6}` with a
71
- sign bit. Rounding is stochastic: for a value between two grid points, the higher
72
- is chosen with probability equal to the fractional distance, using a counter-based
73
- (seed, step, global-index) RNG, so the expected quantized value equals the input.
74
- With error feedback, the per-element residual `x - dequant(quantize(x))` is
75
- carried and added into the next quantization, so the time-averaged quantized
76
- value equals the true value even under round-to-nearest. Codes are packed two per
77
- byte; the block scales are e4m3, the layout NVFP4 tensor cores read.
 
78
 
79
  ## Correctness
80
 
81
  Measured on L4, H200, and RTX PRO 6000 through `get_kernel`:
82
 
83
- - **Exact encoding.** The quantize/dequantize matches an independent reference
84
- implementation to 0 (`max abs diff = 0`).
85
- - **Stochastic rounding is unbiased.** For a value 2.3 sitting between the grid
86
- points 2 and 3, the stochastic-rounding mean is 2.300 against a true 2.3, where
87
- round-to-nearest is biased to 2.000.
88
- - **Error feedback cancels the drift.** With round-to-nearest plus error
89
- feedback the time-averaged quantized value is 2.297, tracking the true 2.3,
90
- where plain round-to-nearest stays stuck at 2.000.
91
- - **Round-to-nearest stalls low-bit training; stochastic rounding and error
92
- feedback do not.** With the weights *stored* in NVFP4 and updated in place, so
93
- each step's update is smaller than a quantization step, round-to-nearest
94
- discards every update and training stalls, while stochastic rounding (unbiased)
95
- and error feedback (residual-carrying) apply the updates and recover
96
- near-bf16 loss. A pure accumulation of a sub-ULP increment reaches a true 1.50
97
- under SR (1.50) and EF (1.50) while RTN stays at the starting 0.50. A linear
98
- model trained with NVFP4-stored weights reaches MSE 1.0 (EF) and 13 (SR) against
99
- a bf16 0.0, where RTN sits at 77; a small MLP reaches loss 0.159 (EF) and 0.171
100
- (SR) against a bf16 0.158, where RTN stalls at 1.03. Error feedback recovers
101
- essentially the full-precision loss; stochastic rounding, being a noisier
102
- estimator, trails it but still beats RTN by a wide margin.
103
-
104
- ## Scope
105
-
106
- The verified contribution is the quantizer that makes 4-bit training stable:
107
- correct NVFP4 encoding, unbiased stochastic rounding, and error feedback that
108
- preserves sub-ULP information. Their value shows up when the low precision is in
109
- the training state itself, meaning weights stored and updated in NVFP4, where the
110
- per-step update is below a quantization step: there round-to-nearest stalls and
111
- stochastic rounding or error feedback recover near-bf16 loss, as measured above.
112
- When instead only the forward matmul operands are quantized and a full-precision
113
- master weight is kept, the choice of rounding is second order, since the master
114
- carries the sub-ULP information; that regime does not need this kernel's rounding
115
- modes and is not where the gain is. Two matmuls are bundled: `fp4_matmul` takes a
116
- numerically exact FP4 path (dequantize, bf16 accumulate) that is portable to every
117
- compute-capability-8.0+ GPU, and `fp4_mm` runs the operands on the Blackwell NVFP4
118
- tensor cores by swizzling the e4m3 block scales into the layout `torch._scaled_mm`
119
- consumes. On an RTX PRO 6000 the hardware path returns a bit-identical result
120
- (relative error 0) and is about 10-14x faster than the dequantize path and 1.5-1.7x
121
- faster than a bf16 matmul.
122
 
123
  ## Requirements and limits
124
 
125
- - NVIDIA GPU with compute capability 8.0+ (e4m3 scale via software conversion).
 
126
  - `K` a multiple of 16; float32 or bfloat16 input.
 
 
 
 
 
 
127
 
128
  ## References
129
 
130
  NVFP4 and OCP microscaling (MXFP) formats; Gupta et al., "Deep Learning with
131
- Limited Numerical Precision" (stochastic rounding, 2015); Seide et al., "1-bit
132
- SGD" (error feedback, 2014); low-precision training with unbiased quantization.
133
 
134
  ## License
135
 
 
5
 
6
  # fp4-train
7
 
8
+ fp4-train is an NVFP4 quantizer with stochastic rounding and error feedback,
9
+ the primitives for stable 4-bit training, loadable through `kernels`. The
10
+ reference baseline is an independent NVFP4 encoding implementation, matched
11
+ to zero error, with fp32 training as the convergence reference.
12
+
13
+ Training with weights stored in 4-bit fails under ordinary rounding: once the
14
+ per-step update is smaller than a quantization step, round-to-nearest
15
+ discards every update and the model stops learning. This kernel quantizes to
16
+ NVFP4 (e2m1 values with an e4m3 scale per 16-element block) with stochastic
17
+ rounding, so quantization is unbiased and sub-step updates accumulate in
18
+ expectation, and it carries a per-element error-feedback residual so the
19
+ dropped fraction of every update is banked instead of lost. The packed output
20
+ is the layout Blackwell FP4 tensor cores consume.
21
+
22
+ ![Four training curves: round-to-nearest flatlines while stochastic rounding and error feedback descend toward the fp32 reference, above an e2m1 grid where a value hops between neighbors](https://huggingface.co/kernels/phanerozoic/fp4-train/resolve/main/media/hero.gif)
23
+
24
+ *Weights stored in NVFP4 and updated in place, 500 steps of sub-step updates:
25
+ round-to-nearest never leaves its starting loss (6.56); stochastic rounding
26
+ reaches 1.80, error feedback 0.77, against the fp32 reference at 0.02. Below,
27
+ quantizing 2.3 on the e2m1 grid hops between 2 and 3 with the running mean at
28
+ 2.26.*
29
 
30
  ## Usage
31
 
 
35
 
36
  fp4 = get_kernel("phanerozoic/fp4-train", version=1, trust_remote_code=True)
37
 
38
+ # NVFP4-stored-weight training: the weight lives in 4-bit, updated in place
 
 
39
  ef = fp4.ErrorFeedback(stochastic=False) # RTN + error feedback
40
+ W = W0.clone()
41
  for step in range(num_steps):
42
+ grad = compute_grad(W)
43
+ W = ef.qdq("layer0.weight", W - lr * grad, step=step)
 
44
 
45
  # quantization primitives
46
+ packed, scale = fp4.quantize(W, stochastic=True, seed=0, step=global_step)
47
  Wq = fp4.dequantize(packed, scale, *W.shape)
48
 
 
 
 
 
 
49
  # native NVFP4 tensor-core matmul on Blackwell (a @ b^T, K a multiple of 16)
50
+ y = fp4.fp4_mm(a, b)
 
51
  ```
52
 
53
  `version` selects the release branch; `trust_remote_code` is required by
 
60
  | `quantize(x, stochastic, residual, seed, step)` | NVFP4 quantize; error feedback if `residual` is a `[M,K]` f32 tensor (updated in place) |
61
  | `dequantize(packed, scale, M, K)` | NVFP4 -> bf16 |
62
  | `qdq(x, stochastic, residual, seed, step)` | quantize then dequantize (the value the tensor cores use) |
63
+ | `fp4_linear(x, weight, stochastic, seed, step)` | quantized-operand linear (straight-through gradient) |
64
  | `fp4_matmul(a, b, stochastic, seed, step)` | `a @ b^T` FP4-operand product, dequant + bf16 accumulate (portable, cc 8.0+) |
65
  | `fp4_mm(a, b, stochastic, seed, step)` | `a @ b^T` on the Blackwell NVFP4 tensor cores (native `_scaled_mm`, ~10x faster) |
66
  | `to_fp4_operands(x, stochastic, seed, step)` | packed fp4 + e4m3 scale as the torch FP4 dtypes for `_scaled_mm` |
67
  | `ErrorFeedback` | per-tensor residual state for weight quantization |
68
 
69
+ ## Method
70
 
71
+ Each 16-element block gets a scale of `block_absmax / 6` encoded to e4m3, so
72
+ the largest element maps near the e2m1 maximum. Every element is divided by
73
+ the decoded scale and encoded to the e2m1 grid `{0, .5, 1, 1.5, 2, 3, 4, 6}`
74
+ with a sign bit. Rounding is stochastic: for a value between two grid points,
75
+ the higher is chosen with probability equal to the fractional distance, using
76
+ a counter-based `(seed, step, global-index)` RNG, so the expected quantized
77
+ value equals the input. With error feedback, the residual
78
+ `x - dequant(quantize(x))` is carried and added into the next quantization,
79
+ so the time-averaged value equals the true value even under round-to-nearest.
80
+ Codes pack two per byte; block scales are e4m3, the layout NVFP4 tensor cores
81
+ read.
82
 
83
  ## Correctness
84
 
85
  Measured on L4, H200, and RTX PRO 6000 through `get_kernel`:
86
 
87
+ - Exact encoding: quantize/dequantize matches an independent reference to 0
88
+ (`max abs diff = 0`).
89
+ - Stochastic rounding is unbiased: for 2.3 between grid points 2 and 3, the
90
+ SR mean is 2.300 where round-to-nearest is biased to 2.000.
91
+ - Error feedback cancels the drift: RTN plus error feedback time-averages to
92
+ 2.297.
93
+ - With NVFP4-stored weights and sub-step updates, RTN stalls at the starting
94
+ loss while SR and EF recover near-full-precision loss: a linear model
95
+ reaches MSE 1.0 (EF) and 13 (SR) against bf16 0.0 with RTN at 77; a small
96
+ MLP reaches 0.159 (EF) and 0.171 (SR) against bf16 0.158 with RTN at 1.03.
97
+ - On an RTX PRO 6000 the hardware `fp4_mm` path returns a bit-identical
98
+ result to the dequantize path (relative error 0) and is about 10-14x
99
+ faster, and 1.5-1.7x faster than a bf16 matmul.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  ## Requirements and limits
102
 
103
+ - NVIDIA GPU with compute capability 8.0+ (e4m3 scale via software
104
+ conversion); `fp4_mm` requires Blackwell.
105
  - `K` a multiple of 16; float32 or bfloat16 input.
106
+ - The rounding modes matter when the low precision is in the training state
107
+ itself (weights stored and updated in NVFP4). With a full-precision master
108
+ weight, the master carries the sub-ULP information and the choice of
109
+ rounding is second order.
110
+ - Blocks whose absmax falls below the e4m3 scale range quantize to zero; keep
111
+ stored-weight scales inside the representable range.
112
 
113
  ## References
114
 
115
  NVFP4 and OCP microscaling (MXFP) formats; Gupta et al., "Deep Learning with
116
+ Limited Numerical Precision" (stochastic rounding, 2015); Seide et al.,
117
+ "1-bit SGD" (error feedback, 2014).
118
 
119
  ## License
120