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,152 @@
|
|
|
1
|
+
"""Hub v3 executable forms for ``gated_delta_core``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
from ...guard import PROCEED, GuardedSeam
|
|
8
|
+
from .. import hub_kernel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HubV3GatedDeltaCore(GuardedSeam, torch.nn.Module):
|
|
12
|
+
"""Single-token H=32/48, D=128 recurrence with explicit state output.
|
|
13
|
+
|
|
14
|
+
The log-decay ``g`` binds in the dtype the host actually exposes:
|
|
15
|
+
BF16 through the original entry, FP32 through the ``gf32`` twin —
|
|
16
|
+
the 27B-class cached-decode hosts keep ``g`` in FP32, and rounding
|
|
17
|
+
it through BF16 (or casting in the hot path) is what qualification
|
|
18
|
+
used to refuse here.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, sample: torch.Tensor,
|
|
22
|
+
g_dtype: torch.dtype = torch.bfloat16,
|
|
23
|
+
state_dtype: torch.dtype = torch.bfloat16):
|
|
24
|
+
super().__init__()
|
|
25
|
+
if sample.dtype != torch.bfloat16:
|
|
26
|
+
raise ValueError("gated_delta_core v3 requires BF16 Q/K/V")
|
|
27
|
+
if g_dtype not in (torch.bfloat16, torch.float32):
|
|
28
|
+
raise ValueError(
|
|
29
|
+
"gated_delta_core v3 serves BF16 or FP32 log-decay only")
|
|
30
|
+
if state_dtype not in (torch.bfloat16, torch.float32):
|
|
31
|
+
raise ValueError(
|
|
32
|
+
"gated_delta_core v3 serves BF16 or FP32 state only")
|
|
33
|
+
if state_dtype is torch.float32 and g_dtype is not torch.float32:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
"gated_delta_core v3 has no BF16-g/FP32-state entry; no "
|
|
36
|
+
"host has exposed that combination")
|
|
37
|
+
self._g_dtype = g_dtype
|
|
38
|
+
self._state_dtype = state_dtype
|
|
39
|
+
if sample.ndim != 4 or sample.shape[0] != 1 \
|
|
40
|
+
or sample.shape[1] != 1 \
|
|
41
|
+
or sample.shape[2] not in (32, 48) \
|
|
42
|
+
or sample.shape[3] != 128:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
"gated_delta_core v3 requires Q shape "
|
|
45
|
+
"(1,1,H,128) with H=32 or H=48; the published "
|
|
46
|
+
"sequence API has no explicit state output")
|
|
47
|
+
if not sample.is_contiguous():
|
|
48
|
+
raise ValueError("gated_delta_core v3 requires contiguous Q/K/V")
|
|
49
|
+
self.heads = int(sample.shape[2])
|
|
50
|
+
self._ops = hub_kernel("flashrt/gated-delta-attention", ">=3")
|
|
51
|
+
if g_dtype is torch.float32:
|
|
52
|
+
name = ("gated_delta_recurrent_inout_gf32_sf32_bf16"
|
|
53
|
+
if state_dtype is torch.float32
|
|
54
|
+
else "gated_delta_recurrent_inout_gf32_bf16")
|
|
55
|
+
step = getattr(self._ops, name, None)
|
|
56
|
+
if step is None:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"refused: the installed gated-delta-attention build "
|
|
59
|
+
f"predates the {name} entry; a release carrying it "
|
|
60
|
+
"is required")
|
|
61
|
+
else:
|
|
62
|
+
step = self._ops.gated_delta_recurrent_inout_bf16
|
|
63
|
+
self._step = step
|
|
64
|
+
self.register_buffer(
|
|
65
|
+
"_state_out",
|
|
66
|
+
torch.empty(
|
|
67
|
+
1, self.heads, 128, 128,
|
|
68
|
+
device=sample.device, dtype=self._state_dtype),
|
|
69
|
+
persistent=False,
|
|
70
|
+
)
|
|
71
|
+
self.register_buffer(
|
|
72
|
+
"_out",
|
|
73
|
+
torch.empty(
|
|
74
|
+
1, self.heads, 128,
|
|
75
|
+
device=sample.device, dtype=torch.bfloat16),
|
|
76
|
+
persistent=False,
|
|
77
|
+
)
|
|
78
|
+
self._frt_arm(
|
|
79
|
+
dtypes=(torch.bfloat16,), device=sample.device, k=128,
|
|
80
|
+
rows=self.heads)
|
|
81
|
+
|
|
82
|
+
def forward(
|
|
83
|
+
self,
|
|
84
|
+
query: torch.Tensor,
|
|
85
|
+
key: torch.Tensor,
|
|
86
|
+
value: torch.Tensor,
|
|
87
|
+
log_decay: torch.Tensor,
|
|
88
|
+
beta: torch.Tensor,
|
|
89
|
+
state: torch.Tensor | None,
|
|
90
|
+
*,
|
|
91
|
+
output_final_state: bool,
|
|
92
|
+
use_qk_l2norm: bool,
|
|
93
|
+
) -> tuple[torch.Tensor, torch.Tensor | None]:
|
|
94
|
+
admitted = self._frt_admit(query)
|
|
95
|
+
if admitted is not PROCEED:
|
|
96
|
+
return admitted
|
|
97
|
+
if query.ndim != 4 or query.shape[0] != 1 \
|
|
98
|
+
or query.shape[1:] != (1, self.heads, 128):
|
|
99
|
+
raise ValueError(
|
|
100
|
+
"gated_delta_core v3 query shape moved after binding")
|
|
101
|
+
if key.shape != query.shape or value.shape != query.shape:
|
|
102
|
+
raise ValueError("gated_delta_core v3 Q/K/V shapes differ")
|
|
103
|
+
if not (query.is_contiguous() and key.is_contiguous()
|
|
104
|
+
and value.is_contiguous()):
|
|
105
|
+
raise ValueError("gated_delta_core v3 requires contiguous Q/K/V")
|
|
106
|
+
if log_decay.shape != query.shape[:3] \
|
|
107
|
+
or beta.shape != log_decay.shape:
|
|
108
|
+
raise ValueError("gated_delta_core v3 gating shapes differ")
|
|
109
|
+
if log_decay.dtype != self._g_dtype \
|
|
110
|
+
or beta.dtype != torch.bfloat16:
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f"gated_delta_core v3 bound {self._g_dtype} log-decay "
|
|
113
|
+
"and BF16 beta; the host's dtypes moved after binding")
|
|
114
|
+
if state is None or state.shape != (1, self.heads, 128, 128):
|
|
115
|
+
raise ValueError("gated_delta_core v3 state shape differs")
|
|
116
|
+
if state.dtype != self._state_dtype or not state.is_contiguous():
|
|
117
|
+
raise ValueError(
|
|
118
|
+
f"gated_delta_core v3 bound contiguous {self._state_dtype} "
|
|
119
|
+
"state; the host's state moved after binding")
|
|
120
|
+
# One custom op. The caller's state is read-only and the final state is
|
|
121
|
+
# written into graph-stable storage for snapshot and rollback.
|
|
122
|
+
out, state_out = self._step(
|
|
123
|
+
query[:, 0], key[:, 0], value[:, 0],
|
|
124
|
+
log_decay[:, 0], beta[:, 0], state,
|
|
125
|
+
use_qk_l2norm=use_qk_l2norm,
|
|
126
|
+
state_out=self._state_out,
|
|
127
|
+
out=self._out,
|
|
128
|
+
)
|
|
129
|
+
return out[:, None], state_out if output_final_state else None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def bind_gated_delta_core(sample: dict[str, torch.Tensor]):
|
|
133
|
+
"""Bind v3 decode recurrence and launch the observed real sample once.
|
|
134
|
+
|
|
135
|
+
The entry is chosen by the observed sample's log-decay dtype — the
|
|
136
|
+
form the host actually calls with, not a preference."""
|
|
137
|
+
state = sample.get("state")
|
|
138
|
+
core = HubV3GatedDeltaCore(
|
|
139
|
+
sample["query"], g_dtype=sample["g"].dtype,
|
|
140
|
+
state_dtype=(state.dtype if state is not None
|
|
141
|
+
else torch.bfloat16))
|
|
142
|
+
with torch.no_grad():
|
|
143
|
+
core(
|
|
144
|
+
sample["query"], sample["key"], sample["value"],
|
|
145
|
+
sample["g"], sample["beta"], sample.get("state"),
|
|
146
|
+
output_final_state=bool(sample.get("output_final_state", True)),
|
|
147
|
+
use_qk_l2norm=bool(sample.get("use_qk_l2norm", True)),
|
|
148
|
+
)
|
|
149
|
+
guard = core._frt_guard
|
|
150
|
+
if guard is not None:
|
|
151
|
+
guard.calls = 0
|
|
152
|
+
return core
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Whole-graph shape-lowering adapters, one per host family.
|
|
2
|
+
|
|
3
|
+
``capture`` consults this registry when it is handed a model: every
|
|
4
|
+
adapter that recognizes the host pins that family's shape glue for the
|
|
5
|
+
fixed request, and hands back an ``undo``. A host no family recognizes
|
|
6
|
+
is captured as-is — correct for hosts that are already graph-safe.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .protocol import (GraphLowering, GraphLoweringRefused,
|
|
10
|
+
lower_for_capture,
|
|
11
|
+
register_graph_lowering_adapter)
|
|
12
|
+
from .qwen3_vl import Qwen3VLGraphLoweringAdapter
|
|
13
|
+
|
|
14
|
+
# Built-ins register at import time; they recognize by capability, not
|
|
15
|
+
# by class name or version string.
|
|
16
|
+
register_graph_lowering_adapter(Qwen3VLGraphLoweringAdapter())
|
|
17
|
+
|
|
18
|
+
from .pi052_denoise import Pi05DenoiseGraphLoweringAdapter # noqa: E402
|
|
19
|
+
|
|
20
|
+
register_graph_lowering_adapter(Pi05DenoiseGraphLoweringAdapter())
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"GraphLowering",
|
|
24
|
+
"GraphLoweringRefused",
|
|
25
|
+
"lower_for_capture",
|
|
26
|
+
"register_graph_lowering_adapter",
|
|
27
|
+
]
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Pi0.5 flow-matching family: the timestep schedule becomes resident.
|
|
2
|
+
|
|
3
|
+
The host builds its denoise schedule inside ``sample_actions`` as
|
|
4
|
+
``torch.tensor([...python floats...], device=cuda)`` — one
|
|
5
|
+
host-to-device copy per call. Whether that line survives capture has
|
|
6
|
+
depended on the compiler's mood: a dynamo that covers the whole method
|
|
7
|
+
bakes it into the graph, a dependency upgrade that adds a graph break
|
|
8
|
+
in front of it drops the copy into the capture stream and the capture
|
|
9
|
+
refuses (measured: the same host line passed on 2026-07-25 and
|
|
10
|
+
refused after a transformers upgrade landed the next day). A pin must
|
|
11
|
+
not gamble on coverage.
|
|
12
|
+
|
|
13
|
+
The pin scopes one rule around the host's own method: a
|
|
14
|
+
``torch.tensor`` call that constructs a *constant float list* on a
|
|
15
|
+
device resolves to a cached resident tensor — same values, same
|
|
16
|
+
device, same dtype, allocated once outside capture. Everything else
|
|
17
|
+
passes straight through, the schedule stays value-identical by
|
|
18
|
+
construction, and the undo restores the host method bit-for-bit.
|
|
19
|
+
Scalar constructors use device-native fills instead of a CPU staging copy;
|
|
20
|
+
unlike list schedules, mutable scalars are not cached between calls.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import types
|
|
26
|
+
|
|
27
|
+
import torch
|
|
28
|
+
|
|
29
|
+
from .protocol import GraphLowering
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _looks_like_pi05_flow(module) -> bool:
|
|
33
|
+
return (callable(getattr(module, "sample_actions", None))
|
|
34
|
+
and callable(getattr(module, "denoise_step", None))
|
|
35
|
+
and callable(getattr(module, "embed_suffix", None))
|
|
36
|
+
and hasattr(module, "paligemma_with_expert"))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Pi05DenoiseGraphLoweringAdapter:
|
|
40
|
+
"""Family: pi05_denoise — one pin, the resident step schedule."""
|
|
41
|
+
|
|
42
|
+
def lower(self, model, forward) -> GraphLowering | None:
|
|
43
|
+
target = None
|
|
44
|
+
if _looks_like_pi05_flow(model):
|
|
45
|
+
target = model
|
|
46
|
+
else:
|
|
47
|
+
for _name, mod in getattr(
|
|
48
|
+
model, "named_modules", lambda: ())():
|
|
49
|
+
if _looks_like_pi05_flow(mod):
|
|
50
|
+
target = mod
|
|
51
|
+
break
|
|
52
|
+
if target is None and _looks_like_pi05_flow(
|
|
53
|
+
getattr(model, "model", None)):
|
|
54
|
+
target = model.model
|
|
55
|
+
if target is None:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
cache: dict[tuple, torch.Tensor] = {}
|
|
59
|
+
real_tensor = torch.tensor
|
|
60
|
+
host_fn = target.sample_actions
|
|
61
|
+
had_instance = "sample_actions" in target.__dict__
|
|
62
|
+
|
|
63
|
+
def caching_tensor(data, *args, **kwargs):
|
|
64
|
+
device = kwargs.get("device")
|
|
65
|
+
if (device is not None and not args
|
|
66
|
+
and isinstance(data, (float, int, bool))):
|
|
67
|
+
return torch.full((), data, **kwargs)
|
|
68
|
+
if (device is not None and isinstance(data, (list, tuple))
|
|
69
|
+
and data
|
|
70
|
+
and all(isinstance(x, (float, int, bool))
|
|
71
|
+
for x in data)):
|
|
72
|
+
key = (tuple(data), str(device),
|
|
73
|
+
str(kwargs.get("dtype")))
|
|
74
|
+
hit = cache.get(key)
|
|
75
|
+
if hit is None:
|
|
76
|
+
hit = real_tensor(data, *args, **kwargs)
|
|
77
|
+
cache[key] = hit
|
|
78
|
+
return hit
|
|
79
|
+
return real_tensor(data, *args, **kwargs)
|
|
80
|
+
|
|
81
|
+
real_setitem = torch.Tensor.__setitem__
|
|
82
|
+
|
|
83
|
+
def filling_setitem(t, idx, val):
|
|
84
|
+
# a python-scalar write into a CUDA tensor is a
|
|
85
|
+
# host-to-device copy the capture stream refuses; the same
|
|
86
|
+
# store as an immediate-value fill is graph-legal and
|
|
87
|
+
# bit-identical
|
|
88
|
+
if (t.is_cuda and isinstance(idx, int)
|
|
89
|
+
and isinstance(val, (int, float, bool))):
|
|
90
|
+
t.narrow(0, idx, 1).fill_(val)
|
|
91
|
+
return
|
|
92
|
+
real_setitem(t, idx, val)
|
|
93
|
+
|
|
94
|
+
def pinned(self, *args, **kwargs):
|
|
95
|
+
torch.tensor = caching_tensor
|
|
96
|
+
torch.Tensor.__setitem__ = filling_setitem
|
|
97
|
+
try:
|
|
98
|
+
return host_fn(*args, **kwargs)
|
|
99
|
+
finally:
|
|
100
|
+
torch.tensor = real_tensor
|
|
101
|
+
torch.Tensor.__setitem__ = real_setitem
|
|
102
|
+
|
|
103
|
+
target.sample_actions = types.MethodType(pinned, target)
|
|
104
|
+
|
|
105
|
+
# ---- pin 3: the pixel-patch embedding stack rides the band ----
|
|
106
|
+
# The host keeps norms/embeddings in fp32 as a training-fidelity
|
|
107
|
+
# choice; in the captured serving form every consumer of the
|
|
108
|
+
# patch embeds casts to bf16 at its own entry, so the fp32 conv
|
|
109
|
+
# pair is pure spend. Structural match only (a full-patch
|
|
110
|
+
# Conv2d — kernel == stride — beside a position Embedding),
|
|
111
|
+
# weights carried down in place with the originals retained,
|
|
112
|
+
# and the arm's end-to-end parity gate stays the judge.
|
|
113
|
+
embed_saved: list = []
|
|
114
|
+
embed_hooks: list = []
|
|
115
|
+
for _n, mod in getattr(model, "named_modules", lambda: ())():
|
|
116
|
+
pe = getattr(mod, "patch_embedding", None)
|
|
117
|
+
pos = getattr(mod, "position_embedding", None)
|
|
118
|
+
if not (isinstance(pe, torch.nn.Conv2d)
|
|
119
|
+
and isinstance(pos, torch.nn.Embedding)):
|
|
120
|
+
continue
|
|
121
|
+
if tuple(pe.kernel_size) != tuple(pe.stride):
|
|
122
|
+
continue
|
|
123
|
+
f32 = [p for p in mod.parameters()
|
|
124
|
+
if p.dtype == torch.float32]
|
|
125
|
+
if not f32:
|
|
126
|
+
continue
|
|
127
|
+
for p in f32:
|
|
128
|
+
embed_saved.append((p, p.data))
|
|
129
|
+
p.data = p.data.to(torch.bfloat16)
|
|
130
|
+
embed_hooks.append(pe.register_forward_pre_hook(
|
|
131
|
+
lambda _m, args: (args[0].to(torch.bfloat16),)
|
|
132
|
+
+ tuple(args[1:])))
|
|
133
|
+
# dtype-transparent at the module boundary: every
|
|
134
|
+
# downstream consumer keeps seeing the dtype the host
|
|
135
|
+
# chose; only the patch projection itself rides the band
|
|
136
|
+
embed_hooks.append(mod.register_forward_hook(
|
|
137
|
+
lambda _m, _a, out: out.to(torch.float32)
|
|
138
|
+
if isinstance(out, torch.Tensor) else out))
|
|
139
|
+
# ---- pin 4: fp32 host linears ride the band, transparently --
|
|
140
|
+
# The same fidelity policy leaves a handful of glue linears
|
|
141
|
+
# (modality projector, time/action MLPs) in fp32, which on this
|
|
142
|
+
# class of device means simt kernels with no tensor cores. Each
|
|
143
|
+
# one is carried down in place with both boundaries cast back,
|
|
144
|
+
# so every consumer and producer keeps its dtype contract and
|
|
145
|
+
# the parity gate judges the whole move.
|
|
146
|
+
for _n, mod in getattr(model, "named_modules", lambda: ())():
|
|
147
|
+
if not isinstance(mod, torch.nn.Linear):
|
|
148
|
+
continue
|
|
149
|
+
if mod.weight.dtype is not torch.float32:
|
|
150
|
+
continue
|
|
151
|
+
embed_saved.append((mod.weight, mod.weight.data))
|
|
152
|
+
mod.weight.data = mod.weight.data.to(torch.bfloat16)
|
|
153
|
+
if mod.bias is not None:
|
|
154
|
+
embed_saved.append((mod.bias, mod.bias.data))
|
|
155
|
+
mod.bias.data = mod.bias.data.to(torch.bfloat16)
|
|
156
|
+
embed_hooks.append(mod.register_forward_pre_hook(
|
|
157
|
+
lambda _m, args: (args[0].to(torch.bfloat16),)
|
|
158
|
+
+ tuple(args[1:])))
|
|
159
|
+
embed_hooks.append(mod.register_forward_hook(
|
|
160
|
+
lambda _m, _a, out: out.to(torch.float32)))
|
|
161
|
+
pins = ["resident_step_schedule", "scalar_setitem_fill"]
|
|
162
|
+
if embed_saved:
|
|
163
|
+
pins.append("patch_embed_band")
|
|
164
|
+
|
|
165
|
+
def undo() -> None:
|
|
166
|
+
torch.tensor = real_tensor
|
|
167
|
+
if had_instance:
|
|
168
|
+
target.sample_actions = host_fn
|
|
169
|
+
elif "sample_actions" in target.__dict__:
|
|
170
|
+
del target.sample_actions
|
|
171
|
+
for hook in embed_hooks:
|
|
172
|
+
hook.remove()
|
|
173
|
+
for p, data in embed_saved:
|
|
174
|
+
p.data = data
|
|
175
|
+
|
|
176
|
+
return GraphLowering(
|
|
177
|
+
undo=undo, family="pi05_denoise",
|
|
178
|
+
pins=tuple(pins),
|
|
179
|
+
details={"host": type(target).__name__})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Host-family adapter protocol for whole-graph shape lowering.
|
|
2
|
+
|
|
3
|
+
CUDA graph capture needs every shape-derived quantity of the request to
|
|
4
|
+
be a constant: position tables, token routing, sequence cumsums — the
|
|
5
|
+
things a host recomputes per call, often through a synchronize the
|
|
6
|
+
capture cannot record. Which functions those are is host-family
|
|
7
|
+
knowledge, exactly like where the attention math runs — so the lowering
|
|
8
|
+
lives in registered adapters, not in user harnesses. A user asks to
|
|
9
|
+
capture; the family that recognizes its host pins its own glue.
|
|
10
|
+
|
|
11
|
+
Every pin is a shape-derived constant of one fixed request, never a
|
|
12
|
+
value-dependent quantity, and every application returns an ``undo``
|
|
13
|
+
that restores the host bit-for-bit.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import Any, Callable, Mapping, Protocol
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class GraphLoweringRefused(RuntimeError):
|
|
23
|
+
"""A recognized host cannot be pinned safely."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class GraphLowering:
|
|
28
|
+
"""One applied family lowering and how to take it back off."""
|
|
29
|
+
|
|
30
|
+
undo: Callable[[], None]
|
|
31
|
+
family: str
|
|
32
|
+
pins: tuple[str, ...]
|
|
33
|
+
details: Mapping[str, Any] = field(default_factory=dict)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class GraphLoweringAdapter(Protocol):
|
|
37
|
+
"""One host-family realization of the lowering contract."""
|
|
38
|
+
|
|
39
|
+
def lower(
|
|
40
|
+
self,
|
|
41
|
+
model: Any,
|
|
42
|
+
forward: Callable[[], Any],
|
|
43
|
+
) -> GraphLowering | None: ...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_ADAPTERS: list[GraphLoweringAdapter] = []
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def register_graph_lowering_adapter(adapter: GraphLoweringAdapter) -> None:
|
|
50
|
+
"""Register a host-family lowering adapter."""
|
|
51
|
+
_ADAPTERS.append(adapter)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def lower_for_capture(
|
|
55
|
+
model: Any,
|
|
56
|
+
forward: Callable[[], Any],
|
|
57
|
+
) -> list[GraphLowering]:
|
|
58
|
+
"""Apply every registered lowering that recognizes this host.
|
|
59
|
+
|
|
60
|
+
An empty list is not a fallback: it means no family recognized the
|
|
61
|
+
model, and capture proceeds on the host's own forward — which is
|
|
62
|
+
correct for hosts that are already graph-safe. A recognized family
|
|
63
|
+
that cannot pin safely raises :class:`GraphLoweringRefused` instead
|
|
64
|
+
of leaving the host half-pinned; adapters must apply atomically.
|
|
65
|
+
"""
|
|
66
|
+
applied: list[GraphLowering] = []
|
|
67
|
+
try:
|
|
68
|
+
for adapter in _ADAPTERS:
|
|
69
|
+
lowering = adapter.lower(model, forward)
|
|
70
|
+
if lowering is not None:
|
|
71
|
+
applied.append(lowering)
|
|
72
|
+
except Exception:
|
|
73
|
+
for lowering in reversed(applied):
|
|
74
|
+
lowering.undo()
|
|
75
|
+
raise
|
|
76
|
+
return applied
|