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,207 @@
1
+ """SGLang engine family: explicit assembly across a process boundary.
2
+
3
+ SGLang shares vLLM's module lineage — projections return
4
+ ``(out, bias)``, seams answer the same structural predicates — but its
5
+ scheduler is a **spawned subprocess**: a patch applied in the launcher
6
+ process never reaches the model. The carrier that does is
7
+ ``sitecustomize``: :func:`install` writes a hook module into a
8
+ temporary directory, prepends it to ``PYTHONPATH``, and every
9
+ interpreter the engine spawns runs it at startup. The hook is gated on
10
+ an environment flag and is inert anywhere else.
11
+
12
+ Two further engine facts ride here:
13
+
14
+ - **Quantized checkpoints hold FP8 block weights.** A dense binder fed
15
+ raw FP8 bytes produces garbage that no bind-time smoke can catch
16
+ (finite, right shape, wrong scale). Dense seating therefore
17
+ dequantizes through the module's own ``weight_scale_inv`` in row
18
+ slabs before packing — cross-format regrids carry their scale
19
+ semantics or they do not run.
20
+ - **The radix prefix cache breaks repeat-determinism on hybrid
21
+ (linear-attention) models** — measured, host-side, seam exonerated.
22
+ Serving such a model with seats attached should disable the radix
23
+ cache until the host fixes the interplay.
24
+
25
+ Usage, before constructing the engine::
26
+
27
+ from flashrt_structures.adapters import sglang_engine
28
+ sglang_engine.install()
29
+ llm = sgl.Engine(model_path=...)
30
+ llm.generate(...)
31
+
32
+ Scope: dense projection seams (the measured 2x-class win on this
33
+ engine). The fused-MoE and LM-head surfaces differ from vLLM's and are
34
+ refused until profiled, not approximated.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import os
40
+ import pathlib
41
+ import sys
42
+ import tempfile
43
+
44
+ import torch
45
+
46
+ _ATTACH_FLAG = "FRT_SGLANG_ATTACH"
47
+ _PATH_VAR = "FRT_SGLANG_STRUCTURES_PATH"
48
+ _SEATS_VAR = "FRT_SGLANG_SEATS"
49
+
50
+ #: dense projection seams by dataflow position; the qwen3_5 family rows
51
+ #: plus the engine-wide decoder conventions. Overridable per install.
52
+ DENSE_SEAT_SUFFIXES = (
53
+ "linear_attn.out_proj", "linear_attn.in_proj_qkvz",
54
+ "self_attn.qkv_proj", "self_attn.o_proj",
55
+ "shared_expert.gate_up_proj", "shared_expert.down_proj",
56
+ "mlp.gate_up_proj", "mlp.down_proj",
57
+ )
58
+
59
+
60
+ def _dense_weight(module):
61
+ """The module's weight as a dense BF16 matrix.
62
+
63
+ FP8 block-quantized modules carry ``weight_scale_inv``; the dequant
64
+ runs in 4096-row slabs so the transient stays bounded on tight
65
+ cards. A quantized weight without its scale is refused."""
66
+ w = module.weight.data
67
+ fp8 = getattr(torch, "float8_e4m3fn", None)
68
+ if w.dtype != fp8:
69
+ return w
70
+ scale = getattr(module, "weight_scale_inv",
71
+ getattr(module, "weight_scale", None))
72
+ if scale is None:
73
+ raise ValueError("refused: fp8 weight without a block scale")
74
+ sd = scale.data.float()
75
+ n, k = w.shape
76
+ bn = -(-n // sd.shape[0])
77
+ bk = -(-k // sd.shape[1])
78
+ rows = sd.repeat_interleave(bn, 0)[:n]
79
+ out = torch.empty(n, k, device=w.device, dtype=torch.bfloat16)
80
+ for i in range(0, n, 4096):
81
+ j = min(i + 4096, n)
82
+ out[i:j] = (w[i:j].float()
83
+ * rows[i:j].repeat_interleave(bk, 1)[:, :k]
84
+ ).to(torch.bfloat16)
85
+ return out
86
+
87
+
88
+ def attach_engine(model, *, seats=DENSE_SEAT_SUFFIXES, use_gemv=None,
89
+ release=False, verbose=True):
90
+ """Seat an SGLang model's dense projections; returns the handle."""
91
+ from .. import swap as _swap
92
+ from ..impls.linear_proj import nvfp4_dynamic as _linear
93
+ from .vllm_engine import _ProjSeat, _is_projection
94
+
95
+ if use_gemv is None:
96
+ use_gemv = torch.cuda.get_device_capability() >= (12, 0)
97
+ if not use_gemv:
98
+ orig_init = _linear.LinearProjNvfp4Dynamic.__init__
99
+
100
+ def _init(self, *a, **kw):
101
+ orig_init(self, *a, **kw)
102
+ self._gemv = None
103
+ _linear.LinearProjNvfp4Dynamic.__init__ = _init
104
+
105
+ refused = []
106
+ targets = [(n, m) for n, m in model.named_modules()
107
+ if any(n.endswith(s) for s in seats) and _is_projection(m)]
108
+ targets.sort(key=lambda t: t[1].weight.numel())
109
+ model.eval()
110
+
111
+ # On a tight card the relief must land while binding continues, not
112
+ # after it: with release, seats attach in slabs of original bytes
113
+ # and each slab's originals move to the weight store before the
114
+ # next slab binds. Without release, one handle carries everything.
115
+ GROUP = 512 << 20
116
+ handles, swaps, group_bytes, seated = [], {}, 0, 0
117
+
118
+ def flush():
119
+ nonlocal swaps, group_bytes, seated
120
+ if not swaps:
121
+ return
122
+ handle = _swap.attach(model, swaps)
123
+ if release:
124
+ handle.consume()
125
+ handles.append(handle)
126
+ seated += len(swaps)
127
+ swaps, group_bytes = {}, 0
128
+
129
+ for name, mod in targets:
130
+ try:
131
+ seam, _ = _linear.bind_proj_seam({"w": _dense_weight(mod)})
132
+ except Exception as e:
133
+ if not refused:
134
+ print(f"[structures.sglang] first refusal {name}: "
135
+ f"{e!r}"[:180], flush=True)
136
+ refused.append((name, repr(e)[:120]))
137
+ continue
138
+ swaps[name] = _ProjSeat(seam)
139
+ group_bytes += mod.weight.numel() * mod.weight.element_size()
140
+ if release and group_bytes >= GROUP:
141
+ flush()
142
+ flush()
143
+ if verbose:
144
+ print(f"[structures.sglang] {seated} seats "
145
+ f"({len(handles)} handles), {len(refused)} refused",
146
+ flush=True)
147
+ for h in handles:
148
+ h.notes = {"refused": refused}
149
+ return handles if len(handles) != 1 else handles[0]
150
+
151
+
152
+ def _patch_runner():
153
+ """Runs inside the spawned scheduler, via the sitecustomize hook."""
154
+ import sglang.srt.model_executor.model_runner as mr
155
+
156
+ orig = mr.ModelRunner.load_model
157
+
158
+ def load_model(self, *a, **kw):
159
+ orig(self, *a, **kw)
160
+ seats = tuple(s for s in os.environ.get(_SEATS_VAR, "").split(",")
161
+ if s) or DENSE_SEAT_SUFFIXES
162
+ try:
163
+ attach_engine(self.model, seats=seats,
164
+ release=os.environ.get("FRT_SGLANG_RELEASE") == "1")
165
+ except Exception as e:
166
+ print(f"[structures.sglang] attach refused: {e!r}", flush=True)
167
+ mr.ModelRunner.load_model = load_model
168
+
169
+
170
+ _HOOK = """\
171
+ import os
172
+ if os.environ.get({flag!r}) == "1":
173
+ try:
174
+ import sys
175
+ sys.path.insert(0, os.environ[{path!r}])
176
+ from flashrt_structures.adapters import sglang_engine
177
+ sglang_engine._patch_runner()
178
+ except Exception as e:
179
+ print(f"[structures.sglang] hook inert: {{e!r}}", flush=True)
180
+ """
181
+
182
+
183
+ def install(*, seats=None, structures_path=None, release=False):
184
+ """Arm the spawn hook; call before constructing the engine.
185
+
186
+ Writes a ``sitecustomize`` into a temporary directory, prepends it
187
+ to ``PYTHONPATH`` and flags the attach on — every interpreter the
188
+ engine spawns picks it up; other processes see a dormant flag."""
189
+ if structures_path is None:
190
+ structures_path = str(
191
+ pathlib.Path(__file__).resolve().parents[3])
192
+ hook_dir = tempfile.mkdtemp(prefix="frt-sglang-hook-")
193
+ hook = pathlib.Path(hook_dir) / "sitecustomize.py"
194
+ hook.write_text(_HOOK.format(flag=_ATTACH_FLAG, path=_PATH_VAR))
195
+ os.environ[_PATH_VAR] = structures_path
196
+ os.environ[_ATTACH_FLAG] = "1"
197
+ if seats:
198
+ os.environ[_SEATS_VAR] = ",".join(seats)
199
+ if release:
200
+ os.environ["FRT_SGLANG_RELEASE"] = "1"
201
+ prev = os.environ.get("PYTHONPATH", "")
202
+ os.environ["PYTHONPATH"] = (hook_dir + (":" + prev if prev else ""))
203
+ # the launcher itself may import sitecustomize-late; patch it too so
204
+ # single-process embeddings behave the same way
205
+ if "sglang" in sys.modules:
206
+ _patch_runner()
207
+ return hook_dir
@@ -0,0 +1,73 @@
1
+ """Route transformers-host attention through a hub kernel interface.
2
+
3
+ Any transformers-generation host resolves its attention through
4
+ ``ALL_ATTENTION_FUNCTIONS[config._attn_implementation]`` — the same
5
+ registry the capture lowering's pinned vision attention and the
6
+ per-head rope route already consult. Registering a hub attention
7
+ kernel there and switching the config is therefore a seat like any
8
+ other: capability-qualified (registry present, package resolvable),
9
+ fully revertible (configs restored, registry entry removed), and
10
+ receipted. Which interface actually wins on a box is a measured band
11
+ decision (``decisions.lookup("backbone_attn")``), never a default
12
+ flip: an empty cache keeps the host's own interface.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import importlib
18
+
19
+ import torch
20
+
21
+
22
+ class TransformersAttentionInterfaceAdapter:
23
+ __name__ = "transformers_attention_interface"
24
+
25
+ def __call__(self, model, plan=None):
26
+ from ..decisions import lookup
27
+ from ..impls import KernelUnavailable, hub_kernel
28
+
29
+ if lookup("backbone_attn", default="host") != "fa4":
30
+ return None
31
+ from ..impls.graph_lowering.qwen3_vl import _find_qwen3_vl
32
+ target = _find_qwen3_vl(model)
33
+ if target is None:
34
+ return None
35
+ try:
36
+ fa4 = hub_kernel("kernels-community/flash-attn4", ">=0")
37
+ except KernelUnavailable as missing:
38
+ return {"refused": [("backbone_attn",
39
+ f"fa4 unavailable: {missing}")]}
40
+
41
+ def fa4_interface(module, query, key, value,
42
+ attention_mask=None, scaling=None,
43
+ dropout=0.0, is_causal=False, **kwargs):
44
+ del module, attention_mask, dropout, kwargs
45
+ out = fa4.flash_attn_func(
46
+ query.transpose(1, 2), key.transpose(1, 2),
47
+ value.transpose(1, 2), softmax_scale=scaling,
48
+ causal=bool(is_causal))
49
+ if isinstance(out, tuple):
50
+ out = out[0]
51
+ return out, None
52
+
53
+ base = target.model
54
+ modeling = importlib.import_module(type(base.visual).__module__)
55
+ registry = modeling.ALL_ATTENTION_FUNCTIONS
56
+ try:
57
+ registry["flashrt_fa4"] = fa4_interface
58
+ except TypeError:
59
+ registry.register("flashrt_fa4", fa4_interface)
60
+ configs = [base.visual.config, base.language_model.config,
61
+ target.config]
62
+ saved = [(cfg, cfg._attn_implementation) for cfg in configs]
63
+ for cfg in configs:
64
+ cfg._attn_implementation = "flashrt_fa4"
65
+
66
+ def revert():
67
+ for cfg, prev in saved:
68
+ cfg._attn_implementation = prev
69
+ registry.pop("flashrt_fa4", None) \
70
+ if hasattr(registry, "pop") else None
71
+
72
+ return {"revert": [revert],
73
+ "notes": {"backbone_attn": "fa4"}}
@@ -0,0 +1,152 @@
1
+ """Capability adapter for Transformers-style Gated Delta call slots."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import torch
6
+
7
+ from ..impls.gated_delta_core import bind_gated_delta_core
8
+
9
+
10
+ def _compatible(module) -> bool:
11
+ return (
12
+ callable(getattr(module, "recurrent_gated_delta_rule", None))
13
+ and callable(getattr(module, "chunk_gated_delta_rule", None))
14
+ and getattr(module, "num_v_heads", None) in (32, 48)
15
+ and getattr(module, "head_k_dim", None) == 128
16
+ and getattr(module, "head_v_dim", None) == 128
17
+ )
18
+
19
+
20
+ class _Recorder:
21
+ def __init__(self, original, rows, phase):
22
+ self.original = original
23
+ self.rows = rows
24
+ self.phase = phase
25
+
26
+ def __call__(self, query, key, value, g, beta, *args, **kwargs):
27
+ state = kwargs.get("initial_state")
28
+ row = {
29
+ "phase": self.phase,
30
+ "query": query.detach(),
31
+ "key": key.detach(),
32
+ "value": value.detach(),
33
+ "g": g.detach(),
34
+ "beta": beta.detach(),
35
+ "state": state.detach() if state is not None else None,
36
+ "output_final_state": kwargs.get("output_final_state", False),
37
+ "use_qk_l2norm": kwargs.get(
38
+ "use_qk_l2norm_in_kernel", False),
39
+ }
40
+ if not self.rows:
41
+ self.rows.append(row)
42
+ else:
43
+ expected = tuple(self.rows[0][name].shape
44
+ for name in ("query", "key", "value", "g", "beta"))
45
+ got = tuple(row[name].shape
46
+ for name in ("query", "key", "value", "g", "beta"))
47
+ if got != expected:
48
+ raise ValueError(
49
+ "gated_delta_core: shape moved inside one host call")
50
+ return self.original(query, key, value, g, beta, *args, **kwargs)
51
+
52
+
53
+ class _Route:
54
+ def __init__(self, core, original):
55
+ self.core = core
56
+ self.original = original
57
+
58
+ def __call__(self, query, key, value, g, beta, *args, **kwargs):
59
+ supported_keys = {
60
+ "initial_state", "output_final_state",
61
+ "use_qk_l2norm_in_kernel", "chunk_size", "cu_seqlens",
62
+ }
63
+ unsupported = set(kwargs).difference(supported_keys)
64
+ packed = kwargs.get("cu_seqlens") is not None
65
+ if args or unsupported or packed or query.shape[1] != 1:
66
+ guard = getattr(self.core, "_frt_guard", None)
67
+ if guard is not None and not torch.compiler.is_compiling():
68
+ reason = "unsupported packed or extended GDN call contract"
69
+ guard.refuse(reason)
70
+ return self.original(
71
+ query, key, value, g, beta, *args, **kwargs)
72
+ initial_state = kwargs.get("initial_state")
73
+ output_final_state = bool(kwargs.get("output_final_state", False))
74
+ use_norm = bool(kwargs.get("use_qk_l2norm_in_kernel", False))
75
+ return self.core(
76
+ query, key, value, g, beta, initial_state,
77
+ output_final_state=output_final_state,
78
+ use_qk_l2norm=use_norm,
79
+ )
80
+
81
+
82
+ class TransformersGatedDeltaAdapter:
83
+ """Route recurrent/chunk callable slots without matching class names."""
84
+
85
+ __name__ = "transformers_gated_delta"
86
+
87
+ def __call__(self, model, forward):
88
+ sites = [(path, module) for path, module in model.named_modules()
89
+ if _compatible(module)]
90
+ if not sites:
91
+ return None
92
+ captures = [[] for _ in sites]
93
+ originals = []
94
+ for (_, module), rows in zip(sites, captures):
95
+ recurrent = module.recurrent_gated_delta_rule
96
+ chunk = module.chunk_gated_delta_rule
97
+ originals.append((module, recurrent, chunk))
98
+ module.recurrent_gated_delta_rule = _Recorder(
99
+ recurrent, rows, "decode_recurrent")
100
+ module.chunk_gated_delta_rule = _Recorder(
101
+ chunk, rows, "sequence")
102
+ try:
103
+ with torch.no_grad():
104
+ forward()
105
+ finally:
106
+ for module, recurrent, chunk in originals:
107
+ module.recurrent_gated_delta_rule = recurrent
108
+ module.chunk_gated_delta_rule = chunk
109
+
110
+ if not any(captures):
111
+ return None
112
+ routes = []
113
+ observed = {}
114
+ for (path, module), rows, (_, recurrent, chunk) in zip(
115
+ sites, captures, originals):
116
+ if not rows:
117
+ continue
118
+ row = rows[0]
119
+ if row["phase"] != "decode_recurrent":
120
+ raise ValueError(
121
+ "gated_delta_core: Hub v3 explicit-state executable "
122
+ "covers recurrent decode only; a sequence-inout artifact "
123
+ "is required for prefill")
124
+ if not all(row[name].is_contiguous()
125
+ for name in ("query", "key", "value")):
126
+ raise ValueError(
127
+ "gated_delta_core: formal Hub v3 artifact requires "
128
+ "contiguous Q/K/V, but this host exposes split views; "
129
+ "a stride-aware recurrence artifact is required")
130
+ # Each site owns its output/state scratch. Sharing one core across
131
+ # equal signatures would make later layers overwrite live buffers
132
+ # from earlier layers during compiled or captured execution.
133
+ core = bind_gated_delta_core(row)
134
+ routes.append((module, recurrent, chunk, core))
135
+ observed[f"{path}.gated_delta_core"] = core
136
+
137
+ def enable():
138
+ for module, recurrent, chunk, core in routes:
139
+ module.recurrent_gated_delta_rule = _Route(core, recurrent)
140
+ module.chunk_gated_delta_rule = _Route(core, chunk)
141
+
142
+ def disable():
143
+ for module, recurrent, chunk, _ in routes:
144
+ module.recurrent_gated_delta_rule = recurrent
145
+ module.chunk_gated_delta_rule = chunk
146
+
147
+ enable()
148
+ return {
149
+ "observed": observed,
150
+ "revert": [disable],
151
+ "toggle": (enable, disable),
152
+ }
@@ -0,0 +1,96 @@
1
+ """Fused-layer adapter for Transformers-style gated-delta layers.
2
+
3
+ Where the callable-slot adapter serves the recurrent rule alone, this
4
+ one binds the whole layer's cached-decode step as the fused Hub chain
5
+ (``impls.gated_delta_core.fused_layer``). It is registered ahead of the
6
+ callable-slot adapter and refuses cleanly — letting the ladder fall
7
+ through — when the installed packages predate the chain's entries or a
8
+ layer is outside the fused profile. Recognition is by shape, never by
9
+ class or model names.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import torch
15
+
16
+ from ..impls.gated_delta_core import fused_layer
17
+
18
+
19
+ def _fusable(module) -> bool:
20
+ hv = getattr(module, "num_v_heads", None)
21
+ hk = getattr(module, "num_k_heads", None)
22
+ return (
23
+ all(isinstance(getattr(module, name, None), torch.nn.Linear)
24
+ for name in ("in_proj_qkv", "in_proj_z", "in_proj_b",
25
+ "in_proj_a", "out_proj"))
26
+ and getattr(module, "conv1d", None) is not None
27
+ and getattr(module, "A_log", None) is not None
28
+ and getattr(module, "dt_bias", None) is not None
29
+ and getattr(module, "norm", None) is not None
30
+ # the chain's own profile envelope: D=128, v-heads a multiple
31
+ # of k-heads (the 48/16 host keeps its dedicated entries, other
32
+ # profiles route the head-generic ones; the bind refuses if the
33
+ # installed build predates them)
34
+ and isinstance(hv, int) and isinstance(hk, int)
35
+ and hv > 0 and hk > 0 and hv % hk == 0
36
+ and getattr(module, "head_k_dim", None) == 128
37
+ and getattr(module, "head_v_dim", None) == 128
38
+ )
39
+
40
+
41
+ def _layer_index(path: str) -> int | None:
42
+ parts = path.split(".")
43
+ for i in range(len(parts) - 1, 0, -1):
44
+ if parts[i - 1] == "layers" and parts[i].isdigit():
45
+ return int(parts[i])
46
+ return None
47
+
48
+
49
+ class TransformersGatedDeltaFusedAdapter:
50
+ """Bind every fusable gated-delta layer module as one fused seam."""
51
+
52
+ __name__ = "transformers_gated_delta_fused"
53
+ scheme_aware = True
54
+
55
+ def __call__(self, model, forward, scheme=None):
56
+ fmt = getattr(scheme, "gdn_projection_format", None)
57
+ release = bool(getattr(scheme, "gdn_release_host_weights",
58
+ False))
59
+ sites = []
60
+ for path, module in model.named_modules():
61
+ for child_name, child in module.named_children():
62
+ child_path = f"{path}.{child_name}" if path else child_name
63
+ if not _fusable(child):
64
+ continue
65
+ idx = _layer_index(child_path)
66
+ if idx is None:
67
+ continue
68
+ sites.append((module, child_name, child, idx))
69
+ if not sites:
70
+ return None
71
+
72
+ routes = []
73
+ observed = {}
74
+ for parent, child_name, child, idx in sites:
75
+ # a package predating the chain raises here once, and the
76
+ # whole adapter steps aside for the callable-slot ladder
77
+ bound = fused_layer.bind_fused_decode_layer(
78
+ child, idx, projection_format=fmt,
79
+ release_host_weights=release)
80
+ routes.append((parent, child_name, child, bound))
81
+ observed[f"{child_name}@{idx}.gated_delta_fused"] = bound
82
+
83
+ def enable():
84
+ for parent, child_name, _child, bound in routes:
85
+ setattr(parent, child_name, bound)
86
+
87
+ def disable():
88
+ for parent, child_name, child, _bound in routes:
89
+ setattr(parent, child_name, child)
90
+
91
+ enable()
92
+ return {
93
+ "observed": observed,
94
+ "revert": [disable],
95
+ "toggle": (enable, disable),
96
+ }