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,143 @@
|
|
|
1
|
+
"""BF16 packed-QKV bias/split/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
|
+
|
|
10
|
+
|
|
11
|
+
class PackedBiasQkvRope(GuardedSeam, torch.nn.Module):
|
|
12
|
+
"""Fixed-capacity wrapper around one formal Hub custom op."""
|
|
13
|
+
|
|
14
|
+
_frt_can_fallback = False
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
qkv_bias: torch.Tensor,
|
|
19
|
+
*,
|
|
20
|
+
row_capacity: int,
|
|
21
|
+
q_heads: int,
|
|
22
|
+
kv_heads: int,
|
|
23
|
+
head_dim: int,
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__()
|
|
26
|
+
if min(row_capacity, q_heads, kv_heads, head_dim) <= 0:
|
|
27
|
+
raise ValueError(
|
|
28
|
+
"qkv_rope: capacities and head dimensions must be positive"
|
|
29
|
+
)
|
|
30
|
+
if head_dim % 2 or head_dim > 256:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
"qkv_rope: head_dim must be even and no larger than 256"
|
|
33
|
+
)
|
|
34
|
+
width = (q_heads + 2 * kv_heads) * head_dim
|
|
35
|
+
if qkv_bias.numel() != width:
|
|
36
|
+
raise ValueError(f"qkv_rope: bias must contain {width} elements")
|
|
37
|
+
if qkv_bias.dtype is not torch.bfloat16 or not qkv_bias.is_cuda:
|
|
38
|
+
raise ValueError("qkv_rope: bias must be CUDA BF16")
|
|
39
|
+
|
|
40
|
+
self.row_capacity = int(row_capacity)
|
|
41
|
+
self.q_heads = int(q_heads)
|
|
42
|
+
self.kv_heads = int(kv_heads)
|
|
43
|
+
self.head_dim = int(head_dim)
|
|
44
|
+
self.width = int(width)
|
|
45
|
+
self._fn = hub_kernel(
|
|
46
|
+
"flashrt/flashrt-qkv-cache-rope", ">=1"
|
|
47
|
+
).qkv_split_bias_rope_bf16
|
|
48
|
+
self.register_buffer("qkv_bias", qkv_bias.detach().contiguous())
|
|
49
|
+
device = qkv_bias.device
|
|
50
|
+
self.register_buffer(
|
|
51
|
+
"q_out",
|
|
52
|
+
torch.empty(
|
|
53
|
+
row_capacity,
|
|
54
|
+
q_heads,
|
|
55
|
+
head_dim,
|
|
56
|
+
device=device,
|
|
57
|
+
dtype=torch.bfloat16,
|
|
58
|
+
),
|
|
59
|
+
persistent=False,
|
|
60
|
+
)
|
|
61
|
+
self.register_buffer(
|
|
62
|
+
"k_out",
|
|
63
|
+
torch.empty(
|
|
64
|
+
row_capacity,
|
|
65
|
+
kv_heads,
|
|
66
|
+
head_dim,
|
|
67
|
+
device=device,
|
|
68
|
+
dtype=torch.bfloat16,
|
|
69
|
+
),
|
|
70
|
+
persistent=False,
|
|
71
|
+
)
|
|
72
|
+
self.register_buffer(
|
|
73
|
+
"v_out", torch.empty_like(self.k_out), persistent=False
|
|
74
|
+
)
|
|
75
|
+
self._frt_arm(
|
|
76
|
+
dtypes={torch.bfloat16}, device=device, k=width, row_capacity=row_capacity
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def forward(
|
|
80
|
+
self,
|
|
81
|
+
packed_qkv: torch.Tensor,
|
|
82
|
+
cos: torch.Tensor,
|
|
83
|
+
sin: torch.Tensor,
|
|
84
|
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
85
|
+
admitted = self._frt_admit(packed_qkv, cos, sin)
|
|
86
|
+
if admitted is not PROCEED:
|
|
87
|
+
return admitted
|
|
88
|
+
if packed_qkv.dim() != 3 or packed_qkv.shape[0] != 1:
|
|
89
|
+
raise GuardRefused(
|
|
90
|
+
"qkv_rope: packed QKV must have shape (1, T, width)"
|
|
91
|
+
)
|
|
92
|
+
_, tokens, width = packed_qkv.shape
|
|
93
|
+
if width != self.width or tokens > self.row_capacity:
|
|
94
|
+
raise GuardRefused("qkv_rope: packed QKV is outside the bound capacity")
|
|
95
|
+
if not packed_qkv.is_contiguous():
|
|
96
|
+
raise GuardRefused("qkv_rope: packed QKV must be contiguous")
|
|
97
|
+
expected = {(1, tokens, self.head_dim // 2), (1, tokens, self.head_dim)}
|
|
98
|
+
if tuple(cos.shape) not in expected or tuple(sin.shape) not in expected:
|
|
99
|
+
raise GuardRefused(
|
|
100
|
+
"qkv_rope: cos/sin shape does not match the token/head form"
|
|
101
|
+
)
|
|
102
|
+
if (
|
|
103
|
+
cos.dtype is not torch.float32
|
|
104
|
+
or sin.dtype is not torch.float32
|
|
105
|
+
or cos.device != packed_qkv.device
|
|
106
|
+
or sin.device != packed_qkv.device
|
|
107
|
+
or not cos.is_contiguous()
|
|
108
|
+
or not sin.is_contiguous()
|
|
109
|
+
):
|
|
110
|
+
raise GuardRefused("qkv_rope: cos/sin must be contiguous CUDA FP32")
|
|
111
|
+
|
|
112
|
+
q_out = self.q_out[:tokens].view(1, tokens, self.q_heads, self.head_dim)
|
|
113
|
+
k_out = self.k_out[:tokens].view(1, tokens, self.kv_heads, self.head_dim)
|
|
114
|
+
v_out = self.v_out[:tokens].view(1, tokens, self.kv_heads, self.head_dim)
|
|
115
|
+
return self._fn(
|
|
116
|
+
packed_qkv,
|
|
117
|
+
self.qkv_bias,
|
|
118
|
+
cos,
|
|
119
|
+
sin,
|
|
120
|
+
self.q_heads,
|
|
121
|
+
self.kv_heads,
|
|
122
|
+
self.head_dim,
|
|
123
|
+
q_out=q_out,
|
|
124
|
+
k_out=k_out,
|
|
125
|
+
v_out=v_out,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def bind_packed_bias_qkv_rope(
|
|
130
|
+
qkv_bias: torch.Tensor,
|
|
131
|
+
*,
|
|
132
|
+
row_capacity: int,
|
|
133
|
+
q_heads: int,
|
|
134
|
+
kv_heads: int,
|
|
135
|
+
head_dim: int,
|
|
136
|
+
) -> PackedBiasQkvRope:
|
|
137
|
+
return PackedBiasQkvRope(
|
|
138
|
+
qkv_bias,
|
|
139
|
+
row_capacity=row_capacity,
|
|
140
|
+
q_heads=q_heads,
|
|
141
|
+
kv_heads=kv_heads,
|
|
142
|
+
head_dim=head_dim,
|
|
143
|
+
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Step-table memoization for step-constant conditioning producers.
|
|
2
|
+
|
|
3
|
+
Diffusion-style hosts recompute ``dense(cond)`` in every layer at every
|
|
4
|
+
denoise step, yet ``cond`` depends only on the timestep: over a tick the
|
|
5
|
+
producer emits a small fixed set of vectors. This implementation
|
|
6
|
+
replaces such a producer with a calibrated table — the distinct
|
|
7
|
+
conditioning vectors seen during calibration and the outputs the host's
|
|
8
|
+
own producer computed for them. At runtime the module locates the
|
|
9
|
+
current step by nearest-neighbour match against the stored vectors
|
|
10
|
+
(pure tensor ops: safe under both compile tracing and graph capture,
|
|
11
|
+
no Python state) and gathers the stored row instead of running the
|
|
12
|
+
GEMV. Outputs are bit-identical to calibration by construction; the
|
|
13
|
+
match itself is arbitrated by the caller's parity gate.
|
|
14
|
+
|
|
15
|
+
Qualification refuses hosts whose conditioning is not actually
|
|
16
|
+
step-quantized: if calibration sees more distinct vectors than
|
|
17
|
+
``max_steps``, the producer depends on more than the step and a table
|
|
18
|
+
would silently mis-hit — that host keeps its GEMV.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import torch
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StepTableLinear(torch.nn.Module):
|
|
27
|
+
"""Drop-in for a ``nn.Linear`` whose input is step-constant."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, original: torch.nn.Module, conds: torch.Tensor,
|
|
30
|
+
table: torch.Tensor,
|
|
31
|
+
locator: "StepTableLinear | None" = None):
|
|
32
|
+
super().__init__()
|
|
33
|
+
self.host_linear = original
|
|
34
|
+
# match score: argmax(2 c·k - |k|^2) == nearest neighbour.
|
|
35
|
+
# Sibling tables fed by the same conditioning stream share the
|
|
36
|
+
# locator buffers (same tensor objects), so a compiling host
|
|
37
|
+
# sees one common subexpression per step instead of one locate
|
|
38
|
+
# per table — the redundant matches fold away.
|
|
39
|
+
if locator is not None:
|
|
40
|
+
self.register_buffer("conds_t", locator.conds_t)
|
|
41
|
+
self.register_buffer("cond_sq", locator.cond_sq)
|
|
42
|
+
else:
|
|
43
|
+
self.register_buffer("conds_t", conds.float().t().contiguous())
|
|
44
|
+
self.register_buffer("cond_sq",
|
|
45
|
+
(conds.float() ** 2).sum(-1).contiguous())
|
|
46
|
+
self.register_buffer("table", table.contiguous())
|
|
47
|
+
|
|
48
|
+
def forward(self, cond: torch.Tensor) -> torch.Tensor:
|
|
49
|
+
flat = cond.reshape(-1, cond.shape[-1]).float()
|
|
50
|
+
scores = 2.0 * (flat @ self.conds_t) - self.cond_sq
|
|
51
|
+
idx = scores.argmax(dim=-1)
|
|
52
|
+
out = self.table.index_select(0, idx)
|
|
53
|
+
return out.reshape(*cond.shape[:-1], out.shape[-1])
|
|
54
|
+
|
|
55
|
+
def __getattr__(self, name):
|
|
56
|
+
try:
|
|
57
|
+
return super().__getattr__(name)
|
|
58
|
+
except AttributeError:
|
|
59
|
+
return getattr(super().__getattr__("host_linear"), name)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def bind_step_table(original: torch.nn.Module,
|
|
63
|
+
calibration: list[tuple[torch.Tensor, torch.Tensor]],
|
|
64
|
+
*, max_steps: int = 64,
|
|
65
|
+
dedup_rtol: float = 1e-5,
|
|
66
|
+
share_locator_with: StepTableLinear | None = None
|
|
67
|
+
) -> StepTableLinear:
|
|
68
|
+
"""Build a step table from real ``(cond, out)`` calibration pairs.
|
|
69
|
+
|
|
70
|
+
Pairs come from hooking the host's own producer over at least one
|
|
71
|
+
full tick, so the table rows are exactly what the host computed.
|
|
72
|
+
Refuses (``ValueError``) when the distinct-vector count exceeds
|
|
73
|
+
``max_steps`` — the producer is then not step-constant and a table
|
|
74
|
+
would alias different inputs onto one row.
|
|
75
|
+
|
|
76
|
+
``share_locator_with``: a sibling table bound from the same
|
|
77
|
+
conditioning stream; when its stored vectors match this
|
|
78
|
+
calibration exactly (same set, same order), the new table reuses
|
|
79
|
+
the sibling's locator buffers so redundant per-table step matches
|
|
80
|
+
can fold into one. On any mismatch the table keeps its own
|
|
81
|
+
locator — sharing is an optimization, never an assumption.
|
|
82
|
+
"""
|
|
83
|
+
if not calibration:
|
|
84
|
+
raise ValueError("step_table: no calibration pairs captured")
|
|
85
|
+
conds: list[torch.Tensor] = []
|
|
86
|
+
outs: list[torch.Tensor] = []
|
|
87
|
+
for cond, out in calibration:
|
|
88
|
+
c = cond.detach().reshape(-1, cond.shape[-1])
|
|
89
|
+
o = out.detach().reshape(-1, out.shape[-1])
|
|
90
|
+
for row in range(c.shape[0]):
|
|
91
|
+
cr = c[row]
|
|
92
|
+
if any(torch.allclose(cr, seen, rtol=dedup_rtol,
|
|
93
|
+
atol=1e-6 * cr.abs().max().item() + 1e-12)
|
|
94
|
+
for seen in conds):
|
|
95
|
+
continue
|
|
96
|
+
conds.append(cr.clone())
|
|
97
|
+
outs.append(o[row].clone())
|
|
98
|
+
if len(conds) > max_steps:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"step_table: >{max_steps} distinct conditioning "
|
|
101
|
+
"vectors — producer is not step-constant, keeping "
|
|
102
|
+
"the host GEMV")
|
|
103
|
+
stacked = torch.stack(conds)
|
|
104
|
+
locator = None
|
|
105
|
+
if (share_locator_with is not None
|
|
106
|
+
and share_locator_with.conds_t.shape[1] == stacked.shape[0]
|
|
107
|
+
and torch.allclose(share_locator_with.conds_t.t(),
|
|
108
|
+
stacked.float().to(
|
|
109
|
+
share_locator_with.conds_t.device),
|
|
110
|
+
rtol=dedup_rtol, atol=1e-6)):
|
|
111
|
+
locator = share_locator_with
|
|
112
|
+
return StepTableLinear(original, stacked, torch.stack(outs),
|
|
113
|
+
locator=locator)
|
|
File without changes
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""FP8-static implementation of the ``vision_ffn`` structure.
|
|
2
|
+
|
|
3
|
+
Composes the fused FP8 fc1 -> GELU -> fc2 block (biases included) from
|
|
4
|
+
the ``flashrt/flashrt-fp8-ffn`` Hub kernel. ``bind`` covers the full
|
|
5
|
+
structure boundary; ``bind_mlp_seam`` covers the normed-input ->
|
|
6
|
+
ffn-output slice for hosts whose replaceable module boundary is the MLP.
|
|
7
|
+
Weights use the checkpoint-native (out, in) layout directly.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from functools import lru_cache
|
|
14
|
+
from typing import Callable, Mapping, Sequence
|
|
15
|
+
|
|
16
|
+
import torch
|
|
17
|
+
|
|
18
|
+
from ...guard import CAST_OK, FP8_ONLY, PROCEED, GuardedSeam
|
|
19
|
+
|
|
20
|
+
KERNEL_DEP = {
|
|
21
|
+
"provider": "hf",
|
|
22
|
+
"repo": "flashrt/flashrt-fp8-ffn",
|
|
23
|
+
"version": ">=1",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_FP8 = torch.float8_e4m3fn
|
|
27
|
+
_FP8_MAX = 448.0
|
|
28
|
+
|
|
29
|
+
SUPPORT = {
|
|
30
|
+
"D": {"min": 512, "max": 16384},
|
|
31
|
+
"F": {"min": 1024, "max": 16384},
|
|
32
|
+
"m_classes": ("small", "medium", "large"),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@lru_cache(maxsize=1)
|
|
37
|
+
def _kernel():
|
|
38
|
+
from flashrt_structures.impls import hub_kernel
|
|
39
|
+
|
|
40
|
+
return hub_kernel(KERNEL_DEP["repo"], KERNEL_DEP["version"])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _amax_scale(tensor: torch.Tensor) -> torch.Tensor:
|
|
44
|
+
return (tensor.float().abs().max() / _FP8_MAX).clamp(min=1e-8)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _quantize(tensor: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
|
|
48
|
+
return (tensor.float() / scale).clamp(-_FP8_MAX, _FP8_MAX).to(_FP8)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class BoundVisionFfnFp8:
|
|
53
|
+
"""Bound callable for the full structure boundary."""
|
|
54
|
+
|
|
55
|
+
fused_mlp: Callable[..., torch.Tensor]
|
|
56
|
+
w_norm: torch.Tensor | None
|
|
57
|
+
b_norm: torch.Tensor | None
|
|
58
|
+
fc1_fp8: torch.Tensor
|
|
59
|
+
b_fc1: torch.Tensor
|
|
60
|
+
fc2_fp8: torch.Tensor
|
|
61
|
+
b_fc2: torch.Tensor
|
|
62
|
+
input_scale: torch.Tensor
|
|
63
|
+
fc1_scale: torch.Tensor
|
|
64
|
+
hidden_scale: torch.Tensor
|
|
65
|
+
fc2_scale: torch.Tensor
|
|
66
|
+
eps: float
|
|
67
|
+
in_dtype: str = "bf16"
|
|
68
|
+
|
|
69
|
+
def ffn(self, normed: torch.Tensor) -> torch.Tensor:
|
|
70
|
+
"""The normed-input -> ffn-output slice (no norm, no residual).
|
|
71
|
+
|
|
72
|
+
On the BF16 entry the kernel quantizes the input itself; on the
|
|
73
|
+
FP8 entry an upstream producer already did, with the shared
|
|
74
|
+
activation scale, so the input passes straight through."""
|
|
75
|
+
shape = normed.shape
|
|
76
|
+
if getattr(self, "in_dtype", "bf16") == "fp8_static":
|
|
77
|
+
out = self.fused_mlp(
|
|
78
|
+
normed.reshape(-1, shape[-1]),
|
|
79
|
+
self.fc1_fp8, self.b_fc1, self.fc2_fp8, self.b_fc2,
|
|
80
|
+
self.input_scale.view(1), self.fc1_scale.view(1),
|
|
81
|
+
self.hidden_scale.view(1), self.fc2_scale.view(1))
|
|
82
|
+
return out.reshape(*shape[:-1], out.shape[-1])
|
|
83
|
+
out = self.fused_mlp(
|
|
84
|
+
normed.reshape(-1, shape[-1]).to(torch.bfloat16).contiguous(),
|
|
85
|
+
self.fc1_fp8,
|
|
86
|
+
self.b_fc1,
|
|
87
|
+
self.fc2_fp8,
|
|
88
|
+
self.b_fc2,
|
|
89
|
+
self.input_scale.view(1),
|
|
90
|
+
self.fc1_scale.view(1),
|
|
91
|
+
self.hidden_scale.view(1),
|
|
92
|
+
self.fc2_scale.view(1),
|
|
93
|
+
)
|
|
94
|
+
return out.reshape(shape).to(normed.dtype)
|
|
95
|
+
|
|
96
|
+
def __call__(self, x: torch.Tensor) -> torch.Tensor:
|
|
97
|
+
h = torch.nn.functional.layer_norm(
|
|
98
|
+
x.float(), (x.shape[-1],),
|
|
99
|
+
(self.w_norm.float() if self.w_norm is not None else None),
|
|
100
|
+
(self.b_norm.float() if self.b_norm is not None else None),
|
|
101
|
+
self.eps).to(x.dtype)
|
|
102
|
+
return x + self.ffn(h).to(x.dtype)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class FusedGeluMlp(GuardedSeam, torch.nn.Module):
|
|
106
|
+
"""MLP-seam module: the host keeps its own norm and residual.
|
|
107
|
+
|
|
108
|
+
``original`` is retained whole (host MLP naming varies across model
|
|
109
|
+
families), and attribute lookups fall through to it so hosts that
|
|
110
|
+
introspect the module they call keep working. It is also the per-call
|
|
111
|
+
way back: an input outside the calibrated form runs the host MLP.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
_frt_host_attr = "host_mlp"
|
|
115
|
+
_frt_can_fallback = True
|
|
116
|
+
|
|
117
|
+
def __init__(self, bound: BoundVisionFfnFp8,
|
|
118
|
+
original: torch.nn.Module | None = None):
|
|
119
|
+
super().__init__()
|
|
120
|
+
self._bound = bound
|
|
121
|
+
if original is not None:
|
|
122
|
+
self.host_mlp = original
|
|
123
|
+
self._frt_arm(
|
|
124
|
+
dtypes=(FP8_ONLY if bound.in_dtype == "fp8_static" else CAST_OK),
|
|
125
|
+
device=bound.fc1_fp8.device,
|
|
126
|
+
k=int(bound.fc1_fp8.shape[1]))
|
|
127
|
+
|
|
128
|
+
def __getattr__(self, name):
|
|
129
|
+
try:
|
|
130
|
+
return super().__getattr__(name)
|
|
131
|
+
except AttributeError:
|
|
132
|
+
if name == "host_mlp":
|
|
133
|
+
raise
|
|
134
|
+
return getattr(super().__getattr__("host_mlp"), name)
|
|
135
|
+
|
|
136
|
+
def forward(self, hidden: torch.Tensor) -> torch.Tensor:
|
|
137
|
+
admitted = self._frt_admit(hidden)
|
|
138
|
+
if admitted is not PROCEED:
|
|
139
|
+
return admitted
|
|
140
|
+
return self._bound.ffn(hidden)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _calibrate(normed_samples, w_fc1, b_fc1):
|
|
144
|
+
if not normed_samples:
|
|
145
|
+
raise ValueError("calibration samples must be non-empty")
|
|
146
|
+
device = w_fc1.device
|
|
147
|
+
input_amax = torch.zeros((), device=device)
|
|
148
|
+
hidden_amax = torch.zeros((), device=device)
|
|
149
|
+
for h in normed_samples:
|
|
150
|
+
flat = h.reshape(-1, h.shape[-1]).float().to(device)
|
|
151
|
+
hidden = torch.nn.functional.gelu(
|
|
152
|
+
flat @ w_fc1.float().t() + b_fc1.float(), approximate="tanh")
|
|
153
|
+
input_amax = torch.maximum(input_amax, flat.abs().max())
|
|
154
|
+
hidden_amax = torch.maximum(hidden_amax, hidden.abs().max())
|
|
155
|
+
return ((input_amax / _FP8_MAX).clamp(min=1e-8),
|
|
156
|
+
(hidden_amax / _FP8_MAX).clamp(min=1e-8))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _check(weights: Mapping[str, torch.Tensor]) -> tuple[int, int]:
|
|
160
|
+
w_fc1, w_fc2 = weights["w_fc1"], weights["w_fc2"]
|
|
161
|
+
dim_f, dim_d = w_fc1.shape
|
|
162
|
+
if w_fc2.shape != (dim_d, dim_f):
|
|
163
|
+
raise ValueError(
|
|
164
|
+
f"inconsistent weight dims: fc1 {tuple(w_fc1.shape)}, "
|
|
165
|
+
f"fc2 {tuple(w_fc2.shape)}"
|
|
166
|
+
)
|
|
167
|
+
for name, dim in (("D", dim_d), ("F", dim_f)):
|
|
168
|
+
bounds = SUPPORT[name]
|
|
169
|
+
if not bounds["min"] <= dim <= bounds["max"]:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"{name}={dim} outside support envelope "
|
|
172
|
+
f"[{bounds['min']}, {bounds['max']}]"
|
|
173
|
+
)
|
|
174
|
+
if not (w_fc1.is_cuda and w_fc2.is_cuda):
|
|
175
|
+
raise ValueError("fp8_static requires CUDA-resident weights")
|
|
176
|
+
return dim_d, dim_f
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _build(weights, input_scale, hidden_scale, eps, variant=None):
|
|
180
|
+
variant = variant or {}
|
|
181
|
+
_check(weights)
|
|
182
|
+
fc1_scale = _amax_scale(weights["w_fc1"])
|
|
183
|
+
fc2_scale = _amax_scale(weights["w_fc2"])
|
|
184
|
+
to_bf16 = lambda t: t.to(torch.bfloat16)
|
|
185
|
+
# capability probe: the v2 entries carry the down bias in the GEMM
|
|
186
|
+
# epilogue (one launch and one full output write fewer). Prefer
|
|
187
|
+
# them when the installed package ships them; absence is a
|
|
188
|
+
# fallback, never a refusal.
|
|
189
|
+
kern = _kernel()
|
|
190
|
+
if variant.get("in_dtype") == "fp8_static":
|
|
191
|
+
fused = (getattr(kern, "fp8_gelu_mlp_v2_bf16", None)
|
|
192
|
+
or kern.fp8_gelu_mlp_bf16)
|
|
193
|
+
else:
|
|
194
|
+
fused = (getattr(kern, "bf16_fp8_gelu_mlp_v2_bf16", None)
|
|
195
|
+
or kern.bf16_fp8_gelu_mlp_bf16)
|
|
196
|
+
return BoundVisionFfnFp8(
|
|
197
|
+
fused_mlp=fused,
|
|
198
|
+
in_dtype=variant.get("in_dtype", "bf16"),
|
|
199
|
+
w_norm=weights["w_norm"],
|
|
200
|
+
b_norm=weights["b_norm"],
|
|
201
|
+
fc1_fp8=_quantize(weights["w_fc1"], fc1_scale),
|
|
202
|
+
b_fc1=to_bf16(weights["b_fc1"]),
|
|
203
|
+
fc2_fp8=_quantize(weights["w_fc2"], fc2_scale),
|
|
204
|
+
b_fc2=to_bf16(weights["b_fc2"]),
|
|
205
|
+
input_scale=input_scale,
|
|
206
|
+
fc1_scale=fc1_scale,
|
|
207
|
+
hidden_scale=hidden_scale,
|
|
208
|
+
fc2_scale=fc2_scale,
|
|
209
|
+
eps=eps,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@torch.no_grad()
|
|
214
|
+
def bind(
|
|
215
|
+
weights: Mapping[str, torch.Tensor],
|
|
216
|
+
*,
|
|
217
|
+
variant: Mapping[str, str],
|
|
218
|
+
calibration_inputs: Sequence[Mapping[str, torch.Tensor]],
|
|
219
|
+
eps: float = 1e-6,
|
|
220
|
+
) -> BoundVisionFfnFp8:
|
|
221
|
+
"""Bind the full structure: calibration inputs are boundary inputs."""
|
|
222
|
+
if variant.get("activation", "gelu") != "gelu":
|
|
223
|
+
raise ValueError("vision_ffn fp8_static supports gelu only")
|
|
224
|
+
if not calibration_inputs:
|
|
225
|
+
raise ValueError("calibration_inputs must be non-empty")
|
|
226
|
+
normed = [
|
|
227
|
+
torch.nn.functional.layer_norm(
|
|
228
|
+
s["x"].float(), (s["x"].shape[-1],),
|
|
229
|
+
(weights["w_norm"].float()
|
|
230
|
+
if weights["w_norm"] is not None else None),
|
|
231
|
+
(weights["b_norm"].float()
|
|
232
|
+
if weights["b_norm"] is not None else None),
|
|
233
|
+
eps)
|
|
234
|
+
for s in calibration_inputs
|
|
235
|
+
]
|
|
236
|
+
input_scale, hidden_scale = _calibrate(
|
|
237
|
+
normed, weights["w_fc1"], weights["b_fc1"])
|
|
238
|
+
return _build(weights, input_scale, hidden_scale, eps)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@torch.no_grad()
|
|
242
|
+
def bind_mlp_seam(
|
|
243
|
+
weights: Mapping[str, torch.Tensor],
|
|
244
|
+
*,
|
|
245
|
+
input_scale: float,
|
|
246
|
+
hidden_scale: float,
|
|
247
|
+
original: torch.nn.Module | None = None,
|
|
248
|
+
eps: float = 1e-6,
|
|
249
|
+
) -> FusedGeluMlp:
|
|
250
|
+
"""Bind the MLP-seam slice from two already-calibrated scales.
|
|
251
|
+
|
|
252
|
+
``input_scale`` is the amax at this MLP's input, ``hidden_scale`` the
|
|
253
|
+
amax at its second projection's input — which is the post-activation
|
|
254
|
+
hidden this kernel quantises. Measured where they are, not recomputed
|
|
255
|
+
from kept inputs.
|
|
256
|
+
"""
|
|
257
|
+
dev = weights["w_fc1"].device
|
|
258
|
+
bound = _build(weights,
|
|
259
|
+
torch.tensor(float(input_scale), device=dev),
|
|
260
|
+
torch.tensor(float(hidden_scale), device=dev), eps)
|
|
261
|
+
return FusedGeluMlp(bound, original=original)
|