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,264 @@
|
|
|
1
|
+
"""Fixed-iteration lowering for the semantic OpenPI action schedule.
|
|
2
|
+
|
|
3
|
+
The matcher deliberately does not import OpenPI or name one of its concrete
|
|
4
|
+
classes. It recognizes the boundary that the host exposes: observation
|
|
5
|
+
preprocessing, prefix embedding/cache construction, an iterative denoise
|
|
6
|
+
step, and the paired PaliGemma/expert container. This covers the OpenPI
|
|
7
|
+
PyTorch PI action family while an unrelated module is correctly ignored.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import functools
|
|
13
|
+
import inspect
|
|
14
|
+
from typing import Any, Callable
|
|
15
|
+
|
|
16
|
+
import torch
|
|
17
|
+
|
|
18
|
+
from .protocol import FixedIterationLowering, FixedIterationRefused
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _unwrap(call: Callable[..., Any]) -> Callable[..., Any]:
|
|
22
|
+
"""Unwrap ``torch.compile`` while preserving an already-bound method."""
|
|
23
|
+
wrapped = getattr(call, "__wrapped__", call)
|
|
24
|
+
owner = getattr(call, "__self__", None)
|
|
25
|
+
if owner is not None and inspect.isfunction(wrapped):
|
|
26
|
+
return wrapped.__get__(owner, type(owner))
|
|
27
|
+
return wrapped
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _semantic_model(candidate: Any) -> bool:
|
|
31
|
+
required_calls = (
|
|
32
|
+
"_preprocess_observation",
|
|
33
|
+
"_prepare_attention_masks_4d",
|
|
34
|
+
"embed_prefix",
|
|
35
|
+
"denoise_step",
|
|
36
|
+
"sample_actions",
|
|
37
|
+
"sample_noise",
|
|
38
|
+
)
|
|
39
|
+
if not isinstance(candidate, torch.nn.Module):
|
|
40
|
+
return False
|
|
41
|
+
if not all(callable(getattr(candidate, name, None))
|
|
42
|
+
for name in required_calls):
|
|
43
|
+
return False
|
|
44
|
+
config = getattr(candidate, "config", None)
|
|
45
|
+
if not all(hasattr(config, name)
|
|
46
|
+
for name in ("action_horizon", "action_dim")):
|
|
47
|
+
return False
|
|
48
|
+
pair = getattr(candidate, "paligemma_with_expert", None)
|
|
49
|
+
pali = getattr(pair, "paligemma", None)
|
|
50
|
+
expert = getattr(pair, "gemma_expert", None)
|
|
51
|
+
if not callable(getattr(pair, "forward", None)):
|
|
52
|
+
return False
|
|
53
|
+
if getattr(pali, "language_model", None) is None:
|
|
54
|
+
return False
|
|
55
|
+
if getattr(expert, "model", None) is None:
|
|
56
|
+
return False
|
|
57
|
+
try:
|
|
58
|
+
names = tuple(inspect.signature(
|
|
59
|
+
_unwrap(candidate.sample_actions)).parameters)
|
|
60
|
+
except (TypeError, ValueError):
|
|
61
|
+
return False
|
|
62
|
+
return names[:2] == ("device", "observation") \
|
|
63
|
+
and "noise" in names and "num_steps" in names
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _partial_invocation(
|
|
67
|
+
forward: Callable[[], Any],
|
|
68
|
+
) -> tuple[Any, Callable[..., Any], tuple[Any, ...], dict[str, Any]] | None:
|
|
69
|
+
if not isinstance(forward, functools.partial):
|
|
70
|
+
return None
|
|
71
|
+
call = forward.func
|
|
72
|
+
model = getattr(call, "__self__", None)
|
|
73
|
+
if not _semantic_model(model):
|
|
74
|
+
return None
|
|
75
|
+
return model, _unwrap(call), tuple(forward.args), dict(forward.keywords or {})
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _record_invocation(
|
|
79
|
+
forward: Callable[[], Any],
|
|
80
|
+
model: Any,
|
|
81
|
+
) -> tuple[Callable[..., Any], tuple[Any, ...], dict[str, Any], Any]:
|
|
82
|
+
"""Record one call through ``model.sample_actions``, then restore it."""
|
|
83
|
+
original = model.sample_actions
|
|
84
|
+
raw = _unwrap(original)
|
|
85
|
+
calls: list[tuple[tuple[Any, ...], dict[str, Any]]] = []
|
|
86
|
+
|
|
87
|
+
def record(*args: Any, **kwargs: Any) -> Any:
|
|
88
|
+
calls.append((tuple(args), dict(kwargs)))
|
|
89
|
+
return raw(*args, **kwargs)
|
|
90
|
+
|
|
91
|
+
owned = "sample_actions" in vars(model)
|
|
92
|
+
try:
|
|
93
|
+
model.sample_actions = record
|
|
94
|
+
with torch.no_grad():
|
|
95
|
+
reference = forward()
|
|
96
|
+
finally:
|
|
97
|
+
if owned:
|
|
98
|
+
model.sample_actions = original
|
|
99
|
+
else:
|
|
100
|
+
delattr(model, "sample_actions")
|
|
101
|
+
if len(calls) != 1:
|
|
102
|
+
raise FixedIterationRefused(
|
|
103
|
+
"fixed_iter: the host forward must call the recognized action "
|
|
104
|
+
f"schedule exactly once, observed {len(calls)} call(s)")
|
|
105
|
+
args, kwargs = calls[0]
|
|
106
|
+
return raw, args, kwargs, reference
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _bind_arguments(
|
|
110
|
+
call: Callable[..., Any],
|
|
111
|
+
args: tuple[Any, ...],
|
|
112
|
+
kwargs: dict[str, Any],
|
|
113
|
+
) -> inspect.BoundArguments:
|
|
114
|
+
try:
|
|
115
|
+
signature = inspect.signature(call)
|
|
116
|
+
bound = signature.bind(*args, **kwargs)
|
|
117
|
+
bound.apply_defaults()
|
|
118
|
+
return bound
|
|
119
|
+
except (TypeError, ValueError) as exc:
|
|
120
|
+
raise FixedIterationRefused(
|
|
121
|
+
f"fixed_iter: cannot bind the host schedule arguments: {exc}") \
|
|
122
|
+
from exc
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _attention_masks(pad_masks: torch.Tensor,
|
|
126
|
+
att_masks: torch.Tensor) -> torch.Tensor:
|
|
127
|
+
"""OpenPI's prefix-mask dataflow, expressed without a host import."""
|
|
128
|
+
cumsum = torch.cumsum(att_masks, dim=1)
|
|
129
|
+
causal = cumsum[:, None, :] <= cumsum[:, :, None]
|
|
130
|
+
valid = pad_masks[:, None, :] * pad_masks[:, :, None]
|
|
131
|
+
return causal & valid
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _fixed_forward(
|
|
135
|
+
model: Any,
|
|
136
|
+
*,
|
|
137
|
+
device: Any,
|
|
138
|
+
observation: Any,
|
|
139
|
+
noise: torch.Tensor,
|
|
140
|
+
steps: int,
|
|
141
|
+
) -> torch.Tensor:
|
|
142
|
+
"""The host schedule with only its fixed loop spelling normalized."""
|
|
143
|
+
batch = observation.state.shape[0]
|
|
144
|
+
images, image_masks, tokens, token_masks, state = \
|
|
145
|
+
model._preprocess_observation(observation, train=False)
|
|
146
|
+
prefix, prefix_pad, prefix_ar = model.embed_prefix(
|
|
147
|
+
images, image_masks, tokens, token_masks)
|
|
148
|
+
prefix_mask = _attention_masks(prefix_pad, prefix_ar)
|
|
149
|
+
prefix_positions = torch.cumsum(prefix_pad, dim=1) - 1
|
|
150
|
+
prefix_mask_4d = model._prepare_attention_masks_4d(prefix_mask)
|
|
151
|
+
|
|
152
|
+
model.paligemma_with_expert.paligemma.language_model.config \
|
|
153
|
+
._attn_implementation = "eager"
|
|
154
|
+
_, prefix_kv = model.paligemma_with_expert.forward(
|
|
155
|
+
attention_mask=prefix_mask_4d,
|
|
156
|
+
position_ids=prefix_positions,
|
|
157
|
+
past_key_values=None,
|
|
158
|
+
inputs_embeds=[prefix, None],
|
|
159
|
+
use_cache=True,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# device-native creation: torch.tensor(scalar, device=cuda) stages
|
|
163
|
+
# on the CPU and copies, which a capturing stream refuses the
|
|
164
|
+
# moment a seat's graph break makes these lines run eager
|
|
165
|
+
dt = torch.full((), -1.0 / steps, dtype=torch.float32,
|
|
166
|
+
device=device)
|
|
167
|
+
value = noise
|
|
168
|
+
timestep = torch.ones((), dtype=torch.float32, device=device)
|
|
169
|
+
for _ in range(steps):
|
|
170
|
+
velocity = model.denoise_step(
|
|
171
|
+
state,
|
|
172
|
+
prefix_pad,
|
|
173
|
+
prefix_kv,
|
|
174
|
+
value,
|
|
175
|
+
timestep.expand(batch),
|
|
176
|
+
)
|
|
177
|
+
value = value + dt * velocity
|
|
178
|
+
timestep += dt
|
|
179
|
+
return value
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _observation_windows(observation: Any) -> dict[str, torch.Tensor]:
|
|
183
|
+
"""Expose every tensor the fixed schedule reads between replays."""
|
|
184
|
+
windows: dict[str, torch.Tensor] = {}
|
|
185
|
+
for attr in (
|
|
186
|
+
"images",
|
|
187
|
+
"image_masks",
|
|
188
|
+
"state",
|
|
189
|
+
"tokenized_prompt",
|
|
190
|
+
"tokenized_prompt_mask",
|
|
191
|
+
"token_ar_mask",
|
|
192
|
+
"token_loss_mask",
|
|
193
|
+
):
|
|
194
|
+
value = getattr(observation, attr, None)
|
|
195
|
+
if torch.is_tensor(value):
|
|
196
|
+
windows[f"observation.{attr}"] = value
|
|
197
|
+
elif isinstance(value, dict):
|
|
198
|
+
for key, tensor in sorted(value.items()):
|
|
199
|
+
if torch.is_tensor(tensor):
|
|
200
|
+
windows[f"observation.{attr}.{key}"] = tensor
|
|
201
|
+
return windows
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class OpenPIFixedIterationAdapter:
|
|
205
|
+
"""Normalize a fixed OpenPI action loop without modifying the host."""
|
|
206
|
+
|
|
207
|
+
family = "cond_iter_pipeline.openpi_action"
|
|
208
|
+
|
|
209
|
+
def lower(
|
|
210
|
+
self,
|
|
211
|
+
forward: Callable[[], Any],
|
|
212
|
+
model: Any | None,
|
|
213
|
+
) -> FixedIterationLowering | None:
|
|
214
|
+
invocation = _partial_invocation(forward)
|
|
215
|
+
if invocation is not None:
|
|
216
|
+
found, raw, args, kwargs = invocation
|
|
217
|
+
with torch.no_grad():
|
|
218
|
+
reference = raw(*args, **kwargs)
|
|
219
|
+
model = found
|
|
220
|
+
elif _semantic_model(model):
|
|
221
|
+
raw, args, kwargs, reference = _record_invocation(forward, model)
|
|
222
|
+
else:
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
bound = _bind_arguments(raw, args, kwargs)
|
|
226
|
+
device = bound.arguments["device"]
|
|
227
|
+
observation = bound.arguments["observation"]
|
|
228
|
+
noise = bound.arguments.get("noise")
|
|
229
|
+
steps = bound.arguments.get("num_steps")
|
|
230
|
+
if noise is None:
|
|
231
|
+
raise FixedIterationRefused(
|
|
232
|
+
"fixed_iter: noise must be explicit so capture exposes a "
|
|
233
|
+
"replayable SWAP window instead of freezing in-graph RNG")
|
|
234
|
+
if not isinstance(noise, torch.Tensor):
|
|
235
|
+
raise FixedIterationRefused(
|
|
236
|
+
"fixed_iter: explicit noise must be a torch.Tensor")
|
|
237
|
+
if isinstance(steps, bool) or not isinstance(steps, int) or steps <= 0:
|
|
238
|
+
raise FixedIterationRefused(
|
|
239
|
+
f"fixed_iter: num_steps must be one positive fixed int, got "
|
|
240
|
+
f"{steps!r}")
|
|
241
|
+
|
|
242
|
+
def lowered() -> torch.Tensor:
|
|
243
|
+
with torch.no_grad():
|
|
244
|
+
return _fixed_forward(
|
|
245
|
+
model,
|
|
246
|
+
device=device,
|
|
247
|
+
observation=observation,
|
|
248
|
+
noise=noise,
|
|
249
|
+
steps=steps,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return FixedIterationLowering(
|
|
253
|
+
forward=lowered,
|
|
254
|
+
reference_output=reference,
|
|
255
|
+
family=self.family,
|
|
256
|
+
steps=steps,
|
|
257
|
+
exact=True,
|
|
258
|
+
windows={"noise": noise, **_observation_windows(observation)},
|
|
259
|
+
details={
|
|
260
|
+
"source_form": "tensor_controlled_while",
|
|
261
|
+
"canonical_form": "fixed_for",
|
|
262
|
+
"noise_window": "explicit",
|
|
263
|
+
},
|
|
264
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Host-family adapter protocol for fixed iterative schedules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Callable, Mapping, Protocol
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FixedIterationRefused(RuntimeError):
|
|
12
|
+
"""A candidate schedule was recognized but cannot be lowered safely."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class FixedIterationLowering:
|
|
17
|
+
"""A graph-safe callable plus the reference output that qualified it."""
|
|
18
|
+
|
|
19
|
+
forward: Callable[[], Any]
|
|
20
|
+
reference_output: Any
|
|
21
|
+
family: str
|
|
22
|
+
steps: int
|
|
23
|
+
exact: bool = True
|
|
24
|
+
compile_before_capture: bool = True
|
|
25
|
+
windows: Mapping[str, torch.Tensor] = field(default_factory=dict)
|
|
26
|
+
details: Mapping[str, Any] = field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FixedIterationAdapter(Protocol):
|
|
30
|
+
"""One host-family realization of the fixed-iteration contract."""
|
|
31
|
+
|
|
32
|
+
def lower(
|
|
33
|
+
self,
|
|
34
|
+
forward: Callable[[], Any],
|
|
35
|
+
model: Any | None,
|
|
36
|
+
) -> FixedIterationLowering | None: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_ADAPTERS: list[FixedIterationAdapter] = []
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def register_fixed_iteration_adapter(adapter: FixedIterationAdapter) -> None:
|
|
43
|
+
"""Register a host-family schedule adapter (last registration runs last)."""
|
|
44
|
+
_ADAPTERS.append(adapter)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def normalize_fixed_iteration(
|
|
48
|
+
forward: Callable[[], Any],
|
|
49
|
+
model: Any | None = None,
|
|
50
|
+
) -> FixedIterationLowering | None:
|
|
51
|
+
"""Return a qualified lowering, or ``None`` for an ordinary callable.
|
|
52
|
+
|
|
53
|
+
``None`` is not a fallback: it means no adapter recognized the callable,
|
|
54
|
+
so the existing capture path receives it unchanged. Once an adapter
|
|
55
|
+
recognizes its family, an unsafe form raises :class:`FixedIterationRefused`
|
|
56
|
+
rather than silently returning to the original graph-unsafe loop.
|
|
57
|
+
"""
|
|
58
|
+
for adapter in _ADAPTERS:
|
|
59
|
+
lowering = adapter.lower(forward, model)
|
|
60
|
+
if lowering is not None:
|
|
61
|
+
if lowering.exact:
|
|
62
|
+
want = _first_tensor(lowering.reference_output)
|
|
63
|
+
with torch.no_grad():
|
|
64
|
+
got = _first_tensor(lowering.forward())
|
|
65
|
+
if want is None or got is None:
|
|
66
|
+
raise FixedIterationRefused(
|
|
67
|
+
f"{lowering.family}: exact schedule qualification "
|
|
68
|
+
"needs one tensor output")
|
|
69
|
+
if not torch.equal(got, want):
|
|
70
|
+
raise FixedIterationRefused(
|
|
71
|
+
f"{lowering.family}: fixed-iteration lowering is "
|
|
72
|
+
"not bit-exact")
|
|
73
|
+
return lowering
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _first_tensor(value: Any) -> torch.Tensor | None:
|
|
78
|
+
if torch.is_tensor(value):
|
|
79
|
+
return value
|
|
80
|
+
logits = getattr(value, "logits", None)
|
|
81
|
+
if torch.is_tensor(logits):
|
|
82
|
+
return logits
|
|
83
|
+
if isinstance(value, Mapping):
|
|
84
|
+
for key in sorted(value):
|
|
85
|
+
found = _first_tensor(value[key])
|
|
86
|
+
if found is not None:
|
|
87
|
+
return found
|
|
88
|
+
return None
|
|
89
|
+
if isinstance(value, (tuple, list)):
|
|
90
|
+
for item in value:
|
|
91
|
+
found = _first_tensor(item)
|
|
92
|
+
if found is not None:
|
|
93
|
+
return found
|
|
94
|
+
return None
|