# Is there anything left for a rotation inside the int8 quantisation group? (#1861)
#
# The proposal: rotate inside each activation quantisation group immediately
# before the absmax, so a single heavy channel cannot set the group's scale.
# The served pipeline already does exactly that.
#
#   arbi_serve/weight_quant/exl3/csrc/exl3_a_prep.cu:6
#       suh pre-scale -> 128-wide Hadamard -> SIGMA k-relabel -> per-token int8
#   ...:403 exl3_had_shuffle_32(t0, t1, t2, t3, lane)   <- the rotation
#   ...:407 amax = fmaxf(amax, ...)                     <- the absmax, after it
#   arbi_serve/weight_quant/exl3/had_basis.py     HAD_BASIS_WIDTH  = 128
#   arbi_serve/weight_quant/exl3/a_prep.py        A_PREP_HADAMARD_BLOCK = 128
#   arbi_serve/weight_quant/exl3/int8_kernel.py   A_GROUP = 128
#
# The rotation width IS the quantisation group width, and the normalised
# Sylvester Hadamard is an involution, so a second rotation at that width is
# the first one's inverse.  tests/test_exl3_had_basis.py pins both facts.
#
# ---------------------------------------------------------------------------
# NULL CONTROL AND MECHANISM -- rotation_headroom.py --self-check, in
# registry.arbi.work/arbi-serve:test-latest, CPU, float64.
# rotation_headroom.py --self-check --group 128
# per-block floor  E[max of 128 |N(0,1)|] = 2.8276
# 4096 rows x 40 blocks, float64

input                                            wblk  wblk/floor   spread
iid Gaussian (the null: must read 1.000)       2.8343      1.0024   1.3665
one heavy channel per block, unrotated         9.8585      3.4866   3.0537
  + one group-wide Hadamard (as served)        1.8262      0.6458   2.2707
  + a second one (the proposal)                9.8585      3.4866   3.0537

Row three is what the quantiser already sees.  Row four is what adding a
rotation inside the quantisation group produces, and it is row two: at the
group width the second rotation is the first one's inverse.

# ---------------------------------------------------------------------------
# THE SERVED 27B -- rotation_headroom.py over a gate3_kl.py --class-err-out
# census: Qwen3.8-27B-exl3-4.0bpw on 206 gpu1, INT8 arm -- the run checked in
# beside this file as receipt_classerr_27b.json, which is what the script reads
# when given no --class-err, so this table reproduces from the tree.
#
# SAMPLE, stated because it is small: that census records block statistics on
# the LAST ROW of each linear's call, and only the first scored position is a
# prefill chunk, so the ten rows below are means over 400 single activation
# rows -- one per served linear, every layer, M=512.  Taking them over every
# row of every call is what this branch adds to gate3_kl.py (`wblk` and
# `spread_r`, beside the scored-row `spread` the exclusion frontier is computed
# from); no GPU was free to re-run the census with them, so the table is the
# one-row-per-linear census and says so.
#
# The activation these statistics are taken on is the one the INT8 arm hands
# the kernel: the STD leg's rotated input had_r_128(x, suh), i.e. AFTER the
# rotation and before the absmax, which is what makes this the right tensor to
# ask the question on.
# rotation_headroom.py --group 128
# per-block floor  E[max of 128 |N(0,1)|] = 2.8276

class                           K  blk   crest   G(K)  ratio  crest_g  ratio  spread | noise cut:    rot  group   both
linear_attn.in_proj_qkv      5120   40   5.491  3.857  1.423    2.496  0.883   2.300 |             1.423  2.209  1.942
linear_attn.in_proj_z        5120   40   6.410  3.857  1.662    2.126  0.752   3.408 |             1.662  3.019  2.267
mlp.gate_proj                5120   40   4.755  3.857  1.233    2.703  0.956   1.815 |             1.233  1.765  1.682
mlp.up_proj                  5120   40   4.023  3.857  1.043    2.836  1.003   1.436 |             1.043  1.425  1.423
self_attn.k_proj             5120   40   5.131  3.857  1.330    2.352  0.832   2.313 |             1.330  2.190  1.815
self_attn.q_proj             5120   40   5.876  3.857  1.523    2.066  0.731   3.802 |             1.523  2.850  2.078
self_attn.v_proj             5120   40   4.711  3.857  1.221    2.645  0.935   1.850 |             1.221  1.785  1.666
linear_attn.out_proj         6144   48   6.491  3.902  1.664    2.627  0.929   2.950 |             1.664  2.473  2.296
self_attn.o_proj             6144   48   6.070  3.902  1.556    2.674  0.946   2.617 |             1.556  2.286  2.147
mlp.down_proj               17408  136   6.033  4.148  1.455    2.443  0.864   2.748 |             1.455  2.480  2.134

A ratio of 1.000 in the crest_g column is a class whose blocks are exactly as
flat as a rotation makes them.  Below 1.000 the existing Hadamard is already
flatter than that, and any further rotation -- which converges to the floor --
raises the per-group quantisation step; the worst such regression here is 1.368x.

`rot` is the CEILING of every cross-block rotation, at any width and any
cost; `group` is what the per-group scale measures today; `both` is the two
together.  `group` beats `rot` on every class and `both` is below `group`, so
a cheaper partial mix does not need pricing: the full one already loses.

# ---------------------------------------------------------------------------
# READING
#
# crest/G(K) is 1.04-1.66: the ROW is not flat, and the per-token scale pays
# for that.  crest_g/G(128) is 0.73-1.00 on all ten classes: inside a 128-block
# the activation is already at or below what a rotation converges to, so a
# within-group rotation has nothing to flatten.  The whole gap between the two
# columns is `spread`, 1.44-3.80 -- ACROSS a row's blocks, which no rotation
# confined to one group can reach and which a per-group scale removes by
# construction (#1927).
#
# The classes with the most across-block spread are the classes the exclusion
# frontier already ranks first for error per unit of speed: q_proj (spread
# 3.80, err/speed 4.96) and in_proj_z (3.41, 3.11).
#
# Adding a rotation ON TOP of the per-group scale is a regression, not a
# no-op: any further rotation converges to the floor, and every class is
# already below it.  Worst case here is q_proj at 2.8276/2.066 = 1.368x the
# per-group quantisation step.
#
# A CHEAPER PARTIAL CROSS-BLOCK MIX -- a strided butterfly, a block permute,
# mixing only the worst blocks -- does not need pricing separately.  The last
# three columns are the ceilings: `rot` is the best ANY cross-block rotation
# can do at any width and any cost (take the row to G(K)), `group` is what the
# per-group scale measures today, `both` is the two together.  `group` beats
# `rot` on all ten classes (1.43-3.02 against 1.04-1.66) and `both` is below
# `group` on all ten.  The full mix already loses, so a partial one cannot win.
# This is the same conclusion the 512-wide Hadamard reached on Ada by pricing
# (+0.52 pp accept for +7.6% TTFT plus a checkpoint migration), reached here
# from the activation statistics instead.
