flashrt-structures 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. flashrt_structures/__init__.py +174 -0
  2. flashrt_structures/adapters/__init__.py +55 -0
  3. flashrt_structures/adapters/diffusers_attention.py +237 -0
  4. flashrt_structures/adapters/diffusers_rotary_attention.py +239 -0
  5. flashrt_structures/adapters/factored_qk_norm_rope.py +252 -0
  6. flashrt_structures/adapters/factored_two_way_attention.py +99 -0
  7. flashrt_structures/adapters/gemma_attention.py +226 -0
  8. flashrt_structures/adapters/packed_qkv_rope.py +342 -0
  9. flashrt_structures/adapters/packed_stream_qk_norm_rope.py +376 -0
  10. flashrt_structures/adapters/qwen_per_head_qk_norm_rope.py +231 -0
  11. flashrt_structures/adapters/sglang_engine.py +207 -0
  12. flashrt_structures/adapters/transformers_attention_interface.py +73 -0
  13. flashrt_structures/adapters/transformers_gated_delta.py +152 -0
  14. flashrt_structures/adapters/transformers_gated_delta_fused.py +96 -0
  15. flashrt_structures/adapters/vllm_engine.py +424 -0
  16. flashrt_structures/adjudicate.py +85 -0
  17. flashrt_structures/aot.py +191 -0
  18. flashrt_structures/autobuild.py +2052 -0
  19. flashrt_structures/beta/__init__.py +43 -0
  20. flashrt_structures/beta/conform.py +94 -0
  21. flashrt_structures/beta/joins.py +113 -0
  22. flashrt_structures/beta/negotiate.py +84 -0
  23. flashrt_structures/beta/ports.py +140 -0
  24. flashrt_structures/decisions.py +80 -0
  25. flashrt_structures/discover.py +623 -0
  26. flashrt_structures/explain.py +70 -0
  27. flashrt_structures/frontdoor.py +572 -0
  28. flashrt_structures/gates.py +465 -0
  29. flashrt_structures/guard.py +421 -0
  30. flashrt_structures/handle.py +189 -0
  31. flashrt_structures/impls/__init__.py +219 -0
  32. flashrt_structures/impls/adaln_producer/__init__.py +8 -0
  33. flashrt_structures/impls/adaln_producer/broker.py +116 -0
  34. flashrt_structures/impls/adaln_producer/fused.py +388 -0
  35. flashrt_structures/impls/adarms_stack/__init__.py +8 -0
  36. flashrt_structures/impls/adarms_stack/fp8_chain.py +832 -0
  37. flashrt_structures/impls/adarms_stack/region.py +102 -0
  38. flashrt_structures/impls/attention_core/__init__.py +132 -0
  39. flashrt_structures/impls/attention_core/fa2_seqused.py +458 -0
  40. flashrt_structures/impls/attention_core/fa4_cute.py +154 -0
  41. flashrt_structures/impls/attention_core/fa4_fp8.py +178 -0
  42. flashrt_structures/impls/attention_core/masked_mha.py +158 -0
  43. flashrt_structures/impls/attention_core/two_way_fa2.py +220 -0
  44. flashrt_structures/impls/cadence_static/__init__.py +18 -0
  45. flashrt_structures/impls/cadence_static/buffers.py +122 -0
  46. flashrt_structures/impls/cadence_static/cross_attention.py +187 -0
  47. flashrt_structures/impls/chain_elements.py +89 -0
  48. flashrt_structures/impls/decode_loop/__init__.py +0 -0
  49. flashrt_structures/impls/decode_loop/fp8_kv.py +206 -0
  50. flashrt_structures/impls/decode_loop/mtp_speculative.py +245 -0
  51. flashrt_structures/impls/decode_loop/whole_step.py +852 -0
  52. flashrt_structures/impls/decoder_block/__init__.py +6 -0
  53. flashrt_structures/impls/decoder_block/attn_sublayer.py +110 -0
  54. flashrt_structures/impls/decoder_block/fused.py +167 -0
  55. flashrt_structures/impls/decoder_ffn/__init__.py +0 -0
  56. flashrt_structures/impls/decoder_ffn/fp8_static.py +310 -0
  57. flashrt_structures/impls/decoder_ffn/fp8_static.yaml +22 -0
  58. flashrt_structures/impls/decoder_ffn/w4a16_static.py +221 -0
  59. flashrt_structures/impls/decoder_ffn/w8a16_static.py +183 -0
  60. flashrt_structures/impls/dit_stack/__init__.py +11 -0
  61. flashrt_structures/impls/dit_stack/fp4_chain.py +417 -0
  62. flashrt_structures/impls/dit_stack/region.py +86 -0
  63. flashrt_structures/impls/fixed_iter/__init__.py +29 -0
  64. flashrt_structures/impls/fixed_iter/openpi.py +264 -0
  65. flashrt_structures/impls/fixed_iter/protocol.py +94 -0
  66. flashrt_structures/impls/gated_delta_core/__init__.py +3 -0
  67. flashrt_structures/impls/gated_delta_core/fused_layer.py +545 -0
  68. flashrt_structures/impls/gated_delta_core/hub_v3.py +152 -0
  69. flashrt_structures/impls/graph_lowering/__init__.py +27 -0
  70. flashrt_structures/impls/graph_lowering/pi052_denoise.py +179 -0
  71. flashrt_structures/impls/graph_lowering/protocol.py +76 -0
  72. flashrt_structures/impls/graph_lowering/qwen3_vl.py +364 -0
  73. flashrt_structures/impls/linear_proj/__init__.py +0 -0
  74. flashrt_structures/impls/linear_proj/fp8_static.py +270 -0
  75. flashrt_structures/impls/linear_proj/nvfp4_balance.py +131 -0
  76. flashrt_structures/impls/linear_proj/nvfp4_dynamic.py +182 -0
  77. flashrt_structures/impls/linear_proj/w8a16_static.py +230 -0
  78. flashrt_structures/impls/modnorm_qkv_chain/__init__.py +0 -0
  79. flashrt_structures/impls/modnorm_qkv_chain/fp8_ptok_table.py +291 -0
  80. flashrt_structures/impls/moe_experts/__init__.py +9 -0
  81. flashrt_structures/impls/moe_experts/nvfp4_dynamic.py +208 -0
  82. flashrt_structures/impls/moe_experts/nvfp4_w4a16.py +129 -0
  83. flashrt_structures/impls/norm_fused/__init__.py +3 -0
  84. flashrt_structures/impls/norm_fused/bf16.py +94 -0
  85. flashrt_structures/impls/norm_fused/fp8_producer.py +84 -0
  86. flashrt_structures/impls/patch_projection/__init__.py +3 -0
  87. flashrt_structures/impls/patch_projection/bf16_flat.py +139 -0
  88. flashrt_structures/impls/prefill_tower/__init__.py +10 -0
  89. flashrt_structures/impls/prefill_tower/fp8_chain.py +955 -0
  90. flashrt_structures/impls/prefill_tower/region.py +99 -0
  91. flashrt_structures/impls/qk_norm_rope/__init__.py +12 -0
  92. flashrt_structures/impls/qk_norm_rope/per_head_gqa.py +199 -0
  93. flashrt_structures/impls/qk_norm_rope/projection_bf16.py +165 -0
  94. flashrt_structures/impls/qkv_pack/__init__.py +5 -0
  95. flashrt_structures/impls/qkv_pack/bf16.py +110 -0
  96. flashrt_structures/impls/qkv_pack/fp8_static.py +435 -0
  97. flashrt_structures/impls/qkv_pack/nvfp4_balance.py +218 -0
  98. flashrt_structures/impls/qkv_rope/__init__.py +3 -0
  99. flashrt_structures/impls/qkv_rope/packed_bias_bf16.py +143 -0
  100. flashrt_structures/impls/step_table.py +113 -0
  101. flashrt_structures/impls/vision_ffn/__init__.py +0 -0
  102. flashrt_structures/impls/vision_ffn/fp8_static.py +261 -0
  103. flashrt_structures/impls/vision_ffn/nvfp4_balance.py +211 -0
  104. flashrt_structures/impls/vision_tower/__init__.py +7 -0
  105. flashrt_structures/impls/vision_tower/fp8_chain.py +533 -0
  106. flashrt_structures/impls/vision_tower/region.py +91 -0
  107. flashrt_structures/matrix.py +126 -0
  108. flashrt_structures/points.py +368 -0
  109. flashrt_structures/prequantized.py +131 -0
  110. flashrt_structures/quantize_on_adopt.py +94 -0
  111. flashrt_structures/recipe.py +438 -0
  112. flashrt_structures/regions.py +208 -0
  113. flashrt_structures/schemes.py +490 -0
  114. flashrt_structures/stages.py +298 -0
  115. flashrt_structures/storage.py +255 -0
  116. flashrt_structures/swap.py +398 -0
  117. flashrt_structures/workspace.py +110 -0
  118. flashrt_structures-0.2.0.dist-info/METADATA +140 -0
  119. flashrt_structures-0.2.0.dist-info/RECORD +122 -0
  120. flashrt_structures-0.2.0.dist-info/WHEEL +5 -0
  121. flashrt_structures-0.2.0.dist-info/licenses/LICENSE +202 -0
  122. flashrt_structures-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,131 @@
1
+ """NVFP4 (W4A4) ``linear_proj`` with per-input-channel balance.
2
+
3
+ The W4 recipe as a formal impl variant: the weight is folded with the
4
+ activation-only channel balance fitted on calibrated per-channel amax
5
+ (``fit_input_channel_balance`` — the production formula), packed to
6
+ NVFP4 (E2M1 data plus per-16-element-block scale factors), and the
7
+ activation is quantized dynamically per call after the inverse fold.
8
+ The fold is exact (``x' @ W'.T == x @ W.T``) and only then is either
9
+ side quantized, so the balance costs no arithmetic identity — it moves
10
+ quantization error out of the hot channels.
11
+
12
+ Activations carry dynamic per-block scales computed per call, so there
13
+ is no static activation scale to drift across a denoise schedule; the
14
+ per-channel amax calibration feeds the *balance*, not a scale.
15
+
16
+ Unlike :mod:`.nvfp4_dynamic` (the adoption path for checkpoints that
17
+ are already packed and have no host form to return to), this variant
18
+ retains the host module whole: a call outside the calibrated form runs
19
+ it, and detach restores it bit-exact.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import Mapping
25
+
26
+ import torch
27
+
28
+ from ...guard import CAST_OK, PROCEED, GuardedSeam
29
+ from .nvfp4_dynamic import KERNEL_DEP, _check, _kernel # noqa: F401
30
+
31
+ _VARIANT = 2 # the qualified GEMM dispatch across the served shapes
32
+
33
+
34
+ class LinearProjNvfp4Balance(GuardedSeam, torch.nn.Module):
35
+ """Balanced-fold projection: dynamic FP4 quantize + FP4 GEMM."""
36
+
37
+ _frt_host_attr = "host_linear"
38
+ _frt_can_fallback = True
39
+
40
+ def __init__(self, w_packed, w_sfb, inv_s, bias, n, k,
41
+ original: torch.nn.Module | None = None):
42
+ super().__init__()
43
+ self.register_buffer("_w_packed", w_packed)
44
+ self.register_buffer("_w_sfb", w_sfb)
45
+ self.register_buffer("_inv_s", inv_s)
46
+ if bias is not None:
47
+ self.register_buffer("_bias", bias)
48
+ else:
49
+ self._bias = None
50
+ self._n = n
51
+ kern = _kernel()
52
+ self._kern = kern
53
+ self._gemm = kern.fp4_w4a16_linear_bf16
54
+ # capability probe: the fused-bias epilogue entry, where the
55
+ # installed package variant ships it — absence is the ordinary
56
+ # two-launch path, never a refusal
57
+ self._gemm_bias = getattr(kern, "nvfp4_gemm_bias_bf16", None)
58
+ if original is not None:
59
+ self.host_linear = original
60
+ self._frt_arm(dtypes=CAST_OK, device=w_packed.device, k=int(k))
61
+
62
+ def __getattr__(self, name):
63
+ try:
64
+ return super().__getattr__(name)
65
+ except AttributeError:
66
+ if name == "host_linear":
67
+ raise
68
+ return getattr(super().__getattr__("host_linear"), name)
69
+
70
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
71
+ admitted = self._frt_admit(x)
72
+ if admitted is not PROCEED:
73
+ return admitted
74
+ shape = x.shape
75
+ flat = (x.reshape(-1, shape[-1]).to(torch.float16)
76
+ * self._inv_s).contiguous()
77
+ a_packed, a_sfa = self._kern.quantize_fp4_sfa_fp16(flat)
78
+ if self._bias is not None and self._gemm_bias is not None:
79
+ y = self._gemm_bias(a_packed, self._w_packed, a_sfa,
80
+ self._w_sfb, self._bias)
81
+ else:
82
+ y = self._gemm(a_packed, self._w_packed, a_sfa, self._w_sfb,
83
+ variant=_VARIANT)
84
+ if self._bias is not None:
85
+ y = y + self._bias
86
+ return y.reshape(*shape[:-1], self._n).type_as(x)
87
+
88
+
89
+ @torch.no_grad()
90
+ def bind_proj_seam(
91
+ weights: Mapping[str, torch.Tensor],
92
+ *,
93
+ channel_amax,
94
+ original: torch.nn.Module | None = None,
95
+ alpha: float = 0.5,
96
+ clamp=(0.25, 4.0),
97
+ ) -> LinearProjNvfp4Balance:
98
+ """Bind one projection from a dense ``[N, K]`` weight.
99
+
100
+ ``channel_amax`` is the calibrated per-input-channel amax vector at
101
+ this projection's input (``[K]``) — it parameterises the balance
102
+ fold, never a scale. ``alpha``/``clamp`` are the decision's recipe
103
+ payload.
104
+ """
105
+ from flash_rt.core.quantization import fit_input_channel_balance
106
+
107
+ n, k = _check(weights)
108
+ kern = _kernel()
109
+ w = weights["w"].detach()
110
+ amax = torch.as_tensor(channel_amax, device=w.device,
111
+ dtype=torch.float32)
112
+ w_bal, inv_s = fit_input_channel_balance(
113
+ w.float(), amax, alpha=alpha,
114
+ clamp=(float(clamp[0]), float(clamp[1])),
115
+ out_dtype=torch.float32)
116
+ w_packed, w_sfb = kern.quantize_fp4_sfa_fp16(
117
+ w_bal.to("cuda", torch.float16).contiguous(), is_sfb=True)
118
+ bias = weights.get("b")
119
+ if bias is not None:
120
+ bias = bias.detach().to("cuda", torch.bfloat16)
121
+ bound = LinearProjNvfp4Balance(
122
+ w_packed, w_sfb, inv_s.to("cuda", torch.float16), bias, n, k,
123
+ original=original)
124
+ probe = bound(torch.zeros(1, k, device=w_packed.device,
125
+ dtype=torch.bfloat16))
126
+ if probe.shape != (1, n) or not torch.isfinite(probe).all():
127
+ raise ValueError(
128
+ f"refused: nvfp4_balance bind smoke produced shape "
129
+ f"{tuple(probe.shape)}, "
130
+ f"finite={bool(torch.isfinite(probe).all())}")
131
+ return bound
@@ -0,0 +1,182 @@
1
+ """NVFP4 (W4A4, dynamic activation scales) ``linear_proj`` implementation.
2
+
3
+ Weights are packed to NVFP4 (E2M1 data plus per-16-element-block scale
4
+ factors) at bind time; activations are quantized to the same format at
5
+ runtime, per call, with dynamically computed block scales — no
6
+ calibration data at either end. This is the execution form behind the
7
+ 27B enablement line: checkpoints whose upstream loader decompresses
8
+ 4-bit weights to BF16 inside ``forward`` (and therefore cannot fit the
9
+ card) run on the same card once their projections consume the packed
10
+ layout directly.
11
+
12
+ The ``flashrt/fp4-gemm`` entry point ``fp4_w4a16_linear_bf16`` takes the
13
+ pre-quantized activation tensor plus its scale factors — despite the
14
+ ``a16`` in its historical name, the GEMM it runs is W4A4. ``variant=2``
15
+ is the qualified dispatch across the decode and short-prefill shapes
16
+ this impl serves (same-token 1.0000 against an exact reference on the
17
+ 27B host, decode and prefill both through this path).
18
+
19
+ There is no host fallback: the module this replaces holds packed
20
+ weights the host cannot execute. The guard therefore refuses instead of
21
+ falling back, and adoption of a whole checkpoint is a load-time
22
+ transform, not a reversible attachment.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from collections.abc import Mapping
28
+ from functools import lru_cache
29
+
30
+ import torch
31
+
32
+ from ...guard import CAST_OK, PROCEED, GuardedSeam
33
+
34
+ KERNEL_DEP = {
35
+ "provider": "huggingface_kernels",
36
+ "repo": "flashrt/fp4-gemm",
37
+ "version": ">=1",
38
+ }
39
+
40
+ #: mirrors the kernel's own shape checks (``torch_binding.cpp``: K
41
+ #: divisible by 16 for the per-block scale factors, positive dims) —
42
+ #: no invented size walls: the adoption path serves whatever the
43
+ #: checkpoint author packed, and the 27B host's 17408-wide FFN is a
44
+ #: qualified shape, not an edge case
45
+ SUPPORT = {
46
+ "K": {"min": 16, "multiple_of": 16},
47
+ "N": {"min": 1},
48
+ }
49
+
50
+
51
+ @lru_cache(maxsize=1)
52
+ def _kernel():
53
+ from flashrt_structures.impls import hub_kernel
54
+
55
+ return hub_kernel(KERNEL_DEP["repo"], KERNEL_DEP["version"])
56
+
57
+
58
+ def _check(weights: Mapping[str, torch.Tensor]) -> tuple[int, int]:
59
+ w = weights["w"]
60
+ if w.dim() != 2:
61
+ raise ValueError(f"w must be [N, K], got {tuple(w.shape)}")
62
+ n, k = w.shape
63
+ for name, dim in (("K", k), ("N", n)):
64
+ bounds = SUPPORT[name]
65
+ if dim < bounds["min"]:
66
+ raise ValueError(
67
+ f"{name}={dim} outside support envelope "
68
+ f"(min {bounds['min']})")
69
+ if bounds.get("multiple_of") and dim % bounds["multiple_of"]:
70
+ raise ValueError(
71
+ f"{name}={dim} must be a multiple of "
72
+ f"{bounds['multiple_of']}")
73
+ b = weights.get("b")
74
+ if b is not None and tuple(b.shape) != (n,):
75
+ raise ValueError(
76
+ f"bias shape {tuple(b.shape)} does not match N={n}")
77
+ return n, k
78
+
79
+
80
+ def _quantize_activation(kern, flat: torch.Tensor):
81
+ """Use the direct BF16 producer when the installed artifact carries it."""
82
+ if flat.dtype is torch.bfloat16:
83
+ direct = getattr(kern, "quantize_fp4_sfa_bf16", None)
84
+ if direct is not None:
85
+ return direct(flat.contiguous())
86
+ return kern.quantize_fp4_sfa_fp16(
87
+ flat.to(torch.float16).contiguous())
88
+
89
+
90
+ class LinearProjNvfp4Dynamic(GuardedSeam, torch.nn.Module):
91
+ """Packed-weight projection: FP4 GEMM with runtime activation scales."""
92
+
93
+ _frt_can_fallback = False
94
+
95
+ def __init__(self, w_packed, w_sfb, bias, n, k):
96
+ super().__init__()
97
+ self.register_buffer("_w_packed", w_packed)
98
+ self.register_buffer("_w_sfb", w_sfb)
99
+ self._bias = bias
100
+ self._n = n
101
+ self._k = k
102
+ kern = _kernel()
103
+ self._kern = kern
104
+ self._gemm = kern.fp4_w4a16_linear_bf16
105
+ # M=1 decode rows route to the warp-split GEMV where the build
106
+ # carries it and the shape qualifies (its own contract: N%8,
107
+ # K a multiple of 64*warps). Absence is not a refusal - the
108
+ # tiled GEMM serves every shape correctly, the GEMV just fills
109
+ # the SMs it underfills at long-K decode shapes.
110
+ # The entry's presence in the build is not its qualification to
111
+ # run: the aarch64 package carries it and the kernel refuses at
112
+ # call time on anything below SM120, which surfaces as a runtime
113
+ # error on the first M=1 row rather than as a choice made here.
114
+ # The engine adapters already withhold it off SM120; deciding it
115
+ # once, where the arm is selected, means an impl bound directly -
116
+ # through structures.get(), or any hand assembly - behaves the
117
+ # same as one bound through a door.
118
+ gemv = getattr(kern, "fp4_w4a4_gemv_warpsplit_bf16", None)
119
+ cc = (torch.cuda.get_device_capability(w_packed.device)
120
+ if w_packed.is_cuda else (0, 0))
121
+ self._gemv = (gemv if gemv is not None and cc >= (12, 0)
122
+ and n % 8 == 0 and k % (64 * 4) == 0 else None)
123
+ self._frt_arm(dtypes=CAST_OK, device=w_packed.device, k=int(k))
124
+
125
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
126
+ admitted = self._frt_admit(x)
127
+ if admitted is not PROCEED:
128
+ return admitted
129
+ shape = x.shape
130
+ flat = x.reshape(-1, shape[-1])
131
+ a_packed, a_sfa = _quantize_activation(self._kern, flat)
132
+ if flat.shape[0] == 1 and self._gemv is not None:
133
+ y = self._gemv(a_packed, self._w_packed, a_sfa, self._w_sfb)
134
+ else:
135
+ y = self._gemm(a_packed, self._w_packed, a_sfa, self._w_sfb,
136
+ variant=2)
137
+ if self._bias is not None:
138
+ y = y + self._bias
139
+ return y.reshape(*shape[:-1], self._n).type_as(x)
140
+
141
+
142
+ @torch.no_grad()
143
+ def bind_proj_seam(
144
+ weights: Mapping[str, torch.Tensor],
145
+ ) -> tuple[LinearProjNvfp4Dynamic, float]:
146
+ """Bind one projection from a dense ``[N, K]`` weight.
147
+
148
+ The weight is packed to the Hub kernel's NVFP4 layout on the GPU;
149
+ the returned float is the pack-and-unpack relative L2 against the
150
+ input weight — the *conversion* cost of regridding into this layout,
151
+ reported so a caller adopting a whole checkpoint can put it in the
152
+ receipt instead of losing it.
153
+ """
154
+ n, k = _check(weights)
155
+ kern = _kernel()
156
+ w = weights["w"].to("cuda", torch.float16).contiguous()
157
+ w_packed, w_sfb = kern.quantize_fp4_sfa_fp16(w, is_sfb=True)
158
+ # the conversion check accumulates in row slabs: a whole-tensor
159
+ # FP32 dequant doubles the bind's transient footprint, and on
160
+ # head-class weights that spike is what fails under a tight budget
161
+ deq = kern.dequantize_fp4_sfa_fp16(w_packed, w_sfb)
162
+ num_sq = den_sq = 0.0
163
+ for i in range(0, n, 4096):
164
+ diff = deq[i:i + 4096].float() - w[i:i + 4096].float()
165
+ num_sq += float(diff.square().sum())
166
+ den_sq += float(w[i:i + 4096].float().square().sum())
167
+ del deq
168
+ rel = (num_sq ** 0.5) / max(den_sq ** 0.5, 1e-12)
169
+ bias = weights.get("b")
170
+ if bias is not None:
171
+ bias = bias.detach().to("cuda", torch.bfloat16)
172
+ bound = LinearProjNvfp4Dynamic(w_packed, w_sfb, bias, n, k)
173
+ # bind-time smoke: one M=1 launch through the real entry point before
174
+ # the seam is handed out — a stale build or missing symbol surfaces
175
+ # as a clean bind refusal, not later inside the host's forward
176
+ probe = bound(torch.zeros(1, k, device=w_packed.device,
177
+ dtype=torch.bfloat16))
178
+ if probe.shape != (1, n) or not torch.isfinite(probe).all():
179
+ raise ValueError(
180
+ f"refused: nvfp4 bind smoke produced shape "
181
+ f"{tuple(probe.shape)}, finite={bool(torch.isfinite(probe).all())}")
182
+ return bound, rel
@@ -0,0 +1,230 @@
1
+ """Weight-only INT8 implementation of the ``linear_proj`` structure.
2
+
3
+ The decode-band twin of the FP8 projection impl: weights are quantized
4
+ per output channel to INT8 at bind time, activations stay BF16, so the
5
+ seam needs no calibration data at all. This is the projection-shaped
6
+ slice of the recipe already shipped for ``decoder_ffn`` —
7
+ ``w8a16_static`` there covers the gated MLP, this file covers the
8
+ attention Q/K/V/O family and any other single projection the discovery
9
+ qualifies.
10
+
11
+ The ``flashrt/weight-only-ffn`` package's linear entry point qualifies
12
+ its auto dispatch narrowly, and this impl mirrors that table exactly
13
+ rather than stretching it (``torch_binding.cpp``: ``check_variant`` and
14
+ ``w8_auto_linear_supported``):
15
+
16
+ - M in [1, 4] — the decode band; and
17
+ - K <= 1024 always qualifies; K <= 4096 needs N >= 1024; larger K needs
18
+ N >= 1024 for M <= 2 and N >= 2048 for M in {3, 4}.
19
+
20
+ Calls outside the band are dispatched to the retained host module by
21
+ declared plan, counted in the ledger — prefill runs the host GEMM,
22
+ decode runs the kernel, and the qualification record states which band
23
+ the kernel serves.
24
+
25
+ The linear entry point carries no bias operand. A projection with a
26
+ bias gets it added in BF16 after the GEMM — one [M<=4, N] elementwise
27
+ add inside the decode band, where the weight read dominates end to end.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ from collections.abc import Mapping
33
+ from functools import lru_cache
34
+
35
+ import torch
36
+
37
+ from ...guard import CAST_OK, PROCEED, GuardedSeam
38
+
39
+ KERNEL_DEP = {
40
+ "provider": "huggingface_kernels",
41
+ "repo": "flashrt/weight-only-ffn",
42
+ "version": ">=1",
43
+ }
44
+
45
+ SUPPORT = {
46
+ "K": {"min": 512, "max": 16384},
47
+ "N": {"min": 128, "max": 262144},
48
+ "M": {"min": 1, "max": 4},
49
+ "m_classes": ("micro",),
50
+ }
51
+
52
+
53
+ def _qualified(m: int, n: int, k: int) -> bool:
54
+ """The kernel's own auto-dispatch qualification, mirrored.
55
+
56
+ Copied from ``w8_auto_linear_supported`` plus the ``variant=0``
57
+ M-bound in the package's ``torch_binding.cpp`` — the kernel raises
58
+ outside this table, so the band dispatch must agree with it, not
59
+ rediscover it as runtime errors.
60
+ """
61
+ if not SUPPORT["M"]["min"] <= m <= SUPPORT["M"]["max"]:
62
+ return False
63
+ if k <= 1024:
64
+ return True
65
+ if k <= 4096:
66
+ return n >= 1024
67
+ return n >= (1024 if m <= 2 else 2048)
68
+
69
+
70
+ @lru_cache(maxsize=1)
71
+ def _kernel():
72
+ from flashrt_structures.impls import hub_kernel
73
+
74
+ return hub_kernel(KERNEL_DEP["repo"], KERNEL_DEP["version"])
75
+
76
+
77
+ def _check(weights: Mapping[str, torch.Tensor]) -> tuple[int, int]:
78
+ w = weights["w"]
79
+ if w.dim() != 2:
80
+ raise ValueError(f"w must be [N, K], got {tuple(w.shape)}")
81
+ n, k = w.shape
82
+ for name, dim in (("K", k), ("N", n)):
83
+ bounds = SUPPORT[name]
84
+ if not bounds["min"] <= dim <= bounds["max"]:
85
+ raise ValueError(
86
+ f"{name}={dim} outside support envelope "
87
+ f"[{bounds['min']}, {bounds['max']}]")
88
+ b = weights.get("b")
89
+ if b is not None and tuple(b.shape) != (n,):
90
+ raise ValueError(
91
+ f"bias shape {tuple(b.shape)} does not match N={n}")
92
+ return n, k
93
+
94
+
95
+ class BoundLinearProjW8A16:
96
+ """Projection callable: x[M, K] in, y[M, N] out (BF16)."""
97
+
98
+ def __init__(self, linear_fn, w_q, w_scale, bias, n, k):
99
+ self._linear = linear_fn
100
+ self._w_q = w_q
101
+ self._w_scale = w_scale
102
+ self._bias = bias
103
+ self._n = n
104
+ self._k = k
105
+
106
+ def project(self, x: torch.Tensor) -> torch.Tensor:
107
+ shape = x.shape
108
+ flat = x.reshape(-1, shape[-1])
109
+ m = flat.shape[0]
110
+ if not _qualified(m, self._n, self._k):
111
+ raise ValueError(
112
+ f"M={m} outside the W8A16 auto-dispatch qualification "
113
+ f"for N={self._n}, K={self._k} (decode band M in "
114
+ f"[1, {SUPPORT['M']['max']}])")
115
+ y = self._linear(flat.to(torch.bfloat16).contiguous(),
116
+ self._w_q, self._w_scale)
117
+ if self._bias is not None:
118
+ y = y + self._bias
119
+ return y.reshape(*shape[:-1], self._n).to(x.dtype)
120
+
121
+ __call__ = project
122
+
123
+
124
+ class LinearProjW8A16(GuardedSeam, torch.nn.Module):
125
+ """Drop-in projection module with declared M-dispatch.
126
+
127
+ The weight-only kernel covers the decode band; calls with larger M
128
+ (prefill) are dispatched to the retained host module. This is part
129
+ of the declared plan — per-M dispatch on the real workload — not a
130
+ fallback: both paths are first-class, and the ledger counts the
131
+ dispatch so neither path's share of the calls is ever unknown.
132
+
133
+ ``original`` is retained whole and attribute lookups fall through to
134
+ it, so host code that introspects ``weight``/``in_features`` keeps
135
+ working.
136
+ """
137
+
138
+ _frt_host_attr = "host_linear"
139
+ _frt_can_fallback = True
140
+
141
+ def __init__(self, bound: BoundLinearProjW8A16,
142
+ original: torch.nn.Module | None = None):
143
+ super().__init__()
144
+ self._bound = bound
145
+ # the same tensors, reachable through *module* attributes: an
146
+ # exporter attributes a tensor by its access path, and a tensor
147
+ # reached only through a plain object gets lifted as an
148
+ # anonymous immutable constant — unnameable in a
149
+ # weights-external package. Identity is unchanged.
150
+ self.register_buffer("_frt_w_q", bound._w_q)
151
+ self.register_buffer("_frt_w_scale", bound._w_scale)
152
+ if bound._bias is not None:
153
+ self.register_buffer("_frt_bias", bound._bias)
154
+ else:
155
+ self._frt_bias = None
156
+ if original is not None:
157
+ self.host_linear = original
158
+ guard = self._frt_arm(dtypes=CAST_OK,
159
+ device=bound._w_q.device,
160
+ k=int(bound._k))
161
+ guard.notes["dispatched_by_band"] = 0
162
+
163
+ def __getattr__(self, name):
164
+ try:
165
+ return super().__getattr__(name)
166
+ except AttributeError:
167
+ if name == "host_linear":
168
+ raise
169
+ return getattr(super().__getattr__("host_linear"), name)
170
+
171
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
172
+ admitted = self._frt_admit(x)
173
+ if admitted is not PROCEED:
174
+ return admitted
175
+ m = x.numel() // x.shape[-1]
176
+ if not _qualified(m, self._bound._n, self._bound._k):
177
+ host = self._frt_host()
178
+ if host is not None:
179
+ guard = self._frt_guard
180
+ if guard is not None and not torch.compiler.is_compiling():
181
+ guard.notes["dispatched_by_band"] += 1
182
+ return host(x)
183
+ return self._bound.project(x) # states the refusal
184
+ shape = x.shape
185
+ flat = x.reshape(-1, shape[-1])
186
+ y = self._bound._linear(flat.to(torch.bfloat16).contiguous(),
187
+ self._frt_w_q, self._frt_w_scale)
188
+ if self._frt_bias is not None:
189
+ y = y + self._frt_bias
190
+ return y.reshape(*shape[:-1], self._bound._n).to(x.dtype)
191
+
192
+
193
+ @torch.no_grad()
194
+ def bind_proj_seam(
195
+ weights: Mapping[str, torch.Tensor],
196
+ *,
197
+ original: torch.nn.Module | None = None,
198
+ ) -> LinearProjW8A16:
199
+ """Bind one projection with weight-only INT8.
200
+
201
+ ``weights['w']`` is checkpoint-layout ``[N, K]``, exactly what the
202
+ kernel consumes — no transpose. No calibration data is required:
203
+ quantization is per-output-channel on weights only, and the optional
204
+ ``weights['b']`` is kept in BF16.
205
+ """
206
+ n, k = _check(weights)
207
+ if not _qualified(1, n, k):
208
+ raise ValueError(
209
+ f"refused: N={n}, K={k} has no qualified fast path even at "
210
+ f"M=1; the W8A16 projection cannot serve this seam at any M")
211
+ kern = _kernel()
212
+ w = weights["w"].to("cuda", torch.bfloat16).contiguous()
213
+ w_q, w_scale = kern.quantize_w8_weight_bf16(w)
214
+ bias = weights.get("b")
215
+ if bias is not None:
216
+ bias = bias.detach().to("cuda", torch.bfloat16)
217
+ bound = BoundLinearProjW8A16(kern.w8a16_linear_bf16, w_q, w_scale,
218
+ bias, n, k)
219
+ # bind-time smoke: one M=1 launch through the real entry point before
220
+ # the seam is handed out. A stale build or missing symbol must
221
+ # surface here as a clean bind refusal, not later inside the host's
222
+ # forward — identical output cannot catch it there, because the
223
+ # fallback path is numerically exact.
224
+ probe = bound.project(torch.zeros(1, k, device=w_q.device,
225
+ dtype=torch.bfloat16))
226
+ if probe.shape != (1, n) or not torch.isfinite(probe).all():
227
+ raise ValueError(
228
+ f"refused: w8a16 bind smoke produced shape "
229
+ f"{tuple(probe.shape)}, finite={bool(torch.isfinite(probe).all())}")
230
+ return LinearProjW8A16(bound, original=original)
File without changes