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,226 @@
|
|
|
1
|
+
"""Attention adapter for Gemma-family denoise hosts (pi05 / pi_gemma).
|
|
2
|
+
|
|
3
|
+
Where the attention math runs is host-specific. In this family the
|
|
4
|
+
transformer's own forward calls ``modeling_gemma.eager_attention_forward``
|
|
5
|
+
directly, bypassing the config/interface dispatch entirely, so the seam
|
|
6
|
+
is that function, not a module. This adapter locates it by capturing one
|
|
7
|
+
denoise pass, binds an :mod:`..impls.attention_core` per layer from the
|
|
8
|
+
captured shapes and masks, and installs a function-level patch that
|
|
9
|
+
routes the fixed denoise shape to the packed-KV kernel while leaving
|
|
10
|
+
prefill and any other shape on the host path.
|
|
11
|
+
|
|
12
|
+
Registering this adapter lets ``autobuild`` pick up the attention_core
|
|
13
|
+
structure for this host family with no per-host scaffolding at the call
|
|
14
|
+
site — the host still just calls ``auto_swaps``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from ..impls.attention_core import bind_attention_core
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GemmaAttentionAdapter:
|
|
23
|
+
"""Recognise a Gemma-family denoise host and wire its fa2 seam."""
|
|
24
|
+
|
|
25
|
+
__name__ = "gemma_attention"
|
|
26
|
+
|
|
27
|
+
def __call__(self, model, forward, *, prefix_cadence: bool = False):
|
|
28
|
+
"""Wire the fa2 seam, or refuse when nobody will refresh its prefix.
|
|
29
|
+
|
|
30
|
+
This structure keeps the attention prefix — the vision and language
|
|
31
|
+
tokens — in a packed region and only rewrites the suffix per step.
|
|
32
|
+
That is correct within one observation, and it is what the bind-time
|
|
33
|
+
check proves: the prefix does not move across the denoise loop.
|
|
34
|
+
|
|
35
|
+
It is *not* correct across observations. A new image produces a new
|
|
36
|
+
prefix, and the packed region still holds the one calibration
|
|
37
|
+
captured, so the model attends to the wrong frame. Measured on
|
|
38
|
+
Pi0.5 over twelve unseen frames: output match 0.9957 with this seam
|
|
39
|
+
against 0.9997 without it, and max deviation 0.113 against 0.035.
|
|
40
|
+
|
|
41
|
+
The refresh exists (``bind_attention_core`` returns it) and the
|
|
42
|
+
tick pipeline drives it at the observation cadence. A caller that
|
|
43
|
+
cannot must not get this seam, so it is offered only when
|
|
44
|
+
``prefix_cadence`` says the refresh will be called.
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
import transformers.models.gemma.modeling_gemma as mg
|
|
48
|
+
except ImportError:
|
|
49
|
+
return None
|
|
50
|
+
orig = mg.eager_attention_forward
|
|
51
|
+
|
|
52
|
+
recs = {"q": None, "masks": [], "keys": [], "values": []}
|
|
53
|
+
|
|
54
|
+
def record(module, query, key, value, attention_mask, **kw):
|
|
55
|
+
if query.shape[2] < 128: # denoise (short) vs prefill
|
|
56
|
+
recs["q"] = query.detach()
|
|
57
|
+
recs["masks"].append(
|
|
58
|
+
attention_mask.detach()
|
|
59
|
+
if attention_mask is not None else None)
|
|
60
|
+
recs["keys"].append(key.detach().clone())
|
|
61
|
+
recs["values"].append(value.detach().clone())
|
|
62
|
+
return orig(module, query, key, value, attention_mask, **kw)
|
|
63
|
+
|
|
64
|
+
mg.eager_attention_forward = record
|
|
65
|
+
try:
|
|
66
|
+
with __import__("torch").no_grad():
|
|
67
|
+
forward()
|
|
68
|
+
finally:
|
|
69
|
+
mg.eager_attention_forward = orig
|
|
70
|
+
if recs["q"] is None:
|
|
71
|
+
return None # host never called this seam — not our family
|
|
72
|
+
if not prefix_cadence:
|
|
73
|
+
# after the family check, not before it: a refusal recorded
|
|
74
|
+
# against a host that never had this seam is misinformation
|
|
75
|
+
raise ValueError(
|
|
76
|
+
"attention_core: this seam holds the attention prefix "
|
|
77
|
+
"across calls and is only correct while someone refreshes "
|
|
78
|
+
"it when the observation changes. Pass prefix_cadence=True "
|
|
79
|
+
"and call plan.updates on every new observation, or leave "
|
|
80
|
+
"it unbound — unbound measured 0.9997 output match on "
|
|
81
|
+
"Pi0.5 unseen frames against 0.9957 bound-and-stale")
|
|
82
|
+
|
|
83
|
+
n_layers = _infer_layers(model)
|
|
84
|
+
if n_layers == 0 or len(recs["keys"]) % n_layers != 0:
|
|
85
|
+
return None
|
|
86
|
+
steps = len(recs["keys"]) // n_layers
|
|
87
|
+
captures = [{
|
|
88
|
+
"q": recs["q"],
|
|
89
|
+
"keys": [recs["keys"][i + s * n_layers] for s in range(steps)],
|
|
90
|
+
"values": [recs["values"][i + s * n_layers]
|
|
91
|
+
for s in range(steps)],
|
|
92
|
+
"mask": recs["masks"][i],
|
|
93
|
+
} for i in range(n_layers)]
|
|
94
|
+
|
|
95
|
+
bound = bind_attention_core(captures)
|
|
96
|
+
if bound is None:
|
|
97
|
+
return None # head_dim unsupported → host keeps its path
|
|
98
|
+
cores, prefix_update = bound
|
|
99
|
+
seq_q = recs["q"].shape[2]
|
|
100
|
+
expert, expert_path = _expert_layers_at(model)
|
|
101
|
+
for i, layer in enumerate(expert):
|
|
102
|
+
layer.self_attn._fa2_core = cores[i]
|
|
103
|
+
|
|
104
|
+
# no isolated speed bench here: benching this kernel against a
|
|
105
|
+
# standalone compiled attention says it loses, while the same
|
|
106
|
+
# swap measured inside the assembled graph wins by 0.76ms
|
|
107
|
+
# (10x the intra-process variance) and improves parity. An
|
|
108
|
+
# isolated probe cannot see what the seam actually replaces;
|
|
109
|
+
# the composed net-win gate is the one that can.
|
|
110
|
+
def fa2_fn(module, query, key, value, attention_mask, **kw):
|
|
111
|
+
# no Python-visible side effects in here: a counter or any
|
|
112
|
+
# host-side bookkeeping forces dynamo to break the graph at
|
|
113
|
+
# every attention call, which fragments the surrounding
|
|
114
|
+
# compiled region and pushes its CPU-side ops onto the
|
|
115
|
+
# capture stream
|
|
116
|
+
if query.shape[2] != seq_q or not hasattr(module, "_fa2_core"):
|
|
117
|
+
return orig(module, query, key, value, attention_mask, **kw)
|
|
118
|
+
return module._fa2_core(query, key, value,
|
|
119
|
+
scale=kw.get("scaling")), None
|
|
120
|
+
|
|
121
|
+
mg.eager_attention_forward = fa2_fn
|
|
122
|
+
self._seq_q = seq_q
|
|
123
|
+
|
|
124
|
+
def enable() -> None:
|
|
125
|
+
mg.eager_attention_forward = fa2_fn
|
|
126
|
+
|
|
127
|
+
def disable() -> None:
|
|
128
|
+
"""Route attention back to the host without unbinding.
|
|
129
|
+
|
|
130
|
+
The gate needs a baseline arm that is the host, and this seam
|
|
131
|
+
is the one that cannot be turned off by restoring a module:
|
|
132
|
+
it is a patched function, so it stays live through
|
|
133
|
+
``detach()`` of every swap around it. Without a toggle the
|
|
134
|
+
"off" arm would still be running this kernel and the net-win
|
|
135
|
+
measurement would be comparing the attachment against itself.
|
|
136
|
+
The bound cores stay where they are — the patch is what
|
|
137
|
+
routes to them, and rebuilding them per arm would recapture.
|
|
138
|
+
"""
|
|
139
|
+
if mg.eager_attention_forward is fa2_fn:
|
|
140
|
+
mg.eager_attention_forward = orig
|
|
141
|
+
|
|
142
|
+
def revert() -> None:
|
|
143
|
+
"""Undo everything this adapter did to the host and to
|
|
144
|
+
``transformers``.
|
|
145
|
+
|
|
146
|
+
The patch above is a module-level rebinding, so without this
|
|
147
|
+
it outlives the attachment: ``handle.detach()`` would restore
|
|
148
|
+
every swapped module and leave the attention seam patched, and
|
|
149
|
+
the promise that detaching gives back the original model would
|
|
150
|
+
be false for the one seam that is not a module. The core
|
|
151
|
+
attributes go too — a core still hanging off the host would
|
|
152
|
+
keep the routed path reachable and keep reporting itself as
|
|
153
|
+
live.
|
|
154
|
+
"""
|
|
155
|
+
if mg.eager_attention_forward is fa2_fn:
|
|
156
|
+
mg.eager_attention_forward = orig
|
|
157
|
+
for layer in expert or ():
|
|
158
|
+
if getattr(layer.self_attn, "_fa2_core", None) is not None:
|
|
159
|
+
del layer.self_attn._fa2_core
|
|
160
|
+
|
|
161
|
+
# the swap map is empty (the seam is a function, not a module);
|
|
162
|
+
# the patch and the per-layer core buffers are the swap. They are
|
|
163
|
+
# handed back as ``observed`` so the cores still appear in the
|
|
164
|
+
# attachment's ledger: a seam that cannot be swapped at a path can
|
|
165
|
+
# still be counted, and "the shape guard sent every call to the
|
|
166
|
+
# host" has to be visible somewhere. The shape check inside
|
|
167
|
+
# ``fa2_fn`` deliberately keeps no counter of its own (that is a
|
|
168
|
+
# graph break per attention call); it shows up instead as a core
|
|
169
|
+
# whose own call count stayed at zero.
|
|
170
|
+
# note: no extra host forward is run to self-verify — replaying
|
|
171
|
+
# the host mutates its state (cache growth, guard shapes) and
|
|
172
|
+
# that changes what the stage then captures. The recording pass
|
|
173
|
+
# above already proves the seam is live in this host.
|
|
174
|
+
observed = {f"{expert_path}.{i}.self_attn::fa2_core": core
|
|
175
|
+
for i, core in enumerate(cores)}
|
|
176
|
+
# the refresh goes back to the caller. Discarding it was the whole
|
|
177
|
+
# defect: the prefix then had no way to follow the observation.
|
|
178
|
+
return {}, prefix_update, {"revert": [revert], "observed": observed,
|
|
179
|
+
"toggle": (enable, disable)}
|
|
180
|
+
|
|
181
|
+
def sublayer(self, layer):
|
|
182
|
+
"""An attention sublayer for one host block, or ``None``.
|
|
183
|
+
|
|
184
|
+
Offered to the ``decoder_block`` structure, which owns the
|
|
185
|
+
boundary where the projections' layout meets the kernel's. This
|
|
186
|
+
family is half-split rotary, and the core bound above is what
|
|
187
|
+
the function patch would otherwise route to — so the sublayer
|
|
188
|
+
replaces a routed call, not a host path, and returning ``None``
|
|
189
|
+
simply leaves that routing in place.
|
|
190
|
+
"""
|
|
191
|
+
from ..impls.decoder_block import bind_attn_sublayer
|
|
192
|
+
|
|
193
|
+
attn = getattr(layer, "self_attn", None)
|
|
194
|
+
if attn is None:
|
|
195
|
+
return None
|
|
196
|
+
return bind_attn_sublayer(attn, getattr(attn, "_fa2_core", None))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _infer_layers(model) -> int:
|
|
200
|
+
layers = _expert_layers(model)
|
|
201
|
+
return len(layers) if layers is not None else 0
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _expert_layers(model):
|
|
205
|
+
"""Find the denoise decoder layers under either the model or a
|
|
206
|
+
policy wrapper — callers hand us whichever root they hold."""
|
|
207
|
+
return _expert_layers_at(model)[0]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _expert_layers_at(model) -> tuple[object, str]:
|
|
211
|
+
"""The denoise decoder layers and the dotted path they were found at.
|
|
212
|
+
|
|
213
|
+
The path matters to the receipt: this adapter's seam is a patched
|
|
214
|
+
function rather than a swapped module, so the only way it can be named
|
|
215
|
+
in a report is by the layers it attached its cores to.
|
|
216
|
+
"""
|
|
217
|
+
for path in ("paligemma_with_expert.gemma_expert.model.layers",
|
|
218
|
+
"model.paligemma_with_expert.gemma_expert.model.layers"):
|
|
219
|
+
node = model
|
|
220
|
+
for part in path.split("."):
|
|
221
|
+
node = getattr(node, part, None)
|
|
222
|
+
if node is None:
|
|
223
|
+
break
|
|
224
|
+
else:
|
|
225
|
+
return node, path
|
|
226
|
+
return None, ""
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Route packed-QKV vision attention through the generic qkv_rope seam."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import types
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
import torch.nn.functional as F
|
|
10
|
+
|
|
11
|
+
from ..impls.qkv_rope import bind_packed_bias_qkv_rope
|
|
12
|
+
from ..impls.attention_core.fa2_seqused import DenseAttention
|
|
13
|
+
from ..guard import GuardRefused
|
|
14
|
+
from ..discover import discover
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PackedQkvRopeAdapter:
|
|
18
|
+
"""Recognize packed biased QKV plus rotate-half RoPE by capability."""
|
|
19
|
+
|
|
20
|
+
__name__ = "packed_qkv_rope"
|
|
21
|
+
|
|
22
|
+
def __call__(self, model, plan, caps, *, compose_attention=None):
|
|
23
|
+
if compose_attention is None:
|
|
24
|
+
compose_attention = "attention_core" in getattr(
|
|
25
|
+
plan, "_requested_structures", ()
|
|
26
|
+
)
|
|
27
|
+
routes = []
|
|
28
|
+
observed = {}
|
|
29
|
+
refused = []
|
|
30
|
+
attention_scratch = {}
|
|
31
|
+
smoke_inputs = {}
|
|
32
|
+
|
|
33
|
+
capacities = {}
|
|
34
|
+
for seam in discover(model, ("vision_ffn",)):
|
|
35
|
+
block = seam.parent_path
|
|
36
|
+
rows = int(caps.get(seam.path, {}).get("rows", 0))
|
|
37
|
+
if rows > 0:
|
|
38
|
+
capacities[block] = max(capacities.get(block, 0), rows)
|
|
39
|
+
|
|
40
|
+
for path, module in model.named_modules():
|
|
41
|
+
qkv = getattr(module, "qkv", None)
|
|
42
|
+
proj = getattr(module, "proj", None)
|
|
43
|
+
if not (
|
|
44
|
+
isinstance(qkv, torch.nn.Linear)
|
|
45
|
+
and isinstance(proj, torch.nn.Linear)
|
|
46
|
+
):
|
|
47
|
+
continue
|
|
48
|
+
site = f"{path}::packed_qkv_rope"
|
|
49
|
+
|
|
50
|
+
def refuse(reason, where=site):
|
|
51
|
+
refused.append((where, f"qkv_rope refused: {reason}"))
|
|
52
|
+
|
|
53
|
+
required = (
|
|
54
|
+
"num_heads",
|
|
55
|
+
"head_dim",
|
|
56
|
+
"scaling",
|
|
57
|
+
"config",
|
|
58
|
+
"attention_dropout",
|
|
59
|
+
"is_causal",
|
|
60
|
+
)
|
|
61
|
+
if not all(hasattr(module, attr) for attr in required):
|
|
62
|
+
refuse("host lacks the packed-attention capability slots")
|
|
63
|
+
continue
|
|
64
|
+
if module.training:
|
|
65
|
+
refuse("training/dropout form is outside the inference seam")
|
|
66
|
+
continue
|
|
67
|
+
if bool(module.is_causal):
|
|
68
|
+
refuse("causal attention is outside the bidirectional vision seam")
|
|
69
|
+
continue
|
|
70
|
+
heads, head_dim = int(module.num_heads), int(module.head_dim)
|
|
71
|
+
dim = heads * head_dim
|
|
72
|
+
if (
|
|
73
|
+
qkv.in_features != dim
|
|
74
|
+
or qkv.out_features != 3 * dim
|
|
75
|
+
or proj.in_features != dim
|
|
76
|
+
or proj.out_features != dim
|
|
77
|
+
or qkv.bias is None
|
|
78
|
+
or qkv.weight.dtype is not torch.bfloat16
|
|
79
|
+
or qkv.bias.dtype is not torch.bfloat16
|
|
80
|
+
):
|
|
81
|
+
refuse("projections do not form BF16 packed equal-head QKV")
|
|
82
|
+
continue
|
|
83
|
+
block_path = path.rsplit(".", 1)[0] if "." in path else ""
|
|
84
|
+
row_capacity = capacities.get(block_path, 0)
|
|
85
|
+
if row_capacity <= 0:
|
|
86
|
+
refuse(
|
|
87
|
+
"no real vision-token capacity was observed for the "
|
|
88
|
+
"sibling block"
|
|
89
|
+
)
|
|
90
|
+
continue
|
|
91
|
+
try:
|
|
92
|
+
source = importlib.import_module(type(module).__module__)
|
|
93
|
+
eager_attention = getattr(source, "eager_attention_forward")
|
|
94
|
+
attention_functions = getattr(source, "ALL_ATTENTION_FUNCTIONS")
|
|
95
|
+
except (ImportError, AttributeError, ValueError) as exc:
|
|
96
|
+
refuse(f"cannot resolve the host attention dispatcher: {exc}")
|
|
97
|
+
continue
|
|
98
|
+
implementation = getattr(module.config, "_attn_implementation", None)
|
|
99
|
+
try:
|
|
100
|
+
attention = attention_functions.get_interface(
|
|
101
|
+
implementation, eager_attention
|
|
102
|
+
)
|
|
103
|
+
except (AttributeError, KeyError, TypeError) as exc:
|
|
104
|
+
refuse(
|
|
105
|
+
f"attention implementation {implementation!r} is "
|
|
106
|
+
f"unavailable: {exc}"
|
|
107
|
+
)
|
|
108
|
+
continue
|
|
109
|
+
try:
|
|
110
|
+
bound = bind_packed_bias_qkv_rope(
|
|
111
|
+
qkv.bias,
|
|
112
|
+
row_capacity=row_capacity,
|
|
113
|
+
q_heads=heads,
|
|
114
|
+
kv_heads=heads,
|
|
115
|
+
head_dim=head_dim,
|
|
116
|
+
)
|
|
117
|
+
except (ValueError, RuntimeError) as exc:
|
|
118
|
+
refuse(str(exc))
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
dense_attention = None
|
|
122
|
+
if compose_attention:
|
|
123
|
+
shape = (1, heads, row_capacity, head_dim)
|
|
124
|
+
scratch_key = (
|
|
125
|
+
shape, qkv.weight.dtype, qkv.weight.device,
|
|
126
|
+
)
|
|
127
|
+
try:
|
|
128
|
+
dense_attention = DenseAttention(
|
|
129
|
+
shape,
|
|
130
|
+
shape,
|
|
131
|
+
qkv.weight.dtype,
|
|
132
|
+
qkv.weight.device,
|
|
133
|
+
scratch=attention_scratch.get(scratch_key),
|
|
134
|
+
)
|
|
135
|
+
attention_scratch.setdefault(
|
|
136
|
+
scratch_key, dense_attention._scratch
|
|
137
|
+
)
|
|
138
|
+
samples = smoke_inputs.get(scratch_key)
|
|
139
|
+
if samples is None:
|
|
140
|
+
samples = tuple(
|
|
141
|
+
torch.empty(
|
|
142
|
+
shape,
|
|
143
|
+
device=qkv.weight.device,
|
|
144
|
+
dtype=qkv.weight.dtype,
|
|
145
|
+
)
|
|
146
|
+
for _ in range(3)
|
|
147
|
+
)
|
|
148
|
+
smoke_inputs[scratch_key] = samples
|
|
149
|
+
with torch.no_grad():
|
|
150
|
+
dense_attention(
|
|
151
|
+
*samples, scale=float(module.scaling)
|
|
152
|
+
)
|
|
153
|
+
if dense_attention._frt_guard is not None:
|
|
154
|
+
dense_attention._frt_guard.calls = 0
|
|
155
|
+
except (ValueError, RuntimeError) as exc:
|
|
156
|
+
refuse(f"single-segment attention unavailable: {exc}")
|
|
157
|
+
dense_attention = None
|
|
158
|
+
|
|
159
|
+
original = module.forward
|
|
160
|
+
had_instance_forward = "forward" in module.__dict__
|
|
161
|
+
|
|
162
|
+
def _routed_impl(
|
|
163
|
+
self,
|
|
164
|
+
hidden_states,
|
|
165
|
+
cu_seqlens,
|
|
166
|
+
position_embeddings=None,
|
|
167
|
+
*,
|
|
168
|
+
rope=bound,
|
|
169
|
+
qkv_proj=qkv,
|
|
170
|
+
output_proj=proj,
|
|
171
|
+
attention_fn=attention,
|
|
172
|
+
attention_scale=float(module.scaling),
|
|
173
|
+
implementation_name=implementation,
|
|
174
|
+
attention_core=dense_attention,
|
|
175
|
+
**kwargs,
|
|
176
|
+
):
|
|
177
|
+
tokens = hidden_states.shape[0]
|
|
178
|
+
if (
|
|
179
|
+
hidden_states.dim() != 2
|
|
180
|
+
or hidden_states.shape[1] != qkv_proj.in_features
|
|
181
|
+
or hidden_states.dtype is not torch.bfloat16
|
|
182
|
+
or hidden_states.device != qkv_proj.weight.device
|
|
183
|
+
):
|
|
184
|
+
raise GuardRefused(
|
|
185
|
+
"qkv_rope: hidden state is outside the bound "
|
|
186
|
+
"packed-attention form"
|
|
187
|
+
)
|
|
188
|
+
if not (
|
|
189
|
+
isinstance(position_embeddings, tuple)
|
|
190
|
+
and len(position_embeddings) == 2
|
|
191
|
+
):
|
|
192
|
+
raise GuardRefused(
|
|
193
|
+
"qkv_rope: host did not provide a (cos, sin) table"
|
|
194
|
+
)
|
|
195
|
+
cos, sin = position_embeddings
|
|
196
|
+
if (
|
|
197
|
+
cos.dtype is not torch.float32
|
|
198
|
+
or sin.dtype is not torch.float32
|
|
199
|
+
or cos.device != hidden_states.device
|
|
200
|
+
or sin.device != hidden_states.device
|
|
201
|
+
or not cos.is_contiguous()
|
|
202
|
+
or not sin.is_contiguous()
|
|
203
|
+
):
|
|
204
|
+
# checked before any work: a refused call must not
|
|
205
|
+
# leave a wasted packed projection behind — under
|
|
206
|
+
# CUDA graph capture that dead GEMM would replay
|
|
207
|
+
# forever
|
|
208
|
+
raise GuardRefused(
|
|
209
|
+
"qkv_rope: cos/sin must be contiguous CUDA FP32 "
|
|
210
|
+
"(a host loaded with a blanket .to(dtype) casts "
|
|
211
|
+
"its rotary buffers and can never satisfy this)"
|
|
212
|
+
)
|
|
213
|
+
packed = F.linear(
|
|
214
|
+
hidden_states, qkv_proj.weight, None
|
|
215
|
+
).view(1, tokens, -1)
|
|
216
|
+
query, key, value = rope(
|
|
217
|
+
packed, cos.view(1, tokens, -1), sin.view(1, tokens, -1)
|
|
218
|
+
)
|
|
219
|
+
query = query.transpose(1, 2)
|
|
220
|
+
key = key.transpose(1, 2)
|
|
221
|
+
value = value.transpose(1, 2)
|
|
222
|
+
|
|
223
|
+
if attention_core is not None and cu_seqlens.numel() == 2:
|
|
224
|
+
output = attention_core(
|
|
225
|
+
query,
|
|
226
|
+
key,
|
|
227
|
+
value,
|
|
228
|
+
scale=attention_scale,
|
|
229
|
+
).transpose(1, 2)
|
|
230
|
+
elif implementation_name == "flash_attention_2":
|
|
231
|
+
max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max()
|
|
232
|
+
output, _ = attention_fn(
|
|
233
|
+
self,
|
|
234
|
+
query,
|
|
235
|
+
key,
|
|
236
|
+
value,
|
|
237
|
+
attention_mask=None,
|
|
238
|
+
scaling=attention_scale,
|
|
239
|
+
dropout=0.0,
|
|
240
|
+
cu_seq_lens_q=cu_seqlens,
|
|
241
|
+
cu_seq_lens_k=cu_seqlens,
|
|
242
|
+
max_length_q=max_seqlen,
|
|
243
|
+
max_length_k=max_seqlen,
|
|
244
|
+
is_causal=False,
|
|
245
|
+
**kwargs,
|
|
246
|
+
)
|
|
247
|
+
else:
|
|
248
|
+
lengths = cu_seqlens[1:] - cu_seqlens[:-1]
|
|
249
|
+
splits = [
|
|
250
|
+
torch.split(tensor, lengths.tolist(), dim=2)
|
|
251
|
+
for tensor in (query, key, value)
|
|
252
|
+
]
|
|
253
|
+
outputs = [
|
|
254
|
+
attention_fn(
|
|
255
|
+
self,
|
|
256
|
+
q,
|
|
257
|
+
k,
|
|
258
|
+
v,
|
|
259
|
+
attention_mask=None,
|
|
260
|
+
scaling=attention_scale,
|
|
261
|
+
dropout=0.0,
|
|
262
|
+
is_causal=False,
|
|
263
|
+
**kwargs,
|
|
264
|
+
)[0]
|
|
265
|
+
for q, k, v in zip(*splits)
|
|
266
|
+
]
|
|
267
|
+
output = torch.cat(outputs, dim=1)
|
|
268
|
+
output = output.reshape(tokens, -1).contiguous()
|
|
269
|
+
return output_proj(output)
|
|
270
|
+
|
|
271
|
+
def routed(self, hidden_states, cu_seqlens,
|
|
272
|
+
position_embeddings=None, *, rope=bound,
|
|
273
|
+
host_forward=original, **kwargs):
|
|
274
|
+
# A contract check tripping inside the routed body is a
|
|
275
|
+
# refusal like any other: strict mode raises, production
|
|
276
|
+
# mode counts it and runs the call on the host module
|
|
277
|
+
# this seam replaced. Before this net existed, one
|
|
278
|
+
# drifted cos/sin table aborted the whole forward even
|
|
279
|
+
# in fallback mode — the exact two-fates defect the
|
|
280
|
+
# unified refusal type was introduced to remove.
|
|
281
|
+
try:
|
|
282
|
+
return _routed_impl(self, hidden_states, cu_seqlens,
|
|
283
|
+
position_embeddings, **kwargs)
|
|
284
|
+
except GuardRefused as refusal:
|
|
285
|
+
guard = getattr(rope, "_frt_guard", None)
|
|
286
|
+
if guard is None or guard.mode == "raise":
|
|
287
|
+
raise
|
|
288
|
+
guard.refuse(str(refusal))
|
|
289
|
+
if getattr(guard, "detached", False):
|
|
290
|
+
# the guard has given up on this seam; honor it
|
|
291
|
+
# here too — the host forward returns without
|
|
292
|
+
# the routed shim in front of it
|
|
293
|
+
self.forward = host_forward
|
|
294
|
+
# keyword, not positional: host signatures place
|
|
295
|
+
# extra parameters (rotary_pos_emb) between the
|
|
296
|
+
# required pair and the embeddings
|
|
297
|
+
return host_forward(
|
|
298
|
+
hidden_states, cu_seqlens,
|
|
299
|
+
position_embeddings=position_embeddings, **kwargs)
|
|
300
|
+
|
|
301
|
+
routed_method = types.MethodType(routed, module)
|
|
302
|
+
routes.append(
|
|
303
|
+
(module, routed_method, original, had_instance_forward)
|
|
304
|
+
)
|
|
305
|
+
observed[site] = bound
|
|
306
|
+
if dense_attention is not None:
|
|
307
|
+
observed[f"{path}::attention_core"] = dense_attention
|
|
308
|
+
|
|
309
|
+
if not routes:
|
|
310
|
+
return {"refused": refused} if refused else None
|
|
311
|
+
|
|
312
|
+
def enable() -> None:
|
|
313
|
+
for module, routed, _, _ in routes:
|
|
314
|
+
module.forward = routed
|
|
315
|
+
|
|
316
|
+
def disable() -> None:
|
|
317
|
+
for module, _, original, _ in routes:
|
|
318
|
+
module.forward = original
|
|
319
|
+
|
|
320
|
+
def revert() -> None:
|
|
321
|
+
for module, _, original, had_instance_forward in routes:
|
|
322
|
+
if had_instance_forward:
|
|
323
|
+
module.forward = original
|
|
324
|
+
elif "forward" in module.__dict__:
|
|
325
|
+
del module.forward
|
|
326
|
+
|
|
327
|
+
enable()
|
|
328
|
+
if compose_attention and any(
|
|
329
|
+
name.endswith("::attention_core") for name in observed
|
|
330
|
+
):
|
|
331
|
+
plan.notes["attention_adapter"] = (
|
|
332
|
+
"PackedQkvRopeAdapter.single_segment_dense"
|
|
333
|
+
)
|
|
334
|
+
plan.notes.setdefault("composed_structures", []).append(
|
|
335
|
+
"qkv_rope->attention_core"
|
|
336
|
+
)
|
|
337
|
+
return {
|
|
338
|
+
"observed": observed,
|
|
339
|
+
"revert": [revert],
|
|
340
|
+
"toggle": (enable, disable),
|
|
341
|
+
"refused": refused,
|
|
342
|
+
}
|