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,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
+ }