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,955 @@
|
|
|
1
|
+
"""The fused static-FP8 launch chain over a plain-norm prefill tower.
|
|
2
|
+
|
|
3
|
+
Per layer: RMSNorm→FP8 (the host's ``(1+w)`` folded into the kernel's
|
|
4
|
+
weight), one merged-QKV FP8 GEMM, split+RoPE into a chain-owned
|
|
5
|
+
cache, dense non-causal attention over exactly the used keys (pads
|
|
6
|
+
trail, so a used-key count carries the host's mask), FP8 output
|
|
7
|
+
projection, and a fused residual+RMSNorm→FP8 into the merged gate/up
|
|
8
|
+
GEMM. The keys are un-permuted back to host rotate-half layout and
|
|
9
|
+
appended to the host's own cache object — the tower's whole product
|
|
10
|
+
is that cache, and every downstream reader keeps its contract.
|
|
11
|
+
|
|
12
|
+
Same equivalences as the sibling conditioned-stack chain (shared
|
|
13
|
+
helpers): the rotate-half↔adjacent-pair permutation on q/k rows, the
|
|
14
|
+
probe-calibrated activation scales, the attention ladder. The mask
|
|
15
|
+
fact here is simpler: one valid-query row names the used-key run;
|
|
16
|
+
pad-query rows are garbage-in-garbage-out by construction (their
|
|
17
|
+
hidden states feed only pad positions, their keys are masked by
|
|
18
|
+
every downstream mask).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import types
|
|
24
|
+
from typing import Any, Callable
|
|
25
|
+
|
|
26
|
+
import torch
|
|
27
|
+
|
|
28
|
+
from .. import KernelUnavailable, hub_kernel
|
|
29
|
+
from ...guard import GuardedSeam
|
|
30
|
+
from ..adarms_stack.fp8_chain import _make_attend
|
|
31
|
+
from ..chain_elements import (
|
|
32
|
+
ATTN_RUNGS, FP8_MAX, attention_rungs as _attention_rungs,
|
|
33
|
+
cache_kv as _cache_kv, fp8_weight as _fp8_weight,
|
|
34
|
+
gelu_tanh_like as _gelu_tanh_like,
|
|
35
|
+
interleave_rows as _interleave_rows)
|
|
36
|
+
|
|
37
|
+
GEMM_PACKAGE = "flashrt/fp8-gemm"
|
|
38
|
+
NORM_PACKAGE = "flashrt/flashrt-residual-norm-quant"
|
|
39
|
+
ROPE_PACKAGE = "flashrt/flashrt-qkv-cache-rope"
|
|
40
|
+
FFN_PACKAGE = "flashrt/transformer-fused-ops"
|
|
41
|
+
GEMM_SYMBOLS = ("fp8_linear_bf16",)
|
|
42
|
+
NORM_SYMBOLS = ("rms_norm_quant_fp8_static_bf16",
|
|
43
|
+
"residual_add_rms_norm_quant_fp8_static_bf16")
|
|
44
|
+
ROPE_SYMBOLS = ("qkv_split_rope_kvcache_bf16",)
|
|
45
|
+
FFN_SYMBOLS = ("gate_geglu_merged_quant_fp8_static_bf16",
|
|
46
|
+
"quantize_fp8_static_bf16")
|
|
47
|
+
FP4_GEMM_PACKAGE = "flashrt/fp4-gemm"
|
|
48
|
+
FP4_FUSE_PACKAGE = "flashrt/fp4-fused-ops"
|
|
49
|
+
|
|
50
|
+
#: the band table IS the recipe (same convention as the decoder
|
|
51
|
+
#: stack). The ``fp4`` row is the native encoder preset: the FFN pair
|
|
52
|
+
#: and the attention output projection ride NVFP4 with dynamic block
|
|
53
|
+
#: scales, QKV stays static FP8. The row qualifies when the fused
|
|
54
|
+
#: GEGLU producer ships a bf16 entry and the FP4 GEMM accepts the
|
|
55
|
+
#: prefix row band (a bind-time functional probe, not a device list).
|
|
56
|
+
BANDS: dict[str, dict] = {
|
|
57
|
+
"fp8": {"packages": (), "precision_rank": 0, "awq": 0.0},
|
|
58
|
+
"fp4": {"packages": (
|
|
59
|
+
(FP4_GEMM_PACKAGE, ("nvfp4_gemm_bias_bf16",
|
|
60
|
+
"quantize_fp4_sfa_bf16")),
|
|
61
|
+
(FP4_FUSE_PACKAGE,
|
|
62
|
+
("residual_add_rms_norm_quant_nvfp4_swizzled_bf16",
|
|
63
|
+
"gelu_mul_nvfp4_bf16"))),
|
|
64
|
+
"precision_rank": 1, "awq": 0.8, "smoke_floor": 0.95},
|
|
65
|
+
# the native published-tier P1 form (epilogue_hw): the gate/up
|
|
66
|
+
# weight is pairwise row-interleaved with the down-projection's AWQ
|
|
67
|
+
# 1/s folded into the up rows at pack time, ONE GEMM computes
|
|
68
|
+
# gelu(gate)*up in the epilogue and writes the down GEMM's packed
|
|
69
|
+
# FP4 input directly — the bf16 hidden intermediate, its quantize
|
|
70
|
+
# round-trip, and the separate combiner all leave the chain
|
|
71
|
+
"fp4_p1": {"packages": (
|
|
72
|
+
(FP4_GEMM_PACKAGE, ("nvfp4_gemm_bias_bf16",
|
|
73
|
+
"nvfp4_gemm_geglu_nvfp4_fp16",
|
|
74
|
+
"quantize_fp4_sfa_bf16")),
|
|
75
|
+
(FP4_FUSE_PACKAGE,
|
|
76
|
+
("residual_add_rms_norm_quant_nvfp4_swizzled_bf16",))),
|
|
77
|
+
# floor provenance: this band's own measured pair — tower smoke
|
|
78
|
+
# 0.796 at 17-layer coverage judged E2E captured-parity 0.99660
|
|
79
|
+
# PASS (the reference tier's own raw cosine sits at 0.9975).
|
|
80
|
+
# The old 0.95 floor's failing pair (smoke 0.90 -> E2E 0.742)
|
|
81
|
+
# came from the retired merged-GEMM form and does not transfer.
|
|
82
|
+
# The arm's 0.99 end-to-end parity gate stays the judge.
|
|
83
|
+
"precision_rank": 1, "awq": 0.8, "smoke_floor": 0.75,
|
|
84
|
+
"fp4_layers": "all_but_last"},
|
|
85
|
+
# the native constructor's precision-safe preset, verbatim:
|
|
86
|
+
# fp4_layers=(7, 8, 9) — the middle encoder FFN band. Floor
|
|
87
|
+
# calibrated per coverage (the gate scales with layer count):
|
|
88
|
+
# measured smoke 0.938 at 3-layer coverage judged E2E
|
|
89
|
+
# captured-parity 0.99828 PASS, while the 0.95 floor's provenance
|
|
90
|
+
# (smoke 0.90 -> E2E 0.742) came from a 15-layer arm and does not
|
|
91
|
+
# transfer to a 3-layer preset.
|
|
92
|
+
"fp4_p1_mid": {"packages": (
|
|
93
|
+
(FP4_GEMM_PACKAGE, ("nvfp4_gemm_bias_bf16",
|
|
94
|
+
"nvfp4_gemm_geglu_nvfp4_fp16",
|
|
95
|
+
"quantize_fp4_sfa_bf16")),
|
|
96
|
+
(FP4_FUSE_PACKAGE,
|
|
97
|
+
("residual_add_rms_norm_quant_nvfp4_swizzled_bf16",))),
|
|
98
|
+
"precision_rank": 1, "awq": 0.8, "smoke_floor": 0.92,
|
|
99
|
+
"fp4_layers": (7, 8, 9)},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#: whole-tower smoke over hidden states and every layer's cached K/V
|
|
103
|
+
#: — a min over ~37 tensors after 18 residual layers of static FP8,
|
|
104
|
+
#: so the compounding tail sits lower than a single-output floor
|
|
105
|
+
#: (measured: worst deep-layer V ≈0.97, hidden ≈0.96, while the
|
|
106
|
+
#: end-to-end action parity the arm gates on holds ≥0.9999). The
|
|
107
|
+
#: arm's 0.99 parity gate stays the judge.
|
|
108
|
+
SMOKE_FLOOR = 0.95
|
|
109
|
+
|
|
110
|
+
#: transition rung (author-gated): a native kernel module whose
|
|
111
|
+
#: cutlass_fp8_*_bf16out entries serve the rows the hub entry refuses.
|
|
112
|
+
#: Test-only — the hub rung wins the moment its package covers the
|
|
113
|
+
#: band, with no code change here.
|
|
114
|
+
FVK_SO_ENV = "FRT_FVK_SO"
|
|
115
|
+
_FVK_SITE = {"qkv": "cutlass_fp8_sq_bf16out",
|
|
116
|
+
"o": "cutlass_fp8_sq_bf16out",
|
|
117
|
+
"gu": "cutlass_fp8_t1_bf16out",
|
|
118
|
+
"dn": "cutlass_fp8_wide_bf16out"}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _load_fvk():
|
|
122
|
+
import importlib.util
|
|
123
|
+
import os
|
|
124
|
+
so = os.environ.get(FVK_SO_ENV)
|
|
125
|
+
if not so or not os.path.exists(so):
|
|
126
|
+
return None
|
|
127
|
+
spec = importlib.util.spec_from_file_location(
|
|
128
|
+
"flash_rt_kernels", so)
|
|
129
|
+
mod = importlib.util.module_from_spec(spec)
|
|
130
|
+
spec.loader.exec_module(mod)
|
|
131
|
+
return mod
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def missing_symbols(band: str = "fp8") -> list[str]:
|
|
135
|
+
gaps: list[str] = []
|
|
136
|
+
for repo, symbols in ((GEMM_PACKAGE, GEMM_SYMBOLS),
|
|
137
|
+
(NORM_PACKAGE, NORM_SYMBOLS),
|
|
138
|
+
(ROPE_PACKAGE, ROPE_SYMBOLS),
|
|
139
|
+
(FFN_PACKAGE, FFN_SYMBOLS)
|
|
140
|
+
) + BANDS[band]["packages"]:
|
|
141
|
+
try:
|
|
142
|
+
kern = hub_kernel(repo, ">=1")
|
|
143
|
+
except KernelUnavailable:
|
|
144
|
+
gaps.append(repo)
|
|
145
|
+
continue
|
|
146
|
+
gaps.extend(f"{repo}:{s}" for s in symbols
|
|
147
|
+
if not hasattr(kern, s))
|
|
148
|
+
if not _attention_rungs():
|
|
149
|
+
gaps.append("attention: " + " or ".join(
|
|
150
|
+
r[1] for r in ATTN_RUNGS))
|
|
151
|
+
return gaps
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class BoundPrefillFp8Chain(GuardedSeam, torch.nn.Module):
|
|
155
|
+
"""Bind-time state: FP8 weights, folded norm weights, caches."""
|
|
156
|
+
|
|
157
|
+
_frt_can_fallback = False
|
|
158
|
+
|
|
159
|
+
def __init__(self) -> None:
|
|
160
|
+
super().__init__()
|
|
161
|
+
self.table: list[dict] = []
|
|
162
|
+
self.dims: dict = {}
|
|
163
|
+
self.buf: dict = {}
|
|
164
|
+
self.rope = None
|
|
165
|
+
self.scaling = 1.0
|
|
166
|
+
self.eps = 1e-6
|
|
167
|
+
self.s_used = 0
|
|
168
|
+
self.out_ctor = None
|
|
169
|
+
self.kperm = None
|
|
170
|
+
self.kperm_inv = None
|
|
171
|
+
self.final_w = None
|
|
172
|
+
self.cache_type = None
|
|
173
|
+
self.kernels: dict = {}
|
|
174
|
+
self.band = "fp8"
|
|
175
|
+
#: region wire (armed by autobuild): per-layer (k, v) sink
|
|
176
|
+
#: views of a bound consumer's chain caches. The tower's K is
|
|
177
|
+
#: already in chain layout (same split kernel, same adjacent-
|
|
178
|
+
#: pair convention), so writing the sink is the identity of
|
|
179
|
+
#: the consumer's own gather — the consumer drops it in-graph.
|
|
180
|
+
self.prefix_sinks = None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _plain_norm_weight(norm) -> torch.Tensor | None:
|
|
184
|
+
"""The affine vector of an unconditioned RMS norm, or None."""
|
|
185
|
+
if getattr(norm, "dense", None) is not None:
|
|
186
|
+
return None
|
|
187
|
+
w = getattr(norm, "weight", None)
|
|
188
|
+
if w is None or getattr(w, "ndim", 0) != 1:
|
|
189
|
+
return None
|
|
190
|
+
return w
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _stack_parts(stack):
|
|
194
|
+
layers = list(stack.layers)
|
|
195
|
+
attn = layers[0].self_attn
|
|
196
|
+
head_dim = getattr(attn, "head_dim", None)
|
|
197
|
+
if not isinstance(head_dim, int):
|
|
198
|
+
raise ValueError("attention exposes no integer head_dim")
|
|
199
|
+
nh = attn.q_proj.out_features // head_dim
|
|
200
|
+
kv = attn.k_proj.out_features // head_dim
|
|
201
|
+
dim = attn.q_proj.in_features
|
|
202
|
+
hidden = layers[0].mlp.gate_proj.out_features
|
|
203
|
+
return layers, nh, kv, head_dim, dim, hidden
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _prefill_mask_facts(mask: torch.Tensor, seq: int) -> int | None:
|
|
207
|
+
"""One valid-query row names the used-key run ``[0, s_used)``."""
|
|
208
|
+
if mask.dim() != 4 or mask.shape[0] != 1 or mask.shape[-1] != seq:
|
|
209
|
+
return None
|
|
210
|
+
rows = mask[0, 0] if mask.shape[1] == 1 else mask[0, :1][0]
|
|
211
|
+
if rows.shape[-2] != seq:
|
|
212
|
+
return None
|
|
213
|
+
row = rows[0] == 0
|
|
214
|
+
s_used = int(row.sum())
|
|
215
|
+
if s_used < 1 or not bool(row[:s_used].all()):
|
|
216
|
+
return None
|
|
217
|
+
return s_used
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _build_rope(bound, stack, position_ids) -> bool:
|
|
221
|
+
hd = bound.dims["hd"]
|
|
222
|
+
half = hd // 2
|
|
223
|
+
dummy = torch.zeros(1, position_ids.shape[1], hd, device="cuda",
|
|
224
|
+
dtype=torch.float32)
|
|
225
|
+
cos, sin = stack.rotary_emb(dummy, position_ids.to("cuda"))
|
|
226
|
+
cos, sin = cos[0].float(), sin[0].float()
|
|
227
|
+
if not torch.allclose(cos[:, :half], cos[:, half:], atol=1e-5):
|
|
228
|
+
return False
|
|
229
|
+
rope = torch.empty(position_ids.shape[1], hd, device="cuda",
|
|
230
|
+
dtype=torch.bfloat16)
|
|
231
|
+
rope[:, 0::2] = cos[:, :half].to(torch.bfloat16)
|
|
232
|
+
rope[:, 1::2] = sin[:, :half].to(torch.bfloat16)
|
|
233
|
+
bound.rope = rope
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@torch.no_grad()
|
|
238
|
+
def _interleave_gu_rows(g16: torch.Tensor, u16: torch.Tensor,
|
|
239
|
+
inv_s_dn: torch.Tensor | None = None
|
|
240
|
+
) -> torch.Tensor:
|
|
241
|
+
"""Pairwise interleave gate/up rows along N for the fused GeGLU
|
|
242
|
+
epilogue GEMM (il[2j] = gate[j], il[2j+1] = up[j]) — the native
|
|
243
|
+
pack, verbatim. Any per-output-column scale must live in the
|
|
244
|
+
weights because the epilogue applies no per-column vector; the
|
|
245
|
+
down-projection AWQ inv_s is folded into the up rows
|
|
246
|
+
(gelu(g) * u * inv_s == gelu(g) * (u * inv_s))."""
|
|
247
|
+
if inv_s_dn is not None:
|
|
248
|
+
u16 = (u16.float() * inv_s_dn.float().unsqueeze(1)).to(u16.dtype)
|
|
249
|
+
il = torch.empty(2 * g16.shape[0], g16.shape[1],
|
|
250
|
+
dtype=g16.dtype, device=g16.device)
|
|
251
|
+
il[0::2] = g16
|
|
252
|
+
il[1::2] = u16
|
|
253
|
+
return il.contiguous()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _awq_scale(chan_amax: torch.Tensor, alpha: float) -> torch.Tensor:
|
|
257
|
+
"""The native per-input-channel AWQ pre-scale, verbatim:
|
|
258
|
+
``s = (a / a.mean())^alpha`` clamped to [0.25, 4]. The GEMM columns
|
|
259
|
+
carry ``s``; whichever element feeds the GEMM carries ``1/s`` —
|
|
260
|
+
the math is preserved exactly, only the quantizer sees a tamer
|
|
261
|
+
channel spread."""
|
|
262
|
+
a = chan_amax.float().clamp(min=1e-6)
|
|
263
|
+
return (a / a.mean()).pow(alpha).clamp(min=0.25, max=4.0)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _quantize(bound, layers, amax, chan=None) -> None:
|
|
267
|
+
nh, kv, hd = (bound.dims[k] for k in ("nh", "kv", "hd"))
|
|
268
|
+
fp4 = BANDS[bound.band]["packages"] != ()
|
|
269
|
+
alpha = BANDS[bound.band].get("awq", 0.0)
|
|
270
|
+
quant4 = (bound.kernels["kg4"].quantize_fp4_sfa_bf16
|
|
271
|
+
if fp4 else None)
|
|
272
|
+
# data-driven layer subset (the native preset runs 17 of 18): a
|
|
273
|
+
# layer whose down-projection input carries a >20x-median channel
|
|
274
|
+
# outlier stays on the calibrated FP8 form; the receipt records
|
|
275
|
+
# which layers rode the band
|
|
276
|
+
import os as _os3
|
|
277
|
+
skip: set = set()
|
|
278
|
+
preset = BANDS[bound.band].get("fp4_layers")
|
|
279
|
+
if preset == "all_but_last":
|
|
280
|
+
# the native published-tier preset: fp4_layers = range(17) —
|
|
281
|
+
# every layer except the last rides FP4, no data-driven subset
|
|
282
|
+
skip = {len(layers) - 1}
|
|
283
|
+
elif preset is not None:
|
|
284
|
+
skip = set(range(len(layers))) - set(preset)
|
|
285
|
+
else:
|
|
286
|
+
outlier = float(_os3.environ.get("FRT_PREFILL_FP4_OUTLIER",
|
|
287
|
+
"20"))
|
|
288
|
+
if fp4 and chan is not None:
|
|
289
|
+
for i in range(len(layers)):
|
|
290
|
+
v = chan.get((i, "dn"))
|
|
291
|
+
if v is not None and float(v.max()) > outlier * float(
|
|
292
|
+
v.median()):
|
|
293
|
+
skip.add(i)
|
|
294
|
+
bound.dims["fp4_skipped"] = sorted(skip)
|
|
295
|
+
for i, ly in enumerate(layers):
|
|
296
|
+
attn, mlp = ly.self_attn, ly.mlp
|
|
297
|
+
a_qkv, a_o, a_gu, a_dn = (amax[(i, s)] / FP8_MAX for s in
|
|
298
|
+
("qkv", "o", "gu", "dn"))
|
|
299
|
+
fold_in = (1.0 + ly.input_layernorm.weight.detach()
|
|
300
|
+
.float()).to(attn.q_proj.weight.device)
|
|
301
|
+
fold_post = (1.0 + ly.post_attention_layernorm.weight.detach()
|
|
302
|
+
.float()).to(attn.q_proj.weight.device)
|
|
303
|
+
qkv_w = torch.cat([
|
|
304
|
+
_interleave_rows(attn.q_proj.weight, nh, hd),
|
|
305
|
+
_interleave_rows(attn.k_proj.weight, kv, hd),
|
|
306
|
+
attn.v_proj.weight], dim=0).float() * fold_in[None, :]
|
|
307
|
+
gu_w = torch.cat([mlp.gate_proj.weight, mlp.up_proj.weight],
|
|
308
|
+
dim=0).float() * fold_post[None, :]
|
|
309
|
+
entry: dict[str, Any] = {}
|
|
310
|
+
for name, w, act in (("qkv", qkv_w, a_qkv),
|
|
311
|
+
("o", attn.o_proj.weight, a_o),
|
|
312
|
+
("gu", gu_w, a_gu),
|
|
313
|
+
("dn", mlp.down_proj.weight, a_dn)):
|
|
314
|
+
if fp4 and name != "qkv" and i not in skip:
|
|
315
|
+
# the native encoder preset: FFN pair and the output
|
|
316
|
+
# projection ride NVFP4 dynamic block scales, with the
|
|
317
|
+
# AWQ pre-scale carried by whatever feeds each GEMM —
|
|
318
|
+
# gu's 1/s by the norm producer's weight vector, dn's
|
|
319
|
+
# 1/s by the up rows it multiplies through
|
|
320
|
+
wq = w.detach().float().to("cuda")
|
|
321
|
+
il_hw = BANDS[bound.band].get("fp4_layers") is not None
|
|
322
|
+
if alpha and name == "gu" and chan is not None:
|
|
323
|
+
H = wq.shape[0] // 2
|
|
324
|
+
s_gu = _awq_scale(chan[(i, "gu")], alpha)
|
|
325
|
+
wq = wq * s_gu[None, :]
|
|
326
|
+
entry["inv_s_gu"] = (1.0 / s_gu).to(
|
|
327
|
+
torch.bfloat16).contiguous()
|
|
328
|
+
s_dn = _awq_scale(chan[(i, "dn")], alpha)
|
|
329
|
+
if il_hw:
|
|
330
|
+
# native il pack: dn's 1/s folds into the up
|
|
331
|
+
# rows at interleave time, not at runtime
|
|
332
|
+
entry["inv_s_dn"] = (1.0 / s_dn).to(
|
|
333
|
+
torch.float16).contiguous()
|
|
334
|
+
else:
|
|
335
|
+
wq[H:] = wq[H:] / s_dn[:, None]
|
|
336
|
+
entry["s_dn"] = s_dn
|
|
337
|
+
if alpha and name == "dn" and chan is not None:
|
|
338
|
+
wq = wq * entry["s_dn"][None, :]
|
|
339
|
+
mse = (None if il_hw else
|
|
340
|
+
getattr(bound.kernels["kg4"],
|
|
341
|
+
"quantize_fp4_sfa_mse_fp16", None))
|
|
342
|
+
|
|
343
|
+
def _pack_fp4(mat):
|
|
344
|
+
if mse is not None:
|
|
345
|
+
# the archived fp4 band's per-block MSE scales;
|
|
346
|
+
# the native published tier quantizes plain RTN
|
|
347
|
+
return mse(mat.to(torch.float16).contiguous(),
|
|
348
|
+
is_sfb=True)
|
|
349
|
+
return quant4(mat.to(torch.bfloat16).contiguous(),
|
|
350
|
+
is_sfb=True)
|
|
351
|
+
|
|
352
|
+
if il_hw and name == "gu":
|
|
353
|
+
H = wq.shape[0] // 2
|
|
354
|
+
entry["gu_il"] = _pack_fp4(_interleave_gu_rows(
|
|
355
|
+
wq[:H].contiguous(), wq[H:].contiguous(),
|
|
356
|
+
entry.get("inv_s_dn")))
|
|
357
|
+
continue
|
|
358
|
+
entry[name] = _pack_fp4(wq)
|
|
359
|
+
continue
|
|
360
|
+
packed, w_scale = _fp8_weight(w)
|
|
361
|
+
entry[name] = packed
|
|
362
|
+
entry[f"a_{name}"] = act * w_scale
|
|
363
|
+
entry["sc_qkv"] = torch.tensor([a_qkv], device="cuda",
|
|
364
|
+
dtype=torch.float32)
|
|
365
|
+
entry["sc_gu"] = torch.tensor([a_gu], device="cuda",
|
|
366
|
+
dtype=torch.float32)
|
|
367
|
+
entry["inv_o"] = 1.0 / a_o if a_o > 0 else 1.0
|
|
368
|
+
entry["inv_dn"] = 1.0 / a_dn if a_dn > 0 else 1.0
|
|
369
|
+
def _t(v):
|
|
370
|
+
return torch.tensor([v], device="cuda",
|
|
371
|
+
dtype=torch.float32)
|
|
372
|
+
if "a_gu" in entry:
|
|
373
|
+
entry["t_wsc_gu"] = _t(entry["a_gu"] / a_gu)
|
|
374
|
+
entry["t_sc_dn"] = _t(a_dn)
|
|
375
|
+
if "a_dn" in entry:
|
|
376
|
+
entry["t_wsc_dn"] = _t(entry["a_dn"] / a_dn)
|
|
377
|
+
entry["t_sc_o"] = _t(a_o)
|
|
378
|
+
bound.table.append(entry)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _alloc(bound) -> None:
|
|
382
|
+
S, D, nh, kv, hd, H = (bound.dims[k] for k in
|
|
383
|
+
("seq", "dim", "nh", "kv", "hd", "hidden"))
|
|
384
|
+
dev, bf = "cuda", torch.bfloat16
|
|
385
|
+
b = bound.buf
|
|
386
|
+
b["res"] = torch.empty(S, D, device=dev, dtype=bf)
|
|
387
|
+
b["xn8"] = torch.empty(S, D, device=dev, dtype=torch.float8_e4m3fn)
|
|
388
|
+
b["qkv"] = torch.empty(S, (nh + 2 * kv) * hd, device=dev, dtype=bf)
|
|
389
|
+
b["q"] = torch.empty(1, S, nh, hd, device=dev, dtype=bf)
|
|
390
|
+
kc = torch.zeros(1, S, kv, hd, device=dev, dtype=bf)
|
|
391
|
+
vc = torch.zeros(1, S, kv, hd, device=dev, dtype=bf)
|
|
392
|
+
L = bound.dims["layers"]
|
|
393
|
+
b["kc"] = [kc] * L
|
|
394
|
+
b["vc"] = [vc] * L
|
|
395
|
+
b["fg"] = torch.empty(S, D, device=dev, dtype=bf)
|
|
396
|
+
b["gu"] = torch.empty(S, 2 * H, device=dev, dtype=bf)
|
|
397
|
+
b["hid8"] = torch.empty(S, H, device=dev,
|
|
398
|
+
dtype=torch.float8_e4m3fn)
|
|
399
|
+
b["o8"] = torch.empty(S, nh * hd, device=dev,
|
|
400
|
+
dtype=torch.float8_e4m3fn)
|
|
401
|
+
b["seqused"] = torch.full((1,), bound.s_used, device=dev,
|
|
402
|
+
dtype=torch.int32)
|
|
403
|
+
b["ones_w"] = torch.ones(D, device=dev, dtype=bf)
|
|
404
|
+
if BANDS[bound.band]["packages"] != ():
|
|
405
|
+
quant4 = bound.kernels["kg4"].quantize_fp4_sfa_bf16
|
|
406
|
+
b["zero_x"] = torch.zeros(S, D, device=dev, dtype=bf)
|
|
407
|
+
b["xp4"], b["xsf4"] = quant4(b["zero_x"])
|
|
408
|
+
b["op4"], b["osf4"] = quant4(
|
|
409
|
+
torch.zeros(S, nh * hd, device=dev, dtype=bf))
|
|
410
|
+
b["hp4"], b["hsf4"] = quant4(
|
|
411
|
+
torch.zeros(S, H, device=dev, dtype=bf))
|
|
412
|
+
b["zb"] = {n: torch.zeros(n, device=dev, dtype=bf)
|
|
413
|
+
for n in (2 * H, D)}
|
|
414
|
+
if BANDS[bound.band].get("fp4_layers") is not None:
|
|
415
|
+
b["il_scratch"] = torch.empty(S, H, device=dev,
|
|
416
|
+
dtype=torch.uint8)
|
|
417
|
+
b["out_full"] = torch.zeros(bound.dims["seq_full"], D, device=dev,
|
|
418
|
+
dtype=bf)
|
|
419
|
+
b["att"] = torch.empty(1, S, nh, hd, device=dev, dtype=bf)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _make_run(bound: BoundPrefillFp8Chain):
|
|
423
|
+
kg = bound.kernels["kg"]
|
|
424
|
+
kn = bound.kernels["kn"]
|
|
425
|
+
kr = bound.kernels["kr"]
|
|
426
|
+
kf = bound.kernels["kf"]
|
|
427
|
+
attend = bound.kernels["attend"]
|
|
428
|
+
S, D, nh, kv, hd, H, L = (bound.dims[k] for k in
|
|
429
|
+
("seq", "dim", "nh", "kv", "hd",
|
|
430
|
+
"hidden", "layers"))
|
|
431
|
+
b = bound.buf
|
|
432
|
+
table = bound.table
|
|
433
|
+
eps = bound.eps
|
|
434
|
+
qkv3 = b["qkv"].view(1, S, (nh + 2 * kv) * hd)
|
|
435
|
+
fp8 = torch.float8_e4m3fn
|
|
436
|
+
kperm_inv = bound.kperm_inv
|
|
437
|
+
kh = b["kc"][0].view(S, hd)
|
|
438
|
+
|
|
439
|
+
gemm = bound.kernels["gemm"]
|
|
440
|
+
|
|
441
|
+
if BANDS[bound.band]["packages"] != ():
|
|
442
|
+
kg4 = bound.kernels["kg4"]
|
|
443
|
+
kf4 = bound.kernels["kf4"]
|
|
444
|
+
gemm4 = kg4.nvfp4_gemm_bias_bf16
|
|
445
|
+
quant4 = kg4.quantize_fp4_sfa_bf16
|
|
446
|
+
norm4 = kf4.residual_add_rms_norm_quant_nvfp4_swizzled_bf16
|
|
447
|
+
xp4, xsf4 = b["xp4"], b["xsf4"]
|
|
448
|
+
op4, osf4 = b["op4"], b["osf4"]
|
|
449
|
+
hp4, hsf4 = b["hp4"], b["hsf4"]
|
|
450
|
+
zb = b["zb"]
|
|
451
|
+
|
|
452
|
+
def out_project(att2, e):
|
|
453
|
+
if not isinstance(e["o"], tuple):
|
|
454
|
+
kf.quantize_fp8_static_bf16(att2, e["t_sc_o"],
|
|
455
|
+
out=b["o8"])
|
|
456
|
+
gemm(e, "o", b["o8"], b["fg"])
|
|
457
|
+
return
|
|
458
|
+
quant4(att2, op4, osf4)
|
|
459
|
+
gemm4(op4, e["o"][0], osf4, e["o"][1], zb[D], out=b["fg"])
|
|
460
|
+
|
|
461
|
+
def _ffn_fp8(res, e):
|
|
462
|
+
# an outlier layer kept on the calibrated FP8 form
|
|
463
|
+
kn.rms_norm_quant_fp8_static_bf16(
|
|
464
|
+
res, b["ones_w"], e["sc_gu"], eps, out=b["xn8"])
|
|
465
|
+
gemm(e, "gu", b["xn8"], b["gu"])
|
|
466
|
+
kf.gate_geglu_merged_quant_fp8_static_bf16(
|
|
467
|
+
b["gu"], e["t_sc_dn"], out=b["hid8"])
|
|
468
|
+
gemm(e, "dn", b["hid8"], b["fg"])
|
|
469
|
+
|
|
470
|
+
if BANDS[bound.band].get("fp4_layers") is not None:
|
|
471
|
+
geglu_hw = kg4.nvfp4_gemm_geglu_nvfp4_fp16
|
|
472
|
+
il_scr = b["il_scratch"]
|
|
473
|
+
|
|
474
|
+
def ffn_project(res, e):
|
|
475
|
+
if "gu_il" not in e:
|
|
476
|
+
_ffn_fp8(res, e)
|
|
477
|
+
return
|
|
478
|
+
w_gu = e.get("inv_s_gu")
|
|
479
|
+
norm4(res, b["zero_x"],
|
|
480
|
+
w_gu if w_gu is not None else b["ones_w"], eps,
|
|
481
|
+
packed=xp4, sfa=xsf4)
|
|
482
|
+
# skinny=False: the encoder-M schedule (element-benched
|
|
483
|
+
# 301us vs the narrow-N decoder schedule's 484us at
|
|
484
|
+
# M=657 x 2H=32768 x K=2048)
|
|
485
|
+
geglu_hw(xp4, e["gu_il"][0], xsf4, e["gu_il"][1],
|
|
486
|
+
skinny=False, scratch=il_scr,
|
|
487
|
+
out_packed=hp4, out_sfa=hsf4)
|
|
488
|
+
gemm4(hp4, e["dn"][0], hsf4, e["dn"][1], zb[D],
|
|
489
|
+
out=b["fg"])
|
|
490
|
+
else:
|
|
491
|
+
geglu4 = kf4.gelu_mul_nvfp4_bf16
|
|
492
|
+
|
|
493
|
+
def ffn_project(res, e):
|
|
494
|
+
if not isinstance(e.get("gu"), tuple):
|
|
495
|
+
_ffn_fp8(res, e)
|
|
496
|
+
return
|
|
497
|
+
# x=0 keeps the residual untouched; the producer emits
|
|
498
|
+
# rms(res) straight to packed FP4 for the gate/up GEMM.
|
|
499
|
+
# Its weight vector doubles as the AWQ 1/s carrier.
|
|
500
|
+
w_gu = e.get("inv_s_gu")
|
|
501
|
+
norm4(res, b["zero_x"],
|
|
502
|
+
w_gu if w_gu is not None else b["ones_w"], eps,
|
|
503
|
+
packed=xp4, sfa=xsf4)
|
|
504
|
+
gemm4(xp4, e["gu"][0], xsf4, e["gu"][1], zb[2 * H],
|
|
505
|
+
out=b["gu"])
|
|
506
|
+
geglu4(b["gu"], packed=hp4, sfa=hsf4)
|
|
507
|
+
gemm4(hp4, e["dn"][0], hsf4, e["dn"][1], zb[D],
|
|
508
|
+
out=b["fg"])
|
|
509
|
+
else:
|
|
510
|
+
def out_project(att2, e):
|
|
511
|
+
kf.quantize_fp8_static_bf16(att2, e["t_sc_o"],
|
|
512
|
+
out=b["o8"])
|
|
513
|
+
gemm(e, "o", b["o8"], b["fg"])
|
|
514
|
+
|
|
515
|
+
def ffn_project(res, e):
|
|
516
|
+
kn.rms_norm_quant_fp8_static_bf16(
|
|
517
|
+
res, b["ones_w"], e["sc_gu"], eps, out=b["xn8"])
|
|
518
|
+
gemm(e, "gu", b["xn8"], b["gu"])
|
|
519
|
+
kf.gate_geglu_merged_quant_fp8_static_bf16(
|
|
520
|
+
b["gu"], e["t_sc_dn"], out=b["hid8"])
|
|
521
|
+
gemm(e, "dn", b["hid8"], b["fg"])
|
|
522
|
+
|
|
523
|
+
rf = torch.profiler.record_function
|
|
524
|
+
|
|
525
|
+
S_full = bound.dims["seq_full"]
|
|
526
|
+
|
|
527
|
+
def run(x2d, pkv):
|
|
528
|
+
res = b["res"]
|
|
529
|
+
res.copy_(x2d[:S])
|
|
530
|
+
for l in range(L):
|
|
531
|
+
e = table[l]
|
|
532
|
+
if l == 0:
|
|
533
|
+
kn.rms_norm_quant_fp8_static_bf16(
|
|
534
|
+
res, b["ones_w"], e["sc_qkv"], eps, out=b["xn8"])
|
|
535
|
+
gemm(e, "qkv", b["xn8"], b["qkv"])
|
|
536
|
+
with rf("pf:rope"):
|
|
537
|
+
kr.qkv_split_rope_kvcache_bf16(
|
|
538
|
+
qkv3, bound.rope, nh, kv, hd, 0, q_out=b["q"],
|
|
539
|
+
k_cache=b["kc"][0], v_cache=b["vc"][0],
|
|
540
|
+
max_seq_len=S)
|
|
541
|
+
if bound.prefix_sinks is not None:
|
|
542
|
+
with rf("pf:wire"):
|
|
543
|
+
dk, dv = bound.prefix_sinks[l]
|
|
544
|
+
dk.copy_(kh)
|
|
545
|
+
dv.copy_(b["vc"][0].view(S, hd))
|
|
546
|
+
if pkv is not None:
|
|
547
|
+
# fresh tensors per layer: the cache keeps references,
|
|
548
|
+
# and the shared buffers are overwritten next layer
|
|
549
|
+
with rf("pf:cache"):
|
|
550
|
+
k_host = torch.index_select(kh, -1, kperm_inv)
|
|
551
|
+
pkv.update(k_host.view(1, 1, S, hd),
|
|
552
|
+
b["vc"][0].reshape(1, 1, S, hd)
|
|
553
|
+
.clone(), l)
|
|
554
|
+
with rf("pf:attn"):
|
|
555
|
+
att2 = attend(l)
|
|
556
|
+
with rf("pf:o"):
|
|
557
|
+
out_project(att2, e)
|
|
558
|
+
res.add_(b["fg"])
|
|
559
|
+
ffn_project(res, e)
|
|
560
|
+
if l < L - 1:
|
|
561
|
+
nxt = table[l + 1]
|
|
562
|
+
kn.residual_add_rms_norm_quant_fp8_static_bf16(
|
|
563
|
+
res, b["fg"], b["ones_w"], nxt["sc_qkv"], eps,
|
|
564
|
+
out=b["xn8"])
|
|
565
|
+
else:
|
|
566
|
+
res.add_(b["fg"])
|
|
567
|
+
fin = res.float()
|
|
568
|
+
fin = fin * torch.rsqrt(fin.square().mean(-1, keepdim=True)
|
|
569
|
+
+ eps)
|
|
570
|
+
b["out_full"][:S] = (fin * bound.final_w).to(torch.bfloat16)
|
|
571
|
+
return bound.out_ctor(
|
|
572
|
+
last_hidden_state=b["out_full"].view(1, S_full, D),
|
|
573
|
+
past_key_values=pkv)
|
|
574
|
+
|
|
575
|
+
return run
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def bind_prefill_fp8_chain(model, root: str,
|
|
579
|
+
probe: Callable[[], Any],
|
|
580
|
+
band: str = "fp8") -> dict:
|
|
581
|
+
"""Bind the chain onto the tower at ``root``; adapter contract out."""
|
|
582
|
+
try:
|
|
583
|
+
kg = hub_kernel(GEMM_PACKAGE, ">=1")
|
|
584
|
+
kn = hub_kernel(NORM_PACKAGE, ">=1")
|
|
585
|
+
kr = hub_kernel(ROPE_PACKAGE, ">=1")
|
|
586
|
+
kf = hub_kernel(FFN_PACKAGE, ">=1")
|
|
587
|
+
kg4 = kf4 = None
|
|
588
|
+
if BANDS[band]["packages"]:
|
|
589
|
+
kg4 = hub_kernel(FP4_GEMM_PACKAGE, ">=1")
|
|
590
|
+
kf4 = hub_kernel(FP4_FUSE_PACKAGE, ">=1")
|
|
591
|
+
except KernelUnavailable as exc:
|
|
592
|
+
return {"refused": f"prefill_{band}_chain: {exc}"}
|
|
593
|
+
rungs = _attention_rungs()
|
|
594
|
+
gaps = missing_symbols(band=band)
|
|
595
|
+
if gaps:
|
|
596
|
+
return {"refused": "prefill_fp8_chain missing: "
|
|
597
|
+
f"{', '.join(gaps)}"}
|
|
598
|
+
|
|
599
|
+
stack = model.get_submodule(root) if root else model
|
|
600
|
+
layers, nh, kv, hd, dim, hidden = _stack_parts(stack)
|
|
601
|
+
if kv != 1:
|
|
602
|
+
return {"refused": f"prefill_fp8_chain: kv_heads {kv} outside "
|
|
603
|
+
"the single-KV band"}
|
|
604
|
+
if not _gelu_tanh_like(layers[0].mlp.act_fn):
|
|
605
|
+
return {"refused": "prefill_fp8_chain: FFN activation is not "
|
|
606
|
+
"tanh-GELU"}
|
|
607
|
+
scalings = {float(ly.self_attn.scaling) for ly in layers}
|
|
608
|
+
if len(scalings) != 1:
|
|
609
|
+
return {"refused": "prefill_fp8_chain: per-layer attention "
|
|
610
|
+
"scaling differs"}
|
|
611
|
+
final_w = _plain_norm_weight(stack.norm)
|
|
612
|
+
if final_w is None or any(
|
|
613
|
+
_plain_norm_weight(ly.input_layernorm) is None
|
|
614
|
+
or _plain_norm_weight(ly.post_attention_layernorm) is None
|
|
615
|
+
for ly in layers):
|
|
616
|
+
return {"refused": "prefill_fp8_chain: a norm is not a plain "
|
|
617
|
+
"affine RMS norm"}
|
|
618
|
+
|
|
619
|
+
bound = BoundPrefillFp8Chain()
|
|
620
|
+
bound.kernels = {"kg": kg, "kn": kn, "kr": kr, "kf": kf,
|
|
621
|
+
"kg4": kg4, "kf4": kf4}
|
|
622
|
+
bound.band = band
|
|
623
|
+
bound.scaling = scalings.pop()
|
|
624
|
+
bound.dims = {"nh": nh, "kv": kv, "hd": hd, "dim": dim,
|
|
625
|
+
"hidden": hidden, "layers": len(layers)}
|
|
626
|
+
bound.eps = float(getattr(stack.norm, "eps", 1e-6))
|
|
627
|
+
bound.final_w = (1.0 + final_w.detach().float()).to("cuda")
|
|
628
|
+
|
|
629
|
+
# ---- one probe: the prefill call, mask fact, amax sites ----
|
|
630
|
+
calls: list[dict] = []
|
|
631
|
+
amax: dict[tuple[int, str], float] = {}
|
|
632
|
+
samples_amax: list[dict] = []
|
|
633
|
+
samples_chan: list[dict] = []
|
|
634
|
+
# the quantizer statistic is a high element quantile, not the raw
|
|
635
|
+
# peak: one outlier element must cost itself saturation, not the
|
|
636
|
+
# whole tensor's resolution (the house two-level rule, single-call
|
|
637
|
+
# analog). FRT_CALIB_Q=1.0 restores the raw peak for A/B runs.
|
|
638
|
+
import os as _os
|
|
639
|
+
calib_q = float(_os.environ.get("FRT_CALIB_Q", "1.0"))
|
|
640
|
+
|
|
641
|
+
def note(site, unfold=None):
|
|
642
|
+
safe = (unfold.abs().clamp(min=1e-6)
|
|
643
|
+
if unfold is not None else None)
|
|
644
|
+
|
|
645
|
+
def hook(_m, args):
|
|
646
|
+
x = args[0].detach().float().abs()
|
|
647
|
+
if safe is not None:
|
|
648
|
+
# the chain quantizes the pure-RMS output and folds the
|
|
649
|
+
# norm's (1+w) into the GEMM weight columns (the tight
|
|
650
|
+
# distribution is what FP8 can afford); measure what
|
|
651
|
+
# the chain will actually see. A (1+w)=0 channel's
|
|
652
|
+
# host output is exactly zero — its weight column is
|
|
653
|
+
# zero too, so its recovered magnitude may be anything
|
|
654
|
+
# and the clamp keeps it out of the statistic.
|
|
655
|
+
x = x / safe
|
|
656
|
+
x = x.flatten()
|
|
657
|
+
if calib_q >= 1.0 or x.numel() < 1000:
|
|
658
|
+
peak = float(x.amax())
|
|
659
|
+
else:
|
|
660
|
+
k = max(1, int(x.numel() * (1.0 - calib_q)))
|
|
661
|
+
peak = float(x.kthvalue(x.numel() - k + 1).values)
|
|
662
|
+
amax[site] = max(amax.get(site, 0.0), peak)
|
|
663
|
+
return hook
|
|
664
|
+
|
|
665
|
+
chan: dict = {}
|
|
666
|
+
|
|
667
|
+
def cnote(site, unfold=None):
|
|
668
|
+
safe = (unfold.abs().clamp(min=1e-6).cuda()
|
|
669
|
+
if unfold is not None else None)
|
|
670
|
+
|
|
671
|
+
def hook(_m, args):
|
|
672
|
+
x = args[0].detach()
|
|
673
|
+
v = x.float().abs().reshape(-1, x.shape[-1]).amax(dim=0)
|
|
674
|
+
if safe is not None:
|
|
675
|
+
# the chain's producer emits the pure-RMS output — the
|
|
676
|
+
# host folds (1+w) in; measure what the chain will see
|
|
677
|
+
v = v / safe
|
|
678
|
+
prev = chan.get(site)
|
|
679
|
+
chan[site] = (v if prev is None
|
|
680
|
+
else torch.maximum(prev, v))
|
|
681
|
+
return hook
|
|
682
|
+
|
|
683
|
+
hooks = []
|
|
684
|
+
for i, ly in enumerate(layers):
|
|
685
|
+
fold_in = (1.0 + ly.input_layernorm.weight.detach().float())
|
|
686
|
+
fold_post = (1.0 + ly.post_attention_layernorm.weight
|
|
687
|
+
.detach().float())
|
|
688
|
+
hooks.append(ly.self_attn.q_proj.register_forward_pre_hook(
|
|
689
|
+
note((i, "qkv"), fold_in)))
|
|
690
|
+
hooks.append(ly.self_attn.o_proj.register_forward_pre_hook(
|
|
691
|
+
note((i, "o"))))
|
|
692
|
+
hooks.append(ly.mlp.gate_proj.register_forward_pre_hook(
|
|
693
|
+
note((i, "gu"), fold_post)))
|
|
694
|
+
hooks.append(ly.mlp.down_proj.register_forward_pre_hook(
|
|
695
|
+
note((i, "dn"))))
|
|
696
|
+
if BANDS[band]["packages"] and BANDS[band].get("awq"):
|
|
697
|
+
hooks.append(ly.mlp.gate_proj.register_forward_pre_hook(
|
|
698
|
+
cnote((i, "gu"), fold_post)))
|
|
699
|
+
hooks.append(ly.mlp.down_proj.register_forward_pre_hook(
|
|
700
|
+
cnote((i, "dn"))))
|
|
701
|
+
|
|
702
|
+
saved_probe = stack.__dict__.get("forward")
|
|
703
|
+
host_forward = stack.forward
|
|
704
|
+
|
|
705
|
+
def capturing(_self, *args, **kwargs):
|
|
706
|
+
out = host_forward(*args, **kwargs)
|
|
707
|
+
embs = kwargs.get("inputs_embeds")
|
|
708
|
+
pkv = getattr(out, "past_key_values", None)
|
|
709
|
+
hidden_out = getattr(out, "last_hidden_state", None)
|
|
710
|
+
if (embs is not None and kwargs.get("use_cache")
|
|
711
|
+
and hidden_out is not None
|
|
712
|
+
and embs.dim() == 3 and embs.shape[0] == 1
|
|
713
|
+
and kwargs.get("adarms_cond") is None):
|
|
714
|
+
entry = {
|
|
715
|
+
"x": embs.detach().clone(),
|
|
716
|
+
"mask": kwargs.get("attention_mask"),
|
|
717
|
+
"pos": kwargs.get("position_ids"),
|
|
718
|
+
"out": hidden_out.detach().clone(),
|
|
719
|
+
"out_type": type(out),
|
|
720
|
+
"cache_type": type(pkv) if pkv is not None else None,
|
|
721
|
+
"kv": [tuple(t.detach().clone()
|
|
722
|
+
for t in _cache_kv(pkv, i))
|
|
723
|
+
for i in range(len(layers))] if pkv is not None
|
|
724
|
+
else None,
|
|
725
|
+
}
|
|
726
|
+
entry["mask"] = (entry["mask"].detach().clone()
|
|
727
|
+
if entry["mask"] is not None else None)
|
|
728
|
+
entry["pos"] = (entry["pos"].detach().clone()
|
|
729
|
+
if entry["pos"] is not None else None)
|
|
730
|
+
calls.append(entry)
|
|
731
|
+
return out
|
|
732
|
+
|
|
733
|
+
stack.forward = types.MethodType(capturing, stack)
|
|
734
|
+
try:
|
|
735
|
+
sample_fns = list(getattr(probe, "samples", None) or (probe,))
|
|
736
|
+
with torch.inference_mode():
|
|
737
|
+
for fn in sample_fns:
|
|
738
|
+
fn()
|
|
739
|
+
if amax:
|
|
740
|
+
samples_amax.append(dict(amax))
|
|
741
|
+
amax.clear()
|
|
742
|
+
if chan:
|
|
743
|
+
samples_chan.append({k: v.clone()
|
|
744
|
+
for k, v in chan.items()})
|
|
745
|
+
chan.clear()
|
|
746
|
+
finally:
|
|
747
|
+
for hook in hooks:
|
|
748
|
+
hook.remove()
|
|
749
|
+
if saved_probe is not None:
|
|
750
|
+
stack.forward = saved_probe
|
|
751
|
+
else:
|
|
752
|
+
stack.__dict__.pop("forward", None)
|
|
753
|
+
|
|
754
|
+
if not calls:
|
|
755
|
+
return {"refused": "prefill_fp8_chain: probe never made a "
|
|
756
|
+
"prefill call"}
|
|
757
|
+
# the house two-level statistic: max over calls within one sample
|
|
758
|
+
# (the hooks), then the calibration percentile across samples —
|
|
759
|
+
# one sample degenerates to today's raw max exactly
|
|
760
|
+
if samples_amax:
|
|
761
|
+
import numpy as np
|
|
762
|
+
|
|
763
|
+
from flash_rt.core.calibration import accumulate_amax
|
|
764
|
+
sites = set().union(*(d.keys() for d in samples_amax))
|
|
765
|
+
amax = {site: float(accumulate_amax(
|
|
766
|
+
[np.asarray([d[site]]) for d in samples_amax
|
|
767
|
+
if site in d], percentile=99.9)[0]) for site in sites}
|
|
768
|
+
ckeys = (set().union(*(d.keys() for d in samples_chan))
|
|
769
|
+
if samples_chan else set())
|
|
770
|
+
chan = {k: torch.from_numpy(accumulate_amax(
|
|
771
|
+
[d[k].cpu().numpy() for d in samples_chan if k in d],
|
|
772
|
+
percentile=99.9)).to("cuda")
|
|
773
|
+
for k in ckeys}
|
|
774
|
+
first = calls[0]
|
|
775
|
+
if first["mask"] is None or first["pos"] is None:
|
|
776
|
+
return {"refused": "prefill_fp8_chain: probe call carried no "
|
|
777
|
+
"mask or positions"}
|
|
778
|
+
S = first["x"].shape[1]
|
|
779
|
+
s_used = _prefill_mask_facts(first["mask"], S)
|
|
780
|
+
if s_used is None:
|
|
781
|
+
return {"refused": "prefill_fp8_chain: mask outside the "
|
|
782
|
+
"[valid|pad] band"}
|
|
783
|
+
dead = [(i, s) for i in range(len(layers))
|
|
784
|
+
for s in ("qkv", "o", "gu", "dn")
|
|
785
|
+
if (i, s) not in amax or not amax[(i, s)] > 0.0]
|
|
786
|
+
if dead:
|
|
787
|
+
return {"refused": "prefill_fp8_chain: dead quantizer "
|
|
788
|
+
f"site(s) {dead[:6]} of {len(dead)}; "
|
|
789
|
+
f"sample={ {k: amax[k] for k in list(amax)[:4]} }"}
|
|
790
|
+
|
|
791
|
+
bound.dims["seq"] = s_used
|
|
792
|
+
bound.dims["seq_full"] = S
|
|
793
|
+
bound.s_used = s_used
|
|
794
|
+
bound.out_ctor = first["out_type"]
|
|
795
|
+
bound.cache_type = first["cache_type"]
|
|
796
|
+
# the GEMM entry's row band is a runtime fact, not a symbol fact:
|
|
797
|
+
# probe it at the bound shape, then fall to the transition rung
|
|
798
|
+
gemm_mode = "hub"
|
|
799
|
+
try:
|
|
800
|
+
kg.fp8_linear_bf16(
|
|
801
|
+
torch.zeros(S, dim, device="cuda",
|
|
802
|
+
dtype=torch.float8_e4m3fn),
|
|
803
|
+
torch.zeros(dim, dim, device="cuda",
|
|
804
|
+
dtype=torch.float8_e4m3fn), alpha=1.0)
|
|
805
|
+
except Exception as hub_exc: # noqa: BLE001 — a band fact
|
|
806
|
+
fvk = _load_fvk()
|
|
807
|
+
if fvk is None or any(not hasattr(fvk, n)
|
|
808
|
+
for n in set(_FVK_SITE.values())):
|
|
809
|
+
return {"refused": "prefill_fp8_chain: the FP8 GEMM entry "
|
|
810
|
+
f"refuses {S} rows ({hub_exc})"}
|
|
811
|
+
gemm_mode = "fvk"
|
|
812
|
+
bound.kernels["fvk"] = fvk
|
|
813
|
+
if gemm_mode == "hub":
|
|
814
|
+
def gemm(e, site, x8, out):
|
|
815
|
+
kg.fp8_linear_bf16(x8, e[site], alpha=e["a_" + site],
|
|
816
|
+
out=out)
|
|
817
|
+
else:
|
|
818
|
+
fvk = bound.kernels["fvk"]
|
|
819
|
+
fns = {site: getattr(fvk, name)
|
|
820
|
+
for site, name in _FVK_SITE.items()}
|
|
821
|
+
|
|
822
|
+
def gemm(e, site, x8, out):
|
|
823
|
+
w = e[site]
|
|
824
|
+
stream = torch.cuda.current_stream().cuda_stream
|
|
825
|
+
fns[site](x8.data_ptr(), w.data_ptr(), out.data_ptr(),
|
|
826
|
+
x8.shape[0], w.shape[0], w.shape[1],
|
|
827
|
+
e["a_" + site], 0.0, stream)
|
|
828
|
+
bound.kernels["gemm"] = gemm
|
|
829
|
+
if not _build_rope(bound, stack, first["pos"]):
|
|
830
|
+
return {"refused": "prefill_fp8_chain: rotary table is not "
|
|
831
|
+
"half-duplicated"}
|
|
832
|
+
_quantize(bound, layers, amax, chan=chan)
|
|
833
|
+
_alloc(bound)
|
|
834
|
+
half = hd // 2
|
|
835
|
+
kperm = torch.empty(hd, dtype=torch.long, device="cuda")
|
|
836
|
+
kperm[0::2] = torch.arange(half, device="cuda")
|
|
837
|
+
kperm[1::2] = torch.arange(half, hd, device="cuda")
|
|
838
|
+
bound.kperm = kperm
|
|
839
|
+
bound.kperm_inv = torch.argsort(kperm)
|
|
840
|
+
|
|
841
|
+
attend, attn_mode = None, None
|
|
842
|
+
rung_trail = []
|
|
843
|
+
for mode, kern in rungs:
|
|
844
|
+
try:
|
|
845
|
+
candidate = _make_attend(bound, mode, kern)
|
|
846
|
+
candidate(0)
|
|
847
|
+
torch.cuda.synchronize()
|
|
848
|
+
except Exception as exc: # noqa: BLE001 — a dead rung, next
|
|
849
|
+
rung_trail.append(f"{mode}: {type(exc).__name__}")
|
|
850
|
+
continue
|
|
851
|
+
attend, attn_mode = candidate, mode
|
|
852
|
+
break
|
|
853
|
+
if attend is None:
|
|
854
|
+
return {"refused": "prefill_fp8_chain: no attention rung "
|
|
855
|
+
f"executes here ({'; '.join(rung_trail)})"}
|
|
856
|
+
bound.kernels["attend"] = attend
|
|
857
|
+
|
|
858
|
+
run = _make_run(bound)
|
|
859
|
+
guard = bound._frt_arm(dtypes=(torch.bfloat16,),
|
|
860
|
+
device=torch.device("cuda"))
|
|
861
|
+
guard.notes["n_layers"] = len(layers)
|
|
862
|
+
guard.notes["s_used"] = s_used
|
|
863
|
+
guard.notes["attention"] = attn_mode
|
|
864
|
+
guard.notes["gemm"] = gemm_mode
|
|
865
|
+
|
|
866
|
+
# ---- smoke: hidden states and the cache the tower left behind ----
|
|
867
|
+
import os as _os2
|
|
868
|
+
# the smoke floor is a band fact: the FP8 calibration set the 0.95
|
|
869
|
+
# line at its own compounding depth; a deeper-compounding band
|
|
870
|
+
# carries its own line and the arm's captured parity gate (0.99
|
|
871
|
+
# against the stock reference) stays the end-to-end judge either way
|
|
872
|
+
band_floor = BANDS[band].get("smoke_floor", SMOKE_FLOOR)
|
|
873
|
+
floor = float(_os2.environ.get("FRT_PREFILL_SMOKE_FLOOR",
|
|
874
|
+
str(band_floor)))
|
|
875
|
+
scores: list[tuple[float, str]] = []
|
|
876
|
+
with torch.inference_mode():
|
|
877
|
+
for c in calls:
|
|
878
|
+
fresh = c["cache_type"]() if c["cache_type"] else None
|
|
879
|
+
got = run(c["x"][0].to(torch.bfloat16), fresh)
|
|
880
|
+
valid = slice(0, s_used)
|
|
881
|
+
cos = torch.nn.functional.cosine_similarity(
|
|
882
|
+
got.last_hidden_state[0, valid].float().flatten(),
|
|
883
|
+
c["out"][0, valid].float().flatten(), dim=0)
|
|
884
|
+
scores.append((float(cos), "hidden"))
|
|
885
|
+
if fresh is not None and c["kv"] is not None:
|
|
886
|
+
for l in range(len(layers)):
|
|
887
|
+
gk, gv = _cache_kv(fresh, l)
|
|
888
|
+
hk, hv = c["kv"][l]
|
|
889
|
+
for tag, a, bb in (("k", gk, hk), ("v", gv, hv)):
|
|
890
|
+
cc = torch.nn.functional.cosine_similarity(
|
|
891
|
+
a[..., :s_used, :].float().flatten(),
|
|
892
|
+
bb[..., :s_used, :].float().flatten(),
|
|
893
|
+
dim=0)
|
|
894
|
+
scores.append((float(cc), f"{tag}{l}"))
|
|
895
|
+
worst = min(s0 for s0, _ in scores) if scores else None
|
|
896
|
+
trail = sorted(scores)[:4]
|
|
897
|
+
if worst is None or worst < floor:
|
|
898
|
+
return {"refused": f"prefill_fp8_chain smoke cos {worst} < "
|
|
899
|
+
f"{floor}; worst={trail}"}
|
|
900
|
+
guard.notes["smoke_cos"] = round(worst, 6)
|
|
901
|
+
guard.notes["smoke_worst"] = [(round(a, 4), t) for a, t in trail]
|
|
902
|
+
|
|
903
|
+
# ---- route ----
|
|
904
|
+
saved = stack.__dict__.get("forward")
|
|
905
|
+
x_shape = tuple(first["x"].shape)
|
|
906
|
+
mask_shape = tuple(first["mask"].shape)
|
|
907
|
+
|
|
908
|
+
def routed(_self, *args, **kwargs):
|
|
909
|
+
compiling = torch.compiler.is_compiling()
|
|
910
|
+
capturing_now = (False if compiling
|
|
911
|
+
else torch.cuda.is_current_stream_capturing())
|
|
912
|
+
eager = not compiling and not capturing_now
|
|
913
|
+
if eager:
|
|
914
|
+
guard.calls += 1
|
|
915
|
+
embs = kwargs.get("inputs_embeds")
|
|
916
|
+
mask = kwargs.get("attention_mask")
|
|
917
|
+
pkv = kwargs.get("past_key_values")
|
|
918
|
+
ok = (not args and embs is not None
|
|
919
|
+
and kwargs.get("use_cache")
|
|
920
|
+
and kwargs.get("adarms_cond") is None
|
|
921
|
+
and tuple(embs.shape) == x_shape
|
|
922
|
+
and (mask is None or tuple(mask.shape) == mask_shape))
|
|
923
|
+
if not ok:
|
|
924
|
+
if not eager:
|
|
925
|
+
raise RuntimeError(
|
|
926
|
+
"prefill_fp8_chain: out-of-contract call during "
|
|
927
|
+
"capture/compile — fix the eager path first")
|
|
928
|
+
guard.fallbacks += 1
|
|
929
|
+
guard.last_reason = "call outside the routed contract"
|
|
930
|
+
return host_forward(*args, **kwargs)
|
|
931
|
+
if pkv is None and bound.cache_type is not None:
|
|
932
|
+
pkv = bound.cache_type()
|
|
933
|
+
return run(embs[0].to(torch.bfloat16), pkv)
|
|
934
|
+
|
|
935
|
+
def enable() -> None:
|
|
936
|
+
stack.forward = types.MethodType(routed, stack)
|
|
937
|
+
|
|
938
|
+
def disable() -> None:
|
|
939
|
+
if saved is not None:
|
|
940
|
+
stack.forward = saved
|
|
941
|
+
elif "forward" in stack.__dict__:
|
|
942
|
+
del stack.forward
|
|
943
|
+
|
|
944
|
+
def revert() -> None:
|
|
945
|
+
disable()
|
|
946
|
+
bound.table.clear()
|
|
947
|
+
bound.buf.clear()
|
|
948
|
+
|
|
949
|
+
enable()
|
|
950
|
+
return {
|
|
951
|
+
"observed": {f"{root}::prefill_fp8_chain": bound},
|
|
952
|
+
"revert": [revert],
|
|
953
|
+
"toggle": (enable, disable),
|
|
954
|
+
"smoke_cos": worst,
|
|
955
|
+
}
|