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,364 @@
|
|
|
1
|
+
"""Whole-graph shape lowering for the Qwen3-VL backbone family.
|
|
2
|
+
|
|
3
|
+
The GR00T N1.7 hosts (official Isaac-GR00T and the LeRobot port) both
|
|
4
|
+
run a Qwen3-VL vision-language backbone that recomputes, on every call,
|
|
5
|
+
quantities that depend only on the request's shape and token placement:
|
|
6
|
+
multimodal position ids, rope tables, vision patch routing, sequence
|
|
7
|
+
cumsums, placeholder masks. Several of those recomputations synchronize
|
|
8
|
+
(``.item()`` / ``.tolist()`` / mask ``.all()``), which CUDA graph
|
|
9
|
+
capture forbids. For one fixed observation shape they are constants, so
|
|
10
|
+
this adapter records one real request, computes each constant once, and
|
|
11
|
+
pins the handful of host functions that would otherwise recompute them.
|
|
12
|
+
|
|
13
|
+
Two transformers generations of the vision contract are served, probed
|
|
14
|
+
by class presence rather than version: the older visual tower returns
|
|
15
|
+
``(merged, deepstack_list)``; the newer one wraps the same tensors in
|
|
16
|
+
``BaseModelOutputWithDeepstackFeatures`` and splits pooled embeddings
|
|
17
|
+
inside ``get_image_features``. Hosts that wrap the backbone in their
|
|
18
|
+
own per-call glue (a legacy position-id injector) are pinned through a
|
|
19
|
+
capability probe on the wrapper, never through a host table.
|
|
20
|
+
|
|
21
|
+
Every pin is a shape-derived constant of one fixed request; image and
|
|
22
|
+
state *values* flow through untouched, and ``undo()`` restores every
|
|
23
|
+
patched attribute.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import types
|
|
29
|
+
from typing import Any, Callable
|
|
30
|
+
|
|
31
|
+
import torch
|
|
32
|
+
|
|
33
|
+
from .protocol import GraphLowering, GraphLoweringRefused
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _find_qwen3_vl(model: Any):
|
|
37
|
+
"""The first Qwen3-VL-shaped conditional-generation module, or None."""
|
|
38
|
+
for _, module in model.named_modules():
|
|
39
|
+
inner = getattr(module, "model", None)
|
|
40
|
+
visual = getattr(inner, "visual", None)
|
|
41
|
+
if (visual is not None
|
|
42
|
+
and hasattr(visual, "fast_pos_embed_interpolate")
|
|
43
|
+
and hasattr(visual, "deepstack_visual_indexes")
|
|
44
|
+
and hasattr(inner, "language_model")
|
|
45
|
+
and hasattr(module, "lm_head")
|
|
46
|
+
and getattr(getattr(module, "config", None),
|
|
47
|
+
"image_token_id", None) is not None):
|
|
48
|
+
return module
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _find_glue_owner(model: Any, qwen: Any):
|
|
53
|
+
"""A host wrapper owning ``qwen`` with per-call position-id glue."""
|
|
54
|
+
for _, module in model.named_modules():
|
|
55
|
+
if (getattr(module, "model", None) is qwen
|
|
56
|
+
and hasattr(module, "_ensure_legacy_qwen3_position_ids")):
|
|
57
|
+
return module
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _first_output_tensor(value):
|
|
62
|
+
"""One deterministic tensor from a pipeline output, for proofs."""
|
|
63
|
+
if torch.is_tensor(value):
|
|
64
|
+
return value
|
|
65
|
+
if isinstance(value, dict):
|
|
66
|
+
for key in sorted(value):
|
|
67
|
+
found = _first_output_tensor(value[key])
|
|
68
|
+
if found is not None:
|
|
69
|
+
return found
|
|
70
|
+
return None
|
|
71
|
+
if isinstance(value, (tuple, list)):
|
|
72
|
+
for item in value:
|
|
73
|
+
found = _first_output_tensor(item)
|
|
74
|
+
if found is not None:
|
|
75
|
+
return found
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Qwen3VLGraphLoweringAdapter:
|
|
80
|
+
"""Pin the Qwen3-VL backbone's shape glue for one fixed request."""
|
|
81
|
+
|
|
82
|
+
family = "qwen3_vl_backbone"
|
|
83
|
+
|
|
84
|
+
def lower(self, model: Any,
|
|
85
|
+
forward: Callable[[], Any]) -> GraphLowering | None:
|
|
86
|
+
qwen = _find_qwen3_vl(model)
|
|
87
|
+
if qwen is None:
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
# ---- record one real request ---------------------------------
|
|
91
|
+
request: dict[str, torch.Tensor] = {}
|
|
92
|
+
|
|
93
|
+
def grab(_module, args, kwargs):
|
|
94
|
+
for key in ("input_ids", "attention_mask", "image_grid_thw"):
|
|
95
|
+
value = kwargs.get(key)
|
|
96
|
+
if value is None and args:
|
|
97
|
+
break
|
|
98
|
+
if value is not None and key not in request:
|
|
99
|
+
request[key] = value
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
handle = qwen.register_forward_pre_hook(grab, with_kwargs=True)
|
|
103
|
+
try:
|
|
104
|
+
# the recording pass is calibration-class work: it runs with
|
|
105
|
+
# observation hooks on host modules, and a compiled callable
|
|
106
|
+
# must execute eagerly here — otherwise its first trace
|
|
107
|
+
# happens with the recording hooks inside the graph
|
|
108
|
+
runner = forward
|
|
109
|
+
dynamo = getattr(torch, "_dynamo", None)
|
|
110
|
+
if dynamo is not None and hasattr(dynamo, "disable"):
|
|
111
|
+
runner = dynamo.disable(forward)
|
|
112
|
+
with torch.inference_mode():
|
|
113
|
+
runner()
|
|
114
|
+
finally:
|
|
115
|
+
handle.remove()
|
|
116
|
+
missing = [k for k in ("input_ids", "attention_mask",
|
|
117
|
+
"image_grid_thw") if k not in request]
|
|
118
|
+
if missing:
|
|
119
|
+
raise GraphLoweringRefused(
|
|
120
|
+
f"{self.family}: could not observe {missing} on the "
|
|
121
|
+
"recorded request — the host calls its backbone in a "
|
|
122
|
+
"form this family does not recognize")
|
|
123
|
+
|
|
124
|
+
input_ids = request["input_ids"]
|
|
125
|
+
attention_mask = request["attention_mask"]
|
|
126
|
+
grid_thw = request["image_grid_thw"]
|
|
127
|
+
if not bool(torch.all(attention_mask == 1).item()):
|
|
128
|
+
raise GraphLoweringRefused(
|
|
129
|
+
f"{self.family}: fixed-shape capture requires an "
|
|
130
|
+
"all-one attention mask")
|
|
131
|
+
|
|
132
|
+
base = qwen.model
|
|
133
|
+
visual = base.visual
|
|
134
|
+
|
|
135
|
+
# ---- the constants of this request ---------------------------
|
|
136
|
+
import importlib
|
|
137
|
+
modeling = importlib.import_module(type(visual).__module__)
|
|
138
|
+
output_cls = getattr(modeling,
|
|
139
|
+
"BaseModelOutputWithDeepstackFeatures", None)
|
|
140
|
+
|
|
141
|
+
pos_embeds = visual.fast_pos_embed_interpolate(grid_thw)
|
|
142
|
+
rotary = visual.rot_pos_emb(grid_thw)
|
|
143
|
+
rotary_pair = torch.cat((rotary, rotary), dim=-1)
|
|
144
|
+
position_embeddings = (rotary_pair.cos(), rotary_pair.sin())
|
|
145
|
+
fixed_cu_seqlens = torch.nn.functional.pad(
|
|
146
|
+
torch.repeat_interleave(grid_thw[:, 1] * grid_thw[:, 2],
|
|
147
|
+
grid_thw[:, 0]).cumsum(
|
|
148
|
+
dim=0, dtype=torch.int32), (1, 0), value=0)
|
|
149
|
+
vision_lengths = tuple(
|
|
150
|
+
int(v) for v in
|
|
151
|
+
(fixed_cu_seqlens[1:] - fixed_cu_seqlens[:-1]).cpu().tolist())
|
|
152
|
+
split_sizes = tuple(int(v) for v in (
|
|
153
|
+
grid_thw.prod(-1)
|
|
154
|
+
// visual.spatial_merge_size ** 2).cpu().tolist())
|
|
155
|
+
image_mask = (input_ids
|
|
156
|
+
== qwen.config.image_token_id).unsqueeze(-1)
|
|
157
|
+
video_mask = (input_ids
|
|
158
|
+
== qwen.config.video_token_id).unsqueeze(-1)
|
|
159
|
+
visual_indices = (input_ids.reshape(-1)
|
|
160
|
+
== qwen.config.image_token_id).nonzero().flatten()
|
|
161
|
+
try:
|
|
162
|
+
fixed_position_ids, fixed_rope_deltas = base.get_rope_index(
|
|
163
|
+
input_ids, grid_thw, None, attention_mask=attention_mask)
|
|
164
|
+
have_rope_index = True
|
|
165
|
+
except (TypeError, IndexError):
|
|
166
|
+
# newer transformers changed this helper's signature; hosts
|
|
167
|
+
# that inject position_ids never call it — nothing to pin
|
|
168
|
+
have_rope_index = False
|
|
169
|
+
|
|
170
|
+
glue_owner = _find_glue_owner(model, qwen)
|
|
171
|
+
glue_position_ids = None
|
|
172
|
+
if glue_owner is not None:
|
|
173
|
+
probe = {"input_ids": input_ids,
|
|
174
|
+
"attention_mask": attention_mask,
|
|
175
|
+
"image_grid_thw": grid_thw}
|
|
176
|
+
if hasattr(glue_owner, "_ensure_mm_token_type_ids"):
|
|
177
|
+
glue_owner._ensure_mm_token_type_ids(probe)
|
|
178
|
+
glue_owner._ensure_legacy_qwen3_position_ids(probe)
|
|
179
|
+
glue_position_ids = probe.get("position_ids")
|
|
180
|
+
|
|
181
|
+
# ---- pin ------------------------------------------------------
|
|
182
|
+
from transformers import masking_utils
|
|
183
|
+
|
|
184
|
+
saved = {
|
|
185
|
+
"visual_forward": visual.forward,
|
|
186
|
+
"attn_forwards": [b.attn.forward for b in visual.blocks],
|
|
187
|
+
"get_image_features": base.get_image_features,
|
|
188
|
+
"get_placeholder_mask": base.get_placeholder_mask,
|
|
189
|
+
"deepstack": base.language_model._deepstack_process,
|
|
190
|
+
"use_cache": qwen.config.text_config.use_cache,
|
|
191
|
+
"ignore_mask": masking_utils._ignore_causal_mask_sdpa,
|
|
192
|
+
}
|
|
193
|
+
if have_rope_index:
|
|
194
|
+
saved["get_rope_index"] = base.get_rope_index
|
|
195
|
+
if glue_owner is not None and glue_position_ids is not None:
|
|
196
|
+
saved["glue"] = glue_owner._ensure_legacy_qwen3_position_ids
|
|
197
|
+
|
|
198
|
+
def fixed_visual_forward(self, hidden_states, grid_thw=None,
|
|
199
|
+
**kwargs):
|
|
200
|
+
del grid_thw
|
|
201
|
+
kwargs.pop("return_dict", None)
|
|
202
|
+
hidden_states = self.patch_embed(hidden_states)
|
|
203
|
+
hidden_states = hidden_states + pos_embeds
|
|
204
|
+
seq, _ = hidden_states.size()
|
|
205
|
+
hidden_states = hidden_states.reshape(seq, -1)
|
|
206
|
+
deepstack_features = []
|
|
207
|
+
for index, block in enumerate(self.blocks):
|
|
208
|
+
hidden_states = block(
|
|
209
|
+
hidden_states, cu_seqlens=fixed_cu_seqlens,
|
|
210
|
+
position_embeddings=position_embeddings, **kwargs)
|
|
211
|
+
if index in self.deepstack_visual_indexes:
|
|
212
|
+
merger = self.deepstack_visual_indexes.index(index)
|
|
213
|
+
deepstack_features.append(
|
|
214
|
+
self.deepstack_merger_list[merger](hidden_states))
|
|
215
|
+
merged = self.merger(hidden_states)
|
|
216
|
+
if output_cls is not None:
|
|
217
|
+
return output_cls(last_hidden_state=hidden_states,
|
|
218
|
+
pooler_output=merged,
|
|
219
|
+
deepstack_features=deepstack_features)
|
|
220
|
+
return merged, deepstack_features
|
|
221
|
+
|
|
222
|
+
def fixed_vision_attention(self, hidden_states, cu_seqlens=None,
|
|
223
|
+
rotary_pos_emb=None,
|
|
224
|
+
position_embeddings_arg=None, **kwargs):
|
|
225
|
+
del cu_seqlens, rotary_pos_emb, position_embeddings_arg
|
|
226
|
+
seq = hidden_states.shape[0]
|
|
227
|
+
query, key, value = (self.qkv(hidden_states)
|
|
228
|
+
.reshape(seq, 3, self.num_heads, -1)
|
|
229
|
+
.permute(1, 0, 2, 3).unbind(0))
|
|
230
|
+
cos_t, sin_t = position_embeddings
|
|
231
|
+
query, key = modeling.apply_rotary_pos_emb_vision(
|
|
232
|
+
query, key, cos_t, sin_t)
|
|
233
|
+
query = query.transpose(0, 1).unsqueeze(0)
|
|
234
|
+
key = key.transpose(0, 1).unsqueeze(0)
|
|
235
|
+
value = value.transpose(0, 1).unsqueeze(0)
|
|
236
|
+
interface = modeling.eager_attention_forward
|
|
237
|
+
if self.config._attn_implementation != "eager":
|
|
238
|
+
interface = modeling.ALL_ATTENTION_FUNCTIONS[
|
|
239
|
+
self.config._attn_implementation]
|
|
240
|
+
chunks = (torch.split(t, vision_lengths, dim=2)
|
|
241
|
+
for t in (query, key, value))
|
|
242
|
+
outputs = [interface(self, q, k, v, attention_mask=None,
|
|
243
|
+
scaling=self.scaling, dropout=0.0,
|
|
244
|
+
is_causal=False, **kwargs)[0]
|
|
245
|
+
for q, k, v in zip(*chunks)]
|
|
246
|
+
return self.proj(torch.cat(outputs, dim=1)
|
|
247
|
+
.reshape(seq, -1).contiguous())
|
|
248
|
+
|
|
249
|
+
def fixed_get_image_features(self, pixel_values,
|
|
250
|
+
image_grid_thw=None, **kwargs):
|
|
251
|
+
del kwargs
|
|
252
|
+
pixel_values = pixel_values.type(self.visual.dtype)
|
|
253
|
+
vision_output = self.visual(pixel_values,
|
|
254
|
+
grid_thw=image_grid_thw)
|
|
255
|
+
if output_cls is not None:
|
|
256
|
+
vision_output.pooler_output = torch.split(
|
|
257
|
+
vision_output.pooler_output, split_sizes)
|
|
258
|
+
return vision_output
|
|
259
|
+
embeds, deepstack = vision_output
|
|
260
|
+
return torch.split(embeds, split_sizes), deepstack
|
|
261
|
+
|
|
262
|
+
def fixed_get_placeholder_mask(self, input_ids_arg, inputs_embeds,
|
|
263
|
+
image_features=None,
|
|
264
|
+
video_features=None):
|
|
265
|
+
del self, input_ids_arg, image_features, video_features
|
|
266
|
+
return (image_mask.expand_as(inputs_embeds),
|
|
267
|
+
video_mask.expand_as(inputs_embeds))
|
|
268
|
+
|
|
269
|
+
def fixed_deepstack(self, hidden_states, visual_pos_masks,
|
|
270
|
+
visual_embeds):
|
|
271
|
+
del self, visual_pos_masks
|
|
272
|
+
b, s, c = hidden_states.shape
|
|
273
|
+
flat = hidden_states.reshape(b * s, c)
|
|
274
|
+
updated = flat.index_select(0, visual_indices) + visual_embeds
|
|
275
|
+
return flat.index_copy(0, visual_indices,
|
|
276
|
+
updated).reshape(b, s, c)
|
|
277
|
+
|
|
278
|
+
visual.forward = types.MethodType(fixed_visual_forward, visual)
|
|
279
|
+
for block in visual.blocks:
|
|
280
|
+
block.attn.forward = types.MethodType(fixed_vision_attention,
|
|
281
|
+
block.attn)
|
|
282
|
+
base.get_image_features = types.MethodType(
|
|
283
|
+
fixed_get_image_features, base)
|
|
284
|
+
base.get_placeholder_mask = types.MethodType(
|
|
285
|
+
fixed_get_placeholder_mask, base)
|
|
286
|
+
base.language_model._deepstack_process = types.MethodType(
|
|
287
|
+
fixed_deepstack, base.language_model)
|
|
288
|
+
qwen.config.text_config.use_cache = False
|
|
289
|
+
masking_utils._ignore_causal_mask_sdpa = lambda *a, **k: True
|
|
290
|
+
if have_rope_index:
|
|
291
|
+
base.get_rope_index = types.MethodType(
|
|
292
|
+
lambda self, *a, **k: (fixed_position_ids,
|
|
293
|
+
fixed_rope_deltas), base)
|
|
294
|
+
if "glue" in saved:
|
|
295
|
+
def pinned_glue(model_input, _pids=glue_position_ids):
|
|
296
|
+
model_input["position_ids"] = _pids
|
|
297
|
+
glue_owner._ensure_legacy_qwen3_position_ids = pinned_glue
|
|
298
|
+
|
|
299
|
+
pins = ["visual_forward", "vision_attention",
|
|
300
|
+
"get_image_features", "get_placeholder_mask",
|
|
301
|
+
"deepstack", "use_cache", "causal_mask_decision"]
|
|
302
|
+
if have_rope_index:
|
|
303
|
+
pins.append("get_rope_index")
|
|
304
|
+
if "glue" in saved:
|
|
305
|
+
pins.append("legacy_position_ids_glue")
|
|
306
|
+
|
|
307
|
+
# ---- dead-output pin, held only under proof ------------------
|
|
308
|
+
# A feature-extraction pipeline never reads the vocabulary
|
|
309
|
+
# logits, but whether a compiler can prove that depends on the
|
|
310
|
+
# host's wrapper form — one host's graph dead-code-eliminates
|
|
311
|
+
# the [tokens, vocab] projection, another's keeps it alive and
|
|
312
|
+
# pays real milliseconds for it every call. The family settles
|
|
313
|
+
# it with a receipt instead: skip the head, re-run the recorded
|
|
314
|
+
# request, and keep the pin only if the pipeline output is
|
|
315
|
+
# bit-identical. Any mismatch or error restores the head alone.
|
|
316
|
+
lm_head = getattr(qwen, "lm_head", None)
|
|
317
|
+
if lm_head is not None and hasattr(lm_head, "forward"):
|
|
318
|
+
with torch.inference_mode():
|
|
319
|
+
before = _first_output_tensor(runner())
|
|
320
|
+
saved_head = lm_head.forward
|
|
321
|
+
|
|
322
|
+
def skipped_head(self, hidden_states, *args, **kwargs):
|
|
323
|
+
del args, kwargs
|
|
324
|
+
return hidden_states[..., :0]
|
|
325
|
+
|
|
326
|
+
lm_head.forward = types.MethodType(skipped_head, lm_head)
|
|
327
|
+
proven = False
|
|
328
|
+
if before is not None:
|
|
329
|
+
try:
|
|
330
|
+
with torch.inference_mode():
|
|
331
|
+
after = _first_output_tensor(runner())
|
|
332
|
+
proven = after is not None and torch.equal(before,
|
|
333
|
+
after)
|
|
334
|
+
except Exception: # noqa: BLE001 — proof failed
|
|
335
|
+
proven = False
|
|
336
|
+
if proven:
|
|
337
|
+
saved["lm_head"] = saved_head
|
|
338
|
+
pins.append("lm_head_dead_output")
|
|
339
|
+
else:
|
|
340
|
+
lm_head.forward = saved_head
|
|
341
|
+
|
|
342
|
+
def undo():
|
|
343
|
+
visual.forward = saved["visual_forward"]
|
|
344
|
+
for block, fwd in zip(visual.blocks, saved["attn_forwards"]):
|
|
345
|
+
block.attn.forward = fwd
|
|
346
|
+
base.get_image_features = saved["get_image_features"]
|
|
347
|
+
base.get_placeholder_mask = saved["get_placeholder_mask"]
|
|
348
|
+
base.language_model._deepstack_process = saved["deepstack"]
|
|
349
|
+
qwen.config.text_config.use_cache = saved["use_cache"]
|
|
350
|
+
masking_utils._ignore_causal_mask_sdpa = saved["ignore_mask"]
|
|
351
|
+
if "get_rope_index" in saved:
|
|
352
|
+
base.get_rope_index = saved["get_rope_index"]
|
|
353
|
+
if "glue" in saved:
|
|
354
|
+
glue_owner._ensure_legacy_qwen3_position_ids = saved["glue"]
|
|
355
|
+
if "lm_head" in saved:
|
|
356
|
+
qwen.lm_head.forward = saved["lm_head"]
|
|
357
|
+
|
|
358
|
+
return GraphLowering(
|
|
359
|
+
undo=undo, family=self.family, pins=tuple(pins),
|
|
360
|
+
details={"tokens": int(input_ids.shape[-1]),
|
|
361
|
+
"vision_patches": int(sum(vision_lengths)),
|
|
362
|
+
"vision_contract": ("output_class"
|
|
363
|
+
if output_cls is not None
|
|
364
|
+
else "tuple")})
|
|
File without changes
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""FP8-static implementation of the ``linear_proj`` structure.
|
|
2
|
+
|
|
3
|
+
A projection has more than one executable form, and which one applies is
|
|
4
|
+
a property of the host's own weights rather than a tuning knob:
|
|
5
|
+
|
|
6
|
+
- ``bias``: ``bf16_fp8_linear_bias_bf16`` — fused input quantization,
|
|
7
|
+
FP8 weights, BF16 bias and output. Three kernels per call.
|
|
8
|
+
- ``no_bias``: the quantize as its own kernel followed by
|
|
9
|
+
``fp8_gemm_bf16``. Two kernels. Hosts whose projections carry no bias
|
|
10
|
+
(the whole Gemma/Llama/Qwen attention family) would otherwise get a
|
|
11
|
+
zero bias built for them and pay a kernel to add it.
|
|
12
|
+
- ``fp8_in``: ``fp8_gemm_bf16`` straight, for a seam whose producer
|
|
13
|
+
already emits FP8. One kernel, no quantization at all.
|
|
14
|
+
|
|
15
|
+
Each form is qualified by its own work band, because a band expresses
|
|
16
|
+
what that form is amortizing and the forms do not carry the same cost.
|
|
17
|
+
Measured against the host's own BF16 Linear at real shapes on RTX 5090
|
|
18
|
+
(work = M*N*K):
|
|
19
|
+
|
|
20
|
+
form band evidence
|
|
21
|
+
bias [2e8, inf) 1.05e8 ties the host (6.68 vs 6.66 us);
|
|
22
|
+
3.0e9 wins 2.5x (26.0 vs 66.6) — the
|
|
23
|
+
fused quantize needs a large GEMM to
|
|
24
|
+
disappear behind
|
|
25
|
+
no_bias [2e7, 1e9] 2.6e7 wins 0.67us, 1.3e7 loses 0.25us
|
|
26
|
+
(one quantize launch to amortize); above
|
|
27
|
+
the band it *loses* to the bias form even
|
|
28
|
+
with no bias to add — 3.0e9: 26.21 vs
|
|
29
|
+
26.04 — because the fused entry bundles
|
|
30
|
+
its quantize instead of launching one
|
|
31
|
+
fp8_in [0, inf) nothing to amortize; the producer paid
|
|
32
|
+
|
|
33
|
+
Hence a large projection with no bias still takes the bias form and gets
|
|
34
|
+
a zero bias built for it: that is the measured faster path at that size,
|
|
35
|
+
not an oversight. The rule is which form the measurements put this shape
|
|
36
|
+
in, not which parts the host happens to have.
|
|
37
|
+
|
|
38
|
+
Outside every band the projection is refused and the refusal names the
|
|
39
|
+
form, so "refused" never reads as "this projection cannot be bound" —
|
|
40
|
+
only as "not in that form, at this size".
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
from functools import lru_cache
|
|
46
|
+
from typing import Mapping, Sequence
|
|
47
|
+
|
|
48
|
+
import torch
|
|
49
|
+
|
|
50
|
+
from ...guard import CAST_OK, FP8_ONLY, PROCEED, GuardedSeam
|
|
51
|
+
|
|
52
|
+
KERNEL_DEP = {
|
|
53
|
+
"provider": "hf",
|
|
54
|
+
"repo": "flashrt/flashrt-fp8-ffn",
|
|
55
|
+
"version": ">=1",
|
|
56
|
+
}
|
|
57
|
+
QUANT_DEP = {
|
|
58
|
+
"provider": "hf",
|
|
59
|
+
"repo": "flashrt/flashrt-gemm-epilogues",
|
|
60
|
+
"version": ">=1",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_FP8 = torch.float8_e4m3fn
|
|
64
|
+
_FP8_MAX = 448.0
|
|
65
|
+
|
|
66
|
+
# per-form work bands (M*N*K), each measured against the host's own
|
|
67
|
+
# BF16 Linear at real shapes — see the module docstring
|
|
68
|
+
_BAND = {"bias": (2.0e8, float("inf")),
|
|
69
|
+
"no_bias": (2.0e7, 1.0e9),
|
|
70
|
+
"fp8_in": (0.0, float("inf"))}
|
|
71
|
+
|
|
72
|
+
SUPPORT = {
|
|
73
|
+
"work_band": _BAND,
|
|
74
|
+
"K": {"min": 512, "max": 16384},
|
|
75
|
+
"N": {"min": 128, "max": 16384},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@lru_cache(maxsize=1)
|
|
80
|
+
def _kernel():
|
|
81
|
+
from flashrt_structures.impls import hub_kernel
|
|
82
|
+
|
|
83
|
+
return hub_kernel(KERNEL_DEP["repo"], KERNEL_DEP["version"])
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@lru_cache(maxsize=1)
|
|
87
|
+
def _quant_kernel():
|
|
88
|
+
"""The standalone quantize the no-bias form needs, or None.
|
|
89
|
+
|
|
90
|
+
Its absence is not an error: the form simply does not qualify and
|
|
91
|
+
the projection falls back to the bias form's floor.
|
|
92
|
+
"""
|
|
93
|
+
from flashrt_structures.impls import hub_kernel
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
return hub_kernel(QUANT_DEP["repo"], QUANT_DEP["version"])
|
|
97
|
+
except Exception: # noqa: BLE001
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _amax_scale(t: torch.Tensor) -> torch.Tensor:
|
|
102
|
+
return (t.float().abs().max() / _FP8_MAX).clamp(min=1e-8)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class FusedLinearProj(GuardedSeam, torch.nn.Module):
|
|
106
|
+
"""Drop-in replacement for one nn.Linear projection.
|
|
107
|
+
|
|
108
|
+
``original`` is retained whole and attribute lookups fall through to
|
|
109
|
+
it, so host code that introspects ``weight``/``bias``/``in_features``
|
|
110
|
+
keeps working — and a call outside the calibrated form runs it.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
_frt_host_attr = "host_linear"
|
|
114
|
+
_frt_can_fallback = True
|
|
115
|
+
|
|
116
|
+
def __init__(self, w_fp8, bias, input_scale, weight_scale,
|
|
117
|
+
original: torch.nn.Module | None = None,
|
|
118
|
+
form: str = "bias"):
|
|
119
|
+
super().__init__()
|
|
120
|
+
self._w_fp8 = w_fp8
|
|
121
|
+
self._bias = bias
|
|
122
|
+
self._input_scale = input_scale
|
|
123
|
+
self._weight_scale = weight_scale
|
|
124
|
+
self._bufs: dict[int, tuple[torch.Tensor, torch.Tensor]] = {}
|
|
125
|
+
self.form = form
|
|
126
|
+
# resolve the op at bind time: calling the hub loader inside
|
|
127
|
+
# forward makes dynamo trace through kernels.get_kernel's
|
|
128
|
+
# version resolution (network + inspect.Signature) — 26 graph
|
|
129
|
+
# breaks that fragment the surrounding compiled region
|
|
130
|
+
kf = _kernel()
|
|
131
|
+
self._gemm = kf.fp8_gemm_bf16
|
|
132
|
+
self._fn = kf.bf16_fp8_linear_bias_bf16
|
|
133
|
+
if form == "no_bias":
|
|
134
|
+
ke = _quant_kernel()
|
|
135
|
+
self._quantize = ke.channel_scale_quantize_fp8_static_bf16
|
|
136
|
+
# the standalone quantize is a per-channel one; a flat
|
|
137
|
+
# per-tensor scale is the identity channel vector. Held like
|
|
138
|
+
# the other tensors here, as a plain attribute.
|
|
139
|
+
self._chan = torch.ones(w_fp8.shape[1], device=w_fp8.device,
|
|
140
|
+
dtype=torch.bfloat16)
|
|
141
|
+
if original is not None:
|
|
142
|
+
self.host_linear = original
|
|
143
|
+
self._frt_arm(
|
|
144
|
+
dtypes=FP8_ONLY if form == "fp8_in" else CAST_OK,
|
|
145
|
+
device=w_fp8.device, k=int(w_fp8.shape[1]))
|
|
146
|
+
|
|
147
|
+
def __getattr__(self, name):
|
|
148
|
+
try:
|
|
149
|
+
return super().__getattr__(name)
|
|
150
|
+
except AttributeError:
|
|
151
|
+
if name == "host_linear":
|
|
152
|
+
raise
|
|
153
|
+
return getattr(super().__getattr__("host_linear"), name)
|
|
154
|
+
|
|
155
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
156
|
+
admitted = self._frt_admit(x)
|
|
157
|
+
if admitted is not PROCEED:
|
|
158
|
+
return admitted
|
|
159
|
+
shape = x.shape
|
|
160
|
+
flat = x.reshape(-1, shape[-1])
|
|
161
|
+
m = flat.shape[0]
|
|
162
|
+
if torch.compiler.is_compiling():
|
|
163
|
+
# traced regions run the ops functionally: a persistent
|
|
164
|
+
# out-buffer is module state mutated by the op, which
|
|
165
|
+
# functionalization cannot rewrite (the whole-graph export
|
|
166
|
+
# hit exactly that), and inside a compiled graph the
|
|
167
|
+
# allocation is planned away regardless
|
|
168
|
+
x_fp8 = out = None
|
|
169
|
+
else:
|
|
170
|
+
bufs = self._bufs.get(m)
|
|
171
|
+
if bufs is None:
|
|
172
|
+
bufs = (torch.empty(m, self._w_fp8.shape[1],
|
|
173
|
+
device=x.device, dtype=_FP8),
|
|
174
|
+
torch.empty(m, self._w_fp8.shape[0],
|
|
175
|
+
device=x.device,
|
|
176
|
+
dtype=torch.bfloat16))
|
|
177
|
+
self._bufs[m] = bufs
|
|
178
|
+
x_fp8, out = bufs
|
|
179
|
+
if self.form == "fp8_in":
|
|
180
|
+
y = self._gemm(flat, self._w_fp8, self._input_scale,
|
|
181
|
+
self._weight_scale, out=out)
|
|
182
|
+
return y.reshape(*shape[:-1], y.shape[-1])
|
|
183
|
+
flat = flat.to(torch.bfloat16).contiguous()
|
|
184
|
+
if self.form == "no_bias":
|
|
185
|
+
self._quantize(flat, self._chan, self._input_scale, out=x_fp8)
|
|
186
|
+
y = self._gemm(x_fp8, self._w_fp8, self._input_scale,
|
|
187
|
+
self._weight_scale, out=out)
|
|
188
|
+
else:
|
|
189
|
+
y = self._fn(flat, self._w_fp8, self._bias, self._input_scale,
|
|
190
|
+
self._weight_scale, input_fp8=x_fp8, out=out)
|
|
191
|
+
return y.reshape(*shape[:-1], y.shape[-1]).to(x.dtype)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _form_for(bias: torch.Tensor | None, in_dtype: str,
|
|
195
|
+
work: float) -> str:
|
|
196
|
+
"""Which form this projection takes, from what it is *and* its size.
|
|
197
|
+
|
|
198
|
+
Two inputs, not one. What the host has decides which forms are
|
|
199
|
+
available: a projection fed FP8 has no input to quantize, one with a
|
|
200
|
+
real bias must add it. Size decides which of the available forms is
|
|
201
|
+
actually faster — the no-bias form trades a bundled quantize for a
|
|
202
|
+
separate launch, which is a win only while the GEMM is short enough
|
|
203
|
+
for a launch to matter. Above its band a projection with no bias is
|
|
204
|
+
better off in the bias form with a zero bias, and that is measured,
|
|
205
|
+
not conceded.
|
|
206
|
+
"""
|
|
207
|
+
if in_dtype == "fp8_static":
|
|
208
|
+
return "fp8_in"
|
|
209
|
+
has_bias = bias is not None and bool(bias.any())
|
|
210
|
+
lo, hi = _BAND["no_bias"]
|
|
211
|
+
if not has_bias and _quant_kernel() is not None and work <= hi:
|
|
212
|
+
return "no_bias"
|
|
213
|
+
return "bias"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@torch.no_grad()
|
|
217
|
+
def bind_proj_seam(
|
|
218
|
+
weights: Mapping[str, torch.Tensor],
|
|
219
|
+
*,
|
|
220
|
+
input_scale: float,
|
|
221
|
+
row_profile: Sequence[int],
|
|
222
|
+
original: torch.nn.Module | None = None,
|
|
223
|
+
in_dtype: str = "bf16",
|
|
224
|
+
) -> FusedLinearProj:
|
|
225
|
+
"""Bind one projection: ``weights['w']`` is checkpoint-layout [N, K].
|
|
226
|
+
|
|
227
|
+
``input_scale`` is the calibrated per-tensor FP8 scale at this
|
|
228
|
+
projection's input. ``row_profile`` is the row counts that input
|
|
229
|
+
arrived with across calibration — a shape observation, not a
|
|
230
|
+
statistic, and the median of it is what the work-based form
|
|
231
|
+
qualification is measured against.
|
|
232
|
+
"""
|
|
233
|
+
if not row_profile:
|
|
234
|
+
raise ValueError("row_profile must be non-empty")
|
|
235
|
+
w = weights["w"]
|
|
236
|
+
n, k = w.shape
|
|
237
|
+
for name, dim in (("K", k), ("N", n)):
|
|
238
|
+
lo, hi = SUPPORT[name]["min"], SUPPORT[name]["max"]
|
|
239
|
+
if not lo <= dim <= hi:
|
|
240
|
+
raise ValueError(f"{name}={dim} outside support envelope")
|
|
241
|
+
raw_bias = weights.get("b")
|
|
242
|
+
ms = sorted(int(m) for m in row_profile)
|
|
243
|
+
m_med = ms[len(ms) // 2]
|
|
244
|
+
work = float(m_med) * n * k
|
|
245
|
+
form = _form_for(raw_bias, in_dtype, work)
|
|
246
|
+
lo, _ = _BAND[form]
|
|
247
|
+
if work < lo:
|
|
248
|
+
raise ValueError(
|
|
249
|
+
f"projection work {m_med}x{n}x{k} below the {form} form's "
|
|
250
|
+
f"band ({lo:.0e}) — host keeps its Linear. Bands are per "
|
|
251
|
+
"form: the same projection may qualify in another one")
|
|
252
|
+
if not w.is_cuda:
|
|
253
|
+
raise ValueError("fp8_static requires CUDA-resident weights")
|
|
254
|
+
|
|
255
|
+
device = w.device
|
|
256
|
+
w_scale = _amax_scale(w)
|
|
257
|
+
w_fp8 = (w.float() / w_scale).clamp(-_FP8_MAX, _FP8_MAX).to(_FP8)
|
|
258
|
+
scale = torch.tensor(float(input_scale), device=device)
|
|
259
|
+
bias = raw_bias
|
|
260
|
+
if bias is None:
|
|
261
|
+
bias = torch.zeros(n, device=device, dtype=torch.bfloat16)
|
|
262
|
+
else:
|
|
263
|
+
bias = bias.detach().to(torch.bfloat16)
|
|
264
|
+
bound = FusedLinearProj(w_fp8, bias, scale.view(1),
|
|
265
|
+
w_scale.view(1), original=original, form=form)
|
|
266
|
+
for m in set(ms): # pre-allocate per calibrated M: keeps the hot
|
|
267
|
+
bound._bufs[m] = ( # path allocation-free (graph/compile safe)
|
|
268
|
+
torch.empty(m, k, device=device, dtype=_FP8),
|
|
269
|
+
torch.empty(m, n, device=device, dtype=torch.bfloat16))
|
|
270
|
+
return bound
|