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,219 @@
|
|
|
1
|
+
"""Structure implementations.
|
|
2
|
+
|
|
3
|
+
``hub_kernel`` is the shared, process-wide hub loader: two impls that
|
|
4
|
+
depend on the same kernel repo must share one loaded module — a second
|
|
5
|
+
``kernels.get_kernel`` import of the same repo re-registers its fake
|
|
6
|
+
ops and torch.library raises.
|
|
7
|
+
|
|
8
|
+
The loader also checks the package's own hardware declaration. A Hub
|
|
9
|
+
kernel package ships ``metadata.json`` with the CUDA archs it was built
|
|
10
|
+
for; that file is maintained on the kernels side and is the single
|
|
11
|
+
source of truth for hardware support — this layer reads it, it does not
|
|
12
|
+
keep a second table. A device outside the declared archs gets a clean
|
|
13
|
+
refusal here, before the kernel produces an unrelated-looking runtime
|
|
14
|
+
error; the refusal is caught by the binder and recorded in the plan
|
|
15
|
+
notes like any other. A package without metadata is loaded as before —
|
|
16
|
+
absence of a declaration is not evidence of incompatibility.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import json
|
|
21
|
+
import pathlib
|
|
22
|
+
import re
|
|
23
|
+
from functools import lru_cache
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _device_cc() -> tuple[int, int] | None:
|
|
27
|
+
"""Compute capability of the current CUDA device, or ``None``."""
|
|
28
|
+
import torch
|
|
29
|
+
|
|
30
|
+
if not torch.cuda.is_available():
|
|
31
|
+
return None
|
|
32
|
+
return torch.cuda.get_device_capability()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _declared_archs(module) -> list[str] | None:
|
|
36
|
+
"""The package's own ``backend.archs`` declaration, if it ships one."""
|
|
37
|
+
try:
|
|
38
|
+
meta = pathlib.Path(module.__file__).parent / "metadata.json"
|
|
39
|
+
if not meta.is_file():
|
|
40
|
+
return None
|
|
41
|
+
archs = json.loads(meta.read_text()).get("backend", {}).get("archs")
|
|
42
|
+
return list(archs) if archs else None
|
|
43
|
+
except (OSError, ValueError, AttributeError):
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
_CUDA_ARCH = re.compile(
|
|
48
|
+
r"^(?P<major>[0-9]+)\.(?P<minor>[0-9]+)"
|
|
49
|
+
r"(?P<specific>a)?(?P<ptx>\+PTX)?$")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _cuda_arch_supports_device(
|
|
53
|
+
arch: str, device_cc: tuple[int, int]) -> bool:
|
|
54
|
+
"""Whether one Hub CUDA arch declaration can execute on ``device_cc``.
|
|
55
|
+
|
|
56
|
+
Plain cubins are binary-compatible with later minor capabilities in
|
|
57
|
+
the same major family. Generic PTX is forward-compatible with any
|
|
58
|
+
greater compute capability. Architecture-specific ``a`` targets are
|
|
59
|
+
exact-only, including when they also carry PTX.
|
|
60
|
+
"""
|
|
61
|
+
match = _CUDA_ARCH.fullmatch(arch)
|
|
62
|
+
if match is None:
|
|
63
|
+
return False
|
|
64
|
+
target = (int(match["major"]), int(match["minor"]))
|
|
65
|
+
if match["specific"]:
|
|
66
|
+
return device_cc == target
|
|
67
|
+
if match["ptx"]:
|
|
68
|
+
return device_cc >= target
|
|
69
|
+
return device_cc[0] == target[0] and device_cc[1] >= target[1]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class KernelUnavailable(ValueError):
|
|
73
|
+
"""This host cannot supply this kernel package.
|
|
74
|
+
|
|
75
|
+
One exception type for every way the distribution layer can come up
|
|
76
|
+
empty — the repository is not published, not staged in an offline
|
|
77
|
+
cache, has no build variant for the host, or will not import here.
|
|
78
|
+
They differ only in what an operator has to go fix, which is what
|
|
79
|
+
the message carries; to a caller they are the same event, and the
|
|
80
|
+
same one the arch declaration produces: *not here*.
|
|
81
|
+
|
|
82
|
+
A ``ValueError`` subclass on purpose. Every layer that already
|
|
83
|
+
treats a refusal as an outcome to record rather than an error to
|
|
84
|
+
propagate — the variant families, the recipe engine's per-lever
|
|
85
|
+
build — catches ``ValueError``, and an absent package must not be
|
|
86
|
+
the one refusal that aborts a run instead of being written down.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
#: every package this process could not supply, in the order it was
|
|
91
|
+
#: asked for. Skipping an unavailable package keeps a run moving, which
|
|
92
|
+
#: is the right behaviour — but a package that is *broken* here and one
|
|
93
|
+
#: that was simply never shipped here both come out as "skipped", and
|
|
94
|
+
#: only the first is somebody's bug. So nothing is inferred and nothing
|
|
95
|
+
#: is dropped: the original failure is recorded verbatim and travels
|
|
96
|
+
#: into the receipt, where a reader can tell the two apart.
|
|
97
|
+
_UNAVAILABLE: list[dict] = []
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def unavailable_report() -> list[dict]:
|
|
101
|
+
"""Packages this process asked for and could not get."""
|
|
102
|
+
return [dict(row) for row in _UNAVAILABLE]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def clear_unavailable_report() -> None:
|
|
106
|
+
_UNAVAILABLE.clear()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _record_unavailable(repo: str, version: str, cause: BaseException):
|
|
110
|
+
row = {
|
|
111
|
+
"repo": repo,
|
|
112
|
+
"version": version,
|
|
113
|
+
"error": type(cause).__name__,
|
|
114
|
+
"detail": str(cause)[:400],
|
|
115
|
+
}
|
|
116
|
+
if not any(r["repo"] == repo and r["error"] == row["error"]
|
|
117
|
+
for r in _UNAVAILABLE):
|
|
118
|
+
_UNAVAILABLE.append(row)
|
|
119
|
+
return row
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _check_arch(repo: str, module) -> None:
|
|
123
|
+
archs = _declared_archs(module)
|
|
124
|
+
if archs is None:
|
|
125
|
+
return
|
|
126
|
+
cc = _device_cc()
|
|
127
|
+
if cc is None:
|
|
128
|
+
# no CUDA device: binding fails later at weight transfer anyway;
|
|
129
|
+
# the arch check has nothing truthful to say here
|
|
130
|
+
return
|
|
131
|
+
want = f"{cc[0]}.{cc[1]}"
|
|
132
|
+
if any(_cuda_arch_supports_device(a, cc) for a in archs):
|
|
133
|
+
return
|
|
134
|
+
refusal = KernelUnavailable(
|
|
135
|
+
f"refused: kernel package {repo!r} declares archs {archs}, "
|
|
136
|
+
f"device is sm {want}")
|
|
137
|
+
_record_unavailable(repo, "declared-archs", refusal)
|
|
138
|
+
raise refusal
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
#: modules cached independently of the arch check: ``get_kernel`` must
|
|
142
|
+
#: run at most once per repo even when the check refuses (a second load
|
|
143
|
+
#: re-registers the package's fake ops and torch.library raises — the
|
|
144
|
+
#: refusal path must not manufacture that error on retry)
|
|
145
|
+
_LOADED: dict[tuple[str, str], object] = {}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@lru_cache(maxsize=None)
|
|
149
|
+
def hub_kernel(repo: str, version: str):
|
|
150
|
+
try:
|
|
151
|
+
from kernels import get_kernel
|
|
152
|
+
except ImportError as absent:
|
|
153
|
+
# The client itself is missing or shadowed. This is the same
|
|
154
|
+
# event as every other way a package fails to arrive - "not
|
|
155
|
+
# here" - and it must travel as one, or the layers that catch a
|
|
156
|
+
# refusal to record it and keep going will instead abort on the
|
|
157
|
+
# one unavailability nobody declared. It is also the state a
|
|
158
|
+
# fresh ``pip install flash-rt`` is in, since the client is not
|
|
159
|
+
# a hard dependency, so the message says how to leave it.
|
|
160
|
+
_record_unavailable(repo, version, absent)
|
|
161
|
+
raise KernelUnavailable(
|
|
162
|
+
f"kernel package {repo!r} ({version}) is unavailable on this "
|
|
163
|
+
f"host: the kernel client is not installed "
|
|
164
|
+
f"({type(absent).__name__}: {absent}).\n"
|
|
165
|
+
f" pip install kernels\n"
|
|
166
|
+
f"or install this distribution with its hub extra:\n"
|
|
167
|
+
f" pip install 'flash-rt[hub]'") from absent
|
|
168
|
+
|
|
169
|
+
key = (repo, version)
|
|
170
|
+
if key not in _LOADED:
|
|
171
|
+
# author pin for artifact bisection: an exact hub revision
|
|
172
|
+
# outranks version resolution for this repo only. A perf or
|
|
173
|
+
# correctness drift that arrives with a rebuilt artifact is
|
|
174
|
+
# isolated by flipping one env var, not by editing caches.
|
|
175
|
+
rev = os.environ.get(
|
|
176
|
+
"FRT_KERNEL_REV_" + re.sub(r"[^A-Za-z0-9]", "_",
|
|
177
|
+
repo).upper())
|
|
178
|
+
try:
|
|
179
|
+
import inspect as _ins
|
|
180
|
+
_kw = {}
|
|
181
|
+
if "trust_remote_code" in _ins.signature(
|
|
182
|
+
get_kernel).parameters:
|
|
183
|
+
# the trust gate arrived with newer kernels; our own
|
|
184
|
+
# first-party artifacts are the explicit trust set
|
|
185
|
+
_kw["trust_remote_code"] = True
|
|
186
|
+
try:
|
|
187
|
+
try:
|
|
188
|
+
_LOADED[key] = (get_kernel(repo, revision=rev,
|
|
189
|
+
**_kw)
|
|
190
|
+
if rev
|
|
191
|
+
else get_kernel(repo,
|
|
192
|
+
version=version,
|
|
193
|
+
**_kw))
|
|
194
|
+
except ValueError as ve:
|
|
195
|
+
# newer kernels resolve an exact integer version
|
|
196
|
+
# where older ones accepted a range string; the
|
|
197
|
+
# range's floor is the same request in both bands
|
|
198
|
+
m = re.match(r"^\s*>=\s*v?(\d+)", str(version))
|
|
199
|
+
if not (m and "available versions" in str(ve)):
|
|
200
|
+
raise
|
|
201
|
+
_LOADED[key] = get_kernel(
|
|
202
|
+
repo, version=int(m.group(1)), **_kw)
|
|
203
|
+
except TypeError:
|
|
204
|
+
# kernels<0.13 — the band transformers pins — has no
|
|
205
|
+
# semver resolution kwarg; the default revision is
|
|
206
|
+
# exactly what that library resolved before semver
|
|
207
|
+
# tags existed. Widest-band compat: 0.12 through 0.16
|
|
208
|
+
# serve the same call site.
|
|
209
|
+
_LOADED[key] = (get_kernel(repo, revision=rev) if rev
|
|
210
|
+
else get_kernel(repo)) # pre-semver band
|
|
211
|
+
except (OSError, RuntimeError, ValueError) as unavailable:
|
|
212
|
+
_record_unavailable(repo, version, unavailable)
|
|
213
|
+
raise KernelUnavailable(
|
|
214
|
+
f"kernel package {repo!r} ({version}) is unavailable on "
|
|
215
|
+
f"this host: {type(unavailable).__name__}: "
|
|
216
|
+
f"{unavailable}") from unavailable
|
|
217
|
+
module = _LOADED[key]
|
|
218
|
+
_check_arch(repo, module)
|
|
219
|
+
return module
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from .broker import StyleBroker, bind_style_broker
|
|
2
|
+
from .fused import (AdaLNProducer, StepLocator, StyleTable,
|
|
3
|
+
bind_adaln_producer, bind_step_locator,
|
|
4
|
+
bind_style_table)
|
|
5
|
+
|
|
6
|
+
__all__ = ["AdaLNProducer", "StepLocator", "StyleBroker", "StyleTable",
|
|
7
|
+
"bind_adaln_producer", "bind_step_locator",
|
|
8
|
+
"bind_style_broker", "bind_style_table"]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Step-scoped style materialisation shared across one conditioning stream.
|
|
2
|
+
|
|
3
|
+
The adaptive-norm kernel takes ``style`` as a contiguous ``(rows, W)``
|
|
4
|
+
tensor, so each producer expands its one style row to the row count and
|
|
5
|
+
materialises it. That is correct and, done per producer, wasteful in a
|
|
6
|
+
way no single producer can see: every producer on one conditioning
|
|
7
|
+
stream resolves the *same* step, and the whole stream's styles are
|
|
8
|
+
therefore fixed for the duration of a step. Measured on pi05, the
|
|
9
|
+
per-producer form spends 0.68 ms in 720 launches moving 221 MB — a
|
|
10
|
+
volume worth about 0.15 ms at this card's bandwidth, so the cost is the
|
|
11
|
+
launches, not the bytes.
|
|
12
|
+
|
|
13
|
+
The broker turns per-call work into per-step work. One index lookup and
|
|
14
|
+
one copy fill a ``(P, rows, W)`` buffer for every producer in the
|
|
15
|
+
stream; each producer then reads ``buf[slot]``, which is a contiguous
|
|
16
|
+
view, so the kernel's contract is untouched. This is the
|
|
17
|
+
``cadence_static`` idea applied inside a structure rather than around a
|
|
18
|
+
module: hold what changes at step cadence, not at call cadence.
|
|
19
|
+
|
|
20
|
+
Two things make it safe rather than merely fast:
|
|
21
|
+
|
|
22
|
+
- the writer is the producer the host actually calls first, taken from
|
|
23
|
+
the observed call order of the calibration pass, not from the order
|
|
24
|
+
the modules happen to sit in the tree;
|
|
25
|
+
- the readers depend on the writer through the buffer itself, the same
|
|
26
|
+
ordering the packed-projection stash relies on inside compiled and
|
|
27
|
+
captured graphs.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import torch
|
|
33
|
+
from torch import nn
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@torch.library.custom_op("flash_rt_structures::style_broadcast",
|
|
37
|
+
mutates_args={"out"})
|
|
38
|
+
def _style_broadcast(src: torch.Tensor, out: torch.Tensor) -> None:
|
|
39
|
+
"""Fill ``out`` (P, rows, W) by repeating each row of ``src`` (P, W).
|
|
40
|
+
|
|
41
|
+
Opaque on purpose. Written as plain tensor work, the compiler sees a
|
|
42
|
+
buffer whose only consumers are slices of it and inlines the fill
|
|
43
|
+
into each consumer — a correct buffer elimination that happens to
|
|
44
|
+
undo the sharing this broker exists for. Measured: the fill stayed
|
|
45
|
+
at 720 launches, they merely moved bucket. Behind an opaque op the
|
|
46
|
+
fill happens once and the readers read.
|
|
47
|
+
"""
|
|
48
|
+
out.copy_(src.unsqueeze(1).expand_as(out))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@_style_broadcast.register_fake
|
|
52
|
+
def _style_broadcast_fake(src: torch.Tensor, out: torch.Tensor) -> None:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class StyleBroker(nn.Module):
|
|
57
|
+
"""One conditioning stream's styles, materialised once per step."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, locator, tables, rows: int):
|
|
60
|
+
super().__init__()
|
|
61
|
+
widths = {t.shape[-1] for t in tables}
|
|
62
|
+
if len(widths) != 1:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"style_broker: producers differ in style width {widths}")
|
|
65
|
+
steps = {t.shape[0] for t in tables}
|
|
66
|
+
if len(steps) != 1:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"style_broker: producers differ in step count {steps}")
|
|
69
|
+
self.locator = locator
|
|
70
|
+
self.slots = len(tables)
|
|
71
|
+
self.rows = rows
|
|
72
|
+
# [steps, slots, W]: one index_select picks the whole stream's
|
|
73
|
+
# styles for the current step
|
|
74
|
+
self.register_buffer("stack", torch.stack(
|
|
75
|
+
[t.to(torch.bfloat16) for t in tables], dim=1).contiguous())
|
|
76
|
+
self.register_buffer("buf", torch.empty(
|
|
77
|
+
self.slots, rows, widths.pop(), device=self.stack.device,
|
|
78
|
+
dtype=torch.bfloat16))
|
|
79
|
+
|
|
80
|
+
def refresh(self, cond: torch.Tensor) -> torch.Tensor:
|
|
81
|
+
"""Resolve the step and materialise every slot. Writer only."""
|
|
82
|
+
idx = self.locator(cond)
|
|
83
|
+
sel = self.stack.index_select(0, idx).reshape(self.slots, -1)
|
|
84
|
+
torch.ops.flash_rt_structures.style_broadcast(sel, self.buf)
|
|
85
|
+
return idx
|
|
86
|
+
|
|
87
|
+
def slice(self, slot: int) -> torch.Tensor:
|
|
88
|
+
"""This producer's style for the current step, contiguous."""
|
|
89
|
+
return self.buf[slot]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def bind_style_broker(producers, rows: int) -> StyleBroker | None:
|
|
93
|
+
"""Attach one broker to producers already bound on the same stream.
|
|
94
|
+
|
|
95
|
+
``producers`` must be in the order the host calls them; the first is
|
|
96
|
+
the writer. Returns ``None`` when there is nothing to share (a single
|
|
97
|
+
producer pays the same either way), leaving every producer on its own
|
|
98
|
+
materialisation — this composes onto bound producers and can only
|
|
99
|
+
remove work, never add a requirement.
|
|
100
|
+
"""
|
|
101
|
+
# a form that never reads a materialised style has nothing to share:
|
|
102
|
+
# attaching anyway would hold a buffer nobody reads and still report
|
|
103
|
+
# a broker as active. Found on the second host, where every producer
|
|
104
|
+
# is the layer form.
|
|
105
|
+
producers = [p for p in producers if p.takes_style_rows]
|
|
106
|
+
if len(producers) < 2:
|
|
107
|
+
return None
|
|
108
|
+
locator = producers[0].locator
|
|
109
|
+
if any(p.locator is not locator for p in producers):
|
|
110
|
+
raise ValueError(
|
|
111
|
+
"style_broker: producers do not share a step locator, so they "
|
|
112
|
+
"are not one conditioning stream")
|
|
113
|
+
broker = StyleBroker(locator, [p.styles for p in producers], rows)
|
|
114
|
+
for slot, producer in enumerate(producers):
|
|
115
|
+
producer.attach_broker(broker, slot, writer=(slot == 0))
|
|
116
|
+
return broker
|