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,421 @@
1
+ """Runtime seam contract: check it, fall back, and leave a receipt.
2
+
3
+ A bound structure is calibrated for one execution form — a device, an
4
+ input dtype, a width, and for the implementations that preallocate, a row
5
+ count. Hand it something else and the honest outcomes are two: refuse, or
6
+ run the host's own module instead. The outcome this module exists to
7
+ prevent is the third one, where the kernel accepts a buffer it should not
8
+ have and the host keeps going with a wrong answer.
9
+
10
+ Falling back is allowed. Falling back *quietly* is not, because a seam
11
+ that always falls back reads exactly like a seam that works: the plan
12
+ still lists it, the receipt still counts it, and the latency it was
13
+ supposed to buy is simply absent. So every fallback is counted, the first
14
+ one per seam says so out loud, and a seam that never stops falling back
15
+ takes itself out of the model rather than staying on as a lie.
16
+
17
+ Three layers, one per timescale:
18
+
19
+ per call the contract is checked; a violation runs the host module
20
+ and increments the seam's ledger
21
+ first time that seam warns once, naming the path and the reason
22
+ persistent after ``SELF_DETACH_AFTER`` consecutive fallbacks the seam
23
+ restores the host module permanently and says so
24
+
25
+ ``attach`` collects every guard it swaps in, so ``handle.report()`` is the
26
+ one place to ask what actually ran. Tests assert on it: a probe that does
27
+ not check its ledger has not checked that the thing it measured was on.
28
+
29
+ The check runs in Python, so it costs a call in an eager host and nothing
30
+ in a captured one — a graph replays the kernel it traced and never
31
+ re-enters this code. That is the right way round: the eager host is the
32
+ one whose shapes and devices can still change under it.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import threading
38
+ import warnings
39
+ from typing import Any, Callable, Iterable
40
+
41
+ import torch
42
+
43
+ GUARD_ATTR = "_frt_guard"
44
+ SELF_DETACH_AFTER = 32
45
+
46
+ #: returned by :meth:`GuardedSeam._frt_admit` when the call may proceed
47
+ PROCEED = object()
48
+
49
+ _FP8 = torch.float8_e4m3fn
50
+ #: what a bf16-entry implementation will cast for itself. FP8 is
51
+ #: deliberately absent: an fp8 tensor handed to a bf16 entry is a
52
+ #: negotiation that did not happen, not an input to convert.
53
+ CAST_OK = frozenset({torch.bfloat16, torch.float16, torch.float32})
54
+ #: what an fp8-entry implementation requires, exactly
55
+ FP8_ONLY = frozenset({_FP8})
56
+
57
+
58
+ class GuardRefused(RuntimeError):
59
+ """A seam was called outside the form it was bound for."""
60
+
61
+
62
+ def _concrete(device: torch.device | str) -> torch.device:
63
+ """Resolve a device to the indexed form a tensor will report.
64
+
65
+ ``torch.device("cuda")`` carries no index while every tensor on it
66
+ reports ``cuda:0``, so an index-less contract would refuse every call
67
+ it was meant to admit — a guard that fails closed on correct input is
68
+ worse than no guard, because it reverts a working seam and blames the
69
+ input. Normalising once at bind time keeps the per-call test an exact
70
+ comparison.
71
+ """
72
+ device = torch.device(device)
73
+ if device.type == "cuda" and device.index is None:
74
+ return torch.device("cuda", torch.cuda.current_device())
75
+ return device
76
+
77
+
78
+ class SeamGuard:
79
+ """One seam's runtime contract, its ledger, and its own exit.
80
+
81
+ Created at bind time by the implementation, which knows the form it
82
+ was calibrated for. Given a site by :func:`~flashrt_structures.swap.attach`,
83
+ which knows where in the model it ended up.
84
+ """
85
+
86
+ __slots__ = ("dtypes", "device", "k", "rows", "row_capacity", "kind",
87
+ "can_fallback", "calls", "fallbacks", "consecutive",
88
+ "last_reason", "detached", "site", "mode", "notes",
89
+ "thread", "_restore", "_warned", "pair")
90
+
91
+ def __init__(self, *, kind: str, dtypes: Iterable[torch.dtype] | None,
92
+ device: torch.device, k: int | None, rows: int | None,
93
+ row_capacity: int | None, can_fallback: bool) -> None:
94
+ self.kind = kind
95
+ #: structure-specific counters an implementation keeps about
96
+ #: itself, reported alongside the contract ones
97
+ self.notes: dict[str, Any] = {}
98
+ self.dtypes = None if dtypes is None else frozenset(dtypes)
99
+ self.device = _concrete(device)
100
+ self.k = k
101
+ self.rows = rows
102
+ self.row_capacity = row_capacity
103
+ self.can_fallback = can_fallback
104
+ self.calls = 0
105
+ self.fallbacks = 0
106
+ self.consecutive = 0
107
+ self.last_reason: str | None = None
108
+ self.detached = False
109
+ self.site: str | None = None
110
+ self.mode = "fallback"
111
+ #: the thread that first ran this seam. These implementations keep
112
+ #: preallocated stash, quantize and attention scratch buffers whose
113
+ #: whole economy is that one call consumes a result before the next
114
+ #: one writes it; a second thread in the same seam interleaves the
115
+ #: writes and the corruption is silent and shape-correct. Single
116
+ #: stream per attachment is the contract, and this is where it is
117
+ #: enforced rather than assumed.
118
+ self.thread: int | None = None
119
+ self._restore: Callable[[], None] | None = None
120
+ self._warned: set[str] = set()
121
+ #: the other half of a negotiated pair (a producer whose only
122
+ #: consumer this seam is, or vice versa). A pair is
123
+ #: all-or-nothing: one out-of-contract call falsifies the
124
+ #: premise for both seats, and a producer left seated alone
125
+ #: keeps feeding a host that cannot read its format.
126
+ self.pair: "SeamGuard | None" = None
127
+
128
+ # ---- site binding (attach time) --------------------------------
129
+
130
+ def bind_site(self, site: str, *, restore: Callable[[], None] | None,
131
+ mode: str) -> None:
132
+ self.site = site
133
+ self._restore = restore
134
+ self.mode = mode
135
+
136
+ def release_site(self) -> None:
137
+ self.site = None
138
+ self._restore = None
139
+
140
+ # ---- per call --------------------------------------------------
141
+
142
+ def admit(self, x: torch.Tensor) -> str | None:
143
+ """Return ``None`` to proceed, else why this call cannot.
144
+
145
+ The fast path is the whole point: one dtype test, one width test,
146
+ one device test, and a row test only where the implementation
147
+ preallocated for a fixed row count.
148
+ """
149
+ self.calls += 1
150
+ if self.dtypes is not None and x.dtype not in self.dtypes:
151
+ return f"input dtype {x.dtype} (bound for {self._dtype_note()})"
152
+ if self.k is not None and x.shape[-1] != self.k:
153
+ return f"input width {x.shape[-1]} (bound for {self.k})"
154
+ if x.device != self.device:
155
+ return f"input on {x.device} (bound on {self.device})"
156
+ if self.rows is not None:
157
+ rows = x.numel() // x.shape[-1]
158
+ if rows != self.rows:
159
+ return f"{rows} row(s) (bound for {self.rows})"
160
+ if self.row_capacity is not None:
161
+ rows = x.numel() // x.shape[-1]
162
+ if rows > self.row_capacity:
163
+ return (f"{rows} row(s) (buffer capacity "
164
+ f"{self.row_capacity})")
165
+ tid = threading.get_ident()
166
+ if self.thread is None:
167
+ self.thread = tid
168
+ elif tid != self.thread:
169
+ return ("called from a second thread; this seam's stash and "
170
+ "scratch buffers are shared across its calls, so "
171
+ "concurrent use would interleave writes into them")
172
+ self.consecutive = 0
173
+ return None
174
+
175
+ def _dtype_note(self) -> str:
176
+ if self.dtypes == FP8_ONLY:
177
+ return "fp8 from a producer seam"
178
+ return "/".join(sorted(str(d).replace("torch.", "")
179
+ for d in self.dtypes or ()))
180
+
181
+ def refuse(self, reason: str) -> None:
182
+ """Record a fallback; speak up the first time and at the end."""
183
+ where = self.site or f"<unattached {self.kind}>"
184
+ if self.mode == "raise":
185
+ # before counting: in strict mode nothing fell back, the call
186
+ # was refused, and a ledger claiming otherwise would be one
187
+ # more thing that says something that did not happen
188
+ self.last_reason = reason
189
+ raise GuardRefused(f"{where}: {reason}")
190
+ self.fallbacks += 1
191
+ self.consecutive += 1
192
+ self.last_reason = reason
193
+ if reason not in self._warned:
194
+ self._warned.add(reason)
195
+ warnings.warn(
196
+ f"structures: {where} fell back to the host module — "
197
+ f"{reason}. Further fallbacks of this kind are counted in "
198
+ f"handle.report() and not repeated here.",
199
+ RuntimeWarning, stacklevel=4)
200
+ if self.pair is not None and not self.detached:
201
+ # a negotiated pair collapses on the first out-of-contract
202
+ # call: the runtime path has disproved the bind-time
203
+ # premise, and every call the pair survives past this one
204
+ # is a producer feeding a consumer that is no longer there
205
+ self._self_detach()
206
+ if not self.pair.detached:
207
+ self.pair._self_detach()
208
+ return
209
+ if (self.consecutive >= SELF_DETACH_AFTER and not self.detached):
210
+ self._self_detach()
211
+
212
+ def _self_detach(self) -> None:
213
+ """Stop pretending: put the host module back for good."""
214
+ self.detached = True
215
+ where = self.site or f"<unattached {self.kind}>"
216
+ if self._restore is None:
217
+ warnings.warn(
218
+ f"structures: {where} has fallen back "
219
+ f"{self.consecutive} times in a row and cannot restore "
220
+ f"itself (it is held inside another structure, not swapped "
221
+ f"in at a path). It is costing a check per call and buying "
222
+ f"nothing — detach the attachment.",
223
+ RuntimeWarning, stacklevel=5)
224
+ return
225
+ self._restore()
226
+ warnings.warn(
227
+ f"structures: {where} fell back {self.consecutive} times in a "
228
+ f"row ({self.last_reason}) — the host module has been restored "
229
+ f"at that path permanently. The rest of the attachment is "
230
+ f"unaffected.",
231
+ RuntimeWarning, stacklevel=5)
232
+
233
+ # ---- receipt ---------------------------------------------------
234
+
235
+ def entry(self) -> dict[str, Any]:
236
+ entry = {"kind": self.kind, "calls": self.calls,
237
+ "fallbacks": self.fallbacks, "detached": self.detached,
238
+ "last_reason": self.last_reason,
239
+ "form": {"dtype": self._dtype_note(), "k": self.k,
240
+ "rows": self.rows,
241
+ "row_capacity": self.row_capacity,
242
+ "device": str(self.device)}}
243
+ if self.notes:
244
+ entry["notes"] = dict(self.notes)
245
+ return entry
246
+
247
+
248
+ class GuardedSeam:
249
+ """Mixin: contract check, host fallback, and lifecycle refusals.
250
+
251
+ Mixed in *before* ``torch.nn.Module`` so its ``_apply`` and
252
+ ``state_dict`` overrides win. An implementation arms itself at the end
253
+ of ``__init__`` with :meth:`_frt_arm` and opens its ``forward`` with
254
+ :meth:`_frt_admit`.
255
+ """
256
+
257
+ #: attribute holding the retained host module, if any
258
+ _frt_host_attr: str | None = None
259
+ #: whether calling that module with this ``forward``'s own arguments
260
+ #: reproduces the host's behaviour. False where the replacement's
261
+ #: boundary is not the retained module's boundary (a composed block,
262
+ #: an attention core called by an adapter): those refuse instead.
263
+ _frt_can_fallback: bool = False
264
+ #: class default so attribute lookup never reaches a host-forwarding
265
+ #: ``__getattr__`` for an implementation that has not armed yet
266
+ _frt_guard: SeamGuard | None = None
267
+
268
+ def _frt_arm(self, *, dtypes: Iterable[torch.dtype] | None,
269
+ device: torch.device, k: int | None = None,
270
+ rows: int | None = None,
271
+ row_capacity: int | None = None) -> SeamGuard:
272
+ """Declare the form this instance was calibrated for."""
273
+ if rows is not None and row_capacity is not None:
274
+ raise ValueError("rows and row_capacity are mutually exclusive")
275
+ guard = SeamGuard(
276
+ kind=type(self).__name__, dtypes=dtypes, device=device, k=k,
277
+ rows=rows, row_capacity=row_capacity,
278
+ can_fallback=self._frt_can_fallback)
279
+ object.__setattr__(self, GUARD_ATTR, guard)
280
+ return guard
281
+
282
+ def _frt_touch(self) -> None:
283
+ """Count a call whose form another guard already checked.
284
+
285
+ Two cases need this. A replacement that ignores its inputs (a
286
+ frozen buffer on a slower cadence) has no contract to test but
287
+ still has to be countable. And a structure reached through a wider
288
+ entry than its own ``forward`` — a producer that a composed block
289
+ calls directly — is checked by the block's contract and would
290
+ otherwise report zero calls, which reads as "never ran" when it
291
+ ran every time.
292
+ """
293
+ guard = self._frt_guard
294
+ if guard is not None and not torch.compiler.is_compiling():
295
+ guard.calls += 1
296
+
297
+ def _frt_host(self) -> torch.nn.Module | None:
298
+ attr = self._frt_host_attr
299
+ if attr is None:
300
+ return None
301
+ # straight out of _modules: going through getattr would hit the
302
+ # host-forwarding __getattr__ these implementations define
303
+ return self.__dict__.get("_modules", {}).get(attr)
304
+
305
+ def _frt_admit(self, x: torch.Tensor, *args: Any, **kwargs: Any) -> Any:
306
+ """``PROCEED``, or the host's own result for this call.
307
+
308
+ Raises :class:`GuardRefused` when there is no host module to fall
309
+ back to, or when the attachment asked for strict mode.
310
+
311
+ Steps aside entirely while a compiler is tracing, for two reasons
312
+ that point the same way. It has to: the ledger's counter is a
313
+ Python-visible side effect in a hot forward, and dynamo either
314
+ breaks the graph around it or re-derives the region's boundaries
315
+ around it — the surrounding compiled region is worth more than the
316
+ count. And it may: dynamo installs its own guards on exactly the
317
+ dtype, shape and device this contract tests, so a traced region
318
+ handed a different form recompiles rather than running the wrong
319
+ kernel. Inside a compiled region this check is redundant; in an
320
+ eager host it is the only thing there.
321
+ """
322
+ guard = self._frt_guard
323
+ if guard is None or torch.compiler.is_compiling():
324
+ return PROCEED
325
+ reason = guard.admit(x)
326
+ if reason is None:
327
+ return PROCEED
328
+ host = self._frt_host()
329
+ if host is None or not guard.can_fallback:
330
+ raise GuardRefused(
331
+ f"{guard.site or guard.kind}: {reason}; this structure has "
332
+ "no equivalent host module to fall back to")
333
+ if any(p.is_meta or p.numel() == 0
334
+ for p in host.parameters()) and not \
335
+ getattr(host, "_frt_tickets", None):
336
+ raise GuardRefused(
337
+ f"{guard.site or guard.kind}: {reason}; the host module's "
338
+ "weights were consumed and cannot be restored")
339
+ if getattr(host, "_frt_tickets", None):
340
+ # the host's weights were consumed (their truth lives in the
341
+ # weight store); a live fallback restores them once — slower
342
+ # than a resident copy, never wrong — and the ledger says so
343
+ from .storage import restore_for_fallback
344
+ restore_for_fallback(host)
345
+ guard.notes["restored_for_fallback"] = (
346
+ guard.notes.get("restored_for_fallback", 0) + 1)
347
+ guard.refuse(reason)
348
+ return self._frt_host_call(host, x, *args, **kwargs)
349
+
350
+ def _frt_host_call(self, host: torch.nn.Module, x: torch.Tensor,
351
+ *args: Any, **kwargs: Any) -> Any:
352
+ """How to reproduce this seam with the host module. Overridable."""
353
+ return host(x, *args, **kwargs)
354
+
355
+ # ---- lifecycle refusals ---------------------------------------
356
+
357
+ def _apply(self, *args: Any, **kwargs: Any):
358
+ """Refuse device/dtype migration while swapped into a model.
359
+
360
+ The packed weights and static scales of these implementations are
361
+ derived tensors held outside the parameter system, so a migration
362
+ would move the host's copy and leave the kernel's behind. Before
363
+ attach and after detach this is an ordinary module and migrates
364
+ normally.
365
+ """
366
+ guard = self._frt_guard
367
+ if guard is not None and guard.site is not None:
368
+ raise GuardRefused(
369
+ f"{guard.site}: cannot migrate device or dtype while "
370
+ "structures are attached — detach() first, migrate, then "
371
+ "attach again (the scales are calibrated per form and do "
372
+ "not survive a dtype change anyway)")
373
+ return super()._apply(*args, **kwargs)
374
+
375
+ def state_dict(self, *args: Any, destination: Any = None,
376
+ prefix: str = "", keep_vars: bool = False):
377
+ """Emit the host module's own state at this path.
378
+
379
+ A checkpoint should not change shape because an optimisation is
380
+ attached: the packed and quantised tensors here are derived from
381
+ the host weights and belong to the bind, not to the model's
382
+ state. Delegating keeps the schema and the values identical to the
383
+ unattached model, so saving while attached is safe.
384
+ """
385
+ host = self._frt_host()
386
+ if host is None or args:
387
+ return super().state_dict(*args, destination=destination,
388
+ prefix=prefix, keep_vars=keep_vars)
389
+ return host.state_dict(destination=destination, prefix=prefix,
390
+ keep_vars=keep_vars)
391
+
392
+ def _load_from_state_dict(self, state_dict, prefix, local_metadata,
393
+ strict, missing_keys, unexpected_keys,
394
+ error_msgs):
395
+ """Refuse loading into a swapped model rather than half-doing it.
396
+
397
+ Loading would have to re-derive every packed weight and re-run
398
+ calibration; silently loading the host's copy and leaving the
399
+ kernel's stale is the failure this refuses. ``detach()`` restores
400
+ the real modules, so the honest order is detach, load, attach.
401
+ """
402
+ guard = self._frt_guard
403
+ if guard is not None and guard.site is not None:
404
+ error_msgs.append(
405
+ f"{guard.site}: cannot load_state_dict while structures "
406
+ "are attached — detach(), load, then attach again so the "
407
+ "packed weights are rebuilt from the loaded ones")
408
+ return
409
+ return super()._load_from_state_dict(
410
+ state_dict, prefix, local_metadata, strict, missing_keys,
411
+ unexpected_keys, error_msgs)
412
+
413
+
414
+ def collect(module: torch.nn.Module) -> list[tuple[str, SeamGuard]]:
415
+ """Every guard at or under ``module``, by relative module name."""
416
+ found = []
417
+ for name, child in module.named_modules():
418
+ guard = child.__dict__.get(GUARD_ATTR)
419
+ if isinstance(guard, SeamGuard):
420
+ found.append((name, guard))
421
+ return found
@@ -0,0 +1,189 @@
1
+ """Explicit front door: ``structures.get(name)`` — pull one structure,
2
+ bind it to one host module, plug it in yourself.
3
+
4
+ This is the single-site, fully visible counterpart of ``attach``. The
5
+ consumption mirrors ``kernels.get_kernel``:
6
+
7
+ ffn = structures.get("decoder_ffn")
8
+ new_mlp = ffn.bind(model.model.layers[3].mlp, calibration=[x1, x2])
9
+ model.model.layers[3].mlp = new_mlp # your swap, your call
10
+
11
+ ``bind`` extracts the weights from the module you hand it, calibrates on
12
+ the samples you provide, builds the fused replacement, and self-checks
13
+ it against the original module on those same samples. A replacement
14
+ that misses the parity gate raises ``GateRefused`` instead of returning
15
+ — the explicit path fails loudly, it never hands back a bad part.
16
+ ``attach`` is this same operation, batched over every discovered site
17
+ with the net-win A/B added.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import Sequence
23
+
24
+ import torch
25
+
26
+ from .discover import _VISION_PROJ, activation_for
27
+ from .gates import parity_metrics
28
+ from flash_rt.catalog.registry import StructureSpec, load
29
+
30
+
31
+ class GateRefused(RuntimeError):
32
+ """The bound replacement did not meet the parity gate."""
33
+
34
+
35
+ class StructureHandle:
36
+ """One pulled structure, ready to bind to host modules."""
37
+
38
+ def __init__(self, spec: StructureSpec):
39
+ self.spec = spec
40
+ self.name = spec.name
41
+
42
+ def __repr__(self) -> str:
43
+ return f"StructureHandle({self.name!r}, v{self.spec.version})"
44
+
45
+ def bind(
46
+ self,
47
+ module: torch.nn.Module,
48
+ calibration: torch.Tensor | Sequence[torch.Tensor],
49
+ *,
50
+ variant: dict[str, str] | None = None,
51
+ norm: torch.nn.Module | None = None,
52
+ residual: torch.Tensor | Sequence[torch.Tensor] | None = None,
53
+ gate_cos: float = 0.999,
54
+ percentile: float = 99.9,
55
+ ) -> torch.nn.Module:
56
+ """Build a gated drop-in replacement for ``module``.
57
+
58
+ ``calibration``: one or more real inputs of ``module`` (the
59
+ normed hidden states it actually sees). ``residual``: the
60
+ matching pre-norm hidden states — the structure gate is declared
61
+ at the boundary *including* the residual add, so provide these
62
+ to gate at the declared boundary; without them the self-check
63
+ runs on the bare seam output, which is strictly harsher.
64
+ ``norm``: optional sibling norm module, recorded for
65
+ full-structure use; the MLP seam itself leaves the norm in the
66
+ host. ``gate_cos=0`` skips the self-check (not recommended).
67
+ """
68
+ samples = ([calibration] if torch.is_tensor(calibration)
69
+ else list(calibration))
70
+ if not samples:
71
+ raise ValueError("calibration must contain at least one sample")
72
+ residuals = (None if residual is None else
73
+ [residual] if torch.is_tensor(residual)
74
+ else list(residual))
75
+ if residuals is not None and len(residuals) != len(samples):
76
+ raise ValueError("residual must align 1:1 with calibration")
77
+
78
+ if self.name == "decoder_ffn":
79
+ from .impls.decoder_ffn import fp8_static as impl
80
+ gate = module.gate_proj
81
+ device = gate.weight.device
82
+ dims = {"D": gate.in_features, "F": gate.out_features}
83
+ var = {"activation": activation_for(module, "silu"),
84
+ "norm_weight_mode": "direct"}
85
+ weights = {
86
+ "w_norm": (norm.weight.detach() if norm is not None
87
+ and getattr(norm, "weight", None) is not None
88
+ else torch.ones(dims["D"], device=device)),
89
+ "w_gate": gate.weight.detach().t().contiguous(),
90
+ "w_up": module.up_proj.weight.detach().t().contiguous(),
91
+ "w_down": module.down_proj.weight.detach().t().contiguous(),
92
+ }
93
+ elif self.name == "vision_ffn":
94
+ from .impls.vision_ffn import fp8_static as impl
95
+ fc_attrs = next(
96
+ (pair for pair in _VISION_PROJ
97
+ if all(isinstance(getattr(module, a, None), torch.nn.Linear)
98
+ for a in pair)), None)
99
+ if fc_attrs is None:
100
+ raise ValueError(
101
+ "module has no fc1/fc2 (or linear_fc1/linear_fc2) pair")
102
+ fc1 = getattr(module, fc_attrs[0])
103
+ fc2 = getattr(module, fc_attrs[1])
104
+ device = fc1.weight.device
105
+ dims = {"D": fc1.in_features, "F": fc1.out_features}
106
+ var = {"activation": activation_for(module, "gelu")}
107
+ weights = {
108
+ "w_norm": (norm.weight.detach() if norm is not None
109
+ else torch.ones(dims["D"], device=device)),
110
+ "b_norm": (norm.bias.detach() if norm is not None
111
+ else torch.zeros(dims["D"], device=device)),
112
+ "w_fc1": fc1.weight.detach(), "b_fc1": fc1.bias.detach(),
113
+ "w_fc2": fc2.weight.detach(), "b_fc2": fc2.bias.detach(),
114
+ }
115
+ else:
116
+ raise ValueError(
117
+ f"structure {self.name!r} has no module-seam bind; "
118
+ "stage pipelines go through the capture door")
119
+ var.update(variant or {})
120
+
121
+ # the explicit door owns real samples, so it measures the seam's
122
+ # points by running the host module under the same hooks the
123
+ # distribution layer uses — one path for what a point is and where
124
+ # it lives, whichever door asked
125
+ from .points import Collector, Point
126
+
127
+ points = [Point("x_after_norm", "", "input")]
128
+ second = ("down_proj" if self.name == "decoder_ffn"
129
+ else (fc_attrs[1] if self.name == "vision_ffn" else None))
130
+ second_name = ("act_after_mul" if self.name == "decoder_ffn"
131
+ else "hidden_after_act")
132
+ if second is not None:
133
+ points.append(Point(second_name, second, "input"))
134
+ collector = Collector(points=points)
135
+ handles = collector.hooks(
136
+ lambda path: module if not path else getattr(module, path))
137
+ try:
138
+ with torch.no_grad():
139
+ for sample in samples:
140
+ module(sample.to(device))
141
+ collector.end_sample()
142
+ finally:
143
+ for handle in handles:
144
+ handle.remove()
145
+ collector.reduce(percentile)
146
+ kwargs = {} if self.name == "vision_ffn" else {"variant": var}
147
+ bound = impl.bind_mlp_seam(
148
+ weights,
149
+ input_scale=collector.scale("", "x_after_norm"),
150
+ hidden_scale=collector.scale(second or "", second_name),
151
+ original=module, **kwargs)
152
+
153
+ worst = 1.0
154
+ with torch.no_grad():
155
+ for k, sample in enumerate(samples):
156
+ x = sample.to(device)
157
+ got, want = bound(x), module(x)
158
+ if residuals is not None:
159
+ res = residuals[k].to(device)
160
+ got, want = res + got, res + want
161
+ worst = min(worst, parity_metrics(got, want)["cosine"])
162
+ boundary = ("structure (incl. residual)" if residuals is not None
163
+ else "bare seam")
164
+ if gate_cos and worst < gate_cos:
165
+ raise GateRefused(
166
+ f"{self.name} replacement worst cos {worst:.6f} < "
167
+ f"{gate_cos} at {boundary} boundary on {len(samples)} "
168
+ "calibration sample(s) — not handing back a part that "
169
+ "fails its own gate"
170
+ + ("" if residuals is not None else
171
+ "; pass residual= to gate at the declared boundary"))
172
+ bound.certification = {
173
+ "structure": self.name, "version": self.spec.version,
174
+ "dims": dims, "variant": var, "worst_cos": round(worst, 7),
175
+ "gate_boundary": boundary, "samples": len(samples),
176
+ "m_profile": sorted({
177
+ int(s.reshape(-1, s.shape[-1]).shape[0]) for s in samples}),
178
+ }
179
+ return bound
180
+
181
+
182
+ def get(name: str) -> StructureHandle:
183
+ """Pull one structure from the catalog (get_kernel-style)."""
184
+ spec = load(name)
185
+ if spec.kind != "region":
186
+ raise ValueError(
187
+ f"{name!r} is a {spec.kind} structure — module-level bind "
188
+ "does not apply; use the capture/provider route")
189
+ return StructureHandle(spec)