flashrt-structures 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. flashrt_structures/__init__.py +174 -0
  2. flashrt_structures/adapters/__init__.py +55 -0
  3. flashrt_structures/adapters/diffusers_attention.py +237 -0
  4. flashrt_structures/adapters/diffusers_rotary_attention.py +239 -0
  5. flashrt_structures/adapters/factored_qk_norm_rope.py +252 -0
  6. flashrt_structures/adapters/factored_two_way_attention.py +99 -0
  7. flashrt_structures/adapters/gemma_attention.py +226 -0
  8. flashrt_structures/adapters/packed_qkv_rope.py +342 -0
  9. flashrt_structures/adapters/packed_stream_qk_norm_rope.py +376 -0
  10. flashrt_structures/adapters/qwen_per_head_qk_norm_rope.py +231 -0
  11. flashrt_structures/adapters/sglang_engine.py +207 -0
  12. flashrt_structures/adapters/transformers_attention_interface.py +73 -0
  13. flashrt_structures/adapters/transformers_gated_delta.py +152 -0
  14. flashrt_structures/adapters/transformers_gated_delta_fused.py +96 -0
  15. flashrt_structures/adapters/vllm_engine.py +424 -0
  16. flashrt_structures/adjudicate.py +85 -0
  17. flashrt_structures/aot.py +191 -0
  18. flashrt_structures/autobuild.py +2052 -0
  19. flashrt_structures/beta/__init__.py +43 -0
  20. flashrt_structures/beta/conform.py +94 -0
  21. flashrt_structures/beta/joins.py +113 -0
  22. flashrt_structures/beta/negotiate.py +84 -0
  23. flashrt_structures/beta/ports.py +140 -0
  24. flashrt_structures/decisions.py +80 -0
  25. flashrt_structures/discover.py +623 -0
  26. flashrt_structures/explain.py +70 -0
  27. flashrt_structures/frontdoor.py +572 -0
  28. flashrt_structures/gates.py +465 -0
  29. flashrt_structures/guard.py +421 -0
  30. flashrt_structures/handle.py +189 -0
  31. flashrt_structures/impls/__init__.py +219 -0
  32. flashrt_structures/impls/adaln_producer/__init__.py +8 -0
  33. flashrt_structures/impls/adaln_producer/broker.py +116 -0
  34. flashrt_structures/impls/adaln_producer/fused.py +388 -0
  35. flashrt_structures/impls/adarms_stack/__init__.py +8 -0
  36. flashrt_structures/impls/adarms_stack/fp8_chain.py +832 -0
  37. flashrt_structures/impls/adarms_stack/region.py +102 -0
  38. flashrt_structures/impls/attention_core/__init__.py +132 -0
  39. flashrt_structures/impls/attention_core/fa2_seqused.py +458 -0
  40. flashrt_structures/impls/attention_core/fa4_cute.py +154 -0
  41. flashrt_structures/impls/attention_core/fa4_fp8.py +178 -0
  42. flashrt_structures/impls/attention_core/masked_mha.py +158 -0
  43. flashrt_structures/impls/attention_core/two_way_fa2.py +220 -0
  44. flashrt_structures/impls/cadence_static/__init__.py +18 -0
  45. flashrt_structures/impls/cadence_static/buffers.py +122 -0
  46. flashrt_structures/impls/cadence_static/cross_attention.py +187 -0
  47. flashrt_structures/impls/chain_elements.py +89 -0
  48. flashrt_structures/impls/decode_loop/__init__.py +0 -0
  49. flashrt_structures/impls/decode_loop/fp8_kv.py +206 -0
  50. flashrt_structures/impls/decode_loop/mtp_speculative.py +245 -0
  51. flashrt_structures/impls/decode_loop/whole_step.py +852 -0
  52. flashrt_structures/impls/decoder_block/__init__.py +6 -0
  53. flashrt_structures/impls/decoder_block/attn_sublayer.py +110 -0
  54. flashrt_structures/impls/decoder_block/fused.py +167 -0
  55. flashrt_structures/impls/decoder_ffn/__init__.py +0 -0
  56. flashrt_structures/impls/decoder_ffn/fp8_static.py +310 -0
  57. flashrt_structures/impls/decoder_ffn/fp8_static.yaml +22 -0
  58. flashrt_structures/impls/decoder_ffn/w4a16_static.py +221 -0
  59. flashrt_structures/impls/decoder_ffn/w8a16_static.py +183 -0
  60. flashrt_structures/impls/dit_stack/__init__.py +11 -0
  61. flashrt_structures/impls/dit_stack/fp4_chain.py +417 -0
  62. flashrt_structures/impls/dit_stack/region.py +86 -0
  63. flashrt_structures/impls/fixed_iter/__init__.py +29 -0
  64. flashrt_structures/impls/fixed_iter/openpi.py +264 -0
  65. flashrt_structures/impls/fixed_iter/protocol.py +94 -0
  66. flashrt_structures/impls/gated_delta_core/__init__.py +3 -0
  67. flashrt_structures/impls/gated_delta_core/fused_layer.py +545 -0
  68. flashrt_structures/impls/gated_delta_core/hub_v3.py +152 -0
  69. flashrt_structures/impls/graph_lowering/__init__.py +27 -0
  70. flashrt_structures/impls/graph_lowering/pi052_denoise.py +179 -0
  71. flashrt_structures/impls/graph_lowering/protocol.py +76 -0
  72. flashrt_structures/impls/graph_lowering/qwen3_vl.py +364 -0
  73. flashrt_structures/impls/linear_proj/__init__.py +0 -0
  74. flashrt_structures/impls/linear_proj/fp8_static.py +270 -0
  75. flashrt_structures/impls/linear_proj/nvfp4_balance.py +131 -0
  76. flashrt_structures/impls/linear_proj/nvfp4_dynamic.py +182 -0
  77. flashrt_structures/impls/linear_proj/w8a16_static.py +230 -0
  78. flashrt_structures/impls/modnorm_qkv_chain/__init__.py +0 -0
  79. flashrt_structures/impls/modnorm_qkv_chain/fp8_ptok_table.py +291 -0
  80. flashrt_structures/impls/moe_experts/__init__.py +9 -0
  81. flashrt_structures/impls/moe_experts/nvfp4_dynamic.py +208 -0
  82. flashrt_structures/impls/moe_experts/nvfp4_w4a16.py +129 -0
  83. flashrt_structures/impls/norm_fused/__init__.py +3 -0
  84. flashrt_structures/impls/norm_fused/bf16.py +94 -0
  85. flashrt_structures/impls/norm_fused/fp8_producer.py +84 -0
  86. flashrt_structures/impls/patch_projection/__init__.py +3 -0
  87. flashrt_structures/impls/patch_projection/bf16_flat.py +139 -0
  88. flashrt_structures/impls/prefill_tower/__init__.py +10 -0
  89. flashrt_structures/impls/prefill_tower/fp8_chain.py +955 -0
  90. flashrt_structures/impls/prefill_tower/region.py +99 -0
  91. flashrt_structures/impls/qk_norm_rope/__init__.py +12 -0
  92. flashrt_structures/impls/qk_norm_rope/per_head_gqa.py +199 -0
  93. flashrt_structures/impls/qk_norm_rope/projection_bf16.py +165 -0
  94. flashrt_structures/impls/qkv_pack/__init__.py +5 -0
  95. flashrt_structures/impls/qkv_pack/bf16.py +110 -0
  96. flashrt_structures/impls/qkv_pack/fp8_static.py +435 -0
  97. flashrt_structures/impls/qkv_pack/nvfp4_balance.py +218 -0
  98. flashrt_structures/impls/qkv_rope/__init__.py +3 -0
  99. flashrt_structures/impls/qkv_rope/packed_bias_bf16.py +143 -0
  100. flashrt_structures/impls/step_table.py +113 -0
  101. flashrt_structures/impls/vision_ffn/__init__.py +0 -0
  102. flashrt_structures/impls/vision_ffn/fp8_static.py +261 -0
  103. flashrt_structures/impls/vision_ffn/nvfp4_balance.py +211 -0
  104. flashrt_structures/impls/vision_tower/__init__.py +7 -0
  105. flashrt_structures/impls/vision_tower/fp8_chain.py +533 -0
  106. flashrt_structures/impls/vision_tower/region.py +91 -0
  107. flashrt_structures/matrix.py +126 -0
  108. flashrt_structures/points.py +368 -0
  109. flashrt_structures/prequantized.py +131 -0
  110. flashrt_structures/quantize_on_adopt.py +94 -0
  111. flashrt_structures/recipe.py +438 -0
  112. flashrt_structures/regions.py +208 -0
  113. flashrt_structures/schemes.py +490 -0
  114. flashrt_structures/stages.py +298 -0
  115. flashrt_structures/storage.py +255 -0
  116. flashrt_structures/swap.py +398 -0
  117. flashrt_structures/workspace.py +110 -0
  118. flashrt_structures-0.2.0.dist-info/METADATA +140 -0
  119. flashrt_structures-0.2.0.dist-info/RECORD +122 -0
  120. flashrt_structures-0.2.0.dist-info/WHEEL +5 -0
  121. flashrt_structures-0.2.0.dist-info/licenses/LICENSE +202 -0
  122. flashrt_structures-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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