ubospica commited on
Commit
3d1ec00
·
verified ·
1 Parent(s): 1444ea1

Shorten precision analysis to the requested English text

Browse files

Translate the selected six-part explanation and append measured workload errors, including the KDA individual-state outlier. Keep reproduction commands in README.

Files changed (2) hide show
  1. PRECISION_ANALYSIS.md +145 -283
  2. README.md +3 -2
PRECISION_ANALYSIS.md CHANGED
@@ -1,283 +1,145 @@
1
- # Precision Analysis: TileLang Baselines and CuTe DSL Kernels
2
-
3
- This document compares the original TileLang baselines with the released GDN
4
- and KDA CuTe DSL implementations. It describes unchanged formats, arithmetic
5
- changes, precision improvements, deliberate approximations, and measured limits.
6
- The paired measurements were collected on NVIDIA B300 on 2026-10-26.
7
-
8
- The output format and the original benchmark thresholds are unchanged. The
9
- current implementations retain the rebuilt state and residual in FP32 Tensor
10
- Memory (TMEM), while using reduced-precision operands and selected approximations
11
- elsewhere. FP32 storage or accumulation does not imply FP32 precision at every
12
- operation, or bitwise equivalence to the mathematical reference.
13
-
14
- ## 1. Three different meanings of precision
15
-
16
- The following are separate implementations and measurement roles:
17
-
18
- 1. **Mathematical reference:** `definition.json` computes the flush mainly with
19
- FP32 PyTorch operations. It still rounds the new rank-4 factors to FP16 and
20
- quantizes the residual to INT8. Its decoded output already has format error.
21
- 2. **Original TileLang baseline:** already uses mixed precision, including
22
- FP16/BF16 tensor-core operands, FP32 accumulation, FP16 intermediate storage,
23
- and FP32 corrections. It is not an all-FP32 implementation.
24
- 3. **Current CuTe DSL implementation:** also uses mixed precision. It changes
25
- which intermediates retain FP32, how products are compensated, and where
26
- lower-precision approximations are accepted.
27
-
28
- The judge rebuilds a separate target state in FP64 from the same supplied
29
- inputs. These inputs already contain INT8, FP16, and BF16 values. This target is
30
- not an unquantized model state from before those input conversions. FP64 is used
31
- for validation, not by the production flush arithmetic.
32
-
33
- ## 2. Input and output formats are unchanged
34
-
35
- The fixed geometry is 32 heads, a 128 by 128 state, a 16-entry update window,
36
- and rank 4.
37
-
38
- | Data | Original baseline | Current implementation |
39
- |---|---|---|
40
- | Residual `codes` | INT8, values in [-127, 127] | Same |
41
- | Checkpoint scales `s_k`, `s_v` | FP32 | Same |
42
- | Low-rank factors `u`, `q` | FP16 | Same |
43
- | Update buffers `kbuf`, `ubuf` | BF16 | Same |
44
- | Weights `w`, GDN scalar gate `p` | FP32 | Same |
45
- | KDA gate history `gbuf` | FP16 | Same |
46
- | KDA cumulative log-gate `pcum` | FP32 | Same |
47
- | Supplied starting basis `q0` | FP32 | Same |
48
-
49
- The checkpoint continues to decode as
50
-
51
- $$
52
- S_{\mathrm{old}}[v,k]
53
- =\sum_{r=1}^{4}q[r,v]u[r,k]
54
- +C[v,k]\,s_k[k]s_v[v]/127.
55
- $$
56
-
57
- The rank, window length, supplied starting basis, and one round of subspace
58
- iteration are retained. The new factors are rounded to FP16 before forming the
59
- final residual, so that the residual compensates for the factors actually stored.
60
- INT8 codes retain nearest rounding and the same value range.
61
-
62
- `Pool.raw` is an FP32 backing allocation with typed views into its storage.
63
- That backing dtype does not make its INT8 codes or FP16 factors FP32 data.
64
-
65
- ## 3. State and residual storage
66
-
67
- ### GDN baseline
68
-
69
- The baseline first forms a partial state `B` containing the dequantized old
70
- residual and buffered updates, then stores `B` in FP16 shared memory. The old
71
- rank-4 term is carried separately through FP32 algebraic corrections rather than
72
- being irreversibly folded into that FP16 matrix. The final residual is computed
73
- with FP32 accumulation, then stored in FP16 before scale calculation and coding.
74
-
75
- This distinction matters: the baseline does not simply cast the entire state
76
- to FP16 and discard the old low-rank correction.
77
-
78
- ### KDA baseline
79
-
80
- The default configuration has `KEXACT=True`. For the final residual it performs
81
- another dequantization/rebuild from the staged inputs, accumulates the terms in
82
- FP32, and applies the per-channel checkpoint decay in FP32. It preserves the
83
- unscaled old factors for this residual calculation.
84
-
85
- The residual is then scaled by a power of two per block and stored in FP16.
86
- Scaling protects small residuals from FP16 underflow, but does not remove FP16
87
- significand rounding. The projection stage still uses an FP16 partial state and
88
- an FP16 copy of the decayed old factors.
89
-
90
- ### Current implementations
91
-
92
- Both kernels retain the full rebuilt state in FP32 TMEM. They also create a
93
- scaled FP16 shared-memory copy for the projection products. After producing the
94
- new FP16 factors, they subtract the low-rank product from the retained FP32
95
- state and keep the residual in FP32 through the quantization passes.
96
-
97
- This removes an important FP16 storage-rounding step from the final residual
98
- path. It also lets KDA avoid the baseline's second full residual rebuild.
99
- However, the retained FP32 state was itself formed using mixed-precision
100
- products, so it is not necessarily the same bit pattern as a sequence of FP32
101
- reference operations.
102
-
103
- Residual accuracy is sensitive to cancellation: when `S` and the new low-rank
104
- product are large but their difference is small, a rounding error measured
105
- relative to `S` can be large relative to the residual.
106
-
107
- ## 4. Product precision and high/low compensation
108
-
109
- The original baselines already use BF16 high/low splitting for weighted or
110
- decayed updates. Conceptually, a value is represented as
111
-
112
- $$
113
- x\approx x_{\mathrm{hi}}+x_{\mathrm{lo}},\qquad
114
- Ax\approx Ax_{\mathrm{hi}}+Ax_{\mathrm{lo}}.
115
- $$
116
-
117
- The current GDN implementation folds the update weight into the key operand,
118
- whereas the baseline primarily folds it into the value operand. The current GDN
119
- high BF16 part is truncated and the low part compensates for the discarded
120
- bits. These are mathematically equivalent organizations with different
121
- floating-point rounding and accumulation orders.
122
-
123
- The current kernels also use TF32 high/low compensation for scaled or decayed
124
- old low-rank terms. This retains more information than a single TF32 conversion,
125
- but is not a guarantee of exact FP32 multiplication or reduction.
126
-
127
- The projection products primarily use FP16 operands with FP32 accumulation.
128
- The fast projection path can still lose information in the FP16 state copy;
129
- retaining a separate FP32 state improves the residual path without making the
130
- computed low-rank subspace identical to the reference.
131
-
132
- `f32x2` means two independent FP32 operations executed in a packed instruction.
133
- It does not mean FP64, double-double arithmetic, or additional significant bits
134
- for either value.
135
-
136
- ## 5. Starting basis and dynamic range
137
-
138
- The baseline converts `q0` directly to FP16 for the projection. The current GDN
139
- kernel first rescales its columns by powers of two and represents the basis
140
- with FP16 high and low parts. KDA also conditions the starting basis with
141
- Cholesky orthogonalization before storing its high/low representation.
142
-
143
- The current kernels choose a per-head power-of-two scale for state construction
144
- and projection. The scale is undone when the factors and checkpoint scales are
145
- written. For normal representable values, multiplication by a power of two
146
- changes the exponent without introducing the same rounding as an arbitrary
147
- scale factor. Range limits and subsequent FP16 conversion still matter.
148
-
149
- KDA already used block scaling in the baseline's residual-storage stage. The
150
- current implementation extends range management to the full state path while
151
- retaining its FP32 main copy. It does not make FP16 and BF16 interchangeable.
152
-
153
- ## 6. Orthogonalization changes
154
-
155
- The baseline defaults to `GRAM16=False`: both CholeskyQR passes compute their
156
- Gram matrices in FP32. A comment describing an optional FP16 second Gram pass
157
- does not describe this default path.
158
-
159
- The current kernels can skip the second orthogonalization pass when the first
160
- pass meets a conditioning test. KDA performs the second pass when needed. GDN
161
- also has a more conservative path with state-low-part compensation and repeated
162
- Gram-Schmidt projections for difficult cases; its second Cholesky pass can
163
- suppress numerically dependent directions.
164
-
165
- These choices alter numerical results and are not merely scheduling changes.
166
- Two QR passes also do not mean two rounds of the outer subspace-iteration
167
- algorithm; these are different operations.
168
-
169
- ## 7. Deliberate lower-precision approximations
170
-
171
- Some current operations trade local precision for throughput:
172
-
173
- - **GDN column sums:** parts of the absolute-residual column reduction use
174
- FP16 packed addition before returning to FP32. The baseline reads an FP16
175
- residual but accumulates those column sums in FP32.
176
- - **KDA column-scale reciprocal:** the reciprocal is rounded to FP16. That same
177
- rounded value is used for coding and to derive the corresponding FP32 output
178
- scale. A scale stored in FP32 can therefore still reflect a lower-precision
179
- internal computation.
180
- - **Row normalization:** the current kernels primarily use approximate
181
- reciprocals, whereas the baseline uses a correctly rounded reciprocal here.
182
- GDN retains a more accurate fallback for extreme row scales.
183
- - **Conditional QR:** skipping the second pass changes the amount of numerical
184
- correction performed on well-conditioned inputs.
185
-
186
- For example, ignoring zero-value guards and clamps, KDA's scaled residual
187
- `E_prime` yields a column reciprocal approximately of the form
188
-
189
- $$
190
- r_h=\operatorname{FP16}\left(
191
- \operatorname{rsqrt}(\operatorname{mean}_v|E_{\mathrm{prime}}|)
192
- \right).
193
- $$
194
-
195
- Coding uses `r_h`, and the stored column scale is derived from its reciprocal
196
- with the state scale undone. This keeps the encoded values and saved scales
197
- consistent, while moving the quantization grid relative to a fully FP32 scale
198
- calculation. Individual codes and scales can consequently differ from the
199
- reference even when decoded-state error remains close to it.
200
-
201
- ## 8. What the accuracy metrics measure
202
-
203
- For the original benchmark, each head index has an error averaged over batch
204
- items and matrix elements:
205
-
206
- $$
207
- e_h=\operatorname{mean}_{b,v,k}
208
- \left|\operatorname{decode}(\mathrm{checkpoint})-S_{64}\right|.
209
- $$
210
-
211
- On the relative-error branch, the reported ratio is
212
-
213
- $$
214
- \rho_h=e_h^{\mathrm{implementation}}/e_h^{\mathrm{FP32\ reference}}.
215
- $$
216
-
217
- The original requirements are unchanged:
218
-
219
- - If reference error is nonzero and at least `1e-4 * mean(abs(S64))`, candidate
220
- error must be at most `1.01 * reference_error`.
221
- - Otherwise, the bound is `1e-3 * mean(abs(S64))`.
222
- - Outputs must be finite and codes must remain in [-127, 127].
223
-
224
- The reference output is itself quantized. A ratio of 1.0004 means approximately
225
- 0.04% more reconstruction error than that reference, not a state error of 0.04%.
226
- This criterion does not require elementwise-identical codes, factors, or scales.
227
-
228
- ## 9. Matched B300 measurements
229
-
230
- Both implementations received identical tensors. The comparison used six batch
231
- sizes (16, 32, 64, 128, 256, 512) and three input families: structured edge cases,
232
- a second window starting from the reference checkpoint, and random in-domain
233
- inputs. Each implementation passed all 18 original benchmark cases per kernel.
234
-
235
- | Metric | GDN baseline | GDN current | KDA baseline | KDA current |
236
- |---|---:|---:|---:|---:|
237
- | Worst original-benchmark reference-relative error ratio | 1.00324455 | 1.00041398 | 1.00296773 | 1.00105573 |
238
- | Maximum individual-state MAE / mean(abs(S64)) | 1.052615% | 0.576760% | 0.613289% | 0.612224% |
239
- | Original cases passed | 18/18 | 18/18 | 18/18 | 18/18 |
240
- | Failures in the additional individual-state check | 30/96,768 | 0/96,768 | 44/96,768 | 1/96,768 |
241
-
242
- The individual-state check applies the same threshold formulas separately to
243
- each `(batch item, head)`, including evaluating the relative/absolute branch
244
- separately for that state. It does not average over batch items and is stricter
245
- than the original benchmark. The failure counts in the last row are not failures
246
- of the original aggregated scoring procedure.
247
-
248
- ### Current KDA boundary case
249
-
250
- One individual state in the batch-128 random case has a reference-relative MAE
251
- ratio of **1.01256057**, exceeding the individual relative limit of 1.01. Its
252
- original aggregated benchmark case passes. Therefore, the data supports better
253
- measured original-benchmark ratios, but not a claim that every individual state
254
- satisfies the same bound.
255
-
256
- No stage-by-stage numerical ablation was performed. The isolated failure cannot
257
- be assigned to the FP16 reciprocal, projection, conditional QR, or another
258
- specific approximation from these measurements alone.
259
-
260
- These are finite test results. They do not establish bounds for every possible
261
- input, long sequences of self-generated checkpoints, or full-model perplexity.
262
- In particular, the second-window test starts from a reference checkpoint rather
263
- than a long chain of each implementation's own outputs.
264
-
265
- ## 10. Evidence and reproduction
266
-
267
- - Original mathematical definitions: [GDN](gdn/definition.json), [KDA](kda/definition.json).
268
- - Unmodified TileLang baselines: [GDN](gdn/baseline.py), [KDA](kda/baseline.py).
269
- - Released CuTe sources: [GDN](build/torch-cuda/_gdn.py), [KDA](build/torch-cuda/_kda.py).
270
- - Matched measurements: [GDN](gdn/precision_comparison.json), [KDA](kda/precision_comparison.json).
271
- - Source hashes and release details: [GDN](gdn/README.md#files-and-provenance), [KDA](kda/README.md#files-and-provenance).
272
-
273
- Using the pinned [dependencies](requirements.txt), run on an idle B300:
274
-
275
- ```bash
276
- python compare_precision.py gdn
277
- python compare_precision.py kda
278
- ```
279
-
280
- The [comparison script](compare_precision.py) creates the inputs once for both
281
- implementations, rebuilds the FP64 target, invokes the original judge, and
282
- records the extra individual-state check separately. It writes results to each
283
- component's `precision_comparison.json`.
 
1
+ # Precision Analysis
2
+
3
+ The input/output formats and acceptance thresholds are unchanged. Internally,
4
+ FP32 state/residual retention has been added, while some lower-precision
5
+ approximations are used in projection, reduction, and scale computation. I have
6
+ rerun the original baseline and current implementation on B300 with exactly
7
+ the same inputs.
8
+
9
+ First, distinguish three things:
10
+
11
+ - **Mathematical reference:** mainly uses FP32 arithmetic, then outputs FP16
12
+ low-rank factors and an INT8 residual.
13
+ - **Original TileLang baseline:** already uses mixed precision, including
14
+ FP16/BF16 multiplication, FP32 accumulation, and FP16 intermediate storage.
15
+ - **Current CuTe implementation:** still uses mixed precision, but changes
16
+ which intermediates retain FP32 and where approximations are allowed.
17
+
18
+ The external data types are completely unchanged:
19
+
20
+ | Data | Original baseline and current implementation |
21
+ |---|---|
22
+ | Residual `codes` | INT8, range [-127, 127] |
23
+ | Low-rank factors `u`, `q` | FP16 |
24
+ | Update buffers `kbuf`, `ubuf` | BF16 |
25
+ | Scales `s_k`, `s_v` | FP32 |
26
+ | `w`, `p`, `pcum`, `q0` | FP32 |
27
+ | KDA gate history `gbuf` | FP16 |
28
+
29
+ The numerical changes occur at the following stages.
30
+
31
+ 1. **GDN state and residual: reduce FP16 rounding of matrix intermediates.**
32
+
33
+ The original baseline first computes the quantized residual plus the window
34
+ updates to obtain `B`, then stores `B` in FP16. The old rank-4 term participates
35
+ separately through FP32 algebraic corrections. After the final residual is
36
+ computed, it is stored in FP16 again before scales and codes are generated.
37
+
38
+ The current implementation retains the full rebuilt state `S` in FP32 TMEM.
39
+ It uses a scaled FP16 copy to extract the low-rank component, but computes
40
+
41
+ $$
42
+ E=S-q_{\rm new}^{T}u_{\rm new}
43
+ $$
44
+
45
+ using the retained FP32 state. The residual also remains in FP32 until
46
+ quantization.
47
+
48
+ This is the main precision improvement: when subtracting two large values
49
+ produces a small residual, rounding the state to FP16 beforehand can make
50
+ the rounding error large relative to that residual.
51
+
52
+ 2. **KDA residual: FP32 reconstruction already existed; the current kernel
53
+ removes the subsequent FP16 round trip.**
54
+
55
+ The original KDA baseline enables `KEXACT=True` by default: it decodes the
56
+ checkpoint again and recomputes the residual in FP32, avoiding direct use
57
+ of the earlier FP16 state. However, it then scales the residual by a power
58
+ of two per block and stores it in FP16.
59
+
60
+ The current implementation retains the FP32 state directly, subtracts the
61
+ new low-rank term from it, and keeps the residual in FP32 TMEM. The change
62
+ therefore removes the FP16 store and reload after residual computation;
63
+ it is not a change from an entirely FP16 pipeline to an entirely FP32 one.
64
+
65
+ 3. **Product compensation: retain BF16 high/low splitting and reorganize the
66
+ arithmetic.**
67
+
68
+ The original baseline already uses BF16 high/low splitting. For an FP32
69
+ value, the idea is
70
+
71
+ $$
72
+ x\approx x_{\rm hi}+x_{\rm lo},\qquad
73
+ Ax\approx Ax_{\rm hi}+Ax_{\rm lo}.
74
+ $$
75
+
76
+ The current GDN implementation folds the weight into the key operand and
77
+ then splits it into BF16 high and low parts; the baseline mainly folds
78
+ the weight into the value operand. These are mathematically equivalent,
79
+ but the rounding locations and accumulation order change.
80
+
81
+ The current implementation also uses TF32 high/low compensation for
82
+ weighted or decayed old low-rank terms. This retains more information
83
+ than a single TF32 conversion, but still does not establish bitwise
84
+ equivalence to IEEE FP32 computation.
85
+
86
+ 4. **Starting matrix and dynamic range: add scaling and low-part protection.**
87
+
88
+ The original baseline converts `q0` directly to FP16. The current
89
+ implementation first scales each column by a power of two and retains
90
+ FP16 high and low parts. KDA also conditions the starting basis through
91
+ orthogonalization.
92
+
93
+ The current implementation also chooses a per-head power-of-two scale
94
+ during state reconstruction, keeping the FP16 projection copy within a
95
+ suitable numerical range. The KDA baseline already had per-block scaling
96
+ at the residual stage; the current kernel moves this protection earlier
97
+ into the state stage.
98
+
99
+ Scaling helps avoid overflow and underflow, but FP16 rounding remains.
100
+
101
+ 5. **Orthogonalization: reduce computation according to numerical conditioning,
102
+ while retaining corrections for difficult inputs.**
103
+
104
+ The original baseline defaults to `GRAM16=False`, so the Gram matrices
105
+ in both QR passes are computed in FP32.
106
+
107
+ The current implementation skips the second orthogonalization pass when
108
+ conditioning is good and adds corrections when conditioning is poor. GDN
109
+ also uses state-low-part compensation and reorthogonalization for
110
+ ill-conditioned cases. These choices change numerical results and are
111
+ numerical tradeoffs.
112
+
113
+ Also, `f32x2` in the code means computing two FP32 values simultaneously;
114
+ it does not increase the precision of either value.
115
+
116
+ 6. **Scales and reductions: these do introduce lower-precision approximations.**
117
+
118
+ - **GDN:** part of the residual column summation uses packed FP16 addition
119
+ before returning to FP32. The baseline reads the FP16 residual and
120
+ accumulates those sums in FP32.
121
+ - **KDA:** the column-scale reciprocal is rounded to FP16, then used to
122
+ generate codes and the corresponding FP32 output scale. A scale stored
123
+ in FP32 therefore need not have been computed entirely at FP32 precision.
124
+ - Current row-scale normalization mainly uses approximate reciprocals;
125
+ the baseline uses a correctly rounded reciprocal. GDN retains a more
126
+ accurate fallback for extreme scales.
127
+ - The final INT8 nearest-rounding rule and value range remain unchanged.
128
+
129
+ These paths can be compared directly in the original
130
+ [GDN baseline](gdn/baseline.py), [KDA baseline](kda/baseline.py), and current
131
+ [GDN](build/torch-cuda/_gdn.py) and [KDA](build/torch-cuda/_kda.py) implementations.
132
+
133
+ On the six given B300 workloads (batches 16, 32, 64, 128, 256, and 512), tested
134
+ with structured, second-window, and random inputs, both implementations pass
135
+ all 18 original cases per kernel. The worst reconstruction-error ratio relative
136
+ to the FP32 reference improves from **1.00324455 to 1.00041398 for GDN** and from
137
+ **1.00296773 to 1.00105573 for KDA**, with both errors measured against the same
138
+ FP64 rebuilt state. The current kernels' maximum individual-state
139
+ `MAE / mean(abs(S64))` is **0.576760% for GDN** and **0.612224% for KDA**.
140
+ An additional check of 96,768 individual states per kernel finds no GDN
141
+ violations and one KDA violation: a batch-128 random state has an error ratio
142
+ of **1.01256057**, exceeding the individual limit of 1.01. The original judge
143
+ averages over the batch dimension and still passes that case. Full measurements
144
+ are in the [GDN](gdn/precision_comparison.json) and
145
+ [KDA](kda/precision_comparison.json) reports.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -65,8 +65,9 @@ are unchanged; both implementations pass all 18 original comparison cases.
65
  The additional individual-state check found one current KDA outlier in the
66
  batch-128 random case (error ratio 1.01256057 against the individual limit 1.01).
67
  The original benchmark averages the batch dimension; the extra check treats
68
- individual states separately. Full records and reproduction commands are in
69
- the analysis document.
 
70
 
71
  ## Reproduce
72
 
 
65
  The additional individual-state check found one current KDA outlier in the
66
  batch-128 random case (error ratio 1.01256057 against the individual limit 1.01).
67
  The original benchmark averages the batch dimension; the extra check treats
68
+ individual states separately. Full records are linked in the analysis document.
69
+ Reproduce with `python compare_precision.py gdn` and
70
+ `python compare_precision.py kda`.
71
 
72
  ## Reproduce
73