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,545 @@
1
+ """Fused decode form of a whole gated-delta layer.
2
+
3
+ The transformers fallback runs this layer as ~75 launches of Python
4
+ glue per token; serving its pieces individually is measurably negative
5
+ on a launch-bound host (a quantized projection swap *lost* throughput
6
+ here — the receipts are on the record). This impl owns the layer's
7
+ cached-decode step as one short chain of Hub kernels:
8
+
9
+ packed in_proj GEMV -> causal_conv1d_update -> broadcast QKV split
10
+ -> gating -> gated-delta recurrent core -> gated RMSNorm -> out_proj
11
+
12
+ A scheme may route the two projection GEMVs through the dynamic NVFP4
13
+ band (``gdn_projection_format="nvfp4_dynamic"``); the BF16 weights are
14
+ retained for prefill and detach either way.
15
+
16
+ Everything else — prefill, uncached calls, masked batches — dispatches
17
+ to the retained host layer and is counted.
18
+
19
+ Cache contract (the host's, followed not replaced): the layer reads and
20
+ writes ``cache_params.conv_states[idx]`` and ``recurrent_states[idx]``.
21
+ The host keeps the last K raw inputs in the conv state; the Hub update
22
+ kernel keeps the previous K-1, so the impl feeds ``state[..., 1:]`` and
23
+ rolls the host slot forward itself. The recurrent state slot is
24
+ normalised to a stable BF16 tensor on the first decode step and never
25
+ re-pointed after that: the core writes a scratch buffer (it cannot
26
+ write the slot it is reading within the same step) and the result is
27
+ copied back into the slot, which is what graph replay requires.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ from functools import lru_cache
33
+
34
+ import torch
35
+
36
+ from ...guard import CAST_OK, PROCEED, GuardedSeam
37
+
38
+ GDA_DEP = {"provider": "hf", "repo": "flashrt/gated-delta-attention",
39
+ "version": ">=3"}
40
+ CONV_DEP = {"provider": "hf", "repo": "flashrt/causal-conv1d-state",
41
+ "version": ">=1"}
42
+ FUSED_DEP = {"provider": "hf", "repo": "flashrt/transformer-fused-ops",
43
+ "version": ">=1"}
44
+
45
+
46
+ @lru_cache(maxsize=1)
47
+ def _packages():
48
+ from flashrt_structures.impls import hub_kernel
49
+
50
+ gda = hub_kernel(GDA_DEP["repo"], GDA_DEP["version"])
51
+ conv = hub_kernel(CONV_DEP["repo"], CONV_DEP["version"])
52
+ fused = hub_kernel(FUSED_DEP["repo"], FUSED_DEP["version"])
53
+ for pkg, name in ((gda, "lin_split_qkv_broadcast_bf16"),
54
+ (gda, "gdn_gating_bf16"),
55
+ (gda, "gated_delta_recurrent_inout_bf16"),
56
+ (conv, "causal_conv1d_update_bf16"),
57
+ (fused, "rms_norm_gated_silu_bf16")):
58
+ if not hasattr(pkg, name):
59
+ raise ValueError(
60
+ f"refused: installed build lacks {name}; a release "
61
+ "carrying the fused decode chain is required")
62
+ return gda, conv, fused
63
+
64
+
65
+ class FusedGatedDeltaDecodeLayer(GuardedSeam, torch.nn.Module):
66
+ """Drop-in replacement for one gated-delta layer module."""
67
+
68
+ _frt_host_attr = "host_layer"
69
+ _frt_can_fallback = True
70
+
71
+ def __init__(self, host, layer_idx: int,
72
+ projection_format: str | None = None):
73
+ super().__init__()
74
+ gda, conv, fused = _packages()
75
+ self._gda, self._conv, self._fused = gda, conv, fused
76
+ self.host_layer = host
77
+ self._idx = int(layer_idx)
78
+ self._hv = int(host.num_v_heads)
79
+ self._hk = int(host.num_k_heads)
80
+ self._d = int(host.head_v_dim)
81
+ if (self._d != 128 or int(host.head_k_dim) != 128
82
+ or self._hv <= 0 or self._hk <= 0
83
+ or self._hv % self._hk):
84
+ raise ValueError(
85
+ "fused decode chain serves D=128 profiles whose v-head "
86
+ "count is a multiple of the k-head count; other "
87
+ "profiles keep the host layer")
88
+ # the original 48/16 profile keeps its dedicated entries,
89
+ # byte-for-byte; every other profile routes the head-generic
90
+ # entries, whose absence from an older build is a clean bind
91
+ # refusal (the ladder falls back to the callable-slot rule)
92
+ if (self._hv, self._hk) == (48, 16):
93
+ self._split_fn = gda.lin_split_qkv_broadcast_bf16
94
+ self._gate_fn = gda.gdn_gating_bf16
95
+ self._chunk_name = "gdn_chunk_from_conv_smem_bf16"
96
+ else:
97
+ for name in ("lin_split_qkv_broadcast_h_bf16",
98
+ "gdn_gating_h_bf16"):
99
+ if not hasattr(gda, name):
100
+ raise ValueError(
101
+ f"refused: installed build lacks {name}; the "
102
+ f"{self._hv}/{self._hk}-head profile needs the "
103
+ "head-generic chain entries")
104
+ hv, hk, d = self._hv, self._hk, self._d
105
+
106
+ def _split(conv_out):
107
+ return gda.lin_split_qkv_broadcast_h_bf16(
108
+ conv_out, hv, hk, d)
109
+
110
+ def _gate(a, b, neg_exp_a, dt_bias):
111
+ return gda.gdn_gating_h_bf16(a, b, neg_exp_a, dt_bias,
112
+ num_heads=hv)
113
+
114
+ self._split_fn = _split
115
+ self._gate_fn = _gate
116
+ self._chunk_name = "gdn_chunk_from_conv_smem_h_bf16"
117
+ dev = host.in_proj_qkv.weight.device
118
+ # the four input projections read the same activation; packing
119
+ # them row-wise turns four GEMV launches into one, bit-identical
120
+ # per output row. The host projections are rebound onto views of
121
+ # the packed rows so the layer still carries one copy of these
122
+ # weights — prefill and detach see the exact same values.
123
+ self._packed_w = torch.cat(
124
+ [host.in_proj_qkv.weight.detach(),
125
+ host.in_proj_z.weight.detach(),
126
+ host.in_proj_b.weight.detach(),
127
+ host.in_proj_a.weight.detach()], dim=0)
128
+ self._splits = []
129
+ off = 0
130
+ for name in ("in_proj_qkv", "in_proj_z", "in_proj_b",
131
+ "in_proj_a"):
132
+ lin = getattr(host, name)
133
+ n = int(lin.weight.shape[0])
134
+ lin.weight = torch.nn.Parameter(
135
+ self._packed_w[off:off + n],
136
+ requires_grad=lin.weight.requires_grad)
137
+ self._splits.append((off, off + n))
138
+ off += n
139
+ self._conv_w = host.conv1d.weight.detach().squeeze(1).contiguous()
140
+ self._conv_b = (host.conv1d.bias.detach().contiguous()
141
+ if host.conv1d.bias is not None else None)
142
+ self._neg_exp_a = (-host.A_log.detach().float().exp()).contiguous()
143
+ self._dt_bias = host.dt_bias.detach().float().contiguous()
144
+ self._eps = float(getattr(host.norm, "variance_epsilon",
145
+ getattr(host.norm, "eps", 1e-6)))
146
+ d_model = int(host.in_proj_qkv.weight.shape[1])
147
+ # optional W4A4 decode band on the two projection GEMVs — a
148
+ # scheme decision, never a default. The BF16 weights (and the
149
+ # host views into them) are retained: prefill and detach stay
150
+ # exact, only the decode band changes representation. A refusal
151
+ # (missing package, unqualified shape) degrades to the BF16
152
+ # band for this layer and is counted, not raised.
153
+ self._proj_in = self._proj_out = None
154
+ if projection_format == "nvfp4_dynamic":
155
+ from ..linear_proj import nvfp4_dynamic
156
+ try:
157
+ self._proj_in, rel_in = nvfp4_dynamic.bind_proj_seam(
158
+ {"w": self._packed_w})
159
+ self._proj_out, rel_out = nvfp4_dynamic.bind_proj_seam(
160
+ {"w": host.out_proj.weight.detach()})
161
+ self._proj_rel = (rel_in, rel_out)
162
+ except ValueError:
163
+ self._proj_in = self._proj_out = None
164
+ elif projection_format is not None:
165
+ raise ValueError(
166
+ f"refused: unknown gdn projection format "
167
+ f"{projection_format!r}")
168
+ self._state_a = torch.empty(1, self._hv, self._d, self._d,
169
+ device=dev, dtype=torch.bfloat16)
170
+ self._core_out = torch.empty(1, self._hv, self._d, device=dev,
171
+ dtype=torch.bfloat16)
172
+ # prefill chain needs the chunk entries; their absence is not a
173
+ # bind refusal — prompts simply keep the host form
174
+ self._chunk_ok = (
175
+ hasattr(conv, "causal_conv1d_update_chunk_parallel_bf16")
176
+ and hasattr(gda, self._chunk_name))
177
+ # the WY pipeline is the prompt-length core: one fused chain
178
+ # over all chunks, state carried inside the kernels — the
179
+ # serial per-chunk walk of the fallback core is the measured
180
+ # long-prompt TTFT term. Its entries are 48/16-shaped; other
181
+ # profiles keep the chunk walk until head-generic WY ships.
182
+ self._wy_h = (self._hv, self._hk) != (48, 16)
183
+ _wy_names = ((
184
+ "gdn_wy_norm_cumsum_pack_qk_h_bf16",
185
+ "gdn_wy_kkt_b64_h_bf16",
186
+ "gdn_wy_cast_ai_h_f32_to_bf16",
187
+ "gdn_wy_recompute_wu_b64_mma_fla_h_bf16",
188
+ "gdn_wy_chunk_h_b64_mma_fla_h_bf16",
189
+ "gdn_wy_output_o_b64_mma_fla_h_bf16",
190
+ ) if self._wy_h else (
191
+ "lin_split_qkv_gqa_bf16",
192
+ "gdn_wy_norm_cumsum_pack_qk_bf16",
193
+ "gdn_wy_kkt_b64_bf16",
194
+ "gdn_wy_cast_ai_f32_to_bf16",
195
+ "gdn_wy_recompute_wu_b64_mma_fla_bf16",
196
+ "gdn_wy_chunk_h_b64_mma_fla_bf16",
197
+ "gdn_wy_output_o_b64_mma_fla_bf16",
198
+ ))
199
+ self._wy_ok = (self._chunk_ok
200
+ and all(hasattr(gda, n) for n in _wy_names))
201
+ guard = self._frt_arm(dtypes=CAST_OK, device=dev, k=d_model)
202
+ guard.notes["host_form_calls"] = 0
203
+ guard.notes["proj_band"] = ("nvfp4" if self._proj_in is not None
204
+ else "bf16")
205
+
206
+ def __getattr__(self, name):
207
+ try:
208
+ return super().__getattr__(name)
209
+ except AttributeError:
210
+ if name == "host_layer":
211
+ raise
212
+ return getattr(super().__getattr__("host_layer"), name)
213
+
214
+ def _host_form(self, *args, **kwargs):
215
+ if getattr(self, "_released", False):
216
+ raise ValueError(
217
+ "refused: host projection weights were released "
218
+ "(one-way band); this call shape has no host fallback")
219
+ guard = self._frt_guard
220
+ if guard is not None and not torch.compiler.is_compiling():
221
+ guard.notes["host_form_calls"] += 1
222
+ return self.host_layer(*args, **kwargs)
223
+
224
+ def _prefill_chain(self, hidden_states, cache_params):
225
+ """Whole-prompt form: conv chunk + fused gating/split/recurrent.
226
+
227
+ Chunks of 64 carry the conv state and the recurrent state
228
+ forward in place, so any prompt length runs through the same
229
+ two kernels per chunk. Larger slabs are on the record as a
230
+ negative: at S>64 the chunk kernel's internal combine is not
231
+ run-to-run stable (the repeat gate caught it) and the latency
232
+ win measured under three percent — the fixed-order 64 chunk is
233
+ the contract. Both host cache slots are written with the
234
+ host's own semantics (last-K raw inputs; final state).
235
+ """
236
+ host = self.host_layer
237
+ S = hidden_states.shape[1]
238
+ x = hidden_states.view(S, -1)
239
+ allp = (self._proj_in(x) if self._proj_in is not None
240
+ else torch.nn.functional.linear(x, self._packed_w))
241
+ (q0, q1), (z0, z1), (b0, b1), (a0, a1) = self._splits
242
+ mixed = allp[:, q0:q1].contiguous()
243
+ a_all = allp[:, a0:a1].contiguous()
244
+ b_all = allp[:, b0:b1].contiguous()
245
+ kk = self._conv_w.shape[-1]
246
+ # continuation (a verify batch mid-stream) seeds from the live
247
+ # slots; a fresh prompt starts from zero. The signal is an
248
+ # explicit attribute only loop-owned caches carry — host caches
249
+ # lack it and always get prompt semantics.
250
+ # three continuation sources, one rule: a filled conv slot means
251
+ # mid-stream unless the cache explicitly says fresh. Hosts that
252
+ # chunk long prompts re-enter this branch per chunk with their
253
+ # own cache carrying state (the 2K receipts caught the zero-
254
+ # reset); loop-owned caches say False around a fresh prompt and
255
+ # True around a verify batch.
256
+ flag = getattr(cache_params, "frt_continue", None)
257
+ old_slot = cache_params.conv_states[self._idx]
258
+ cont = torch.is_tensor(old_slot) and flag is not False
259
+ if cont:
260
+ conv_state = old_slot[:, :, 1:].contiguous().clone()
261
+ state = cache_params.recurrent_states[self._idx] \
262
+ .view(self._hv, self._d, self._d) \
263
+ .to(torch.bfloat16).contiguous().clone()
264
+ else:
265
+ conv_state = torch.zeros(1, mixed.shape[1], kk - 1,
266
+ device=mixed.device,
267
+ dtype=mixed.dtype)
268
+ state = torch.zeros(self._hv, self._d, self._d,
269
+ device=mixed.device, dtype=torch.bfloat16)
270
+ if self._wy_ok and S > 64:
271
+ # the WY pipeline packs the whole span up front — gigabytes
272
+ # of transients at deep prompts. Slabs bound the working
273
+ # set: conv_state and state carry in place across slab
274
+ # calls exactly as they do across the 64-chunks inside, so
275
+ # the chunk sequence (and the arithmetic) is unchanged.
276
+ slab = 8192
277
+ if S > slab:
278
+ core_out = torch.empty(S, self._hv, self._d,
279
+ device=mixed.device,
280
+ dtype=torch.bfloat16)
281
+ for s0 in range(0, S, slab):
282
+ s1 = min(s0 + slab, S)
283
+ core_out[s0:s1] = self._wy_core(
284
+ mixed[s0:s1], a_all[s0:s1], b_all[s0:s1],
285
+ conv_state, state, s1 - s0)
286
+ else:
287
+ core_out = self._wy_core(mixed, a_all, b_all,
288
+ conv_state, state, S)
289
+ return self._prefill_epilogue(
290
+ hidden_states, cache_params, allp, mixed, core_out,
291
+ state, cont, old_slot, S)
292
+ core_out = torch.empty(S, self._hv, self._d,
293
+ device=mixed.device, dtype=torch.bfloat16)
294
+ for s0 in range(0, S, 64):
295
+ s1 = min(s0 + 64, S)
296
+ conv_out = self._conv.causal_conv1d_update_chunk_parallel_bf16(
297
+ mixed[s0:s1].view(1, s1 - s0, -1), self._conv_w,
298
+ conv_state, self._conv_b, apply_silu=True)
299
+ if self._chunk_name.endswith("_h_bf16"):
300
+ self._gda.gdn_chunk_from_conv_smem_h_bf16(
301
+ conv_out.view(s1 - s0, -1), a_all[s0:s1],
302
+ b_all[s0:s1], self._neg_exp_a, self._dt_bias, state,
303
+ num_v_heads=self._hv, num_k_heads=self._hk,
304
+ head_dim=self._d, use_qk_l2norm=True,
305
+ out=core_out[s0:s1])
306
+ else:
307
+ self._gda.gdn_chunk_from_conv_smem_bf16(
308
+ conv_out.view(s1 - s0, -1), a_all[s0:s1],
309
+ b_all[s0:s1], self._neg_exp_a, self._dt_bias, state,
310
+ use_qk_l2norm=True, out=core_out[s0:s1])
311
+ return self._prefill_epilogue(
312
+ hidden_states, cache_params, allp, mixed, core_out, state,
313
+ cont, old_slot, S)
314
+
315
+ def _wy_core(self, mixed, a_all, b_all, conv_state, state, S):
316
+ """Whole-prompt gated-delta core: the WY pipeline, one pass.
317
+
318
+ The conv update runs the full prompt in one launch; the WY
319
+ chain (norm/cumsum -> KKT -> triangular solve -> WU recompute
320
+ -> chunk-state carry -> output) keeps its chunks inside the
321
+ kernels, carrying ``state`` in place — no serial per-chunk walk
322
+ on the host, which is the measured long-prompt TTFT term the
323
+ fallback core pays.
324
+ """
325
+ gda = self._gda
326
+ conv_out = self._conv.causal_conv1d_update_chunk_parallel_bf16(
327
+ mixed.view(1, S, -1), self._conv_w, conv_state,
328
+ self._conv_b, apply_silu=True)
329
+ co = conv_out.view(S, -1)
330
+ g, beta = self._gate_fn(
331
+ a_all.view(S, self._hv), b_all.view(S, self._hv),
332
+ self._neg_exp_a, self._dt_bias)
333
+ if self._wy_h:
334
+ return self._wy_core_h(gda, co, g, beta, state, S)
335
+ q16, k16, v48 = gda.lin_split_qkv_gqa_bf16(co)
336
+ q16_l2, k16_l2, q_pack_hv, _k_pack_hk, g_cumsum = \
337
+ gda.gdn_wy_norm_cumsum_pack_qk_bf16(q16, k16, g)
338
+ big_a = gda.gdn_wy_kkt_b64_bf16(k16_l2, beta, g_cumsum)
339
+ # the packaged triangular solve walks its rows serially and is
340
+ # the measured 82% of this chain; the same inverse — semantics
341
+ # pinned numerically: inv(I + strict_tril(A)) — through the
342
+ # batched cuBLAS solve runs ~40x faster and stays deterministic
343
+ eye = torch.eye(64, device=big_a.device,
344
+ dtype=big_a.dtype).expand_as(big_a).contiguous()
345
+ ai = torch.linalg.solve_triangular(
346
+ eye + torch.tril(big_a, -1), eye, upper=False).contiguous()
347
+ ai_pack = gda.gdn_wy_cast_ai_f32_to_bf16(ai, S)
348
+ w_pack, u_pack = gda.gdn_wy_recompute_wu_b64_mma_fla_bf16(
349
+ k16_l2, v48, beta, g_cumsum, ai_pack)
350
+ h0, _v_new, v_new_pack, k_pack_hv = \
351
+ gda.gdn_wy_chunk_h_b64_mma_fla_bf16(
352
+ k16_l2, w_pack, u_pack, g_cumsum, state)
353
+ return gda.gdn_wy_output_o_b64_mma_fla_bf16(
354
+ q_pack_hv, k_pack_hv, v_new_pack, h0, g_cumsum)
355
+
356
+ def _wy_core_h(self, gda, co, g, beta, state, S):
357
+ """The head-generic arm of the WY pipeline (non-48/16 hosts).
358
+
359
+ The GQA split is contiguous column slices of the conv output —
360
+ pinned bit-equal to the dedicated split kernel on the record —
361
+ so the head-generic arm slices instead of asking for a kernel.
362
+ """
363
+ kd = self._hk * self._d
364
+ hp = {"num_v_heads": self._hv, "num_k_heads": self._hk,
365
+ "head_dim": self._d}
366
+ q = co[:, :kd].contiguous().view(S, self._hk, self._d)
367
+ k = co[:, kd:2 * kd].contiguous().view(S, self._hk, self._d)
368
+ v = co[:, 2 * kd:].contiguous().view(S, self._hv, self._d)
369
+ q_l2, k_l2, q_pack_hv, _k_pack_hk, g_cumsum = \
370
+ gda.gdn_wy_norm_cumsum_pack_qk_h_bf16(q, k, g, **hp)
371
+ big_a = gda.gdn_wy_kkt_b64_h_bf16(k_l2, beta, g_cumsum, **hp)
372
+ eye = torch.eye(64, device=big_a.device,
373
+ dtype=big_a.dtype).expand_as(big_a).contiguous()
374
+ ai = torch.linalg.solve_triangular(
375
+ eye + torch.tril(big_a, -1), eye, upper=False).contiguous()
376
+ ai_pack = gda.gdn_wy_cast_ai_h_f32_to_bf16(
377
+ ai, S, num_v_heads=self._hv)
378
+ w_pack, u_pack = gda.gdn_wy_recompute_wu_b64_mma_fla_h_bf16(
379
+ k_l2, v, beta, g_cumsum, ai_pack, **hp)
380
+ h0, _v_new, v_new_pack, k_pack_hv = \
381
+ gda.gdn_wy_chunk_h_b64_mma_fla_h_bf16(
382
+ k_l2, w_pack, u_pack, g_cumsum, state, **hp)
383
+ return gda.gdn_wy_output_o_b64_mma_fla_h_bf16(
384
+ q_pack_hv, k_pack_hv, v_new_pack, h0, g_cumsum, **hp)
385
+
386
+ def _prefill_epilogue(self, hidden_states, cache_params, allp,
387
+ mixed, core_out, state, cont, old_slot, S):
388
+ host = self.host_layer
389
+ (_q0, _q1), (z0, z1), _b, _a = self._splits
390
+ kk = self._conv_w.shape[-1]
391
+ normed = self._fused.rms_norm_gated_silu_bf16(
392
+ core_out.reshape(S * self._hv, self._d),
393
+ allp[:, z0:z1].contiguous().view(S * self._hv, self._d),
394
+ host.norm.weight, eps=self._eps)
395
+ flat_norm = normed.view(S, -1)
396
+ out = (self._proj_out(flat_norm) if self._proj_out is not None
397
+ else torch.nn.functional.linear(flat_norm,
398
+ host.out_proj.weight))
399
+ # write INTO existing slots when they match — a repoint here
400
+ # would strand a captured graph on the old tensors
401
+ state4 = state.view(1, self._hv, self._d, self._d)
402
+ rec = cache_params.recurrent_states[self._idx]
403
+ if (torch.is_tensor(rec) and rec.shape == state4.shape
404
+ and rec.dtype == state4.dtype):
405
+ rec.copy_(state4)
406
+ else:
407
+ cache_params.recurrent_states[self._idx] = state4
408
+ # the host slot keeps the last K *raw* projected inputs
409
+ take = min(kk, S)
410
+ cslot = cache_params.conv_states[self._idx]
411
+ if not (torch.is_tensor(cslot)
412
+ and cslot.shape == (1, mixed.shape[1], kk)
413
+ and cslot.dtype == mixed.dtype):
414
+ cslot = mixed.new_zeros(1, mixed.shape[1], kk)
415
+ cache_params.conv_states[self._idx] = cslot
416
+ if cont and S < kk:
417
+ # short continuation: the slot keeps the last kk raw inputs
418
+ # across the old tail and the new tokens
419
+ head = old_slot[:, :, S:].clone()
420
+ cslot[:, :, :kk - S].copy_(head)
421
+ else:
422
+ cslot.zero_()
423
+ cslot[0, :, kk - take:] = mixed[S - take:].t()
424
+ return out.view(1, S, -1).to(hidden_states.dtype)
425
+
426
+ def forward(self, hidden_states, cache_params=None,
427
+ attention_mask=None):
428
+ admitted = self._frt_admit(hidden_states)
429
+ if admitted is not PROCEED:
430
+ return admitted
431
+ decode = (cache_params is not None
432
+ and getattr(cache_params, "has_previous_state", False)
433
+ and hidden_states.shape[0] == 1
434
+ and hidden_states.shape[1] == 1
435
+ and (attention_mask is None
436
+ or bool(attention_mask.all())))
437
+ if not decode:
438
+ if (self._chunk_ok and cache_params is not None
439
+ and hidden_states.shape[0] == 1
440
+ and hidden_states.shape[1] > 1
441
+ and (attention_mask is None
442
+ or bool(attention_mask.all()))):
443
+ return self._prefill_chain(hidden_states, cache_params)
444
+ return self._host_form(hidden_states, cache_params,
445
+ attention_mask)
446
+
447
+ return self._decode_one(hidden_states, cache_params)
448
+
449
+ def _decode_one(self, hidden_states, cache_params):
450
+ host = self.host_layer
451
+ x = hidden_states.view(1, -1)
452
+ allp = (self._proj_in(x) if self._proj_in is not None
453
+ else torch.nn.functional.linear(x, self._packed_w))
454
+ # column slices of a single-row output stay contiguous
455
+ (q0, q1), (z0, z1), (b0, b1), (a0, a1) = self._splits
456
+ mixed = allp[:, q0:q1]
457
+ z = allp[:, z0:z1]
458
+ b = allp[:, b0:b1]
459
+ a = allp[:, a0:a1]
460
+
461
+ conv_host = cache_params.conv_states[self._idx]
462
+ hub_state = conv_host[:, :, 1:].contiguous()
463
+ conv_out = self._conv.causal_conv1d_update_bf16(
464
+ mixed, self._conv_w, hub_state, self._conv_b,
465
+ apply_silu=True)
466
+ # the host slot keeps the last K raw inputs; roll it forward.
467
+ # hub_state is a snapshot, so the two writes never overlap reads.
468
+ conv_host[:, :, :-1].copy_(hub_state)
469
+ conv_host[:, :, -1:].copy_(mixed.view(1, -1, 1))
470
+
471
+ q, k, v = self._split_fn(conv_out)
472
+ g, beta = self._gate_fn(
473
+ a.view(1, self._hv), b.view(1, self._hv),
474
+ self._neg_exp_a, self._dt_bias)
475
+ state_in = cache_params.recurrent_states[self._idx]
476
+ if state_in.dtype != torch.bfloat16 or not state_in.is_contiguous():
477
+ # normalise the cache slot to a contiguous BF16 tensor once
478
+ # (first decode after prefill); after this the slot pointer
479
+ # never changes, which is what graph replay requires
480
+ state_in = state_in.to(torch.bfloat16).contiguous()
481
+ cache_params.recurrent_states[self._idx] = state_in
482
+ core_out, new_state = self._gda.gated_delta_recurrent_inout_bf16(
483
+ q.view(1, self._hv, self._d), k.view(1, self._hv, self._d),
484
+ v.view(1, self._hv, self._d), g, beta,
485
+ state_in, use_qk_l2norm=True,
486
+ state_out=self._state_a, out=self._core_out)
487
+ # scratch -> slot copy keeps the slot pointer stable; the core
488
+ # cannot write the slot it is reading within the same step
489
+ state_in.copy_(new_state)
490
+
491
+ normed = self._fused.rms_norm_gated_silu_bf16(
492
+ core_out.view(self._hv, self._d), z.view(self._hv, self._d),
493
+ host.norm.weight, eps=self._eps)
494
+ flat_norm = normed.view(1, -1)
495
+ out = (self._proj_out(flat_norm) if self._proj_out is not None
496
+ else torch.nn.functional.linear(flat_norm,
497
+ host.out_proj.weight))
498
+ return out.view(1, 1, -1).to(hidden_states.dtype)
499
+
500
+
501
+ @torch.no_grad()
502
+ def bind_fused_decode_layer(host, layer_idx: int,
503
+ projection_format: str | None = None,
504
+ release_host_weights: bool = False):
505
+ """Bind one layer; a smoke step runs on zeros before handing out."""
506
+ bound = FusedGatedDeltaDecodeLayer(host, layer_idx,
507
+ projection_format)
508
+
509
+ class _Cache:
510
+ pass
511
+
512
+ cache = _Cache()
513
+ d_model = int(host.in_proj_qkv.weight.shape[1])
514
+ conv_k = int(host.conv1d.weight.shape[-1])
515
+ conv_dim = int(host.conv1d.weight.shape[0])
516
+ dev = host.in_proj_qkv.weight.device
517
+ cache.conv_states = {layer_idx: torch.zeros(
518
+ 1, conv_dim, conv_k, device=dev, dtype=torch.bfloat16)}
519
+ cache.recurrent_states = {layer_idx: torch.zeros(
520
+ 1, bound._hv, bound._d, bound._d, device=dev,
521
+ dtype=torch.bfloat16)}
522
+ cache.has_previous_state = True
523
+ probe = bound(torch.zeros(1, 1, d_model, device=dev,
524
+ dtype=torch.bfloat16), cache, None)
525
+ if probe.shape != (1, 1, d_model) or \
526
+ not torch.isfinite(probe.float()).all():
527
+ raise ValueError("refused: fused decode chain smoke failed")
528
+ guard = bound._frt_guard
529
+ if guard is not None:
530
+ guard.calls = 0
531
+ if release_host_weights and bound._proj_in is not None \
532
+ and bound._proj_out is not None:
533
+ # one-way: the FP4 band passed its smoke, the BF16 projection
534
+ # weights go. From here the host form refuses instead of
535
+ # falling back, and detach restores structure, not bytes.
536
+ empty = torch.nn.Parameter(
537
+ host.in_proj_qkv.weight.new_empty(0), requires_grad=False)
538
+ for name in ("in_proj_qkv", "in_proj_z", "in_proj_b",
539
+ "in_proj_a", "out_proj"):
540
+ getattr(host, name).weight = empty
541
+ bound._packed_w = None
542
+ bound._released = True
543
+ if guard is not None:
544
+ guard.notes["host_weights"] = "released (one-way)"
545
+ return bound