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,417 @@
1
+ """The fused NVFP4 launch chain over an alternating DiT block stack.
2
+
3
+ Eight kernels per layer, no elementwise traffic between them: the
4
+ AdaLN and the pre-FFN norm emit FP4 directly, the attention output
5
+ projection and the FFN down-projection carry the residual add in
6
+ their epilogues, and the FFN up-projection emits bias+GELU straight
7
+ back to FP4 for the down GEMM. The per-layer AdaLN modulators come
8
+ from one stacked projection per distinct timestep, resolved at run
9
+ time by nearest-neighbour match — the step-table form, computed at
10
+ bind. Cross-attention keys and values go through the block's own
11
+ ``to_k``/``to_v`` **resolved at call time**, so whatever seat holds
12
+ that path when the call happens (a cadence bank, a quantized linear,
13
+ the host) serves it — the chain never captures a module reference at
14
+ bind (the o_proj dead-seat lesson).
15
+
16
+ This is a region candidate, not a specialist: it binds through the
17
+ adapter contract (observed guard, enable/disable toggle, bit-exact
18
+ revert), its activation is decided by :mod:`..regions` receipts, and
19
+ every out-of-contract call falls back to the retained host forward
20
+ with the reason on the ledger. Contract checks run eager-only and
21
+ step aside during CUDA graph capture — the captured window is
22
+ certified by its own gate.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import types
28
+ from typing import Any, Callable
29
+
30
+ import torch
31
+
32
+ from .. import KernelUnavailable, hub_kernel
33
+ from ...guard import GuardedSeam
34
+
35
+ GEMM_PACKAGE = "flashrt/fp4-gemm"
36
+ NORM_PACKAGE = "flashrt/adaptive-layernorm-producers"
37
+ GEMM_SYMBOLS = ("nvfp4_gemm_bias_bf16", "nvfp4_gemm_bias_residual_bf16",
38
+ "nvfp4_gemm_bias_gelu_nvfp4", "quantize_fp4_sfa_bf16")
39
+ NORM_SYMBOLS = ("ada_layer_norm_quant_nvfp4_swizzled_bf16",
40
+ "layer_norm_no_affine_quant_nvfp4_swizzled_bf16")
41
+
42
+ #: bind-time smoke over the whole routed stack, on every probe step.
43
+ #: The late-binding lesson calibrates this: a per-layer error class of
44
+ #: ~2e-3 compounds across ~32 layers, so the stack floor sits below
45
+ #: the per-seam 0.995 line on purpose; the arm's end-to-end parity
46
+ #: gate (0.99 against the host's own eager run) stays the judge.
47
+ SMOKE_FLOOR = 0.985
48
+
49
+
50
+ def missing_symbols() -> list[str]:
51
+ """The factual prerequisites this box does not meet (may be empty)."""
52
+ gaps: list[str] = []
53
+ for repo, symbols in ((GEMM_PACKAGE, GEMM_SYMBOLS),
54
+ (NORM_PACKAGE, NORM_SYMBOLS)):
55
+ try:
56
+ kern = hub_kernel(repo, ">=1")
57
+ except KernelUnavailable:
58
+ gaps.append(repo)
59
+ continue
60
+ gaps.extend(f"{repo}:{s}" for s in symbols
61
+ if not hasattr(kern, s))
62
+ return gaps
63
+
64
+
65
+ class BoundDitFp4Chain(GuardedSeam, torch.nn.Module):
66
+ """The chain's bind-time state: quantized weights and step tables.
67
+
68
+ Weight tables and step tables are plain tensor attributes, not
69
+ buffers — this module is a ledger citizen (``plan.observed``), not
70
+ a state_dict citizen; the truth of every weight stays with the
71
+ host modules it absorbs, which is what makes revert and fallback
72
+ bit-exact for free.
73
+ """
74
+
75
+ _frt_can_fallback = False # fallback is the routed closure's job
76
+
77
+ def __init__(self) -> None:
78
+ super().__init__()
79
+ self.table: list[dict] = []
80
+ self.blocks: list = [] # plain list: no child registration
81
+ self.dims: dict = {}
82
+ self.t_keys = None
83
+ self.mods_table = None
84
+ self.tails_table = None
85
+ self.text_idx = None
86
+ self.image_idx = None
87
+ self.mask_shape = None
88
+ self.kernels: dict = {}
89
+
90
+
91
+ def _stack_parts(dit) -> tuple[list, int, int, int, int]:
92
+ blocks = list(dit.transformer_blocks)
93
+ head = blocks[0]
94
+ return (blocks, head.num_attention_heads, head.attention_head_dim,
95
+ head.dim, len(blocks))
96
+
97
+
98
+ @torch.no_grad()
99
+ def _quantize(bound: BoundDitFp4Chain, dit, kg) -> None:
100
+ """NVFP4-pack every stack GEMM weight from the pristine host."""
101
+ blocks, _nh, _hd, _dim, _n = _stack_parts(dit)
102
+
103
+ def quant(w: torch.Tensor):
104
+ return kg.quantize_fp4_sfa_bf16(
105
+ w.detach().to("cuda", torch.bfloat16).contiguous(),
106
+ is_sfb=True)
107
+
108
+ def bias(module: torch.nn.Module):
109
+ return module.bias.detach().to(
110
+ "cuda", torch.bfloat16).contiguous()
111
+
112
+ for block in blocks:
113
+ attn = block.attn1
114
+ is_self = attn.to_k.in_features == attn.to_q.in_features
115
+ entry: dict[str, Any] = {"is_self": is_self}
116
+ if is_self:
117
+ w = torch.cat([attn.to_q.weight, attn.to_k.weight,
118
+ attn.to_v.weight], dim=0)
119
+ entry["qkv"] = quant(w)
120
+ entry["qkv_b"] = torch.cat(
121
+ [attn.to_q.bias, attn.to_k.bias, attn.to_v.bias]
122
+ ).detach().to("cuda", torch.bfloat16).contiguous()
123
+ else:
124
+ entry["q"] = quant(attn.to_q.weight)
125
+ entry["q_b"] = bias(attn.to_q)
126
+ entry["o"] = quant(attn.to_out[0].weight)
127
+ entry["o_b"] = bias(attn.to_out[0])
128
+ entry["up"] = quant(block.ff.net[0].proj.weight)
129
+ entry["up_b"] = bias(block.ff.net[0].proj)
130
+ entry["down"] = quant(block.ff.net[2].weight)
131
+ entry["down_b"] = bias(block.ff.net[2])
132
+ bound.table.append(entry)
133
+
134
+
135
+ @torch.no_grad()
136
+ def _step_tables(bound: BoundDitFp4Chain, dit, seen, masks) -> None:
137
+ """Per-step modulators from one stacked projection per timestep.
138
+
139
+ Computing them in-graph reads the stacked modulator weights once
140
+ per step — pure bandwidth, and the profile named that one skinny
141
+ GEMM as a whole regression. Here they are computed once per
142
+ distinct timestep the probe saw; the run resolves the step by
143
+ nearest-neighbour match, pure tensor ops, capture-safe.
144
+ """
145
+ blocks, _nh, _hd, dim, n_layers = _stack_parts(dit)
146
+ ada_w = torch.cat([b.norm1.linear.weight for b in blocks], dim=0)
147
+ ada_b = torch.cat([b.norm1.linear.bias for b in blocks], dim=0)
148
+ silu = torch.nn.functional.silu
149
+ keys, mods, tails = [], [], []
150
+ for t, temb in seen:
151
+ keys.append(t)
152
+ mods.append(torch.nn.functional.linear(silu(temb), ada_w, ada_b)
153
+ .view(n_layers, 2, dim).to(torch.bfloat16))
154
+ shift, scale = dit.proj_out_1(silu(temb)).chunk(2, dim=1)
155
+ tails.append(torch.stack(
156
+ [shift.reshape(dim), scale.reshape(dim)]).to(torch.bfloat16))
157
+ image_rows = (masks["image"] & masks["backbone"]).reshape(-1)
158
+ text_rows = (~masks["image"] & masks["backbone"]).reshape(-1)
159
+ bound.t_keys = torch.cat(keys).contiguous()
160
+ bound.mods_table = torch.stack(mods).contiguous()
161
+ bound.tails_table = torch.stack(tails).contiguous()
162
+ bound.text_idx = torch.where(text_rows)[0].contiguous()
163
+ bound.image_idx = torch.where(image_rows)[0].contiguous()
164
+ bound.mask_shape = tuple(masks["image"].shape)
165
+
166
+
167
+ def _make_run(bound: BoundDitFp4Chain, dit) -> Callable:
168
+ """The chain body: eight launches per layer over the stack."""
169
+ kg, kq = bound.kernels["kg"], bound.kernels["kq"]
170
+ ada_fp4 = kq.ada_layer_norm_quant_nvfp4_swizzled_bf16
171
+ ln_fp4 = kq.layer_norm_no_affine_quant_nvfp4_swizzled_bf16
172
+ gemm_bias = kg.nvfp4_gemm_bias_bf16
173
+ gemm_bias_res = kg.nvfp4_gemm_bias_residual_bf16
174
+ gemm_gelu_fp4 = kg.nvfp4_gemm_bias_gelu_nvfp4
175
+ quant_act = kg.quantize_fp4_sfa_bf16
176
+ sdpa = torch.nn.functional.scaled_dot_product_attention
177
+ nh, hd = bound.dims["nh"], bound.dims["hd"]
178
+ dim, n_layers = bound.dims["dim"], bound.dims["n_layers"]
179
+ every_n = bound.dims["every_n"]
180
+ table = bound.table
181
+ blocks = bound.blocks
182
+
183
+ def layer(li, h, sa, scale, shift, enc, rows):
184
+ entry = table[li]
185
+ xp, xs = ada_fp4(h, scale, shift)
186
+ if entry["is_self"]:
187
+ qkv = gemm_bias(xp, entry["qkv"][0], xs, entry["qkv"][1],
188
+ entry["qkv_b"])
189
+ packs = qkv.view(sa, 3, nh, hd).permute(1, 2, 0, 3)
190
+ o = sdpa(packs[0].unsqueeze(0), packs[1].unsqueeze(0),
191
+ packs[2].unsqueeze(0))
192
+ else:
193
+ q = gemm_bias(xp, entry["q"][0], xs, entry["q"][1],
194
+ entry["q_b"])
195
+ # call-time resolution: whoever seats to_k/to_v now (a
196
+ # cadence bank, a quantized linear, the host) serves this
197
+ attn = blocks[li].attn1
198
+ kb = attn.to_k(enc).index_select(-2, rows)
199
+ vb = attn.to_v(enc).index_select(-2, rows)
200
+ skv = rows.shape[0]
201
+ o = sdpa(q.view(1, sa, nh, hd).transpose(1, 2),
202
+ kb.reshape(1, skv, nh, hd).transpose(1, 2),
203
+ vb.reshape(1, skv, nh, hd).transpose(1, 2))
204
+ o = o.transpose(1, 2).reshape(sa, dim).contiguous()
205
+ op, osf = quant_act(o)
206
+ h = gemm_bias_res(op, entry["o"][0], osf, entry["o"][1],
207
+ entry["o_b"], h)
208
+ np_, ns = ln_fp4(h)
209
+ hp, hs = gemm_gelu_fp4(np_, entry["up"][0], ns, entry["up"][1],
210
+ entry["up_b"])
211
+ return gemm_bias_res(hp, entry["down"][0], hs, entry["down"][1],
212
+ entry["down_b"], h)
213
+
214
+ def run(hidden_states, encoder_hidden_states, timestep,
215
+ return_all_hidden_states):
216
+ t = timestep.reshape(-1)[:1].float()
217
+ idx = (bound.t_keys - t).abs().argmin().reshape(1)
218
+ mods = bound.mods_table.index_select(0, idx)[0]
219
+ tail = bound.tails_table.index_select(0, idx)[0]
220
+
221
+ b, sa, d = hidden_states.shape
222
+ h = hidden_states.reshape(sa, d).to(torch.bfloat16).contiguous()
223
+ all_h = [hidden_states] if return_all_hidden_states else None
224
+ for li in range(n_layers):
225
+ if li % 2 == 1:
226
+ h = layer(li, h, sa, mods[li, 0], mods[li, 1], None, None)
227
+ else:
228
+ rows = (bound.text_idx if li % (2 * every_n) == 0
229
+ else bound.image_idx)
230
+ h = layer(li, h, sa, mods[li, 0], mods[li, 1],
231
+ encoder_hidden_states, rows)
232
+ if all_h is not None:
233
+ all_h.append(h.reshape(b, sa, d))
234
+
235
+ h = h.reshape(b, sa, d).type_as(hidden_states)
236
+ h = (dit.norm_out(h) * (1 + tail[1].reshape(1, 1, d))
237
+ + tail[0].reshape(1, 1, d))
238
+ out = dit.proj_out_2(h)
239
+ if return_all_hidden_states:
240
+ return out, all_h
241
+ return out
242
+
243
+ return run
244
+
245
+
246
+ def bind_dit_fp4_chain(model, root: str,
247
+ probe: Callable[[], Any]) -> dict:
248
+ """Bind the chain onto the stack at ``root``; adapter contract out.
249
+
250
+ One probe run does all the observation: the distinct timesteps
251
+ (step tables), the attention masks (compact-row banks), and every
252
+ stack call's arguments and output (the smoke reference). The
253
+ routed form must track the host on **every** probe step above
254
+ ``SMOKE_FLOOR`` or the whole bind refuses — no partial routing of
255
+ a stack. Returns ``{"refused": reason}`` on any refusal, with the
256
+ host untouched.
257
+ """
258
+ try:
259
+ kg = hub_kernel(GEMM_PACKAGE, ">=1")
260
+ kq = hub_kernel(NORM_PACKAGE, ">=1")
261
+ except KernelUnavailable as exc:
262
+ return {"refused": f"dit_fp4_chain: {exc}"}
263
+ gaps = missing_symbols()
264
+ if gaps:
265
+ return {"refused": f"dit_fp4_chain missing: {', '.join(gaps)}"}
266
+
267
+ dit = model.get_submodule(root) if root else model
268
+ blocks, nh, hd, dim, n_layers = _stack_parts(dit)
269
+
270
+ bound = BoundDitFp4Chain()
271
+ bound.kernels = {"kg": kg, "kq": kq}
272
+ bound.blocks = blocks
273
+ bound.dims = {"nh": nh, "hd": hd, "dim": dim, "n_layers": n_layers,
274
+ "every_n": getattr(dit, "attend_text_every_n_blocks", 2)}
275
+ _quantize(bound, dit, kg)
276
+
277
+ # ---- one probe: timesteps, masks, and the smoke reference ----
278
+ seen: list[tuple[torch.Tensor, torch.Tensor]] = []
279
+ masks: dict = {}
280
+ calls: list[tuple[tuple, dict, torch.Tensor]] = []
281
+
282
+ def note(_module, args, output):
283
+ t = args[0].reshape(-1)[:1].float()
284
+ if not any(torch.allclose(t, prev) for prev, _ in seen):
285
+ seen.append((t.detach().clone(), output.detach().clone()))
286
+
287
+ def grab(_module, args, kwargs, output):
288
+ img = kwargs.get("image_mask")
289
+ bb = kwargs.get("backbone_attention_mask")
290
+ if img is not None and bb is not None and not masks:
291
+ masks["image"] = img.detach().clone()
292
+ masks["backbone"] = bb.detach().clone()
293
+ out = output[0] if isinstance(output, tuple) else output
294
+ calls.append((args, dict(kwargs), out.detach().clone()))
295
+
296
+ hooks = [dit.timestep_encoder.register_forward_hook(note),
297
+ dit.register_forward_hook(grab, with_kwargs=True)]
298
+ try:
299
+ with torch.inference_mode():
300
+ probe()
301
+ finally:
302
+ for hook in hooks:
303
+ hook.remove()
304
+ if not seen:
305
+ return {"refused": "dit_fp4_chain: probe saw no timesteps"}
306
+ if not masks:
307
+ return {"refused": "dit_fp4_chain: probe saw no attention masks"}
308
+ if not calls:
309
+ return {"refused": "dit_fp4_chain: probe never called the stack"}
310
+
311
+ _step_tables(bound, dit, seen, masks)
312
+ run = _make_run(bound, dit)
313
+ guard = bound._frt_arm(dtypes=(torch.bfloat16,),
314
+ device=bound.t_keys.device)
315
+ guard.notes["n_layers"] = n_layers
316
+ guard.notes["steps"] = len(seen)
317
+
318
+ # ---- smoke: the routed stack against every captured host call ----
319
+ worst = None
320
+ with torch.inference_mode():
321
+ for args, kwargs, ref in calls:
322
+ parsed = _parse_call(args, kwargs)
323
+ if parsed is None:
324
+ return {"refused": "dit_fp4_chain: probe call shape "
325
+ "outside the routed contract"}
326
+ got = run(*parsed)
327
+ got = got[0] if isinstance(got, tuple) else got
328
+ cos = torch.nn.functional.cosine_similarity(
329
+ got.float().flatten(), ref.float().flatten(), dim=0)
330
+ worst = float(cos) if worst is None else min(worst,
331
+ float(cos))
332
+ if worst is None or worst < SMOKE_FLOOR:
333
+ return {"refused": f"dit_fp4_chain smoke cos {worst} < "
334
+ f"{SMOKE_FLOOR} across {len(calls)} probe "
335
+ "step(s)"}
336
+ guard.notes["smoke_cos"] = round(worst, 6)
337
+
338
+ # ---- route ----
339
+ saved = dit.__dict__.get("forward")
340
+ host_forward = dit.forward
341
+
342
+ def routed(_dit_self, *args, **kwargs):
343
+ # the ledger is eager-only, like every guard: a compiler
344
+ # tracing this sees constant-False branches and no side
345
+ # effects (no graph breaks in the hot loop), and a capturing
346
+ # stream skips the Python state the replay would never run
347
+ compiling = torch.compiler.is_compiling()
348
+ capturing = (False if compiling
349
+ else torch.cuda.is_current_stream_capturing())
350
+ eager = not compiling and not capturing
351
+ if eager:
352
+ guard.calls += 1
353
+ parsed = _parse_call(args, kwargs)
354
+ if parsed is None:
355
+ if not eager:
356
+ raise RuntimeError(
357
+ "dit_fp4_chain: out-of-contract call during "
358
+ "capture/compile — fix the eager path first")
359
+ guard.fallbacks += 1
360
+ guard.last_reason = "call outside the routed contract"
361
+ return host_forward(*args, **kwargs)
362
+ if eager:
363
+ img = kwargs.get("image_mask")
364
+ if (img is not None
365
+ and tuple(img.shape) != bound.mask_shape):
366
+ guard.fallbacks += 1
367
+ guard.last_reason = "attention mask shape changed"
368
+ return host_forward(*args, **kwargs)
369
+ return run(*parsed)
370
+
371
+ def enable() -> None:
372
+ dit.forward = types.MethodType(routed, dit)
373
+
374
+ def disable() -> None:
375
+ if saved is not None:
376
+ dit.forward = saved
377
+ elif "forward" in dit.__dict__:
378
+ del dit.forward
379
+
380
+ def revert() -> None:
381
+ disable()
382
+ bound.table.clear()
383
+ bound.blocks = []
384
+
385
+ enable()
386
+ return {
387
+ "observed": {f"{root}::dit_fp4_chain": bound},
388
+ "revert": [revert],
389
+ "toggle": (enable, disable),
390
+ "smoke_cos": worst,
391
+ }
392
+
393
+
394
+ def _parse_call(args: tuple, kwargs: dict):
395
+ """The routed contract: the native stack signature, batch of one.
396
+
397
+ Anything else — an unexpected keyword, a missing timestep, a
398
+ batched call — is the host's, not the chain's.
399
+ """
400
+ known = ("hidden_states", "encoder_hidden_states", "timestep",
401
+ "encoder_attention_mask", "return_all_hidden_states",
402
+ "image_mask", "backbone_attention_mask")
403
+ if any(k not in known for k in kwargs):
404
+ return None
405
+ merged = dict(zip(known, args))
406
+ if set(merged).intersection(kwargs):
407
+ return None
408
+ merged.update(kwargs)
409
+ hidden = merged.get("hidden_states")
410
+ enc = merged.get("encoder_hidden_states")
411
+ t = merged.get("timestep")
412
+ if hidden is None or enc is None or t is None:
413
+ return None
414
+ if hidden.dim() != 3 or hidden.shape[0] != 1:
415
+ return None
416
+ return (hidden, enc, t,
417
+ bool(merged.get("return_all_hidden_states", False)))
@@ -0,0 +1,86 @@
1
+ """The dit_block region family: structural identification, candidates.
2
+
3
+ The identifier matches shape, never names: a module carrying a
4
+ ``transformer_blocks`` list whose blocks each hold one attention group
5
+ (``to_q``/``to_k``/``to_v``/``to_out``), a two-linear gated FFN, and a
6
+ per-block AdaLN projection — with at least one cross block (key width
7
+ differs from query width) and the stack-level tail (timestep encoder,
8
+ final norm, the two output projections). That is the span the fused
9
+ chain candidate knows how to absorb; a host that merely resembles it
10
+ is exactly what the bind-time smoke gate exists to refuse.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import torch
16
+
17
+ from . import fp4_chain
18
+ from ... import regions
19
+
20
+
21
+ def _linear(mod) -> bool:
22
+ return isinstance(mod, torch.nn.Linear) and mod.bias is not None
23
+
24
+
25
+ def _block_ok(block) -> bool:
26
+ attn = getattr(block, "attn1", None)
27
+ ff = getattr(block, "ff", None)
28
+ norm1 = getattr(block, "norm1", None)
29
+ if attn is None or ff is None or norm1 is None:
30
+ return False
31
+ if not all(_linear(getattr(attn, a, None))
32
+ for a in ("to_q", "to_k", "to_v")):
33
+ return False
34
+ out = getattr(attn, "to_out", None)
35
+ if out is None or len(out) < 1 or not _linear(out[0]):
36
+ return False
37
+ net = getattr(ff, "net", None)
38
+ if (net is None or len(net) < 3
39
+ or not _linear(getattr(net[0], "proj", None))
40
+ or not _linear(net[2])):
41
+ return False
42
+ if not _linear(getattr(norm1, "linear", None)):
43
+ return False
44
+ return isinstance(getattr(block, "dim", None), int)
45
+
46
+
47
+ def identify(model) -> list[str]:
48
+ roots = []
49
+ for path, mod in model.named_modules():
50
+ blocks = getattr(mod, "transformer_blocks", None)
51
+ if not isinstance(blocks, torch.nn.ModuleList) or len(blocks) < 2:
52
+ continue
53
+ if not all(_block_ok(b) for b in blocks):
54
+ continue
55
+ if not all(hasattr(mod, a) for a in
56
+ ("timestep_encoder", "norm_out",
57
+ "proj_out_1", "proj_out_2")):
58
+ continue
59
+ if not any(b.attn1.to_k.in_features != b.attn1.to_q.in_features
60
+ for b in blocks):
61
+ continue
62
+ roots.append(path)
63
+ return roots
64
+
65
+
66
+ def _bind(model, root, probe):
67
+ return fp4_chain.bind_dit_fp4_chain(model, root, probe)
68
+
69
+
70
+ FAMILY = regions.RegionFamily(
71
+ family="dit_block",
72
+ identify=identify,
73
+ candidates=[regions.RegionCandidate(
74
+ name="fp4_chain",
75
+ missing=fp4_chain.missing_symbols,
76
+ bind=_bind,
77
+ )],
78
+ )
79
+
80
+
81
+ def register() -> None:
82
+ """(Re-)register the family — idempotent, import calls it once."""
83
+ regions.register_region_family(FAMILY)
84
+
85
+
86
+ register()
@@ -0,0 +1,29 @@
1
+ """Executable fixed-iteration schedule normalization.
2
+
3
+ The stage catalog describes iterative pipelines semantically. This package
4
+ is the executable bridge for hosts whose Python spelling is not graph-safe:
5
+ host-family adapters expose the same ``init -> K * step -> readout`` schedule
6
+ as a fixed callable that :func:`flashrt_structures.capture` can compile and
7
+ capture without changing the host repository.
8
+ """
9
+
10
+ from .protocol import (
11
+ FixedIterationLowering,
12
+ FixedIterationRefused,
13
+ normalize_fixed_iteration,
14
+ register_fixed_iteration_adapter,
15
+ )
16
+
17
+ # Built-ins register at import time. They use semantic capabilities and
18
+ # signatures, never model IDs.
19
+ from .openpi import OpenPIFixedIterationAdapter
20
+
21
+ register_fixed_iteration_adapter(OpenPIFixedIterationAdapter())
22
+
23
+ __all__ = [
24
+ "FixedIterationLowering",
25
+ "FixedIterationRefused",
26
+ "OpenPIFixedIterationAdapter",
27
+ "normalize_fixed_iteration",
28
+ "register_fixed_iteration_adapter",
29
+ ]