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,43 @@
|
|
|
1
|
+
"""Beta: the join between two bound structures, declared rather than implied.
|
|
2
|
+
|
|
3
|
+
Every structure in the catalog declares its own boundary. What none of
|
|
4
|
+
them declares is the *join* — what has to agree between a structure and
|
|
5
|
+
the one it feeds. Today those agreements exist only inside each impl's
|
|
6
|
+
code, so they are never checked, never negotiated, and never optimised
|
|
7
|
+
across. Every join therefore defaults to "materialise a fresh tensor in
|
|
8
|
+
the host's convention", which is what a hand-written runtime never does:
|
|
9
|
+
there the author holds the whole dataflow and picks, per join, the same
|
|
10
|
+
buffer, the same layout, no re-quantisation.
|
|
11
|
+
|
|
12
|
+
The vocabulary here is derived, not designed. Each attribute exists
|
|
13
|
+
because one specific join cost real time or gave a wrong verdict, and
|
|
14
|
+
each carries that incident in its docstring. The evidence for the whole
|
|
15
|
+
idea is one controlled comparison: ``dtype`` is the only join attribute
|
|
16
|
+
that was ever declared and negotiated, and it is the only one that
|
|
17
|
+
stopped causing failures. The other five were never declared and every
|
|
18
|
+
one of them bit.
|
|
19
|
+
|
|
20
|
+
**This is beta.** Three rules keep it honest, and they double as the
|
|
21
|
+
conditions for deleting it:
|
|
22
|
+
|
|
23
|
+
1. *Descriptive before prescriptive.* It must correctly describe the
|
|
24
|
+
joins that already work before it is allowed to change any behaviour.
|
|
25
|
+
A vocabulary that cannot express what the stack already does is the
|
|
26
|
+
wrong vocabulary.
|
|
27
|
+
2. *No attribute without a negotiator.* An attribute that no join
|
|
28
|
+
negotiates and no measurement depends on gets deleted, not
|
|
29
|
+
documented.
|
|
30
|
+
3. *Off by default.* Nothing in the main path consumes this unless asked
|
|
31
|
+
to, and an attribute a port does not declare is simply not negotiated
|
|
32
|
+
— so coverage can only grow.
|
|
33
|
+
|
|
34
|
+
The deprecation signal is stated up front: if each new structure needs a
|
|
35
|
+
new attribute, this is not a vocabulary, it is a junk drawer. The
|
|
36
|
+
attribute set is supposed to converge as structures are added. If it
|
|
37
|
+
does not, delete this package.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from .negotiate import JoinRefused, negotiate
|
|
41
|
+
from .ports import ATTRIBUTES, Join, Port
|
|
42
|
+
|
|
43
|
+
__all__ = ["ATTRIBUTES", "Join", "JoinRefused", "Port", "negotiate"]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Check the declarations against the impls they claim to describe.
|
|
2
|
+
|
|
3
|
+
A spec file that nobody checks drifts away from the code and becomes
|
|
4
|
+
worse than no spec, because it is then confidently wrong. Writing these
|
|
5
|
+
ports down already corrected one of my own beliefs about what
|
|
6
|
+
``attention_core`` can emit; that only stays true if something keeps
|
|
7
|
+
checking.
|
|
8
|
+
|
|
9
|
+
This is deliberately shallow. It asks the impls what they support and
|
|
10
|
+
compares against what the ports claim — it does not run a model. A
|
|
11
|
+
declaration that cannot be checked this way is a declaration that should
|
|
12
|
+
not be made.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Sequence
|
|
18
|
+
|
|
19
|
+
from .joins import DECLARED
|
|
20
|
+
from .ports import Port
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _offers(port: Port, attr: str) -> tuple[str, ...]:
|
|
24
|
+
return tuple(port.offers.get(attr, ()))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check() -> list[str]:
|
|
28
|
+
"""Return one line per disagreement; empty means the ports hold."""
|
|
29
|
+
problems: list[str] = []
|
|
30
|
+
|
|
31
|
+
from ..impls.attention_core import fa2_seqused
|
|
32
|
+
from ..impls.linear_proj import fp8_static as proj
|
|
33
|
+
from ..impls.qkv_pack import PackedLinear
|
|
34
|
+
|
|
35
|
+
core_cls = fa2_seqused.PackedKVAttention
|
|
36
|
+
|
|
37
|
+
# buffer=alias is only real if both sides expose the entries that
|
|
38
|
+
# make it real, and if the consumer can be told to stop copying
|
|
39
|
+
for name, (out_port, in_port) in DECLARED.items():
|
|
40
|
+
if "alias" not in _offers(out_port, "buffer"):
|
|
41
|
+
continue
|
|
42
|
+
if out_port.structure == "qkv_pack" and not hasattr(
|
|
43
|
+
PackedLinear, "alias_stash"):
|
|
44
|
+
problems.append(
|
|
45
|
+
f"{name}: qkv_pack declares buffer=alias but has no "
|
|
46
|
+
"alias_stash entry")
|
|
47
|
+
if in_port.structure == "attention_core":
|
|
48
|
+
for entry in ("alias_suffix", "forward_suffix"):
|
|
49
|
+
if not hasattr(core_cls, entry):
|
|
50
|
+
problems.append(
|
|
51
|
+
f"{name}: attention_core declares buffer=alias "
|
|
52
|
+
f"but has no {entry} entry")
|
|
53
|
+
|
|
54
|
+
# layout=bshd on the core's kv port is what forward_suffix takes; the
|
|
55
|
+
# host-layout entry is a different port and must not be confused
|
|
56
|
+
kv_in = DECLARED["qkv_pack->attention_core"][1]
|
|
57
|
+
if _offers(kv_in, "layout") != ("bshd",):
|
|
58
|
+
problems.append(
|
|
59
|
+
"qkv_pack->attention_core: the core's kv port takes the "
|
|
60
|
+
"kernel layout only; forward() is the host-layout entry and "
|
|
61
|
+
"belongs to a different port")
|
|
62
|
+
|
|
63
|
+
# dtype=fp8_static has to be an entry the consumer really has
|
|
64
|
+
for name, (_, in_port) in DECLARED.items():
|
|
65
|
+
if "fp8_static" not in _offers(in_port, "dtype"):
|
|
66
|
+
continue
|
|
67
|
+
if in_port.structure == "linear_proj":
|
|
68
|
+
if "fp8_in" not in proj._BAND:
|
|
69
|
+
problems.append(
|
|
70
|
+
f"{name}: linear_proj declares dtype=fp8_static but "
|
|
71
|
+
"has no fp8_in form")
|
|
72
|
+
|
|
73
|
+
# opacity=must_persist is a claim about a compiler contract, so it
|
|
74
|
+
# has to point at something the compiler cannot see through
|
|
75
|
+
style_out = DECLARED["style_broker->producer"][0]
|
|
76
|
+
if "must_persist" in _offers(style_out, "opacity"):
|
|
77
|
+
import torch
|
|
78
|
+
if not hasattr(torch.ops, "flash_rt_structures") or not hasattr(
|
|
79
|
+
torch.ops.flash_rt_structures, "style_broadcast"):
|
|
80
|
+
problems.append(
|
|
81
|
+
"style_broker->producer: opacity=must_persist is claimed "
|
|
82
|
+
"but the fill is not behind an opaque op, so the "
|
|
83
|
+
"compiler may inline it back into each reader")
|
|
84
|
+
|
|
85
|
+
return problems
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def report(problems: Sequence[str] | None = None) -> str:
|
|
89
|
+
problems = check() if problems is None else problems
|
|
90
|
+
if not problems:
|
|
91
|
+
return (f"beta: {len(DECLARED)} declared join(s), all consistent "
|
|
92
|
+
"with the impls")
|
|
93
|
+
return "beta: declarations disagree with the impls\n " + "\n ".join(
|
|
94
|
+
problems)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""The joins this stack already has, declared.
|
|
2
|
+
|
|
3
|
+
Descriptive before prescriptive: these ports describe what the bound
|
|
4
|
+
structures do *today*, including the joins that already work. If the
|
|
5
|
+
vocabulary cannot express the stack as it stands, the vocabulary is
|
|
6
|
+
wrong and no behaviour should be changed on top of it.
|
|
7
|
+
|
|
8
|
+
Each port carries the shape of the tensor it is about in its note, so a
|
|
9
|
+
reader composing structures by hand — the explicit door — can see the
|
|
10
|
+
agreement without reading the impl.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from .ports import Port
|
|
16
|
+
|
|
17
|
+
# --- adaptive norm producing the packed projections' input ------------
|
|
18
|
+
# Works today. The producer emits fp8 with a shared act scale and the
|
|
19
|
+
# pack skips its own input quantization; the two are bound atomically.
|
|
20
|
+
# Declared here to prove the vocabulary can express a join that already
|
|
21
|
+
# holds, not to change it.
|
|
22
|
+
ADALN_OUT = Port(
|
|
23
|
+
structure="adaln_producer", name="y", direction="out",
|
|
24
|
+
offers={"dtype": ("fp8_static", "bf16"),
|
|
25
|
+
"layout": ("row_major",),
|
|
26
|
+
"carry": ("gated_residual", "none"),
|
|
27
|
+
"cadence": ("per_call",)},
|
|
28
|
+
note="(rows, D) normed activation; carry=gated_residual means the "
|
|
29
|
+
"producer absorbed the pending residual rather than the host "
|
|
30
|
+
"closing it with an elementwise add")
|
|
31
|
+
|
|
32
|
+
QKV_PACK_IN = Port(
|
|
33
|
+
structure="qkv_pack", name="x", direction="in",
|
|
34
|
+
offers={"dtype": ("fp8_static", "bf16"),
|
|
35
|
+
"layout": ("row_major",)},
|
|
36
|
+
note="(rows, K) shared input of the sibling projections")
|
|
37
|
+
|
|
38
|
+
DECODER_FFN_IN = Port(
|
|
39
|
+
structure="decoder_ffn", name="x", direction="in",
|
|
40
|
+
offers={"dtype": ("fp8_static", "bf16"),
|
|
41
|
+
"layout": ("row_major",)},
|
|
42
|
+
note="(rows, D) normed input; the bf16 entry fuses its own quantize, "
|
|
43
|
+
"which is why this join only pays when the producer is already "
|
|
44
|
+
"there for another reason")
|
|
45
|
+
|
|
46
|
+
# --- packed projections feeding the attention core --------------------
|
|
47
|
+
# The join that still does cancelling work. The pack writes k/v into its
|
|
48
|
+
# own stash buffers and the core copies them into its packed KV region;
|
|
49
|
+
# both sides can express the alias, nothing negotiates it yet.
|
|
50
|
+
QKV_PACK_OUT = Port(
|
|
51
|
+
structure="qkv_pack", name="kv", direction="out",
|
|
52
|
+
offers={"dtype": ("bf16",),
|
|
53
|
+
"layout": ("bshd", "row_major"),
|
|
54
|
+
"buffer": ("alias", "fresh")},
|
|
55
|
+
note="(rows, N_k) / (rows, N_v) sibling outputs; viewed as "
|
|
56
|
+
"(B, S, H, D) they are already the kernel's layout, and the "
|
|
57
|
+
"stash buffer they land in could be the consumer's region")
|
|
58
|
+
|
|
59
|
+
ATTENTION_CORE_KV_IN = Port(
|
|
60
|
+
structure="attention_core", name="kv", direction="in",
|
|
61
|
+
offers={"dtype": ("bf16",),
|
|
62
|
+
"layout": ("bshd",),
|
|
63
|
+
"buffer": ("alias", "fresh")},
|
|
64
|
+
note="(B, S_suffix, H_kv, D) suffix keys/values; the packed region's "
|
|
65
|
+
"suffix rows are contiguous, so a producer can write into them "
|
|
66
|
+
"directly")
|
|
67
|
+
|
|
68
|
+
ATTENTION_CORE_OUT = Port(
|
|
69
|
+
structure="attention_core", name="out", direction="out",
|
|
70
|
+
offers={"dtype": ("bf16",), "layout": ("bshd", "row_major")},
|
|
71
|
+
note="(B, S, H, D). row_major is free *because* the layout is bshd: "
|
|
72
|
+
"its last two axes are contiguous, so the (.., H*D) reshape the "
|
|
73
|
+
"output projection wants is a view. From bhsd the same reshape "
|
|
74
|
+
"would cost a transpose and a copy — which is the whole reason "
|
|
75
|
+
"layout belongs on the join rather than inside either side")
|
|
76
|
+
|
|
77
|
+
LINEAR_PROJ_IN = Port(
|
|
78
|
+
structure="linear_proj", name="x", direction="in",
|
|
79
|
+
offers={"dtype": ("fp8_static", "bf16"), "layout": ("row_major",)},
|
|
80
|
+
note="(rows, K); the fp8 form has no quantize to amortise, which is "
|
|
81
|
+
"why its work band starts an order of magnitude lower")
|
|
82
|
+
|
|
83
|
+
# --- the stream-scoped style materialisation --------------------------
|
|
84
|
+
# The join that the compiler was allowed to undo until it was declared
|
|
85
|
+
# opaque. Declared here because opacity is otherwise invisible: nothing
|
|
86
|
+
# about the tensors says the arrangement must survive.
|
|
87
|
+
STYLE_BROKER_OUT = Port(
|
|
88
|
+
structure="adaln_producer", name="style", direction="out",
|
|
89
|
+
offers={"layout": ("row_major",),
|
|
90
|
+
"cadence": ("per_step", "per_call"),
|
|
91
|
+
"opacity": ("must_persist", "fusible")},
|
|
92
|
+
note="(rows, 3D) style rows for one step; per_step means one fill "
|
|
93
|
+
"serves every producer on the stream, which only survives when "
|
|
94
|
+
"the fill is opaque to the compiler")
|
|
95
|
+
|
|
96
|
+
STYLE_CONSUMER_IN = Port(
|
|
97
|
+
structure="adaln_producer", name="style", direction="in",
|
|
98
|
+
offers={"layout": ("row_major",),
|
|
99
|
+
"cadence": ("per_step", "per_call"),
|
|
100
|
+
"opacity": ("must_persist", "fusible")},
|
|
101
|
+
note="(rows, 3D) contiguous, as the kernel requires")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
#: Every join the stack has today, producer first. The pairs that
|
|
105
|
+
#: already hold are here to be checked against reality; the pairs that
|
|
106
|
+
#: do not are here to be measured.
|
|
107
|
+
DECLARED = {
|
|
108
|
+
"adaln->qkv_pack": (ADALN_OUT, QKV_PACK_IN),
|
|
109
|
+
"adaln->decoder_ffn": (ADALN_OUT, DECODER_FFN_IN),
|
|
110
|
+
"qkv_pack->attention_core": (QKV_PACK_OUT, ATTENTION_CORE_KV_IN),
|
|
111
|
+
"attention_core->linear_proj": (ATTENTION_CORE_OUT, LINEAR_PROJ_IN),
|
|
112
|
+
"style_broker->producer": (STYLE_BROKER_OUT, STYLE_CONSUMER_IN),
|
|
113
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Negotiate one join: intersect what both sides offer, name what fails.
|
|
2
|
+
|
|
3
|
+
This generalises what the fp8 producer chain already does by hand — two
|
|
4
|
+
components agreeing on one attribute and binding atomically — to the
|
|
5
|
+
whole vocabulary. The parts that carried their weight there are kept:
|
|
6
|
+
|
|
7
|
+
- the intersection is computed before anything binds, so a join that
|
|
8
|
+
cannot be agreed changes nothing;
|
|
9
|
+
- a refusal names the attribute that failed, not the structure, because
|
|
10
|
+
"refused" must never read as "this structure cannot be joined";
|
|
11
|
+
- an attribute only one side declares stays unnegotiated and is reported
|
|
12
|
+
as such, so a partial declaration is a partial gain rather than a
|
|
13
|
+
silent override.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Callable, Mapping, Sequence
|
|
19
|
+
|
|
20
|
+
from .ports import Join, Port
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class JoinRefused(ValueError):
|
|
24
|
+
"""The two ports have no common value for some attribute."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def negotiate(
|
|
28
|
+
producer: Port,
|
|
29
|
+
consumer: Port,
|
|
30
|
+
*,
|
|
31
|
+
prefer: Mapping[str, Sequence[str]] | None = None,
|
|
32
|
+
cost: Callable[[str, str], float] | None = None,
|
|
33
|
+
) -> Join:
|
|
34
|
+
"""Agree the attributes both ports declare.
|
|
35
|
+
|
|
36
|
+
``prefer`` overrides the producer's own preference order for an
|
|
37
|
+
attribute — this is where a measured band belongs, so that a choice
|
|
38
|
+
follows the shapes at hand rather than a fixed opinion baked into
|
|
39
|
+
the port. ``cost`` picks by number instead when one is available;
|
|
40
|
+
the lowest wins.
|
|
41
|
+
"""
|
|
42
|
+
if producer.direction != "out" or consumer.direction != "in":
|
|
43
|
+
raise JoinRefused(
|
|
44
|
+
f"{producer.structure}.{producer.name} -> "
|
|
45
|
+
f"{consumer.structure}.{consumer.name}: a join runs from an "
|
|
46
|
+
"out port to an in port")
|
|
47
|
+
|
|
48
|
+
prefer = dict(prefer or {})
|
|
49
|
+
chosen: dict[str, str] = {}
|
|
50
|
+
unconstrained: list[str] = []
|
|
51
|
+
|
|
52
|
+
for attr in sorted(set(producer.offers) | set(consumer.offers)):
|
|
53
|
+
out_side = producer.offers.get(attr)
|
|
54
|
+
in_side = consumer.offers.get(attr)
|
|
55
|
+
if out_side is None or in_side is None:
|
|
56
|
+
# only one side has an opinion: leave the join as the host
|
|
57
|
+
# already has it, and say so
|
|
58
|
+
unconstrained.append(attr)
|
|
59
|
+
continue
|
|
60
|
+
common = [v for v in out_side if v in in_side]
|
|
61
|
+
if not common:
|
|
62
|
+
raise JoinRefused(
|
|
63
|
+
f"{producer.structure}.{producer.name} -> "
|
|
64
|
+
f"{consumer.structure}.{consumer.name}: no common {attr} "
|
|
65
|
+
f"({list(out_side)} vs {list(in_side)}) — the join stays "
|
|
66
|
+
"as the host has it; the structures themselves are fine")
|
|
67
|
+
order = [v for v in prefer.get(attr, ()) if v in common] or common
|
|
68
|
+
if cost is not None:
|
|
69
|
+
chosen[attr] = min(order, key=lambda value: cost(attr, value))
|
|
70
|
+
else:
|
|
71
|
+
chosen[attr] = order[0]
|
|
72
|
+
|
|
73
|
+
return Join(producer=producer, consumer=consumer, chosen=chosen,
|
|
74
|
+
unconstrained=tuple(unconstrained))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def describe(joins: Sequence[Join]) -> str:
|
|
78
|
+
"""A readable account of what a set of joins agreed on."""
|
|
79
|
+
lines = []
|
|
80
|
+
for join in joins:
|
|
81
|
+
lines.append(str(join))
|
|
82
|
+
if join.unconstrained:
|
|
83
|
+
lines.append(f" unconstrained: {', '.join(join.unconstrained)}")
|
|
84
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""The join vocabulary: what two adjacent structures have to agree on.
|
|
2
|
+
|
|
3
|
+
Every attribute below was extracted from a join that had already gone
|
|
4
|
+
wrong, and the incident is recorded with it. Nothing here is speculative
|
|
5
|
+
— an attribute nobody negotiates is meant to be deleted, not kept as
|
|
6
|
+
documentation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Mapping, Sequence
|
|
13
|
+
|
|
14
|
+
# Attribute -> the values a port may declare, most-preferred first where
|
|
15
|
+
# order carries meaning. A port that declares nothing for an attribute
|
|
16
|
+
# leaves it unnegotiated, which is exactly today's behaviour.
|
|
17
|
+
ATTRIBUTES: dict[str, tuple[str, ...]] = {
|
|
18
|
+
# Who holds the activation in what numeric type, and therefore who
|
|
19
|
+
# pays for the conversion.
|
|
20
|
+
#
|
|
21
|
+
# Incident: an fp8 producer and the pack it feeds, bound
|
|
22
|
+
# independently, can diverge — a pack expecting fp8 whose producer
|
|
23
|
+
# did not bind leaves the host to insert a quantize, which the
|
|
24
|
+
# compiler fuses into the upstream gated residual and turns into fp8
|
|
25
|
+
# arithmetic the target cannot lower. Binding the pair atomically
|
|
26
|
+
# fixed it. This is the one join attribute that was ever declared,
|
|
27
|
+
# and the only one that stopped failing.
|
|
28
|
+
"dtype": ("fp8_static", "bf16", "int8_static"),
|
|
29
|
+
|
|
30
|
+
# How the tensor's axes are arranged when it crosses the join.
|
|
31
|
+
#
|
|
32
|
+
# Incident: the host lays q/k/v out as (B, H, S, D) because that is
|
|
33
|
+
# what eager attention wants; the fused kernel wants (B, S, H, D)
|
|
34
|
+
# and transposed back. Two transposes that cancel, plus the copies
|
|
35
|
+
# that make each contiguous, per projection per layer per step —
|
|
36
|
+
# and neither side could see the other, because each was correct on
|
|
37
|
+
# its own.
|
|
38
|
+
"layout": ("row_major", "bshd", "bhsd"),
|
|
39
|
+
|
|
40
|
+
# Who allocates the tensor the consumer reads. ``alias`` is the
|
|
41
|
+
# declarative form of what a hand-written runtime does when it hands
|
|
42
|
+
# the next stage a pointer: the producer writes straight into the
|
|
43
|
+
# region the consumer already owns.
|
|
44
|
+
#
|
|
45
|
+
# Incident: the packed projection writes its k/v into its own stash
|
|
46
|
+
# buffers and the attention core then copies them into its packed
|
|
47
|
+
# KV region — two buffers where one would do.
|
|
48
|
+
"buffer": ("alias", "caller_provided", "fresh"),
|
|
49
|
+
|
|
50
|
+
# Whether a sublayer result is still pending at the join, so the
|
|
51
|
+
# consumer can absorb it instead of the host closing it first.
|
|
52
|
+
#
|
|
53
|
+
# Incident: the adaptive-norm kernel computes ``residual + x * gate``
|
|
54
|
+
# before it norms. Bound at the norm boundary there is no residual to
|
|
55
|
+
# hand it, so it was fed zeros and the host kept its own elementwise
|
|
56
|
+
# add — measured +0.17 ms, refused. The same kernel inside a block
|
|
57
|
+
# boundary replaces that add instead of adding to it, and turns
|
|
58
|
+
# positive. Same kernel, opposite verdict, because the join was
|
|
59
|
+
# declared at the wrong place.
|
|
60
|
+
"carry": ("gated_residual", "none"),
|
|
61
|
+
|
|
62
|
+
# How often the value on this join actually changes, as opposed to
|
|
63
|
+
# how often it is read.
|
|
64
|
+
#
|
|
65
|
+
# Incident: a step's style is fixed for every producer on one
|
|
66
|
+
# conditioning stream, but was materialised per call — 720 launches
|
|
67
|
+
# for work that changes 10 times a tick.
|
|
68
|
+
"cadence": ("per_observation", "per_step", "per_call"),
|
|
69
|
+
|
|
70
|
+
# Whether the compiler may rewrite away the arrangement this join
|
|
71
|
+
# depends on. Not a hardware or dataflow property: a contract with
|
|
72
|
+
# the compiler underneath.
|
|
73
|
+
#
|
|
74
|
+
# Incident: a step-scoped shared buffer, written as plain tensor
|
|
75
|
+
# work, was legally eliminated — the compiler saw a buffer whose
|
|
76
|
+
# only consumers were slices of it and inlined the fill back into
|
|
77
|
+
# each of them. Semantically identical, and the sharing was gone:
|
|
78
|
+
# -0.164 ms instead of -0.419 ms, with the launch count unchanged.
|
|
79
|
+
# A structure layer sitting on a compiler has to be able to say
|
|
80
|
+
# which of its decisions may not be undone.
|
|
81
|
+
"opacity": ("must_persist", "fusible"),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class PortError(ValueError):
|
|
86
|
+
"""A port declared something outside the vocabulary."""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True)
|
|
90
|
+
class Port:
|
|
91
|
+
"""One side of a join: what this structure can accept or emit.
|
|
92
|
+
|
|
93
|
+
``offers`` maps an attribute to the values this port supports, most
|
|
94
|
+
preferred first. An attribute left out is not negotiated — the join
|
|
95
|
+
keeps whatever the host already does, which is why declaring a port
|
|
96
|
+
can only add.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
structure: str
|
|
100
|
+
name: str
|
|
101
|
+
direction: str # "in" | "out"
|
|
102
|
+
offers: Mapping[str, Sequence[str]] = field(default_factory=dict)
|
|
103
|
+
note: str = ""
|
|
104
|
+
|
|
105
|
+
def __post_init__(self) -> None:
|
|
106
|
+
if self.direction not in ("in", "out"):
|
|
107
|
+
raise PortError(
|
|
108
|
+
f"{self.structure}.{self.name}: direction must be in/out")
|
|
109
|
+
for attr, values in self.offers.items():
|
|
110
|
+
known = ATTRIBUTES.get(attr)
|
|
111
|
+
if known is None:
|
|
112
|
+
raise PortError(
|
|
113
|
+
f"{self.structure}.{self.name}: {attr!r} is not a join "
|
|
114
|
+
f"attribute. The vocabulary is closed on purpose — "
|
|
115
|
+
f"adding one means a new incident to record")
|
|
116
|
+
unknown = [v for v in values if v not in known]
|
|
117
|
+
if unknown:
|
|
118
|
+
raise PortError(
|
|
119
|
+
f"{self.structure}.{self.name}: {attr}={unknown} not in "
|
|
120
|
+
f"{known}")
|
|
121
|
+
if not values:
|
|
122
|
+
raise PortError(
|
|
123
|
+
f"{self.structure}.{self.name}: {attr} offers nothing; "
|
|
124
|
+
"leave the attribute out to keep it unnegotiated")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass(frozen=True)
|
|
128
|
+
class Join:
|
|
129
|
+
"""A negotiated agreement between two ports."""
|
|
130
|
+
|
|
131
|
+
producer: Port
|
|
132
|
+
consumer: Port
|
|
133
|
+
chosen: Mapping[str, str]
|
|
134
|
+
unconstrained: Sequence[str] = () # declared by one side only
|
|
135
|
+
note: str = ""
|
|
136
|
+
|
|
137
|
+
def __str__(self) -> str:
|
|
138
|
+
picks = ", ".join(f"{k}={v}" for k, v in sorted(self.chosen.items()))
|
|
139
|
+
return (f"{self.producer.structure}.{self.producer.name} -> "
|
|
140
|
+
f"{self.consumer.structure}.{self.consumer.name} [{picks}]")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Band decisions: measured once per box, cached, consumed by every tier.
|
|
2
|
+
|
|
3
|
+
The seat-level micro-race was refuted in both directions by
|
|
4
|
+
production-form measurement; what stands is the captured end-to-end
|
|
5
|
+
number. This cache is where those numbers live: a band measurement run
|
|
6
|
+
records the winner per (device, band), and both assembly tiers read it
|
|
7
|
+
— the automatic path to route its formats, the explicit path as its
|
|
8
|
+
default band. An author pin (env) always outranks the cache; an empty
|
|
9
|
+
cache falls to the precision-order default. The cache file is a
|
|
10
|
+
receipt and is transportable: an edge box can ship with its decisions
|
|
11
|
+
measured elsewhere on identical hardware.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import pathlib
|
|
19
|
+
|
|
20
|
+
import torch
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _cache_path() -> pathlib.Path:
|
|
24
|
+
return pathlib.Path(os.environ.get(
|
|
25
|
+
"FRT_DECISION_CACHE",
|
|
26
|
+
os.path.expanduser("~/.cache/flashrt/band_decisions.json")))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _key(band: str) -> str:
|
|
30
|
+
dev = (torch.cuda.get_device_name(0)
|
|
31
|
+
if torch.cuda.is_available() else "cpu")
|
|
32
|
+
return f"{dev}|{band}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def lookup(band: str, default: str | None = None) -> str | None:
|
|
36
|
+
try:
|
|
37
|
+
data = json.loads(_cache_path().read_text())
|
|
38
|
+
except (OSError, ValueError):
|
|
39
|
+
return default
|
|
40
|
+
entry = data.get(_key(band))
|
|
41
|
+
return entry.get("winner", default) if entry else default
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def record(band: str, winner: str, times_ms: dict) -> pathlib.Path:
|
|
45
|
+
path = _cache_path()
|
|
46
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
try:
|
|
48
|
+
data = json.loads(path.read_text())
|
|
49
|
+
except (OSError, ValueError):
|
|
50
|
+
data = {}
|
|
51
|
+
data[_key(band)] = {"winner": winner, "times_ms": times_ms}
|
|
52
|
+
path.write_text(json.dumps(data, indent=1))
|
|
53
|
+
return path
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def import_decisions(source: str) -> dict:
|
|
57
|
+
"""Merge a decision file measured elsewhere into the local cache.
|
|
58
|
+
|
|
59
|
+
The peak-avoidance path for a memory-tight box: adjudicate on a
|
|
60
|
+
roomy machine of the same device fingerprint, ship the file, and
|
|
61
|
+
the tight box binds warm from its first run — the race (and its
|
|
62
|
+
peak) never happens there. Entries whose device fingerprint does
|
|
63
|
+
not match this box are carried along untouched (the cache is keyed
|
|
64
|
+
by device, so they are inert here and correct if the file travels
|
|
65
|
+
on). Local entries win on conflict: a number measured on this box
|
|
66
|
+
outranks an imported one. Returns a receipt of what was merged.
|
|
67
|
+
"""
|
|
68
|
+
imported = json.loads(pathlib.Path(source).read_text())
|
|
69
|
+
path = _cache_path()
|
|
70
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
try:
|
|
72
|
+
local = json.loads(path.read_text())
|
|
73
|
+
except (OSError, ValueError):
|
|
74
|
+
local = {}
|
|
75
|
+
added = [k for k in imported if k not in local]
|
|
76
|
+
merged = dict(imported)
|
|
77
|
+
merged.update(local)
|
|
78
|
+
path.write_text(json.dumps(merged, indent=2))
|
|
79
|
+
return {"imported": len(added), "kept_local": len(local),
|
|
80
|
+
"entries": sorted(added)}
|