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.
- flashrt_structures/__init__.py +174 -0
- flashrt_structures/adapters/__init__.py +55 -0
- flashrt_structures/adapters/diffusers_attention.py +237 -0
- flashrt_structures/adapters/diffusers_rotary_attention.py +239 -0
- flashrt_structures/adapters/factored_qk_norm_rope.py +252 -0
- flashrt_structures/adapters/factored_two_way_attention.py +99 -0
- flashrt_structures/adapters/gemma_attention.py +226 -0
- flashrt_structures/adapters/packed_qkv_rope.py +342 -0
- flashrt_structures/adapters/packed_stream_qk_norm_rope.py +376 -0
- flashrt_structures/adapters/qwen_per_head_qk_norm_rope.py +231 -0
- flashrt_structures/adapters/sglang_engine.py +207 -0
- flashrt_structures/adapters/transformers_attention_interface.py +73 -0
- flashrt_structures/adapters/transformers_gated_delta.py +152 -0
- flashrt_structures/adapters/transformers_gated_delta_fused.py +96 -0
- flashrt_structures/adapters/vllm_engine.py +424 -0
- flashrt_structures/adjudicate.py +85 -0
- flashrt_structures/aot.py +191 -0
- flashrt_structures/autobuild.py +2052 -0
- flashrt_structures/beta/__init__.py +43 -0
- flashrt_structures/beta/conform.py +94 -0
- flashrt_structures/beta/joins.py +113 -0
- flashrt_structures/beta/negotiate.py +84 -0
- flashrt_structures/beta/ports.py +140 -0
- flashrt_structures/decisions.py +80 -0
- flashrt_structures/discover.py +623 -0
- flashrt_structures/explain.py +70 -0
- flashrt_structures/frontdoor.py +572 -0
- flashrt_structures/gates.py +465 -0
- flashrt_structures/guard.py +421 -0
- flashrt_structures/handle.py +189 -0
- flashrt_structures/impls/__init__.py +219 -0
- flashrt_structures/impls/adaln_producer/__init__.py +8 -0
- flashrt_structures/impls/adaln_producer/broker.py +116 -0
- flashrt_structures/impls/adaln_producer/fused.py +388 -0
- flashrt_structures/impls/adarms_stack/__init__.py +8 -0
- flashrt_structures/impls/adarms_stack/fp8_chain.py +832 -0
- flashrt_structures/impls/adarms_stack/region.py +102 -0
- flashrt_structures/impls/attention_core/__init__.py +132 -0
- flashrt_structures/impls/attention_core/fa2_seqused.py +458 -0
- flashrt_structures/impls/attention_core/fa4_cute.py +154 -0
- flashrt_structures/impls/attention_core/fa4_fp8.py +178 -0
- flashrt_structures/impls/attention_core/masked_mha.py +158 -0
- flashrt_structures/impls/attention_core/two_way_fa2.py +220 -0
- flashrt_structures/impls/cadence_static/__init__.py +18 -0
- flashrt_structures/impls/cadence_static/buffers.py +122 -0
- flashrt_structures/impls/cadence_static/cross_attention.py +187 -0
- flashrt_structures/impls/chain_elements.py +89 -0
- flashrt_structures/impls/decode_loop/__init__.py +0 -0
- flashrt_structures/impls/decode_loop/fp8_kv.py +206 -0
- flashrt_structures/impls/decode_loop/mtp_speculative.py +245 -0
- flashrt_structures/impls/decode_loop/whole_step.py +852 -0
- flashrt_structures/impls/decoder_block/__init__.py +6 -0
- flashrt_structures/impls/decoder_block/attn_sublayer.py +110 -0
- flashrt_structures/impls/decoder_block/fused.py +167 -0
- flashrt_structures/impls/decoder_ffn/__init__.py +0 -0
- flashrt_structures/impls/decoder_ffn/fp8_static.py +310 -0
- flashrt_structures/impls/decoder_ffn/fp8_static.yaml +22 -0
- flashrt_structures/impls/decoder_ffn/w4a16_static.py +221 -0
- flashrt_structures/impls/decoder_ffn/w8a16_static.py +183 -0
- flashrt_structures/impls/dit_stack/__init__.py +11 -0
- flashrt_structures/impls/dit_stack/fp4_chain.py +417 -0
- flashrt_structures/impls/dit_stack/region.py +86 -0
- flashrt_structures/impls/fixed_iter/__init__.py +29 -0
- flashrt_structures/impls/fixed_iter/openpi.py +264 -0
- flashrt_structures/impls/fixed_iter/protocol.py +94 -0
- flashrt_structures/impls/gated_delta_core/__init__.py +3 -0
- flashrt_structures/impls/gated_delta_core/fused_layer.py +545 -0
- flashrt_structures/impls/gated_delta_core/hub_v3.py +152 -0
- flashrt_structures/impls/graph_lowering/__init__.py +27 -0
- flashrt_structures/impls/graph_lowering/pi052_denoise.py +179 -0
- flashrt_structures/impls/graph_lowering/protocol.py +76 -0
- flashrt_structures/impls/graph_lowering/qwen3_vl.py +364 -0
- flashrt_structures/impls/linear_proj/__init__.py +0 -0
- flashrt_structures/impls/linear_proj/fp8_static.py +270 -0
- flashrt_structures/impls/linear_proj/nvfp4_balance.py +131 -0
- flashrt_structures/impls/linear_proj/nvfp4_dynamic.py +182 -0
- flashrt_structures/impls/linear_proj/w8a16_static.py +230 -0
- flashrt_structures/impls/modnorm_qkv_chain/__init__.py +0 -0
- flashrt_structures/impls/modnorm_qkv_chain/fp8_ptok_table.py +291 -0
- flashrt_structures/impls/moe_experts/__init__.py +9 -0
- flashrt_structures/impls/moe_experts/nvfp4_dynamic.py +208 -0
- flashrt_structures/impls/moe_experts/nvfp4_w4a16.py +129 -0
- flashrt_structures/impls/norm_fused/__init__.py +3 -0
- flashrt_structures/impls/norm_fused/bf16.py +94 -0
- flashrt_structures/impls/norm_fused/fp8_producer.py +84 -0
- flashrt_structures/impls/patch_projection/__init__.py +3 -0
- flashrt_structures/impls/patch_projection/bf16_flat.py +139 -0
- flashrt_structures/impls/prefill_tower/__init__.py +10 -0
- flashrt_structures/impls/prefill_tower/fp8_chain.py +955 -0
- flashrt_structures/impls/prefill_tower/region.py +99 -0
- flashrt_structures/impls/qk_norm_rope/__init__.py +12 -0
- flashrt_structures/impls/qk_norm_rope/per_head_gqa.py +199 -0
- flashrt_structures/impls/qk_norm_rope/projection_bf16.py +165 -0
- flashrt_structures/impls/qkv_pack/__init__.py +5 -0
- flashrt_structures/impls/qkv_pack/bf16.py +110 -0
- flashrt_structures/impls/qkv_pack/fp8_static.py +435 -0
- flashrt_structures/impls/qkv_pack/nvfp4_balance.py +218 -0
- flashrt_structures/impls/qkv_rope/__init__.py +3 -0
- flashrt_structures/impls/qkv_rope/packed_bias_bf16.py +143 -0
- flashrt_structures/impls/step_table.py +113 -0
- flashrt_structures/impls/vision_ffn/__init__.py +0 -0
- flashrt_structures/impls/vision_ffn/fp8_static.py +261 -0
- flashrt_structures/impls/vision_ffn/nvfp4_balance.py +211 -0
- flashrt_structures/impls/vision_tower/__init__.py +7 -0
- flashrt_structures/impls/vision_tower/fp8_chain.py +533 -0
- flashrt_structures/impls/vision_tower/region.py +91 -0
- flashrt_structures/matrix.py +126 -0
- flashrt_structures/points.py +368 -0
- flashrt_structures/prequantized.py +131 -0
- flashrt_structures/quantize_on_adopt.py +94 -0
- flashrt_structures/recipe.py +438 -0
- flashrt_structures/regions.py +208 -0
- flashrt_structures/schemes.py +490 -0
- flashrt_structures/stages.py +298 -0
- flashrt_structures/storage.py +255 -0
- flashrt_structures/swap.py +398 -0
- flashrt_structures/workspace.py +110 -0
- flashrt_structures-0.2.0.dist-info/METADATA +140 -0
- flashrt_structures-0.2.0.dist-info/RECORD +122 -0
- flashrt_structures-0.2.0.dist-info/WHEEL +5 -0
- flashrt_structures-0.2.0.dist-info/licenses/LICENSE +202 -0
- 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
|