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.
Files changed (122) hide show
  1. flashrt_structures/__init__.py +174 -0
  2. flashrt_structures/adapters/__init__.py +55 -0
  3. flashrt_structures/adapters/diffusers_attention.py +237 -0
  4. flashrt_structures/adapters/diffusers_rotary_attention.py +239 -0
  5. flashrt_structures/adapters/factored_qk_norm_rope.py +252 -0
  6. flashrt_structures/adapters/factored_two_way_attention.py +99 -0
  7. flashrt_structures/adapters/gemma_attention.py +226 -0
  8. flashrt_structures/adapters/packed_qkv_rope.py +342 -0
  9. flashrt_structures/adapters/packed_stream_qk_norm_rope.py +376 -0
  10. flashrt_structures/adapters/qwen_per_head_qk_norm_rope.py +231 -0
  11. flashrt_structures/adapters/sglang_engine.py +207 -0
  12. flashrt_structures/adapters/transformers_attention_interface.py +73 -0
  13. flashrt_structures/adapters/transformers_gated_delta.py +152 -0
  14. flashrt_structures/adapters/transformers_gated_delta_fused.py +96 -0
  15. flashrt_structures/adapters/vllm_engine.py +424 -0
  16. flashrt_structures/adjudicate.py +85 -0
  17. flashrt_structures/aot.py +191 -0
  18. flashrt_structures/autobuild.py +2052 -0
  19. flashrt_structures/beta/__init__.py +43 -0
  20. flashrt_structures/beta/conform.py +94 -0
  21. flashrt_structures/beta/joins.py +113 -0
  22. flashrt_structures/beta/negotiate.py +84 -0
  23. flashrt_structures/beta/ports.py +140 -0
  24. flashrt_structures/decisions.py +80 -0
  25. flashrt_structures/discover.py +623 -0
  26. flashrt_structures/explain.py +70 -0
  27. flashrt_structures/frontdoor.py +572 -0
  28. flashrt_structures/gates.py +465 -0
  29. flashrt_structures/guard.py +421 -0
  30. flashrt_structures/handle.py +189 -0
  31. flashrt_structures/impls/__init__.py +219 -0
  32. flashrt_structures/impls/adaln_producer/__init__.py +8 -0
  33. flashrt_structures/impls/adaln_producer/broker.py +116 -0
  34. flashrt_structures/impls/adaln_producer/fused.py +388 -0
  35. flashrt_structures/impls/adarms_stack/__init__.py +8 -0
  36. flashrt_structures/impls/adarms_stack/fp8_chain.py +832 -0
  37. flashrt_structures/impls/adarms_stack/region.py +102 -0
  38. flashrt_structures/impls/attention_core/__init__.py +132 -0
  39. flashrt_structures/impls/attention_core/fa2_seqused.py +458 -0
  40. flashrt_structures/impls/attention_core/fa4_cute.py +154 -0
  41. flashrt_structures/impls/attention_core/fa4_fp8.py +178 -0
  42. flashrt_structures/impls/attention_core/masked_mha.py +158 -0
  43. flashrt_structures/impls/attention_core/two_way_fa2.py +220 -0
  44. flashrt_structures/impls/cadence_static/__init__.py +18 -0
  45. flashrt_structures/impls/cadence_static/buffers.py +122 -0
  46. flashrt_structures/impls/cadence_static/cross_attention.py +187 -0
  47. flashrt_structures/impls/chain_elements.py +89 -0
  48. flashrt_structures/impls/decode_loop/__init__.py +0 -0
  49. flashrt_structures/impls/decode_loop/fp8_kv.py +206 -0
  50. flashrt_structures/impls/decode_loop/mtp_speculative.py +245 -0
  51. flashrt_structures/impls/decode_loop/whole_step.py +852 -0
  52. flashrt_structures/impls/decoder_block/__init__.py +6 -0
  53. flashrt_structures/impls/decoder_block/attn_sublayer.py +110 -0
  54. flashrt_structures/impls/decoder_block/fused.py +167 -0
  55. flashrt_structures/impls/decoder_ffn/__init__.py +0 -0
  56. flashrt_structures/impls/decoder_ffn/fp8_static.py +310 -0
  57. flashrt_structures/impls/decoder_ffn/fp8_static.yaml +22 -0
  58. flashrt_structures/impls/decoder_ffn/w4a16_static.py +221 -0
  59. flashrt_structures/impls/decoder_ffn/w8a16_static.py +183 -0
  60. flashrt_structures/impls/dit_stack/__init__.py +11 -0
  61. flashrt_structures/impls/dit_stack/fp4_chain.py +417 -0
  62. flashrt_structures/impls/dit_stack/region.py +86 -0
  63. flashrt_structures/impls/fixed_iter/__init__.py +29 -0
  64. flashrt_structures/impls/fixed_iter/openpi.py +264 -0
  65. flashrt_structures/impls/fixed_iter/protocol.py +94 -0
  66. flashrt_structures/impls/gated_delta_core/__init__.py +3 -0
  67. flashrt_structures/impls/gated_delta_core/fused_layer.py +545 -0
  68. flashrt_structures/impls/gated_delta_core/hub_v3.py +152 -0
  69. flashrt_structures/impls/graph_lowering/__init__.py +27 -0
  70. flashrt_structures/impls/graph_lowering/pi052_denoise.py +179 -0
  71. flashrt_structures/impls/graph_lowering/protocol.py +76 -0
  72. flashrt_structures/impls/graph_lowering/qwen3_vl.py +364 -0
  73. flashrt_structures/impls/linear_proj/__init__.py +0 -0
  74. flashrt_structures/impls/linear_proj/fp8_static.py +270 -0
  75. flashrt_structures/impls/linear_proj/nvfp4_balance.py +131 -0
  76. flashrt_structures/impls/linear_proj/nvfp4_dynamic.py +182 -0
  77. flashrt_structures/impls/linear_proj/w8a16_static.py +230 -0
  78. flashrt_structures/impls/modnorm_qkv_chain/__init__.py +0 -0
  79. flashrt_structures/impls/modnorm_qkv_chain/fp8_ptok_table.py +291 -0
  80. flashrt_structures/impls/moe_experts/__init__.py +9 -0
  81. flashrt_structures/impls/moe_experts/nvfp4_dynamic.py +208 -0
  82. flashrt_structures/impls/moe_experts/nvfp4_w4a16.py +129 -0
  83. flashrt_structures/impls/norm_fused/__init__.py +3 -0
  84. flashrt_structures/impls/norm_fused/bf16.py +94 -0
  85. flashrt_structures/impls/norm_fused/fp8_producer.py +84 -0
  86. flashrt_structures/impls/patch_projection/__init__.py +3 -0
  87. flashrt_structures/impls/patch_projection/bf16_flat.py +139 -0
  88. flashrt_structures/impls/prefill_tower/__init__.py +10 -0
  89. flashrt_structures/impls/prefill_tower/fp8_chain.py +955 -0
  90. flashrt_structures/impls/prefill_tower/region.py +99 -0
  91. flashrt_structures/impls/qk_norm_rope/__init__.py +12 -0
  92. flashrt_structures/impls/qk_norm_rope/per_head_gqa.py +199 -0
  93. flashrt_structures/impls/qk_norm_rope/projection_bf16.py +165 -0
  94. flashrt_structures/impls/qkv_pack/__init__.py +5 -0
  95. flashrt_structures/impls/qkv_pack/bf16.py +110 -0
  96. flashrt_structures/impls/qkv_pack/fp8_static.py +435 -0
  97. flashrt_structures/impls/qkv_pack/nvfp4_balance.py +218 -0
  98. flashrt_structures/impls/qkv_rope/__init__.py +3 -0
  99. flashrt_structures/impls/qkv_rope/packed_bias_bf16.py +143 -0
  100. flashrt_structures/impls/step_table.py +113 -0
  101. flashrt_structures/impls/vision_ffn/__init__.py +0 -0
  102. flashrt_structures/impls/vision_ffn/fp8_static.py +261 -0
  103. flashrt_structures/impls/vision_ffn/nvfp4_balance.py +211 -0
  104. flashrt_structures/impls/vision_tower/__init__.py +7 -0
  105. flashrt_structures/impls/vision_tower/fp8_chain.py +533 -0
  106. flashrt_structures/impls/vision_tower/region.py +91 -0
  107. flashrt_structures/matrix.py +126 -0
  108. flashrt_structures/points.py +368 -0
  109. flashrt_structures/prequantized.py +131 -0
  110. flashrt_structures/quantize_on_adopt.py +94 -0
  111. flashrt_structures/recipe.py +438 -0
  112. flashrt_structures/regions.py +208 -0
  113. flashrt_structures/schemes.py +490 -0
  114. flashrt_structures/stages.py +298 -0
  115. flashrt_structures/storage.py +255 -0
  116. flashrt_structures/swap.py +398 -0
  117. flashrt_structures/workspace.py +110 -0
  118. flashrt_structures-0.2.0.dist-info/METADATA +140 -0
  119. flashrt_structures-0.2.0.dist-info/RECORD +122 -0
  120. flashrt_structures-0.2.0.dist-info/WHEEL +5 -0
  121. flashrt_structures-0.2.0.dist-info/licenses/LICENSE +202 -0
  122. 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)