flashrt-structures 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flashrt_structures/__init__.py +174 -0
- flashrt_structures/adapters/__init__.py +55 -0
- flashrt_structures/adapters/diffusers_attention.py +237 -0
- flashrt_structures/adapters/diffusers_rotary_attention.py +239 -0
- flashrt_structures/adapters/factored_qk_norm_rope.py +252 -0
- flashrt_structures/adapters/factored_two_way_attention.py +99 -0
- flashrt_structures/adapters/gemma_attention.py +226 -0
- flashrt_structures/adapters/packed_qkv_rope.py +342 -0
- flashrt_structures/adapters/packed_stream_qk_norm_rope.py +376 -0
- flashrt_structures/adapters/qwen_per_head_qk_norm_rope.py +231 -0
- flashrt_structures/adapters/sglang_engine.py +207 -0
- flashrt_structures/adapters/transformers_attention_interface.py +73 -0
- flashrt_structures/adapters/transformers_gated_delta.py +152 -0
- flashrt_structures/adapters/transformers_gated_delta_fused.py +96 -0
- flashrt_structures/adapters/vllm_engine.py +424 -0
- flashrt_structures/adjudicate.py +85 -0
- flashrt_structures/aot.py +191 -0
- flashrt_structures/autobuild.py +2052 -0
- flashrt_structures/beta/__init__.py +43 -0
- flashrt_structures/beta/conform.py +94 -0
- flashrt_structures/beta/joins.py +113 -0
- flashrt_structures/beta/negotiate.py +84 -0
- flashrt_structures/beta/ports.py +140 -0
- flashrt_structures/decisions.py +80 -0
- flashrt_structures/discover.py +623 -0
- flashrt_structures/explain.py +70 -0
- flashrt_structures/frontdoor.py +572 -0
- flashrt_structures/gates.py +465 -0
- flashrt_structures/guard.py +421 -0
- flashrt_structures/handle.py +189 -0
- flashrt_structures/impls/__init__.py +219 -0
- flashrt_structures/impls/adaln_producer/__init__.py +8 -0
- flashrt_structures/impls/adaln_producer/broker.py +116 -0
- flashrt_structures/impls/adaln_producer/fused.py +388 -0
- flashrt_structures/impls/adarms_stack/__init__.py +8 -0
- flashrt_structures/impls/adarms_stack/fp8_chain.py +832 -0
- flashrt_structures/impls/adarms_stack/region.py +102 -0
- flashrt_structures/impls/attention_core/__init__.py +132 -0
- flashrt_structures/impls/attention_core/fa2_seqused.py +458 -0
- flashrt_structures/impls/attention_core/fa4_cute.py +154 -0
- flashrt_structures/impls/attention_core/fa4_fp8.py +178 -0
- flashrt_structures/impls/attention_core/masked_mha.py +158 -0
- flashrt_structures/impls/attention_core/two_way_fa2.py +220 -0
- flashrt_structures/impls/cadence_static/__init__.py +18 -0
- flashrt_structures/impls/cadence_static/buffers.py +122 -0
- flashrt_structures/impls/cadence_static/cross_attention.py +187 -0
- flashrt_structures/impls/chain_elements.py +89 -0
- flashrt_structures/impls/decode_loop/__init__.py +0 -0
- flashrt_structures/impls/decode_loop/fp8_kv.py +206 -0
- flashrt_structures/impls/decode_loop/mtp_speculative.py +245 -0
- flashrt_structures/impls/decode_loop/whole_step.py +852 -0
- flashrt_structures/impls/decoder_block/__init__.py +6 -0
- flashrt_structures/impls/decoder_block/attn_sublayer.py +110 -0
- flashrt_structures/impls/decoder_block/fused.py +167 -0
- flashrt_structures/impls/decoder_ffn/__init__.py +0 -0
- flashrt_structures/impls/decoder_ffn/fp8_static.py +310 -0
- flashrt_structures/impls/decoder_ffn/fp8_static.yaml +22 -0
- flashrt_structures/impls/decoder_ffn/w4a16_static.py +221 -0
- flashrt_structures/impls/decoder_ffn/w8a16_static.py +183 -0
- flashrt_structures/impls/dit_stack/__init__.py +11 -0
- flashrt_structures/impls/dit_stack/fp4_chain.py +417 -0
- flashrt_structures/impls/dit_stack/region.py +86 -0
- flashrt_structures/impls/fixed_iter/__init__.py +29 -0
- flashrt_structures/impls/fixed_iter/openpi.py +264 -0
- flashrt_structures/impls/fixed_iter/protocol.py +94 -0
- flashrt_structures/impls/gated_delta_core/__init__.py +3 -0
- flashrt_structures/impls/gated_delta_core/fused_layer.py +545 -0
- flashrt_structures/impls/gated_delta_core/hub_v3.py +152 -0
- flashrt_structures/impls/graph_lowering/__init__.py +27 -0
- flashrt_structures/impls/graph_lowering/pi052_denoise.py +179 -0
- flashrt_structures/impls/graph_lowering/protocol.py +76 -0
- flashrt_structures/impls/graph_lowering/qwen3_vl.py +364 -0
- flashrt_structures/impls/linear_proj/__init__.py +0 -0
- flashrt_structures/impls/linear_proj/fp8_static.py +270 -0
- flashrt_structures/impls/linear_proj/nvfp4_balance.py +131 -0
- flashrt_structures/impls/linear_proj/nvfp4_dynamic.py +182 -0
- flashrt_structures/impls/linear_proj/w8a16_static.py +230 -0
- flashrt_structures/impls/modnorm_qkv_chain/__init__.py +0 -0
- flashrt_structures/impls/modnorm_qkv_chain/fp8_ptok_table.py +291 -0
- flashrt_structures/impls/moe_experts/__init__.py +9 -0
- flashrt_structures/impls/moe_experts/nvfp4_dynamic.py +208 -0
- flashrt_structures/impls/moe_experts/nvfp4_w4a16.py +129 -0
- flashrt_structures/impls/norm_fused/__init__.py +3 -0
- flashrt_structures/impls/norm_fused/bf16.py +94 -0
- flashrt_structures/impls/norm_fused/fp8_producer.py +84 -0
- flashrt_structures/impls/patch_projection/__init__.py +3 -0
- flashrt_structures/impls/patch_projection/bf16_flat.py +139 -0
- flashrt_structures/impls/prefill_tower/__init__.py +10 -0
- flashrt_structures/impls/prefill_tower/fp8_chain.py +955 -0
- flashrt_structures/impls/prefill_tower/region.py +99 -0
- flashrt_structures/impls/qk_norm_rope/__init__.py +12 -0
- flashrt_structures/impls/qk_norm_rope/per_head_gqa.py +199 -0
- flashrt_structures/impls/qk_norm_rope/projection_bf16.py +165 -0
- flashrt_structures/impls/qkv_pack/__init__.py +5 -0
- flashrt_structures/impls/qkv_pack/bf16.py +110 -0
- flashrt_structures/impls/qkv_pack/fp8_static.py +435 -0
- flashrt_structures/impls/qkv_pack/nvfp4_balance.py +218 -0
- flashrt_structures/impls/qkv_rope/__init__.py +3 -0
- flashrt_structures/impls/qkv_rope/packed_bias_bf16.py +143 -0
- flashrt_structures/impls/step_table.py +113 -0
- flashrt_structures/impls/vision_ffn/__init__.py +0 -0
- flashrt_structures/impls/vision_ffn/fp8_static.py +261 -0
- flashrt_structures/impls/vision_ffn/nvfp4_balance.py +211 -0
- flashrt_structures/impls/vision_tower/__init__.py +7 -0
- flashrt_structures/impls/vision_tower/fp8_chain.py +533 -0
- flashrt_structures/impls/vision_tower/region.py +91 -0
- flashrt_structures/matrix.py +126 -0
- flashrt_structures/points.py +368 -0
- flashrt_structures/prequantized.py +131 -0
- flashrt_structures/quantize_on_adopt.py +94 -0
- flashrt_structures/recipe.py +438 -0
- flashrt_structures/regions.py +208 -0
- flashrt_structures/schemes.py +490 -0
- flashrt_structures/stages.py +298 -0
- flashrt_structures/storage.py +255 -0
- flashrt_structures/swap.py +398 -0
- flashrt_structures/workspace.py +110 -0
- flashrt_structures-0.2.0.dist-info/METADATA +140 -0
- flashrt_structures-0.2.0.dist-info/RECORD +122 -0
- flashrt_structures-0.2.0.dist-info/WHEEL +5 -0
- flashrt_structures-0.2.0.dist-info/licenses/LICENSE +202 -0
- flashrt_structures-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,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
|
+
}
|