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,99 @@
|
|
|
1
|
+
"""The prefill_tower region family: structural identification, candidates.
|
|
2
|
+
|
|
3
|
+
The identifier matches shape, never names: a module carrying a
|
|
4
|
+
``layers`` list whose blocks each hold a bias-free attention group and
|
|
5
|
+
gated FFN under *plain* affine RMS norms (a 1-D weight and no
|
|
6
|
+
conditioning projection — the conditioned twin belongs to the sibling
|
|
7
|
+
family), plus a rotary table and a plain stack-level norm. The key
|
|
8
|
+
width must be narrower than the query width: the chain's cache layout
|
|
9
|
+
is written for the single-KV band.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import torch
|
|
15
|
+
|
|
16
|
+
from . import fp8_chain
|
|
17
|
+
from ... import regions
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _plain_linear(mod) -> bool:
|
|
21
|
+
return isinstance(mod, torch.nn.Linear) and mod.bias is None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _plain_norm(mod) -> bool:
|
|
25
|
+
return fp8_chain._plain_norm_weight(mod) is not None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _block_ok(block) -> bool:
|
|
29
|
+
attn = getattr(block, "self_attn", None)
|
|
30
|
+
mlp = getattr(block, "mlp", None)
|
|
31
|
+
if attn is None or mlp is None:
|
|
32
|
+
return False
|
|
33
|
+
if not all(_plain_linear(getattr(attn, a, None))
|
|
34
|
+
for a in ("q_proj", "k_proj", "v_proj", "o_proj")):
|
|
35
|
+
return False
|
|
36
|
+
if not all(_plain_linear(getattr(mlp, a, None))
|
|
37
|
+
for a in ("gate_proj", "up_proj", "down_proj")):
|
|
38
|
+
return False
|
|
39
|
+
if mlp.gate_proj.out_features != mlp.up_proj.out_features:
|
|
40
|
+
return False
|
|
41
|
+
if not _plain_norm(getattr(block, "input_layernorm", None)):
|
|
42
|
+
return False
|
|
43
|
+
return _plain_norm(getattr(block, "post_attention_layernorm", None))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def identify(model) -> list[str]:
|
|
47
|
+
roots = []
|
|
48
|
+
for path, mod in model.named_modules():
|
|
49
|
+
layers = getattr(mod, "layers", None)
|
|
50
|
+
if not isinstance(layers, torch.nn.ModuleList) or len(layers) < 2:
|
|
51
|
+
continue
|
|
52
|
+
if not callable(getattr(mod, "rotary_emb", None)):
|
|
53
|
+
continue
|
|
54
|
+
head = layers[0]
|
|
55
|
+
attn = getattr(head, "self_attn", None)
|
|
56
|
+
if attn is None or not isinstance(
|
|
57
|
+
getattr(attn, "q_proj", None), torch.nn.Linear):
|
|
58
|
+
continue
|
|
59
|
+
if not _plain_norm(getattr(mod, "norm", None)):
|
|
60
|
+
continue
|
|
61
|
+
if not all(_block_ok(b) for b in layers):
|
|
62
|
+
continue
|
|
63
|
+
if attn.k_proj.out_features >= attn.q_proj.out_features:
|
|
64
|
+
continue
|
|
65
|
+
roots.append(path)
|
|
66
|
+
return roots
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _bind(model, root, probe):
|
|
70
|
+
return fp8_chain.bind_prefill_fp8_chain(model, root, probe)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _band_candidate(band: str, row: dict) -> regions.RegionCandidate:
|
|
74
|
+
return regions.RegionCandidate(
|
|
75
|
+
name=f"{band}_chain",
|
|
76
|
+
missing=lambda band=band: fp8_chain.missing_symbols(band=band),
|
|
77
|
+
bind=lambda model, root, probe, band=band:
|
|
78
|
+
fp8_chain.bind_prefill_fp8_chain(model, root, probe,
|
|
79
|
+
band=band),
|
|
80
|
+
precision_rank=row["precision_rank"],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
#: candidates generate from the band table — a precision band is a
|
|
85
|
+
#: table row in the chain module, never new wiring here
|
|
86
|
+
FAMILY = regions.RegionFamily(
|
|
87
|
+
family="prefill_tower",
|
|
88
|
+
identify=identify,
|
|
89
|
+
candidates=[_band_candidate(band, row)
|
|
90
|
+
for band, row in fp8_chain.BANDS.items()],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def register() -> None:
|
|
95
|
+
"""(Re-)register the family — idempotent, import calls it once."""
|
|
96
|
+
regions.register_region_family(FAMILY)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
register()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .projection_bf16 import (
|
|
2
|
+
ProjectionQkNormRope,
|
|
3
|
+
bind_projection_qk_norm_rope,
|
|
4
|
+
)
|
|
5
|
+
from .per_head_gqa import PerHeadGqaQkNormRope, bind_per_head_gqa_qk_norm_rope
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"PerHeadGqaQkNormRope",
|
|
9
|
+
"ProjectionQkNormRope",
|
|
10
|
+
"bind_per_head_gqa_qk_norm_rope",
|
|
11
|
+
"bind_projection_qk_norm_rope",
|
|
12
|
+
]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Per-head GQA Q/K RMSNorm and rotate-half RoPE implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
from .. import hub_kernel
|
|
8
|
+
from ...guard import PROCEED, GuardRefused, GuardedSeam
|
|
9
|
+
from ...workspace import lease
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PerHeadGqaQkNormRope(GuardedSeam, torch.nn.Module):
|
|
13
|
+
"""Consume packed GQA QKV and produce attention-ready workspaces."""
|
|
14
|
+
|
|
15
|
+
_frt_can_fallback = False
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
q_norm_weight: torch.Tensor,
|
|
20
|
+
k_norm_weight: torch.Tensor,
|
|
21
|
+
*,
|
|
22
|
+
row_capacity: int,
|
|
23
|
+
q_heads: int,
|
|
24
|
+
kv_heads: int,
|
|
25
|
+
head_dim: int,
|
|
26
|
+
eps: float = 1e-6,
|
|
27
|
+
workspace_lane: str | None = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
super().__init__()
|
|
30
|
+
if row_capacity <= 0 or q_heads <= 0 or kv_heads <= 0:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
"qk_norm_rope: row capacity and head counts must be positive"
|
|
33
|
+
)
|
|
34
|
+
if head_dim != 128:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
"qk_norm_rope: per-head GQA kernel requires head_dim == 128"
|
|
37
|
+
)
|
|
38
|
+
if q_norm_weight.shape != (128,) or k_norm_weight.shape != (128,):
|
|
39
|
+
raise ValueError(
|
|
40
|
+
"qk_norm_rope: per-head norm weights must have shape (128,)"
|
|
41
|
+
)
|
|
42
|
+
if q_norm_weight.device != k_norm_weight.device:
|
|
43
|
+
raise ValueError("qk_norm_rope: Q/K norm weights must share device")
|
|
44
|
+
|
|
45
|
+
self.row_capacity = int(row_capacity)
|
|
46
|
+
self.q_heads = int(q_heads)
|
|
47
|
+
self.kv_heads = int(kv_heads)
|
|
48
|
+
self.head_dim = 128
|
|
49
|
+
self.eps = float(eps)
|
|
50
|
+
kernel = hub_kernel("flashrt/flashrt-qkv-cache-rope", ">=1")
|
|
51
|
+
try:
|
|
52
|
+
self._fn = kernel.qkv_split_per_head_norm_rope_bf16
|
|
53
|
+
except AttributeError as exc:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
"qk_norm_rope: flashrt-qkv-cache-rope artifact lacks the "
|
|
56
|
+
"per-head GQA entry"
|
|
57
|
+
) from exc
|
|
58
|
+
self.register_buffer(
|
|
59
|
+
"q_norm_weight",
|
|
60
|
+
q_norm_weight.detach().to(torch.bfloat16).contiguous(),
|
|
61
|
+
)
|
|
62
|
+
self.register_buffer(
|
|
63
|
+
"k_norm_weight",
|
|
64
|
+
k_norm_weight.detach().to(torch.bfloat16).contiguous(),
|
|
65
|
+
)
|
|
66
|
+
device = q_norm_weight.device
|
|
67
|
+
if workspace_lane is not None:
|
|
68
|
+
# a caller that declares its outputs call-scoped (no cache,
|
|
69
|
+
# consumed inside the layer) shares one workspace per lane
|
|
70
|
+
# across every same-shape layer — the difference between a
|
|
71
|
+
# 19k-token host binding 52 layers and OOMing on the 53rd
|
|
72
|
+
self.q_out = lease((self.row_capacity, self.q_heads, 128),
|
|
73
|
+
torch.bfloat16, device,
|
|
74
|
+
tag=f"qkr_q|{workspace_lane}")
|
|
75
|
+
self.k_out = lease((self.row_capacity, self.kv_heads, 128),
|
|
76
|
+
torch.bfloat16, device,
|
|
77
|
+
tag=f"qkr_k|{workspace_lane}")
|
|
78
|
+
self.v_out = lease((self.row_capacity, self.kv_heads, 128),
|
|
79
|
+
torch.bfloat16, device,
|
|
80
|
+
tag=f"qkr_v|{workspace_lane}")
|
|
81
|
+
else:
|
|
82
|
+
self.register_buffer(
|
|
83
|
+
"q_out",
|
|
84
|
+
torch.empty(
|
|
85
|
+
self.row_capacity,
|
|
86
|
+
self.q_heads,
|
|
87
|
+
128,
|
|
88
|
+
device=device,
|
|
89
|
+
dtype=torch.bfloat16,
|
|
90
|
+
),
|
|
91
|
+
persistent=False,
|
|
92
|
+
)
|
|
93
|
+
self.register_buffer(
|
|
94
|
+
"k_out",
|
|
95
|
+
torch.empty(
|
|
96
|
+
self.row_capacity,
|
|
97
|
+
self.kv_heads,
|
|
98
|
+
128,
|
|
99
|
+
device=device,
|
|
100
|
+
dtype=torch.bfloat16,
|
|
101
|
+
),
|
|
102
|
+
persistent=False,
|
|
103
|
+
)
|
|
104
|
+
self.register_buffer(
|
|
105
|
+
"v_out",
|
|
106
|
+
torch.empty_like(self.k_out),
|
|
107
|
+
persistent=False,
|
|
108
|
+
)
|
|
109
|
+
self._frt_arm(
|
|
110
|
+
dtypes={torch.bfloat16},
|
|
111
|
+
device=device,
|
|
112
|
+
k=(self.q_heads + 2 * self.kv_heads) * 128,
|
|
113
|
+
row_capacity=self.row_capacity,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def forward(
|
|
117
|
+
self,
|
|
118
|
+
packed_qkv: torch.Tensor,
|
|
119
|
+
cos: torch.Tensor,
|
|
120
|
+
sin: torch.Tensor,
|
|
121
|
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
122
|
+
admitted = self._frt_admit(packed_qkv, cos, sin)
|
|
123
|
+
if admitted is not PROCEED:
|
|
124
|
+
return admitted
|
|
125
|
+
if packed_qkv.dim() != 3:
|
|
126
|
+
raise GuardRefused(
|
|
127
|
+
"qk_norm_rope: packed QKV must have shape (B, T, width)"
|
|
128
|
+
)
|
|
129
|
+
batch, tokens, width = packed_qkv.shape
|
|
130
|
+
rows = batch * tokens
|
|
131
|
+
expected_width = (self.q_heads + 2 * self.kv_heads) * 128
|
|
132
|
+
if width != expected_width or rows > self.row_capacity:
|
|
133
|
+
raise GuardRefused(
|
|
134
|
+
"qk_norm_rope: packed GQA QKV is outside the bound form"
|
|
135
|
+
)
|
|
136
|
+
expected_freq = (batch, tokens, 128)
|
|
137
|
+
if cos.shape != expected_freq or sin.shape != expected_freq:
|
|
138
|
+
raise GuardRefused(
|
|
139
|
+
f"qk_norm_rope: cos/sin must have shape {expected_freq}"
|
|
140
|
+
)
|
|
141
|
+
if (
|
|
142
|
+
cos.dtype is not torch.bfloat16
|
|
143
|
+
or sin.dtype is not torch.bfloat16
|
|
144
|
+
or cos.device != packed_qkv.device
|
|
145
|
+
or sin.device != packed_qkv.device
|
|
146
|
+
):
|
|
147
|
+
raise GuardRefused(
|
|
148
|
+
"qk_norm_rope: cos/sin must be BF16 on the QKV device"
|
|
149
|
+
)
|
|
150
|
+
if not packed_qkv.is_contiguous():
|
|
151
|
+
raise GuardRefused("qk_norm_rope: packed QKV must be contiguous")
|
|
152
|
+
|
|
153
|
+
q_out = self.q_out[:rows].view(batch, tokens, self.q_heads, 128)
|
|
154
|
+
k_out = self.k_out[:rows].view(batch, tokens, self.kv_heads, 128)
|
|
155
|
+
v_out = self.v_out[:rows].view(batch, tokens, self.kv_heads, 128)
|
|
156
|
+
return self._fn(
|
|
157
|
+
packed_qkv,
|
|
158
|
+
self.q_norm_weight,
|
|
159
|
+
self.k_norm_weight,
|
|
160
|
+
cos,
|
|
161
|
+
sin,
|
|
162
|
+
self.q_heads,
|
|
163
|
+
self.kv_heads,
|
|
164
|
+
eps=self.eps,
|
|
165
|
+
q_out=q_out,
|
|
166
|
+
k_out=k_out,
|
|
167
|
+
v_out=v_out,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def bind_per_head_gqa_qk_norm_rope(
|
|
172
|
+
q_norm_weight: torch.Tensor,
|
|
173
|
+
k_norm_weight: torch.Tensor,
|
|
174
|
+
*,
|
|
175
|
+
row_capacity: int,
|
|
176
|
+
q_heads: int,
|
|
177
|
+
kv_heads: int,
|
|
178
|
+
head_dim: int,
|
|
179
|
+
eps: float = 1e-6,
|
|
180
|
+
workspace_lane: str | None = None,
|
|
181
|
+
) -> PerHeadGqaQkNormRope:
|
|
182
|
+
"""Bind the capacity-guarded per-head GQA implementation.
|
|
183
|
+
|
|
184
|
+
``q_norm_weight`` and ``k_norm_weight`` are checkpoint-native vectors
|
|
185
|
+
with layout ``[head_dim]``. The packed activation is laid out
|
|
186
|
+
``[Q heads..., K heads..., V heads...]``, each head contiguous at
|
|
187
|
+
``head_dim`` elements; no transposition or interleaving of the norm
|
|
188
|
+
weights is performed.
|
|
189
|
+
"""
|
|
190
|
+
return PerHeadGqaQkNormRope(
|
|
191
|
+
q_norm_weight,
|
|
192
|
+
k_norm_weight,
|
|
193
|
+
row_capacity=row_capacity,
|
|
194
|
+
q_heads=q_heads,
|
|
195
|
+
kv_heads=kv_heads,
|
|
196
|
+
head_dim=head_dim,
|
|
197
|
+
eps=eps,
|
|
198
|
+
workspace_lane=workspace_lane,
|
|
199
|
+
)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Packed-QKV implementation of projection-scope Q/K norm plus RoPE.
|
|
2
|
+
|
|
3
|
+
This is the Wan form of :mod:`qk_norm_rope`: Q and K are normalized over
|
|
4
|
+
the complete projection before the output is viewed as heads. It consumes
|
|
5
|
+
the contiguous output of a QKV pack and materializes Q/K/V attention
|
|
6
|
+
workspaces in one Hub kernel. Per-head Cosmos/Qwen/audio normalization is a
|
|
7
|
+
different implementation variant of the same catalog structure.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import torch
|
|
13
|
+
|
|
14
|
+
from .. import hub_kernel
|
|
15
|
+
from ...guard import PROCEED, GuardRefused, GuardedSeam
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ProjectionQkNormRope(GuardedSeam, torch.nn.Module):
|
|
19
|
+
"""Fixed-shape packed QKV postprocess for projection-scope RMSNorm."""
|
|
20
|
+
|
|
21
|
+
_frt_can_fallback = False
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
q_norm_weight: torch.Tensor,
|
|
26
|
+
k_norm_weight: torch.Tensor,
|
|
27
|
+
*,
|
|
28
|
+
batch: int,
|
|
29
|
+
tokens: int,
|
|
30
|
+
heads: int,
|
|
31
|
+
head_dim: int,
|
|
32
|
+
qkv_bias: torch.Tensor | None = None,
|
|
33
|
+
eps: float = 1e-6,
|
|
34
|
+
) -> None:
|
|
35
|
+
super().__init__()
|
|
36
|
+
if min(batch, tokens, heads, head_dim) <= 0:
|
|
37
|
+
raise ValueError("qk_norm_rope: shape dimensions must be positive")
|
|
38
|
+
if head_dim % 2:
|
|
39
|
+
raise ValueError("qk_norm_rope: head_dim must be even")
|
|
40
|
+
dim = int(heads) * int(head_dim)
|
|
41
|
+
if q_norm_weight.numel() != dim or k_norm_weight.numel() != dim:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"qk_norm_rope: projection-scope norm weights must each "
|
|
44
|
+
f"contain heads * head_dim = {dim} elements")
|
|
45
|
+
device = q_norm_weight.device
|
|
46
|
+
if k_norm_weight.device != device:
|
|
47
|
+
raise ValueError("qk_norm_rope: Q/K norm weights must share device")
|
|
48
|
+
if qkv_bias is None:
|
|
49
|
+
qkv_bias = torch.zeros(
|
|
50
|
+
3 * dim, device=device, dtype=torch.bfloat16)
|
|
51
|
+
if qkv_bias.numel() != 3 * dim or qkv_bias.device != device:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"qk_norm_rope: qkv_bias must contain 3 * heads * head_dim "
|
|
54
|
+
"elements on the norm-weight device")
|
|
55
|
+
|
|
56
|
+
self.batch = int(batch)
|
|
57
|
+
self.tokens = int(tokens)
|
|
58
|
+
self.heads = int(heads)
|
|
59
|
+
self.head_dim = int(head_dim)
|
|
60
|
+
self.dim = dim
|
|
61
|
+
self.eps = float(eps)
|
|
62
|
+
self._fn = hub_kernel(
|
|
63
|
+
"flashrt/flashrt-qkv-cache-rope",
|
|
64
|
+
">=1",
|
|
65
|
+
).qkv_split_bias_norm_rope_v_bf16
|
|
66
|
+
self.register_buffer(
|
|
67
|
+
"q_norm_weight",
|
|
68
|
+
q_norm_weight.detach().reshape(dim).to(torch.bfloat16).contiguous(),
|
|
69
|
+
)
|
|
70
|
+
self.register_buffer(
|
|
71
|
+
"k_norm_weight",
|
|
72
|
+
k_norm_weight.detach().reshape(dim).to(torch.bfloat16).contiguous(),
|
|
73
|
+
)
|
|
74
|
+
self.register_buffer(
|
|
75
|
+
"qkv_bias",
|
|
76
|
+
qkv_bias.detach().reshape(3 * dim).to(torch.bfloat16).contiguous(),
|
|
77
|
+
)
|
|
78
|
+
shape = (self.batch, self.tokens, self.heads, self.head_dim)
|
|
79
|
+
self.register_buffer(
|
|
80
|
+
"q_out",
|
|
81
|
+
torch.empty(shape, device=device, dtype=torch.bfloat16),
|
|
82
|
+
persistent=False,
|
|
83
|
+
)
|
|
84
|
+
self.register_buffer(
|
|
85
|
+
"k_out",
|
|
86
|
+
torch.empty(shape, device=device, dtype=torch.bfloat16),
|
|
87
|
+
persistent=False,
|
|
88
|
+
)
|
|
89
|
+
self.register_buffer(
|
|
90
|
+
"v_out",
|
|
91
|
+
torch.empty(shape, device=device, dtype=torch.bfloat16),
|
|
92
|
+
persistent=False,
|
|
93
|
+
)
|
|
94
|
+
self._frt_arm(
|
|
95
|
+
dtypes={torch.bfloat16},
|
|
96
|
+
device=device,
|
|
97
|
+
k=3 * dim,
|
|
98
|
+
rows=self.batch * self.tokens,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def forward(
|
|
102
|
+
self,
|
|
103
|
+
packed_qkv: torch.Tensor,
|
|
104
|
+
cos: torch.Tensor,
|
|
105
|
+
sin: torch.Tensor,
|
|
106
|
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
107
|
+
admitted = self._frt_admit(packed_qkv, cos, sin)
|
|
108
|
+
if admitted is not PROCEED:
|
|
109
|
+
return admitted
|
|
110
|
+
expected = (self.batch, self.tokens, 3 * self.dim)
|
|
111
|
+
if tuple(packed_qkv.shape) != expected:
|
|
112
|
+
raise GuardRefused(
|
|
113
|
+
f"qk_norm_rope: packed QKV shape {tuple(packed_qkv.shape)} "
|
|
114
|
+
f"(bound for {expected})")
|
|
115
|
+
freq_shape = (self.tokens, self.head_dim // 2)
|
|
116
|
+
if tuple(cos.shape) != freq_shape or tuple(sin.shape) != freq_shape:
|
|
117
|
+
raise GuardRefused(
|
|
118
|
+
f"qk_norm_rope: cos/sin must have shape {freq_shape}")
|
|
119
|
+
if (cos.dtype is not torch.float32 or sin.dtype is not torch.float32
|
|
120
|
+
or cos.device != packed_qkv.device
|
|
121
|
+
or sin.device != packed_qkv.device):
|
|
122
|
+
raise GuardRefused(
|
|
123
|
+
"qk_norm_rope: cos/sin must be float32 on the QKV device")
|
|
124
|
+
if not packed_qkv.is_contiguous():
|
|
125
|
+
raise GuardRefused("qk_norm_rope: packed QKV must be contiguous")
|
|
126
|
+
|
|
127
|
+
return self._fn(
|
|
128
|
+
packed_qkv,
|
|
129
|
+
self.qkv_bias,
|
|
130
|
+
self.q_norm_weight,
|
|
131
|
+
self.k_norm_weight,
|
|
132
|
+
cos,
|
|
133
|
+
sin,
|
|
134
|
+
self.heads,
|
|
135
|
+
self.head_dim,
|
|
136
|
+
rope_seq_len=self.tokens,
|
|
137
|
+
eps=self.eps,
|
|
138
|
+
q_out=self.q_out,
|
|
139
|
+
k_out=self.k_out,
|
|
140
|
+
v_out=self.v_out,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def bind_projection_qk_norm_rope(
|
|
145
|
+
q_norm_weight: torch.Tensor,
|
|
146
|
+
k_norm_weight: torch.Tensor,
|
|
147
|
+
*,
|
|
148
|
+
batch: int,
|
|
149
|
+
tokens: int,
|
|
150
|
+
heads: int,
|
|
151
|
+
head_dim: int,
|
|
152
|
+
qkv_bias: torch.Tensor | None = None,
|
|
153
|
+
eps: float = 1e-6,
|
|
154
|
+
) -> ProjectionQkNormRope:
|
|
155
|
+
"""Bind the fixed-shape projection-scope implementation."""
|
|
156
|
+
return ProjectionQkNormRope(
|
|
157
|
+
q_norm_weight,
|
|
158
|
+
k_norm_weight,
|
|
159
|
+
batch=batch,
|
|
160
|
+
tokens=tokens,
|
|
161
|
+
heads=heads,
|
|
162
|
+
head_dim=head_dim,
|
|
163
|
+
qkv_bias=qkv_bias,
|
|
164
|
+
eps=eps,
|
|
165
|
+
)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Non-quantising BF16 implementation of ``qkv_pack``.
|
|
2
|
+
|
|
3
|
+
Sibling projections that proved shared-input fixed-order dataflow are one
|
|
4
|
+
larger BF16 GEMM. No quantisation is introduced: this is the portable
|
|
5
|
+
structural form used when an end-to-end accuracy gate refuses FP8/FP4.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
|
|
12
|
+
import torch
|
|
13
|
+
|
|
14
|
+
from ...guard import CAST_OK, PROCEED, GuardedSeam
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PackedBf16Linear(GuardedSeam, torch.nn.Module):
|
|
18
|
+
_frt_host_attr = "host_linear"
|
|
19
|
+
_frt_can_fallback = True
|
|
20
|
+
|
|
21
|
+
def __init__(self, mods: Sequence[torch.nn.Linear], rows: int):
|
|
22
|
+
super().__init__()
|
|
23
|
+
if len(mods) < 2:
|
|
24
|
+
raise ValueError("qkv_pack: need at least two siblings")
|
|
25
|
+
kdims = {int(mod.weight.shape[1]) for mod in mods}
|
|
26
|
+
if len(kdims) != 1:
|
|
27
|
+
raise ValueError(f"qkv_pack: sibling K dims differ {kdims}")
|
|
28
|
+
self.splits = tuple(int(mod.weight.shape[0]) for mod in mods)
|
|
29
|
+
weight = torch.cat(
|
|
30
|
+
[mod.weight.detach() for mod in mods], dim=0).contiguous()
|
|
31
|
+
bias = None
|
|
32
|
+
if any(mod.bias is not None for mod in mods):
|
|
33
|
+
bias = torch.cat([
|
|
34
|
+
(mod.bias.detach() if mod.bias is not None else
|
|
35
|
+
torch.zeros(mod.weight.shape[0], device=mod.weight.device,
|
|
36
|
+
dtype=mod.weight.dtype))
|
|
37
|
+
for mod in mods
|
|
38
|
+
]).contiguous()
|
|
39
|
+
self.register_buffer("packed_weight", weight)
|
|
40
|
+
self.register_buffer("packed_bias", bias)
|
|
41
|
+
self.host_linear = mods[0]
|
|
42
|
+
for index, width in enumerate(self.splits[1:], 1):
|
|
43
|
+
self.register_buffer(
|
|
44
|
+
f"stash{index}", torch.empty(
|
|
45
|
+
rows, width, device=weight.device, dtype=weight.dtype))
|
|
46
|
+
self._frt_arm(dtypes=CAST_OK, device=weight.device,
|
|
47
|
+
k=next(iter(kdims)), row_capacity=rows)
|
|
48
|
+
|
|
49
|
+
def _run(self, flat: torch.Tensor) -> torch.Tensor:
|
|
50
|
+
y = torch.nn.functional.linear(
|
|
51
|
+
flat.to(self.packed_weight.dtype), self.packed_weight,
|
|
52
|
+
self.packed_bias)
|
|
53
|
+
offset = self.splits[0]
|
|
54
|
+
for index, width in enumerate(self.splits[1:], 1):
|
|
55
|
+
getattr(self, f"stash{index}")[:flat.shape[0]].copy_(
|
|
56
|
+
y[:, offset:offset + width])
|
|
57
|
+
offset += width
|
|
58
|
+
return y
|
|
59
|
+
|
|
60
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
61
|
+
admitted = self._frt_admit(x)
|
|
62
|
+
if admitted is not PROCEED:
|
|
63
|
+
return admitted
|
|
64
|
+
y = self._run(x.reshape(-1, x.shape[-1]))
|
|
65
|
+
out = y[:, :self.splits[0]].contiguous()
|
|
66
|
+
return out.reshape(*x.shape[:-1], self.splits[0]).to(x.dtype)
|
|
67
|
+
|
|
68
|
+
def __getattr__(self, name):
|
|
69
|
+
try:
|
|
70
|
+
return super().__getattr__(name)
|
|
71
|
+
except AttributeError:
|
|
72
|
+
return getattr(super().__getattr__("host_linear"), name)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Bf16StashReader(GuardedSeam, torch.nn.Module):
|
|
76
|
+
_frt_host_attr = "host_linear"
|
|
77
|
+
_frt_can_fallback = True
|
|
78
|
+
_frt_requires_sibling_order = True
|
|
79
|
+
|
|
80
|
+
def __init__(self, original: torch.nn.Linear,
|
|
81
|
+
packed: PackedBf16Linear, index: int):
|
|
82
|
+
super().__init__()
|
|
83
|
+
self.host_linear = original
|
|
84
|
+
self._packed = (packed,)
|
|
85
|
+
self.index = int(index)
|
|
86
|
+
head = packed._frt_guard
|
|
87
|
+
self._frt_arm(dtypes=head.dtypes, device=head.device, k=head.k,
|
|
88
|
+
row_capacity=head.row_capacity)
|
|
89
|
+
|
|
90
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
91
|
+
admitted = self._frt_admit(x)
|
|
92
|
+
if admitted is not PROCEED:
|
|
93
|
+
return admitted
|
|
94
|
+
rows = x.numel() // x.shape[-1]
|
|
95
|
+
out = getattr(self._packed[0], f"stash{self.index}")[:rows]
|
|
96
|
+
return out.reshape(*x.shape[:-1], out.shape[-1]).to(x.dtype)
|
|
97
|
+
|
|
98
|
+
def __getattr__(self, name):
|
|
99
|
+
try:
|
|
100
|
+
return super().__getattr__(name)
|
|
101
|
+
except AttributeError:
|
|
102
|
+
return getattr(super().__getattr__("host_linear"), name)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def bind_qkv_pack(mods: Sequence[torch.nn.Linear], *, rows: int):
|
|
106
|
+
packed = PackedBf16Linear(mods, rows)
|
|
107
|
+
return [packed, *(
|
|
108
|
+
Bf16StashReader(mod, packed, index)
|
|
109
|
+
for index, mod in enumerate(mods[1:], 1)
|
|
110
|
+
)]
|