Publish K96-tail source patches
Browse files- reproduction/closure/patches/kquant/changed-files/kquant/candidate_hessian.py +292 -0
- reproduction/closure/patches/kquant/changed-files/kquant/csrc/qsrt_quantize_tiles_kernel.cuh +492 -0
- reproduction/closure/patches/kquant/changed-files/kquant/csrc/sqg_quantize.cu +358 -0
- reproduction/closure/patches/kquant/changed-files/kquant/exl3_encoder_backend.py +0 -0
- reproduction/closure/patches/kquant/changed-files/kquant/exl3_reference.py +281 -0
- reproduction/closure/patches/kquant/changed-files/kquant/qsrt.py +1141 -0
- reproduction/closure/patches/kquant/changed-files/kquant/sqg_e4m3.py +438 -0
- reproduction/closure/patches/kquant/changed-files/kquant/sqg_quantizer.py +270 -0
- reproduction/closure/patches/kquant/changed-files/tests/test_qsrt.py +486 -0
- reproduction/closure/patches/kquant/changed-files/tests/test_sqg_e4m3.py +236 -0
- reproduction/closure/patches/kquant/changed-files/tests/test_sqg_quantizer.py +262 -0
- reproduction/closure/patches/kquant/changes.patch +1386 -0
- reproduction/closure/patches/qsrt/changed-files/qsrt/pack/qsrt_candidates.py +1637 -0
- reproduction/closure/patches/qsrt/changed-files/qsrt/qsrt_coupled.py +381 -0
- reproduction/closure/patches/qsrt/changed-files/tests/test_qsrt_coupled.py +181 -0
- reproduction/closure/patches/qsrt/changes.patch +442 -0
reproduction/closure/patches/kquant/changed-files/kquant/candidate_hessian.py
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Candidate-specific covariance helpers for offline expert encoding studies."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import hashlib
|
| 6 |
+
import math
|
| 7 |
+
from collections.abc import Mapping, Sequence
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def partition_documents(
|
| 13 |
+
documents: Sequence[str], *, modulus: int, confirmation_index: int
|
| 14 |
+
) -> tuple[list[str], list[str]]:
|
| 15 |
+
"""Deterministically split document hashes into fit and confirmation sets."""
|
| 16 |
+
|
| 17 |
+
if modulus < 2:
|
| 18 |
+
raise ValueError("document partition modulus must be at least 2")
|
| 19 |
+
if not 0 <= confirmation_index < modulus:
|
| 20 |
+
raise ValueError("confirmation index must be in the partition modulus")
|
| 21 |
+
if not documents or len(set(documents)) != len(documents):
|
| 22 |
+
raise ValueError("documents must be a nonempty unique sequence")
|
| 23 |
+
fit: list[str] = []
|
| 24 |
+
confirmation: list[str] = []
|
| 25 |
+
for document in documents:
|
| 26 |
+
digest = hashlib.blake2b(document.encode(), digest_size=8).digest()
|
| 27 |
+
bucket = int.from_bytes(digest, "little") % modulus
|
| 28 |
+
(confirmation if bucket == confirmation_index else fit).append(document)
|
| 29 |
+
if not fit or not confirmation:
|
| 30 |
+
raise ValueError("document partition produced an empty side")
|
| 31 |
+
return fit, confirmation
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def subset_request_documents(
|
| 35 |
+
request_documents: Mapping[int, str], documents: Sequence[str]
|
| 36 |
+
) -> dict[int, str]:
|
| 37 |
+
"""Return request/document entries belonging to ``documents`` exactly."""
|
| 38 |
+
|
| 39 |
+
selected = set(documents)
|
| 40 |
+
if not selected:
|
| 41 |
+
raise ValueError("document subset must be nonempty")
|
| 42 |
+
result = {
|
| 43 |
+
int(request): document
|
| 44 |
+
for request, document in request_documents.items()
|
| 45 |
+
if document in selected
|
| 46 |
+
}
|
| 47 |
+
covered = set(result.values())
|
| 48 |
+
if covered != selected:
|
| 49 |
+
missing = sorted(selected - covered)
|
| 50 |
+
raise ValueError(f"documents lack request epochs: {missing[:8]}")
|
| 51 |
+
return result
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def request_mask(
|
| 55 |
+
observations: torch.Tensor, request_documents: Mapping[int, str]
|
| 56 |
+
) -> torch.Tensor:
|
| 57 |
+
"""Select capture rows whose encoded request epoch is in a corpus subset."""
|
| 58 |
+
|
| 59 |
+
if observations.ndim != 1:
|
| 60 |
+
raise ValueError("observations must be one-dimensional")
|
| 61 |
+
if not request_documents:
|
| 62 |
+
raise ValueError("request document mapping must be nonempty")
|
| 63 |
+
steps = torch.bitwise_right_shift(observations, 32)
|
| 64 |
+
allowed = torch.tensor(
|
| 65 |
+
sorted(request_documents), dtype=steps.dtype, device=steps.device
|
| 66 |
+
)
|
| 67 |
+
return torch.isin(steps, allowed)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def weighted_covariance(
|
| 71 |
+
rows: torch.Tensor,
|
| 72 |
+
weights: torch.Tensor,
|
| 73 |
+
*,
|
| 74 |
+
device: torch.device,
|
| 75 |
+
chunk_rows: int = 256,
|
| 76 |
+
return_cpu: bool = True,
|
| 77 |
+
) -> tuple[torch.Tensor, float]:
|
| 78 |
+
"""Return ``sum(w * x xT) / sum(w)`` as symmetric FP32."""
|
| 79 |
+
|
| 80 |
+
if rows.ndim != 2 or weights.ndim != 1 or rows.shape[0] != weights.shape[0]:
|
| 81 |
+
raise ValueError("invalid covariance sample shapes")
|
| 82 |
+
if not rows.shape[0] or not rows.shape[1]:
|
| 83 |
+
raise ValueError("covariance samples must be nonempty")
|
| 84 |
+
if chunk_rows <= 0:
|
| 85 |
+
raise ValueError("chunk_rows must be positive")
|
| 86 |
+
if not torch.all(torch.isfinite(rows)) or not torch.all(torch.isfinite(weights)):
|
| 87 |
+
raise ValueError("covariance samples must be finite")
|
| 88 |
+
if torch.any(weights < 0):
|
| 89 |
+
raise ValueError("covariance weights must be nonnegative")
|
| 90 |
+
|
| 91 |
+
dimension = int(rows.shape[1])
|
| 92 |
+
accumulator = torch.zeros(
|
| 93 |
+
(dimension, dimension), dtype=torch.float32, device=device
|
| 94 |
+
)
|
| 95 |
+
denominator_tensor = weights.to(device=device, dtype=torch.float64).sum()
|
| 96 |
+
if not bool(torch.isfinite(denominator_tensor)) or not bool(
|
| 97 |
+
denominator_tensor > 0
|
| 98 |
+
):
|
| 99 |
+
raise ValueError("covariance weights must have a positive finite sum")
|
| 100 |
+
for begin in range(0, int(rows.shape[0]), chunk_rows):
|
| 101 |
+
values = rows[begin : begin + chunk_rows].to(
|
| 102 |
+
device=device, dtype=torch.float32
|
| 103 |
+
)
|
| 104 |
+
local_weights = weights[begin : begin + chunk_rows].to(
|
| 105 |
+
device=device, dtype=torch.float32
|
| 106 |
+
)
|
| 107 |
+
accumulator.addmm_(values.T, values * local_weights[:, None])
|
| 108 |
+
covariance = accumulator.div_(denominator_tensor.to(torch.float32))
|
| 109 |
+
covariance = ((covariance + covariance.T) * 0.5).contiguous()
|
| 110 |
+
denominator = float(denominator_tensor)
|
| 111 |
+
if return_cpu:
|
| 112 |
+
covariance = covariance.cpu()
|
| 113 |
+
if not torch.all(torch.isfinite(covariance)):
|
| 114 |
+
raise ValueError("weighted covariance is non-finite")
|
| 115 |
+
return covariance, denominator
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def shrink_covariance(
|
| 119 |
+
global_covariance: torch.Tensor,
|
| 120 |
+
local_covariance: torch.Tensor,
|
| 121 |
+
alpha: float | torch.Tensor,
|
| 122 |
+
) -> torch.Tensor:
|
| 123 |
+
"""Convexly shrink a low-support local covariance toward a global one."""
|
| 124 |
+
|
| 125 |
+
if isinstance(alpha, torch.Tensor):
|
| 126 |
+
if alpha.numel() != 1 or not bool(torch.isfinite(alpha)) or not bool(
|
| 127 |
+
(alpha >= 0) & (alpha <= 1)
|
| 128 |
+
):
|
| 129 |
+
raise ValueError("alpha must be a finite scalar in [0, 1]")
|
| 130 |
+
blend_weight: float | torch.Tensor = alpha.to(
|
| 131 |
+
device=global_covariance.device, dtype=torch.float32
|
| 132 |
+
)
|
| 133 |
+
else:
|
| 134 |
+
alpha_value = float(alpha)
|
| 135 |
+
if not math.isfinite(alpha_value) or not 0.0 <= alpha_value <= 1.0:
|
| 136 |
+
raise ValueError("alpha must be finite and in [0, 1]")
|
| 137 |
+
blend_weight = alpha_value
|
| 138 |
+
if (
|
| 139 |
+
global_covariance.ndim != 2
|
| 140 |
+
or global_covariance.shape[0] != global_covariance.shape[1]
|
| 141 |
+
or local_covariance.shape != global_covariance.shape
|
| 142 |
+
):
|
| 143 |
+
raise ValueError("global and local covariances must be equal-size squares")
|
| 144 |
+
if not torch.all(torch.isfinite(global_covariance)) or not torch.all(
|
| 145 |
+
torch.isfinite(local_covariance)
|
| 146 |
+
):
|
| 147 |
+
raise ValueError("covariances must be finite")
|
| 148 |
+
blended = torch.lerp(
|
| 149 |
+
global_covariance.float(), local_covariance.float(), blend_weight
|
| 150 |
+
)
|
| 151 |
+
return ((blended + blended.T) * 0.5).contiguous()
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def weighted_effective_sample_size(weights: torch.Tensor) -> float:
|
| 155 |
+
"""Return Kish effective sample size for nonnegative row weights."""
|
| 156 |
+
|
| 157 |
+
if weights.ndim != 1 or not weights.numel():
|
| 158 |
+
raise ValueError("weights must be a nonempty vector")
|
| 159 |
+
if not torch.all(torch.isfinite(weights)) or torch.any(weights < 0):
|
| 160 |
+
raise ValueError("weights must be finite and nonnegative")
|
| 161 |
+
value = _weighted_effective_sample_size_tensor(weights)
|
| 162 |
+
return float(value)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def _weighted_effective_sample_size_tensor(weights: torch.Tensor) -> torch.Tensor:
|
| 166 |
+
"""Kish support as a scalar tensor on the input numerical device."""
|
| 167 |
+
|
| 168 |
+
if weights.ndim != 1 or not weights.numel():
|
| 169 |
+
raise ValueError("weights must be a nonempty vector")
|
| 170 |
+
if not torch.all(torch.isfinite(weights)) or torch.any(weights < 0):
|
| 171 |
+
raise ValueError("weights must be finite and nonnegative")
|
| 172 |
+
weights64 = weights.double()
|
| 173 |
+
total = weights64.sum()
|
| 174 |
+
squared_total = weights64.square().sum()
|
| 175 |
+
if total <= 0 or squared_total <= 0:
|
| 176 |
+
raise ValueError("weights must have positive effective support")
|
| 177 |
+
return total.square() / squared_total
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def adaptive_identity_shrinkage(
|
| 181 |
+
local_covariance: torch.Tensor,
|
| 182 |
+
weights: torch.Tensor,
|
| 183 |
+
*,
|
| 184 |
+
max_local_alpha: float = 0.75,
|
| 185 |
+
) -> tuple[torch.Tensor, dict[str, float]]:
|
| 186 |
+
"""Shrink a weighted local covariance toward its scaled identity.
|
| 187 |
+
|
| 188 |
+
The Oracle Approximating Shrinkage expression is evaluated with Kish
|
| 189 |
+
effective sample size in place of an unweighted row count. The target is
|
| 190 |
+
always ``trace(local) / dimension * I``. This is important for expert
|
| 191 |
+
``H2`` matrices: intermediate coordinates have no common meaning across
|
| 192 |
+
experts, so a pooled post-SiTU covariance is not a valid prior.
|
| 193 |
+
``max_local_alpha`` preserves a nonzero identity contribution even for
|
| 194 |
+
very well-supported experts.
|
| 195 |
+
"""
|
| 196 |
+
|
| 197 |
+
max_local_alpha = float(max_local_alpha)
|
| 198 |
+
if not math.isfinite(max_local_alpha) or not 0.0 <= max_local_alpha <= 1.0:
|
| 199 |
+
raise ValueError("max_local_alpha must be finite and in [0, 1]")
|
| 200 |
+
if local_covariance.ndim != 2 or (
|
| 201 |
+
local_covariance.shape[0] != local_covariance.shape[1]
|
| 202 |
+
):
|
| 203 |
+
raise ValueError("local covariance must be square")
|
| 204 |
+
if not torch.all(torch.isfinite(local_covariance)):
|
| 205 |
+
raise ValueError("local covariance must be finite")
|
| 206 |
+
|
| 207 |
+
effective_rows_tensor = _weighted_effective_sample_size_tensor(weights)
|
| 208 |
+
dimension = int(local_covariance.shape[0])
|
| 209 |
+
local64 = local_covariance.double()
|
| 210 |
+
local_trace = torch.trace(local64)
|
| 211 |
+
if local_trace <= 0:
|
| 212 |
+
raise ValueError("local covariance trace must be positive")
|
| 213 |
+
|
| 214 |
+
mu = local_trace / dimension
|
| 215 |
+
mean_square = local64.square().mean()
|
| 216 |
+
denominator = (effective_rows_tensor + 1.0) * (
|
| 217 |
+
mean_square - mu.square() / dimension
|
| 218 |
+
)
|
| 219 |
+
raw_oas = (mean_square + mu.square()) / denominator.clamp_min(
|
| 220 |
+
torch.finfo(local64.dtype).tiny
|
| 221 |
+
)
|
| 222 |
+
oas_shrinkage_tensor = torch.where(
|
| 223 |
+
denominator <= 0,
|
| 224 |
+
torch.ones_like(raw_oas),
|
| 225 |
+
raw_oas.clamp(0.0, 1.0),
|
| 226 |
+
)
|
| 227 |
+
local_alpha_tensor = torch.minimum(
|
| 228 |
+
torch.as_tensor(max_local_alpha, device=local64.device, dtype=local64.dtype),
|
| 229 |
+
1.0 - oas_shrinkage_tensor,
|
| 230 |
+
)
|
| 231 |
+
scaled_identity = torch.eye(
|
| 232 |
+
dimension,
|
| 233 |
+
dtype=torch.float32,
|
| 234 |
+
device=local_covariance.device,
|
| 235 |
+
).mul_(mu.to(dtype=torch.float32))
|
| 236 |
+
blended = shrink_covariance(
|
| 237 |
+
scaled_identity, local_covariance, local_alpha_tensor.to(torch.float32)
|
| 238 |
+
)
|
| 239 |
+
# Evidence scalars are detached only after the numerical result is frozen.
|
| 240 |
+
effective_rows = float(effective_rows_tensor)
|
| 241 |
+
oas_shrinkage = float(oas_shrinkage_tensor)
|
| 242 |
+
local_alpha = float(local_alpha_tensor)
|
| 243 |
+
identity_scale = float(mu)
|
| 244 |
+
return blended, {
|
| 245 |
+
"effective_sample_size": effective_rows,
|
| 246 |
+
"oas_shrinkage": oas_shrinkage,
|
| 247 |
+
"local_alpha": local_alpha,
|
| 248 |
+
"max_local_alpha": max_local_alpha,
|
| 249 |
+
"identity_scale": identity_scale,
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def covariance_comparison(
|
| 254 |
+
reference: torch.Tensor, candidate: torch.Tensor
|
| 255 |
+
) -> dict[str, float]:
|
| 256 |
+
"""Compact scale/shape diagnostics for two covariance matrices."""
|
| 257 |
+
|
| 258 |
+
if reference.ndim != 2 or reference.shape[0] != reference.shape[1]:
|
| 259 |
+
raise ValueError("reference covariance must be square")
|
| 260 |
+
if candidate.shape != reference.shape:
|
| 261 |
+
raise ValueError("candidate covariance shape differs")
|
| 262 |
+
reference = reference.float()
|
| 263 |
+
candidate = candidate.float()
|
| 264 |
+
if not torch.all(torch.isfinite(reference)) or not torch.all(torch.isfinite(candidate)):
|
| 265 |
+
raise ValueError("covariances must be finite")
|
| 266 |
+
reference_norm = torch.linalg.vector_norm(reference.double())
|
| 267 |
+
reference_diagonal = torch.diagonal(reference).double()
|
| 268 |
+
candidate_diagonal = torch.diagonal(candidate).double()
|
| 269 |
+
diagonal_denominator = torch.linalg.vector_norm(
|
| 270 |
+
reference_diagonal
|
| 271 |
+
) * torch.linalg.vector_norm(candidate_diagonal)
|
| 272 |
+
trace = torch.trace(reference.double())
|
| 273 |
+
return {
|
| 274 |
+
"relative_frobenius_difference": float(
|
| 275 |
+
torch.linalg.vector_norm((candidate - reference).double())
|
| 276 |
+
/ reference_norm
|
| 277 |
+
)
|
| 278 |
+
if reference_norm > 0
|
| 279 |
+
else math.inf,
|
| 280 |
+
"trace_ratio": float(torch.trace(candidate.double()) / trace)
|
| 281 |
+
if trace != 0
|
| 282 |
+
else math.inf,
|
| 283 |
+
"diagonal_cosine_similarity": float(
|
| 284 |
+
torch.dot(reference_diagonal, candidate_diagonal)
|
| 285 |
+
/ diagonal_denominator
|
| 286 |
+
)
|
| 287 |
+
if diagonal_denominator > 0
|
| 288 |
+
else 0.0,
|
| 289 |
+
"candidate_symmetry_max_abs": float(
|
| 290 |
+
(candidate - candidate.T).abs().max()
|
| 291 |
+
),
|
| 292 |
+
}
|
reproduction/closure/patches/kquant/changed-files/kquant/csrc/qsrt_quantize_tiles_kernel.cuh
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#pragma once
|
| 2 |
+
|
| 3 |
+
// Templated trellis-quantization (Viterbi) kernel, instantiated per (K, cb) in
|
| 4 |
+
// comp_units/quantize_tiles_inst_k*.cu
|
| 5 |
+
|
| 6 |
+
#include <cuda_fp16.h>
|
| 7 |
+
#include <cuda_bf16.h>
|
| 8 |
+
#include <cublas_v2.h>
|
| 9 |
+
#include <cstdio>
|
| 10 |
+
#include "exl3_compat/util.h"
|
| 11 |
+
#include "exl3_compat/util.cuh"
|
| 12 |
+
#include "exl3_compat/quant/codebook.cuh"
|
| 13 |
+
|
| 14 |
+
#define QUANTIZE_TILES_K2_NUM_THREADS 1024
|
| 15 |
+
#define QUANTIZE_TILES_K3_NUM_THREADS 640
|
| 16 |
+
#define QUANTIZE_TILES_K4_NUM_THREADS 704
|
| 17 |
+
#ifndef H_INF
|
| 18 |
+
#define H_INF __ushort_as_half(0x7c00)
|
| 19 |
+
#endif
|
| 20 |
+
#ifndef H_MAX_FINITE
|
| 21 |
+
#define H_MAX_FINITE __ushort_as_half(0x7bff)
|
| 22 |
+
#endif
|
| 23 |
+
|
| 24 |
+
template <int K, int cb>
|
| 25 |
+
__global__ __launch_bounds__(
|
| 26 |
+
K == 2 ? QUANTIZE_TILES_K2_NUM_THREADS :
|
| 27 |
+
(K == 3 ? QUANTIZE_TILES_K3_NUM_THREADS : QUANTIZE_TILES_K4_NUM_THREADS),
|
| 28 |
+
K == 2 ? 1 : 2)
|
| 29 |
+
void quantize_tiles_kernel
|
| 30 |
+
(
|
| 31 |
+
const float* __restrict__ input_tiles_ptr,
|
| 32 |
+
float* __restrict__ output_tiles_ptr,
|
| 33 |
+
uint16_t* __restrict__ output_indices_ptr,
|
| 34 |
+
half* __restrict__ temp_costs_ptr,
|
| 35 |
+
uint8_t* __restrict__ temp_edges_ptr,
|
| 36 |
+
int tailbite_context
|
| 37 |
+
)
|
| 38 |
+
{
|
| 39 |
+
extern __shared__ uint8_t shbuf[];
|
| 40 |
+
uint8_t* sh = shbuf;
|
| 41 |
+
|
| 42 |
+
constexpr int Kr = 16 - K;
|
| 43 |
+
constexpr int max_q = 1 << K;
|
| 44 |
+
constexpr int edges = 65536 >> K;
|
| 45 |
+
constexpr int decisions_per_row =
|
| 46 |
+
K == 2 ? edges / 4 : (K <= 4 ? edges / 2 : edges);
|
| 47 |
+
constexpr int decision_shift = 16 - 2 * K;
|
| 48 |
+
constexpr int predecessor_step = 1 << decision_shift;
|
| 49 |
+
constexpr int num_threads =
|
| 50 |
+
K == 2 ? QUANTIZE_TILES_K2_NUM_THREADS :
|
| 51 |
+
(K == 3 ? QUANTIZE_TILES_K3_NUM_THREADS : QUANTIZE_TILES_K4_NUM_THREADS);
|
| 52 |
+
|
| 53 |
+
const int tile_idx = blockIdx.x;
|
| 54 |
+
const int thread = threadIdx.x;
|
| 55 |
+
const float* input_tile = input_tiles_ptr + 256 * tile_idx;
|
| 56 |
+
float* output_tile = output_tiles_ptr + 256 * tile_idx;
|
| 57 |
+
uint16_t* output_indices = output_indices_ptr + 256 * tile_idx;
|
| 58 |
+
uint8_t* temp_edges = temp_edges_ptr + 256 * decisions_per_row * tile_idx;
|
| 59 |
+
|
| 60 |
+
half* sh_input_tile = (half*) sh; sh += 256 * sizeof(half);
|
| 61 |
+
half* sh_min = (half*) sh; sh += 32 * sizeof(half);
|
| 62 |
+
int* sh_idx = (int*) sh; sh += 32 * sizeof(int);
|
| 63 |
+
|
| 64 |
+
half* sh_temp_costs = (half*) sh;
|
| 65 |
+
half* temp_costs = K >= 2 ? sh_temp_costs : temp_costs_ptr + 2 * edges * tile_idx;
|
| 66 |
+
half* temp_costs_inc = temp_costs + edges;
|
| 67 |
+
|
| 68 |
+
if (thread < 256) sh_input_tile[thread] = __float2half_rn(input_tile[thread]);
|
| 69 |
+
__syncthreads();
|
| 70 |
+
|
| 71 |
+
auto forward = [&](int roll, int steps, int pre_state, int trace_start)
|
| 72 |
+
{
|
| 73 |
+
int ri = roll & 255;
|
| 74 |
+
half* t = temp_costs;
|
| 75 |
+
temp_costs = temp_costs_inc;
|
| 76 |
+
temp_costs_inc = t;
|
| 77 |
+
|
| 78 |
+
for (int out_edge_idx = 2 * thread; out_edge_idx < edges; out_edge_idx += 2 * num_threads)
|
| 79 |
+
{
|
| 80 |
+
const half2 w2 = __half2half2(sh_input_tile[ri]);
|
| 81 |
+
int in_edge_idx = out_edge_idx >> K;
|
| 82 |
+
uint32_t product0 = 0;
|
| 83 |
+
uint32_t product1 = 0;
|
| 84 |
+
uint2 sqg_k2_labels{};
|
| 85 |
+
uint4 sqg_k3_labels{};
|
| 86 |
+
uint4 sqg_k4_labels0{};
|
| 87 |
+
uint4 sqg_k4_labels1{};
|
| 88 |
+
half2 decoded2;
|
| 89 |
+
if constexpr (cb == 1)
|
| 90 |
+
{
|
| 91 |
+
product0 = mul_const_u32<0xCBAC1FEDu>(out_edge_idx);
|
| 92 |
+
product1 = product0 + 0xCBAC1FEDu;
|
| 93 |
+
decoded2 = decode_mcg_product_2(product0, product1);
|
| 94 |
+
}
|
| 95 |
+
else if constexpr (cb == 2)
|
| 96 |
+
{
|
| 97 |
+
product0 = out_edge_idx * 0x83DCD12Du;
|
| 98 |
+
product1 = product0 + 0x83DCD12Du;
|
| 99 |
+
decoded2 = decode_mul1_product_2(product0, product1);
|
| 100 |
+
}
|
| 101 |
+
else if constexpr (cb == 4 && K == 2)
|
| 102 |
+
{
|
| 103 |
+
sqg_k2_labels =
|
| 104 |
+
reinterpret_cast<const uint2*>(sqg_e4m3_lut)[out_edge_idx >> 1];
|
| 105 |
+
decoded2 = decode_sqg_k2_pair(sqg_k2_labels, 0);
|
| 106 |
+
}
|
| 107 |
+
else if constexpr (cb == 4 && K == 3)
|
| 108 |
+
{
|
| 109 |
+
sqg_k3_labels =
|
| 110 |
+
reinterpret_cast<const uint4*>(sqg_e4m3_lut)[out_edge_idx >> 1];
|
| 111 |
+
decoded2 = decode_sqg_k3_pair(sqg_k3_labels, 0);
|
| 112 |
+
}
|
| 113 |
+
else if constexpr (cb == 4 && K == 4)
|
| 114 |
+
{
|
| 115 |
+
const int packed_index = out_edge_idx;
|
| 116 |
+
sqg_k4_labels0 =
|
| 117 |
+
reinterpret_cast<const uint4*>(sqg_e4m3_lut)[packed_index];
|
| 118 |
+
sqg_k4_labels1 =
|
| 119 |
+
reinterpret_cast<const uint4*>(sqg_e4m3_lut)[packed_index + 1];
|
| 120 |
+
decoded2 = decode_sqg_k4_pair(sqg_k4_labels0, sqg_k4_labels1, 0);
|
| 121 |
+
}
|
| 122 |
+
else
|
| 123 |
+
{
|
| 124 |
+
decoded2 = decode_3inst_2<cb>(out_edge_idx, out_edge_idx + 1);
|
| 125 |
+
}
|
| 126 |
+
half2 dh2 = __hsub2(decoded2, w2);
|
| 127 |
+
// Positive infinity denotes an unreachable state. Reachable path
|
| 128 |
+
// costs must remain finite when their FP16 sum overflows, or
|
| 129 |
+
// traceback can confuse a saturated path with an unreachable one
|
| 130 |
+
// and violate the constrained cyclic boundary.
|
| 131 |
+
half2 min_err2 = __hmin2(
|
| 132 |
+
__hmul2(dh2, dh2), __half2half2(H_MAX_FINITE));
|
| 133 |
+
if (pre_state >= 0 && in_edge_idx != pre_state) min_err2 = __half2half2(H_INF);
|
| 134 |
+
int min_k0 = 0;
|
| 135 |
+
int min_k1 = 0;
|
| 136 |
+
|
| 137 |
+
#pragma unroll
|
| 138 |
+
for (int k = 1; k < max_q; ++k)
|
| 139 |
+
{
|
| 140 |
+
in_edge_idx += predecessor_step;
|
| 141 |
+
if constexpr (cb == 1)
|
| 142 |
+
{
|
| 143 |
+
// MCG multiplication is linear modulo 2^32 across successive branch states.
|
| 144 |
+
constexpr uint32_t product_step = 0xCBAC1FEDu << Kr;
|
| 145 |
+
product0 += product_step;
|
| 146 |
+
product1 += product_step;
|
| 147 |
+
decoded2 = decode_mcg_product_2(product0, product1);
|
| 148 |
+
}
|
| 149 |
+
else if constexpr (cb == 2)
|
| 150 |
+
{
|
| 151 |
+
// The mul1 multiplication is equally linear modulo 2^32.
|
| 152 |
+
constexpr uint32_t product_step = 0x83DCD12Du << Kr;
|
| 153 |
+
product0 += product_step;
|
| 154 |
+
product1 += product_step;
|
| 155 |
+
decoded2 = decode_mul1_product_2(product0, product1);
|
| 156 |
+
}
|
| 157 |
+
else if constexpr (cb == 4 && K == 2)
|
| 158 |
+
{
|
| 159 |
+
decoded2 = decode_sqg_k2_pair(sqg_k2_labels, k);
|
| 160 |
+
}
|
| 161 |
+
else if constexpr (cb == 4 && K == 3)
|
| 162 |
+
{
|
| 163 |
+
decoded2 = decode_sqg_k3_pair(sqg_k3_labels, k);
|
| 164 |
+
}
|
| 165 |
+
else if constexpr (cb == 4 && K == 4)
|
| 166 |
+
{
|
| 167 |
+
decoded2 = decode_sqg_k4_pair(
|
| 168 |
+
sqg_k4_labels0, sqg_k4_labels1, k);
|
| 169 |
+
}
|
| 170 |
+
else
|
| 171 |
+
{
|
| 172 |
+
const int state0 = (k << Kr) | out_edge_idx;
|
| 173 |
+
decoded2 = decode_3inst_2<cb>(state0, state0 + 1);
|
| 174 |
+
}
|
| 175 |
+
dh2 = __hsub2(decoded2, w2);
|
| 176 |
+
half2 err2 = __hmin2(
|
| 177 |
+
__hmul2(dh2, dh2), __half2half2(H_MAX_FINITE));
|
| 178 |
+
if (pre_state >= 0 && in_edge_idx != pre_state) err2 = __half2half2(H_INF);
|
| 179 |
+
const unsigned less = __hlt2_mask(err2, min_err2);
|
| 180 |
+
min_err2 = __hmin2(err2, min_err2);
|
| 181 |
+
if (less & 0xffffu) min_k0 = k;
|
| 182 |
+
if (less >> 16) min_k1 = k;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
reinterpret_cast<half2*>(temp_costs)[out_edge_idx >> 1] = min_err2;
|
| 186 |
+
if (trace_start == 0)
|
| 187 |
+
{
|
| 188 |
+
if constexpr (K == 2)
|
| 189 |
+
{
|
| 190 |
+
const unsigned pair = min_k0 | (min_k1 << 2);
|
| 191 |
+
const unsigned next = __shfl_down_sync(0xffffffff, pair, 1);
|
| 192 |
+
if ((thread & 1) == 0)
|
| 193 |
+
temp_edges[decisions_per_row * ri + (out_edge_idx >> 2)] =
|
| 194 |
+
(uint8_t) (pair | (next << 4));
|
| 195 |
+
}
|
| 196 |
+
else if constexpr (K <= 4)
|
| 197 |
+
temp_edges[decisions_per_row * ri + (out_edge_idx >> 1)] =
|
| 198 |
+
(uint8_t) (min_k0 | (min_k1 << 4));
|
| 199 |
+
else
|
| 200 |
+
{
|
| 201 |
+
temp_edges[decisions_per_row * ri + out_edge_idx] = (uint8_t) min_k0;
|
| 202 |
+
temp_edges[decisions_per_row * ri + out_edge_idx + 1] = (uint8_t) min_k1;
|
| 203 |
+
}
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
__syncthreads();
|
| 207 |
+
|
| 208 |
+
for (int i = 1; i < steps; ++i)
|
| 209 |
+
{
|
| 210 |
+
ri = (i + roll) & 255;
|
| 211 |
+
t = temp_costs;
|
| 212 |
+
temp_costs = temp_costs_inc;
|
| 213 |
+
temp_costs_inc = t;
|
| 214 |
+
|
| 215 |
+
for (int out_edge_idx = 2 * thread; out_edge_idx < edges; out_edge_idx += 2 * num_threads)
|
| 216 |
+
{
|
| 217 |
+
const half2 w2 = __half2half2(sh_input_tile[ri]);
|
| 218 |
+
int in_edge_idx = out_edge_idx >> K;
|
| 219 |
+
uint32_t product0 = 0;
|
| 220 |
+
uint32_t product1 = 0;
|
| 221 |
+
uint2 sqg_k2_labels{};
|
| 222 |
+
uint4 sqg_k3_labels{};
|
| 223 |
+
uint4 sqg_k4_labels0{};
|
| 224 |
+
uint4 sqg_k4_labels1{};
|
| 225 |
+
half2 decoded2;
|
| 226 |
+
if constexpr (cb == 1)
|
| 227 |
+
{
|
| 228 |
+
product0 = mul_const_u32<0xCBAC1FEDu>(out_edge_idx);
|
| 229 |
+
product1 = product0 + 0xCBAC1FEDu;
|
| 230 |
+
decoded2 = decode_mcg_product_2(product0, product1);
|
| 231 |
+
}
|
| 232 |
+
else if constexpr (cb == 2)
|
| 233 |
+
{
|
| 234 |
+
product0 = out_edge_idx * 0x83DCD12Du;
|
| 235 |
+
product1 = product0 + 0x83DCD12Du;
|
| 236 |
+
decoded2 = decode_mul1_product_2(product0, product1);
|
| 237 |
+
}
|
| 238 |
+
else if constexpr (cb == 4 && K == 2)
|
| 239 |
+
{
|
| 240 |
+
sqg_k2_labels =
|
| 241 |
+
reinterpret_cast<const uint2*>(sqg_e4m3_lut)[out_edge_idx >> 1];
|
| 242 |
+
decoded2 = decode_sqg_k2_pair(sqg_k2_labels, 0);
|
| 243 |
+
}
|
| 244 |
+
else if constexpr (cb == 4 && K == 3)
|
| 245 |
+
{
|
| 246 |
+
sqg_k3_labels =
|
| 247 |
+
reinterpret_cast<const uint4*>(sqg_e4m3_lut)[out_edge_idx >> 1];
|
| 248 |
+
decoded2 = decode_sqg_k3_pair(sqg_k3_labels, 0);
|
| 249 |
+
}
|
| 250 |
+
else if constexpr (cb == 4 && K == 4)
|
| 251 |
+
{
|
| 252 |
+
const int packed_index = out_edge_idx;
|
| 253 |
+
sqg_k4_labels0 =
|
| 254 |
+
reinterpret_cast<const uint4*>(sqg_e4m3_lut)[packed_index];
|
| 255 |
+
sqg_k4_labels1 =
|
| 256 |
+
reinterpret_cast<const uint4*>(sqg_e4m3_lut)[packed_index + 1];
|
| 257 |
+
decoded2 = decode_sqg_k4_pair(
|
| 258 |
+
sqg_k4_labels0, sqg_k4_labels1, 0);
|
| 259 |
+
}
|
| 260 |
+
else
|
| 261 |
+
{
|
| 262 |
+
decoded2 = decode_3inst_2<cb>(out_edge_idx, out_edge_idx + 1);
|
| 263 |
+
}
|
| 264 |
+
half2 dh2 = __hsub2(decoded2, w2);
|
| 265 |
+
half predecessor_cost = temp_costs_inc[in_edge_idx];
|
| 266 |
+
half2 min_err2 = __heq(predecessor_cost, H_INF)
|
| 267 |
+
? __half2half2(H_INF)
|
| 268 |
+
: __hmin2(
|
| 269 |
+
__hfma2(
|
| 270 |
+
dh2, dh2, __half2half2(predecessor_cost)),
|
| 271 |
+
__half2half2(H_MAX_FINITE));
|
| 272 |
+
int min_k0 = 0;
|
| 273 |
+
int min_k1 = 0;
|
| 274 |
+
|
| 275 |
+
#pragma unroll
|
| 276 |
+
for (int k = 1; k < max_q; ++k)
|
| 277 |
+
{
|
| 278 |
+
in_edge_idx += predecessor_step;
|
| 279 |
+
if constexpr (cb == 1)
|
| 280 |
+
{
|
| 281 |
+
// MCG multiplication is linear modulo 2^32 across successive branch states.
|
| 282 |
+
constexpr uint32_t product_step = 0xCBAC1FEDu << Kr;
|
| 283 |
+
product0 += product_step;
|
| 284 |
+
product1 += product_step;
|
| 285 |
+
decoded2 = decode_mcg_product_2(product0, product1);
|
| 286 |
+
}
|
| 287 |
+
else if constexpr (cb == 2)
|
| 288 |
+
{
|
| 289 |
+
// The mul1 multiplication is equally linear modulo 2^32.
|
| 290 |
+
constexpr uint32_t product_step = 0x83DCD12Du << Kr;
|
| 291 |
+
product0 += product_step;
|
| 292 |
+
product1 += product_step;
|
| 293 |
+
decoded2 = decode_mul1_product_2(product0, product1);
|
| 294 |
+
}
|
| 295 |
+
else if constexpr (cb == 4 && K == 2)
|
| 296 |
+
{
|
| 297 |
+
decoded2 = decode_sqg_k2_pair(sqg_k2_labels, k);
|
| 298 |
+
}
|
| 299 |
+
else if constexpr (cb == 4 && K == 3)
|
| 300 |
+
{
|
| 301 |
+
decoded2 = decode_sqg_k3_pair(sqg_k3_labels, k);
|
| 302 |
+
}
|
| 303 |
+
else if constexpr (cb == 4 && K == 4)
|
| 304 |
+
{
|
| 305 |
+
decoded2 = decode_sqg_k4_pair(
|
| 306 |
+
sqg_k4_labels0, sqg_k4_labels1, k);
|
| 307 |
+
}
|
| 308 |
+
else
|
| 309 |
+
{
|
| 310 |
+
const int state0 = (k << Kr) | out_edge_idx;
|
| 311 |
+
decoded2 = decode_3inst_2<cb>(state0, state0 + 1);
|
| 312 |
+
}
|
| 313 |
+
dh2 = __hsub2(decoded2, w2);
|
| 314 |
+
predecessor_cost = temp_costs_inc[in_edge_idx];
|
| 315 |
+
half2 err2 = __heq(predecessor_cost, H_INF)
|
| 316 |
+
? __half2half2(H_INF)
|
| 317 |
+
: __hmin2(
|
| 318 |
+
__hfma2(
|
| 319 |
+
dh2, dh2, __half2half2(predecessor_cost)),
|
| 320 |
+
__half2half2(H_MAX_FINITE));
|
| 321 |
+
const unsigned less = __hlt2_mask(err2, min_err2);
|
| 322 |
+
min_err2 = __hmin2(err2, min_err2);
|
| 323 |
+
if (less & 0xffffu) min_k0 = k;
|
| 324 |
+
if (less >> 16) min_k1 = k;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
reinterpret_cast<half2*>(temp_costs)[out_edge_idx >> 1] = min_err2;
|
| 328 |
+
if (i >= trace_start)
|
| 329 |
+
{
|
| 330 |
+
if constexpr (K == 2)
|
| 331 |
+
{
|
| 332 |
+
const unsigned pair = min_k0 | (min_k1 << 2);
|
| 333 |
+
const unsigned next = __shfl_down_sync(0xffffffff, pair, 1);
|
| 334 |
+
if ((thread & 1) == 0)
|
| 335 |
+
temp_edges[decisions_per_row * ri + (out_edge_idx >> 2)] =
|
| 336 |
+
(uint8_t) (pair | (next << 4));
|
| 337 |
+
}
|
| 338 |
+
else if constexpr (K <= 4)
|
| 339 |
+
temp_edges[decisions_per_row * ri + (out_edge_idx >> 1)] =
|
| 340 |
+
(uint8_t) (min_k0 | (min_k1 << 4));
|
| 341 |
+
else
|
| 342 |
+
{
|
| 343 |
+
temp_edges[decisions_per_row * ri + out_edge_idx] = (uint8_t) min_k0;
|
| 344 |
+
temp_edges[decisions_per_row * ri + out_edge_idx + 1] = (uint8_t) min_k1;
|
| 345 |
+
}
|
| 346 |
+
}
|
| 347 |
+
}
|
| 348 |
+
__syncthreads();
|
| 349 |
+
}
|
| 350 |
+
};
|
| 351 |
+
|
| 352 |
+
auto argmin_cost = [&]()
|
| 353 |
+
{
|
| 354 |
+
// Preserve the historical 1024-thread tie-breaking order.
|
| 355 |
+
const int lane_id = thread & 31;
|
| 356 |
+
const int warp_id = thread >> 5;
|
| 357 |
+
if constexpr (K == 2)
|
| 358 |
+
{
|
| 359 |
+
// With 1024 K2 threads, each warp represents exactly one of the
|
| 360 |
+
// 32 reduction groups used by the historical 512-thread kernel's
|
| 361 |
+
// two local streams. The strict-less reduction therefore keeps
|
| 362 |
+
// the same deterministic tie order.
|
| 363 |
+
half local_min = H_INF;
|
| 364 |
+
int local_idx = -1;
|
| 365 |
+
for (int e = thread; e < edges; e += num_threads)
|
| 366 |
+
{
|
| 367 |
+
half v = temp_costs_inc[e];
|
| 368 |
+
if (__hlt(v, local_min)) { local_min = v; local_idx = e; }
|
| 369 |
+
}
|
| 370 |
+
#pragma unroll
|
| 371 |
+
for (int offset = 16; offset > 0; offset >>= 1)
|
| 372 |
+
{
|
| 373 |
+
half other_min = __shfl_down_sync(0xffffffff, local_min, offset);
|
| 374 |
+
int other_idx = __shfl_down_sync(0xffffffff, local_idx, offset);
|
| 375 |
+
if (__hlt(other_min, local_min)) { local_min = other_min; local_idx = other_idx; }
|
| 376 |
+
}
|
| 377 |
+
sh_min[warp_id] = local_min;
|
| 378 |
+
sh_idx[warp_id] = local_idx;
|
| 379 |
+
}
|
| 380 |
+
else
|
| 381 |
+
{
|
| 382 |
+
// Preserve the established 512-thread reduction tree even when
|
| 383 |
+
// the forward pass uses a wider block. This keeps exact-tie
|
| 384 |
+
// path selection stable across encoder-kernel revisions.
|
| 385 |
+
if (thread < 512)
|
| 386 |
+
{
|
| 387 |
+
half local_min0 = H_INF;
|
| 388 |
+
half local_min1 = H_INF;
|
| 389 |
+
int local_idx0 = -1;
|
| 390 |
+
int local_idx1 = -1;
|
| 391 |
+
for (int e = thread; e < edges; e += 1024)
|
| 392 |
+
{
|
| 393 |
+
half v = temp_costs_inc[e];
|
| 394 |
+
if (__hlt(v, local_min0)) { local_min0 = v; local_idx0 = e; }
|
| 395 |
+
}
|
| 396 |
+
for (int e = thread + 512; e < edges; e += 1024)
|
| 397 |
+
{
|
| 398 |
+
half v = temp_costs_inc[e];
|
| 399 |
+
if (__hlt(v, local_min1)) { local_min1 = v; local_idx1 = e; }
|
| 400 |
+
}
|
| 401 |
+
#pragma unroll
|
| 402 |
+
for (int offset = 16; offset > 0; offset >>= 1)
|
| 403 |
+
{
|
| 404 |
+
half other_min0 = __shfl_down_sync(0xffffffff, local_min0, offset);
|
| 405 |
+
int other_idx0 = __shfl_down_sync(0xffffffff, local_idx0, offset);
|
| 406 |
+
if (__hlt(other_min0, local_min0)) { local_min0 = other_min0; local_idx0 = other_idx0; }
|
| 407 |
+
half other_min1 = __shfl_down_sync(0xffffffff, local_min1, offset);
|
| 408 |
+
int other_idx1 = __shfl_down_sync(0xffffffff, local_idx1, offset);
|
| 409 |
+
if (__hlt(other_min1, local_min1)) { local_min1 = other_min1; local_idx1 = other_idx1; }
|
| 410 |
+
}
|
| 411 |
+
sh_min[warp_id] = local_min0;
|
| 412 |
+
sh_idx[warp_id] = local_idx0;
|
| 413 |
+
sh_min[16 + warp_id] = local_min1;
|
| 414 |
+
sh_idx[16 + warp_id] = local_idx1;
|
| 415 |
+
}
|
| 416 |
+
}
|
| 417 |
+
__syncthreads();
|
| 418 |
+
|
| 419 |
+
int local_idx = 0;
|
| 420 |
+
if (warp_id == 0)
|
| 421 |
+
{
|
| 422 |
+
half local_min = sh_min[lane_id];
|
| 423 |
+
local_idx = sh_idx[lane_id];
|
| 424 |
+
#pragma unroll
|
| 425 |
+
for (int offset = 16; offset > 0; offset >>= 1)
|
| 426 |
+
{
|
| 427 |
+
half other_min = __shfl_down_sync(0xffffffff, local_min, offset);
|
| 428 |
+
int other_idx = __shfl_down_sync(0xffffffff, local_idx, offset);
|
| 429 |
+
if (__hlt(other_min, local_min)) { local_min = other_min; local_idx = other_idx; }
|
| 430 |
+
}
|
| 431 |
+
}
|
| 432 |
+
return local_idx;
|
| 433 |
+
};
|
| 434 |
+
|
| 435 |
+
auto backward = [&](int roll, int steps, bool write, int edge)
|
| 436 |
+
{
|
| 437 |
+
if (thread == 0)
|
| 438 |
+
{
|
| 439 |
+
for (int i = steps - 1; i >= 0; --i)
|
| 440 |
+
{
|
| 441 |
+
const int ri = (i + roll) & 255;
|
| 442 |
+
int decision;
|
| 443 |
+
if constexpr (K == 2)
|
| 444 |
+
{
|
| 445 |
+
const uint8_t packed =
|
| 446 |
+
temp_edges[decisions_per_row * ri + (edge >> 2)];
|
| 447 |
+
decision = (packed >> ((edge & 3) * 2)) & 0x03;
|
| 448 |
+
}
|
| 449 |
+
else if constexpr (K <= 4)
|
| 450 |
+
{
|
| 451 |
+
const uint8_t packed =
|
| 452 |
+
temp_edges[decisions_per_row * ri + (edge >> 1)];
|
| 453 |
+
decision = (packed >> ((edge & 1) * 4)) & 0x0f;
|
| 454 |
+
}
|
| 455 |
+
else
|
| 456 |
+
decision = (int) temp_edges[decisions_per_row * ri + edge];
|
| 457 |
+
const int prev_edge =
|
| 458 |
+
(decision << decision_shift) | (edge >> K);
|
| 459 |
+
const int encoded = (prev_edge << K) | edge;
|
| 460 |
+
edge = prev_edge;
|
| 461 |
+
if (write)
|
| 462 |
+
{
|
| 463 |
+
output_indices[ri] = (uint16_t) encoded;
|
| 464 |
+
if constexpr (cb == 4 && K == 2)
|
| 465 |
+
output_tile[ri] = __half2float(decode_sqg_k2_scalar(encoded));
|
| 466 |
+
else if constexpr (cb == 4 && K == 3)
|
| 467 |
+
output_tile[ri] = __half2float(decode_sqg_k3_scalar(encoded));
|
| 468 |
+
else if constexpr (cb == 4 && K == 4)
|
| 469 |
+
output_tile[ri] = __half2float(decode_sqg_k4_scalar(encoded));
|
| 470 |
+
else
|
| 471 |
+
output_tile[ri] = __half2float(decode_3inst<cb>(encoded));
|
| 472 |
+
}
|
| 473 |
+
else if (ri == 0) break;
|
| 474 |
+
}
|
| 475 |
+
}
|
| 476 |
+
if (thread == 0) sh_idx[0] = edge;
|
| 477 |
+
__syncthreads();
|
| 478 |
+
return sh_idx[0];
|
| 479 |
+
};
|
| 480 |
+
|
| 481 |
+
// Infer a cyclic boundary state from symmetric context around element 0,
|
| 482 |
+
// then run the ordinary complete constrained pass. The historical path
|
| 483 |
+
// uses 128 symbols on either side (a full 256-step primer). Smaller
|
| 484 |
+
// contexts are an encoder-only search approximation: the final pass is
|
| 485 |
+
// still a valid closed trellis and the bitstream/decoder are unchanged.
|
| 486 |
+
const int primer_roll = 256 - tailbite_context;
|
| 487 |
+
const int primer_steps = 2 * tailbite_context;
|
| 488 |
+
forward(primer_roll, primer_steps, -1, tailbite_context);
|
| 489 |
+
int end_state = backward(primer_roll, primer_steps, false, argmin_cost());
|
| 490 |
+
forward(0, 256, end_state, 0);
|
| 491 |
+
backward(0, 256, true, end_state);
|
| 492 |
+
}
|
reproduction/closure/patches/kquant/changed-files/kquant/csrc/sqg_quantize.cu
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <ATen/cuda/CUDAContext.h>
|
| 2 |
+
#include <c10/cuda/CUDAGuard.h>
|
| 3 |
+
#include <cuda_fp16.h>
|
| 4 |
+
#include <cuda_fp8.h>
|
| 5 |
+
#include <torch/extension.h>
|
| 6 |
+
|
| 7 |
+
#include "exl3_compat/util.cuh"
|
| 8 |
+
#include "exl3_compat/quant/codebook.cuh"
|
| 9 |
+
|
| 10 |
+
// Keep only the table pointer in broadcast-friendly constant memory. The
|
| 11 |
+
// labels themselves remain in ordinary read-only global memory: a warp's
|
| 12 |
+
// Viterbi lanes normally request different states, which would serialize a
|
| 13 |
+
// 64-KiB constant-memory table despite its attractive nominal footprint.
|
| 14 |
+
__device__ __constant__ const uint8_t* sqg_e4m3_lut;
|
| 15 |
+
|
| 16 |
+
template <>
|
| 17 |
+
__device__ inline half decode_3inst<4>(uint32_t state)
|
| 18 |
+
{
|
| 19 |
+
const __nv_fp8_storage_t fp8 = sqg_e4m3_lut[state & 0xffffu];
|
| 20 |
+
const __half_raw raw = __nv_cvt_fp8_to_halfraw(fp8, __NV_E4M3);
|
| 21 |
+
return __ushort_as_half(raw.x);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
template <>
|
| 25 |
+
__device__ inline half2 decode_3inst_2<4>(uint32_t state0, uint32_t state1)
|
| 26 |
+
{
|
| 27 |
+
// The trellis kernel always asks for an aligned consecutive pair. Load
|
| 28 |
+
// both E4M3 labels together so a warp consumes every byte in each cache
|
| 29 |
+
// sector instead of issuing separate even- and odd-byte gathers.
|
| 30 |
+
const __nv_fp8x2_storage_t fp8x2 =
|
| 31 |
+
reinterpret_cast<const __nv_fp8x2_storage_t*>(sqg_e4m3_lut)[state0 >> 1];
|
| 32 |
+
const __half2_raw raw = __nv_cvt_fp8x2_to_halfraw2(fp8x2, __NV_E4M3);
|
| 33 |
+
return __halves2half2(__ushort_as_half(raw.x), __ushort_as_half(raw.y));
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
__device__ inline half2 decode_sqg_fp8x2(__nv_fp8x2_storage_t fp8x2)
|
| 37 |
+
{
|
| 38 |
+
const __half2_raw raw = __nv_cvt_fp8x2_to_halfraw2(fp8x2, __NV_E4M3);
|
| 39 |
+
return __halves2half2(__ushort_as_half(raw.x), __ushort_as_half(raw.y));
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
__device__ inline half2 decode_sqg_k2_pair(const uint2& packed, int predecessor)
|
| 43 |
+
{
|
| 44 |
+
const uint32_t word = predecessor < 2 ? packed.x : packed.y;
|
| 45 |
+
return decode_sqg_fp8x2(
|
| 46 |
+
static_cast<__nv_fp8x2_storage_t>(word >> (16 * (predecessor & 1))));
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
__device__ inline half decode_sqg_k2_scalar(uint32_t state)
|
| 50 |
+
{
|
| 51 |
+
constexpr int edges = 65536 >> 2;
|
| 52 |
+
const int predecessor = state >> 14;
|
| 53 |
+
const int out_edge = state & (edges - 1);
|
| 54 |
+
const __nv_fp8x2_storage_t pair =
|
| 55 |
+
reinterpret_cast<const __nv_fp8x2_storage_t*>(sqg_e4m3_lut)[
|
| 56 |
+
(out_edge >> 1) * 4 + predecessor];
|
| 57 |
+
const __nv_fp8_storage_t fp8 =
|
| 58 |
+
static_cast<__nv_fp8_storage_t>(pair >> (8 * (out_edge & 1)));
|
| 59 |
+
const __half_raw raw = __nv_cvt_fp8_to_halfraw(fp8, __NV_E4M3);
|
| 60 |
+
return __ushort_as_half(raw.x);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
__device__ inline half2 decode_sqg_k3_pair(const uint4& packed, int predecessor)
|
| 64 |
+
{
|
| 65 |
+
uint32_t word;
|
| 66 |
+
switch (predecessor >> 1)
|
| 67 |
+
{
|
| 68 |
+
case 0: word = packed.x; break;
|
| 69 |
+
case 1: word = packed.y; break;
|
| 70 |
+
case 2: word = packed.z; break;
|
| 71 |
+
default: word = packed.w; break;
|
| 72 |
+
}
|
| 73 |
+
return decode_sqg_fp8x2(
|
| 74 |
+
static_cast<__nv_fp8x2_storage_t>(word >> (16 * (predecessor & 1))));
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
__device__ inline half2 decode_sqg_k4_pair(
|
| 78 |
+
const uint4& packed0, const uint4& packed1, int predecessor)
|
| 79 |
+
{
|
| 80 |
+
const uint4& packed = predecessor < 8 ? packed0 : packed1;
|
| 81 |
+
const int local = predecessor & 7;
|
| 82 |
+
uint32_t word;
|
| 83 |
+
switch (local >> 1)
|
| 84 |
+
{
|
| 85 |
+
case 0: word = packed.x; break;
|
| 86 |
+
case 1: word = packed.y; break;
|
| 87 |
+
case 2: word = packed.z; break;
|
| 88 |
+
default: word = packed.w; break;
|
| 89 |
+
}
|
| 90 |
+
return decode_sqg_fp8x2(
|
| 91 |
+
static_cast<__nv_fp8x2_storage_t>(word >> (16 * (local & 1))));
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
__device__ inline half decode_sqg_k4_scalar(uint32_t state)
|
| 95 |
+
{
|
| 96 |
+
constexpr int edges = 65536 >> 4;
|
| 97 |
+
const int predecessor = state >> 12;
|
| 98 |
+
const int out_edge = state & (edges - 1);
|
| 99 |
+
const __nv_fp8x2_storage_t pair =
|
| 100 |
+
reinterpret_cast<const __nv_fp8x2_storage_t*>(sqg_e4m3_lut)[
|
| 101 |
+
(out_edge >> 1) * 16 + predecessor];
|
| 102 |
+
const __nv_fp8_storage_t fp8 =
|
| 103 |
+
static_cast<__nv_fp8_storage_t>(pair >> (8 * (out_edge & 1)));
|
| 104 |
+
const __half_raw raw = __nv_cvt_fp8_to_halfraw(fp8, __NV_E4M3);
|
| 105 |
+
return __ushort_as_half(raw.x);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
__device__ inline half decode_sqg_k3_scalar(uint32_t state)
|
| 109 |
+
{
|
| 110 |
+
constexpr int edges = 65536 >> 3;
|
| 111 |
+
const int predecessor = state >> 13;
|
| 112 |
+
const int out_edge = state & (edges - 1);
|
| 113 |
+
const __nv_fp8x2_storage_t pair =
|
| 114 |
+
reinterpret_cast<const __nv_fp8x2_storage_t*>(sqg_e4m3_lut)[
|
| 115 |
+
(out_edge >> 1) * 8 + predecessor];
|
| 116 |
+
const __nv_fp8_storage_t fp8 =
|
| 117 |
+
static_cast<__nv_fp8_storage_t>(pair >> (8 * (out_edge & 1)));
|
| 118 |
+
const __half_raw raw = __nv_cvt_fp8_to_halfraw(fp8, __NV_E4M3);
|
| 119 |
+
return __ushort_as_half(raw.x);
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
#include "qsrt_quantize_tiles_kernel.cuh"
|
| 123 |
+
|
| 124 |
+
namespace
|
| 125 |
+
{
|
| 126 |
+
|
| 127 |
+
template <int K, int codebook>
|
| 128 |
+
void launch_trellis(
|
| 129 |
+
const float* input,
|
| 130 |
+
float* output,
|
| 131 |
+
uint16_t* indices,
|
| 132 |
+
half* costs,
|
| 133 |
+
uint8_t* edges,
|
| 134 |
+
int tiles,
|
| 135 |
+
int max_batch,
|
| 136 |
+
int shared_memory,
|
| 137 |
+
int tailbite_context,
|
| 138 |
+
cudaStream_t stream)
|
| 139 |
+
{
|
| 140 |
+
const int threads = K == 2 ? 1024 : (K == 3 ? 640 : 704);
|
| 141 |
+
cudaFuncSetAttribute(
|
| 142 |
+
quantize_tiles_kernel<K, codebook>,
|
| 143 |
+
cudaFuncAttributeMaxDynamicSharedMemorySize,
|
| 144 |
+
shared_memory);
|
| 145 |
+
cudaFuncSetAttribute(
|
| 146 |
+
quantize_tiles_kernel<K, codebook>,
|
| 147 |
+
cudaFuncAttributePreferredSharedMemoryCarveout,
|
| 148 |
+
K == 4 ? cudaSharedmemCarveoutMaxL1 : cudaSharedmemCarveoutMaxShared);
|
| 149 |
+
for (int start = 0; start < tiles; start += max_batch)
|
| 150 |
+
{
|
| 151 |
+
const int batch = min(max_batch, tiles - start);
|
| 152 |
+
quantize_tiles_kernel<K, codebook><<<batch, threads, shared_memory, stream>>>(
|
| 153 |
+
input + 256 * start,
|
| 154 |
+
output + 256 * start,
|
| 155 |
+
indices + 256 * start,
|
| 156 |
+
costs,
|
| 157 |
+
edges,
|
| 158 |
+
tailbite_context);
|
| 159 |
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
} // namespace
|
| 164 |
+
|
| 165 |
+
void quantize_tiles_sqg_cuda(
|
| 166 |
+
at::Tensor input_tiles,
|
| 167 |
+
at::Tensor output_tiles,
|
| 168 |
+
at::Tensor output_indices,
|
| 169 |
+
at::Tensor temp_costs,
|
| 170 |
+
at::Tensor temp_edges,
|
| 171 |
+
at::Tensor codebook,
|
| 172 |
+
int64_t K,
|
| 173 |
+
int64_t tailbite_context)
|
| 174 |
+
{
|
| 175 |
+
const at::cuda::OptionalCUDAGuard guard(input_tiles.device());
|
| 176 |
+
const cudaStream_t stream = at::cuda::getCurrentCUDAStream().stream();
|
| 177 |
+
|
| 178 |
+
TORCH_CHECK(input_tiles.is_cuda(), "input_tiles must be CUDA");
|
| 179 |
+
TORCH_CHECK(input_tiles.scalar_type() == at::kFloat, "input_tiles must be FP32");
|
| 180 |
+
TORCH_CHECK(input_tiles.dim() == 2 && input_tiles.size(1) == 256,
|
| 181 |
+
"input_tiles must have shape [tiles, 256]");
|
| 182 |
+
TORCH_CHECK(output_tiles.sizes() == input_tiles.sizes(),
|
| 183 |
+
"output_tiles shape mismatch");
|
| 184 |
+
TORCH_CHECK(output_tiles.scalar_type() == at::kFloat,
|
| 185 |
+
"output_tiles must be FP32");
|
| 186 |
+
TORCH_CHECK(output_indices.sizes() == input_tiles.sizes(),
|
| 187 |
+
"output_indices shape mismatch");
|
| 188 |
+
TORCH_CHECK(output_indices.scalar_type() == at::kShort,
|
| 189 |
+
"output_indices must be int16");
|
| 190 |
+
TORCH_CHECK(K >= 2 && K <= 6, "SQG validation kernel supports K2--K6");
|
| 191 |
+
TORCH_CHECK(tailbite_context >= 1 && tailbite_context <= 128,
|
| 192 |
+
"tailbite_context must be in 1..128");
|
| 193 |
+
TORCH_CHECK(codebook.is_cuda() && codebook.device() == input_tiles.device(),
|
| 194 |
+
"codebook must be on the input CUDA device");
|
| 195 |
+
TORCH_CHECK(codebook.scalar_type() == at::kByte && codebook.numel() == 65536,
|
| 196 |
+
"codebook must contain 65,536 uint8 E4M3 values");
|
| 197 |
+
TORCH_CHECK(codebook.is_contiguous(), "codebook must be contiguous");
|
| 198 |
+
|
| 199 |
+
const int edges_per_state = 65536 >> K;
|
| 200 |
+
const int decision_entries =
|
| 201 |
+
K == 2 ? edges_per_state / 4 : (K <= 4 ? edges_per_state / 2 : edges_per_state);
|
| 202 |
+
TORCH_CHECK(temp_costs.scalar_type() == at::kHalf && temp_costs.dim() == 3,
|
| 203 |
+
"temp_costs must be rank-3 FP16");
|
| 204 |
+
TORCH_CHECK(temp_costs.size(1) == 2 && temp_costs.size(2) == edges_per_state,
|
| 205 |
+
"temp_costs shape mismatch");
|
| 206 |
+
TORCH_CHECK(temp_edges.scalar_type() == at::kByte && temp_edges.dim() == 3,
|
| 207 |
+
"temp_edges must be rank-3 uint8");
|
| 208 |
+
TORCH_CHECK(temp_edges.size(1) == 256 && temp_edges.size(2) == decision_entries,
|
| 209 |
+
"temp_edges shape mismatch");
|
| 210 |
+
|
| 211 |
+
const uint8_t* codebook_pointer = codebook.data_ptr<uint8_t>();
|
| 212 |
+
C10_CUDA_CHECK(cudaMemcpyToSymbolAsync(
|
| 213 |
+
sqg_e4m3_lut,
|
| 214 |
+
&codebook_pointer,
|
| 215 |
+
sizeof(codebook_pointer),
|
| 216 |
+
0,
|
| 217 |
+
cudaMemcpyHostToDevice,
|
| 218 |
+
stream));
|
| 219 |
+
|
| 220 |
+
int device = -1;
|
| 221 |
+
C10_CUDA_CHECK(cudaGetDevice(&device));
|
| 222 |
+
int multiprocessors = 0;
|
| 223 |
+
C10_CUDA_CHECK(cudaDeviceGetAttribute(
|
| 224 |
+
&multiprocessors, cudaDevAttrMultiProcessorCount, device));
|
| 225 |
+
const int max_batch = min(
|
| 226 |
+
static_cast<int>(temp_costs.size(0)), 3 * multiprocessors);
|
| 227 |
+
const int shared_memory =
|
| 228 |
+
2 * edges_per_state * static_cast<int>(sizeof(half)) + 512 + 64 + 128;
|
| 229 |
+
const int tiles = static_cast<int>(input_tiles.size(0));
|
| 230 |
+
if (!tiles) return;
|
| 231 |
+
|
| 232 |
+
switch (K)
|
| 233 |
+
{
|
| 234 |
+
case 2:
|
| 235 |
+
launch_trellis<2, 4>(
|
| 236 |
+
input_tiles.data_ptr<float>(), output_tiles.data_ptr<float>(),
|
| 237 |
+
reinterpret_cast<uint16_t*>(output_indices.data_ptr<int16_t>()),
|
| 238 |
+
reinterpret_cast<half*>(temp_costs.data_ptr<at::Half>()),
|
| 239 |
+
temp_edges.data_ptr<uint8_t>(), tiles, max_batch, shared_memory,
|
| 240 |
+
static_cast<int>(tailbite_context), stream);
|
| 241 |
+
break;
|
| 242 |
+
case 3:
|
| 243 |
+
launch_trellis<3, 4>(
|
| 244 |
+
input_tiles.data_ptr<float>(), output_tiles.data_ptr<float>(),
|
| 245 |
+
reinterpret_cast<uint16_t*>(output_indices.data_ptr<int16_t>()),
|
| 246 |
+
reinterpret_cast<half*>(temp_costs.data_ptr<at::Half>()),
|
| 247 |
+
temp_edges.data_ptr<uint8_t>(), tiles, max_batch, shared_memory,
|
| 248 |
+
static_cast<int>(tailbite_context), stream);
|
| 249 |
+
break;
|
| 250 |
+
case 4:
|
| 251 |
+
launch_trellis<4, 4>(
|
| 252 |
+
input_tiles.data_ptr<float>(), output_tiles.data_ptr<float>(),
|
| 253 |
+
reinterpret_cast<uint16_t*>(output_indices.data_ptr<int16_t>()),
|
| 254 |
+
reinterpret_cast<half*>(temp_costs.data_ptr<at::Half>()),
|
| 255 |
+
temp_edges.data_ptr<uint8_t>(), tiles, max_batch, shared_memory,
|
| 256 |
+
static_cast<int>(tailbite_context), stream);
|
| 257 |
+
break;
|
| 258 |
+
case 5:
|
| 259 |
+
launch_trellis<5, 4>(
|
| 260 |
+
input_tiles.data_ptr<float>(), output_tiles.data_ptr<float>(),
|
| 261 |
+
reinterpret_cast<uint16_t*>(output_indices.data_ptr<int16_t>()),
|
| 262 |
+
reinterpret_cast<half*>(temp_costs.data_ptr<at::Half>()),
|
| 263 |
+
temp_edges.data_ptr<uint8_t>(), tiles, max_batch, shared_memory,
|
| 264 |
+
static_cast<int>(tailbite_context), stream);
|
| 265 |
+
break;
|
| 266 |
+
case 6:
|
| 267 |
+
launch_trellis<6, 4>(
|
| 268 |
+
input_tiles.data_ptr<float>(), output_tiles.data_ptr<float>(),
|
| 269 |
+
reinterpret_cast<uint16_t*>(output_indices.data_ptr<int16_t>()),
|
| 270 |
+
reinterpret_cast<half*>(temp_costs.data_ptr<at::Half>()),
|
| 271 |
+
temp_edges.data_ptr<uint8_t>(), tiles, max_batch, shared_memory,
|
| 272 |
+
static_cast<int>(tailbite_context), stream);
|
| 273 |
+
break;
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
void quantize_tiles_procedural_cuda(
|
| 278 |
+
at::Tensor input_tiles,
|
| 279 |
+
at::Tensor output_tiles,
|
| 280 |
+
at::Tensor output_indices,
|
| 281 |
+
at::Tensor temp_costs,
|
| 282 |
+
at::Tensor temp_edges,
|
| 283 |
+
int64_t K,
|
| 284 |
+
int64_t codebook,
|
| 285 |
+
int64_t tailbite_context)
|
| 286 |
+
{
|
| 287 |
+
const at::cuda::OptionalCUDAGuard guard(input_tiles.device());
|
| 288 |
+
const cudaStream_t stream = at::cuda::getCurrentCUDAStream().stream();
|
| 289 |
+
|
| 290 |
+
TORCH_CHECK(input_tiles.is_cuda(), "input_tiles must be CUDA");
|
| 291 |
+
TORCH_CHECK(input_tiles.scalar_type() == at::kFloat, "input_tiles must be FP32");
|
| 292 |
+
TORCH_CHECK(input_tiles.dim() == 2 && input_tiles.size(1) == 256,
|
| 293 |
+
"input_tiles must have shape [tiles, 256]");
|
| 294 |
+
TORCH_CHECK(output_tiles.sizes() == input_tiles.sizes(),
|
| 295 |
+
"output_tiles shape mismatch");
|
| 296 |
+
TORCH_CHECK(output_tiles.scalar_type() == at::kFloat,
|
| 297 |
+
"output_tiles must be FP32");
|
| 298 |
+
TORCH_CHECK(output_indices.sizes() == input_tiles.sizes(),
|
| 299 |
+
"output_indices shape mismatch");
|
| 300 |
+
TORCH_CHECK(output_indices.scalar_type() == at::kShort,
|
| 301 |
+
"output_indices must be int16");
|
| 302 |
+
TORCH_CHECK(K >= 2 && K <= 4, "procedural control supports K2--K4");
|
| 303 |
+
TORCH_CHECK(codebook == 1 || codebook == 2,
|
| 304 |
+
"procedural codebook must be 1 (MCG) or 2 (MUL1)");
|
| 305 |
+
TORCH_CHECK(tailbite_context >= 1 && tailbite_context <= 128,
|
| 306 |
+
"tailbite_context must be in 1..128");
|
| 307 |
+
|
| 308 |
+
const int edges_per_state = 65536 >> K;
|
| 309 |
+
const int decision_entries = K == 2 ? edges_per_state / 4 : edges_per_state / 2;
|
| 310 |
+
TORCH_CHECK(temp_costs.scalar_type() == at::kHalf && temp_costs.dim() == 3,
|
| 311 |
+
"temp_costs must be rank-3 FP16");
|
| 312 |
+
TORCH_CHECK(temp_costs.size(1) == 2 && temp_costs.size(2) == edges_per_state,
|
| 313 |
+
"temp_costs shape mismatch");
|
| 314 |
+
TORCH_CHECK(temp_edges.scalar_type() == at::kByte && temp_edges.dim() == 3,
|
| 315 |
+
"temp_edges must be rank-3 uint8");
|
| 316 |
+
TORCH_CHECK(temp_edges.size(1) == 256 && temp_edges.size(2) == decision_entries,
|
| 317 |
+
"temp_edges shape mismatch");
|
| 318 |
+
|
| 319 |
+
int device = -1;
|
| 320 |
+
C10_CUDA_CHECK(cudaGetDevice(&device));
|
| 321 |
+
int multiprocessors = 0;
|
| 322 |
+
C10_CUDA_CHECK(cudaDeviceGetAttribute(
|
| 323 |
+
&multiprocessors, cudaDevAttrMultiProcessorCount, device));
|
| 324 |
+
const int max_batch = min(
|
| 325 |
+
static_cast<int>(temp_costs.size(0)), 3 * multiprocessors);
|
| 326 |
+
const int shared_memory =
|
| 327 |
+
2 * edges_per_state * static_cast<int>(sizeof(half)) + 512 + 64 + 128;
|
| 328 |
+
const int tiles = static_cast<int>(input_tiles.size(0));
|
| 329 |
+
if (!tiles) return;
|
| 330 |
+
|
| 331 |
+
#define LAUNCH_PROCEDURAL(K_VALUE, CODEBOOK_VALUE) \
|
| 332 |
+
launch_trellis<K_VALUE, CODEBOOK_VALUE>( \
|
| 333 |
+
input_tiles.data_ptr<float>(), output_tiles.data_ptr<float>(), \
|
| 334 |
+
reinterpret_cast<uint16_t*>(output_indices.data_ptr<int16_t>()), \
|
| 335 |
+
reinterpret_cast<half*>(temp_costs.data_ptr<at::Half>()), \
|
| 336 |
+
temp_edges.data_ptr<uint8_t>(), tiles, max_batch, shared_memory, \
|
| 337 |
+
static_cast<int>(tailbite_context), stream)
|
| 338 |
+
|
| 339 |
+
if (codebook == 1)
|
| 340 |
+
{
|
| 341 |
+
switch (K)
|
| 342 |
+
{
|
| 343 |
+
case 2: LAUNCH_PROCEDURAL(2, 1); break;
|
| 344 |
+
case 3: LAUNCH_PROCEDURAL(3, 1); break;
|
| 345 |
+
case 4: LAUNCH_PROCEDURAL(4, 1); break;
|
| 346 |
+
}
|
| 347 |
+
}
|
| 348 |
+
else
|
| 349 |
+
{
|
| 350 |
+
switch (K)
|
| 351 |
+
{
|
| 352 |
+
case 2: LAUNCH_PROCEDURAL(2, 2); break;
|
| 353 |
+
case 3: LAUNCH_PROCEDURAL(3, 2); break;
|
| 354 |
+
case 4: LAUNCH_PROCEDURAL(4, 2); break;
|
| 355 |
+
}
|
| 356 |
+
}
|
| 357 |
+
#undef LAUNCH_PROCEDURAL
|
| 358 |
+
}
|
reproduction/closure/patches/kquant/changed-files/kquant/exl3_encoder_backend.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
reproduction/closure/patches/kquant/changed-files/kquant/exl3_reference.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Pure-PyTorch reference operations for EXL3 procedural codebooks."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
from kquant.sqg_e4m3 import (
|
| 10 |
+
SQG_XOR_CHEB_T12,
|
| 11 |
+
SQG_CHEB,
|
| 12 |
+
SQG_CHEB_NORMAL_E4M3,
|
| 13 |
+
SQG_NORMAL_E4M3,
|
| 14 |
+
sqg_codebook,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
TILE_VALUES = 256
|
| 19 |
+
HADAMARD_BLOCK = 128
|
| 20 |
+
CODEBOOK_SQG_NORMAL_E4M3 = SQG_NORMAL_E4M3
|
| 21 |
+
CODEBOOK_SQG_XOR_CHEB_T12 = SQG_XOR_CHEB_T12
|
| 22 |
+
CODEBOOK_SQG_CHEB_NORMAL_E4M3 = SQG_CHEB_NORMAL_E4M3
|
| 23 |
+
CODEBOOK_SQG_CHEB = SQG_CHEB
|
| 24 |
+
QSRT_CODEBOOKS = (
|
| 25 |
+
CODEBOOK_SQG_XOR_CHEB_T12,
|
| 26 |
+
CODEBOOK_SQG_NORMAL_E4M3,
|
| 27 |
+
CODEBOOK_SQG_CHEB_NORMAL_E4M3,
|
| 28 |
+
CODEBOOK_SQG_CHEB,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _validate_bits(bits: int) -> None:
|
| 33 |
+
if isinstance(bits, bool) or not isinstance(bits, int) or bits not in range(2, 7):
|
| 34 |
+
raise ValueError("the SQG reference supports K=2 through K=6")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def reconstruct_trellis_states(
|
| 38 |
+
edge_symbols: torch.Tensor, bits: int
|
| 39 |
+
) -> torch.Tensor:
|
| 40 |
+
"""Recover cyclic 16-bit trellis states from their stored K-bit edges.
|
| 41 |
+
|
| 42 |
+
A state is the low 16 bits of ``(previous_state << K) | edge``. EXL's
|
| 43 |
+
256-symbol tile is cyclic, so state zero also incorporates the final edge
|
| 44 |
+
symbols in the tile.
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
_validate_bits(bits)
|
| 48 |
+
if edge_symbols.ndim < 1 or edge_symbols.shape[-1] != TILE_VALUES:
|
| 49 |
+
raise ValueError("edge_symbols must have a final dimension of 256")
|
| 50 |
+
if edge_symbols.dtype == torch.bool or edge_symbols.is_floating_point():
|
| 51 |
+
raise TypeError("edge_symbols must use an integer dtype")
|
| 52 |
+
edges = edge_symbols.to(dtype=torch.int64) & ((1 << bits) - 1)
|
| 53 |
+
# Unroll the cyclic recurrence in state space. At position i, edge i is
|
| 54 |
+
# in the low K bits, edge i-1 is above it, and so on. Only ceil(16 / K)
|
| 55 |
+
# edges can survive the low-16-bit mask. Expressing that closed form with
|
| 56 |
+
# 4--8 rolls avoids launching one tensor operation for every one of the
|
| 57 |
+
# 256 symbols when this reference check runs on CUDA.
|
| 58 |
+
states = torch.zeros_like(edges)
|
| 59 |
+
for lag in range(math.ceil(16 / bits)):
|
| 60 |
+
states |= torch.roll(edges, shifts=lag, dims=-1) << (lag * bits)
|
| 61 |
+
return (states & 0xFFFF).to(dtype=torch.int16).contiguous()
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _decode_codebook_states(
|
| 65 |
+
states: torch.Tensor,
|
| 66 |
+
codebook: str,
|
| 67 |
+
*,
|
| 68 |
+
bits: int | None = None,
|
| 69 |
+
rate_axis: str | None = None,
|
| 70 |
+
) -> torch.Tensor:
|
| 71 |
+
if codebook in QSRT_CODEBOOKS:
|
| 72 |
+
if bits is None:
|
| 73 |
+
raise ValueError("SQG reconstruction requires an explicit trellis rate")
|
| 74 |
+
_validate_bits(bits)
|
| 75 |
+
indices = (states.to(dtype=torch.int64) & 0xFFFF).long()
|
| 76 |
+
values = sqg_codebook(
|
| 77 |
+
bits,
|
| 78 |
+
codebook,
|
| 79 |
+
rate_axis=rate_axis,
|
| 80 |
+
device=states.device,
|
| 81 |
+
dtype=torch.float16,
|
| 82 |
+
)
|
| 83 |
+
return values.index_select(0, indices.flatten()).reshape_as(states)
|
| 84 |
+
raise ValueError(
|
| 85 |
+
f"unsupported QSRT codebook {codebook!r}; expected one of {QSRT_CODEBOOKS}"
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def tensor_core_permutation(device: torch.device | str = "cpu") -> torch.Tensor:
|
| 90 |
+
"""Return the native EXL 16x16 row-major -> tensor-core element order."""
|
| 91 |
+
|
| 92 |
+
permutation = [0] * TILE_VALUES
|
| 93 |
+
for thread in range(32):
|
| 94 |
+
row0 = (thread % 4) * 2
|
| 95 |
+
row1 = row0 + 1
|
| 96 |
+
row2 = row0 + 8
|
| 97 |
+
row3 = row0 + 9
|
| 98 |
+
column0 = thread // 4
|
| 99 |
+
column1 = column0 + 8
|
| 100 |
+
permutation[thread * 8 + 0] = row0 * 16 + column0
|
| 101 |
+
permutation[thread * 8 + 1] = row1 * 16 + column0
|
| 102 |
+
permutation[thread * 8 + 2] = row2 * 16 + column0
|
| 103 |
+
permutation[thread * 8 + 3] = row3 * 16 + column0
|
| 104 |
+
permutation[thread * 8 + 4] = row0 * 16 + column1
|
| 105 |
+
permutation[thread * 8 + 5] = row1 * 16 + column1
|
| 106 |
+
permutation[thread * 8 + 6] = row2 * 16 + column1
|
| 107 |
+
permutation[thread * 8 + 7] = row3 * 16 + column1
|
| 108 |
+
return torch.tensor(permutation, dtype=torch.long, device=device)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def decode_regularized_weight(
|
| 112 |
+
states: torch.Tensor,
|
| 113 |
+
*,
|
| 114 |
+
codebook: str = CODEBOOK_SQG_XOR_CHEB_T12,
|
| 115 |
+
codebook_values: torch.Tensor | None = None,
|
| 116 |
+
bits: int | None = None,
|
| 117 |
+
) -> torch.Tensor:
|
| 118 |
+
"""Decode ``[K/16, N/16, 256]`` states to transformed ``[K, N]``."""
|
| 119 |
+
|
| 120 |
+
if states.ndim != 3 or states.shape[-1] != TILE_VALUES:
|
| 121 |
+
raise ValueError("states must have shape [K/16, N/16, 256]")
|
| 122 |
+
if codebook_values is None:
|
| 123 |
+
decoded = _decode_codebook_states(states, codebook, bits=bits).float()
|
| 124 |
+
else:
|
| 125 |
+
if codebook_values.ndim != 1 or codebook_values.numel() != 1 << 16:
|
| 126 |
+
raise ValueError("custom codebook must contain exactly 65,536 values")
|
| 127 |
+
if not codebook_values.is_floating_point():
|
| 128 |
+
raise TypeError("custom codebook must be floating point")
|
| 129 |
+
if not bool(torch.isfinite(codebook_values).all()):
|
| 130 |
+
raise ValueError("custom codebook must be finite")
|
| 131 |
+
indices = (states.to(dtype=torch.int64) & 0xFFFF).long()
|
| 132 |
+
decoded = codebook_values.to(device=states.device).index_select(
|
| 133 |
+
0, indices.flatten()
|
| 134 |
+
).reshape_as(states).float()
|
| 135 |
+
inverse = torch.argsort(tensor_core_permutation(states.device))
|
| 136 |
+
decoded = decoded.index_select(-1, inverse)
|
| 137 |
+
k_tiles, n_tiles, _ = decoded.shape
|
| 138 |
+
return (
|
| 139 |
+
decoded.reshape(k_tiles, n_tiles, 16, 16)
|
| 140 |
+
.permute(0, 2, 1, 3)
|
| 141 |
+
.reshape(k_tiles * 16, n_tiles * 16)
|
| 142 |
+
.contiguous()
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def decode_qsrt_regularized_weight(
|
| 147 |
+
states: torch.Tensor,
|
| 148 |
+
*,
|
| 149 |
+
rate_axis: str,
|
| 150 |
+
tile_bits: tuple[int, ...],
|
| 151 |
+
codebook: str,
|
| 152 |
+
) -> torch.Tensor:
|
| 153 |
+
"""Decode a K2--K6 tile map to transformed ``[K, N]`` weights.
|
| 154 |
+
|
| 155 |
+
SQG uses a distinct state labelling for every supported K, so the physical
|
| 156 |
+
tile rate is part of the numerical decode contract.
|
| 157 |
+
"""
|
| 158 |
+
|
| 159 |
+
if states.ndim != 3 or states.shape[-1] != TILE_VALUES:
|
| 160 |
+
raise ValueError("states must have shape [K/16, N/16, 256]")
|
| 161 |
+
if rate_axis not in ("k", "n"):
|
| 162 |
+
raise ValueError("rate_axis must be 'k' or 'n'")
|
| 163 |
+
axis = 0 if rate_axis == "k" else 1
|
| 164 |
+
if len(tile_bits) != states.shape[axis]:
|
| 165 |
+
raise ValueError("tile_bits length does not match the selected rate axis")
|
| 166 |
+
if any(
|
| 167 |
+
isinstance(value, bool) or not isinstance(value, int) or value not in range(2, 7)
|
| 168 |
+
for value in tile_bits
|
| 169 |
+
):
|
| 170 |
+
raise ValueError("tile_bits must contain only integer K2 through K6")
|
| 171 |
+
if codebook not in QSRT_CODEBOOKS:
|
| 172 |
+
raise ValueError(f"unsupported QSRT codebook: {codebook}")
|
| 173 |
+
|
| 174 |
+
decoded_tiles = torch.empty_like(states, dtype=torch.float32)
|
| 175 |
+
bits_tensor = torch.tensor(tile_bits, dtype=torch.long, device=states.device)
|
| 176 |
+
for bits in sorted(set(tile_bits)):
|
| 177 |
+
positions = torch.nonzero(bits_tensor == bits).flatten()
|
| 178 |
+
selected = states.index_select(axis, positions)
|
| 179 |
+
values = _decode_codebook_states(
|
| 180 |
+
selected, codebook, bits=bits, rate_axis=rate_axis
|
| 181 |
+
).float()
|
| 182 |
+
decoded_tiles.index_copy_(axis, positions, values)
|
| 183 |
+
|
| 184 |
+
inverse = torch.argsort(tensor_core_permutation(states.device))
|
| 185 |
+
decoded_tiles = decoded_tiles.index_select(-1, inverse)
|
| 186 |
+
k_tiles, n_tiles, _ = decoded_tiles.shape
|
| 187 |
+
return (
|
| 188 |
+
decoded_tiles.reshape(k_tiles, n_tiles, 16, 16)
|
| 189 |
+
.permute(0, 2, 1, 3)
|
| 190 |
+
.reshape(k_tiles * 16, n_tiles * 16)
|
| 191 |
+
.contiguous()
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def normalized_hadamard(
|
| 196 |
+
*,
|
| 197 |
+
device: torch.device | str,
|
| 198 |
+
dtype: torch.dtype = torch.float32,
|
| 199 |
+
size: int = HADAMARD_BLOCK,
|
| 200 |
+
) -> torch.Tensor:
|
| 201 |
+
if size <= 0 or size & (size - 1):
|
| 202 |
+
raise ValueError("Hadamard size must be a positive power of two")
|
| 203 |
+
matrix = torch.ones((1, 1), dtype=dtype, device=device)
|
| 204 |
+
while matrix.shape[0] < size:
|
| 205 |
+
matrix = torch.cat(
|
| 206 |
+
(
|
| 207 |
+
torch.cat((matrix, matrix), dim=1),
|
| 208 |
+
torch.cat((matrix, -matrix), dim=1),
|
| 209 |
+
),
|
| 210 |
+
dim=0,
|
| 211 |
+
)
|
| 212 |
+
return matrix * (1.0 / math.sqrt(size))
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def _blockwise_hadamard_left(weight: torch.Tensor) -> torch.Tensor:
|
| 216 |
+
if weight.shape[0] % HADAMARD_BLOCK:
|
| 217 |
+
raise ValueError("EXL K dimension must be divisible by 128")
|
| 218 |
+
hadamard = normalized_hadamard(device=weight.device, dtype=weight.dtype)
|
| 219 |
+
blocks = weight.reshape(-1, HADAMARD_BLOCK, weight.shape[1])
|
| 220 |
+
return torch.matmul(hadamard, blocks).reshape_as(weight).contiguous()
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def _blockwise_hadamard_right(weight: torch.Tensor) -> torch.Tensor:
|
| 224 |
+
if weight.shape[1] % HADAMARD_BLOCK:
|
| 225 |
+
raise ValueError("EXL N dimension must be divisible by 128")
|
| 226 |
+
hadamard = normalized_hadamard(device=weight.device, dtype=weight.dtype)
|
| 227 |
+
blocks = weight.reshape(weight.shape[0], -1, HADAMARD_BLOCK)
|
| 228 |
+
return torch.matmul(blocks, hadamard).reshape_as(weight).contiguous()
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def decode_exl3_weight(
|
| 232 |
+
states: torch.Tensor,
|
| 233 |
+
suh: torch.Tensor,
|
| 234 |
+
svh: torch.Tensor,
|
| 235 |
+
*,
|
| 236 |
+
codebook: str = CODEBOOK_SQG_XOR_CHEB_T12,
|
| 237 |
+
codebook_values: torch.Tensor | None = None,
|
| 238 |
+
bits: int | None = None,
|
| 239 |
+
) -> torch.Tensor:
|
| 240 |
+
"""Decode stored states/scales to an EXL-oriented ``[K, N]`` weight."""
|
| 241 |
+
|
| 242 |
+
weight = decode_regularized_weight(
|
| 243 |
+
states, codebook=codebook, codebook_values=codebook_values, bits=bits
|
| 244 |
+
)
|
| 245 |
+
if suh.ndim != 1 or suh.numel() != weight.shape[0]:
|
| 246 |
+
raise ValueError("suh length does not match the EXL K dimension")
|
| 247 |
+
if svh.ndim != 1 or svh.numel() != weight.shape[1]:
|
| 248 |
+
raise ValueError("svh length does not match the EXL N dimension")
|
| 249 |
+
weight = _blockwise_hadamard_left(weight)
|
| 250 |
+
weight *= suh.to(device=weight.device, dtype=weight.dtype).unsqueeze(1)
|
| 251 |
+
weight = _blockwise_hadamard_right(weight)
|
| 252 |
+
weight *= svh.to(device=weight.device, dtype=weight.dtype).unsqueeze(0)
|
| 253 |
+
return weight.contiguous()
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def decode_qsrt_weight(
|
| 257 |
+
states: torch.Tensor,
|
| 258 |
+
suh: torch.Tensor,
|
| 259 |
+
svh: torch.Tensor,
|
| 260 |
+
*,
|
| 261 |
+
rate_axis: str,
|
| 262 |
+
tile_bits: tuple[int, ...],
|
| 263 |
+
codebook: str = CODEBOOK_SQG_XOR_CHEB_T12,
|
| 264 |
+
) -> torch.Tensor:
|
| 265 |
+
"""Decode stored QSRT states and scales to an EXL ``[K, N]`` weight."""
|
| 266 |
+
|
| 267 |
+
weight = decode_qsrt_regularized_weight(
|
| 268 |
+
states,
|
| 269 |
+
rate_axis=rate_axis,
|
| 270 |
+
tile_bits=tile_bits,
|
| 271 |
+
codebook=codebook,
|
| 272 |
+
)
|
| 273 |
+
if suh.ndim != 1 or suh.numel() != weight.shape[0]:
|
| 274 |
+
raise ValueError("suh length does not match the EXL K dimension")
|
| 275 |
+
if svh.ndim != 1 or svh.numel() != weight.shape[1]:
|
| 276 |
+
raise ValueError("svh length does not match the EXL N dimension")
|
| 277 |
+
weight = _blockwise_hadamard_left(weight)
|
| 278 |
+
weight *= suh.to(device=weight.device, dtype=weight.dtype).unsqueeze(1)
|
| 279 |
+
weight = _blockwise_hadamard_right(weight)
|
| 280 |
+
weight *= svh.to(device=weight.device, dtype=weight.dtype).unsqueeze(0)
|
| 281 |
+
return weight.contiguous()
|
reproduction/closure/patches/kquant/changed-files/kquant/qsrt.py
ADDED
|
@@ -0,0 +1,1141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Reference codec geometry for Kimi-K3 QSRT candidates.
|
| 2 |
+
|
| 3 |
+
Candidate payloads describe complete experts and contain no tensor-parallel
|
| 4 |
+
ownership. TP sharding is a property of the atom-major storage/runtime layer.
|
| 5 |
+
The ordinary expert weights use ``[out_features, in_features]`` orientation,
|
| 6 |
+
while the EXL encoder consumes their transpose, ``[K, N]``. Consequently the
|
| 7 |
+
shared 3072-channel intermediate axis is EXL ``N`` for ``w1``/``w3`` and EXL
|
| 8 |
+
``K`` for ``w2``.
|
| 9 |
+
|
| 10 |
+
The reference packer preserves the native EXL bit ordering for each 16x16
|
| 11 |
+
trellis tile. Its inverse reconstructs both the stored K-bit edge symbols and
|
| 12 |
+
the full 16-bit cyclic trellis states consumed by the SQG decoder.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
from collections.abc import Sequence
|
| 18 |
+
from dataclasses import dataclass
|
| 19 |
+
from typing import Literal
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
|
| 23 |
+
from kquant.exl3_reference import (
|
| 24 |
+
CODEBOOK_SQG_XOR_CHEB_T12,
|
| 25 |
+
CODEBOOK_SQG_CHEB_NORMAL_E4M3,
|
| 26 |
+
CODEBOOK_SQG_CHEB,
|
| 27 |
+
CODEBOOK_SQG_NORMAL_E4M3,
|
| 28 |
+
QSRT_CODEBOOKS,
|
| 29 |
+
decode_qsrt_weight,
|
| 30 |
+
reconstruct_trellis_states,
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
EXPERTS_PER_LAYER = 896
|
| 35 |
+
INTERMEDIATE_CHANNELS = 3072
|
| 36 |
+
LATENT_CHANNELS = 3584
|
| 37 |
+
CONTEXT_GROUP_CHANNELS = 4
|
| 38 |
+
TILE_CHANNELS = 16
|
| 39 |
+
TILE_VALUES = TILE_CHANNELS * TILE_CHANNELS
|
| 40 |
+
RECORD_CHANNELS = 128
|
| 41 |
+
PAIR_CHANNELS = 2 * RECORD_CHANNELS
|
| 42 |
+
RECORDS_PER_EXPERT = INTERMEDIATE_CHANNELS // RECORD_CHANNELS
|
| 43 |
+
PAIRS_PER_EXPERT = RECORDS_PER_EXPERT // 2
|
| 44 |
+
TILES_PER_RECORD_AXIS = RECORD_CHANNELS // TILE_CHANNELS
|
| 45 |
+
SCHEMA = "kquant_kimi_k3_qsrt_candidate_v1"
|
| 46 |
+
LOGICAL_CANDIDATE_SCHEMAS = (SCHEMA,)
|
| 47 |
+
STORAGE_ALIGNMENT = 4096
|
| 48 |
+
PAIR_ROTATION_EXPERT_MULTIPLIER = 5
|
| 49 |
+
|
| 50 |
+
# The phase-1 table uses one byte per expert. Compressed entries contain
|
| 51 |
+
# two four-bit transfer counts: high nibble r13 for fused w1/w3, low nibble r2
|
| 52 |
+
# for w2. Each value is in 0..2. 0xff denotes the exact X4T tier. The nibbles
|
| 53 |
+
# remain deliberately roomy so the fixed table is cheap to inspect and decode.
|
| 54 |
+
FORMAT_BITS = 8
|
| 55 |
+
FORMAT_X4T = 0xFF
|
| 56 |
+
FORMAT_NIBBLE_MASK = 0x0F
|
| 57 |
+
|
| 58 |
+
LAYER_HEADER_BYTES = STORAGE_ALIGNMENT
|
| 59 |
+
FORMAT_TABLE_BYTES = (EXPERTS_PER_LAYER * FORMAT_BITS + 7) // 8
|
| 60 |
+
FORMAT_SECTION_BYTES = STORAGE_ALIGNMENT
|
| 61 |
+
HOT_FORMAT_BYTES = EXPERTS_PER_LAYER
|
| 62 |
+
HOT_SLOT_BYTES = EXPERTS_PER_LAYER * torch.int16.itemsize
|
| 63 |
+
HOT_METADATA_BYTES = HOT_FORMAT_BYTES + HOT_SLOT_BYTES
|
| 64 |
+
|
| 65 |
+
# Shared hidden-side vectors are w1.suh, w3.suh, and w2.svh. The three
|
| 66 |
+
# corresponding intermediate-side vectors remain expert-local. Their
|
| 67 |
+
# atom-major storage is defined independently from a serving TP size.
|
| 68 |
+
SHARED_SCALE_VECTORS = 3
|
| 69 |
+
LOCAL_SCALE_VECTORS = 3
|
| 70 |
+
SHARED_SCALE_ORDER = ("w1.suh", "w3.suh", "w2.svh")
|
| 71 |
+
LOCAL_SCALE_ORDER = ("w1.svh", "w3.svh", "w2.suh")
|
| 72 |
+
SCALE_BYTES = torch.float16.itemsize
|
| 73 |
+
SHARED_SCALE_BYTES = SHARED_SCALE_VECTORS * LATENT_CHANNELS * SCALE_BYTES
|
| 74 |
+
SHARED_SCALE_SECTION_BYTES = 6 * STORAGE_ALIGNMENT
|
| 75 |
+
|
| 76 |
+
# P24 and P33 both carry a six-bit sum over 256 intermediate channels.
|
| 77 |
+
PAIR_BYTES = PAIR_CHANNELS * LATENT_CHANNELS * 3 // 8
|
| 78 |
+
MATRIX_TRELLIS_BYTES = PAIRS_PER_EXPERT * PAIR_BYTES
|
| 79 |
+
EXPERT_TRELLIS_BYTES = 3 * MATRIX_TRELLIS_BYTES
|
| 80 |
+
RateAxis = Literal["k", "n"]
|
| 81 |
+
PairKind = Literal["P24", "P33"]
|
| 82 |
+
|
| 83 |
+
MATRIX_RATE_AXIS: dict[str, RateAxis] = {
|
| 84 |
+
"w1": "n",
|
| 85 |
+
"w3": "n",
|
| 86 |
+
"w2": "k",
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def pair_rotation(layer: int, expert: int) -> int:
|
| 91 |
+
"""Cyclic logical-pair placement used to balance physical atom slots."""
|
| 92 |
+
|
| 93 |
+
if (
|
| 94 |
+
isinstance(layer, bool)
|
| 95 |
+
or not isinstance(layer, int)
|
| 96 |
+
or layer not in range(1, 93)
|
| 97 |
+
):
|
| 98 |
+
raise ValueError("K3 MoE layer must be in 1..92")
|
| 99 |
+
if (
|
| 100 |
+
isinstance(expert, bool)
|
| 101 |
+
or not isinstance(expert, int)
|
| 102 |
+
or not 0 <= expert < EXPERTS_PER_LAYER
|
| 103 |
+
):
|
| 104 |
+
raise ValueError(f"expert must be in 0..{EXPERTS_PER_LAYER - 1}")
|
| 105 |
+
return (PAIR_ROTATION_EXPERT_MULTIPLIER * expert + layer) % PAIRS_PER_EXPERT
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def logical_pair_for_slot(layer: int, expert: int, pair_slot: int) -> int:
|
| 109 |
+
"""Logical candidate pair stored in one physical pair slot."""
|
| 110 |
+
|
| 111 |
+
if (
|
| 112 |
+
isinstance(pair_slot, bool)
|
| 113 |
+
or not isinstance(pair_slot, int)
|
| 114 |
+
or not 0 <= pair_slot < PAIRS_PER_EXPERT
|
| 115 |
+
):
|
| 116 |
+
raise ValueError(f"pair_slot must be in 0..{PAIRS_PER_EXPERT - 1}")
|
| 117 |
+
return (pair_slot - pair_rotation(layer, expert)) % PAIRS_PER_EXPERT
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def physical_pair_slot(layer: int, expert: int, logical_pair: int) -> int:
|
| 121 |
+
"""Physical slot containing one logical candidate pair."""
|
| 122 |
+
|
| 123 |
+
if (
|
| 124 |
+
isinstance(logical_pair, bool)
|
| 125 |
+
or not isinstance(logical_pair, int)
|
| 126 |
+
or not 0 <= logical_pair < PAIRS_PER_EXPERT
|
| 127 |
+
):
|
| 128 |
+
raise ValueError(f"logical_pair must be in 0..{PAIRS_PER_EXPERT - 1}")
|
| 129 |
+
return (logical_pair + pair_rotation(layer, expert)) % PAIRS_PER_EXPERT
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
@dataclass(frozen=True)
|
| 133 |
+
class ModeSpec:
|
| 134 |
+
"""One rate-transfer schedule, ordered from low to high importance.
|
| 135 |
+
|
| 136 |
+
``context_bits`` contains one entry per final 128-channel record.
|
| 137 |
+
``mode_id`` is the number of K2/K4 record exchanges.
|
| 138 |
+
"""
|
| 139 |
+
|
| 140 |
+
mode_id: int
|
| 141 |
+
name: str
|
| 142 |
+
context_bits: tuple[int, ...]
|
| 143 |
+
def __post_init__(self) -> None:
|
| 144 |
+
if isinstance(self.mode_id, bool) or not isinstance(self.mode_id, int):
|
| 145 |
+
raise TypeError("mode_id must be an integer")
|
| 146 |
+
if not 0 <= self.mode_id <= 2:
|
| 147 |
+
raise ValueError("QSRT mode_id must be R0, R1, or R2")
|
| 148 |
+
if not self.name:
|
| 149 |
+
raise ValueError("mode name must not be empty")
|
| 150 |
+
if not self.context_bits or len(self.context_bits) % 2:
|
| 151 |
+
raise ValueError("a mode must contain an even, nonzero context count")
|
| 152 |
+
if any(bits not in (2, 3, 4) for bits in self.context_bits):
|
| 153 |
+
raise ValueError("Kimi-K3 QSRT supports only K2, K3, and K4")
|
| 154 |
+
if sum(self.context_bits) != 3 * len(self.context_bits):
|
| 155 |
+
raise ValueError("a mode must average exactly three trellis bits")
|
| 156 |
+
if INTERMEDIATE_CHANNELS % len(self.context_bits):
|
| 157 |
+
raise ValueError("contexts must divide the 3072-channel axis")
|
| 158 |
+
context_channels = INTERMEDIATE_CHANNELS // len(self.context_bits)
|
| 159 |
+
if context_channels % RECORD_CHANNELS:
|
| 160 |
+
raise ValueError("each context must contain whole 128-channel records")
|
| 161 |
+
if tuple(sorted(self.context_bits)) != self.context_bits:
|
| 162 |
+
raise ValueError("context rates must be monotone from K2 through K4")
|
| 163 |
+
records_per_context = RECORDS_PER_EXPERT // len(self.context_bits)
|
| 164 |
+
k2_records = self.context_bits.count(2) * records_per_context
|
| 165 |
+
k4_records = self.context_bits.count(4) * records_per_context
|
| 166 |
+
if k2_records != k4_records:
|
| 167 |
+
raise ValueError("K2 and K4 record counts must match")
|
| 168 |
+
if self.mode_id != k2_records:
|
| 169 |
+
raise ValueError(
|
| 170 |
+
f"mode_id must equal the rate-transfer count {k2_records}"
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
@property
|
| 174 |
+
def context_count(self) -> int:
|
| 175 |
+
return len(self.context_bits)
|
| 176 |
+
|
| 177 |
+
@property
|
| 178 |
+
def records_per_context(self) -> int:
|
| 179 |
+
return RECORDS_PER_EXPERT // self.context_count
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def _rate_transfer_mode(r: int) -> ModeSpec:
|
| 183 |
+
return ModeSpec(
|
| 184 |
+
r,
|
| 185 |
+
f"R{r}",
|
| 186 |
+
(2,) * r + (3,) * (RECORDS_PER_EXPERT - 2 * r) + (4,) * r,
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
RATE_TRANSFER_MODES = tuple(_rate_transfer_mode(r) for r in range(3))
|
| 191 |
+
R0 = RATE_TRANSFER_MODES[0]
|
| 192 |
+
R1 = RATE_TRANSFER_MODES[1]
|
| 193 |
+
R2 = RATE_TRANSFER_MODES[2]
|
| 194 |
+
REPRESENTATIVE_MODE_CANDIDATES = RATE_TRANSFER_MODES
|
| 195 |
+
EXPERIMENTAL_MODE_CANDIDATES = RATE_TRANSFER_MODES
|
| 196 |
+
|
| 197 |
+
# Frozen phase-1 encoder policy. The payload schema still parses the complete
|
| 198 |
+
# ladder for controlled studies, but the initial all-expert run searches R0,
|
| 199 |
+
# R1, and R2 independently for the fused w1/w3 family and for w2. The
|
| 200 |
+
# covariance alphas are offline encoder controls and consume no format bits.
|
| 201 |
+
PHASE1_MODE_IDS = (0, 1, 2)
|
| 202 |
+
PHASE1_MODE_CANDIDATES = tuple(RATE_TRANSFER_MODES[r] for r in PHASE1_MODE_IDS)
|
| 203 |
+
PHASE1_SHARED_RATE_MODE = False
|
| 204 |
+
PHASE1_H13_EXPERT_LOCAL_ALPHA = 0.0
|
| 205 |
+
# Maximum local weight for adaptive, scaled-identity OAS H2 shrinkage. The
|
| 206 |
+
# actual alpha is support-dependent and can be substantially smaller.
|
| 207 |
+
PHASE1_H2_EXPERT_LOCAL_ALPHA = 0.75
|
| 208 |
+
PHASE1_H2_LOCAL_BASIS = "decoded_candidate_post_situ"
|
| 209 |
+
PHASE1_H2_SHRINKAGE_POLICY = "weighted_oas_scaled_identity"
|
| 210 |
+
_MODES_BY_NAME = {mode.name: mode for mode in RATE_TRANSFER_MODES}
|
| 211 |
+
_MODES_BY_ID = {mode.mode_id: mode for mode in RATE_TRANSFER_MODES}
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def mode_from_context_bits(context_bits: Sequence[int]) -> ModeSpec:
|
| 215 |
+
"""Resolve any legal equal-context ladder allocation to canonical ``R_r``."""
|
| 216 |
+
|
| 217 |
+
bits = tuple(context_bits)
|
| 218 |
+
if not bits or RECORDS_PER_EXPERT % len(bits):
|
| 219 |
+
raise ValueError("allocation contexts must divide the 24 records")
|
| 220 |
+
records_per_context = RECORDS_PER_EXPERT // len(bits)
|
| 221 |
+
r = bits.count(2) * records_per_context
|
| 222 |
+
# ModeSpec performs monotonicity, rate, and equal-donor validation.
|
| 223 |
+
ModeSpec(r, "candidate", bits)
|
| 224 |
+
return RATE_TRANSFER_MODES[r]
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
@dataclass(frozen=True)
|
| 228 |
+
class ExpertFormatSpec:
|
| 229 |
+
"""Provisional per-expert tier and matrix-family rate schedules."""
|
| 230 |
+
|
| 231 |
+
r13: int | None
|
| 232 |
+
r2: int | None
|
| 233 |
+
|
| 234 |
+
def __post_init__(self) -> None:
|
| 235 |
+
if (self.r13 is None) != (self.r2 is None):
|
| 236 |
+
raise ValueError("r13 and r2 must both be absent or both be present")
|
| 237 |
+
for name, value in (("r13", self.r13), ("r2", self.r2)):
|
| 238 |
+
if value is None:
|
| 239 |
+
continue
|
| 240 |
+
if isinstance(value, bool) or not isinstance(value, int):
|
| 241 |
+
raise TypeError(f"{name} must be an integer")
|
| 242 |
+
if not 0 <= value <= 2:
|
| 243 |
+
raise ValueError(f"{name} must be R0, R1, or R2")
|
| 244 |
+
|
| 245 |
+
@classmethod
|
| 246 |
+
def x4t(cls) -> "ExpertFormatSpec":
|
| 247 |
+
return cls(None, None)
|
| 248 |
+
|
| 249 |
+
@classmethod
|
| 250 |
+
def compressed(cls, r13: int, r2: int | None = None) -> "ExpertFormatSpec":
|
| 251 |
+
return cls(r13, r13 if r2 is None else r2)
|
| 252 |
+
|
| 253 |
+
@property
|
| 254 |
+
def is_x4t(self) -> bool:
|
| 255 |
+
return self.r13 is None
|
| 256 |
+
|
| 257 |
+
@property
|
| 258 |
+
def name(self) -> str:
|
| 259 |
+
if self.is_x4t:
|
| 260 |
+
return "X4T"
|
| 261 |
+
assert self.r13 is not None and self.r2 is not None
|
| 262 |
+
return f"R{self.r13}" if self.r13 == self.r2 else f"R{self.r13}/R{self.r2}"
|
| 263 |
+
|
| 264 |
+
@property
|
| 265 |
+
def code(self) -> int:
|
| 266 |
+
if self.is_x4t:
|
| 267 |
+
return FORMAT_X4T
|
| 268 |
+
assert self.r13 is not None and self.r2 is not None
|
| 269 |
+
return (self.r13 << 4) | self.r2
|
| 270 |
+
|
| 271 |
+
@classmethod
|
| 272 |
+
def from_code(cls, code: int) -> "ExpertFormatSpec":
|
| 273 |
+
if code == FORMAT_X4T:
|
| 274 |
+
return cls.x4t()
|
| 275 |
+
r13 = code >> 4
|
| 276 |
+
r2 = code & FORMAT_NIBBLE_MASK
|
| 277 |
+
if r13 > 2 or r2 > 2:
|
| 278 |
+
raise ValueError(f"format table contains invalid rate code 0x{code:02x}")
|
| 279 |
+
return cls.compressed(r13, r2)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
ExpertFormatLike = str | ModeSpec | ExpertFormatSpec | tuple[int, int]
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def align_up(value: int, alignment: int = STORAGE_ALIGNMENT) -> int:
|
| 286 |
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
| 287 |
+
raise ValueError("value must be a non-negative integer")
|
| 288 |
+
if (
|
| 289 |
+
isinstance(alignment, bool)
|
| 290 |
+
or not isinstance(alignment, int)
|
| 291 |
+
or alignment <= 0
|
| 292 |
+
or alignment & (alignment - 1)
|
| 293 |
+
):
|
| 294 |
+
raise ValueError("alignment must be a positive power of two")
|
| 295 |
+
return (value + alignment - 1) & -alignment
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def resolve_expert_format(value: ExpertFormatLike) -> ExpertFormatSpec:
|
| 299 |
+
if isinstance(value, ExpertFormatSpec):
|
| 300 |
+
return value
|
| 301 |
+
if isinstance(value, ModeSpec):
|
| 302 |
+
return ExpertFormatSpec.compressed(value.mode_id)
|
| 303 |
+
if isinstance(value, tuple):
|
| 304 |
+
if len(value) != 2:
|
| 305 |
+
raise ValueError("expert format tuple must be (r13, r2)")
|
| 306 |
+
return ExpertFormatSpec.compressed(value[0], value[1])
|
| 307 |
+
if not isinstance(value, str):
|
| 308 |
+
raise TypeError(
|
| 309 |
+
"expert format must be a name, ModeSpec, ExpertFormatSpec, or pair"
|
| 310 |
+
)
|
| 311 |
+
normalized = value.upper()
|
| 312 |
+
if normalized == "X4T":
|
| 313 |
+
return ExpertFormatSpec.x4t()
|
| 314 |
+
if "/" in normalized:
|
| 315 |
+
left, right = normalized.split("/", 1)
|
| 316 |
+
return ExpertFormatSpec.compressed(
|
| 317 |
+
resolve_mode(left).mode_id, resolve_mode(right).mode_id
|
| 318 |
+
)
|
| 319 |
+
return ExpertFormatSpec.compressed(resolve_mode(normalized).mode_id)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def pack_qsrt_format_table(formats: Sequence[ExpertFormatLike]) -> torch.Tensor:
|
| 323 |
+
"""Pack 896 one-byte expert format codes."""
|
| 324 |
+
|
| 325 |
+
if len(formats) != EXPERTS_PER_LAYER:
|
| 326 |
+
raise ValueError(f"format table must contain {EXPERTS_PER_LAYER} experts")
|
| 327 |
+
return torch.tensor(
|
| 328 |
+
[resolve_expert_format(value).code for value in formats],
|
| 329 |
+
dtype=torch.uint8,
|
| 330 |
+
).contiguous()
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def unpack_qsrt_format_table(payload: torch.Tensor) -> tuple[str, ...]:
|
| 334 |
+
"""Decode the format table and reject invalid rate nibbles."""
|
| 335 |
+
|
| 336 |
+
if payload.dtype != torch.uint8:
|
| 337 |
+
raise TypeError("format table must use torch.uint8")
|
| 338 |
+
if payload.ndim != 1 or payload.numel() != FORMAT_TABLE_BYTES:
|
| 339 |
+
raise ValueError(
|
| 340 |
+
f"format table must contain exactly {FORMAT_TABLE_BYTES} bytes"
|
| 341 |
+
)
|
| 342 |
+
return tuple(
|
| 343 |
+
ExpertFormatSpec.from_code(code).name for code in payload.cpu().tolist()
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def pack_qsrt_format_section(formats: Sequence[ExpertFormatLike]) -> torch.Tensor:
|
| 348 |
+
"""Build the canonical 4-KiB on-disk format-table section."""
|
| 349 |
+
|
| 350 |
+
table = pack_qsrt_format_table(formats)
|
| 351 |
+
section = torch.zeros(FORMAT_SECTION_BYTES, dtype=torch.uint8)
|
| 352 |
+
section[:FORMAT_TABLE_BYTES].copy_(table)
|
| 353 |
+
return section
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
def unpack_qsrt_format_section(payload: torch.Tensor) -> tuple[str, ...]:
|
| 357 |
+
if payload.dtype != torch.uint8:
|
| 358 |
+
raise TypeError("format section must use torch.uint8")
|
| 359 |
+
if payload.ndim != 1 or payload.numel() != FORMAT_SECTION_BYTES:
|
| 360 |
+
raise ValueError(
|
| 361 |
+
f"format section must contain exactly {FORMAT_SECTION_BYTES} bytes"
|
| 362 |
+
)
|
| 363 |
+
if bool(torch.any(payload[FORMAT_TABLE_BYTES:] != 0)):
|
| 364 |
+
raise ValueError("format section has nonzero alignment padding")
|
| 365 |
+
return unpack_qsrt_format_table(payload[:FORMAT_TABLE_BYTES])
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
def expand_qsrt_hot_metadata(
|
| 369 |
+
formats: Sequence[ExpertFormatLike],
|
| 370 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 371 |
+
"""Expand disk codes to uint8 schedules and uint16 tier-local slots."""
|
| 372 |
+
|
| 373 |
+
codes = pack_qsrt_format_table(formats)
|
| 374 |
+
# Validate before deriving slots, including codes supplied through strings.
|
| 375 |
+
unpack_qsrt_format_table(codes)
|
| 376 |
+
slots = torch.empty(EXPERTS_PER_LAYER, dtype=torch.int16)
|
| 377 |
+
compressed_slot = 0
|
| 378 |
+
kept_slot = 0
|
| 379 |
+
for expert, code in enumerate(codes.tolist()):
|
| 380 |
+
if code == FORMAT_X4T:
|
| 381 |
+
slots[expert] = kept_slot
|
| 382 |
+
kept_slot += 1
|
| 383 |
+
else:
|
| 384 |
+
slots[expert] = compressed_slot
|
| 385 |
+
compressed_slot += 1
|
| 386 |
+
return codes, slots
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
def _validate_scale_vector(
|
| 390 |
+
scale: torch.Tensor, length: int, name: str, device: torch.device | None = None
|
| 391 |
+
) -> None:
|
| 392 |
+
if scale.dtype != torch.float16:
|
| 393 |
+
raise TypeError(f"{name} must use torch.float16")
|
| 394 |
+
if scale.ndim != 1 or scale.numel() != length:
|
| 395 |
+
raise ValueError(f"{name} must contain exactly {length} values")
|
| 396 |
+
if not scale.is_contiguous():
|
| 397 |
+
raise ValueError(f"{name} must be contiguous")
|
| 398 |
+
if device is not None and scale.device != device:
|
| 399 |
+
raise ValueError(f"{name} must be on {device}")
|
| 400 |
+
if not bool(torch.all(torch.isfinite(scale))):
|
| 401 |
+
raise ValueError(f"{name} contains non-finite values")
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
def pack_qsrt_shared_scale_section(
|
| 405 |
+
w1_suh: torch.Tensor, w3_suh: torch.Tensor, w2_svh: torch.Tensor
|
| 406 |
+
) -> torch.Tensor:
|
| 407 |
+
"""Pack the three layer-shared hidden-side FP16 vectors and zero padding."""
|
| 408 |
+
|
| 409 |
+
scales = (w1_suh, w3_suh, w2_svh)
|
| 410 |
+
device = scales[0].device
|
| 411 |
+
for name, scale in zip(SHARED_SCALE_ORDER, scales, strict=True):
|
| 412 |
+
_validate_scale_vector(scale, LATENT_CHANNELS, name, device)
|
| 413 |
+
section = torch.zeros(
|
| 414 |
+
SHARED_SCALE_SECTION_BYTES, dtype=torch.uint8, device=device
|
| 415 |
+
)
|
| 416 |
+
cursor = 0
|
| 417 |
+
for scale in scales:
|
| 418 |
+
raw = scale.view(torch.uint8)
|
| 419 |
+
section[cursor : cursor + raw.numel()].copy_(raw)
|
| 420 |
+
cursor += raw.numel()
|
| 421 |
+
if cursor != SHARED_SCALE_BYTES:
|
| 422 |
+
raise AssertionError("shared scale byte accounting drifted")
|
| 423 |
+
return section
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
def unpack_qsrt_shared_scale_section(
|
| 427 |
+
payload: torch.Tensor,
|
| 428 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 429 |
+
if payload.dtype != torch.uint8:
|
| 430 |
+
raise TypeError("shared scale section must use torch.uint8")
|
| 431 |
+
if payload.ndim != 1 or payload.numel() != SHARED_SCALE_SECTION_BYTES:
|
| 432 |
+
raise ValueError(
|
| 433 |
+
"shared scale section must contain exactly "
|
| 434 |
+
f"{SHARED_SCALE_SECTION_BYTES} bytes"
|
| 435 |
+
)
|
| 436 |
+
if bool(torch.any(payload[SHARED_SCALE_BYTES:] != 0)):
|
| 437 |
+
raise ValueError("shared scale section has nonzero alignment padding")
|
| 438 |
+
values = []
|
| 439 |
+
cursor = 0
|
| 440 |
+
vector_bytes = LATENT_CHANNELS * SCALE_BYTES
|
| 441 |
+
for name in SHARED_SCALE_ORDER:
|
| 442 |
+
vector = (
|
| 443 |
+
payload[cursor : cursor + vector_bytes]
|
| 444 |
+
.clone()
|
| 445 |
+
.view(torch.float16)
|
| 446 |
+
.contiguous()
|
| 447 |
+
)
|
| 448 |
+
_validate_scale_vector(vector, LATENT_CHANNELS, name)
|
| 449 |
+
values.append(vector)
|
| 450 |
+
cursor += vector_bytes
|
| 451 |
+
return tuple(values) # type: ignore[return-value]
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
def resolve_mode(mode: ModeSpec | str | int) -> ModeSpec:
|
| 455 |
+
if isinstance(mode, ModeSpec):
|
| 456 |
+
return mode
|
| 457 |
+
if isinstance(mode, str):
|
| 458 |
+
try:
|
| 459 |
+
return _MODES_BY_NAME[mode.upper()]
|
| 460 |
+
except KeyError as exc:
|
| 461 |
+
raise ValueError(f"unknown QSRT mode: {mode}") from exc
|
| 462 |
+
if isinstance(mode, int) and not isinstance(mode, bool):
|
| 463 |
+
try:
|
| 464 |
+
return _MODES_BY_ID[mode]
|
| 465 |
+
except KeyError as exc:
|
| 466 |
+
raise ValueError(f"unknown QSRT mode ID: {mode}") from exc
|
| 467 |
+
raise TypeError("mode must be a ModeSpec, name, or integer ID")
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
def matrix_rate_axis(matrix: str) -> RateAxis:
|
| 471 |
+
try:
|
| 472 |
+
return MATRIX_RATE_AXIS[matrix]
|
| 473 |
+
except KeyError as exc:
|
| 474 |
+
raise ValueError(f"unknown expert matrix: {matrix}") from exc
|
| 475 |
+
|
| 476 |
+
|
| 477 |
+
def record_contexts(mode: ModeSpec | str | int) -> tuple[int, ...]:
|
| 478 |
+
"""Return physical record contexts, paired low/high then moving inward."""
|
| 479 |
+
|
| 480 |
+
spec = resolve_mode(mode)
|
| 481 |
+
contexts: list[int] = []
|
| 482 |
+
for low_context in range(spec.context_count // 2):
|
| 483 |
+
high_context = spec.context_count - 1 - low_context
|
| 484 |
+
for _ in range(spec.records_per_context):
|
| 485 |
+
contexts.extend((low_context, high_context))
|
| 486 |
+
if len(contexts) != RECORDS_PER_EXPERT:
|
| 487 |
+
raise AssertionError("mode construction did not cover 24 records")
|
| 488 |
+
return tuple(contexts)
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
def record_bits(mode: ModeSpec | str | int) -> tuple[int, ...]:
|
| 492 |
+
spec = resolve_mode(mode)
|
| 493 |
+
return tuple(spec.context_bits[context] for context in record_contexts(spec))
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
def pair_bits(mode: ModeSpec | str | int) -> tuple[tuple[int, int], ...]:
|
| 497 |
+
records = record_bits(mode)
|
| 498 |
+
pairs = tuple(zip(records[0::2], records[1::2], strict=True))
|
| 499 |
+
if len(pairs) != PAIRS_PER_EXPERT or any(sum(pair) != 6 for pair in pairs):
|
| 500 |
+
raise ValueError("every QSRT record pair must have a six-bit rate sum")
|
| 501 |
+
return pairs
|
| 502 |
+
|
| 503 |
+
|
| 504 |
+
def pair_kinds(mode: ModeSpec | str | int) -> tuple[PairKind, ...]:
|
| 505 |
+
kinds: list[PairKind] = []
|
| 506 |
+
for pair in pair_bits(mode):
|
| 507 |
+
if pair == (2, 4):
|
| 508 |
+
kinds.append("P24")
|
| 509 |
+
elif pair == (3, 3):
|
| 510 |
+
kinds.append("P33")
|
| 511 |
+
else:
|
| 512 |
+
raise ValueError(f"unsupported QSRT pair schedule: {pair}")
|
| 513 |
+
return tuple(kinds)
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
def _validate_block_contexts(
|
| 517 |
+
block_contexts: torch.Tensor, mode: ModeSpec
|
| 518 |
+
) -> None:
|
| 519 |
+
expected_groups = INTERMEDIATE_CHANNELS // CONTEXT_GROUP_CHANNELS
|
| 520 |
+
if block_contexts.ndim != 1 or block_contexts.numel() != expected_groups:
|
| 521 |
+
raise ValueError(
|
| 522 |
+
f"block_contexts must contain {expected_groups} four-channel groups"
|
| 523 |
+
)
|
| 524 |
+
if block_contexts.dtype == torch.bool or block_contexts.is_floating_point():
|
| 525 |
+
raise TypeError("block_contexts must use an integer dtype")
|
| 526 |
+
if block_contexts.numel() == 0:
|
| 527 |
+
raise ValueError("block_contexts must not be empty")
|
| 528 |
+
if int(block_contexts.min()) < 0 or int(block_contexts.max()) >= mode.context_count:
|
| 529 |
+
raise ValueError("block_contexts contains an out-of-range context ID")
|
| 530 |
+
counts = torch.bincount(
|
| 531 |
+
block_contexts.to(dtype=torch.long), minlength=mode.context_count
|
| 532 |
+
)
|
| 533 |
+
expected = expected_groups // mode.context_count
|
| 534 |
+
if counts.tolist() != [expected] * mode.context_count:
|
| 535 |
+
raise ValueError("every context must have equal population")
|
| 536 |
+
|
| 537 |
+
|
| 538 |
+
def importance_group_order(
|
| 539 |
+
block_contexts: torch.Tensor, mode: ModeSpec | str | int
|
| 540 |
+
) -> torch.Tensor:
|
| 541 |
+
"""Return stable low-to-high context order for the LDLQ recursion."""
|
| 542 |
+
|
| 543 |
+
spec = resolve_mode(mode)
|
| 544 |
+
_validate_block_contexts(block_contexts, spec)
|
| 545 |
+
return torch.argsort(block_contexts, stable=True)
|
| 546 |
+
|
| 547 |
+
|
| 548 |
+
def storage_group_order(
|
| 549 |
+
block_contexts: torch.Tensor, mode: ModeSpec | str | int
|
| 550 |
+
) -> torch.Tensor:
|
| 551 |
+
"""Return the physical group order forming canonical P24/P33 pairs."""
|
| 552 |
+
|
| 553 |
+
spec = resolve_mode(mode)
|
| 554 |
+
_validate_block_contexts(block_contexts, spec)
|
| 555 |
+
groups_per_record = RECORD_CHANNELS // CONTEXT_GROUP_CHANNELS
|
| 556 |
+
by_context = []
|
| 557 |
+
for context in range(spec.context_count):
|
| 558 |
+
indices = torch.nonzero(block_contexts == context).flatten()
|
| 559 |
+
by_context.append(indices.reshape(-1, groups_per_record))
|
| 560 |
+
|
| 561 |
+
chunks = []
|
| 562 |
+
for low_context in range(spec.context_count // 2):
|
| 563 |
+
high_context = spec.context_count - 1 - low_context
|
| 564 |
+
low_records = by_context[low_context]
|
| 565 |
+
high_records = by_context[high_context]
|
| 566 |
+
if low_records.shape != high_records.shape:
|
| 567 |
+
raise ValueError("paired contexts have different record populations")
|
| 568 |
+
for record in range(low_records.shape[0]):
|
| 569 |
+
chunks.extend((low_records[record], high_records[record]))
|
| 570 |
+
return torch.cat(chunks)
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
def rate_transfer_record_order(
|
| 574 |
+
donor_records: Sequence[int],
|
| 575 |
+
recipient_records: Sequence[int],
|
| 576 |
+
*,
|
| 577 |
+
middle_records: Sequence[int] | None = None,
|
| 578 |
+
) -> tuple[int, ...]:
|
| 579 |
+
"""Build a low-to-high record order for an arbitrary equal-rate exchange.
|
| 580 |
+
|
| 581 |
+
Record IDs refer to an existing 24-record partition, usually the
|
| 582 |
+
activation-ranked partition produced by calibration. Donors occupy the
|
| 583 |
+
K2 end of an ``R_r`` schedule and recipients occupy the K4 end. The
|
| 584 |
+
recipient suffix is reversed so ``donor_records[i]`` and
|
| 585 |
+
``recipient_records[i]`` become one physical P24 pair after the canonical
|
| 586 |
+
balanced-atom repack.
|
| 587 |
+
|
| 588 |
+
This order can be baked into the common w1/w3-row and w2-column
|
| 589 |
+
permutation. Therefore an arbitrary *shared* donor/recipient map does
|
| 590 |
+
not require a serving-time per-record map or a different payload format;
|
| 591 |
+
only the offline selector changes.
|
| 592 |
+
"""
|
| 593 |
+
|
| 594 |
+
donors = tuple(donor_records)
|
| 595 |
+
recipients = tuple(recipient_records)
|
| 596 |
+
if len(donors) != len(recipients):
|
| 597 |
+
raise ValueError("donor and recipient record counts must match")
|
| 598 |
+
if len(donors) > PAIRS_PER_EXPERT:
|
| 599 |
+
raise ValueError(f"at most {PAIRS_PER_EXPERT} rate exchanges are allowed")
|
| 600 |
+
|
| 601 |
+
selected = donors + recipients
|
| 602 |
+
if any(
|
| 603 |
+
isinstance(record, bool)
|
| 604 |
+
or not isinstance(record, int)
|
| 605 |
+
or not 0 <= record < RECORDS_PER_EXPERT
|
| 606 |
+
for record in selected
|
| 607 |
+
):
|
| 608 |
+
raise ValueError(
|
| 609 |
+
f"record IDs must be integers in 0..{RECORDS_PER_EXPERT - 1}"
|
| 610 |
+
)
|
| 611 |
+
if len(set(selected)) != len(selected):
|
| 612 |
+
raise ValueError("donor and recipient records must be unique and disjoint")
|
| 613 |
+
|
| 614 |
+
remaining = tuple(
|
| 615 |
+
record for record in range(RECORDS_PER_EXPERT) if record not in set(selected)
|
| 616 |
+
)
|
| 617 |
+
if middle_records is None:
|
| 618 |
+
middle = remaining
|
| 619 |
+
else:
|
| 620 |
+
middle = tuple(middle_records)
|
| 621 |
+
if len(middle) != len(remaining) or set(middle) != set(remaining):
|
| 622 |
+
raise ValueError(
|
| 623 |
+
"middle_records must be a permutation of all unselected records"
|
| 624 |
+
)
|
| 625 |
+
return donors + middle + tuple(reversed(recipients))
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
def relabel_block_contexts_for_record_order(
|
| 629 |
+
block_contexts: torch.Tensor,
|
| 630 |
+
record_order: Sequence[int],
|
| 631 |
+
) -> torch.Tensor:
|
| 632 |
+
"""Relabel a 24-record partition to follow ``record_order``.
|
| 633 |
+
|
| 634 |
+
``record_order[new_record]`` is the old record ID. Sorting the returned
|
| 635 |
+
labels with :func:`importance_group_order` yields the requested record
|
| 636 |
+
order while preserving every complete 128-channel record.
|
| 637 |
+
"""
|
| 638 |
+
|
| 639 |
+
_validate_block_contexts(block_contexts, R0)
|
| 640 |
+
order = tuple(record_order)
|
| 641 |
+
if len(order) != RECORDS_PER_EXPERT:
|
| 642 |
+
raise ValueError(f"record_order must contain {RECORDS_PER_EXPERT} records")
|
| 643 |
+
if any(
|
| 644 |
+
isinstance(record, bool)
|
| 645 |
+
or not isinstance(record, int)
|
| 646 |
+
or not 0 <= record < RECORDS_PER_EXPERT
|
| 647 |
+
for record in order
|
| 648 |
+
) or len(set(order)) != RECORDS_PER_EXPERT:
|
| 649 |
+
raise ValueError("record_order must be a permutation of all record IDs")
|
| 650 |
+
inverse = torch.empty(
|
| 651 |
+
RECORDS_PER_EXPERT, dtype=torch.long, device=block_contexts.device
|
| 652 |
+
)
|
| 653 |
+
inverse[torch.tensor(order, dtype=torch.long, device=block_contexts.device)] = (
|
| 654 |
+
torch.arange(RECORDS_PER_EXPERT, device=block_contexts.device)
|
| 655 |
+
)
|
| 656 |
+
return inverse[block_contexts.to(dtype=torch.long)]
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
def expand_group_order(group_order: torch.Tensor) -> torch.Tensor:
|
| 660 |
+
"""Expand four-channel group IDs into a new-index -> old-index permutation."""
|
| 661 |
+
|
| 662 |
+
expected_groups = INTERMEDIATE_CHANNELS // CONTEXT_GROUP_CHANNELS
|
| 663 |
+
if group_order.ndim != 1 or group_order.numel() != expected_groups:
|
| 664 |
+
raise ValueError(f"group_order must contain {expected_groups} groups")
|
| 665 |
+
_validate_permutation(group_order, expected_groups, "group_order")
|
| 666 |
+
offsets = torch.arange(CONTEXT_GROUP_CHANNELS, device=group_order.device)
|
| 667 |
+
return (
|
| 668 |
+
group_order.to(dtype=torch.long)[:, None] * CONTEXT_GROUP_CHANNELS
|
| 669 |
+
+ offsets[None]
|
| 670 |
+
).flatten()
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def _validate_permutation(
|
| 674 |
+
permutation: torch.Tensor, length: int, name: str = "permutation"
|
| 675 |
+
) -> None:
|
| 676 |
+
if permutation.ndim != 1 or permutation.numel() != length:
|
| 677 |
+
raise ValueError(f"{name} must contain exactly {length} entries")
|
| 678 |
+
if permutation.dtype == torch.bool or permutation.is_floating_point():
|
| 679 |
+
raise TypeError(f"{name} must use an integer dtype")
|
| 680 |
+
values = permutation.to(dtype=torch.long)
|
| 681 |
+
expected = torch.arange(length, device=values.device)
|
| 682 |
+
if not torch.equal(torch.sort(values).values, expected):
|
| 683 |
+
raise ValueError(f"{name} must be a bijection over 0..{length - 1}")
|
| 684 |
+
|
| 685 |
+
|
| 686 |
+
def invert_permutation(permutation: torch.Tensor) -> torch.Tensor:
|
| 687 |
+
_validate_permutation(permutation, permutation.numel())
|
| 688 |
+
return torch.argsort(permutation.to(dtype=torch.long))
|
| 689 |
+
|
| 690 |
+
|
| 691 |
+
def coupled_intermediate_permutation(
|
| 692 |
+
w1: torch.Tensor,
|
| 693 |
+
w3: torch.Tensor,
|
| 694 |
+
w2: torch.Tensor,
|
| 695 |
+
permutation: torch.Tensor,
|
| 696 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 697 |
+
"""Apply one exact SiTU-neuron permutation to ordinary weight matrices.
|
| 698 |
+
|
| 699 |
+
``permutation[new_channel]`` is the corresponding old channel. The
|
| 700 |
+
returned matrices satisfy ``w1' = w1[P]``, ``w3' = w3[P]``, and
|
| 701 |
+
``w2' = w2[:, P]``.
|
| 702 |
+
"""
|
| 703 |
+
|
| 704 |
+
if w1.ndim != 2 or w3.ndim != 2 or w2.ndim != 2:
|
| 705 |
+
raise ValueError("expert weights must all be two-dimensional")
|
| 706 |
+
if w1.shape != w3.shape:
|
| 707 |
+
raise ValueError("w1 and w3 must have identical shapes")
|
| 708 |
+
intermediate, hidden = w1.shape
|
| 709 |
+
if tuple(w2.shape) != (hidden, intermediate):
|
| 710 |
+
raise ValueError("w2 must have shape [hidden, intermediate]")
|
| 711 |
+
_validate_permutation(permutation, intermediate)
|
| 712 |
+
row_index = permutation.to(device=w1.device, dtype=torch.long)
|
| 713 |
+
w3_index = permutation.to(device=w3.device, dtype=torch.long)
|
| 714 |
+
column_index = permutation.to(device=w2.device, dtype=torch.long)
|
| 715 |
+
return (
|
| 716 |
+
w1.index_select(0, row_index).contiguous(),
|
| 717 |
+
w3.index_select(0, w3_index).contiguous(),
|
| 718 |
+
w2.index_select(1, column_index).contiguous(),
|
| 719 |
+
)
|
| 720 |
+
|
| 721 |
+
|
| 722 |
+
def record_repack_order(
|
| 723 |
+
source_group_order: torch.Tensor, destination_group_order: torch.Tensor
|
| 724 |
+
) -> torch.Tensor:
|
| 725 |
+
"""Map destination records to source records without splitting a record."""
|
| 726 |
+
|
| 727 |
+
expected_groups = INTERMEDIATE_CHANNELS // CONTEXT_GROUP_CHANNELS
|
| 728 |
+
_validate_permutation(source_group_order, expected_groups, "source_group_order")
|
| 729 |
+
_validate_permutation(
|
| 730 |
+
destination_group_order, expected_groups, "destination_group_order"
|
| 731 |
+
)
|
| 732 |
+
groups_per_record = RECORD_CHANNELS // CONTEXT_GROUP_CHANNELS
|
| 733 |
+
source_records = source_group_order.cpu().reshape(-1, groups_per_record)
|
| 734 |
+
destination_records = destination_group_order.cpu().reshape(-1, groups_per_record)
|
| 735 |
+
source_lookup = {
|
| 736 |
+
tuple(record.tolist()): index for index, record in enumerate(source_records)
|
| 737 |
+
}
|
| 738 |
+
if len(source_lookup) != RECORDS_PER_EXPERT:
|
| 739 |
+
raise ValueError("source order contains duplicate or malformed records")
|
| 740 |
+
try:
|
| 741 |
+
order = [
|
| 742 |
+
source_lookup[tuple(record.tolist())]
|
| 743 |
+
for record in destination_records
|
| 744 |
+
]
|
| 745 |
+
except KeyError as exc:
|
| 746 |
+
raise ValueError("destination order splits or changes a source record") from exc
|
| 747 |
+
return torch.tensor(order, dtype=torch.long, device=source_group_order.device)
|
| 748 |
+
|
| 749 |
+
|
| 750 |
+
def expand_record_order(record_order: torch.Tensor) -> torch.Tensor:
|
| 751 |
+
"""Expand destination-record -> source-record mapping to channel indices."""
|
| 752 |
+
|
| 753 |
+
_validate_permutation(record_order, RECORDS_PER_EXPERT, "record_order")
|
| 754 |
+
offsets = torch.arange(RECORD_CHANNELS, device=record_order.device)
|
| 755 |
+
return (
|
| 756 |
+
record_order.to(dtype=torch.long)[:, None] * RECORD_CHANNELS + offsets[None]
|
| 757 |
+
).flatten()
|
| 758 |
+
|
| 759 |
+
|
| 760 |
+
def repack_rate_axis(
|
| 761 |
+
tensor: torch.Tensor, record_order: torch.Tensor, axis: int
|
| 762 |
+
) -> torch.Tensor:
|
| 763 |
+
"""Move complete 128-channel blocks on a tensor's logical rate axis."""
|
| 764 |
+
|
| 765 |
+
if tensor.ndim == 0:
|
| 766 |
+
raise ValueError("cannot repack a scalar tensor")
|
| 767 |
+
normalized_axis = axis % tensor.ndim
|
| 768 |
+
if tensor.shape[normalized_axis] != INTERMEDIATE_CHANNELS:
|
| 769 |
+
raise ValueError("rate axis must contain exactly 3072 channels")
|
| 770 |
+
channels = expand_record_order(record_order).to(device=tensor.device)
|
| 771 |
+
return tensor.index_select(normalized_axis, channels).contiguous()
|
| 772 |
+
|
| 773 |
+
|
| 774 |
+
def repack_encoded_records(
|
| 775 |
+
encoded: torch.Tensor, record_order: torch.Tensor, rate_axis: RateAxis
|
| 776 |
+
) -> torch.Tensor:
|
| 777 |
+
"""Reorder complete 128-channel records in ``[K/16, N/16, 256]`` states."""
|
| 778 |
+
|
| 779 |
+
_validate_encoded_shape(encoded, rate_axis)
|
| 780 |
+
_validate_permutation(record_order, RECORDS_PER_EXPERT, "record_order")
|
| 781 |
+
order = record_order.to(device=encoded.device, dtype=torch.long)
|
| 782 |
+
if rate_axis == "k":
|
| 783 |
+
records = encoded.reshape(
|
| 784 |
+
RECORDS_PER_EXPERT,
|
| 785 |
+
TILES_PER_RECORD_AXIS,
|
| 786 |
+
encoded.shape[1],
|
| 787 |
+
TILE_VALUES,
|
| 788 |
+
)
|
| 789 |
+
return records.index_select(0, order).reshape_as(encoded).contiguous()
|
| 790 |
+
records = (
|
| 791 |
+
encoded.reshape(
|
| 792 |
+
encoded.shape[0],
|
| 793 |
+
RECORDS_PER_EXPERT,
|
| 794 |
+
TILES_PER_RECORD_AXIS,
|
| 795 |
+
TILE_VALUES,
|
| 796 |
+
)
|
| 797 |
+
.permute(1, 0, 2, 3)
|
| 798 |
+
.contiguous()
|
| 799 |
+
)
|
| 800 |
+
reordered = records.index_select(0, order)
|
| 801 |
+
return (
|
| 802 |
+
reordered.permute(1, 0, 2, 3).contiguous().reshape_as(encoded).contiguous()
|
| 803 |
+
)
|
| 804 |
+
|
| 805 |
+
|
| 806 |
+
def _validate_k(bits: int) -> None:
|
| 807 |
+
if isinstance(bits, bool) or not isinstance(bits, int) or bits not in range(2, 7):
|
| 808 |
+
raise ValueError("SQG trellis packing supports K=2 through K=6")
|
| 809 |
+
|
| 810 |
+
|
| 811 |
+
def pack_trellis_edges(indices: torch.Tensor, bits: int) -> torch.Tensor:
|
| 812 |
+
"""Pack low K bits with the native EXL ``pack_trellis`` word ordering."""
|
| 813 |
+
|
| 814 |
+
_validate_k(bits)
|
| 815 |
+
if indices.ndim < 1 or indices.shape[-1] != TILE_VALUES:
|
| 816 |
+
raise ValueError("indices must have a final dimension of 256")
|
| 817 |
+
if indices.dtype == torch.bool or indices.is_floating_point():
|
| 818 |
+
raise TypeError("indices must use an integer dtype")
|
| 819 |
+
values = indices.to(dtype=torch.int64) & ((1 << bits) - 1)
|
| 820 |
+
spans = values.reshape(*values.shape[:-1], TILE_CHANNELS, TILE_CHANNELS)
|
| 821 |
+
symbol_shifts = torch.arange(bits - 1, -1, -1, device=values.device)
|
| 822 |
+
bitstream = ((spans[..., None] >> symbol_shifts) & 1).reshape(
|
| 823 |
+
*values.shape[:-1], TILE_CHANNELS, bits * TILE_CHANNELS
|
| 824 |
+
)
|
| 825 |
+
word_shifts = torch.arange(15, -1, -1, device=values.device)
|
| 826 |
+
words = (
|
| 827 |
+
bitstream.reshape(*values.shape[:-1], TILE_CHANNELS, bits, 16)
|
| 828 |
+
<< word_shifts
|
| 829 |
+
).sum(dim=-1)
|
| 830 |
+
flat = words.reshape(*values.shape[:-1], TILE_CHANNELS * bits)
|
| 831 |
+
# Native pack.cu applies SWAP16: adjacent uint16 words exchange places.
|
| 832 |
+
swapped = flat.reshape(*flat.shape[:-1], -1, 2).flip(-1).reshape(flat.shape)
|
| 833 |
+
return swapped.to(dtype=torch.int16).contiguous()
|
| 834 |
+
|
| 835 |
+
|
| 836 |
+
def unpack_trellis_edges(packed: torch.Tensor, bits: int) -> torch.Tensor:
|
| 837 |
+
"""Invert :func:`pack_trellis_edges` to the stored K-bit edge symbols."""
|
| 838 |
+
|
| 839 |
+
_validate_k(bits)
|
| 840 |
+
expected_words = TILE_CHANNELS * bits
|
| 841 |
+
if packed.ndim < 1 or packed.shape[-1] != expected_words:
|
| 842 |
+
raise ValueError(f"packed tiles must end in {expected_words} int16 words")
|
| 843 |
+
if packed.dtype != torch.int16:
|
| 844 |
+
raise TypeError("packed trellis words must use torch.int16")
|
| 845 |
+
words = packed.to(dtype=torch.int64) & 0xFFFF
|
| 846 |
+
words = words.reshape(*words.shape[:-1], -1, 2).flip(-1).reshape(words.shape)
|
| 847 |
+
words = words.reshape(*words.shape[:-1], TILE_CHANNELS, bits)
|
| 848 |
+
word_shifts = torch.arange(15, -1, -1, device=words.device)
|
| 849 |
+
bitstream = ((words[..., None] >> word_shifts) & 1).reshape(
|
| 850 |
+
*words.shape[:-2], TILE_CHANNELS, bits * TILE_CHANNELS
|
| 851 |
+
)
|
| 852 |
+
symbol_bits = bitstream.reshape(
|
| 853 |
+
*words.shape[:-2], TILE_CHANNELS, TILE_CHANNELS, bits
|
| 854 |
+
)
|
| 855 |
+
symbol_shifts = torch.arange(bits - 1, -1, -1, device=words.device)
|
| 856 |
+
values = (symbol_bits << symbol_shifts).sum(dim=-1)
|
| 857 |
+
return values.reshape(*words.shape[:-2], TILE_VALUES).to(torch.int16).contiguous()
|
| 858 |
+
|
| 859 |
+
|
| 860 |
+
def unpack_trellis_states(packed: torch.Tensor, bits: int) -> torch.Tensor:
|
| 861 |
+
"""Unpack native edge words and reconstruct every cyclic 16-bit state."""
|
| 862 |
+
|
| 863 |
+
return reconstruct_trellis_states(unpack_trellis_edges(packed, bits), bits)
|
| 864 |
+
|
| 865 |
+
|
| 866 |
+
@dataclass(frozen=True)
|
| 867 |
+
class QSRTTrellisDescriptor:
|
| 868 |
+
"""TP-independent description of one complete packed expert matrix."""
|
| 869 |
+
|
| 870 |
+
mode_id: int
|
| 871 |
+
rate_axis: RateAxis
|
| 872 |
+
k_tiles: int
|
| 873 |
+
n_tiles: int
|
| 874 |
+
schema: str = SCHEMA
|
| 875 |
+
intermediate_channels: int = INTERMEDIATE_CHANNELS
|
| 876 |
+
record_channels: int = RECORD_CHANNELS
|
| 877 |
+
|
| 878 |
+
def __post_init__(self) -> None:
|
| 879 |
+
# Candidate payloads are allocation-independent and stay in logical
|
| 880 |
+
# pair order. Physical ownership is applied only by the atom sharder.
|
| 881 |
+
if self.schema not in LOGICAL_CANDIDATE_SCHEMAS:
|
| 882 |
+
raise ValueError(f"unsupported QSRT schema: {self.schema}")
|
| 883 |
+
if self.intermediate_channels != INTERMEDIATE_CHANNELS:
|
| 884 |
+
raise ValueError("Kimi-K3 QSRT requires a 3072-channel intermediate axis")
|
| 885 |
+
if self.record_channels != RECORD_CHANNELS:
|
| 886 |
+
raise ValueError("Kimi-K3 QSRT requires 128-channel records")
|
| 887 |
+
resolve_mode(self.mode_id)
|
| 888 |
+
if self.rate_axis not in ("k", "n"):
|
| 889 |
+
raise ValueError("rate_axis must be 'k' or 'n'")
|
| 890 |
+
if self.k_tiles <= 0 or self.n_tiles <= 0:
|
| 891 |
+
raise ValueError("tile dimensions must be positive")
|
| 892 |
+
rate_tiles = self.k_tiles if self.rate_axis == "k" else self.n_tiles
|
| 893 |
+
if rate_tiles * TILE_CHANNELS != INTERMEDIATE_CHANNELS:
|
| 894 |
+
raise ValueError("rate axis must contain exactly 3072 channels")
|
| 895 |
+
orthogonal_tiles = self.n_tiles if self.rate_axis == "k" else self.k_tiles
|
| 896 |
+
if orthogonal_tiles * TILE_CHANNELS != LATENT_CHANNELS:
|
| 897 |
+
raise ValueError("orthogonal axis must contain exactly 3584 channels")
|
| 898 |
+
|
| 899 |
+
@property
|
| 900 |
+
def mode(self) -> ModeSpec:
|
| 901 |
+
return resolve_mode(self.mode_id)
|
| 902 |
+
|
| 903 |
+
@property
|
| 904 |
+
def orthogonal_tiles(self) -> int:
|
| 905 |
+
return self.n_tiles if self.rate_axis == "k" else self.k_tiles
|
| 906 |
+
|
| 907 |
+
@property
|
| 908 |
+
def tiles_per_record(self) -> int:
|
| 909 |
+
return TILES_PER_RECORD_AXIS * self.orthogonal_tiles
|
| 910 |
+
|
| 911 |
+
@property
|
| 912 |
+
def words_per_pair(self) -> int:
|
| 913 |
+
return self.tiles_per_record * TILE_CHANNELS * 6
|
| 914 |
+
|
| 915 |
+
@property
|
| 916 |
+
def payload_words(self) -> int:
|
| 917 |
+
return PAIRS_PER_EXPERT * self.words_per_pair
|
| 918 |
+
|
| 919 |
+
@property
|
| 920 |
+
def payload_bytes(self) -> int:
|
| 921 |
+
return self.payload_words * torch.int16.itemsize
|
| 922 |
+
|
| 923 |
+
@property
|
| 924 |
+
def weight_count(self) -> int:
|
| 925 |
+
return self.k_tiles * self.n_tiles * TILE_VALUES
|
| 926 |
+
|
| 927 |
+
def to_manifest(self) -> dict[str, object]:
|
| 928 |
+
return {
|
| 929 |
+
"schema": self.schema,
|
| 930 |
+
"mode_id": self.mode_id,
|
| 931 |
+
"mode": self.mode.name,
|
| 932 |
+
"rate_axis": self.rate_axis,
|
| 933 |
+
"k_tiles": self.k_tiles,
|
| 934 |
+
"n_tiles": self.n_tiles,
|
| 935 |
+
"intermediate_channels": self.intermediate_channels,
|
| 936 |
+
"record_channels": self.record_channels,
|
| 937 |
+
"pair_channels": PAIR_CHANNELS,
|
| 938 |
+
"record_bits": list(record_bits(self.mode)),
|
| 939 |
+
"pair_kinds": list(pair_kinds(self.mode)),
|
| 940 |
+
"words_per_pair": self.words_per_pair,
|
| 941 |
+
"payload_words": self.payload_words,
|
| 942 |
+
"payload_bytes": self.payload_bytes,
|
| 943 |
+
}
|
| 944 |
+
|
| 945 |
+
|
| 946 |
+
@dataclass(frozen=True)
|
| 947 |
+
class PackedQSRTTrellis:
|
| 948 |
+
descriptor: QSRTTrellisDescriptor
|
| 949 |
+
payload: torch.Tensor
|
| 950 |
+
|
| 951 |
+
def validate(self) -> None:
|
| 952 |
+
if self.payload.dtype != torch.int16:
|
| 953 |
+
raise TypeError("QSRT trellis payload must use torch.int16")
|
| 954 |
+
if self.payload.ndim != 1:
|
| 955 |
+
raise ValueError("QSRT trellis payload must be one-dimensional")
|
| 956 |
+
if self.payload.numel() != self.descriptor.payload_words:
|
| 957 |
+
raise ValueError(
|
| 958 |
+
"QSRT trellis payload length does not match its descriptor"
|
| 959 |
+
)
|
| 960 |
+
|
| 961 |
+
|
| 962 |
+
def _validate_encoded_shape(encoded: torch.Tensor, rate_axis: RateAxis) -> None:
|
| 963 |
+
if encoded.ndim != 3 or encoded.shape[-1] != TILE_VALUES:
|
| 964 |
+
raise ValueError("encoded states must have shape [K/16, N/16, 256]")
|
| 965 |
+
if encoded.dtype == torch.bool or encoded.is_floating_point():
|
| 966 |
+
raise TypeError("encoded states must use an integer dtype")
|
| 967 |
+
if rate_axis not in ("k", "n"):
|
| 968 |
+
raise ValueError("rate_axis must be 'k' or 'n'")
|
| 969 |
+
rate_tiles = encoded.shape[0] if rate_axis == "k" else encoded.shape[1]
|
| 970 |
+
if rate_tiles * TILE_CHANNELS != INTERMEDIATE_CHANNELS:
|
| 971 |
+
raise ValueError("encoded rate axis must contain exactly 3072 channels")
|
| 972 |
+
|
| 973 |
+
|
| 974 |
+
def _record_tiles(
|
| 975 |
+
encoded: torch.Tensor, record: int, rate_axis: RateAxis
|
| 976 |
+
) -> torch.Tensor:
|
| 977 |
+
begin = record * TILES_PER_RECORD_AXIS
|
| 978 |
+
if rate_axis == "k":
|
| 979 |
+
return encoded.narrow(0, begin, TILES_PER_RECORD_AXIS).reshape(-1, TILE_VALUES)
|
| 980 |
+
return encoded.narrow(1, begin, TILES_PER_RECORD_AXIS).reshape(-1, TILE_VALUES)
|
| 981 |
+
|
| 982 |
+
|
| 983 |
+
def pack_qsrt_trellis(
|
| 984 |
+
encoded: torch.Tensor,
|
| 985 |
+
mode: ModeSpec | str | int,
|
| 986 |
+
*,
|
| 987 |
+
rate_axis: RateAxis,
|
| 988 |
+
schema: str = SCHEMA,
|
| 989 |
+
validate_states: bool = True,
|
| 990 |
+
) -> PackedQSRTTrellis:
|
| 991 |
+
"""Pack a complete matrix into fixed-size balanced record pairs."""
|
| 992 |
+
|
| 993 |
+
_validate_encoded_shape(encoded, rate_axis)
|
| 994 |
+
spec = resolve_mode(mode)
|
| 995 |
+
descriptor = QSRTTrellisDescriptor(
|
| 996 |
+
mode_id=spec.mode_id,
|
| 997 |
+
rate_axis=rate_axis,
|
| 998 |
+
k_tiles=encoded.shape[0],
|
| 999 |
+
n_tiles=encoded.shape[1],
|
| 1000 |
+
schema=schema,
|
| 1001 |
+
)
|
| 1002 |
+
pieces = []
|
| 1003 |
+
for record, bits in enumerate(record_bits(spec)):
|
| 1004 |
+
record_states = _record_tiles(encoded, record, rate_axis)
|
| 1005 |
+
record_payload = pack_trellis_edges(record_states, bits)
|
| 1006 |
+
if validate_states and not torch.equal(
|
| 1007 |
+
unpack_trellis_states(record_payload, bits), record_states.to(torch.int16)
|
| 1008 |
+
):
|
| 1009 |
+
raise ValueError("encoded input is not a closed cyclic trellis path")
|
| 1010 |
+
pieces.append(record_payload)
|
| 1011 |
+
payload = torch.cat([piece.reshape(-1) for piece in pieces]).contiguous()
|
| 1012 |
+
packed = PackedQSRTTrellis(descriptor, payload)
|
| 1013 |
+
packed.validate()
|
| 1014 |
+
return packed
|
| 1015 |
+
|
| 1016 |
+
|
| 1017 |
+
def unpack_qsrt_trellis_edges(packed: PackedQSRTTrellis) -> torch.Tensor:
|
| 1018 |
+
"""Unpack a complete QSRT payload to physical-order K-bit edge symbols."""
|
| 1019 |
+
|
| 1020 |
+
packed.validate()
|
| 1021 |
+
descriptor = packed.descriptor
|
| 1022 |
+
output = torch.empty(
|
| 1023 |
+
(descriptor.k_tiles, descriptor.n_tiles, TILE_VALUES),
|
| 1024 |
+
dtype=torch.int16,
|
| 1025 |
+
device=packed.payload.device,
|
| 1026 |
+
)
|
| 1027 |
+
cursor = 0
|
| 1028 |
+
for record, bits in enumerate(record_bits(descriptor.mode)):
|
| 1029 |
+
word_count = descriptor.tiles_per_record * TILE_CHANNELS * bits
|
| 1030 |
+
words = packed.payload.narrow(0, cursor, word_count).reshape(
|
| 1031 |
+
descriptor.tiles_per_record, TILE_CHANNELS * bits
|
| 1032 |
+
)
|
| 1033 |
+
tiles = unpack_trellis_edges(words, bits)
|
| 1034 |
+
begin = record * TILES_PER_RECORD_AXIS
|
| 1035 |
+
if descriptor.rate_axis == "k":
|
| 1036 |
+
output.narrow(0, begin, TILES_PER_RECORD_AXIS).copy_(
|
| 1037 |
+
tiles.reshape(
|
| 1038 |
+
TILES_PER_RECORD_AXIS, descriptor.n_tiles, TILE_VALUES
|
| 1039 |
+
)
|
| 1040 |
+
)
|
| 1041 |
+
else:
|
| 1042 |
+
output.narrow(1, begin, TILES_PER_RECORD_AXIS).copy_(
|
| 1043 |
+
tiles.reshape(
|
| 1044 |
+
descriptor.k_tiles, TILES_PER_RECORD_AXIS, TILE_VALUES
|
| 1045 |
+
)
|
| 1046 |
+
)
|
| 1047 |
+
cursor += word_count
|
| 1048 |
+
if cursor != packed.payload.numel():
|
| 1049 |
+
raise ValueError("QSRT trellis payload has trailing data")
|
| 1050 |
+
return output
|
| 1051 |
+
|
| 1052 |
+
|
| 1053 |
+
def unpack_qsrt_trellis_states(packed: PackedQSRTTrellis) -> torch.Tensor:
|
| 1054 |
+
"""Unpack a complete QSRT payload to its physical-order 16-bit states."""
|
| 1055 |
+
|
| 1056 |
+
edges = unpack_qsrt_trellis_edges(packed)
|
| 1057 |
+
descriptor = packed.descriptor
|
| 1058 |
+
output = torch.empty_like(edges)
|
| 1059 |
+
rates = record_bits(descriptor.mode)
|
| 1060 |
+
# The recurrence is elementwise over every dimension except the trailing
|
| 1061 |
+
# 256-symbol path. Group equal-K records so each matrix performs at most
|
| 1062 |
+
# three Python-level recurrences instead of one per 128-channel record.
|
| 1063 |
+
for bits in sorted(set(rates)):
|
| 1064 |
+
records = [
|
| 1065 |
+
record for record, record_bits_value in enumerate(rates)
|
| 1066 |
+
if record_bits_value == bits
|
| 1067 |
+
]
|
| 1068 |
+
if descriptor.rate_axis == "k":
|
| 1069 |
+
grouped_edges = torch.stack(
|
| 1070 |
+
[
|
| 1071 |
+
edges.narrow(
|
| 1072 |
+
0,
|
| 1073 |
+
record * TILES_PER_RECORD_AXIS,
|
| 1074 |
+
TILES_PER_RECORD_AXIS,
|
| 1075 |
+
)
|
| 1076 |
+
for record in records
|
| 1077 |
+
]
|
| 1078 |
+
)
|
| 1079 |
+
grouped_states = reconstruct_trellis_states(grouped_edges, bits)
|
| 1080 |
+
for index, record in enumerate(records):
|
| 1081 |
+
output.narrow(
|
| 1082 |
+
0,
|
| 1083 |
+
record * TILES_PER_RECORD_AXIS,
|
| 1084 |
+
TILES_PER_RECORD_AXIS,
|
| 1085 |
+
).copy_(grouped_states[index])
|
| 1086 |
+
else:
|
| 1087 |
+
grouped_edges = torch.stack(
|
| 1088 |
+
[
|
| 1089 |
+
edges.narrow(
|
| 1090 |
+
1,
|
| 1091 |
+
record * TILES_PER_RECORD_AXIS,
|
| 1092 |
+
TILES_PER_RECORD_AXIS,
|
| 1093 |
+
)
|
| 1094 |
+
for record in records
|
| 1095 |
+
]
|
| 1096 |
+
)
|
| 1097 |
+
grouped_states = reconstruct_trellis_states(grouped_edges, bits)
|
| 1098 |
+
for index, record in enumerate(records):
|
| 1099 |
+
output.narrow(
|
| 1100 |
+
1,
|
| 1101 |
+
record * TILES_PER_RECORD_AXIS,
|
| 1102 |
+
TILES_PER_RECORD_AXIS,
|
| 1103 |
+
).copy_(grouped_states[index])
|
| 1104 |
+
return output
|
| 1105 |
+
|
| 1106 |
+
|
| 1107 |
+
def decode_qsrt_exl3_weight(
|
| 1108 |
+
packed: PackedQSRTTrellis,
|
| 1109 |
+
suh: torch.Tensor,
|
| 1110 |
+
svh: torch.Tensor,
|
| 1111 |
+
*,
|
| 1112 |
+
codebook: str = CODEBOOK_SQG_XOR_CHEB_T12,
|
| 1113 |
+
) -> torch.Tensor:
|
| 1114 |
+
"""Decode a complete QSRT payload to its physical EXL-oriented weight."""
|
| 1115 |
+
|
| 1116 |
+
packed.validate()
|
| 1117 |
+
_validate_scale_vector(
|
| 1118 |
+
suh,
|
| 1119 |
+
packed.descriptor.k_tiles * TILE_CHANNELS,
|
| 1120 |
+
"suh",
|
| 1121 |
+
packed.payload.device,
|
| 1122 |
+
)
|
| 1123 |
+
_validate_scale_vector(
|
| 1124 |
+
svh,
|
| 1125 |
+
packed.descriptor.n_tiles * TILE_CHANNELS,
|
| 1126 |
+
"svh",
|
| 1127 |
+
packed.payload.device,
|
| 1128 |
+
)
|
| 1129 |
+
tile_bits = tuple(
|
| 1130 |
+
bits
|
| 1131 |
+
for rate in record_bits(packed.descriptor.mode)
|
| 1132 |
+
for bits in (rate,) * TILES_PER_RECORD_AXIS
|
| 1133 |
+
)
|
| 1134 |
+
return decode_qsrt_weight(
|
| 1135 |
+
unpack_qsrt_trellis_states(packed),
|
| 1136 |
+
suh,
|
| 1137 |
+
svh,
|
| 1138 |
+
rate_axis=packed.descriptor.rate_axis,
|
| 1139 |
+
tile_bits=tile_bits,
|
| 1140 |
+
codebook=codebook,
|
| 1141 |
+
)
|
reproduction/closure/patches/kquant/changed-files/kquant/sqg_e4m3.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""QSRT and research-control SQG E4M3 reconstruction tables.
|
| 2 |
+
|
| 3 |
+
This module implements the independently proposed L16 SQG labelling. It
|
| 4 |
+
deliberately separates the rolling trellis graph from its numerical labels:
|
| 5 |
+
the encoder still stores K branch bits per coefficient, while a deterministic
|
| 6 |
+
K-specific mapping turns each 16-bit transition state into an E4M3 value.
|
| 7 |
+
|
| 8 |
+
The primary ``sqg_xor_cheb_t12`` codebook composes the carry-mixed bijective SQG rank
|
| 9 |
+
map with a modal 12-bit compression of the Chebyshev-derived finite-E4M3
|
| 10 |
+
staircase. The older exact-graph and R44 mappings remain explicit research
|
| 11 |
+
controls. All paths return raw E4M3 bytes so the offline encoder and serving
|
| 12 |
+
kernel can share one byte-exact numerical contract.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
from functools import lru_cache
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
SQG_NORMAL_E4M3 = "sqg-normal-e4m3"
|
| 23 |
+
SQG_CHEB_NORMAL_E4M3 = "sqg-cheb-normal-e4m3"
|
| 24 |
+
SQG_CHEB = "sqg_cheb"
|
| 25 |
+
SQG_XOR_CHEB_T12 = "sqg_xor_cheb_t12"
|
| 26 |
+
|
| 27 |
+
_TRANSITIONS = 1 << 16
|
| 28 |
+
_CLIP = 1.0 / 2048.0
|
| 29 |
+
_P = (1.25667142, 2.87422731, -9.02398882, 5.36810336, -0.46703015)
|
| 30 |
+
_Q = (1.0, 2.07630930, -8.08332684, 6.32135736, -1.31208298)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _validate(bits: int, mode: str) -> None:
|
| 34 |
+
if isinstance(bits, bool) or not isinstance(bits, int) or bits not in range(2, 7):
|
| 35 |
+
raise ValueError("SQG supports integer K2 through K6")
|
| 36 |
+
if mode != "normal":
|
| 37 |
+
raise ValueError("the supported R44 mode is 'normal'")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _mix_width(
|
| 41 |
+
values: torch.Tensor,
|
| 42 |
+
*,
|
| 43 |
+
width: int,
|
| 44 |
+
multiplier_a: int,
|
| 45 |
+
multiplier_b: int,
|
| 46 |
+
shift_a: int,
|
| 47 |
+
shift_b: int,
|
| 48 |
+
shift_c: int,
|
| 49 |
+
) -> torch.Tensor:
|
| 50 |
+
"""Apply the report's bijective XOR/multiply mixer modulo ``2**width``."""
|
| 51 |
+
|
| 52 |
+
mask = (1 << width) - 1
|
| 53 |
+
result = values & mask
|
| 54 |
+
for multiplier, shift in (
|
| 55 |
+
(multiplier_a | 1, shift_a),
|
| 56 |
+
(multiplier_b | 1, shift_b),
|
| 57 |
+
):
|
| 58 |
+
result ^= result >> min(max(shift, 1), width - 1)
|
| 59 |
+
result = (result * multiplier) & mask
|
| 60 |
+
result ^= result >> min(max(shift_c, 1), width - 1)
|
| 61 |
+
return result & mask
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _reverse_low_bits(values: torch.Tensor, bits: int) -> torch.Tensor:
|
| 65 |
+
result = torch.zeros_like(values)
|
| 66 |
+
for index in range(bits):
|
| 67 |
+
result |= ((values >> index) & 1) << (bits - 1 - index)
|
| 68 |
+
return result
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _r44_inverse_normal(probability: torch.Tensor) -> torch.Tensor:
|
| 72 |
+
a = 2.0 * probability - 1.0
|
| 73 |
+
x = a.square()
|
| 74 |
+
numerator = torch.full_like(x, _P[-1])
|
| 75 |
+
denominator = torch.full_like(x, _Q[-1])
|
| 76 |
+
for coefficient in reversed(_P[:-1]):
|
| 77 |
+
numerator = numerator * x + coefficient
|
| 78 |
+
for coefficient in reversed(_Q[:-1]):
|
| 79 |
+
denominator = denominator * x + coefficient
|
| 80 |
+
return a * numerator / denominator
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
@lru_cache(maxsize=None)
|
| 84 |
+
def _sqg_cpu_bytes(bits: int, mode: str) -> torch.Tensor:
|
| 85 |
+
_validate(bits, mode)
|
| 86 |
+
width = 16 - bits
|
| 87 |
+
branches = 1 << bits
|
| 88 |
+
states = torch.arange(_TRANSITIONS, dtype=torch.int64)
|
| 89 |
+
history = states >> bits
|
| 90 |
+
branch = states & (branches - 1)
|
| 91 |
+
|
| 92 |
+
phase = _mix_width(
|
| 93 |
+
history,
|
| 94 |
+
width=width,
|
| 95 |
+
multiplier_a=0x65AF,
|
| 96 |
+
multiplier_b=0x16BF,
|
| 97 |
+
shift_a=6,
|
| 98 |
+
shift_b=4,
|
| 99 |
+
shift_c=5,
|
| 100 |
+
)
|
| 101 |
+
syndrome_hash = _mix_width(
|
| 102 |
+
history ^ 0x5105,
|
| 103 |
+
width=width,
|
| 104 |
+
multiplier_a=0x8693,
|
| 105 |
+
multiplier_b=0x2A21,
|
| 106 |
+
shift_a=2,
|
| 107 |
+
shift_b=4,
|
| 108 |
+
shift_c=4,
|
| 109 |
+
)
|
| 110 |
+
syndrome = syndrome_hash & (branches - 1)
|
| 111 |
+
stratum = (
|
| 112 |
+
7 * (_reverse_low_bits(branch, bits) ^ syndrome)
|
| 113 |
+
) & (branches - 1)
|
| 114 |
+
rank = (stratum << width) | phase
|
| 115 |
+
probability = ((rank.double() + 0.5) / _TRANSITIONS).clamp(
|
| 116 |
+
_CLIP, 1.0 - _CLIP
|
| 117 |
+
)
|
| 118 |
+
gaussian = _r44_inverse_normal(probability)
|
| 119 |
+
values = (1.5 * gaussian).float()
|
| 120 |
+
if not bool(torch.isfinite(values).all()):
|
| 121 |
+
raise RuntimeError("SQG compander produced a non-finite value")
|
| 122 |
+
return values.to(torch.float8_e4m3fn).view(torch.uint8).contiguous()
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def sqg_e4m3_bytes(
|
| 126 |
+
bits: int,
|
| 127 |
+
mode: str,
|
| 128 |
+
*,
|
| 129 |
+
device: torch.device | str = "cpu",
|
| 130 |
+
) -> torch.Tensor:
|
| 131 |
+
"""Return the 65,536 raw finite-E4M3 labels for an L16 SQG graph."""
|
| 132 |
+
|
| 133 |
+
_validate(bits, mode)
|
| 134 |
+
return _sqg_cpu_bytes(bits, mode).to(device=device).contiguous()
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def sqg_e4m3_codebook(
|
| 138 |
+
bits: int,
|
| 139 |
+
mode: str,
|
| 140 |
+
*,
|
| 141 |
+
device: torch.device | str = "cpu",
|
| 142 |
+
dtype: torch.dtype = torch.float16,
|
| 143 |
+
) -> torch.Tensor:
|
| 144 |
+
"""Return the SQG table widened from its exact E4M3 wire values."""
|
| 145 |
+
|
| 146 |
+
if not dtype.is_floating_point:
|
| 147 |
+
raise TypeError("SQG codebook dtype must be floating point")
|
| 148 |
+
raw = sqg_e4m3_bytes(bits, mode, device=device)
|
| 149 |
+
return raw.view(torch.float8_e4m3fn).to(dtype=dtype).contiguous()
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def sqg_e4m3_bytes_from_rank_lut(
|
| 153 |
+
bits: int,
|
| 154 |
+
rank_lut: torch.Tensor,
|
| 155 |
+
*,
|
| 156 |
+
device: torch.device | str = "cpu",
|
| 157 |
+
) -> torch.Tensor:
|
| 158 |
+
"""Map a shared rank-indexed E4M3 law onto the unchanged SQG graph.
|
| 159 |
+
|
| 160 |
+
``rank_lut`` is deliberately independent of ``bits``. K2 through K6 may
|
| 161 |
+
expose different outgoing menus because their stratum widths differ, but
|
| 162 |
+
they retain the same history mixer, syndrome mixer, branch permutation,
|
| 163 |
+
and scalar reconstruction law. This is the controlled interface used to
|
| 164 |
+
evaluate alternative companders without introducing rate-specific graph
|
| 165 |
+
edits.
|
| 166 |
+
"""
|
| 167 |
+
|
| 168 |
+
_validate(bits, "normal")
|
| 169 |
+
if rank_lut.dtype != torch.uint8:
|
| 170 |
+
raise TypeError("SQG rank LUT must contain raw uint8 E4M3 labels")
|
| 171 |
+
if rank_lut.ndim != 1 or rank_lut.numel() != _TRANSITIONS:
|
| 172 |
+
raise ValueError("SQG rank LUT must contain exactly 65,536 labels")
|
| 173 |
+
rank_lut = rank_lut.detach().to(device="cpu").contiguous()
|
| 174 |
+
values = rank_lut.view(torch.float8_e4m3fn).float()
|
| 175 |
+
if not bool(torch.isfinite(values).all()):
|
| 176 |
+
raise ValueError("SQG rank LUT must contain only finite E4M3 labels")
|
| 177 |
+
return rank_lut.index_select(0, sqg_rank_permutation(bits)).to(
|
| 178 |
+
device=device
|
| 179 |
+
).contiguous()
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def sqg_e4m3_codebook_from_rank_lut(
|
| 183 |
+
bits: int,
|
| 184 |
+
rank_lut: torch.Tensor,
|
| 185 |
+
*,
|
| 186 |
+
device: torch.device | str = "cpu",
|
| 187 |
+
dtype: torch.dtype = torch.float16,
|
| 188 |
+
) -> torch.Tensor:
|
| 189 |
+
"""Widen a shared rank-indexed E4M3 law after SQG graph labelling."""
|
| 190 |
+
|
| 191 |
+
if not dtype.is_floating_point:
|
| 192 |
+
raise TypeError("SQG codebook dtype must be floating point")
|
| 193 |
+
raw = sqg_e4m3_bytes_from_rank_lut(bits, rank_lut, device=device)
|
| 194 |
+
return raw.view(torch.float8_e4m3fn).to(dtype=dtype).contiguous()
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def _finite_e4m3_rank_lut(values: torch.Tensor) -> torch.Tensor:
|
| 198 |
+
"""Round a monotone real rank law to canonical finite E4M3 bytes."""
|
| 199 |
+
|
| 200 |
+
if values.ndim != 1 or values.numel() != _TRANSITIONS:
|
| 201 |
+
raise ValueError("SQG rank-law values must contain exactly 65,536 entries")
|
| 202 |
+
if not values.is_floating_point() or not bool(torch.isfinite(values).all()):
|
| 203 |
+
raise ValueError("SQG rank-law values must be finite floating point")
|
| 204 |
+
raw = values.float().to(torch.float8_e4m3fn).view(torch.uint8).contiguous()
|
| 205 |
+
# E4M3 has signed zero. The numerical law uses one canonical zero byte so
|
| 206 |
+
# its exact representation is stable across CPU/GPU conversion paths.
|
| 207 |
+
raw = raw.clone()
|
| 208 |
+
raw[(raw & 0x7F) == 0] = 0
|
| 209 |
+
decoded = raw.view(torch.float8_e4m3fn).float()
|
| 210 |
+
if not bool(torch.isfinite(decoded).all()):
|
| 211 |
+
raise RuntimeError("SQG rank law rounded to a non-finite E4M3 value")
|
| 212 |
+
if not bool(torch.all(decoded[1:] >= decoded[:-1])):
|
| 213 |
+
raise RuntimeError("SQG rank law is not monotone after E4M3 rounding")
|
| 214 |
+
return raw
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
@lru_cache(maxsize=1)
|
| 218 |
+
def sqg_cheb_normal_rank_e4m3_bytes() -> torch.Tensor:
|
| 219 |
+
"""Return the exact full-tail normal target used by SQG-Cheb.
|
| 220 |
+
|
| 221 |
+
The Chebyshev evaluator is an implementation of this finite staircase,
|
| 222 |
+
not a different reconstruction law. Computing the midpoint quantiles
|
| 223 |
+
here gives a compact scientific reference for candidate generation and
|
| 224 |
+
reproduces the exhaustively validated L16 SQG-Cheb table byte-for-byte.
|
| 225 |
+
"""
|
| 226 |
+
|
| 227 |
+
ranks = torch.arange(_TRANSITIONS, dtype=torch.float64)
|
| 228 |
+
probability = (ranks + 0.5) / _TRANSITIONS
|
| 229 |
+
values = 1.5 * torch.special.ndtri(probability)
|
| 230 |
+
return _finite_e4m3_rank_lut(values)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
@lru_cache(maxsize=None)
|
| 234 |
+
def sqg_cheb_normal_e4m3_bytes(bits: int) -> torch.Tensor:
|
| 235 |
+
"""Map the exact SQG-Cheb normal staircase onto the frozen SQG graph."""
|
| 236 |
+
|
| 237 |
+
return sqg_e4m3_bytes_from_rank_lut(
|
| 238 |
+
bits, sqg_cheb_normal_rank_e4m3_bytes()
|
| 239 |
+
).contiguous()
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
@lru_cache(maxsize=1)
|
| 243 |
+
def sqg_xor_cheb_t12_rank_lut_bytes() -> torch.Tensor:
|
| 244 |
+
"""Return the frozen 4 KiB modal T12 reconstruction staircase.
|
| 245 |
+
|
| 246 |
+
Each byte represents sixteen consecutive ranks of the exact normal
|
| 247 |
+
staircase. The modal byte is selected per block; ties select the lower
|
| 248 |
+
raw byte. This is the same immutable construction used by the B12X
|
| 249 |
+
``sqg_xor_cheb_t12`` decoder.
|
| 250 |
+
"""
|
| 251 |
+
|
| 252 |
+
exact = sqg_cheb_normal_rank_e4m3_bytes().reshape(1 << 12, 16)
|
| 253 |
+
result = torch.empty(1 << 12, dtype=torch.uint8)
|
| 254 |
+
for index, block in enumerate(exact):
|
| 255 |
+
labels, counts = torch.unique(block, return_counts=True)
|
| 256 |
+
result[index] = labels[counts == counts.max()].min()
|
| 257 |
+
return result.contiguous()
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
@lru_cache(maxsize=None)
|
| 261 |
+
def sqg_xor_rank_permutation(bits: int) -> torch.Tensor:
|
| 262 |
+
"""Return the primary carry-mixed SQG rank for every L16 codeword.
|
| 263 |
+
|
| 264 |
+
The two triangular xorshifts are bijections over the retained history,
|
| 265 |
+
the multiplier is odd, and bit reversal followed by XOR is a branch
|
| 266 |
+
permutation. The resulting map is therefore bijective over all 65,536
|
| 267 |
+
``(stratum, phase)`` coordinates while preserving one outgoing edge per
|
| 268 |
+
stratum in every state.
|
| 269 |
+
"""
|
| 270 |
+
|
| 271 |
+
_validate(bits, "normal")
|
| 272 |
+
width = 16 - bits
|
| 273 |
+
history_mask = (1 << width) - 1
|
| 274 |
+
branch_mask = (1 << bits) - 1
|
| 275 |
+
codeword = torch.arange(_TRANSITIONS, dtype=torch.int64)
|
| 276 |
+
history = codeword >> bits
|
| 277 |
+
branch = codeword & branch_mask
|
| 278 |
+
|
| 279 |
+
mixed = history ^ (history >> 11)
|
| 280 |
+
mixed ^= (mixed << 11) & history_mask
|
| 281 |
+
product = (0x3FA7D929 * mixed + 0xC928FD8E) & 0xFFFFFFFF
|
| 282 |
+
phase = product & history_mask
|
| 283 |
+
syndrome = product >> (32 - bits)
|
| 284 |
+
stratum = _reverse_low_bits(branch, bits) ^ syndrome
|
| 285 |
+
return ((stratum << width) | phase).contiguous()
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
@lru_cache(maxsize=None)
|
| 289 |
+
def _sqg_xor_cheb_t12_cpu_bytes(bits: int) -> torch.Tensor:
|
| 290 |
+
ranks = sqg_xor_rank_permutation(bits)
|
| 291 |
+
return sqg_xor_cheb_t12_rank_lut_bytes().index_select(0, ranks >> 4).contiguous()
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def sqg_xor_cheb_t12_bytes(
|
| 295 |
+
bits: int,
|
| 296 |
+
*,
|
| 297 |
+
device: torch.device | str = "cpu",
|
| 298 |
+
) -> torch.Tensor:
|
| 299 |
+
"""Return the primary QSRT-E4M3 labels for all L16 codewords."""
|
| 300 |
+
|
| 301 |
+
_validate(bits, "normal")
|
| 302 |
+
return _sqg_xor_cheb_t12_cpu_bytes(bits).to(device=device).contiguous()
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def sqg_codebook_bytes(
|
| 306 |
+
bits: int,
|
| 307 |
+
codebook: str,
|
| 308 |
+
*,
|
| 309 |
+
rate_axis: str | None = None,
|
| 310 |
+
device: torch.device | str = "cpu",
|
| 311 |
+
) -> torch.Tensor:
|
| 312 |
+
"""Return exact state-indexed E4M3 labels for a named SQG codebook."""
|
| 313 |
+
|
| 314 |
+
if codebook == SQG_XOR_CHEB_T12:
|
| 315 |
+
return sqg_xor_cheb_t12_bytes(bits, device=device)
|
| 316 |
+
if codebook == SQG_NORMAL_E4M3:
|
| 317 |
+
return sqg_e4m3_bytes(bits, "normal", device=device)
|
| 318 |
+
if codebook == SQG_CHEB_NORMAL_E4M3:
|
| 319 |
+
return sqg_cheb_normal_e4m3_bytes(bits).to(device=device).contiguous()
|
| 320 |
+
if codebook == SQG_CHEB:
|
| 321 |
+
if rate_axis not in ("k", "n"):
|
| 322 |
+
raise ValueError(
|
| 323 |
+
"the w2-only K2-Q8H4 profile requires rate_axis 'k' or 'n'"
|
| 324 |
+
)
|
| 325 |
+
rank_lut = sqg_cheb_normal_rank_e4m3_bytes()
|
| 326 |
+
if bits == 2 and rate_axis == "k":
|
| 327 |
+
return sqg_k2_eight_stratum_e4m3_bytes_from_rank_lut(
|
| 328 |
+
rank_lut, history_bit=4, device=device
|
| 329 |
+
)
|
| 330 |
+
return sqg_e4m3_bytes_from_rank_lut(
|
| 331 |
+
bits, rank_lut, device=device
|
| 332 |
+
).contiguous()
|
| 333 |
+
raise ValueError(f"unsupported SQG codebook: {codebook!r}")
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def sqg_codebook(
|
| 337 |
+
bits: int,
|
| 338 |
+
codebook: str,
|
| 339 |
+
*,
|
| 340 |
+
rate_axis: str | None = None,
|
| 341 |
+
device: torch.device | str = "cpu",
|
| 342 |
+
dtype: torch.dtype = torch.float16,
|
| 343 |
+
) -> torch.Tensor:
|
| 344 |
+
"""Return a named SQG codebook widened from its exact E4M3 labels."""
|
| 345 |
+
|
| 346 |
+
if not dtype.is_floating_point:
|
| 347 |
+
raise TypeError("SQG codebook dtype must be floating point")
|
| 348 |
+
return (
|
| 349 |
+
sqg_codebook_bytes(bits, codebook, rate_axis=rate_axis, device=device)
|
| 350 |
+
.view(torch.float8_e4m3fn)
|
| 351 |
+
.to(dtype=dtype)
|
| 352 |
+
.contiguous()
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
@lru_cache(maxsize=None)
|
| 357 |
+
def sqg_k2_eight_stratum_rank_permutation(
|
| 358 |
+
history_bit: int = 2,
|
| 359 |
+
) -> torch.Tensor:
|
| 360 |
+
"""Return a state-conditioned eight-stratum labelling for a K2 trellis.
|
| 361 |
+
|
| 362 |
+
The physical graph still appends two branch bits and retains fourteen
|
| 363 |
+
state bits. Numerical labelling reinterprets the newest retained history
|
| 364 |
+
bit plus the two new branch bits as a virtual three-bit stratum selector;
|
| 365 |
+
the remaining thirteen bits select phase. A K2 state therefore exposes
|
| 366 |
+
four of eight strata, while its retained history chooses which four.
|
| 367 |
+
Globally this is the same bijective rank law used by K3. ``history_bit``
|
| 368 |
+
selects which retained physical codeword bit becomes the most-significant
|
| 369 |
+
virtual stratum bit. Bit 2 is the newest retained bit and reproduces the
|
| 370 |
+
exact K3 transition-label function over the unmodified codeword.
|
| 371 |
+
"""
|
| 372 |
+
|
| 373 |
+
if isinstance(history_bit, bool) or not isinstance(history_bit, int):
|
| 374 |
+
raise TypeError("K2 eight-stratum history bit must be an integer")
|
| 375 |
+
if not 2 <= history_bit < 16:
|
| 376 |
+
raise ValueError("K2 eight-stratum history bit must be in [2, 15]")
|
| 377 |
+
k3_ranks = sqg_rank_permutation(3)
|
| 378 |
+
if history_bit == 2:
|
| 379 |
+
return k3_ranks
|
| 380 |
+
|
| 381 |
+
transitions = torch.arange(_TRANSITIONS, dtype=torch.int64)
|
| 382 |
+
bit_2 = (transitions >> 2) & 1
|
| 383 |
+
bit_h = (transitions >> history_bit) & 1
|
| 384 |
+
swap = bit_2 ^ bit_h
|
| 385 |
+
permuted = transitions ^ (swap << 2) ^ (swap << history_bit)
|
| 386 |
+
return k3_ranks.index_select(0, permuted).contiguous()
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
def sqg_k2_eight_stratum_e4m3_bytes_from_rank_lut(
|
| 390 |
+
rank_lut: torch.Tensor,
|
| 391 |
+
*,
|
| 392 |
+
history_bit: int = 2,
|
| 393 |
+
device: torch.device | str = "cpu",
|
| 394 |
+
) -> torch.Tensor:
|
| 395 |
+
"""Map one shared rank staircase onto state-conditioned K2 octiles."""
|
| 396 |
+
|
| 397 |
+
if rank_lut.dtype != torch.uint8:
|
| 398 |
+
raise TypeError("SQG rank LUT must contain raw uint8 E4M3 labels")
|
| 399 |
+
if rank_lut.ndim != 1 or rank_lut.numel() != _TRANSITIONS:
|
| 400 |
+
raise ValueError("SQG rank LUT must contain exactly 65,536 labels")
|
| 401 |
+
rank_lut = rank_lut.detach().to(device="cpu").contiguous()
|
| 402 |
+
values = rank_lut.view(torch.float8_e4m3fn).float()
|
| 403 |
+
if not bool(torch.isfinite(values).all()):
|
| 404 |
+
raise ValueError("SQG rank LUT must contain only finite E4M3 labels")
|
| 405 |
+
return rank_lut.index_select(
|
| 406 |
+
0, sqg_k2_eight_stratum_rank_permutation(history_bit)
|
| 407 |
+
).to(device=device).contiguous()
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
def sqg_rank_permutation(bits: int) -> torch.Tensor:
|
| 411 |
+
"""Return the pre-projection quantile rank for structural validation."""
|
| 412 |
+
|
| 413 |
+
_validate(bits, "normal")
|
| 414 |
+
width = 16 - bits
|
| 415 |
+
branches = 1 << bits
|
| 416 |
+
transitions = torch.arange(_TRANSITIONS, dtype=torch.int64)
|
| 417 |
+
history = transitions >> bits
|
| 418 |
+
branch = transitions & (branches - 1)
|
| 419 |
+
phase = _mix_width(
|
| 420 |
+
history,
|
| 421 |
+
width=width,
|
| 422 |
+
multiplier_a=0x65AF,
|
| 423 |
+
multiplier_b=0x16BF,
|
| 424 |
+
shift_a=6,
|
| 425 |
+
shift_b=4,
|
| 426 |
+
shift_c=5,
|
| 427 |
+
)
|
| 428 |
+
syndrome = _mix_width(
|
| 429 |
+
history ^ 0x5105,
|
| 430 |
+
width=width,
|
| 431 |
+
multiplier_a=0x8693,
|
| 432 |
+
multiplier_b=0x2A21,
|
| 433 |
+
shift_a=2,
|
| 434 |
+
shift_b=4,
|
| 435 |
+
shift_c=4,
|
| 436 |
+
) & (branches - 1)
|
| 437 |
+
stratum = (7 * (_reverse_low_bits(branch, bits) ^ syndrome)) & (branches - 1)
|
| 438 |
+
return ((stratum << width) | phase).contiguous()
|
reproduction/closure/patches/kquant/changed-files/kquant/sqg_quantizer.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Research-only bridge from EXL's production encoder to SQG E4M3 tables."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import hashlib
|
| 6 |
+
import importlib.machinery
|
| 7 |
+
import importlib.util
|
| 8 |
+
import os
|
| 9 |
+
import re
|
| 10 |
+
import sys
|
| 11 |
+
from collections.abc import Mapping
|
| 12 |
+
from functools import lru_cache
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
from torch.utils.cpp_extension import load
|
| 17 |
+
|
| 18 |
+
from kquant.sqg_e4m3 import sqg_e4m3_bytes
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
_EXTENSION_NAME = "kquant_sqg_quantize_ext_v22"
|
| 22 |
+
_EXTENSION_PATH_ENV = "KQUANT_SQG_EXTENSION_PATH"
|
| 23 |
+
_EXTENSION_SHA256_ENV = "KQUANT_SQG_EXTENSION_SHA256"
|
| 24 |
+
_REQUIRE_PREBUILT_ENV = "KQUANT_SQG_REQUIRE_PREBUILT"
|
| 25 |
+
_SHA256_RE = re.compile(r"[0-9a-f]{64}")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _sha256_file(path: Path) -> str:
|
| 29 |
+
digest = hashlib.sha256()
|
| 30 |
+
with path.open("rb") as handle:
|
| 31 |
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
| 32 |
+
digest.update(chunk)
|
| 33 |
+
return digest.hexdigest()
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _import_prebuilt_extension(path: Path):
|
| 37 |
+
"""Import one exact pybind extension without invoking the JIT builder."""
|
| 38 |
+
|
| 39 |
+
spec = importlib.util.spec_from_file_location(_EXTENSION_NAME, path)
|
| 40 |
+
if spec is None or spec.loader is None:
|
| 41 |
+
raise ImportError(f"cannot construct an import spec for SQG extension: {path}")
|
| 42 |
+
previous = sys.modules.get(_EXTENSION_NAME)
|
| 43 |
+
module = importlib.util.module_from_spec(spec)
|
| 44 |
+
sys.modules[_EXTENSION_NAME] = module
|
| 45 |
+
try:
|
| 46 |
+
spec.loader.exec_module(module)
|
| 47 |
+
except BaseException:
|
| 48 |
+
if previous is None:
|
| 49 |
+
sys.modules.pop(_EXTENSION_NAME, None)
|
| 50 |
+
else:
|
| 51 |
+
sys.modules[_EXTENSION_NAME] = previous
|
| 52 |
+
raise
|
| 53 |
+
return module
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _load_prebuilt_extension(raw_path: str, expected_sha256: str):
|
| 57 |
+
candidate = Path(raw_path)
|
| 58 |
+
if not candidate.is_absolute():
|
| 59 |
+
raise ValueError(f"{_EXTENSION_PATH_ENV} must be an absolute path")
|
| 60 |
+
if candidate.is_symlink():
|
| 61 |
+
raise ValueError(f"{_EXTENSION_PATH_ENV} may not name a symlink")
|
| 62 |
+
try:
|
| 63 |
+
path = candidate.resolve(strict=True)
|
| 64 |
+
except FileNotFoundError as exc:
|
| 65 |
+
raise FileNotFoundError(
|
| 66 |
+
f"sealed SQG extension does not exist: {candidate}"
|
| 67 |
+
) from exc
|
| 68 |
+
if not path.is_file():
|
| 69 |
+
raise ValueError(f"sealed SQG extension is not a regular file: {path}")
|
| 70 |
+
allowed_names = {
|
| 71 |
+
f"{_EXTENSION_NAME}{suffix}"
|
| 72 |
+
for suffix in importlib.machinery.EXTENSION_SUFFIXES
|
| 73 |
+
}
|
| 74 |
+
if path.name not in allowed_names:
|
| 75 |
+
raise ValueError(
|
| 76 |
+
"sealed SQG extension filename must identify "
|
| 77 |
+
f"{_EXTENSION_NAME}: {path.name}"
|
| 78 |
+
)
|
| 79 |
+
if _SHA256_RE.fullmatch(expected_sha256) is None:
|
| 80 |
+
raise ValueError(f"{_EXTENSION_SHA256_ENV} must be 64 lowercase hex digits")
|
| 81 |
+
observed_sha256 = _sha256_file(path)
|
| 82 |
+
if observed_sha256 != expected_sha256:
|
| 83 |
+
raise RuntimeError(
|
| 84 |
+
"sealed SQG extension SHA256 mismatch: "
|
| 85 |
+
f"{observed_sha256} != {expected_sha256}"
|
| 86 |
+
)
|
| 87 |
+
module = _import_prebuilt_extension(path)
|
| 88 |
+
module_path = Path(str(getattr(module, "__file__", ""))).resolve()
|
| 89 |
+
if module_path != path:
|
| 90 |
+
raise RuntimeError(
|
| 91 |
+
f"loaded SQG extension origin differs: {module_path} != {path}"
|
| 92 |
+
)
|
| 93 |
+
if not callable(getattr(module, "quantize_tiles_procedural", None)) or not callable(
|
| 94 |
+
getattr(module, "quantize_tiles_sqg", None)
|
| 95 |
+
):
|
| 96 |
+
raise RuntimeError(
|
| 97 |
+
"sealed SQG extension lacks required quantization entry points"
|
| 98 |
+
)
|
| 99 |
+
if _sha256_file(path) != expected_sha256:
|
| 100 |
+
raise RuntimeError("sealed SQG extension changed while it was being imported")
|
| 101 |
+
return module
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
@lru_cache(maxsize=1)
|
| 105 |
+
def _extension():
|
| 106 |
+
require_value = os.environ.get(_REQUIRE_PREBUILT_ENV, "")
|
| 107 |
+
if require_value not in ("", "0", "1"):
|
| 108 |
+
raise ValueError(f"{_REQUIRE_PREBUILT_ENV} must be unset, 0, or 1")
|
| 109 |
+
raw_path = os.environ.get(_EXTENSION_PATH_ENV, "")
|
| 110 |
+
expected_sha256 = os.environ.get(_EXTENSION_SHA256_ENV, "")
|
| 111 |
+
if require_value == "1" or raw_path or expected_sha256:
|
| 112 |
+
if not raw_path or not expected_sha256:
|
| 113 |
+
raise RuntimeError(
|
| 114 |
+
f"{_EXTENSION_PATH_ENV} and {_EXTENSION_SHA256_ENV} are both required "
|
| 115 |
+
"for sealed prebuilt SQG loading"
|
| 116 |
+
)
|
| 117 |
+
return _load_prebuilt_extension(raw_path, expected_sha256)
|
| 118 |
+
|
| 119 |
+
project = Path(__file__).resolve().parents[1]
|
| 120 |
+
return load(
|
| 121 |
+
name=_EXTENSION_NAME,
|
| 122 |
+
sources=[
|
| 123 |
+
str(project / "kquant/csrc/sqg_quantize.cpp"),
|
| 124 |
+
str(project / "kquant/csrc/sqg_quantize.cu"),
|
| 125 |
+
],
|
| 126 |
+
extra_include_paths=[str(project / "kquant/csrc")],
|
| 127 |
+
extra_cflags=["-O3"],
|
| 128 |
+
extra_cuda_cflags=[
|
| 129 |
+
"-O3",
|
| 130 |
+
"--use_fast_math",
|
| 131 |
+
"-lineinfo",
|
| 132 |
+
"-Xcudafe",
|
| 133 |
+
"--diag_suppress=177",
|
| 134 |
+
"-Xcudafe",
|
| 135 |
+
"--diag_suppress=20012",
|
| 136 |
+
],
|
| 137 |
+
verbose=False,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
@lru_cache(maxsize=None)
|
| 142 |
+
def _sqg_temp_buffers(device: torch.device, bits: int):
|
| 143 |
+
"""Allocate the packed traceback buffers consumed by kquant's kernel."""
|
| 144 |
+
|
| 145 |
+
multiprocessors = torch.cuda.get_device_properties(device).multi_processor_count
|
| 146 |
+
edges = 65536 >> bits
|
| 147 |
+
decisions = edges // 4 if bits == 2 else (edges // 2 if bits <= 4 else edges)
|
| 148 |
+
free_bytes, _ = torch.cuda.mem_get_info(device)
|
| 149 |
+
decision_bytes_per_tile = 256 * decisions
|
| 150 |
+
affordable = max(256, int(free_bytes * 0.5) // decision_bytes_per_tile)
|
| 151 |
+
max_batch = min(max(256, 3 * multiprocessors), affordable)
|
| 152 |
+
costs = torch.zeros(
|
| 153 |
+
(max_batch, 2, edges), dtype=torch.float16, device=device
|
| 154 |
+
)
|
| 155 |
+
traceback = torch.empty(
|
| 156 |
+
(max_batch, 256, decisions), dtype=torch.uint8, device=device
|
| 157 |
+
)
|
| 158 |
+
return costs, traceback
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def install_sqg_quantizer(quantizer_module) -> None:
|
| 162 |
+
"""Teach a loaded EXL encoder module to consume ``sqg_e4m3_lut``.
|
| 163 |
+
|
| 164 |
+
The patch is process-local. SQG and explicit MCG/MUL1 controls use
|
| 165 |
+
kquant's CUDA extension with one Viterbi/tail-biting implementation. A
|
| 166 |
+
``None`` entry in a rate-specific mapping explicitly selects MCG,
|
| 167 |
+
permitting controlled hybrid rate-curve studies. Calls without any
|
| 168 |
+
kquant codebook argument retain the unmodified upstream EXL behavior.
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
if getattr(quantizer_module, "_kquant_sqg_installed", False):
|
| 172 |
+
return
|
| 173 |
+
original = quantizer_module.quantize_tiles
|
| 174 |
+
|
| 175 |
+
device_luts: dict[tuple[str, int, str], torch.Tensor] = {}
|
| 176 |
+
transposed_sqg_luts: dict[
|
| 177 |
+
tuple[str, int, int], tuple[torch.Tensor, torch.Tensor]
|
| 178 |
+
] = {}
|
| 179 |
+
|
| 180 |
+
def quantize_tiles(tiles: torch.Tensor, quant_args: dict):
|
| 181 |
+
codebook = quant_args.get("sqg_e4m3_lut")
|
| 182 |
+
rate_codebooks = quant_args.get("sqg_e4m3_luts_by_bits")
|
| 183 |
+
mode = quant_args.get("sqg_e4m3_mode")
|
| 184 |
+
if codebook is None and rate_codebooks is None and mode is None:
|
| 185 |
+
return original(tiles, quant_args)
|
| 186 |
+
if len(quant_args["devices"]) != 1:
|
| 187 |
+
raise ValueError("the SQG validation hook currently requires one CUDA device")
|
| 188 |
+
tiles = tiles.contiguous()
|
| 189 |
+
if tiles.dtype != torch.float32 or tiles.ndim != 2 or tiles.shape[1] != 256:
|
| 190 |
+
raise ValueError("SQG tiles must be contiguous FP32 [N, 256]")
|
| 191 |
+
bits = int(quant_args["K"])
|
| 192 |
+
if rate_codebooks is not None:
|
| 193 |
+
if codebook is not None or mode is not None:
|
| 194 |
+
raise ValueError(
|
| 195 |
+
"rate-specific SQG LUTs cannot be combined with another SQG law"
|
| 196 |
+
)
|
| 197 |
+
if not isinstance(rate_codebooks, Mapping):
|
| 198 |
+
raise TypeError("sqg_e4m3_luts_by_bits must be a mapping")
|
| 199 |
+
if set(rate_codebooks) - set(range(2, 7)):
|
| 200 |
+
raise ValueError("rate-specific SQG LUT keys must be K2 through K6")
|
| 201 |
+
try:
|
| 202 |
+
codebook = rate_codebooks[bits]
|
| 203 |
+
except KeyError as exc:
|
| 204 |
+
raise ValueError(f"missing rate-specific SQG K{bits} LUT") from exc
|
| 205 |
+
if codebook is None:
|
| 206 |
+
if bits not in (2, 3, 4):
|
| 207 |
+
raise ValueError(
|
| 208 |
+
"non-SQG procedural controls remain limited to K2 through K4"
|
| 209 |
+
)
|
| 210 |
+
output = torch.empty_like(tiles)
|
| 211 |
+
indices = torch.empty_like(tiles, dtype=torch.int16)
|
| 212 |
+
costs, edges = _sqg_temp_buffers(tiles.device, bits)
|
| 213 |
+
_extension().quantize_tiles_procedural(
|
| 214 |
+
tiles,
|
| 215 |
+
output,
|
| 216 |
+
indices,
|
| 217 |
+
costs,
|
| 218 |
+
edges,
|
| 219 |
+
bits,
|
| 220 |
+
1,
|
| 221 |
+
int(quant_args.get("tailbite_context", 128)),
|
| 222 |
+
)
|
| 223 |
+
return output, indices
|
| 224 |
+
elif codebook is None:
|
| 225 |
+
if mode != "normal":
|
| 226 |
+
raise ValueError("the supported R44 mode is 'normal'")
|
| 227 |
+
key = (str(tiles.device), bits, mode)
|
| 228 |
+
codebook = device_luts.get(key)
|
| 229 |
+
if codebook is None:
|
| 230 |
+
codebook = sqg_e4m3_bytes(bits, mode, device=tiles.device)
|
| 231 |
+
device_luts[key] = codebook
|
| 232 |
+
output = torch.empty_like(tiles)
|
| 233 |
+
indices = torch.empty_like(tiles, dtype=torch.int16)
|
| 234 |
+
costs, edges = _sqg_temp_buffers(tiles.device, bits)
|
| 235 |
+
lut = codebook.to(device=tiles.device, dtype=torch.uint8).contiguous()
|
| 236 |
+
if bits in (2, 3, 4):
|
| 237 |
+
source_key = codebook.data_ptr() if codebook.is_cuda else id(codebook)
|
| 238 |
+
cache_key = (str(tiles.device), bits, source_key)
|
| 239 |
+
cached = transposed_sqg_luts.get(cache_key)
|
| 240 |
+
if cached is None or cached[0] is not codebook:
|
| 241 |
+
# [predecessor, out-edge-pair, pair-byte] ->
|
| 242 |
+
# [out-edge-pair, predecessor, pair-byte]. Each CUDA thread
|
| 243 |
+
# can then fetch all predecessor labels in one uint2 (K2),
|
| 244 |
+
# one uint4 (K3), or two uint4s (K4), rather than issuing one
|
| 245 |
+
# gather per predecessor.
|
| 246 |
+
predecessors = 1 << bits
|
| 247 |
+
out_edge_pairs = (65536 >> bits) // 2
|
| 248 |
+
lut = (
|
| 249 |
+
lut.reshape(predecessors, out_edge_pairs, 2)
|
| 250 |
+
.permute(1, 0, 2)
|
| 251 |
+
.contiguous()
|
| 252 |
+
.reshape(-1)
|
| 253 |
+
)
|
| 254 |
+
transposed_sqg_luts[cache_key] = (codebook, lut)
|
| 255 |
+
else:
|
| 256 |
+
lut = cached[1]
|
| 257 |
+
_extension().quantize_tiles_sqg(
|
| 258 |
+
tiles,
|
| 259 |
+
output,
|
| 260 |
+
indices,
|
| 261 |
+
costs,
|
| 262 |
+
edges,
|
| 263 |
+
lut,
|
| 264 |
+
bits,
|
| 265 |
+
int(quant_args.get("tailbite_context", 128)),
|
| 266 |
+
)
|
| 267 |
+
return output, indices
|
| 268 |
+
|
| 269 |
+
quantizer_module.quantize_tiles = quantize_tiles
|
| 270 |
+
quantizer_module._kquant_sqg_installed = True
|
reproduction/closure/patches/kquant/changed-files/tests/test_qsrt.py
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import replace
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
|
| 9 |
+
from kquant.exl3_reference import reconstruct_trellis_states
|
| 10 |
+
from kquant.qsrt import (
|
| 11 |
+
EXPERT_TRELLIS_BYTES,
|
| 12 |
+
EXPERTS_PER_LAYER,
|
| 13 |
+
FORMAT_SECTION_BYTES,
|
| 14 |
+
FORMAT_TABLE_BYTES,
|
| 15 |
+
HOT_METADATA_BYTES,
|
| 16 |
+
INTERMEDIATE_CHANNELS,
|
| 17 |
+
LAYER_HEADER_BYTES,
|
| 18 |
+
MATRIX_TRELLIS_BYTES,
|
| 19 |
+
PAIRS_PER_EXPERT,
|
| 20 |
+
PAIR_BYTES,
|
| 21 |
+
PHASE1_H13_EXPERT_LOCAL_ALPHA,
|
| 22 |
+
PHASE1_H2_EXPERT_LOCAL_ALPHA,
|
| 23 |
+
PHASE1_H2_LOCAL_BASIS,
|
| 24 |
+
PHASE1_H2_SHRINKAGE_POLICY,
|
| 25 |
+
PHASE1_MODE_CANDIDATES,
|
| 26 |
+
PHASE1_MODE_IDS,
|
| 27 |
+
PHASE1_SHARED_RATE_MODE,
|
| 28 |
+
RECORDS_PER_EXPERT,
|
| 29 |
+
RATE_TRANSFER_MODES,
|
| 30 |
+
R0,
|
| 31 |
+
R1,
|
| 32 |
+
R2,
|
| 33 |
+
SCHEMA,
|
| 34 |
+
SHARED_SCALE_BYTES,
|
| 35 |
+
SHARED_SCALE_SECTION_BYTES,
|
| 36 |
+
STORAGE_ALIGNMENT,
|
| 37 |
+
ExpertFormatSpec,
|
| 38 |
+
ModeSpec,
|
| 39 |
+
PackedQSRTTrellis,
|
| 40 |
+
QSRTTrellisDescriptor,
|
| 41 |
+
coupled_intermediate_permutation,
|
| 42 |
+
decode_qsrt_exl3_weight,
|
| 43 |
+
expand_group_order,
|
| 44 |
+
expand_qsrt_hot_metadata,
|
| 45 |
+
expand_record_order,
|
| 46 |
+
importance_group_order,
|
| 47 |
+
matrix_rate_axis,
|
| 48 |
+
mode_from_context_bits,
|
| 49 |
+
pack_qsrt_format_table,
|
| 50 |
+
pack_qsrt_format_section,
|
| 51 |
+
pack_qsrt_trellis,
|
| 52 |
+
pack_qsrt_shared_scale_section,
|
| 53 |
+
pack_trellis_edges,
|
| 54 |
+
rate_transfer_record_order,
|
| 55 |
+
record_repack_order,
|
| 56 |
+
relabel_block_contexts_for_record_order,
|
| 57 |
+
repack_encoded_records,
|
| 58 |
+
repack_rate_axis,
|
| 59 |
+
pair_bits,
|
| 60 |
+
pair_kinds,
|
| 61 |
+
logical_pair_for_slot,
|
| 62 |
+
physical_pair_slot,
|
| 63 |
+
record_bits,
|
| 64 |
+
record_contexts,
|
| 65 |
+
storage_group_order,
|
| 66 |
+
unpack_qsrt_trellis_edges,
|
| 67 |
+
unpack_qsrt_format_section,
|
| 68 |
+
unpack_qsrt_shared_scale_section,
|
| 69 |
+
unpack_qsrt_format_table,
|
| 70 |
+
unpack_qsrt_trellis_states,
|
| 71 |
+
unpack_trellis_edges,
|
| 72 |
+
unpack_trellis_states,
|
| 73 |
+
)
|
| 74 |
+
from kquant.tp_simulator import situ
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def test_mode_schedules_are_fixed_rate_pairs() -> None:
|
| 78 |
+
assert pair_kinds(R0) == ("P33",) * PAIRS_PER_EXPERT
|
| 79 |
+
assert pair_kinds(R1) == ("P24",) + ("P33",) * 11
|
| 80 |
+
assert pair_kinds(R2) == ("P24",) * 2 + ("P33",) * 10
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def test_pair_rotation_is_a_per_expert_bijection() -> None:
|
| 84 |
+
for layer, expert in ((1, 0), (1, 17), (40, 285), (92, 895)):
|
| 85 |
+
physical = [
|
| 86 |
+
physical_pair_slot(layer, expert, logical_pair)
|
| 87 |
+
for logical_pair in range(PAIRS_PER_EXPERT)
|
| 88 |
+
]
|
| 89 |
+
assert sorted(physical) == list(range(PAIRS_PER_EXPERT))
|
| 90 |
+
assert [
|
| 91 |
+
logical_pair_for_slot(layer, expert, rank)
|
| 92 |
+
for rank in physical
|
| 93 |
+
] == list(range(PAIRS_PER_EXPERT))
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def test_pair_rotation_balances_p24_ownership_across_experts() -> None:
|
| 97 |
+
counts = [0] * PAIRS_PER_EXPERT
|
| 98 |
+
r = 2
|
| 99 |
+
for expert in range(EXPERTS_PER_LAYER):
|
| 100 |
+
for rank in range(PAIRS_PER_EXPERT):
|
| 101 |
+
counts[rank] += logical_pair_for_slot(24, expert, rank) < r
|
| 102 |
+
|
| 103 |
+
assert max(counts) - min(counts) <= 1
|
| 104 |
+
for r, mode in enumerate(RATE_TRANSFER_MODES):
|
| 105 |
+
assert mode.mode_id == r
|
| 106 |
+
assert mode.name == f"R{r}"
|
| 107 |
+
assert len(record_bits(mode)) == RECORDS_PER_EXPERT
|
| 108 |
+
assert len(pair_bits(mode)) == PAIRS_PER_EXPERT
|
| 109 |
+
assert all(sum(pair) == 6 for pair in pair_bits(mode))
|
| 110 |
+
assert sum(record_bits(mode)) / RECORDS_PER_EXPERT == 3
|
| 111 |
+
assert pair_kinds(mode) == ("P24",) * r + ("P33",) * (12 - r)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def test_qsrt_encoder_policy_is_frozen_to_r012() -> None:
|
| 115 |
+
assert PHASE1_MODE_IDS == (0, 1, 2)
|
| 116 |
+
assert tuple(mode.mode_id for mode in PHASE1_MODE_CANDIDATES) == PHASE1_MODE_IDS
|
| 117 |
+
assert not PHASE1_SHARED_RATE_MODE
|
| 118 |
+
assert PHASE1_H13_EXPERT_LOCAL_ALPHA == 0.0
|
| 119 |
+
assert PHASE1_H2_EXPERT_LOCAL_ALPHA == 0.75
|
| 120 |
+
assert PHASE1_H2_LOCAL_BASIS == "decoded_candidate_post_situ"
|
| 121 |
+
assert PHASE1_H2_SHRINKAGE_POLICY == "weighted_oas_scaled_identity"
|
| 122 |
+
assert len(RATE_TRANSFER_MODES) == 3
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def test_context_allocations_map_to_the_rate_transfer_ladder() -> None:
|
| 126 |
+
assert mode_from_context_bits((3, 3, 3, 3)) is R0
|
| 127 |
+
assert mode_from_context_bits((2,) + (3,) * 22 + (4,)) is R1
|
| 128 |
+
assert mode_from_context_bits((2, 2) + (3,) * 20 + (4, 4)) is R2
|
| 129 |
+
with pytest.raises(ValueError, match="monotone"):
|
| 130 |
+
mode_from_context_bits((2, 4) + (3,) * 20 + (2, 4))
|
| 131 |
+
with pytest.raises(ValueError, match="rate-transfer count 6"):
|
| 132 |
+
ModeSpec(1, "bad", (2, 3, 3, 4))
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def test_candidate_descriptor_contains_no_tensor_parallel_geometry() -> None:
|
| 136 |
+
descriptor = QSRTTrellisDescriptor(
|
| 137 |
+
mode_id=R2.mode_id,
|
| 138 |
+
rate_axis="k",
|
| 139 |
+
k_tiles=192,
|
| 140 |
+
n_tiles=224,
|
| 141 |
+
)
|
| 142 |
+
assert "tp_size" not in descriptor.to_manifest()
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def test_provisional_format_table_supports_separate_r13_r2() -> None:
|
| 146 |
+
formats = ["X4T", R0, R1, (1, 2)] * (EXPERTS_PER_LAYER // 4)
|
| 147 |
+
packed = pack_qsrt_format_table(formats)
|
| 148 |
+
|
| 149 |
+
assert packed.dtype == torch.uint8
|
| 150 |
+
assert packed.shape == (FORMAT_TABLE_BYTES,)
|
| 151 |
+
assert packed[:4].tolist() == [0xFF, 0x00, 0x11, 0x12]
|
| 152 |
+
assert unpack_qsrt_format_table(packed) == tuple(
|
| 153 |
+
["X4T", "R0", "R1", "R1/R2"] * (EXPERTS_PER_LAYER // 4)
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
malformed = packed.clone()
|
| 157 |
+
malformed[0] = 0xD0
|
| 158 |
+
with pytest.raises(ValueError, match="invalid rate code"):
|
| 159 |
+
unpack_qsrt_format_table(malformed)
|
| 160 |
+
assert ExpertFormatSpec.compressed(1, 2).code == 0x12
|
| 161 |
+
assert ExpertFormatSpec.from_code(0x12).name == "R1/R2"
|
| 162 |
+
assert ExpertFormatSpec.from_code(0xFF).is_x4t
|
| 163 |
+
with pytest.raises(ValueError, match="896 experts"):
|
| 164 |
+
pack_qsrt_format_table([R0])
|
| 165 |
+
with pytest.raises(TypeError, match="torch.uint8"):
|
| 166 |
+
unpack_qsrt_format_table(packed.int())
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def test_format_section_padding_and_hot_slot_expansion_are_canonical() -> None:
|
| 170 |
+
formats = ["X4T", R0, R1, (1, 2)] * (EXPERTS_PER_LAYER // 4)
|
| 171 |
+
section = pack_qsrt_format_section(formats)
|
| 172 |
+
codes, slots = expand_qsrt_hot_metadata(formats)
|
| 173 |
+
|
| 174 |
+
assert section.shape == (FORMAT_SECTION_BYTES,)
|
| 175 |
+
assert torch.count_nonzero(section[FORMAT_TABLE_BYTES:]) == 0
|
| 176 |
+
assert unpack_qsrt_format_section(section) == tuple(
|
| 177 |
+
["X4T", "R0", "R1", "R1/R2"] * (EXPERTS_PER_LAYER // 4)
|
| 178 |
+
)
|
| 179 |
+
assert codes[:8].tolist() == [255, 0, 17, 18, 255, 0, 17, 18]
|
| 180 |
+
assert slots[:8].tolist() == [0, 0, 1, 2, 1, 3, 4, 5]
|
| 181 |
+
assert codes.numel() + slots.numel() * slots.element_size() == HOT_METADATA_BYTES
|
| 182 |
+
|
| 183 |
+
malformed = section.clone()
|
| 184 |
+
malformed[-1] = 1
|
| 185 |
+
with pytest.raises(ValueError, match="nonzero alignment padding"):
|
| 186 |
+
unpack_qsrt_format_section(malformed)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def test_shared_scale_section_round_trips_exactly() -> None:
|
| 190 |
+
shared = tuple(
|
| 191 |
+
torch.linspace(-1 + index, 1 + index, 3584, dtype=torch.float16)
|
| 192 |
+
for index in range(3)
|
| 193 |
+
)
|
| 194 |
+
section = pack_qsrt_shared_scale_section(*shared)
|
| 195 |
+
actual = unpack_qsrt_shared_scale_section(section)
|
| 196 |
+
assert all(
|
| 197 |
+
torch.equal(expected, observed)
|
| 198 |
+
for expected, observed in zip(shared, actual, strict=True)
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
malformed = section.clone()
|
| 202 |
+
malformed[-1] = 1
|
| 203 |
+
with pytest.raises(ValueError, match="nonzero alignment padding"):
|
| 204 |
+
unpack_qsrt_shared_scale_section(malformed)
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def test_matrix_rate_axis_tracks_shared_intermediate_dimension() -> None:
|
| 208 |
+
assert matrix_rate_axis("w1") == "n"
|
| 209 |
+
assert matrix_rate_axis("w3") == "n"
|
| 210 |
+
assert matrix_rate_axis("w2") == "k"
|
| 211 |
+
with pytest.raises(ValueError, match="unknown expert matrix"):
|
| 212 |
+
matrix_rate_axis("router")
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
@pytest.mark.parametrize(
|
| 216 |
+
("bits", "expected_prefix"),
|
| 217 |
+
[
|
| 218 |
+
(2, [6939] * 6),
|
| 219 |
+
(3, [30469, 1337, 1337, 14711, 14711, 30469]),
|
| 220 |
+
(4, [17767, 291, -12817, -30293, 17767, 291]),
|
| 221 |
+
(5, [12820, 68, 21686, -14526, -31643, 13775]),
|
| 222 |
+
(6, [-31984, 16, 8338, 20871, -11377, -29904]),
|
| 223 |
+
],
|
| 224 |
+
)
|
| 225 |
+
def test_trellis_edge_pack_matches_native_word_order(
|
| 226 |
+
bits: int, expected_prefix: list[int]
|
| 227 |
+
) -> None:
|
| 228 |
+
indices = (torch.arange(256, dtype=torch.int16) % (1 << bits))[None]
|
| 229 |
+
packed = pack_trellis_edges(indices, bits)
|
| 230 |
+
|
| 231 |
+
assert packed.shape == (1, 16 * bits)
|
| 232 |
+
assert packed[0, : len(expected_prefix)].tolist() == expected_prefix
|
| 233 |
+
torch.testing.assert_close(unpack_trellis_edges(packed, bits), indices)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 237 |
+
def test_trellis_edge_pack_round_trips_only_stored_low_bits(bits: int) -> None:
|
| 238 |
+
generator = torch.Generator().manual_seed(100 + bits)
|
| 239 |
+
indices = torch.randint(-32768, 32768, (7, 256), generator=generator)
|
| 240 |
+
packed = pack_trellis_edges(indices, bits)
|
| 241 |
+
unpacked = unpack_trellis_edges(packed, bits)
|
| 242 |
+
|
| 243 |
+
torch.testing.assert_close(unpacked.int(), indices.int() & ((1 << bits) - 1))
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 247 |
+
def test_cyclic_trellis_states_round_trip_exactly(bits: int) -> None:
|
| 248 |
+
generator = torch.Generator().manual_seed(150 + bits)
|
| 249 |
+
edges = torch.randint(0, 1 << bits, (5, 256), generator=generator)
|
| 250 |
+
states = reconstruct_trellis_states(edges, bits)
|
| 251 |
+
packed = pack_trellis_edges(states, bits)
|
| 252 |
+
|
| 253 |
+
torch.testing.assert_close(unpack_trellis_states(packed, bits), states)
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 257 |
+
def test_cyclic_trellis_state_closed_form_matches_scalar_recurrence(bits: int) -> None:
|
| 258 |
+
generator = torch.Generator().manual_seed(175 + bits)
|
| 259 |
+
edges = torch.randint(0, 1 << bits, (3, 5, 256), generator=generator)
|
| 260 |
+
|
| 261 |
+
state = torch.zeros(edges.shape[:-1], dtype=torch.int64)
|
| 262 |
+
for edge in edges[..., -((16 + bits - 1) // bits) :].unbind(dim=-1):
|
| 263 |
+
state = ((state << bits) | edge) & 0xFFFF
|
| 264 |
+
expected = []
|
| 265 |
+
for edge in edges.unbind(dim=-1):
|
| 266 |
+
state = ((state << bits) | edge) & 0xFFFF
|
| 267 |
+
expected.append(state)
|
| 268 |
+
expected_states = torch.stack(expected, dim=-1).to(torch.int16)
|
| 269 |
+
|
| 270 |
+
assert torch.equal(reconstruct_trellis_states(edges, bits), expected_states)
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def _expected_edge_symbols(
|
| 274 |
+
encoded: torch.Tensor, record_bits: tuple[int, ...], rate_axis: str
|
| 275 |
+
) -> torch.Tensor:
|
| 276 |
+
tile_bits = torch.tensor(record_bits).repeat_interleave(8)
|
| 277 |
+
masks = (1 << tile_bits) - 1
|
| 278 |
+
if rate_axis == "k":
|
| 279 |
+
masks = masks[:, None, None]
|
| 280 |
+
else:
|
| 281 |
+
masks = masks[None, :, None]
|
| 282 |
+
return (encoded.to(torch.int64) & masks).to(torch.int16)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def _legal_qsrt_states(
|
| 286 |
+
shape: tuple[int, int, int], record_bits: tuple[int, ...], rate_axis: str, seed: int
|
| 287 |
+
) -> torch.Tensor:
|
| 288 |
+
generator = torch.Generator().manual_seed(seed)
|
| 289 |
+
edges = torch.empty(shape, dtype=torch.int16)
|
| 290 |
+
states = torch.empty_like(edges)
|
| 291 |
+
for record, bits in enumerate(record_bits):
|
| 292 |
+
begin = record * 8
|
| 293 |
+
if rate_axis == "k":
|
| 294 |
+
record_edges = torch.randint(
|
| 295 |
+
0, 1 << bits, (8, shape[1], 256), generator=generator
|
| 296 |
+
).to(torch.int16)
|
| 297 |
+
edges[begin : begin + 8] = record_edges
|
| 298 |
+
states[begin : begin + 8] = reconstruct_trellis_states(
|
| 299 |
+
record_edges, bits
|
| 300 |
+
)
|
| 301 |
+
else:
|
| 302 |
+
record_edges = torch.randint(
|
| 303 |
+
0, 1 << bits, (shape[0], 8, 256), generator=generator
|
| 304 |
+
).to(torch.int16)
|
| 305 |
+
edges[:, begin : begin + 8] = record_edges
|
| 306 |
+
states[:, begin : begin + 8] = reconstruct_trellis_states(
|
| 307 |
+
record_edges, bits
|
| 308 |
+
)
|
| 309 |
+
return states
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
@pytest.mark.parametrize(
|
| 313 |
+
("rate_axis", "shape"),
|
| 314 |
+
[("k", (192, 224, 256)), ("n", (224, 192, 256))],
|
| 315 |
+
)
|
| 316 |
+
@pytest.mark.parametrize("mode", [R0, R1, R2])
|
| 317 |
+
def test_pair_payload_closes_on_both_logical_axes(
|
| 318 |
+
rate_axis: str, shape: tuple[int, int, int], mode
|
| 319 |
+
) -> None:
|
| 320 |
+
encoded = _legal_qsrt_states(
|
| 321 |
+
shape, record_bits(mode), rate_axis, 200 + mode.mode_id
|
| 322 |
+
)
|
| 323 |
+
packed = pack_qsrt_trellis(encoded, mode, rate_axis=rate_axis)
|
| 324 |
+
unpacked = unpack_qsrt_trellis_edges(packed)
|
| 325 |
+
|
| 326 |
+
assert packed.descriptor.schema == SCHEMA
|
| 327 |
+
assert packed.payload.numel() == encoded.numel() * 3 // 16
|
| 328 |
+
assert packed.descriptor.payload_bytes == packed.payload.numel() * 2
|
| 329 |
+
assert (
|
| 330 |
+
packed.descriptor.words_per_pair * PAIRS_PER_EXPERT
|
| 331 |
+
== packed.payload.numel()
|
| 332 |
+
)
|
| 333 |
+
assert packed.descriptor.to_manifest()["pair_kinds"] == list(
|
| 334 |
+
pair_kinds(mode)
|
| 335 |
+
)
|
| 336 |
+
torch.testing.assert_close(
|
| 337 |
+
unpacked, _expected_edge_symbols(encoded, record_bits(mode), rate_axis)
|
| 338 |
+
)
|
| 339 |
+
torch.testing.assert_close(unpack_qsrt_trellis_states(packed), encoded)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def test_packer_rejects_noncyclic_states() -> None:
|
| 343 |
+
malformed = torch.zeros((192, 224, 256), dtype=torch.int16)
|
| 344 |
+
malformed[..., 0] = 1
|
| 345 |
+
|
| 346 |
+
with pytest.raises(ValueError, match="closed cyclic trellis path"):
|
| 347 |
+
pack_qsrt_trellis(malformed, R2, rate_axis="k")
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
def test_payload_rejects_wrong_shape_dtype_and_length() -> None:
|
| 351 |
+
encoded = torch.zeros((192, 224, 256), dtype=torch.int16)
|
| 352 |
+
packed = pack_qsrt_trellis(encoded, R2, rate_axis="k")
|
| 353 |
+
suh = torch.ones(3072, dtype=torch.float16)
|
| 354 |
+
svh = torch.ones(3584, dtype=torch.float16)
|
| 355 |
+
|
| 356 |
+
with pytest.raises(ValueError, match="length"):
|
| 357 |
+
PackedQSRTTrellis(packed.descriptor, packed.payload[:-1]).validate()
|
| 358 |
+
with pytest.raises(TypeError, match="torch.int16"):
|
| 359 |
+
PackedQSRTTrellis(packed.descriptor, packed.payload.int()).validate()
|
| 360 |
+
with pytest.raises(ValueError, match="3072"):
|
| 361 |
+
pack_qsrt_trellis(encoded[:184], R2, rate_axis="k")
|
| 362 |
+
with pytest.raises(ValueError, match="rate_axis"):
|
| 363 |
+
replace(packed.descriptor, rate_axis="x")
|
| 364 |
+
with pytest.raises(TypeError, match="suh must use torch.float16"):
|
| 365 |
+
decode_qsrt_exl3_weight(packed, suh.float(), svh)
|
| 366 |
+
with pytest.raises(ValueError, match="exactly 3072"):
|
| 367 |
+
decode_qsrt_exl3_weight(packed, suh[:-1], svh)
|
| 368 |
+
nonfinite = svh.clone()
|
| 369 |
+
nonfinite[0] = torch.inf
|
| 370 |
+
with pytest.raises(ValueError, match="non-finite"):
|
| 371 |
+
decode_qsrt_exl3_weight(packed, suh, nonfinite)
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def test_context_orders_form_homogeneous_records() -> None:
|
| 375 |
+
block_contexts = torch.arange(24).repeat_interleave(32)
|
| 376 |
+
importance = importance_group_order(block_contexts, R2)
|
| 377 |
+
storage = storage_group_order(block_contexts, R2)
|
| 378 |
+
storage_channels = block_contexts.index_select(0, storage).repeat_interleave(4)
|
| 379 |
+
|
| 380 |
+
assert expand_group_order(storage).shape == (INTERMEDIATE_CHANNELS,)
|
| 381 |
+
assert torch.all(storage_channels.reshape(RECORDS_PER_EXPERT, 128).diff(dim=1) == 0)
|
| 382 |
+
assert (
|
| 383 |
+
storage_channels.reshape(RECORDS_PER_EXPERT, 128)[:, 0].tolist()
|
| 384 |
+
== list(record_contexts(R2))
|
| 385 |
+
)
|
| 386 |
+
|
| 387 |
+
order = record_repack_order(importance, storage)
|
| 388 |
+
encoded = (
|
| 389 |
+
torch.arange(RECORDS_PER_EXPERT)
|
| 390 |
+
.repeat_interleave(8 * 256)
|
| 391 |
+
.reshape(192, 1, 256)
|
| 392 |
+
)
|
| 393 |
+
repacked = repack_encoded_records(encoded, order, "k")
|
| 394 |
+
assert repacked.reshape(RECORDS_PER_EXPERT, -1)[:, 0].tolist() == order.tolist()
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
def test_record_repack_rejects_a_split_record() -> None:
|
| 398 |
+
block_contexts = torch.arange(24).repeat_interleave(32)
|
| 399 |
+
source = importance_group_order(block_contexts, R2)
|
| 400 |
+
malformed = source.clone()
|
| 401 |
+
malformed[31], malformed[32] = malformed[32].clone(), malformed[31].clone()
|
| 402 |
+
|
| 403 |
+
with pytest.raises(ValueError, match="splits or changes"):
|
| 404 |
+
record_repack_order(source, malformed)
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def test_arbitrary_shared_record_map_folds_into_common_permutation() -> None:
|
| 408 |
+
# Start from a deliberately non-contiguous semantic partition so the test
|
| 409 |
+
# exercises relabeling rather than relying on identity group order.
|
| 410 |
+
generator = torch.Generator().manual_seed(47)
|
| 411 |
+
base_contexts = torch.arange(RECORDS_PER_EXPERT).repeat_interleave(32)
|
| 412 |
+
base_contexts = base_contexts[torch.randperm(base_contexts.numel(), generator=generator)]
|
| 413 |
+
donors = (7, 2)
|
| 414 |
+
recipients = (19, 11)
|
| 415 |
+
order = rate_transfer_record_order(donors, recipients)
|
| 416 |
+
relabeled = relabel_block_contexts_for_record_order(base_contexts, order)
|
| 417 |
+
importance = importance_group_order(relabeled, R2)
|
| 418 |
+
ordered_semantic_records = base_contexts.index_select(0, importance).reshape(
|
| 419 |
+
RECORDS_PER_EXPERT, 32
|
| 420 |
+
)[:, 0]
|
| 421 |
+
|
| 422 |
+
assert order[:2] == donors
|
| 423 |
+
assert order[-2:] == tuple(reversed(recipients))
|
| 424 |
+
assert ordered_semantic_records.tolist() == list(order)
|
| 425 |
+
|
| 426 |
+
storage = storage_group_order(relabeled, R2)
|
| 427 |
+
physical_records = base_contexts.index_select(0, storage).reshape(
|
| 428 |
+
RECORDS_PER_EXPERT, 32
|
| 429 |
+
)[:, 0]
|
| 430 |
+
assert list(zip(physical_records[0:4:2].tolist(), physical_records[1:4:2].tolist())) == list(
|
| 431 |
+
zip(donors, recipients, strict=True)
|
| 432 |
+
)
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
@pytest.mark.parametrize(
|
| 436 |
+
"donors,recipients,middle,error",
|
| 437 |
+
[
|
| 438 |
+
((1,), (), None, "counts must match"),
|
| 439 |
+
((1,), (1,), None, "unique and disjoint"),
|
| 440 |
+
((24,), (1,), None, "record IDs"),
|
| 441 |
+
((1,), (2,), tuple(range(21)), "middle_records"),
|
| 442 |
+
],
|
| 443 |
+
)
|
| 444 |
+
def test_arbitrary_record_map_rejects_malformed_assignments(
|
| 445 |
+
donors, recipients, middle, error
|
| 446 |
+
) -> None:
|
| 447 |
+
with pytest.raises(ValueError, match=error):
|
| 448 |
+
rate_transfer_record_order(
|
| 449 |
+
donors, recipients, middle_records=middle
|
| 450 |
+
)
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
def test_record_repack_moves_matching_scale_or_weight_axis() -> None:
|
| 454 |
+
order = torch.arange(RECORDS_PER_EXPERT - 1, -1, -1)
|
| 455 |
+
channels = expand_record_order(order)
|
| 456 |
+
vector = torch.arange(INTERMEDIATE_CHANNELS)
|
| 457 |
+
matrix = vector.repeat(3, 1)
|
| 458 |
+
|
| 459 |
+
torch.testing.assert_close(repack_rate_axis(vector, order, 0), vector[channels])
|
| 460 |
+
torch.testing.assert_close(
|
| 461 |
+
repack_rate_axis(matrix, order, 1), matrix.index_select(1, channels)
|
| 462 |
+
)
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
def test_coupled_permutation_preserves_situ_expert_function() -> None:
|
| 466 |
+
generator = torch.Generator().manual_seed(300)
|
| 467 |
+
hidden = 7
|
| 468 |
+
intermediate = 64
|
| 469 |
+
inputs = torch.randn(5, hidden, dtype=torch.float64, generator=generator)
|
| 470 |
+
w1 = torch.randn(intermediate, hidden, dtype=torch.float64, generator=generator)
|
| 471 |
+
w3 = torch.randn(intermediate, hidden, dtype=torch.float64, generator=generator)
|
| 472 |
+
w2 = torch.randn(hidden, intermediate, dtype=torch.float64, generator=generator)
|
| 473 |
+
permutation = torch.randperm(intermediate, generator=generator)
|
| 474 |
+
|
| 475 |
+
reference_middle = situ(F.linear(inputs, w1), F.linear(inputs, w3))
|
| 476 |
+
reference = F.linear(reference_middle, w2)
|
| 477 |
+
permuted_w1, permuted_w3, permuted_w2 = coupled_intermediate_permutation(
|
| 478 |
+
w1, w3, w2, permutation
|
| 479 |
+
)
|
| 480 |
+
permuted_middle = situ(
|
| 481 |
+
F.linear(inputs, permuted_w1), F.linear(inputs, permuted_w3)
|
| 482 |
+
)
|
| 483 |
+
actual = F.linear(permuted_middle, permuted_w2)
|
| 484 |
+
|
| 485 |
+
torch.testing.assert_close(permuted_middle, reference_middle[:, permutation])
|
| 486 |
+
torch.testing.assert_close(actual, reference, atol=1e-12, rtol=1e-12)
|
reproduction/closure/patches/kquant/changed-files/tests/test_sqg_e4m3.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
from kquant.exl3_reference import (
|
| 9 |
+
CODEBOOK_SQG_XOR_CHEB_T12,
|
| 10 |
+
CODEBOOK_SQG_CHEB_NORMAL_E4M3,
|
| 11 |
+
CODEBOOK_SQG_CHEB,
|
| 12 |
+
CODEBOOK_SQG_NORMAL_E4M3,
|
| 13 |
+
decode_qsrt_regularized_weight,
|
| 14 |
+
decode_regularized_weight,
|
| 15 |
+
)
|
| 16 |
+
from kquant.sqg_e4m3 import (
|
| 17 |
+
sqg_xor_cheb_t12_bytes,
|
| 18 |
+
sqg_xor_cheb_t12_rank_lut_bytes,
|
| 19 |
+
sqg_xor_rank_permutation,
|
| 20 |
+
sqg_cheb_normal_e4m3_bytes,
|
| 21 |
+
sqg_cheb_normal_rank_e4m3_bytes,
|
| 22 |
+
sqg_codebook_bytes,
|
| 23 |
+
sqg_e4m3_bytes,
|
| 24 |
+
sqg_e4m3_bytes_from_rank_lut,
|
| 25 |
+
sqg_e4m3_codebook,
|
| 26 |
+
sqg_e4m3_codebook_from_rank_lut,
|
| 27 |
+
sqg_k2_eight_stratum_rank_permutation,
|
| 28 |
+
sqg_rank_permutation,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
_SQG_XOR_CHEB_T12_SHA256 = {
|
| 33 |
+
"t12": "cca11fe5744c9c93a34f4217f342fbc0f74ecc8a007c076582424a505fc9da5e",
|
| 34 |
+
2: "62027916386245a84c86156a0a08b6cf07e41548871af0e56fb40780558f6293",
|
| 35 |
+
3: "afe7b3633e7d243b00b379b18ec4dca573722b3727cafef47fcb6470d7e7e6c9",
|
| 36 |
+
4: "5a9620f0c4d8f0a60d0b6fbea921dcecbd193e6febf31043aaa6403c20389c2f",
|
| 37 |
+
5: "a1caf5572c67d4face423478f423e1ed6149a8c920be5c28d4702946c93cae2f",
|
| 38 |
+
6: "8e0edc92e20eb92f5287dbdadc8e870fc500fc03c5eed996cae0781d197ebcb0",
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _sha256(tensor: torch.Tensor) -> str:
|
| 43 |
+
return hashlib.sha256(tensor.contiguous().numpy().tobytes()).hexdigest()
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def test_primary_sqg_xor_cheb_t12_matches_runtime_contract() -> None:
|
| 47 |
+
table = sqg_xor_cheb_t12_rank_lut_bytes()
|
| 48 |
+
assert table.dtype == torch.uint8
|
| 49 |
+
assert table.shape == (1 << 12,)
|
| 50 |
+
assert _sha256(table) == _SQG_XOR_CHEB_T12_SHA256["t12"]
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 54 |
+
def test_primary_sqg_xor_cheb_t12_graph_and_labels_match_runtime_contract(
|
| 55 |
+
bits: int,
|
| 56 |
+
) -> None:
|
| 57 |
+
ranks = sqg_xor_rank_permutation(bits)
|
| 58 |
+
labels = sqg_xor_cheb_t12_bytes(bits)
|
| 59 |
+
width = 16 - bits
|
| 60 |
+
strata = (ranks >> width).reshape(1 << width, 1 << bits)
|
| 61 |
+
|
| 62 |
+
assert torch.equal(torch.sort(ranks).values, torch.arange(1 << 16))
|
| 63 |
+
assert torch.equal(
|
| 64 |
+
torch.sort(strata, dim=1).values,
|
| 65 |
+
torch.arange(1 << bits).expand_as(strata),
|
| 66 |
+
)
|
| 67 |
+
assert torch.equal(labels, sqg_xor_cheb_t12_rank_lut_bytes()[ranks >> 4])
|
| 68 |
+
assert torch.equal(labels, sqg_codebook_bytes(bits, CODEBOOK_SQG_XOR_CHEB_T12))
|
| 69 |
+
assert bool(torch.isfinite(labels.view(torch.float8_e4m3fn).float()).all())
|
| 70 |
+
assert not bool(((labels & 0x7F) == 0).logical_and(labels != 0).any())
|
| 71 |
+
assert _sha256(labels) == _SQG_XOR_CHEB_T12_SHA256[bits]
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 75 |
+
def test_sqg_preprojection_mapping_is_a_permutation(bits: int) -> None:
|
| 76 |
+
ranks = sqg_rank_permutation(bits)
|
| 77 |
+
assert ranks.dtype == torch.int64
|
| 78 |
+
assert ranks.shape == (1 << 16,)
|
| 79 |
+
assert torch.equal(torch.sort(ranks).values, torch.arange(1 << 16))
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 83 |
+
def test_sqg_codebook_round_trips_exact_e4m3(bits: int) -> None:
|
| 84 |
+
raw = sqg_e4m3_bytes(bits, "normal")
|
| 85 |
+
codebook = sqg_e4m3_codebook(bits, "normal")
|
| 86 |
+
assert raw.dtype == torch.uint8
|
| 87 |
+
assert codebook.dtype == torch.float16
|
| 88 |
+
assert raw.shape == codebook.shape == (1 << 16,)
|
| 89 |
+
assert torch.equal(codebook.to(torch.float8_e4m3fn).view(torch.uint8), raw)
|
| 90 |
+
assert bool(torch.isfinite(codebook).all())
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 94 |
+
def test_sqg_each_state_spans_all_coarse_strata(bits: int) -> None:
|
| 95 |
+
ranks = sqg_rank_permutation(bits)
|
| 96 |
+
width = 16 - bits
|
| 97 |
+
strata = (ranks >> width).reshape(1 << width, 1 << bits)
|
| 98 |
+
expected = torch.arange(1 << bits).expand_as(strata)
|
| 99 |
+
assert torch.equal(torch.sort(strata, dim=1).values, expected)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 103 |
+
def test_shared_rank_lut_preserves_one_graph_for_every_rate(bits: int) -> None:
|
| 104 |
+
rank_lut = torch.arange(1 << 16, dtype=torch.int64).remainder(126).to(torch.uint8)
|
| 105 |
+
raw = sqg_e4m3_bytes_from_rank_lut(bits, rank_lut)
|
| 106 |
+
expected = rank_lut.index_select(0, sqg_rank_permutation(bits))
|
| 107 |
+
assert torch.equal(raw, expected)
|
| 108 |
+
assert torch.equal(
|
| 109 |
+
sqg_e4m3_codebook_from_rank_lut(bits, rank_lut)
|
| 110 |
+
.to(torch.float8_e4m3fn)
|
| 111 |
+
.view(torch.uint8),
|
| 112 |
+
expected,
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def test_shared_rank_lut_rejects_nonfinite_e4m3() -> None:
|
| 117 |
+
rank_lut = torch.zeros(1 << 16, dtype=torch.uint8)
|
| 118 |
+
rank_lut[0] = 0x7F
|
| 119 |
+
with pytest.raises(ValueError, match="finite E4M3"):
|
| 120 |
+
sqg_e4m3_bytes_from_rank_lut(2, rank_lut)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 124 |
+
def test_sqg_cheb_normal_named_codebook_uses_exact_shared_rank_law(bits: int) -> None:
|
| 125 |
+
expected = sqg_cheb_normal_rank_e4m3_bytes().index_select(
|
| 126 |
+
0, sqg_rank_permutation(bits)
|
| 127 |
+
)
|
| 128 |
+
assert torch.equal(sqg_cheb_normal_e4m3_bytes(bits), expected)
|
| 129 |
+
assert torch.equal(
|
| 130 |
+
sqg_codebook_bytes(bits, CODEBOOK_SQG_CHEB_NORMAL_E4M3), expected
|
| 131 |
+
)
|
| 132 |
+
assert not torch.equal(
|
| 133 |
+
expected, sqg_codebook_bytes(bits, CODEBOOK_SQG_NORMAL_E4M3)
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 138 |
+
@pytest.mark.parametrize("rate_axis", ("k", "n"))
|
| 139 |
+
def test_w2_k2_q8h4_profile_changes_only_k_axis_k2(
|
| 140 |
+
bits: int, rate_axis: str
|
| 141 |
+
) -> None:
|
| 142 |
+
actual = sqg_codebook_bytes(
|
| 143 |
+
bits,
|
| 144 |
+
CODEBOOK_SQG_CHEB,
|
| 145 |
+
rate_axis=rate_axis,
|
| 146 |
+
)
|
| 147 |
+
rank_lut = sqg_cheb_normal_rank_e4m3_bytes()
|
| 148 |
+
ranks = (
|
| 149 |
+
sqg_k2_eight_stratum_rank_permutation(4)
|
| 150 |
+
if bits == 2 and rate_axis == "k"
|
| 151 |
+
else sqg_rank_permutation(bits)
|
| 152 |
+
)
|
| 153 |
+
assert torch.equal(actual, rank_lut.index_select(0, ranks))
|
| 154 |
+
if bits == 2 and rate_axis == "k":
|
| 155 |
+
assert not torch.equal(
|
| 156 |
+
actual, sqg_codebook_bytes(bits, CODEBOOK_SQG_CHEB_NORMAL_E4M3)
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def test_w2_k2_q8h4_profile_requires_rate_axis() -> None:
|
| 161 |
+
with pytest.raises(ValueError, match="requires rate_axis"):
|
| 162 |
+
sqg_codebook_bytes(2, CODEBOOK_SQG_CHEB)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def test_reference_decoder_accepts_custom_codebook() -> None:
|
| 166 |
+
states = torch.arange(256, dtype=torch.int16).reshape(1, 1, 256)
|
| 167 |
+
codebook = sqg_e4m3_codebook(3, "normal")
|
| 168 |
+
decoded = decode_regularized_weight(
|
| 169 |
+
states, codebook_values=codebook
|
| 170 |
+
)
|
| 171 |
+
assert decoded.shape == (16, 16)
|
| 172 |
+
assert torch.equal(
|
| 173 |
+
torch.sort(decoded.flatten()).values,
|
| 174 |
+
torch.sort(codebook[:256].float()).values,
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
@pytest.mark.parametrize("rate_axis", ("k", "n"))
|
| 179 |
+
def test_reference_decoder_applies_rate_specific_sqg_tables(rate_axis: str) -> None:
|
| 180 |
+
states = torch.arange(3 * 3 * 256, dtype=torch.int32).to(torch.int16).reshape(
|
| 181 |
+
3, 3, 256
|
| 182 |
+
)
|
| 183 |
+
tile_bits = (2, 3, 4)
|
| 184 |
+
decoded = decode_qsrt_regularized_weight(
|
| 185 |
+
states,
|
| 186 |
+
rate_axis=rate_axis,
|
| 187 |
+
tile_bits=tile_bits,
|
| 188 |
+
codebook=CODEBOOK_SQG_NORMAL_E4M3,
|
| 189 |
+
)
|
| 190 |
+
axis = 0 if rate_axis == "k" else 1
|
| 191 |
+
pieces = []
|
| 192 |
+
for tile, bits in enumerate(tile_bits):
|
| 193 |
+
selected = states.narrow(axis, tile, 1)
|
| 194 |
+
pieces.append(
|
| 195 |
+
decode_regularized_weight(
|
| 196 |
+
selected,
|
| 197 |
+
codebook=CODEBOOK_SQG_NORMAL_E4M3,
|
| 198 |
+
bits=bits,
|
| 199 |
+
)
|
| 200 |
+
)
|
| 201 |
+
expected = torch.cat(pieces, dim=0 if rate_axis == "k" else 1)
|
| 202 |
+
assert torch.equal(decoded, expected)
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def test_reference_decoder_applies_w2_only_k2_q8h4_profile() -> None:
|
| 206 |
+
states = torch.arange(3 * 3 * 256, dtype=torch.int32).to(torch.int16).reshape(
|
| 207 |
+
3, 3, 256
|
| 208 |
+
)
|
| 209 |
+
tile_bits = (2, 3, 4)
|
| 210 |
+
down = decode_qsrt_regularized_weight(
|
| 211 |
+
states,
|
| 212 |
+
rate_axis="k",
|
| 213 |
+
tile_bits=tile_bits,
|
| 214 |
+
codebook=CODEBOOK_SQG_CHEB,
|
| 215 |
+
)
|
| 216 |
+
upstream = decode_qsrt_regularized_weight(
|
| 217 |
+
states,
|
| 218 |
+
rate_axis="n",
|
| 219 |
+
tile_bits=tile_bits,
|
| 220 |
+
codebook=CODEBOOK_SQG_CHEB,
|
| 221 |
+
)
|
| 222 |
+
native_down = decode_qsrt_regularized_weight(
|
| 223 |
+
states,
|
| 224 |
+
rate_axis="k",
|
| 225 |
+
tile_bits=tile_bits,
|
| 226 |
+
codebook=CODEBOOK_SQG_CHEB_NORMAL_E4M3,
|
| 227 |
+
)
|
| 228 |
+
native_upstream = decode_qsrt_regularized_weight(
|
| 229 |
+
states,
|
| 230 |
+
rate_axis="n",
|
| 231 |
+
tile_bits=tile_bits,
|
| 232 |
+
codebook=CODEBOOK_SQG_CHEB_NORMAL_E4M3,
|
| 233 |
+
)
|
| 234 |
+
assert not torch.equal(down[:16], native_down[:16])
|
| 235 |
+
assert torch.equal(down[16:], native_down[16:])
|
| 236 |
+
assert torch.equal(upstream, native_upstream)
|
reproduction/closure/patches/kquant/changed-files/tests/test_sqg_quantizer.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import hashlib
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from types import SimpleNamespace
|
| 6 |
+
|
| 7 |
+
import pytest
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
import kquant.sqg_quantizer as sqg_quantizer
|
| 11 |
+
from kquant.exl3_reference import reconstruct_trellis_states
|
| 12 |
+
from kquant.sqg_quantizer import install_sqg_quantizer
|
| 13 |
+
from kquant.sqg_e4m3 import sqg_xor_cheb_t12_bytes
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _sealed_extension(
|
| 17 |
+
tmp_path: Path,
|
| 18 |
+
monkeypatch: pytest.MonkeyPatch,
|
| 19 |
+
*,
|
| 20 |
+
filename: str = "kquant_sqg_quantize_ext_v22.so",
|
| 21 |
+
) -> tuple[Path, SimpleNamespace]:
|
| 22 |
+
path = tmp_path / filename
|
| 23 |
+
payload = b"sealed-sqg-extension-test-double"
|
| 24 |
+
path.write_bytes(payload)
|
| 25 |
+
module = SimpleNamespace(
|
| 26 |
+
__file__=str(path),
|
| 27 |
+
quantize_tiles_procedural=lambda *_args: None,
|
| 28 |
+
quantize_tiles_sqg=lambda *_args: None,
|
| 29 |
+
)
|
| 30 |
+
monkeypatch.setenv("KQUANT_SQG_REQUIRE_PREBUILT", "1")
|
| 31 |
+
monkeypatch.setenv("KQUANT_SQG_EXTENSION_PATH", str(path))
|
| 32 |
+
monkeypatch.setenv(
|
| 33 |
+
"KQUANT_SQG_EXTENSION_SHA256", hashlib.sha256(payload).hexdigest()
|
| 34 |
+
)
|
| 35 |
+
return path, module
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def test_required_prebuilt_extension_uses_exact_path_and_never_jit_builds(
|
| 39 |
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
| 40 |
+
) -> None:
|
| 41 |
+
path, module = _sealed_extension(tmp_path, monkeypatch)
|
| 42 |
+
imported: list[Path] = []
|
| 43 |
+
|
| 44 |
+
def fake_import(actual: Path):
|
| 45 |
+
imported.append(actual)
|
| 46 |
+
return module
|
| 47 |
+
|
| 48 |
+
monkeypatch.setattr(sqg_quantizer, "_import_prebuilt_extension", fake_import)
|
| 49 |
+
monkeypatch.setattr(
|
| 50 |
+
sqg_quantizer,
|
| 51 |
+
"load",
|
| 52 |
+
lambda **_kwargs: pytest.fail("JIT builder must not run in prebuilt mode"),
|
| 53 |
+
)
|
| 54 |
+
sqg_quantizer._extension.cache_clear()
|
| 55 |
+
try:
|
| 56 |
+
assert sqg_quantizer._extension() is module
|
| 57 |
+
assert imported == [path.resolve()]
|
| 58 |
+
finally:
|
| 59 |
+
sqg_quantizer._extension.cache_clear()
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def test_required_prebuilt_extension_rejects_wrong_filename_before_import_or_jit(
|
| 63 |
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
| 64 |
+
) -> None:
|
| 65 |
+
_sealed_extension(tmp_path, monkeypatch, filename="foreign_extension.so")
|
| 66 |
+
monkeypatch.setattr(
|
| 67 |
+
sqg_quantizer,
|
| 68 |
+
"_import_prebuilt_extension",
|
| 69 |
+
lambda _path: pytest.fail("foreign extension must not be imported"),
|
| 70 |
+
)
|
| 71 |
+
monkeypatch.setattr(
|
| 72 |
+
sqg_quantizer,
|
| 73 |
+
"load",
|
| 74 |
+
lambda **_kwargs: pytest.fail("JIT builder must not run in prebuilt mode"),
|
| 75 |
+
)
|
| 76 |
+
sqg_quantizer._extension.cache_clear()
|
| 77 |
+
try:
|
| 78 |
+
with pytest.raises(ValueError, match="filename must identify"):
|
| 79 |
+
sqg_quantizer._extension()
|
| 80 |
+
finally:
|
| 81 |
+
sqg_quantizer._extension.cache_clear()
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def test_required_prebuilt_extension_rejects_hash_mismatch_before_import_or_jit(
|
| 85 |
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
| 86 |
+
) -> None:
|
| 87 |
+
_sealed_extension(tmp_path, monkeypatch)
|
| 88 |
+
monkeypatch.setenv("KQUANT_SQG_EXTENSION_SHA256", "0" * 64)
|
| 89 |
+
monkeypatch.setattr(
|
| 90 |
+
sqg_quantizer,
|
| 91 |
+
"_import_prebuilt_extension",
|
| 92 |
+
lambda _path: pytest.fail("mismatched extension must not be imported"),
|
| 93 |
+
)
|
| 94 |
+
monkeypatch.setattr(
|
| 95 |
+
sqg_quantizer,
|
| 96 |
+
"load",
|
| 97 |
+
lambda **_kwargs: pytest.fail("JIT builder must not run in prebuilt mode"),
|
| 98 |
+
)
|
| 99 |
+
sqg_quantizer._extension.cache_clear()
|
| 100 |
+
try:
|
| 101 |
+
with pytest.raises(RuntimeError, match="SHA256 mismatch"):
|
| 102 |
+
sqg_quantizer._extension()
|
| 103 |
+
finally:
|
| 104 |
+
sqg_quantizer._extension.cache_clear()
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
@pytest.mark.parametrize(
|
| 108 |
+
("missing", "message"),
|
| 109 |
+
(
|
| 110 |
+
("KQUANT_SQG_EXTENSION_PATH", "are both required"),
|
| 111 |
+
("KQUANT_SQG_EXTENSION_SHA256", "are both required"),
|
| 112 |
+
),
|
| 113 |
+
)
|
| 114 |
+
def test_required_prebuilt_extension_rejects_partial_binding_without_jit(
|
| 115 |
+
tmp_path: Path,
|
| 116 |
+
monkeypatch: pytest.MonkeyPatch,
|
| 117 |
+
missing: str,
|
| 118 |
+
message: str,
|
| 119 |
+
) -> None:
|
| 120 |
+
_sealed_extension(tmp_path, monkeypatch)
|
| 121 |
+
monkeypatch.delenv(missing)
|
| 122 |
+
monkeypatch.setattr(
|
| 123 |
+
sqg_quantizer,
|
| 124 |
+
"load",
|
| 125 |
+
lambda **_kwargs: pytest.fail("JIT builder must not run in prebuilt mode"),
|
| 126 |
+
)
|
| 127 |
+
sqg_quantizer._extension.cache_clear()
|
| 128 |
+
try:
|
| 129 |
+
with pytest.raises(RuntimeError, match=message):
|
| 130 |
+
sqg_quantizer._extension()
|
| 131 |
+
finally:
|
| 132 |
+
sqg_quantizer._extension.cache_clear()
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def test_rate_specific_none_dispatches_to_kquant_mcg(monkeypatch) -> None:
|
| 136 |
+
calls: list[tuple[torch.Tensor, dict]] = []
|
| 137 |
+
procedural_calls: list[tuple] = []
|
| 138 |
+
|
| 139 |
+
def original(tiles: torch.Tensor, args: dict):
|
| 140 |
+
calls.append((tiles, args))
|
| 141 |
+
return "upstream"
|
| 142 |
+
|
| 143 |
+
def procedural(*args):
|
| 144 |
+
procedural_calls.append(args)
|
| 145 |
+
|
| 146 |
+
monkeypatch.setattr(
|
| 147 |
+
sqg_quantizer,
|
| 148 |
+
"_sqg_temp_buffers",
|
| 149 |
+
lambda _device, _bits: (torch.empty(0), torch.empty(0)),
|
| 150 |
+
)
|
| 151 |
+
monkeypatch.setattr(
|
| 152 |
+
sqg_quantizer,
|
| 153 |
+
"_extension",
|
| 154 |
+
lambda: SimpleNamespace(quantize_tiles_procedural=procedural),
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
module = SimpleNamespace(quantize_tiles=original)
|
| 158 |
+
install_sqg_quantizer(module)
|
| 159 |
+
tiles = torch.zeros((1, 256), dtype=torch.float32)
|
| 160 |
+
result = module.quantize_tiles(
|
| 161 |
+
tiles,
|
| 162 |
+
{
|
| 163 |
+
"K": 2,
|
| 164 |
+
"devices": ["cuda:0"],
|
| 165 |
+
"sqg_e4m3_luts_by_bits": {2: None, 3: None, 4: None},
|
| 166 |
+
},
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
assert all(isinstance(tensor, torch.Tensor) for tensor in result)
|
| 170 |
+
assert not calls
|
| 171 |
+
assert len(procedural_calls) == 1
|
| 172 |
+
assert procedural_calls[0][5:] == (2, 1, 128)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def test_sqg_luts_are_transposed_by_predecessor_for_vector_loads(
|
| 176 |
+
monkeypatch,
|
| 177 |
+
) -> None:
|
| 178 |
+
calls: list[tuple] = []
|
| 179 |
+
|
| 180 |
+
monkeypatch.setattr(
|
| 181 |
+
sqg_quantizer,
|
| 182 |
+
"_sqg_temp_buffers",
|
| 183 |
+
lambda _device, _bits: (torch.empty(0), torch.empty(0)),
|
| 184 |
+
)
|
| 185 |
+
monkeypatch.setattr(
|
| 186 |
+
sqg_quantizer,
|
| 187 |
+
"_extension",
|
| 188 |
+
lambda: SimpleNamespace(quantize_tiles_sqg=lambda *args: calls.append(args)),
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
module = SimpleNamespace(quantize_tiles=lambda *_args: "upstream")
|
| 192 |
+
install_sqg_quantizer(module)
|
| 193 |
+
tiles = torch.zeros((1, 256), dtype=torch.float32)
|
| 194 |
+
for bits in (2, 3, 4):
|
| 195 |
+
raw = torch.arange(65536, dtype=torch.int64).to(torch.uint8)
|
| 196 |
+
module.quantize_tiles(
|
| 197 |
+
tiles,
|
| 198 |
+
{
|
| 199 |
+
"K": bits,
|
| 200 |
+
"devices": ["cuda:0"],
|
| 201 |
+
"sqg_e4m3_lut": raw,
|
| 202 |
+
},
|
| 203 |
+
)
|
| 204 |
+
actual = calls[-1][5]
|
| 205 |
+
predecessors = 1 << bits
|
| 206 |
+
out_edge_pairs = (65536 >> bits) // 2
|
| 207 |
+
expected = (
|
| 208 |
+
raw.reshape(predecessors, out_edge_pairs, 2)
|
| 209 |
+
.permute(1, 0, 2)
|
| 210 |
+
.contiguous()
|
| 211 |
+
.reshape(-1)
|
| 212 |
+
)
|
| 213 |
+
assert torch.equal(actual, expected)
|
| 214 |
+
assert calls[-1][6:] == (bits, 128)
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
@pytest.mark.parametrize("bits", [5, 6])
|
| 218 |
+
def test_k5_k6_sqg_luts_remain_in_direct_state_order(
|
| 219 |
+
monkeypatch, bits: int
|
| 220 |
+
) -> None:
|
| 221 |
+
calls: list[tuple] = []
|
| 222 |
+
monkeypatch.setattr(
|
| 223 |
+
sqg_quantizer,
|
| 224 |
+
"_sqg_temp_buffers",
|
| 225 |
+
lambda _device, _bits: (torch.empty(0), torch.empty(0)),
|
| 226 |
+
)
|
| 227 |
+
monkeypatch.setattr(
|
| 228 |
+
sqg_quantizer,
|
| 229 |
+
"_extension",
|
| 230 |
+
lambda: SimpleNamespace(quantize_tiles_sqg=lambda *args: calls.append(args)),
|
| 231 |
+
)
|
| 232 |
+
module = SimpleNamespace(quantize_tiles=lambda *_args: "upstream")
|
| 233 |
+
install_sqg_quantizer(module)
|
| 234 |
+
raw = torch.arange(65536, dtype=torch.int64).to(torch.uint8)
|
| 235 |
+
module.quantize_tiles(
|
| 236 |
+
torch.zeros((1, 256), dtype=torch.float32),
|
| 237 |
+
{"K": bits, "devices": ["cuda:0"], "sqg_e4m3_lut": raw},
|
| 238 |
+
)
|
| 239 |
+
assert torch.equal(calls[-1][5], raw)
|
| 240 |
+
assert calls[-1][6:] == (bits, 128)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable")
|
| 244 |
+
@pytest.mark.parametrize("bits", range(2, 7))
|
| 245 |
+
def test_sqg_quantizer_keeps_saturated_cost_paths_closed(bits: int) -> None:
|
| 246 |
+
module = SimpleNamespace(quantize_tiles=lambda *_args: "upstream")
|
| 247 |
+
install_sqg_quantizer(module)
|
| 248 |
+
tiles = torch.full((2, 256), 1000.0, dtype=torch.float32, device="cuda")
|
| 249 |
+
|
| 250 |
+
_output, states = module.quantize_tiles(
|
| 251 |
+
tiles,
|
| 252 |
+
{
|
| 253 |
+
"K": bits,
|
| 254 |
+
"devices": ["cuda:0"],
|
| 255 |
+
"sqg_e4m3_lut": sqg_xor_cheb_t12_bytes(bits),
|
| 256 |
+
"tailbite_context": 128,
|
| 257 |
+
},
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
edges = states.to(torch.int64) & ((1 << bits) - 1)
|
| 261 |
+
expected = reconstruct_trellis_states(edges, bits)
|
| 262 |
+
assert torch.equal(states, expected)
|
reproduction/closure/patches/kquant/changes.patch
ADDED
|
@@ -0,0 +1,1386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/kquant/candidate_hessian.py b/kquant/candidate_hessian.py
|
| 2 |
+
index 36f5f30..6603799 100644
|
| 3 |
+
--- a/kquant/candidate_hessian.py
|
| 4 |
+
+++ b/kquant/candidate_hessian.py
|
| 5 |
+
@@ -92,8 +92,10 @@ def weighted_covariance(
|
| 6 |
+
accumulator = torch.zeros(
|
| 7 |
+
(dimension, dimension), dtype=torch.float32, device=device
|
| 8 |
+
)
|
| 9 |
+
- denominator = float(weights.double().sum())
|
| 10 |
+
- if not math.isfinite(denominator) or denominator <= 0:
|
| 11 |
+
+ denominator_tensor = weights.to(device=device, dtype=torch.float64).sum()
|
| 12 |
+
+ if not bool(torch.isfinite(denominator_tensor)) or not bool(
|
| 13 |
+
+ denominator_tensor > 0
|
| 14 |
+
+ ):
|
| 15 |
+
raise ValueError("covariance weights must have a positive finite sum")
|
| 16 |
+
for begin in range(0, int(rows.shape[0]), chunk_rows):
|
| 17 |
+
values = rows[begin : begin + chunk_rows].to(
|
| 18 |
+
@@ -103,8 +105,9 @@ def weighted_covariance(
|
| 19 |
+
device=device, dtype=torch.float32
|
| 20 |
+
)
|
| 21 |
+
accumulator.addmm_(values.T, values * local_weights[:, None])
|
| 22 |
+
- covariance = accumulator.div_(denominator)
|
| 23 |
+
+ covariance = accumulator.div_(denominator_tensor.to(torch.float32))
|
| 24 |
+
covariance = ((covariance + covariance.T) * 0.5).contiguous()
|
| 25 |
+
+ denominator = float(denominator_tensor)
|
| 26 |
+
if return_cpu:
|
| 27 |
+
covariance = covariance.cpu()
|
| 28 |
+
if not torch.all(torch.isfinite(covariance)):
|
| 29 |
+
@@ -115,13 +118,23 @@ def weighted_covariance(
|
| 30 |
+
def shrink_covariance(
|
| 31 |
+
global_covariance: torch.Tensor,
|
| 32 |
+
local_covariance: torch.Tensor,
|
| 33 |
+
- alpha: float,
|
| 34 |
+
+ alpha: float | torch.Tensor,
|
| 35 |
+
) -> torch.Tensor:
|
| 36 |
+
"""Convexly shrink a low-support local covariance toward a global one."""
|
| 37 |
+
|
| 38 |
+
- alpha = float(alpha)
|
| 39 |
+
- if not math.isfinite(alpha) or not 0.0 <= alpha <= 1.0:
|
| 40 |
+
- raise ValueError("alpha must be finite and in [0, 1]")
|
| 41 |
+
+ if isinstance(alpha, torch.Tensor):
|
| 42 |
+
+ if alpha.numel() != 1 or not bool(torch.isfinite(alpha)) or not bool(
|
| 43 |
+
+ (alpha >= 0) & (alpha <= 1)
|
| 44 |
+
+ ):
|
| 45 |
+
+ raise ValueError("alpha must be a finite scalar in [0, 1]")
|
| 46 |
+
+ blend_weight: float | torch.Tensor = alpha.to(
|
| 47 |
+
+ device=global_covariance.device, dtype=torch.float32
|
| 48 |
+
+ )
|
| 49 |
+
+ else:
|
| 50 |
+
+ alpha_value = float(alpha)
|
| 51 |
+
+ if not math.isfinite(alpha_value) or not 0.0 <= alpha_value <= 1.0:
|
| 52 |
+
+ raise ValueError("alpha must be finite and in [0, 1]")
|
| 53 |
+
+ blend_weight = alpha_value
|
| 54 |
+
if (
|
| 55 |
+
global_covariance.ndim != 2
|
| 56 |
+
or global_covariance.shape[0] != global_covariance.shape[1]
|
| 57 |
+
@@ -133,7 +146,7 @@ def shrink_covariance(
|
| 58 |
+
):
|
| 59 |
+
raise ValueError("covariances must be finite")
|
| 60 |
+
blended = torch.lerp(
|
| 61 |
+
- global_covariance.float(), local_covariance.float(), alpha
|
| 62 |
+
+ global_covariance.float(), local_covariance.float(), blend_weight
|
| 63 |
+
)
|
| 64 |
+
return ((blended + blended.T) * 0.5).contiguous()
|
| 65 |
+
|
| 66 |
+
@@ -141,6 +154,17 @@ def shrink_covariance(
|
| 67 |
+
def weighted_effective_sample_size(weights: torch.Tensor) -> float:
|
| 68 |
+
"""Return Kish effective sample size for nonnegative row weights."""
|
| 69 |
+
|
| 70 |
+
+ if weights.ndim != 1 or not weights.numel():
|
| 71 |
+
+ raise ValueError("weights must be a nonempty vector")
|
| 72 |
+
+ if not torch.all(torch.isfinite(weights)) or torch.any(weights < 0):
|
| 73 |
+
+ raise ValueError("weights must be finite and nonnegative")
|
| 74 |
+
+ value = _weighted_effective_sample_size_tensor(weights)
|
| 75 |
+
+ return float(value)
|
| 76 |
+
+
|
| 77 |
+
+
|
| 78 |
+
+def _weighted_effective_sample_size_tensor(weights: torch.Tensor) -> torch.Tensor:
|
| 79 |
+
+ """Kish support as a scalar tensor on the input numerical device."""
|
| 80 |
+
+
|
| 81 |
+
if weights.ndim != 1 or not weights.numel():
|
| 82 |
+
raise ValueError("weights must be a nonempty vector")
|
| 83 |
+
if not torch.all(torch.isfinite(weights)) or torch.any(weights < 0):
|
| 84 |
+
@@ -150,7 +174,7 @@ def weighted_effective_sample_size(weights: torch.Tensor) -> float:
|
| 85 |
+
squared_total = weights64.square().sum()
|
| 86 |
+
if total <= 0 or squared_total <= 0:
|
| 87 |
+
raise ValueError("weights must have positive effective support")
|
| 88 |
+
- return float(total.square() / squared_total)
|
| 89 |
+
+ return total.square() / squared_total
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def adaptive_identity_shrinkage(
|
| 93 |
+
@@ -180,7 +204,7 @@ def adaptive_identity_shrinkage(
|
| 94 |
+
if not torch.all(torch.isfinite(local_covariance)):
|
| 95 |
+
raise ValueError("local covariance must be finite")
|
| 96 |
+
|
| 97 |
+
- effective_rows = weighted_effective_sample_size(weights)
|
| 98 |
+
+ effective_rows_tensor = _weighted_effective_sample_size_tensor(weights)
|
| 99 |
+
dimension = int(local_covariance.shape[0])
|
| 100 |
+
local64 = local_covariance.double()
|
| 101 |
+
local_trace = torch.trace(local64)
|
| 102 |
+
@@ -189,24 +213,34 @@ def adaptive_identity_shrinkage(
|
| 103 |
+
|
| 104 |
+
mu = local_trace / dimension
|
| 105 |
+
mean_square = local64.square().mean()
|
| 106 |
+
- denominator = (effective_rows + 1.0) * (
|
| 107 |
+
+ denominator = (effective_rows_tensor + 1.0) * (
|
| 108 |
+
mean_square - mu.square() / dimension
|
| 109 |
+
)
|
| 110 |
+
- if denominator <= 0:
|
| 111 |
+
- oas_shrinkage = 1.0
|
| 112 |
+
- else:
|
| 113 |
+
- oas_shrinkage = min(
|
| 114 |
+
- 1.0,
|
| 115 |
+
- max(0.0, float((mean_square + mu.square()) / denominator)),
|
| 116 |
+
- )
|
| 117 |
+
- local_alpha = min(max_local_alpha, 1.0 - oas_shrinkage)
|
| 118 |
+
- identity_scale = float(mu)
|
| 119 |
+
+ raw_oas = (mean_square + mu.square()) / denominator.clamp_min(
|
| 120 |
+
+ torch.finfo(local64.dtype).tiny
|
| 121 |
+
+ )
|
| 122 |
+
+ oas_shrinkage_tensor = torch.where(
|
| 123 |
+
+ denominator <= 0,
|
| 124 |
+
+ torch.ones_like(raw_oas),
|
| 125 |
+
+ raw_oas.clamp(0.0, 1.0),
|
| 126 |
+
+ )
|
| 127 |
+
+ local_alpha_tensor = torch.minimum(
|
| 128 |
+
+ torch.as_tensor(max_local_alpha, device=local64.device, dtype=local64.dtype),
|
| 129 |
+
+ 1.0 - oas_shrinkage_tensor,
|
| 130 |
+
+ )
|
| 131 |
+
scaled_identity = torch.eye(
|
| 132 |
+
dimension,
|
| 133 |
+
dtype=torch.float32,
|
| 134 |
+
device=local_covariance.device,
|
| 135 |
+
- ).mul_(identity_scale)
|
| 136 |
+
- blended = shrink_covariance(scaled_identity, local_covariance, local_alpha)
|
| 137 |
+
+ ).mul_(mu.to(dtype=torch.float32))
|
| 138 |
+
+ blended = shrink_covariance(
|
| 139 |
+
+ scaled_identity, local_covariance, local_alpha_tensor.to(torch.float32)
|
| 140 |
+
+ )
|
| 141 |
+
+ # Evidence scalars are detached only after the numerical result is frozen.
|
| 142 |
+
+ effective_rows = float(effective_rows_tensor)
|
| 143 |
+
+ oas_shrinkage = float(oas_shrinkage_tensor)
|
| 144 |
+
+ local_alpha = float(local_alpha_tensor)
|
| 145 |
+
+ identity_scale = float(mu)
|
| 146 |
+
return blended, {
|
| 147 |
+
"effective_sample_size": effective_rows,
|
| 148 |
+
"oas_shrinkage": oas_shrinkage,
|
| 149 |
+
diff --git a/kquant/csrc/qsrt_quantize_tiles_kernel.cuh b/kquant/csrc/qsrt_quantize_tiles_kernel.cuh
|
| 150 |
+
index 76febdd..f4299d1 100644
|
| 151 |
+
--- a/kquant/csrc/qsrt_quantize_tiles_kernel.cuh
|
| 152 |
+
+++ b/kquant/csrc/qsrt_quantize_tiles_kernel.cuh
|
| 153 |
+
@@ -17,6 +17,9 @@
|
| 154 |
+
#ifndef H_INF
|
| 155 |
+
#define H_INF __ushort_as_half(0x7c00)
|
| 156 |
+
#endif
|
| 157 |
+
+#ifndef H_MAX_FINITE
|
| 158 |
+
+#define H_MAX_FINITE __ushort_as_half(0x7bff)
|
| 159 |
+
+#endif
|
| 160 |
+
|
| 161 |
+
template <int K, int cb>
|
| 162 |
+
__global__ __launch_bounds__(
|
| 163 |
+
@@ -121,7 +124,12 @@ void quantize_tiles_kernel
|
| 164 |
+
decoded2 = decode_3inst_2<cb>(out_edge_idx, out_edge_idx + 1);
|
| 165 |
+
}
|
| 166 |
+
half2 dh2 = __hsub2(decoded2, w2);
|
| 167 |
+
- half2 min_err2 = __hmul2(dh2, dh2);
|
| 168 |
+
+ // Positive infinity denotes an unreachable state. Reachable path
|
| 169 |
+
+ // costs must remain finite when their FP16 sum overflows, or
|
| 170 |
+
+ // traceback can confuse a saturated path with an unreachable one
|
| 171 |
+
+ // and violate the constrained cyclic boundary.
|
| 172 |
+
+ half2 min_err2 = __hmin2(
|
| 173 |
+
+ __hmul2(dh2, dh2), __half2half2(H_MAX_FINITE));
|
| 174 |
+
if (pre_state >= 0 && in_edge_idx != pre_state) min_err2 = __half2half2(H_INF);
|
| 175 |
+
int min_k0 = 0;
|
| 176 |
+
int min_k1 = 0;
|
| 177 |
+
@@ -165,7 +173,8 @@ void quantize_tiles_kernel
|
| 178 |
+
decoded2 = decode_3inst_2<cb>(state0, state0 + 1);
|
| 179 |
+
}
|
| 180 |
+
dh2 = __hsub2(decoded2, w2);
|
| 181 |
+
- half2 err2 = __hmul2(dh2, dh2);
|
| 182 |
+
+ half2 err2 = __hmin2(
|
| 183 |
+
+ __hmul2(dh2, dh2), __half2half2(H_MAX_FINITE));
|
| 184 |
+
if (pre_state >= 0 && in_edge_idx != pre_state) err2 = __half2half2(H_INF);
|
| 185 |
+
const unsigned less = __hlt2_mask(err2, min_err2);
|
| 186 |
+
min_err2 = __hmin2(err2, min_err2);
|
| 187 |
+
@@ -253,7 +262,13 @@ void quantize_tiles_kernel
|
| 188 |
+
decoded2 = decode_3inst_2<cb>(out_edge_idx, out_edge_idx + 1);
|
| 189 |
+
}
|
| 190 |
+
half2 dh2 = __hsub2(decoded2, w2);
|
| 191 |
+
- half2 min_err2 = __hfma2(dh2, dh2, __half2half2(temp_costs_inc[in_edge_idx]));
|
| 192 |
+
+ half predecessor_cost = temp_costs_inc[in_edge_idx];
|
| 193 |
+
+ half2 min_err2 = __heq(predecessor_cost, H_INF)
|
| 194 |
+
+ ? __half2half2(H_INF)
|
| 195 |
+
+ : __hmin2(
|
| 196 |
+
+ __hfma2(
|
| 197 |
+
+ dh2, dh2, __half2half2(predecessor_cost)),
|
| 198 |
+
+ __half2half2(H_MAX_FINITE));
|
| 199 |
+
int min_k0 = 0;
|
| 200 |
+
int min_k1 = 0;
|
| 201 |
+
|
| 202 |
+
@@ -296,7 +311,13 @@ void quantize_tiles_kernel
|
| 203 |
+
decoded2 = decode_3inst_2<cb>(state0, state0 + 1);
|
| 204 |
+
}
|
| 205 |
+
dh2 = __hsub2(decoded2, w2);
|
| 206 |
+
- half2 err2 = __hfma2(dh2, dh2, __half2half2(temp_costs_inc[in_edge_idx]));
|
| 207 |
+
+ predecessor_cost = temp_costs_inc[in_edge_idx];
|
| 208 |
+
+ half2 err2 = __heq(predecessor_cost, H_INF)
|
| 209 |
+
+ ? __half2half2(H_INF)
|
| 210 |
+
+ : __hmin2(
|
| 211 |
+
+ __hfma2(
|
| 212 |
+
+ dh2, dh2, __half2half2(predecessor_cost)),
|
| 213 |
+
+ __half2half2(H_MAX_FINITE));
|
| 214 |
+
const unsigned less = __hlt2_mask(err2, min_err2);
|
| 215 |
+
min_err2 = __hmin2(err2, min_err2);
|
| 216 |
+
if (less & 0xffffu) min_k0 = k;
|
| 217 |
+
diff --git a/kquant/csrc/sqg_quantize.cu b/kquant/csrc/sqg_quantize.cu
|
| 218 |
+
index 42dcd88..e51210e 100644
|
| 219 |
+
--- a/kquant/csrc/sqg_quantize.cu
|
| 220 |
+
+++ b/kquant/csrc/sqg_quantize.cu
|
| 221 |
+
@@ -187,7 +187,7 @@ void quantize_tiles_sqg_cuda(
|
| 222 |
+
"output_indices shape mismatch");
|
| 223 |
+
TORCH_CHECK(output_indices.scalar_type() == at::kShort,
|
| 224 |
+
"output_indices must be int16");
|
| 225 |
+
- TORCH_CHECK(K >= 2 && K <= 4, "SQG validation kernel supports K2--K4");
|
| 226 |
+
+ TORCH_CHECK(K >= 2 && K <= 6, "SQG validation kernel supports K2--K6");
|
| 227 |
+
TORCH_CHECK(tailbite_context >= 1 && tailbite_context <= 128,
|
| 228 |
+
"tailbite_context must be in 1..128");
|
| 229 |
+
TORCH_CHECK(codebook.is_cuda() && codebook.device() == input_tiles.device(),
|
| 230 |
+
@@ -255,6 +255,22 @@ void quantize_tiles_sqg_cuda(
|
| 231 |
+
temp_edges.data_ptr<uint8_t>(), tiles, max_batch, shared_memory,
|
| 232 |
+
static_cast<int>(tailbite_context), stream);
|
| 233 |
+
break;
|
| 234 |
+
+ case 5:
|
| 235 |
+
+ launch_trellis<5, 4>(
|
| 236 |
+
+ input_tiles.data_ptr<float>(), output_tiles.data_ptr<float>(),
|
| 237 |
+
+ reinterpret_cast<uint16_t*>(output_indices.data_ptr<int16_t>()),
|
| 238 |
+
+ reinterpret_cast<half*>(temp_costs.data_ptr<at::Half>()),
|
| 239 |
+
+ temp_edges.data_ptr<uint8_t>(), tiles, max_batch, shared_memory,
|
| 240 |
+
+ static_cast<int>(tailbite_context), stream);
|
| 241 |
+
+ break;
|
| 242 |
+
+ case 6:
|
| 243 |
+
+ launch_trellis<6, 4>(
|
| 244 |
+
+ input_tiles.data_ptr<float>(), output_tiles.data_ptr<float>(),
|
| 245 |
+
+ reinterpret_cast<uint16_t*>(output_indices.data_ptr<int16_t>()),
|
| 246 |
+
+ reinterpret_cast<half*>(temp_costs.data_ptr<at::Half>()),
|
| 247 |
+
+ temp_edges.data_ptr<uint8_t>(), tiles, max_batch, shared_memory,
|
| 248 |
+
+ static_cast<int>(tailbite_context), stream);
|
| 249 |
+
+ break;
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
diff --git a/kquant/exl3_encoder_backend.py b/kquant/exl3_encoder_backend.py
|
| 254 |
+
index f810bf3..616b9e6 100644
|
| 255 |
+
--- a/kquant/exl3_encoder_backend.py
|
| 256 |
+
+++ b/kquant/exl3_encoder_backend.py
|
| 257 |
+
@@ -21,6 +21,18 @@ import threading
|
| 258 |
+
had_k, had_n = 128, 128
|
| 259 |
+
codebook_scale = 1.24371088
|
| 260 |
+
|
| 261 |
+
+# The original search used a 0.1..1.9 coarse bracket and could return 2.05
|
| 262 |
+
+# only because its five-point fine grid extended past the bracket. GLM-5.2
|
| 263 |
+
+# contains healthy tensors with a stable minimum around 3.0, so keep the cheap
|
| 264 |
+
+# original bracket as stage one and expand only when its upper point wins.
|
| 265 |
+
+G_SCALE_SEARCH_INITIAL_MIN = 0.1
|
| 266 |
+
+G_SCALE_SEARCH_INITIAL_MAX = 1.9
|
| 267 |
+
+G_SCALE_SEARCH_COARSE_STEP = 0.2
|
| 268 |
+
+G_SCALE_SEARCH_EXPANDED_MAX = 3.9
|
| 269 |
+
+G_SCALE_SEARCH_FINE_STEP = 0.075
|
| 270 |
+
+G_SCALE_SEARCH_HARD_CEILING = 4.05
|
| 271 |
+
+G_SCALE_SEARCH_CEILING_TOLERANCE = 1e-6
|
| 272 |
+
+
|
| 273 |
+
codebook_mcg_mult = 0xCBAC1FED
|
| 274 |
+
codebook_mul1_mult = 0x83DCD12D
|
| 275 |
+
|
| 276 |
+
@@ -319,6 +331,11 @@ def block_ldl(H: torch.Tensor, b: int, quant_args: dict, verbose: bool):
|
| 277 |
+
assert (n % b == 0)
|
| 278 |
+
m = n // b
|
| 279 |
+
|
| 280 |
+
+ keep_hessian_on_device = bool(quant_args.get("keep_hessian_on_device", False))
|
| 281 |
+
+ gpu_only_numerical_path = bool(quant_args.get("gpu_only_numerical_path", False))
|
| 282 |
+
+ if gpu_only_numerical_path and H.device.type != "cuda":
|
| 283 |
+
+ raise RuntimeError("GPU-only BlockLDLQ received a non-CUDA Hessian")
|
| 284 |
+
+
|
| 285 |
+
# Cholesky factorization: H = L @ L.T
|
| 286 |
+
# Try on GPU first
|
| 287 |
+
num_cholesky_retries = 0
|
| 288 |
+
@@ -326,12 +343,14 @@ def block_ldl(H: torch.Tensor, b: int, quant_args: dict, verbose: bool):
|
| 289 |
+
while True:
|
| 290 |
+
try:
|
| 291 |
+
L = torch.linalg.cholesky(H)
|
| 292 |
+
- # H is not needed after this, move to CPU. Then overwrite H's GPU storage with L, since we can't otherwise
|
| 293 |
+
- # free up that VRAM as the tensor is referenced by the parent frame
|
| 294 |
+
- H_cpu = H.cpu()
|
| 295 |
+
- H.copy_(L) # VRAM copy, tiny overhead
|
| 296 |
+
- L = H
|
| 297 |
+
- H = H_cpu
|
| 298 |
+
+ if not keep_hessian_on_device:
|
| 299 |
+
+ # Historical memory-saving path. Production retains both the
|
| 300 |
+
+ # caller-owned H and its factor on CUDA so neither numerical
|
| 301 |
+
+ # tensor crosses the host boundary.
|
| 302 |
+
+ H_cpu = H.cpu()
|
| 303 |
+
+ H.copy_(L) # VRAM copy, tiny overhead
|
| 304 |
+
+ L = H
|
| 305 |
+
+ H = H_cpu
|
| 306 |
+
break
|
| 307 |
+
|
| 308 |
+
except torch._C._LinAlgError as e:
|
| 309 |
+
@@ -346,6 +365,10 @@ def block_ldl(H: torch.Tensor, b: int, quant_args: dict, verbose: bool):
|
| 310 |
+
# Fall back on CPU factorization
|
| 311 |
+
except Exception as e:
|
| 312 |
+
if e.__class__.__name__ == "OutOfMemoryError" or "CUDA out of memory" in str(e) or "HIP out of memory" in str(e):
|
| 313 |
+
+ if gpu_only_numerical_path:
|
| 314 |
+
+ raise RuntimeError(
|
| 315 |
+
+ "GPU-only BlockLDLQ exhausted CUDA memory; CPU fallback is forbidden"
|
| 316 |
+
+ ) from e
|
| 317 |
+
retry_cpu = True
|
| 318 |
+
break
|
| 319 |
+
else:
|
| 320 |
+
@@ -380,7 +403,7 @@ def block_ldl(H: torch.Tensor, b: int, quant_args: dict, verbose: bool):
|
| 321 |
+
# Insert block identity matrices along the diagonal.
|
| 322 |
+
# TODO: Figure out if this is necessary. Diagonal blocks should already be identities after previous step
|
| 323 |
+
L_block = L.view(m, b, m, b).permute(0, 2, 1,3)
|
| 324 |
+
- dr = torch.arange(m)
|
| 325 |
+
+ dr = torch.arange(m, device=L.device)
|
| 326 |
+
L_block[dr, dr] = torch.stack([torch.eye(b, device = L.device, dtype = H.dtype)] * m)
|
| 327 |
+
|
| 328 |
+
return L, H # , D.to(DL.device)
|
| 329 |
+
@@ -1585,6 +1608,43 @@ def ldlq_batched(
|
| 330 |
+
|
| 331 |
+
finalize_capture_H_mutex = threading.Lock()
|
| 332 |
+
|
| 333 |
+
+
|
| 334 |
+
+def _explicit_vector(
|
| 335 |
+
+ value: torch.Tensor,
|
| 336 |
+
+ *,
|
| 337 |
+
+ length: int,
|
| 338 |
+
+ name: str,
|
| 339 |
+
+ device: torch.device,
|
| 340 |
+
+ orientation: str,
|
| 341 |
+
+ signs: bool = False,
|
| 342 |
+
+) -> torch.Tensor:
|
| 343 |
+
+ """Validate a caller-owned regularization vector without retaining it.
|
| 344 |
+
+
|
| 345 |
+
+ QSRT's original shared-input-scale hook stores the first tensor's profile
|
| 346 |
+
+ in module-global state. Model ports that require a topology-neutral
|
| 347 |
+
+ layer-wide vector need a deterministic, caller-owned contract instead.
|
| 348 |
+
+ These optional vectors are cloned for each encode and never enter the
|
| 349 |
+
+ process-global cache.
|
| 350 |
+
+ """
|
| 351 |
+
+
|
| 352 |
+
+ if not isinstance(value, torch.Tensor):
|
| 353 |
+
+ raise TypeError(f"{name} must be a torch.Tensor")
|
| 354 |
+
+ if value.ndim != 1 or value.numel() != length:
|
| 355 |
+
+ raise ValueError(f"{name} must have shape ({length},)")
|
| 356 |
+
+ vector = value.detach().to(device=device, dtype=torch.float32, copy=True)
|
| 357 |
+
+ if not bool(torch.isfinite(vector).all()):
|
| 358 |
+
+ raise ValueError(f"{name} must contain only finite values")
|
| 359 |
+
+ if signs:
|
| 360 |
+
+ if not bool((vector.abs() == 1).all()):
|
| 361 |
+
+ raise ValueError(f"{name} must contain only -1 and +1")
|
| 362 |
+
+ elif not bool((vector > 0).all()):
|
| 363 |
+
+ raise ValueError(f"{name} must contain only positive values")
|
| 364 |
+
+ if orientation == "column":
|
| 365 |
+
+ return vector.unsqueeze(1).contiguous()
|
| 366 |
+
+ if orientation == "row":
|
| 367 |
+
+ return vector.unsqueeze(0).contiguous()
|
| 368 |
+
+ raise ValueError(f"unsupported explicit-vector orientation: {orientation}")
|
| 369 |
+
+
|
| 370 |
+
def finalize_capture_H(H_data: dict, quant_args: dict, verbose: bool):
|
| 371 |
+
with finalize_capture_H_mutex:
|
| 372 |
+
|
| 373 |
+
@@ -1596,7 +1656,23 @@ def finalize_capture_H(H_data: dict, quant_args: dict, verbose: bool):
|
| 374 |
+
|
| 375 |
+
H = H_data["H"]
|
| 376 |
+
k = H.shape[0]
|
| 377 |
+
- su = (torch.randn(k, device = H_data["device"]).sign() + 1e-5).sign().to(torch.float).unsqueeze(1)
|
| 378 |
+
+ explicit_input_signs = quant_args.get("input_signs")
|
| 379 |
+
+ if explicit_input_signs is None:
|
| 380 |
+
+ su = (
|
| 381 |
+
+ (torch.randn(k, device=H_data["device"]).sign() + 1e-5)
|
| 382 |
+
+ .sign()
|
| 383 |
+
+ .to(torch.float)
|
| 384 |
+
+ .unsqueeze(1)
|
| 385 |
+
+ )
|
| 386 |
+
+ else:
|
| 387 |
+
+ su = _explicit_vector(
|
| 388 |
+
+ explicit_input_signs,
|
| 389 |
+
+ length=k,
|
| 390 |
+
+ name="input_signs",
|
| 391 |
+
+ device=H_data["device"],
|
| 392 |
+
+ orientation="column",
|
| 393 |
+
+ signs=True,
|
| 394 |
+
+ )
|
| 395 |
+
H_data["su"] = su
|
| 396 |
+
|
| 397 |
+
return True, None, None, su, None
|
| 398 |
+
@@ -1608,7 +1684,28 @@ def finalize_capture_H(H_data: dict, quant_args: dict, verbose: bool):
|
| 399 |
+
|
| 400 |
+
H = H_data["H"]
|
| 401 |
+
if H_data["finalized"]:
|
| 402 |
+
- return H_data["q_fallback"], H, H_data["L"], H_data["su"], H_data["diag"]
|
| 403 |
+
+ explicit_input_signs = quant_args.get("input_signs")
|
| 404 |
+
+ if explicit_input_signs is not None:
|
| 405 |
+
+ expected = _explicit_vector(
|
| 406 |
+
+ explicit_input_signs,
|
| 407 |
+
+ length=H.shape[0],
|
| 408 |
+
+ name="input_signs",
|
| 409 |
+
+ device=H_data["su"].device,
|
| 410 |
+
+ orientation="column",
|
| 411 |
+
+ signs=True,
|
| 412 |
+
+ )
|
| 413 |
+
+ if not torch.equal(H_data["su"], expected):
|
| 414 |
+
+ raise ValueError(
|
| 415 |
+
+ "finalized Hessian input signs do not match the "
|
| 416 |
+
+ "caller-supplied profile"
|
| 417 |
+
+ )
|
| 418 |
+
+ return (
|
| 419 |
+
+ H_data["q_fallback"],
|
| 420 |
+
+ H,
|
| 421 |
+
+ H_data["L"],
|
| 422 |
+
+ H_data["su"],
|
| 423 |
+
+ H_data["diag"],
|
| 424 |
+
+ )
|
| 425 |
+
|
| 426 |
+
# Mean of samples summed up during forward pass
|
| 427 |
+
# Switch to uncalibrated fallback if no input activations or diagonal is too small (few activations)
|
| 428 |
+
@@ -1634,7 +1731,23 @@ def finalize_capture_H(H_data: dict, quant_args: dict, verbose: bool):
|
| 429 |
+
|
| 430 |
+
# Random sign flips for input channel, fixed for the first linear layer to quantize with this H
|
| 431 |
+
k = H.shape[0]
|
| 432 |
+
- su = (torch.randn(k, device = H.device).sign() + 1e-5).sign().to(torch.float).unsqueeze(1)
|
| 433 |
+
+ explicit_input_signs = quant_args.get("input_signs")
|
| 434 |
+
+ if explicit_input_signs is None:
|
| 435 |
+
+ su = (
|
| 436 |
+
+ (torch.randn(k, device=H.device).sign() + 1e-5)
|
| 437 |
+
+ .sign()
|
| 438 |
+
+ .to(torch.float)
|
| 439 |
+
+ .unsqueeze(1)
|
| 440 |
+
+ )
|
| 441 |
+
+ else:
|
| 442 |
+
+ su = _explicit_vector(
|
| 443 |
+
+ explicit_input_signs,
|
| 444 |
+
+ length=k,
|
| 445 |
+
+ name="input_signs",
|
| 446 |
+
+ device=H.device,
|
| 447 |
+
+ orientation="column",
|
| 448 |
+
+ signs=True,
|
| 449 |
+
+ )
|
| 450 |
+
H_data["su"] = su
|
| 451 |
+
|
| 452 |
+
# Input had
|
| 453 |
+
@@ -1648,14 +1761,21 @@ def finalize_capture_H(H_data: dict, quant_args: dict, verbose: bool):
|
| 454 |
+
L = None
|
| 455 |
+
else:
|
| 456 |
+
L, H = block_ldl(H, 16, quant_args, verbose)
|
| 457 |
+
- dr = torch.arange(k)
|
| 458 |
+
+ dr = torch.arange(k, device=L.device)
|
| 459 |
+
L[dr, dr] = 0
|
| 460 |
+
|
| 461 |
+
H_data["L"] = L
|
| 462 |
+
|
| 463 |
+
- # H is no longer needed except to compute proxy error so move to CPU
|
| 464 |
+
- H = H.cpu()
|
| 465 |
+
- H_data["H"] = H.cpu()
|
| 466 |
+
+ # Paid B300 production keeps caller-owned H/L on the quant device.
|
| 467 |
+
+ # CPU storage remains the default for historical/offline callers.
|
| 468 |
+
+ keep_hessian_on_device = bool(
|
| 469 |
+
+ quant_args.get("keep_hessian_on_device", False)
|
| 470 |
+
+ )
|
| 471 |
+
+ if keep_hessian_on_device:
|
| 472 |
+
+ H_data["H"] = H
|
| 473 |
+
+ else:
|
| 474 |
+
+ H = H.cpu()
|
| 475 |
+
+ H_data["H"] = H.cpu()
|
| 476 |
+
|
| 477 |
+
H_data["finalized"] = True
|
| 478 |
+
H_data["diag"] = diag
|
| 479 |
+
@@ -1752,23 +1872,63 @@ def g_scale_search_batch(
|
| 480 |
+
i = j
|
| 481 |
+
return out
|
| 482 |
+
|
| 483 |
+
- # Stage 1: coarse grid over the full search range, on a subsample of each tensor's tiles
|
| 484 |
+
- coarse = [0.1 + 0.2 * i for i in range(10)] # 0.1 .. 1.9, same range as the old golden-section bracket
|
| 485 |
+
+ # Stage 1: retain the historical cheap bracket on a tile subsample. Only
|
| 486 |
+
+ # tensors whose minimum lands at the upper point pay for the expansion.
|
| 487 |
+
+ coarse = [
|
| 488 |
+
+ G_SCALE_SEARCH_INITIAL_MIN + G_SCALE_SEARCH_COARSE_STEP * i
|
| 489 |
+
+ for i in range(
|
| 490 |
+
+ round(
|
| 491 |
+
+ (G_SCALE_SEARCH_INITIAL_MAX - G_SCALE_SEARCH_INITIAL_MIN)
|
| 492 |
+
+ / G_SCALE_SEARCH_COARSE_STEP
|
| 493 |
+
+ )
|
| 494 |
+
+ + 1
|
| 495 |
+
+ )
|
| 496 |
+
+ ]
|
| 497 |
+
subs = [s[::3] for s in samples]
|
| 498 |
+
pairs1 = [(t, s) for t in range(n_t) for s in coarse]
|
| 499 |
+
mse1 = eval_pairs(pairs1, subs).view(n_t, len(coarse))
|
| 500 |
+
- centers = [coarse[c] for c in mse1.argmin(dim = 1).tolist()]
|
| 501 |
+
+ coarse_best = mse1.argmin(dim = 1).tolist()
|
| 502 |
+
+ centers = [coarse[c] for c in coarse_best]
|
| 503 |
+
+ expanded = [index == len(coarse) - 1 for index in coarse_best]
|
| 504 |
+
+
|
| 505 |
+
+ expansion = [
|
| 506 |
+
+ G_SCALE_SEARCH_INITIAL_MAX + G_SCALE_SEARCH_COARSE_STEP * (i + 1)
|
| 507 |
+
+ for i in range(
|
| 508 |
+
+ round(
|
| 509 |
+
+ (G_SCALE_SEARCH_EXPANDED_MAX - G_SCALE_SEARCH_INITIAL_MAX)
|
| 510 |
+
+ / G_SCALE_SEARCH_COARSE_STEP
|
| 511 |
+
+ )
|
| 512 |
+
+ )
|
| 513 |
+
+ ]
|
| 514 |
+
+ expanded_tensors = [t for t, needed in enumerate(expanded) if needed]
|
| 515 |
+
+ if expanded_tensors:
|
| 516 |
+
+ pairs_x = [(t, scale) for t in expanded_tensors for scale in expansion]
|
| 517 |
+
+ mse_x = eval_pairs(pairs_x, subs).view(len(expanded_tensors), len(expansion))
|
| 518 |
+
+ for row, tensor_index in enumerate(expanded_tensors):
|
| 519 |
+
+ values = torch.cat((mse1[tensor_index], mse_x[row]))
|
| 520 |
+
+ combined = coarse + expansion
|
| 521 |
+
+ centers[tensor_index] = combined[int(values.argmin().item())]
|
| 522 |
+
|
| 523 |
+
# Stage 2: fine grid around each tensor's coarse minimum, on its full sample
|
| 524 |
+
- step = 0.075
|
| 525 |
+
+ step = G_SCALE_SEARCH_FINE_STEP
|
| 526 |
+
fine = [[c + step * (i - 2) for i in range(5)] for c in centers]
|
| 527 |
+
pairs2 = [(t, s) for t in range(n_t) for s in fine[t]]
|
| 528 |
+
mse2 = eval_pairs(pairs2, samples).view(n_t, 5)
|
| 529 |
+
mse2_h = mse2.tolist()
|
| 530 |
+
|
| 531 |
+
results = []
|
| 532 |
+
+ evidence = []
|
| 533 |
+
for t in range(n_t):
|
| 534 |
+
best = min(range(5), key = lambda i: mse2_h[t][i])
|
| 535 |
+
+ if (
|
| 536 |
+
+ fine[t][best]
|
| 537 |
+
+ >= G_SCALE_SEARCH_HARD_CEILING - G_SCALE_SEARCH_CEILING_TOLERANCE
|
| 538 |
+
+ ):
|
| 539 |
+
+ raise RuntimeError(
|
| 540 |
+
+ "global scale search saturated at its hard upper ceiling: "
|
| 541 |
+
+ f"scale={fine[t][best]:.8g}, "
|
| 542 |
+
+ f"ceiling={G_SCALE_SEARCH_HARD_CEILING:.8g}"
|
| 543 |
+
+ )
|
| 544 |
+
if 0 < best < 4:
|
| 545 |
+
y0, y1, y2 = mse2_h[t][best - 1], mse2_h[t][best], mse2_h[t][best + 1]
|
| 546 |
+
denom = y0 - 2.0 * y1 + y2
|
| 547 |
+
@@ -1777,7 +1937,31 @@ def g_scale_search_batch(
|
| 548 |
+
else:
|
| 549 |
+
offset = 0.0
|
| 550 |
+
best_scale = max(fine[t][best] + offset * step, 0.01)
|
| 551 |
+
+ if (
|
| 552 |
+
+ best_scale
|
| 553 |
+
+ >= G_SCALE_SEARCH_HARD_CEILING - G_SCALE_SEARCH_CEILING_TOLERANCE
|
| 554 |
+
+ ):
|
| 555 |
+
+ raise RuntimeError(
|
| 556 |
+
+ "global scale interpolation reached its hard upper ceiling: "
|
| 557 |
+
+ f"scale={best_scale:.8g}, "
|
| 558 |
+
+ f"ceiling={G_SCALE_SEARCH_HARD_CEILING:.8g}"
|
| 559 |
+
+ )
|
| 560 |
+
results.append((best_scale, mse2[t, best]))
|
| 561 |
+
+ evidence.append(
|
| 562 |
+
+ {
|
| 563 |
+
+ "policy": "raw_sample_adaptive_coarse_then_fine_v2",
|
| 564 |
+
+ "initial_min": G_SCALE_SEARCH_INITIAL_MIN,
|
| 565 |
+
+ "initial_max": G_SCALE_SEARCH_INITIAL_MAX,
|
| 566 |
+
+ "expanded": expanded[t],
|
| 567 |
+
+ "expanded_max": G_SCALE_SEARCH_EXPANDED_MAX,
|
| 568 |
+
+ "fine_min": fine[t][0],
|
| 569 |
+
+ "fine_max": fine[t][-1],
|
| 570 |
+
+ "hard_ceiling": G_SCALE_SEARCH_HARD_CEILING,
|
| 571 |
+
+ "selected_scale": best_scale,
|
| 572 |
+
+ "selected_is_below_hard_ceiling": True,
|
| 573 |
+
+ }
|
| 574 |
+
+ )
|
| 575 |
+
+ quant_args["g_scale_search_evidence"] = evidence
|
| 576 |
+
return results
|
| 577 |
+
|
| 578 |
+
|
| 579 |
+
@@ -1867,6 +2051,16 @@ _SHARED_INPUT_SCALES: dict = {}
|
| 580 |
+
def output_signs(n: int, device: torch.device, quant_args: dict) -> torch.Tensor:
|
| 581 |
+
"""Draw output signs from an optional transform-specific RNG stream."""
|
| 582 |
+
|
| 583 |
+
+ explicit_output_signs = quant_args.get("output_signs")
|
| 584 |
+
+ if explicit_output_signs is not None:
|
| 585 |
+
+ return _explicit_vector(
|
| 586 |
+
+ explicit_output_signs,
|
| 587 |
+
+ length=n,
|
| 588 |
+
+ name="output_signs",
|
| 589 |
+
+ device=device,
|
| 590 |
+
+ orientation="row",
|
| 591 |
+
+ signs=True,
|
| 592 |
+
+ )
|
| 593 |
+
if "sv_seed" in quant_args:
|
| 594 |
+
torch.manual_seed(quant_args["sv_seed"])
|
| 595 |
+
return (
|
| 596 |
+
@@ -1920,35 +2114,81 @@ def regularize(
|
| 597 |
+
print(f" - input state skew: {skew_factor.item():.6f}")
|
| 598 |
+
|
| 599 |
+
if force_out_scales is None:
|
| 600 |
+
- apply_out_scales = skew_factor.item() < 0.15
|
| 601 |
+
+ apply_out_scales_mask = skew_factor < 0.15
|
| 602 |
+
else:
|
| 603 |
+
- apply_out_scales = force_out_scales
|
| 604 |
+
+ apply_out_scales_mask = torch.tensor(
|
| 605 |
+
+ bool(force_out_scales), dtype=torch.bool, device=weight.device
|
| 606 |
+
+ )
|
| 607 |
+
|
| 608 |
+
else:
|
| 609 |
+
- apply_out_scales = True if force_out_scales is None else force_out_scales
|
| 610 |
+
+ apply_out_scales_mask = torch.tensor(
|
| 611 |
+
+ True if force_out_scales is None else bool(force_out_scales),
|
| 612 |
+
+ dtype=torch.bool,
|
| 613 |
+
+ device=weight.device,
|
| 614 |
+
+ )
|
| 615 |
+
|
| 616 |
+
if q_fallback:
|
| 617 |
+
- apply_out_scales = force_out_scales
|
| 618 |
+
-
|
| 619 |
+
- # Apply output scales
|
| 620 |
+
- out_channel_scales = block_rms(weight, dim = 0, keepdim = True)
|
| 621 |
+
- mean = out_channel_scales.mean().item()
|
| 622 |
+
- if mean > 1e-30:
|
| 623 |
+
- out_channel_scales /= mean
|
| 624 |
+
- quant_args["zeros"] = False
|
| 625 |
+
+ # Preserve the historical truthiness of a missing fallback override.
|
| 626 |
+
+ apply_out_scales_mask = torch.tensor(
|
| 627 |
+
+ False if force_out_scales is None else bool(force_out_scales),
|
| 628 |
+
+ dtype=torch.bool,
|
| 629 |
+
+ device=weight.device,
|
| 630 |
+
+ )
|
| 631 |
+
+
|
| 632 |
+
+ # Apply output scales. An explicit profile is caller-owned and bypasses
|
| 633 |
+
+ # first-tensor derivation, which is required for topology-neutral ports.
|
| 634 |
+
+ explicit_output_scales = quant_args.get("output_channel_scale_profile")
|
| 635 |
+
+ observed_out_channel_scales = block_rms(weight, dim = 0, keepdim = True)
|
| 636 |
+
+ if explicit_output_scales is not None:
|
| 637 |
+
+ if force_out_scales is False:
|
| 638 |
+
+ raise ValueError(
|
| 639 |
+
+ "output_channel_scale_profile requires apply_out_scales=True"
|
| 640 |
+
+ )
|
| 641 |
+
+ apply_out_scales_mask = torch.ones(
|
| 642 |
+
+ (), dtype=torch.bool, device=weight.device
|
| 643 |
+
+ )
|
| 644 |
+
+ out_channel_scales = _explicit_vector(
|
| 645 |
+
+ explicit_output_scales,
|
| 646 |
+
+ length=weight.shape[1],
|
| 647 |
+
+ name="output_channel_scale_profile",
|
| 648 |
+
+ device=weight.device,
|
| 649 |
+
+ orientation="row",
|
| 650 |
+
+ )
|
| 651 |
+
else:
|
| 652 |
+
- quant_args["zeros"] = True
|
| 653 |
+
- if force_out_scales is not None:
|
| 654 |
+
- apply_out_scales = True
|
| 655 |
+
- zero_out_scales = out_channel_scales.abs() < 1e-30
|
| 656 |
+
-
|
| 657 |
+
- if apply_out_scales:
|
| 658 |
+
- out_channel_scales[zero_out_scales] = 0.1
|
| 659 |
+
- sv = (sv * out_channel_scales + 1e-10).float()
|
| 660 |
+
- if verbose:
|
| 661 |
+
- out_channel_std = out_channel_scales.std().item()
|
| 662 |
+
- out_channel_mean = out_channel_scales.mean().item()
|
| 663 |
+
- print(f" - out ch scales std/mean: {out_channel_std:.6f} {out_channel_mean:.6f}")
|
| 664 |
+
+ out_channel_scales = observed_out_channel_scales
|
| 665 |
+
+ mean = out_channel_scales.mean()
|
| 666 |
+
+ mean_positive = mean > 1e-30
|
| 667 |
+
+ if explicit_output_scales is None:
|
| 668 |
+
+ # Keep the data-dependent normalization decision on CUDA. The
|
| 669 |
+
+ # clamped denominator is ignored by torch.where for an all-zero
|
| 670 |
+
+ # profile and therefore cannot alter that historical branch.
|
| 671 |
+
+ out_channel_scales = torch.where(
|
| 672 |
+
+ mean_positive,
|
| 673 |
+
+ out_channel_scales / mean.clamp_min(1e-30),
|
| 674 |
+
+ out_channel_scales,
|
| 675 |
+
+ )
|
| 676 |
+
+ if force_out_scales is not None:
|
| 677 |
+
+ # Historical behavior forces scaling for an all-zero tensor even when
|
| 678 |
+
+ # an explicit boolean override was supplied.
|
| 679 |
+
+ apply_out_scales_mask = torch.where(
|
| 680 |
+
+ mean_positive,
|
| 681 |
+
+ apply_out_scales_mask,
|
| 682 |
+
+ torch.ones_like(apply_out_scales_mask),
|
| 683 |
+
+ )
|
| 684 |
+
+ zero_out_scales = observed_out_channel_scales.abs() < 1e-30
|
| 685 |
+
+ if explicit_output_scales is not None and bool(zero_out_scales.any()):
|
| 686 |
+
+ raise ValueError(
|
| 687 |
+
+ "an explicit output scale profile cannot encode a zero output channel"
|
| 688 |
+
+ )
|
| 689 |
+
+
|
| 690 |
+
+ candidate_out_channel_scales = out_channel_scales.clone()
|
| 691 |
+
+ candidate_out_channel_scales[zero_out_scales] = 0.1
|
| 692 |
+
+ scaled_sv = (sv * candidate_out_channel_scales + 1e-10).float()
|
| 693 |
+
+ sv = torch.where(apply_out_scales_mask, scaled_sv, sv)
|
| 694 |
+
+ if verbose:
|
| 695 |
+
+ out_channel_std = candidate_out_channel_scales.std().item()
|
| 696 |
+
+ out_channel_mean = candidate_out_channel_scales.mean().item()
|
| 697 |
+
+ print(f" - out ch scales std/mean: {out_channel_std:.6f} {out_channel_mean:.6f}")
|
| 698 |
+
|
| 699 |
+
# Output sign flips (and scales)
|
| 700 |
+
weight /= sv
|
| 701 |
+
@@ -1960,13 +2200,28 @@ def regularize(
|
| 702 |
+
blockwise_preapply_had_r_(weight, had_n)
|
| 703 |
+
|
| 704 |
+
# Input sign flips and scales
|
| 705 |
+
- in_channel_scales = block_rms(weight, dim = 1, keepdim = True)
|
| 706 |
+
- in_channel_scales[in_channel_scales.abs() < 1e-30] = 0.1
|
| 707 |
+
+ explicit_input_scales = quant_args.get("input_channel_scale_profile")
|
| 708 |
+
+ if explicit_input_scales is None:
|
| 709 |
+
+ in_channel_scales = block_rms(weight, dim = 1, keepdim = True)
|
| 710 |
+
+ in_channel_scales[in_channel_scales.abs() < 1e-30] = 0.1
|
| 711 |
+
+ else:
|
| 712 |
+
+ in_channel_scales = _explicit_vector(
|
| 713 |
+
+ explicit_input_scales,
|
| 714 |
+
+ length=weight.shape[0],
|
| 715 |
+
+ name="input_channel_scale_profile",
|
| 716 |
+
+ device=weight.device,
|
| 717 |
+
+ orientation="column",
|
| 718 |
+
+ )
|
| 719 |
+
# kquant shared-su mode: reuse the first tensor's channel-scale profile for
|
| 720 |
+
# every tensor with the same key (e.g. all experts of a MoE layer/matrix),
|
| 721 |
+
# so the stored su vector is identical across them and TP replicas can
|
| 722 |
+
# broadcast one copy. Trellis codes re-fit against the shared profile.
|
| 723 |
+
shared_key = quant_args.get("shared_input_scales_key")
|
| 724 |
+
+ if explicit_input_scales is not None and shared_key is not None:
|
| 725 |
+
+ raise ValueError(
|
| 726 |
+
+ "input_channel_scale_profile cannot be combined with the "
|
| 727 |
+
+ "module-global shared_input_scales_key"
|
| 728 |
+
+ )
|
| 729 |
+
if shared_key is not None:
|
| 730 |
+
in_channel_scales = _SHARED_INPUT_SCALES.setdefault(
|
| 731 |
+
shared_key, in_channel_scales)
|
| 732 |
+
@@ -1976,7 +2231,49 @@ def regularize(
|
| 733 |
+
|
| 734 |
+
# Determine best scale for matrix by test quantizing a sample of tiles along a wrapped diagonal
|
| 735 |
+
if not skip_g_scale:
|
| 736 |
+
- g_scale, mse_scale = g_scale_gss(weight, False, quant_args, pb = pb)
|
| 737 |
+
+ override = quant_args.get("g_scale_override")
|
| 738 |
+
+ if override is None:
|
| 739 |
+
+ g_scale, mse_scale = g_scale_gss(weight, False, quant_args, pb = pb)
|
| 740 |
+
+ else:
|
| 741 |
+
+ if (
|
| 742 |
+
+ isinstance(override, bool)
|
| 743 |
+
+ or not isinstance(override, (int, float))
|
| 744 |
+
+ or not math.isfinite(float(override))
|
| 745 |
+
+ or float(override) <= 0.0
|
| 746 |
+
+ or float(override)
|
| 747 |
+
+ >= G_SCALE_SEARCH_HARD_CEILING - G_SCALE_SEARCH_CEILING_TOLERANCE
|
| 748 |
+
+ ):
|
| 749 |
+
+ raise ValueError(
|
| 750 |
+
+ "g_scale_override must be positive, finite, and below the "
|
| 751 |
+
+ "hard scale ceiling"
|
| 752 |
+
+ )
|
| 753 |
+
+ policy = quant_args.get("g_scale_override_policy")
|
| 754 |
+
+ evidence_id = quant_args.get("g_scale_override_evidence_id")
|
| 755 |
+
+ if policy != "activation_parametric_coupled_v1":
|
| 756 |
+
+ raise ValueError(
|
| 757 |
+
+ "serial g_scale_override requires the activation-parametric "
|
| 758 |
+
+ "coupled policy"
|
| 759 |
+
+ )
|
| 760 |
+
+ if (
|
| 761 |
+
+ not isinstance(evidence_id, str)
|
| 762 |
+
+ or len(evidence_id) != 64
|
| 763 |
+
+ or any(ch not in "0123456789abcdef" for ch in evidence_id)
|
| 764 |
+
+ ):
|
| 765 |
+
+ raise ValueError(
|
| 766 |
+
+ "serial g_scale_override requires a lowercase SHA-256 evidence ID"
|
| 767 |
+
+ )
|
| 768 |
+
+ g_scale = float(override)
|
| 769 |
+
+ mse_scale = torch.tensor(float("nan"), device=weight.device)
|
| 770 |
+
+ quant_args["g_scale_search_evidence"] = [
|
| 771 |
+
+ {
|
| 772 |
+
+ "policy": policy,
|
| 773 |
+
+ "evidence_id": evidence_id,
|
| 774 |
+
+ "raw_sample_search_performed": False,
|
| 775 |
+
+ "hard_ceiling": G_SCALE_SEARCH_HARD_CEILING,
|
| 776 |
+
+ "selected_scale": g_scale,
|
| 777 |
+
+ "selected_is_below_hard_ceiling": True,
|
| 778 |
+
+ }
|
| 779 |
+
+ ]
|
| 780 |
+
else:
|
| 781 |
+
g_scale = 1.0
|
| 782 |
+
weight, su, sv = apply_g_scale(
|
| 783 |
+
@@ -1987,6 +2284,11 @@ def regularize(
|
| 784 |
+
into_sv=bool(quant_args.get("g_scale_into_sv")),
|
| 785 |
+
)
|
| 786 |
+
|
| 787 |
+
+ # The transform is now numerically frozen. Only manifest booleans cross
|
| 788 |
+
+ # to the host; neither value can feed back into weight construction.
|
| 789 |
+
+ apply_out_scales = bool(apply_out_scales_mask.item())
|
| 790 |
+
+ quant_args["zeros"] = bool((~mean_positive).item())
|
| 791 |
+
+
|
| 792 |
+
# ext.test_distribution(weight_os, dist_r, dist_ref, -3.8, 3.8)
|
| 793 |
+
# js_os = jsd(dist_r, dist_ref)
|
| 794 |
+
|
| 795 |
+
@@ -2119,8 +2421,12 @@ def quantize_qsrt(
|
| 796 |
+
H_diag = H_diag.to(device)
|
| 797 |
+
sv = output_signs(n, device, quant_args)
|
| 798 |
+
|
| 799 |
+
- # Move stored L to CPU (if not already), move working L to device
|
| 800 |
+
- if H_data["L"] is not None:
|
| 801 |
+
+ # Historical callers store L on CPU. The B300 path retains it on GPU
|
| 802 |
+
+ # so a shared H13 session does not round-trip a 6144-square factor for
|
| 803 |
+
+ # every gate/up candidate.
|
| 804 |
+
+ if H_data["L"] is not None and not quant_args.get(
|
| 805 |
+
+ "keep_hessian_on_device", False
|
| 806 |
+
+ ):
|
| 807 |
+
H_data["L"] = H_data["L"].cpu()
|
| 808 |
+
if L is not None:
|
| 809 |
+
L = L.to(device)
|
| 810 |
+
@@ -2160,7 +2466,9 @@ def quantize_qsrt(
|
| 811 |
+
|
| 812 |
+
# Select device for work buffers (CPU is slower for small tensors but saves a lot of VRAM on big ones)
|
| 813 |
+
# TODO: Use pynvml or mem_get_info to predict whether CPU buffer is needed
|
| 814 |
+
- if weight_r.numel() > 5e8:
|
| 815 |
+
+ if weight_r.numel() > 5e8 and not quant_args.get(
|
| 816 |
+
+ "gpu_only_numerical_path", False
|
| 817 |
+
+ ):
|
| 818 |
+
weight_r = weight_r.cpu()
|
| 819 |
+
|
| 820 |
+
# Quantize
|
| 821 |
+
@@ -2305,6 +2613,8 @@ def quantize_qsrt_batch(
|
| 822 |
+
"ldlq_tf32",
|
| 823 |
+
"tailbite_context",
|
| 824 |
+
"g_scale_override",
|
| 825 |
+
+ "keep_hessian_on_device",
|
| 826 |
+
+ "gpu_only_numerical_path",
|
| 827 |
+
)
|
| 828 |
+
for group in quant_args_groups:
|
| 829 |
+
reference = group[0]
|
| 830 |
+
@@ -2338,7 +2648,9 @@ def quantize_qsrt_batch(
|
| 831 |
+
su = su.to(device)
|
| 832 |
+
if H_diag is not None:
|
| 833 |
+
H_diag = H_diag.to(device)
|
| 834 |
+
- if H_data["L"] is not None:
|
| 835 |
+
+ if H_data["L"] is not None and not args.get(
|
| 836 |
+
+ "keep_hessian_on_device", False
|
| 837 |
+
+ ):
|
| 838 |
+
H_data["L"] = H_data["L"].cpu()
|
| 839 |
+
sv = output_signs(weight.shape[1], device, args)
|
| 840 |
+
apply_out_scales, weight_r, _, su, sv = regularize(
|
| 841 |
+
diff --git a/kquant/exl3_reference.py b/kquant/exl3_reference.py
|
| 842 |
+
index b1a4e1e..01b4b5d 100644
|
| 843 |
+
--- a/kquant/exl3_reference.py
|
| 844 |
+
+++ b/kquant/exl3_reference.py
|
| 845 |
+
@@ -30,8 +30,8 @@ QSRT_CODEBOOKS = (
|
| 846 |
+
|
| 847 |
+
|
| 848 |
+
def _validate_bits(bits: int) -> None:
|
| 849 |
+
- if isinstance(bits, bool) or not isinstance(bits, int) or bits not in (2, 3, 4):
|
| 850 |
+
- raise ValueError("the QSRT reference supports K=2, K=3, or K=4")
|
| 851 |
+
+ if isinstance(bits, bool) or not isinstance(bits, int) or bits not in range(2, 7):
|
| 852 |
+
+ raise ValueError("the SQG reference supports K=2 through K=6")
|
| 853 |
+
|
| 854 |
+
|
| 855 |
+
def reconstruct_trellis_states(
|
| 856 |
+
@@ -150,9 +150,9 @@ def decode_qsrt_regularized_weight(
|
| 857 |
+
tile_bits: tuple[int, ...],
|
| 858 |
+
codebook: str,
|
| 859 |
+
) -> torch.Tensor:
|
| 860 |
+
- """Decode a K2/K3/K4 tile map to transformed ``[K, N]`` weights.
|
| 861 |
+
+ """Decode a K2--K6 tile map to transformed ``[K, N]`` weights.
|
| 862 |
+
|
| 863 |
+
- SQG uses a distinct state labelling for K2, K3, and K4, so the physical
|
| 864 |
+
+ SQG uses a distinct state labelling for every supported K, so the physical
|
| 865 |
+
tile rate is part of the numerical decode contract.
|
| 866 |
+
"""
|
| 867 |
+
|
| 868 |
+
@@ -164,10 +164,10 @@ def decode_qsrt_regularized_weight(
|
| 869 |
+
if len(tile_bits) != states.shape[axis]:
|
| 870 |
+
raise ValueError("tile_bits length does not match the selected rate axis")
|
| 871 |
+
if any(
|
| 872 |
+
- isinstance(value, bool) or not isinstance(value, int) or value not in (2, 3, 4)
|
| 873 |
+
+ isinstance(value, bool) or not isinstance(value, int) or value not in range(2, 7)
|
| 874 |
+
for value in tile_bits
|
| 875 |
+
):
|
| 876 |
+
- raise ValueError("tile_bits must contain only integer K2, K3, or K4")
|
| 877 |
+
+ raise ValueError("tile_bits must contain only integer K2 through K6")
|
| 878 |
+
if codebook not in QSRT_CODEBOOKS:
|
| 879 |
+
raise ValueError(f"unsupported QSRT codebook: {codebook}")
|
| 880 |
+
|
| 881 |
+
diff --git a/kquant/qsrt.py b/kquant/qsrt.py
|
| 882 |
+
index 0424724..1bfd2a0 100644
|
| 883 |
+
--- a/kquant/qsrt.py
|
| 884 |
+
+++ b/kquant/qsrt.py
|
| 885 |
+
@@ -804,8 +804,8 @@ def repack_encoded_records(
|
| 886 |
+
|
| 887 |
+
|
| 888 |
+
def _validate_k(bits: int) -> None:
|
| 889 |
+
- if isinstance(bits, bool) or not isinstance(bits, int) or bits not in (2, 3, 4):
|
| 890 |
+
- raise ValueError("QSRT supports K=2, K=3, or K=4")
|
| 891 |
+
+ if isinstance(bits, bool) or not isinstance(bits, int) or bits not in range(2, 7):
|
| 892 |
+
+ raise ValueError("SQG trellis packing supports K=2 through K=6")
|
| 893 |
+
|
| 894 |
+
|
| 895 |
+
def pack_trellis_edges(indices: torch.Tensor, bits: int) -> torch.Tensor:
|
| 896 |
+
diff --git a/kquant/sqg_e4m3.py b/kquant/sqg_e4m3.py
|
| 897 |
+
index 2783b10..d7f831d 100644
|
| 898 |
+
--- a/kquant/sqg_e4m3.py
|
| 899 |
+
+++ b/kquant/sqg_e4m3.py
|
| 900 |
+
@@ -31,8 +31,8 @@ _Q = (1.0, 2.07630930, -8.08332684, 6.32135736, -1.31208298)
|
| 901 |
+
|
| 902 |
+
|
| 903 |
+
def _validate(bits: int, mode: str) -> None:
|
| 904 |
+
- if isinstance(bits, bool) or not isinstance(bits, int) or bits not in (2, 3, 4):
|
| 905 |
+
- raise ValueError("SQG supports integer K2, K3, or K4")
|
| 906 |
+
+ if isinstance(bits, bool) or not isinstance(bits, int) or bits not in range(2, 7):
|
| 907 |
+
+ raise ValueError("SQG supports integer K2 through K6")
|
| 908 |
+
if mode != "normal":
|
| 909 |
+
raise ValueError("the supported R44 mode is 'normal'")
|
| 910 |
+
|
| 911 |
+
@@ -157,7 +157,7 @@ def sqg_e4m3_bytes_from_rank_lut(
|
| 912 |
+
) -> torch.Tensor:
|
| 913 |
+
"""Map a shared rank-indexed E4M3 law onto the unchanged SQG graph.
|
| 914 |
+
|
| 915 |
+
- ``rank_lut`` is deliberately independent of ``bits``. K2/K3/K4 may
|
| 916 |
+
+ ``rank_lut`` is deliberately independent of ``bits``. K2 through K6 may
|
| 917 |
+
expose different outgoing menus because their stratum widths differ, but
|
| 918 |
+
they retain the same history mixer, syndrome mixer, branch permutation,
|
| 919 |
+
and scalar reconstruction law. This is the controlled interface used to
|
| 920 |
+
diff --git a/kquant/sqg_quantizer.py b/kquant/sqg_quantizer.py
|
| 921 |
+
index cc29e00..ade6f06 100644
|
| 922 |
+
--- a/kquant/sqg_quantizer.py
|
| 923 |
+
+++ b/kquant/sqg_quantizer.py
|
| 924 |
+
@@ -2,9 +2,15 @@
|
| 925 |
+
|
| 926 |
+
from __future__ import annotations
|
| 927 |
+
|
| 928 |
+
+import hashlib
|
| 929 |
+
+import importlib.machinery
|
| 930 |
+
+import importlib.util
|
| 931 |
+
+import os
|
| 932 |
+
+import re
|
| 933 |
+
+import sys
|
| 934 |
+
+from collections.abc import Mapping
|
| 935 |
+
from functools import lru_cache
|
| 936 |
+
from pathlib import Path
|
| 937 |
+
-from collections.abc import Mapping
|
| 938 |
+
|
| 939 |
+
import torch
|
| 940 |
+
from torch.utils.cpp_extension import load
|
| 941 |
+
@@ -12,11 +18,107 @@ from torch.utils.cpp_extension import load
|
| 942 |
+
from kquant.sqg_e4m3 import sqg_e4m3_bytes
|
| 943 |
+
|
| 944 |
+
|
| 945 |
+
+_EXTENSION_NAME = "kquant_sqg_quantize_ext_v22"
|
| 946 |
+
+_EXTENSION_PATH_ENV = "KQUANT_SQG_EXTENSION_PATH"
|
| 947 |
+
+_EXTENSION_SHA256_ENV = "KQUANT_SQG_EXTENSION_SHA256"
|
| 948 |
+
+_REQUIRE_PREBUILT_ENV = "KQUANT_SQG_REQUIRE_PREBUILT"
|
| 949 |
+
+_SHA256_RE = re.compile(r"[0-9a-f]{64}")
|
| 950 |
+
+
|
| 951 |
+
+
|
| 952 |
+
+def _sha256_file(path: Path) -> str:
|
| 953 |
+
+ digest = hashlib.sha256()
|
| 954 |
+
+ with path.open("rb") as handle:
|
| 955 |
+
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
| 956 |
+
+ digest.update(chunk)
|
| 957 |
+
+ return digest.hexdigest()
|
| 958 |
+
+
|
| 959 |
+
+
|
| 960 |
+
+def _import_prebuilt_extension(path: Path):
|
| 961 |
+
+ """Import one exact pybind extension without invoking the JIT builder."""
|
| 962 |
+
+
|
| 963 |
+
+ spec = importlib.util.spec_from_file_location(_EXTENSION_NAME, path)
|
| 964 |
+
+ if spec is None or spec.loader is None:
|
| 965 |
+
+ raise ImportError(f"cannot construct an import spec for SQG extension: {path}")
|
| 966 |
+
+ previous = sys.modules.get(_EXTENSION_NAME)
|
| 967 |
+
+ module = importlib.util.module_from_spec(spec)
|
| 968 |
+
+ sys.modules[_EXTENSION_NAME] = module
|
| 969 |
+
+ try:
|
| 970 |
+
+ spec.loader.exec_module(module)
|
| 971 |
+
+ except BaseException:
|
| 972 |
+
+ if previous is None:
|
| 973 |
+
+ sys.modules.pop(_EXTENSION_NAME, None)
|
| 974 |
+
+ else:
|
| 975 |
+
+ sys.modules[_EXTENSION_NAME] = previous
|
| 976 |
+
+ raise
|
| 977 |
+
+ return module
|
| 978 |
+
+
|
| 979 |
+
+
|
| 980 |
+
+def _load_prebuilt_extension(raw_path: str, expected_sha256: str):
|
| 981 |
+
+ candidate = Path(raw_path)
|
| 982 |
+
+ if not candidate.is_absolute():
|
| 983 |
+
+ raise ValueError(f"{_EXTENSION_PATH_ENV} must be an absolute path")
|
| 984 |
+
+ if candidate.is_symlink():
|
| 985 |
+
+ raise ValueError(f"{_EXTENSION_PATH_ENV} may not name a symlink")
|
| 986 |
+
+ try:
|
| 987 |
+
+ path = candidate.resolve(strict=True)
|
| 988 |
+
+ except FileNotFoundError as exc:
|
| 989 |
+
+ raise FileNotFoundError(
|
| 990 |
+
+ f"sealed SQG extension does not exist: {candidate}"
|
| 991 |
+
+ ) from exc
|
| 992 |
+
+ if not path.is_file():
|
| 993 |
+
+ raise ValueError(f"sealed SQG extension is not a regular file: {path}")
|
| 994 |
+
+ allowed_names = {
|
| 995 |
+
+ f"{_EXTENSION_NAME}{suffix}"
|
| 996 |
+
+ for suffix in importlib.machinery.EXTENSION_SUFFIXES
|
| 997 |
+
+ }
|
| 998 |
+
+ if path.name not in allowed_names:
|
| 999 |
+
+ raise ValueError(
|
| 1000 |
+
+ "sealed SQG extension filename must identify "
|
| 1001 |
+
+ f"{_EXTENSION_NAME}: {path.name}"
|
| 1002 |
+
+ )
|
| 1003 |
+
+ if _SHA256_RE.fullmatch(expected_sha256) is None:
|
| 1004 |
+
+ raise ValueError(f"{_EXTENSION_SHA256_ENV} must be 64 lowercase hex digits")
|
| 1005 |
+
+ observed_sha256 = _sha256_file(path)
|
| 1006 |
+
+ if observed_sha256 != expected_sha256:
|
| 1007 |
+
+ raise RuntimeError(
|
| 1008 |
+
+ "sealed SQG extension SHA256 mismatch: "
|
| 1009 |
+
+ f"{observed_sha256} != {expected_sha256}"
|
| 1010 |
+
+ )
|
| 1011 |
+
+ module = _import_prebuilt_extension(path)
|
| 1012 |
+
+ module_path = Path(str(getattr(module, "__file__", ""))).resolve()
|
| 1013 |
+
+ if module_path != path:
|
| 1014 |
+
+ raise RuntimeError(
|
| 1015 |
+
+ f"loaded SQG extension origin differs: {module_path} != {path}"
|
| 1016 |
+
+ )
|
| 1017 |
+
+ if not callable(getattr(module, "quantize_tiles_procedural", None)) or not callable(
|
| 1018 |
+
+ getattr(module, "quantize_tiles_sqg", None)
|
| 1019 |
+
+ ):
|
| 1020 |
+
+ raise RuntimeError(
|
| 1021 |
+
+ "sealed SQG extension lacks required quantization entry points"
|
| 1022 |
+
+ )
|
| 1023 |
+
+ if _sha256_file(path) != expected_sha256:
|
| 1024 |
+
+ raise RuntimeError("sealed SQG extension changed while it was being imported")
|
| 1025 |
+
+ return module
|
| 1026 |
+
+
|
| 1027 |
+
+
|
| 1028 |
+
@lru_cache(maxsize=1)
|
| 1029 |
+
def _extension():
|
| 1030 |
+
+ require_value = os.environ.get(_REQUIRE_PREBUILT_ENV, "")
|
| 1031 |
+
+ if require_value not in ("", "0", "1"):
|
| 1032 |
+
+ raise ValueError(f"{_REQUIRE_PREBUILT_ENV} must be unset, 0, or 1")
|
| 1033 |
+
+ raw_path = os.environ.get(_EXTENSION_PATH_ENV, "")
|
| 1034 |
+
+ expected_sha256 = os.environ.get(_EXTENSION_SHA256_ENV, "")
|
| 1035 |
+
+ if require_value == "1" or raw_path or expected_sha256:
|
| 1036 |
+
+ if not raw_path or not expected_sha256:
|
| 1037 |
+
+ raise RuntimeError(
|
| 1038 |
+
+ f"{_EXTENSION_PATH_ENV} and {_EXTENSION_SHA256_ENV} are both required "
|
| 1039 |
+
+ "for sealed prebuilt SQG loading"
|
| 1040 |
+
+ )
|
| 1041 |
+
+ return _load_prebuilt_extension(raw_path, expected_sha256)
|
| 1042 |
+
+
|
| 1043 |
+
project = Path(__file__).resolve().parents[1]
|
| 1044 |
+
return load(
|
| 1045 |
+
- name="kquant_sqg_quantize_ext_v22",
|
| 1046 |
+
+ name=_EXTENSION_NAME,
|
| 1047 |
+
sources=[
|
| 1048 |
+
str(project / "kquant/csrc/sqg_quantize.cpp"),
|
| 1049 |
+
str(project / "kquant/csrc/sqg_quantize.cu"),
|
| 1050 |
+
@@ -42,7 +144,7 @@ def _sqg_temp_buffers(device: torch.device, bits: int):
|
| 1051 |
+
|
| 1052 |
+
multiprocessors = torch.cuda.get_device_properties(device).multi_processor_count
|
| 1053 |
+
edges = 65536 >> bits
|
| 1054 |
+
- decisions = edges // (4 if bits == 2 else 2)
|
| 1055 |
+
+ decisions = edges // 4 if bits == 2 else (edges // 2 if bits <= 4 else edges)
|
| 1056 |
+
free_bytes, _ = torch.cuda.mem_get_info(device)
|
| 1057 |
+
decision_bytes_per_tile = 256 * decisions
|
| 1058 |
+
affordable = max(256, int(free_bytes * 0.5) // decision_bytes_per_tile)
|
| 1059 |
+
@@ -94,13 +196,17 @@ def install_sqg_quantizer(quantizer_module) -> None:
|
| 1060 |
+
)
|
| 1061 |
+
if not isinstance(rate_codebooks, Mapping):
|
| 1062 |
+
raise TypeError("sqg_e4m3_luts_by_bits must be a mapping")
|
| 1063 |
+
- if set(rate_codebooks) - {2, 3, 4}:
|
| 1064 |
+
- raise ValueError("rate-specific SQG LUT keys must be K2, K3, or K4")
|
| 1065 |
+
+ if set(rate_codebooks) - set(range(2, 7)):
|
| 1066 |
+
+ raise ValueError("rate-specific SQG LUT keys must be K2 through K6")
|
| 1067 |
+
try:
|
| 1068 |
+
codebook = rate_codebooks[bits]
|
| 1069 |
+
except KeyError as exc:
|
| 1070 |
+
raise ValueError(f"missing rate-specific SQG K{bits} LUT") from exc
|
| 1071 |
+
if codebook is None:
|
| 1072 |
+
+ if bits not in (2, 3, 4):
|
| 1073 |
+
+ raise ValueError(
|
| 1074 |
+
+ "non-SQG procedural controls remain limited to K2 through K4"
|
| 1075 |
+
+ )
|
| 1076 |
+
output = torch.empty_like(tiles)
|
| 1077 |
+
indices = torch.empty_like(tiles, dtype=torch.int16)
|
| 1078 |
+
costs, edges = _sqg_temp_buffers(tiles.device, bits)
|
| 1079 |
+
diff --git a/tests/test_qsrt.py b/tests/test_qsrt.py
|
| 1080 |
+
index 0d5189d..6e5c469 100644
|
| 1081 |
+
--- a/tests/test_qsrt.py
|
| 1082 |
+
+++ b/tests/test_qsrt.py
|
| 1083 |
+
@@ -218,6 +218,8 @@ def test_matrix_rate_axis_tracks_shared_intermediate_dimension() -> None:
|
| 1084 |
+
(2, [6939] * 6),
|
| 1085 |
+
(3, [30469, 1337, 1337, 14711, 14711, 30469]),
|
| 1086 |
+
(4, [17767, 291, -12817, -30293, 17767, 291]),
|
| 1087 |
+
+ (5, [12820, 68, 21686, -14526, -31643, 13775]),
|
| 1088 |
+
+ (6, [-31984, 16, 8338, 20871, -11377, -29904]),
|
| 1089 |
+
],
|
| 1090 |
+
)
|
| 1091 |
+
def test_trellis_edge_pack_matches_native_word_order(
|
| 1092 |
+
@@ -231,7 +233,7 @@ def test_trellis_edge_pack_matches_native_word_order(
|
| 1093 |
+
torch.testing.assert_close(unpack_trellis_edges(packed, bits), indices)
|
| 1094 |
+
|
| 1095 |
+
|
| 1096 |
+
-@pytest.mark.parametrize("bits", [2, 3, 4])
|
| 1097 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1098 |
+
def test_trellis_edge_pack_round_trips_only_stored_low_bits(bits: int) -> None:
|
| 1099 |
+
generator = torch.Generator().manual_seed(100 + bits)
|
| 1100 |
+
indices = torch.randint(-32768, 32768, (7, 256), generator=generator)
|
| 1101 |
+
@@ -241,7 +243,7 @@ def test_trellis_edge_pack_round_trips_only_stored_low_bits(bits: int) -> None:
|
| 1102 |
+
torch.testing.assert_close(unpacked.int(), indices.int() & ((1 << bits) - 1))
|
| 1103 |
+
|
| 1104 |
+
|
| 1105 |
+
-@pytest.mark.parametrize("bits", [2, 3, 4])
|
| 1106 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1107 |
+
def test_cyclic_trellis_states_round_trip_exactly(bits: int) -> None:
|
| 1108 |
+
generator = torch.Generator().manual_seed(150 + bits)
|
| 1109 |
+
edges = torch.randint(0, 1 << bits, (5, 256), generator=generator)
|
| 1110 |
+
@@ -251,7 +253,7 @@ def test_cyclic_trellis_states_round_trip_exactly(bits: int) -> None:
|
| 1111 |
+
torch.testing.assert_close(unpack_trellis_states(packed, bits), states)
|
| 1112 |
+
|
| 1113 |
+
|
| 1114 |
+
-@pytest.mark.parametrize("bits", [2, 3, 4])
|
| 1115 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1116 |
+
def test_cyclic_trellis_state_closed_form_matches_scalar_recurrence(bits: int) -> None:
|
| 1117 |
+
generator = torch.Generator().manual_seed(175 + bits)
|
| 1118 |
+
edges = torch.randint(0, 1 << bits, (3, 5, 256), generator=generator)
|
| 1119 |
+
diff --git a/tests/test_sqg_e4m3.py b/tests/test_sqg_e4m3.py
|
| 1120 |
+
index f630827..669fd6d 100644
|
| 1121 |
+
--- a/tests/test_sqg_e4m3.py
|
| 1122 |
+
+++ b/tests/test_sqg_e4m3.py
|
| 1123 |
+
@@ -34,6 +34,8 @@ _SQG_XOR_CHEB_T12_SHA256 = {
|
| 1124 |
+
2: "62027916386245a84c86156a0a08b6cf07e41548871af0e56fb40780558f6293",
|
| 1125 |
+
3: "afe7b3633e7d243b00b379b18ec4dca573722b3727cafef47fcb6470d7e7e6c9",
|
| 1126 |
+
4: "5a9620f0c4d8f0a60d0b6fbea921dcecbd193e6febf31043aaa6403c20389c2f",
|
| 1127 |
+
+ 5: "a1caf5572c67d4face423478f423e1ed6149a8c920be5c28d4702946c93cae2f",
|
| 1128 |
+
+ 6: "8e0edc92e20eb92f5287dbdadc8e870fc500fc03c5eed996cae0781d197ebcb0",
|
| 1129 |
+
}
|
| 1130 |
+
|
| 1131 |
+
|
| 1132 |
+
@@ -48,7 +50,7 @@ def test_primary_sqg_xor_cheb_t12_matches_runtime_contract() -> None:
|
| 1133 |
+
assert _sha256(table) == _SQG_XOR_CHEB_T12_SHA256["t12"]
|
| 1134 |
+
|
| 1135 |
+
|
| 1136 |
+
-@pytest.mark.parametrize("bits", (2, 3, 4))
|
| 1137 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1138 |
+
def test_primary_sqg_xor_cheb_t12_graph_and_labels_match_runtime_contract(
|
| 1139 |
+
bits: int,
|
| 1140 |
+
) -> None:
|
| 1141 |
+
@@ -69,7 +71,7 @@ def test_primary_sqg_xor_cheb_t12_graph_and_labels_match_runtime_contract(
|
| 1142 |
+
assert _sha256(labels) == _SQG_XOR_CHEB_T12_SHA256[bits]
|
| 1143 |
+
|
| 1144 |
+
|
| 1145 |
+
-@pytest.mark.parametrize("bits", (2, 3, 4))
|
| 1146 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1147 |
+
def test_sqg_preprojection_mapping_is_a_permutation(bits: int) -> None:
|
| 1148 |
+
ranks = sqg_rank_permutation(bits)
|
| 1149 |
+
assert ranks.dtype == torch.int64
|
| 1150 |
+
@@ -77,7 +79,7 @@ def test_sqg_preprojection_mapping_is_a_permutation(bits: int) -> None:
|
| 1151 |
+
assert torch.equal(torch.sort(ranks).values, torch.arange(1 << 16))
|
| 1152 |
+
|
| 1153 |
+
|
| 1154 |
+
-@pytest.mark.parametrize("bits", (2, 3, 4))
|
| 1155 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1156 |
+
def test_sqg_codebook_round_trips_exact_e4m3(bits: int) -> None:
|
| 1157 |
+
raw = sqg_e4m3_bytes(bits, "normal")
|
| 1158 |
+
codebook = sqg_e4m3_codebook(bits, "normal")
|
| 1159 |
+
@@ -88,7 +90,7 @@ def test_sqg_codebook_round_trips_exact_e4m3(bits: int) -> None:
|
| 1160 |
+
assert bool(torch.isfinite(codebook).all())
|
| 1161 |
+
|
| 1162 |
+
|
| 1163 |
+
-@pytest.mark.parametrize("bits", (2, 3, 4))
|
| 1164 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1165 |
+
def test_sqg_each_state_spans_all_coarse_strata(bits: int) -> None:
|
| 1166 |
+
ranks = sqg_rank_permutation(bits)
|
| 1167 |
+
width = 16 - bits
|
| 1168 |
+
@@ -97,7 +99,7 @@ def test_sqg_each_state_spans_all_coarse_strata(bits: int) -> None:
|
| 1169 |
+
assert torch.equal(torch.sort(strata, dim=1).values, expected)
|
| 1170 |
+
|
| 1171 |
+
|
| 1172 |
+
-@pytest.mark.parametrize("bits", (2, 3, 4))
|
| 1173 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1174 |
+
def test_shared_rank_lut_preserves_one_graph_for_every_rate(bits: int) -> None:
|
| 1175 |
+
rank_lut = torch.arange(1 << 16, dtype=torch.int64).remainder(126).to(torch.uint8)
|
| 1176 |
+
raw = sqg_e4m3_bytes_from_rank_lut(bits, rank_lut)
|
| 1177 |
+
@@ -118,7 +120,7 @@ def test_shared_rank_lut_rejects_nonfinite_e4m3() -> None:
|
| 1178 |
+
sqg_e4m3_bytes_from_rank_lut(2, rank_lut)
|
| 1179 |
+
|
| 1180 |
+
|
| 1181 |
+
-@pytest.mark.parametrize("bits", (2, 3, 4))
|
| 1182 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1183 |
+
def test_sqg_cheb_normal_named_codebook_uses_exact_shared_rank_law(bits: int) -> None:
|
| 1184 |
+
expected = sqg_cheb_normal_rank_e4m3_bytes().index_select(
|
| 1185 |
+
0, sqg_rank_permutation(bits)
|
| 1186 |
+
@@ -132,7 +134,7 @@ def test_sqg_cheb_normal_named_codebook_uses_exact_shared_rank_law(bits: int) ->
|
| 1187 |
+
)
|
| 1188 |
+
|
| 1189 |
+
|
| 1190 |
+
-@pytest.mark.parametrize("bits", (2, 3, 4))
|
| 1191 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1192 |
+
@pytest.mark.parametrize("rate_axis", ("k", "n"))
|
| 1193 |
+
def test_w2_k2_q8h4_profile_changes_only_k_axis_k2(
|
| 1194 |
+
bits: int, rate_axis: str
|
| 1195 |
+
diff --git a/tests/test_sqg_quantizer.py b/tests/test_sqg_quantizer.py
|
| 1196 |
+
index 8d99e86..c470ee2 100644
|
| 1197 |
+
--- a/tests/test_sqg_quantizer.py
|
| 1198 |
+
+++ b/tests/test_sqg_quantizer.py
|
| 1199 |
+
@@ -1,11 +1,135 @@
|
| 1200 |
+
from __future__ import annotations
|
| 1201 |
+
|
| 1202 |
+
+import hashlib
|
| 1203 |
+
+from pathlib import Path
|
| 1204 |
+
from types import SimpleNamespace
|
| 1205 |
+
|
| 1206 |
+
+import pytest
|
| 1207 |
+
import torch
|
| 1208 |
+
|
| 1209 |
+
import kquant.sqg_quantizer as sqg_quantizer
|
| 1210 |
+
+from kquant.exl3_reference import reconstruct_trellis_states
|
| 1211 |
+
from kquant.sqg_quantizer import install_sqg_quantizer
|
| 1212 |
+
+from kquant.sqg_e4m3 import sqg_xor_cheb_t12_bytes
|
| 1213 |
+
+
|
| 1214 |
+
+
|
| 1215 |
+
+def _sealed_extension(
|
| 1216 |
+
+ tmp_path: Path,
|
| 1217 |
+
+ monkeypatch: pytest.MonkeyPatch,
|
| 1218 |
+
+ *,
|
| 1219 |
+
+ filename: str = "kquant_sqg_quantize_ext_v22.so",
|
| 1220 |
+
+) -> tuple[Path, SimpleNamespace]:
|
| 1221 |
+
+ path = tmp_path / filename
|
| 1222 |
+
+ payload = b"sealed-sqg-extension-test-double"
|
| 1223 |
+
+ path.write_bytes(payload)
|
| 1224 |
+
+ module = SimpleNamespace(
|
| 1225 |
+
+ __file__=str(path),
|
| 1226 |
+
+ quantize_tiles_procedural=lambda *_args: None,
|
| 1227 |
+
+ quantize_tiles_sqg=lambda *_args: None,
|
| 1228 |
+
+ )
|
| 1229 |
+
+ monkeypatch.setenv("KQUANT_SQG_REQUIRE_PREBUILT", "1")
|
| 1230 |
+
+ monkeypatch.setenv("KQUANT_SQG_EXTENSION_PATH", str(path))
|
| 1231 |
+
+ monkeypatch.setenv(
|
| 1232 |
+
+ "KQUANT_SQG_EXTENSION_SHA256", hashlib.sha256(payload).hexdigest()
|
| 1233 |
+
+ )
|
| 1234 |
+
+ return path, module
|
| 1235 |
+
+
|
| 1236 |
+
+
|
| 1237 |
+
+def test_required_prebuilt_extension_uses_exact_path_and_never_jit_builds(
|
| 1238 |
+
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
| 1239 |
+
+) -> None:
|
| 1240 |
+
+ path, module = _sealed_extension(tmp_path, monkeypatch)
|
| 1241 |
+
+ imported: list[Path] = []
|
| 1242 |
+
+
|
| 1243 |
+
+ def fake_import(actual: Path):
|
| 1244 |
+
+ imported.append(actual)
|
| 1245 |
+
+ return module
|
| 1246 |
+
+
|
| 1247 |
+
+ monkeypatch.setattr(sqg_quantizer, "_import_prebuilt_extension", fake_import)
|
| 1248 |
+
+ monkeypatch.setattr(
|
| 1249 |
+
+ sqg_quantizer,
|
| 1250 |
+
+ "load",
|
| 1251 |
+
+ lambda **_kwargs: pytest.fail("JIT builder must not run in prebuilt mode"),
|
| 1252 |
+
+ )
|
| 1253 |
+
+ sqg_quantizer._extension.cache_clear()
|
| 1254 |
+
+ try:
|
| 1255 |
+
+ assert sqg_quantizer._extension() is module
|
| 1256 |
+
+ assert imported == [path.resolve()]
|
| 1257 |
+
+ finally:
|
| 1258 |
+
+ sqg_quantizer._extension.cache_clear()
|
| 1259 |
+
+
|
| 1260 |
+
+
|
| 1261 |
+
+def test_required_prebuilt_extension_rejects_wrong_filename_before_import_or_jit(
|
| 1262 |
+
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
| 1263 |
+
+) -> None:
|
| 1264 |
+
+ _sealed_extension(tmp_path, monkeypatch, filename="foreign_extension.so")
|
| 1265 |
+
+ monkeypatch.setattr(
|
| 1266 |
+
+ sqg_quantizer,
|
| 1267 |
+
+ "_import_prebuilt_extension",
|
| 1268 |
+
+ lambda _path: pytest.fail("foreign extension must not be imported"),
|
| 1269 |
+
+ )
|
| 1270 |
+
+ monkeypatch.setattr(
|
| 1271 |
+
+ sqg_quantizer,
|
| 1272 |
+
+ "load",
|
| 1273 |
+
+ lambda **_kwargs: pytest.fail("JIT builder must not run in prebuilt mode"),
|
| 1274 |
+
+ )
|
| 1275 |
+
+ sqg_quantizer._extension.cache_clear()
|
| 1276 |
+
+ try:
|
| 1277 |
+
+ with pytest.raises(ValueError, match="filename must identify"):
|
| 1278 |
+
+ sqg_quantizer._extension()
|
| 1279 |
+
+ finally:
|
| 1280 |
+
+ sqg_quantizer._extension.cache_clear()
|
| 1281 |
+
+
|
| 1282 |
+
+
|
| 1283 |
+
+def test_required_prebuilt_extension_rejects_hash_mismatch_before_import_or_jit(
|
| 1284 |
+
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
| 1285 |
+
+) -> None:
|
| 1286 |
+
+ _sealed_extension(tmp_path, monkeypatch)
|
| 1287 |
+
+ monkeypatch.setenv("KQUANT_SQG_EXTENSION_SHA256", "0" * 64)
|
| 1288 |
+
+ monkeypatch.setattr(
|
| 1289 |
+
+ sqg_quantizer,
|
| 1290 |
+
+ "_import_prebuilt_extension",
|
| 1291 |
+
+ lambda _path: pytest.fail("mismatched extension must not be imported"),
|
| 1292 |
+
+ )
|
| 1293 |
+
+ monkeypatch.setattr(
|
| 1294 |
+
+ sqg_quantizer,
|
| 1295 |
+
+ "load",
|
| 1296 |
+
+ lambda **_kwargs: pytest.fail("JIT builder must not run in prebuilt mode"),
|
| 1297 |
+
+ )
|
| 1298 |
+
+ sqg_quantizer._extension.cache_clear()
|
| 1299 |
+
+ try:
|
| 1300 |
+
+ with pytest.raises(RuntimeError, match="SHA256 mismatch"):
|
| 1301 |
+
+ sqg_quantizer._extension()
|
| 1302 |
+
+ finally:
|
| 1303 |
+
+ sqg_quantizer._extension.cache_clear()
|
| 1304 |
+
+
|
| 1305 |
+
+
|
| 1306 |
+
+@pytest.mark.parametrize(
|
| 1307 |
+
+ ("missing", "message"),
|
| 1308 |
+
+ (
|
| 1309 |
+
+ ("KQUANT_SQG_EXTENSION_PATH", "are both required"),
|
| 1310 |
+
+ ("KQUANT_SQG_EXTENSION_SHA256", "are both required"),
|
| 1311 |
+
+ ),
|
| 1312 |
+
+)
|
| 1313 |
+
+def test_required_prebuilt_extension_rejects_partial_binding_without_jit(
|
| 1314 |
+
+ tmp_path: Path,
|
| 1315 |
+
+ monkeypatch: pytest.MonkeyPatch,
|
| 1316 |
+
+ missing: str,
|
| 1317 |
+
+ message: str,
|
| 1318 |
+
+) -> None:
|
| 1319 |
+
+ _sealed_extension(tmp_path, monkeypatch)
|
| 1320 |
+
+ monkeypatch.delenv(missing)
|
| 1321 |
+
+ monkeypatch.setattr(
|
| 1322 |
+
+ sqg_quantizer,
|
| 1323 |
+
+ "load",
|
| 1324 |
+
+ lambda **_kwargs: pytest.fail("JIT builder must not run in prebuilt mode"),
|
| 1325 |
+
+ )
|
| 1326 |
+
+ sqg_quantizer._extension.cache_clear()
|
| 1327 |
+
+ try:
|
| 1328 |
+
+ with pytest.raises(RuntimeError, match=message):
|
| 1329 |
+
+ sqg_quantizer._extension()
|
| 1330 |
+
+ finally:
|
| 1331 |
+
+ sqg_quantizer._extension.cache_clear()
|
| 1332 |
+
|
| 1333 |
+
|
| 1334 |
+
def test_rate_specific_none_dispatches_to_kquant_mcg(monkeypatch) -> None:
|
| 1335 |
+
@@ -88,3 +212,51 @@ def test_sqg_luts_are_transposed_by_predecessor_for_vector_loads(
|
| 1336 |
+
)
|
| 1337 |
+
assert torch.equal(actual, expected)
|
| 1338 |
+
assert calls[-1][6:] == (bits, 128)
|
| 1339 |
+
+
|
| 1340 |
+
+
|
| 1341 |
+
+@pytest.mark.parametrize("bits", [5, 6])
|
| 1342 |
+
+def test_k5_k6_sqg_luts_remain_in_direct_state_order(
|
| 1343 |
+
+ monkeypatch, bits: int
|
| 1344 |
+
+) -> None:
|
| 1345 |
+
+ calls: list[tuple] = []
|
| 1346 |
+
+ monkeypatch.setattr(
|
| 1347 |
+
+ sqg_quantizer,
|
| 1348 |
+
+ "_sqg_temp_buffers",
|
| 1349 |
+
+ lambda _device, _bits: (torch.empty(0), torch.empty(0)),
|
| 1350 |
+
+ )
|
| 1351 |
+
+ monkeypatch.setattr(
|
| 1352 |
+
+ sqg_quantizer,
|
| 1353 |
+
+ "_extension",
|
| 1354 |
+
+ lambda: SimpleNamespace(quantize_tiles_sqg=lambda *args: calls.append(args)),
|
| 1355 |
+
+ )
|
| 1356 |
+
+ module = SimpleNamespace(quantize_tiles=lambda *_args: "upstream")
|
| 1357 |
+
+ install_sqg_quantizer(module)
|
| 1358 |
+
+ raw = torch.arange(65536, dtype=torch.int64).to(torch.uint8)
|
| 1359 |
+
+ module.quantize_tiles(
|
| 1360 |
+
+ torch.zeros((1, 256), dtype=torch.float32),
|
| 1361 |
+
+ {"K": bits, "devices": ["cuda:0"], "sqg_e4m3_lut": raw},
|
| 1362 |
+
+ )
|
| 1363 |
+
+ assert torch.equal(calls[-1][5], raw)
|
| 1364 |
+
+ assert calls[-1][6:] == (bits, 128)
|
| 1365 |
+
+
|
| 1366 |
+
+
|
| 1367 |
+
+@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is unavailable")
|
| 1368 |
+
+@pytest.mark.parametrize("bits", range(2, 7))
|
| 1369 |
+
+def test_sqg_quantizer_keeps_saturated_cost_paths_closed(bits: int) -> None:
|
| 1370 |
+
+ module = SimpleNamespace(quantize_tiles=lambda *_args: "upstream")
|
| 1371 |
+
+ install_sqg_quantizer(module)
|
| 1372 |
+
+ tiles = torch.full((2, 256), 1000.0, dtype=torch.float32, device="cuda")
|
| 1373 |
+
+
|
| 1374 |
+
+ _output, states = module.quantize_tiles(
|
| 1375 |
+
+ tiles,
|
| 1376 |
+
+ {
|
| 1377 |
+
+ "K": bits,
|
| 1378 |
+
+ "devices": ["cuda:0"],
|
| 1379 |
+
+ "sqg_e4m3_lut": sqg_xor_cheb_t12_bytes(bits),
|
| 1380 |
+
+ "tailbite_context": 128,
|
| 1381 |
+
+ },
|
| 1382 |
+
+ )
|
| 1383 |
+
+
|
| 1384 |
+
+ edges = states.to(torch.int64) & ((1 << bits) - 1)
|
| 1385 |
+
+ expected = reconstruct_trellis_states(edges, bits)
|
| 1386 |
+
+ assert torch.equal(states, expected)
|
reproduction/closure/patches/qsrt/changed-files/qsrt/pack/qsrt_candidates.py
ADDED
|
@@ -0,0 +1,1637 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Offline-streamed all-expert candidates for the QSRT codec."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
import os
|
| 7 |
+
from dataclasses import asdict, dataclass
|
| 8 |
+
from collections.abc import Mapping
|
| 9 |
+
from typing import Literal, Protocol, Sequence
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
import torch.nn.functional as F
|
| 13 |
+
|
| 14 |
+
from qsrt.capture import LayerSamples
|
| 15 |
+
from qsrt.exl3_reference import (
|
| 16 |
+
CODEBOOK_SQG_XOR_CHEB_T12,
|
| 17 |
+
reconstruct_trellis_states,
|
| 18 |
+
)
|
| 19 |
+
from qsrt.qsrt_candidates import (
|
| 20 |
+
PERMUTATION_POLICIES,
|
| 21 |
+
PermutationPolicy,
|
| 22 |
+
RatePairSelection,
|
| 23 |
+
RequestPartition,
|
| 24 |
+
RoutedRows,
|
| 25 |
+
build_expert_hessians,
|
| 26 |
+
deterministic_expert_seed,
|
| 27 |
+
functional_sse_by_request,
|
| 28 |
+
permutation_policy_contexts,
|
| 29 |
+
select_expert_rows,
|
| 30 |
+
select_phase1_rate_pair,
|
| 31 |
+
)
|
| 32 |
+
from qsrt.qsrt import (
|
| 33 |
+
H308,
|
| 34 |
+
K2,
|
| 35 |
+
R0,
|
| 36 |
+
SCHEMA,
|
| 37 |
+
ExpertFormatSpec,
|
| 38 |
+
PHASE1_MODE_IDS,
|
| 39 |
+
RATE_TRANSFER_MODES,
|
| 40 |
+
RECORDS_PER_EXPERT,
|
| 41 |
+
resolve_mode,
|
| 42 |
+
)
|
| 43 |
+
from qsrt.qsrt_coupled import (
|
| 44 |
+
CoupledHadamardExecution,
|
| 45 |
+
CoupledHadamardSpec,
|
| 46 |
+
ExpertActivation,
|
| 47 |
+
coupled_execution,
|
| 48 |
+
encode_coupled_weights,
|
| 49 |
+
expert_activation,
|
| 50 |
+
)
|
| 51 |
+
from qsrt.pack.qsrt_encoder import (
|
| 52 |
+
Layout,
|
| 53 |
+
MATRICES,
|
| 54 |
+
QSRTExpertEncoding,
|
| 55 |
+
QSRTMatrixCandidate,
|
| 56 |
+
QSRTMatrixEncoding,
|
| 57 |
+
QSRTTransformSeeds,
|
| 58 |
+
SearchLayout,
|
| 59 |
+
finalize_qsrt_matrix_candidate,
|
| 60 |
+
quantize_qsrt_matrix_candidates_batched,
|
| 61 |
+
quantize_qsrt_matrix_expert_batch,
|
| 62 |
+
)
|
| 63 |
+
from qsrt.tp_simulator import situ
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
CANDIDATE_POOL_KIND = "qsrt_kimi_k3_qsrt_candidate_pool"
|
| 67 |
+
CANDIDATE_POOL_SCHEMA_VERSION = 7
|
| 68 |
+
OFFICIAL_SOURCE_DAMAGE_METRIC = "official_source_excess_sse"
|
| 69 |
+
HessianPolicy = Literal["captured_blend", "identity"]
|
| 70 |
+
HESSIAN_POLICIES: tuple[HessianPolicy, ...] = ("captured_blend", "identity")
|
| 71 |
+
FOLDED_SCALE_GRID: tuple[float, ...] = (0.85, 0.925, 1.0, 1.075, 1.15)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _debug_assert_candidate_state_closure(
|
| 75 |
+
candidate: QSRTMatrixCandidate,
|
| 76 |
+
*,
|
| 77 |
+
layer: int,
|
| 78 |
+
expert: int,
|
| 79 |
+
stage: str,
|
| 80 |
+
) -> None:
|
| 81 |
+
encoded = candidate.encoded
|
| 82 |
+
plan = candidate.plan
|
| 83 |
+
tile_bits = tuple(int(value) for value in plan.encoder_tile_bits)
|
| 84 |
+
rate_dim = 0 if plan.rate_axis == "k" else 1
|
| 85 |
+
for bits in sorted(set(tile_bits)):
|
| 86 |
+
positions = torch.tensor(
|
| 87 |
+
[index for index, value in enumerate(tile_bits) if value == bits],
|
| 88 |
+
dtype=torch.long,
|
| 89 |
+
device=encoded.device,
|
| 90 |
+
)
|
| 91 |
+
selected = encoded.index_select(rate_dim, positions)
|
| 92 |
+
expected = reconstruct_trellis_states(selected, bits)
|
| 93 |
+
mismatch = selected != expected
|
| 94 |
+
if bool(torch.any(mismatch)):
|
| 95 |
+
count = int(torch.count_nonzero(mismatch))
|
| 96 |
+
first = tuple(
|
| 97 |
+
int(value)
|
| 98 |
+
for value in torch.nonzero(mismatch, as_tuple=False)[0]
|
| 99 |
+
.cpu()
|
| 100 |
+
.tolist()
|
| 101 |
+
)
|
| 102 |
+
raise RuntimeError(
|
| 103 |
+
f"layer {layer} expert {expert} {plan.matrix} R{plan.mode.mode_id} "
|
| 104 |
+
f"failed {stage} state closure at K{bits}: {count} states "
|
| 105 |
+
f"differ; first grouped coordinate {first}"
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def _debug_assert_candidate_tree_closure(
|
| 110 |
+
candidates,
|
| 111 |
+
experts: Sequence[int],
|
| 112 |
+
*,
|
| 113 |
+
layer: int,
|
| 114 |
+
stage: str,
|
| 115 |
+
) -> None:
|
| 116 |
+
if os.environ.get("QSRT_DEBUG_CANDIDATE_CLOSURE") != "1":
|
| 117 |
+
return
|
| 118 |
+
for expert, tree in zip(experts, candidates):
|
| 119 |
+
stack = [tree]
|
| 120 |
+
while stack:
|
| 121 |
+
value = stack.pop()
|
| 122 |
+
if isinstance(value, QSRTMatrixCandidate):
|
| 123 |
+
_debug_assert_candidate_state_closure(
|
| 124 |
+
value, layer=layer, expert=expert, stage=stage
|
| 125 |
+
)
|
| 126 |
+
elif isinstance(value, Mapping):
|
| 127 |
+
stack.extend(value.values())
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
class MatrixStore(Protocol):
|
| 131 |
+
"""Read-only source that materializes one official matrix per call."""
|
| 132 |
+
|
| 133 |
+
def load_matrix(
|
| 134 |
+
self,
|
| 135 |
+
layer: int,
|
| 136 |
+
expert: int,
|
| 137 |
+
matrix: str,
|
| 138 |
+
*,
|
| 139 |
+
device: torch.device | str | None = None,
|
| 140 |
+
) -> torch.Tensor: ...
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
@dataclass
|
| 144 |
+
class QSRTCandidateEncoding:
|
| 145 |
+
"""Selected payload plus the complete phase-1 selection evidence."""
|
| 146 |
+
|
| 147 |
+
expert: QSRTExpertEncoding
|
| 148 |
+
selection: RatePairSelection
|
| 149 |
+
block_contexts: torch.Tensor
|
| 150 |
+
block_scores: torch.Tensor
|
| 151 |
+
context_basis: str
|
| 152 |
+
covariance: dict[str, object]
|
| 153 |
+
evaluated_modes: tuple[int, ...]
|
| 154 |
+
evaluated_formats: tuple[tuple[int, int], ...]
|
| 155 |
+
fit_sse: dict[tuple[int, int], torch.Tensor]
|
| 156 |
+
confirmation_sse: dict[tuple[int, int], torch.Tensor]
|
| 157 |
+
fit_reference_energy: torch.Tensor
|
| 158 |
+
confirmation_reference_energy: torch.Tensor
|
| 159 |
+
fit_counts: torch.Tensor
|
| 160 |
+
confirmation_counts: torch.Tensor
|
| 161 |
+
mode_coding: dict[tuple[int, int], dict[str, object]]
|
| 162 |
+
|
| 163 |
+
def metadata(self) -> dict[str, object]:
|
| 164 |
+
return {
|
| 165 |
+
"selection": asdict(self.selection),
|
| 166 |
+
"context_basis": self.context_basis,
|
| 167 |
+
"covariance": self.covariance,
|
| 168 |
+
"evaluated_modes": list(self.evaluated_modes),
|
| 169 |
+
"mode_coding": {
|
| 170 |
+
(resolve_mode(r13).name if r13 == r2 else f"R{r13}/R{r2}"): value
|
| 171 |
+
for (r13, r2), value in self.mode_coding.items()
|
| 172 |
+
},
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def _source_middle(
|
| 177 |
+
gate_projection: torch.Tensor,
|
| 178 |
+
up_projection: torch.Tensor,
|
| 179 |
+
*,
|
| 180 |
+
activation: ExpertActivation = "situ",
|
| 181 |
+
) -> torch.Tensor:
|
| 182 |
+
return expert_activation(
|
| 183 |
+
gate_projection,
|
| 184 |
+
up_projection,
|
| 185 |
+
activation=activation,
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def _activation_basis(activation: ExpertActivation) -> str:
|
| 190 |
+
if activation not in ("situ", "silu"):
|
| 191 |
+
raise ValueError(f"unsupported expert activation: {activation}")
|
| 192 |
+
return f"post_{activation}"
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def _folded_intermediate_conditioning(
|
| 196 |
+
block_scores: torch.Tensor,
|
| 197 |
+
block_contexts: torch.Tensor,
|
| 198 |
+
*,
|
| 199 |
+
power: float,
|
| 200 |
+
) -> torch.Tensor | None:
|
| 201 |
+
"""Derive a metadata-free per-record output conditioning profile.
|
| 202 |
+
|
| 203 |
+
The source rows are divided by this profile before w1/w3 quantization and
|
| 204 |
+
the exact inverse is folded into their existing expert-local ``svh``
|
| 205 |
+
vectors. Factors are constant over each final 128-channel Hadamard block,
|
| 206 |
+
so the fold commutes with the transform and requires no new runtime tensor.
|
| 207 |
+
"""
|
| 208 |
+
|
| 209 |
+
if not math.isfinite(power):
|
| 210 |
+
raise ValueError("folded scale power must be finite")
|
| 211 |
+
if power == 0.0:
|
| 212 |
+
return None
|
| 213 |
+
if block_scores.ndim != 1 or block_scores.numel() != 3072 // 4:
|
| 214 |
+
raise ValueError("folded conditioning requires 768 group scores")
|
| 215 |
+
if not bool(torch.all(torch.isfinite(block_scores))) or bool(
|
| 216 |
+
torch.any(block_scores < 0)
|
| 217 |
+
):
|
| 218 |
+
raise ValueError("folded conditioning scores must be finite and nonnegative")
|
| 219 |
+
contexts = block_contexts.to(device=block_scores.device, dtype=torch.long)
|
| 220 |
+
group_energy = block_scores.float()
|
| 221 |
+
record_energy = torch.zeros(
|
| 222 |
+
RECORDS_PER_EXPERT, device=block_scores.device, dtype=torch.float32
|
| 223 |
+
).scatter_add_(0, contexts, group_energy.float())
|
| 224 |
+
counts = torch.bincount(contexts, minlength=RECORDS_PER_EXPERT).float()
|
| 225 |
+
record_rms = (record_energy / counts).clamp_min(1e-30).sqrt()
|
| 226 |
+
geometric_mean = record_rms.log().mean().exp()
|
| 227 |
+
# A smaller folded scale magnifies important rows inside the trellis
|
| 228 |
+
# search and shrinks their absolute error again when folded back into svh.
|
| 229 |
+
# This is an output-metric conditioning heuristic, not a new runtime scale.
|
| 230 |
+
desired = (record_rms / geometric_mean).pow(-power)
|
| 231 |
+
grid = torch.tensor(FOLDED_SCALE_GRID, device=desired.device)
|
| 232 |
+
selected = grid[(desired[:, None] - grid[None, :]).abs().argmin(dim=1)]
|
| 233 |
+
return selected.index_select(0, contexts).repeat_interleave(4).contiguous()
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def _load_source_matrix(
|
| 237 |
+
store: MatrixStore,
|
| 238 |
+
layer: int,
|
| 239 |
+
expert: int,
|
| 240 |
+
matrix: str,
|
| 241 |
+
device: torch.device,
|
| 242 |
+
) -> torch.Tensor:
|
| 243 |
+
source = store.load_matrix(layer, expert, matrix, device=device)
|
| 244 |
+
if source.device != device:
|
| 245 |
+
raise ValueError(
|
| 246 |
+
f"official source store returned {source.device}, expected {device}"
|
| 247 |
+
)
|
| 248 |
+
if source.dtype != torch.float32:
|
| 249 |
+
raise ValueError(
|
| 250 |
+
f"official source store returned {source.dtype}, expected torch.float32"
|
| 251 |
+
)
|
| 252 |
+
return source.contiguous()
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def _split_rows(rows: RoutedRows, requests: dict[int, str]) -> torch.Tensor:
|
| 256 |
+
allowed = torch.tensor(
|
| 257 |
+
sorted(requests), dtype=rows.request_steps.dtype, device=rows.request_steps.device
|
| 258 |
+
)
|
| 259 |
+
return torch.isin(rows.request_steps, allowed)
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def _empty_metrics(requests: dict[int, str]) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 263 |
+
count = len(requests)
|
| 264 |
+
return (
|
| 265 |
+
torch.zeros(count, dtype=torch.float64),
|
| 266 |
+
torch.zeros(count, dtype=torch.float64),
|
| 267 |
+
torch.zeros(count, dtype=torch.int64),
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
@dataclass(frozen=True)
|
| 272 |
+
class _DeferredFunctionalRows:
|
| 273 |
+
"""Static routed-metric state shared by every candidate for one fold."""
|
| 274 |
+
|
| 275 |
+
device_mask: torch.Tensor
|
| 276 |
+
reference: torch.Tensor
|
| 277 |
+
route_weights: torch.Tensor
|
| 278 |
+
request_indices: torch.Tensor
|
| 279 |
+
reference_energy: torch.Tensor
|
| 280 |
+
counts: torch.Tensor
|
| 281 |
+
request_count: int
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def _prepare_deferred_functional_rows(
|
| 285 |
+
reference: torch.Tensor,
|
| 286 |
+
gates: torch.Tensor,
|
| 287 |
+
request_steps: torch.Tensor,
|
| 288 |
+
mask: torch.Tensor,
|
| 289 |
+
requests: dict[int, str],
|
| 290 |
+
*,
|
| 291 |
+
device: torch.device,
|
| 292 |
+
) -> _DeferredFunctionalRows:
|
| 293 |
+
"""Prepare invariant metric terms without changing their arithmetic."""
|
| 294 |
+
|
| 295 |
+
device_mask = mask.to(device=device)
|
| 296 |
+
selected_reference = reference[device_mask].double()
|
| 297 |
+
selected_gates = gates[device_mask]
|
| 298 |
+
selected_steps = request_steps[mask]
|
| 299 |
+
ordered_steps = sorted(map(int, requests))
|
| 300 |
+
positions = {step: index for index, step in enumerate(ordered_steps)}
|
| 301 |
+
try:
|
| 302 |
+
request_indices = torch.tensor(
|
| 303 |
+
[positions[int(step)] for step in selected_steps.cpu().tolist()],
|
| 304 |
+
dtype=torch.long,
|
| 305 |
+
)
|
| 306 |
+
except KeyError as exc:
|
| 307 |
+
raise ValueError(
|
| 308 |
+
f"metric row has unmapped request epoch {exc.args[0]}"
|
| 309 |
+
) from exc
|
| 310 |
+
route_weights = selected_gates.double().square().cpu()
|
| 311 |
+
row_reference = (
|
| 312 |
+
selected_reference.square().sum(dim=1).cpu() * route_weights
|
| 313 |
+
)
|
| 314 |
+
reference_energy = torch.zeros(len(ordered_steps), dtype=torch.float64)
|
| 315 |
+
counts = torch.zeros(len(ordered_steps), dtype=torch.int64)
|
| 316 |
+
reference_energy.scatter_add_(0, request_indices, row_reference)
|
| 317 |
+
counts.scatter_add_(
|
| 318 |
+
0,
|
| 319 |
+
request_indices,
|
| 320 |
+
torch.ones_like(request_indices, dtype=torch.int64),
|
| 321 |
+
)
|
| 322 |
+
return _DeferredFunctionalRows(
|
| 323 |
+
device_mask=device_mask,
|
| 324 |
+
reference=selected_reference,
|
| 325 |
+
route_weights=route_weights,
|
| 326 |
+
request_indices=request_indices,
|
| 327 |
+
reference_energy=reference_energy,
|
| 328 |
+
counts=counts,
|
| 329 |
+
request_count=len(ordered_steps),
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def _defer_functional_row_sse(
|
| 334 |
+
plan: _DeferredFunctionalRows,
|
| 335 |
+
candidate: torch.Tensor,
|
| 336 |
+
) -> torch.Tensor:
|
| 337 |
+
"""Compute per-row SSE on CUDA and defer its host synchronization."""
|
| 338 |
+
|
| 339 |
+
delta = candidate[plan.device_mask].double() - plan.reference
|
| 340 |
+
return delta.square().sum(dim=1)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def _finish_deferred_functional_sse(
|
| 344 |
+
plan: _DeferredFunctionalRows,
|
| 345 |
+
keys: Sequence[tuple[int, int]],
|
| 346 |
+
row_sse: Sequence[torch.Tensor],
|
| 347 |
+
) -> dict[tuple[int, int], torch.Tensor]:
|
| 348 |
+
"""Transfer all candidate rows once, then reproduce the CPU reductions."""
|
| 349 |
+
|
| 350 |
+
if len(keys) != len(row_sse):
|
| 351 |
+
raise ValueError("deferred functional metric keys and rows differ")
|
| 352 |
+
if not keys:
|
| 353 |
+
return {}
|
| 354 |
+
rows_cpu = torch.stack(tuple(row_sse)).cpu()
|
| 355 |
+
result: dict[tuple[int, int], torch.Tensor] = {}
|
| 356 |
+
for key, values in zip(keys, rows_cpu):
|
| 357 |
+
sse = torch.zeros(plan.request_count, dtype=torch.float64)
|
| 358 |
+
sse.scatter_add_(
|
| 359 |
+
0,
|
| 360 |
+
plan.request_indices,
|
| 361 |
+
values * plan.route_weights,
|
| 362 |
+
)
|
| 363 |
+
result[key] = sse
|
| 364 |
+
return result
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
def _mode_evidence(encodings: dict[str, QSRTMatrixEncoding]) -> dict[str, object]:
|
| 368 |
+
trellis_bytes = sum(
|
| 369 |
+
value.tensors["trellis"].numel()
|
| 370 |
+
* value.tensors["trellis"].element_size()
|
| 371 |
+
for value in encodings.values()
|
| 372 |
+
)
|
| 373 |
+
scale_bytes = sum(
|
| 374 |
+
value.tensors[name].numel() * value.tensors[name].element_size()
|
| 375 |
+
for value in encodings.values()
|
| 376 |
+
for name in ("suh", "svh")
|
| 377 |
+
)
|
| 378 |
+
return {
|
| 379 |
+
"trellis_bytes": trellis_bytes,
|
| 380 |
+
"scale_bytes": scale_bytes,
|
| 381 |
+
"total_bytes_before_layer_deduplication": trellis_bytes + scale_bytes,
|
| 382 |
+
"matrices": {
|
| 383 |
+
matrix: {
|
| 384 |
+
"proxy": float(value.coding["proxy"]),
|
| 385 |
+
"mode": value.coding["mode"],
|
| 386 |
+
"trellis_descriptor": value.coding["trellis_descriptor"],
|
| 387 |
+
}
|
| 388 |
+
for matrix, value in encodings.items()
|
| 389 |
+
},
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
def _candidate_mode_evidence(encodings: dict[str, object]) -> dict[str, object]:
|
| 394 |
+
trellis_bytes = sum(
|
| 395 |
+
value.reconstruction.numel()
|
| 396 |
+
* sum(value.plan.mode.context_bits)
|
| 397 |
+
// (len(value.plan.mode.context_bits) * 8)
|
| 398 |
+
for value in encodings.values()
|
| 399 |
+
)
|
| 400 |
+
scale_bytes = sum(
|
| 401 |
+
value.tensors[name].numel() * value.tensors[name].element_size()
|
| 402 |
+
for value in encodings.values()
|
| 403 |
+
for name in ("suh", "svh")
|
| 404 |
+
)
|
| 405 |
+
return {
|
| 406 |
+
"trellis_bytes": trellis_bytes,
|
| 407 |
+
"scale_bytes": scale_bytes,
|
| 408 |
+
"total_bytes_before_layer_deduplication": trellis_bytes + scale_bytes,
|
| 409 |
+
"matrices": {
|
| 410 |
+
matrix: {
|
| 411 |
+
"proxy": float(value.proxy),
|
| 412 |
+
"mode": value.plan.mode.name,
|
| 413 |
+
"mode_id": value.plan.mode.mode_id,
|
| 414 |
+
"rate_axis": value.plan.rate_axis,
|
| 415 |
+
}
|
| 416 |
+
for matrix, value in encodings.items()
|
| 417 |
+
},
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
def _candidate_middle_by_r13(
|
| 422 |
+
inputs: torch.Tensor,
|
| 423 |
+
upstream: dict[str, dict[int, QSRTMatrixCandidate]],
|
| 424 |
+
evaluated_modes: Sequence[int],
|
| 425 |
+
*,
|
| 426 |
+
activation: ExpertActivation = "situ",
|
| 427 |
+
) -> dict[int, torch.Tensor]:
|
| 428 |
+
"""Replay exact candidate reconstructions through the model activation."""
|
| 429 |
+
|
| 430 |
+
return {
|
| 431 |
+
int(r13): _source_middle(
|
| 432 |
+
F.linear(inputs, upstream["w1"][int(r13)].reconstruction),
|
| 433 |
+
F.linear(inputs, upstream["w3"][int(r13)].reconstruction),
|
| 434 |
+
activation=activation,
|
| 435 |
+
)
|
| 436 |
+
for r13 in evaluated_modes
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
def _conditional_h2_by_r13(
|
| 441 |
+
inputs: torch.Tensor,
|
| 442 |
+
gates: torch.Tensor,
|
| 443 |
+
fit_mask_device: torch.Tensor,
|
| 444 |
+
middle_by_r13: dict[int, torch.Tensor],
|
| 445 |
+
*,
|
| 446 |
+
global_h13: torch.Tensor,
|
| 447 |
+
global_h2: torch.Tensor,
|
| 448 |
+
device: torch.device,
|
| 449 |
+
hessian_policy: HessianPolicy,
|
| 450 |
+
have_local_support: bool,
|
| 451 |
+
) -> tuple[dict[int, torch.Tensor], dict[str, object]]:
|
| 452 |
+
"""Build one support-aware down-projection covariance per upstream mode."""
|
| 453 |
+
|
| 454 |
+
h2_by_r13: dict[int, torch.Tensor] = {}
|
| 455 |
+
evidence_by_r13: dict[str, object] = {}
|
| 456 |
+
identity_h2 = torch.eye(
|
| 457 |
+
global_h2.shape[0], dtype=torch.float32, device=device
|
| 458 |
+
)
|
| 459 |
+
for r13, middle in middle_by_r13.items():
|
| 460 |
+
if hessian_policy == "identity" or not have_local_support:
|
| 461 |
+
h2_by_r13[r13] = identity_h2
|
| 462 |
+
evidence_by_r13[f"R{r13}"] = {
|
| 463 |
+
"h2_shrinkage_policy": "identity_fallback",
|
| 464 |
+
"h2_local_alpha": 0.0,
|
| 465 |
+
"h2_effective_sample_size": 0.0,
|
| 466 |
+
"h2_identity_scale": 1.0,
|
| 467 |
+
}
|
| 468 |
+
continue
|
| 469 |
+
_, h2, evidence = build_expert_hessians(
|
| 470 |
+
inputs[fit_mask_device],
|
| 471 |
+
gates[fit_mask_device],
|
| 472 |
+
middle[fit_mask_device],
|
| 473 |
+
global_h13=global_h13,
|
| 474 |
+
global_h2=global_h2,
|
| 475 |
+
device=device,
|
| 476 |
+
)
|
| 477 |
+
h2_by_r13[r13] = h2
|
| 478 |
+
evidence_by_r13[f"R{r13}"] = evidence
|
| 479 |
+
return h2_by_r13, evidence_by_r13
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
def _coupled_logical_block_contexts(
|
| 483 |
+
mode_id: int,
|
| 484 |
+
groups: int,
|
| 485 |
+
) -> torch.Tensor:
|
| 486 |
+
"""Label coupled coordinates without changing their LDLQ traversal order."""
|
| 487 |
+
|
| 488 |
+
context_count = resolve_mode(mode_id).context_count
|
| 489 |
+
if groups <= 0 or groups % context_count:
|
| 490 |
+
raise ValueError("coupled contexts do not divide neuron groups")
|
| 491 |
+
return torch.arange(context_count, dtype=torch.long).repeat_interleave(
|
| 492 |
+
groups // context_count
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
def _quantize_conditional_down_grid(
|
| 497 |
+
source_w2_by_expert: Sequence[torch.Tensor],
|
| 498 |
+
block_contexts_by_expert: Sequence[torch.Tensor],
|
| 499 |
+
mode_specs_by_expert: Sequence[tuple[object, ...]],
|
| 500 |
+
h2_by_r13_by_expert: Sequence[dict[int, torch.Tensor]],
|
| 501 |
+
*,
|
| 502 |
+
layer: int,
|
| 503 |
+
device: torch.device,
|
| 504 |
+
shared_scale_scope: object,
|
| 505 |
+
quantizer_module: object | None,
|
| 506 |
+
codebook: str,
|
| 507 |
+
layout: SearchLayout,
|
| 508 |
+
ldlq_tf32: bool,
|
| 509 |
+
tailbite_context: int,
|
| 510 |
+
transform_seeds_by_expert: Sequence[
|
| 511 |
+
Mapping[str, QSRTTransformSeeds]
|
| 512 |
+
] | None = None,
|
| 513 |
+
) -> list[dict[int, dict[int, QSRTMatrixCandidate]]]:
|
| 514 |
+
"""Encode the exhaustive ``w2[r13, r2]`` grid in flattened GPU batches."""
|
| 515 |
+
|
| 516 |
+
count = len(source_w2_by_expert)
|
| 517 |
+
if not (
|
| 518 |
+
count
|
| 519 |
+
== len(block_contexts_by_expert)
|
| 520 |
+
== len(mode_specs_by_expert)
|
| 521 |
+
== len(h2_by_r13_by_expert)
|
| 522 |
+
):
|
| 523 |
+
raise ValueError("conditional down-grid inputs have inconsistent lengths")
|
| 524 |
+
if transform_seeds_by_expert is None:
|
| 525 |
+
transform_seeds_by_expert = [{} for _ in range(count)]
|
| 526 |
+
elif len(transform_seeds_by_expert) != count:
|
| 527 |
+
raise ValueError("conditional down-grid transform seeds have the wrong length")
|
| 528 |
+
|
| 529 |
+
flat_keys: list[tuple[int, int]] = []
|
| 530 |
+
flat_sources: list[dict[str, torch.Tensor]] = []
|
| 531 |
+
flat_contexts: list[torch.Tensor] = []
|
| 532 |
+
flat_modes: list[tuple[object, ...]] = []
|
| 533 |
+
flat_hessians: list[dict[str, torch.Tensor]] = []
|
| 534 |
+
flat_transform_seeds: list[Mapping[str, QSRTTransformSeeds]] = []
|
| 535 |
+
for expert_index, (source, contexts, modes, h2_by_r13) in enumerate(
|
| 536 |
+
zip(
|
| 537 |
+
source_w2_by_expert,
|
| 538 |
+
block_contexts_by_expert,
|
| 539 |
+
mode_specs_by_expert,
|
| 540 |
+
h2_by_r13_by_expert,
|
| 541 |
+
)
|
| 542 |
+
):
|
| 543 |
+
mode_ids = tuple(int(mode.mode_id) for mode in modes)
|
| 544 |
+
h2_bases = tuple(h2_by_r13)
|
| 545 |
+
if h2_bases != mode_ids:
|
| 546 |
+
raise ValueError("conditional H2 modes differ from upstream modes")
|
| 547 |
+
for r13 in h2_bases:
|
| 548 |
+
flat_keys.append((expert_index, r13))
|
| 549 |
+
flat_sources.append({"w2": source})
|
| 550 |
+
flat_contexts.append(contexts)
|
| 551 |
+
flat_modes.append(modes)
|
| 552 |
+
flat_hessians.append({"w2": h2_by_r13[r13]})
|
| 553 |
+
flat_transform_seeds.append(transform_seeds_by_expert[expert_index])
|
| 554 |
+
|
| 555 |
+
guarded_reuse = layout == "qsrt_guarded_reuse"
|
| 556 |
+
if guarded_reuse:
|
| 557 |
+
flat_candidates = quantize_qsrt_matrix_expert_batch(
|
| 558 |
+
flat_sources,
|
| 559 |
+
flat_contexts,
|
| 560 |
+
modes_by_expert=[(modes[0],) for modes in flat_modes],
|
| 561 |
+
hessians_by_expert=flat_hessians,
|
| 562 |
+
layer=layer,
|
| 563 |
+
device=device,
|
| 564 |
+
shared_scale_scope=shared_scale_scope,
|
| 565 |
+
quantizer_module=quantizer_module,
|
| 566 |
+
codebook=codebook,
|
| 567 |
+
layout="importance_ordered",
|
| 568 |
+
ldlq_tf32=ldlq_tf32,
|
| 569 |
+
tailbite_context=tailbite_context,
|
| 570 |
+
transform_seeds_by_expert=flat_transform_seeds,
|
| 571 |
+
)
|
| 572 |
+
shifted_indices = [
|
| 573 |
+
index for index, modes in enumerate(flat_modes) if len(modes) > 1
|
| 574 |
+
]
|
| 575 |
+
if shifted_indices:
|
| 576 |
+
shifted = quantize_qsrt_matrix_expert_batch(
|
| 577 |
+
[flat_sources[index] for index in shifted_indices],
|
| 578 |
+
[flat_contexts[index] for index in shifted_indices],
|
| 579 |
+
modes_by_expert=[flat_modes[index][1:] for index in shifted_indices],
|
| 580 |
+
hessians_by_expert=[flat_hessians[index] for index in shifted_indices],
|
| 581 |
+
layer=layer,
|
| 582 |
+
device=device,
|
| 583 |
+
shared_scale_scope=shared_scale_scope,
|
| 584 |
+
quantizer_module=quantizer_module,
|
| 585 |
+
codebook=codebook,
|
| 586 |
+
layout="qsrt_pair_prefix_reuse",
|
| 587 |
+
ldlq_tf32=ldlq_tf32,
|
| 588 |
+
tailbite_context=tailbite_context,
|
| 589 |
+
transform_seeds_by_expert=[
|
| 590 |
+
flat_transform_seeds[index] for index in shifted_indices
|
| 591 |
+
],
|
| 592 |
+
)
|
| 593 |
+
for index, extra in zip(shifted_indices, shifted):
|
| 594 |
+
flat_candidates[index]["w2"].update(extra["w2"])
|
| 595 |
+
else:
|
| 596 |
+
flat_candidates = quantize_qsrt_matrix_expert_batch(
|
| 597 |
+
flat_sources,
|
| 598 |
+
flat_contexts,
|
| 599 |
+
modes_by_expert=flat_modes,
|
| 600 |
+
hessians_by_expert=flat_hessians,
|
| 601 |
+
layer=layer,
|
| 602 |
+
device=device,
|
| 603 |
+
shared_scale_scope=shared_scale_scope,
|
| 604 |
+
quantizer_module=quantizer_module,
|
| 605 |
+
codebook=codebook,
|
| 606 |
+
layout=layout,
|
| 607 |
+
ldlq_tf32=ldlq_tf32,
|
| 608 |
+
tailbite_context=tailbite_context,
|
| 609 |
+
transform_seeds_by_expert=flat_transform_seeds,
|
| 610 |
+
)
|
| 611 |
+
|
| 612 |
+
result: list[dict[int, dict[int, QSRTMatrixCandidate]]] = [
|
| 613 |
+
{} for _ in range(count)
|
| 614 |
+
]
|
| 615 |
+
for (expert_index, r13), candidates in zip(flat_keys, flat_candidates):
|
| 616 |
+
result[expert_index][r13] = candidates["w2"]
|
| 617 |
+
return result
|
| 618 |
+
|
| 619 |
+
|
| 620 |
+
def encode_phase1_expert(
|
| 621 |
+
store: MatrixStore,
|
| 622 |
+
samples: LayerSamples,
|
| 623 |
+
*,
|
| 624 |
+
layer: int,
|
| 625 |
+
expert: int,
|
| 626 |
+
partition: RequestPartition,
|
| 627 |
+
global_h13: torch.Tensor,
|
| 628 |
+
global_h2: torch.Tensor,
|
| 629 |
+
fallback_block_contexts: torch.Tensor,
|
| 630 |
+
fallback_block_scores: torch.Tensor,
|
| 631 |
+
device: torch.device,
|
| 632 |
+
shared_scale_scope: object,
|
| 633 |
+
mode_ids: Sequence[int] = PHASE1_MODE_IDS,
|
| 634 |
+
quantizer_module: object | None = None,
|
| 635 |
+
min_fit_documents: int = 6,
|
| 636 |
+
min_confirmation_documents: int = 4,
|
| 637 |
+
bootstrap_replicates: int = 2_000,
|
| 638 |
+
minimum_improvement: float = 0.0,
|
| 639 |
+
logical_trellis_schema: str = SCHEMA,
|
| 640 |
+
codebook: str = CODEBOOK_SQG_XOR_CHEB_T12,
|
| 641 |
+
layout: SearchLayout = "importance_ordered",
|
| 642 |
+
ldlq_tf32: bool = False,
|
| 643 |
+
tailbite_context: int = 128,
|
| 644 |
+
hessian_policy: HessianPolicy = "captured_blend",
|
| 645 |
+
permutation_policy: PermutationPolicy = "h2_reverse",
|
| 646 |
+
folded_scale_power: float = 0.0,
|
| 647 |
+
transform_seeds: Mapping[str, QSRTTransformSeeds] | None = None,
|
| 648 |
+
activation: ExpertActivation = "situ",
|
| 649 |
+
) -> QSRTCandidateEncoding:
|
| 650 |
+
"""Encode and conservatively select one official-source expert.
|
| 651 |
+
|
| 652 |
+
The official model is never instantiated. ``w1`` and ``w3`` are streamed
|
| 653 |
+
together so their same-shape R candidates can share one batched traversal;
|
| 654 |
+
they are released before ``w2`` is loaded.
|
| 655 |
+
"""
|
| 656 |
+
|
| 657 |
+
if device.type != "cuda":
|
| 658 |
+
raise ValueError("QSRT candidate encoding requires CUDA")
|
| 659 |
+
if not 1 <= layer <= 92 or not 0 <= expert < 896:
|
| 660 |
+
raise ValueError("invalid K3 layer/expert assignment")
|
| 661 |
+
modes = tuple(int(mode) for mode in mode_ids)
|
| 662 |
+
fixed_k3 = modes == (R0.mode_id,)
|
| 663 |
+
fixed_high_rate = modes == (H308.mode_id,)
|
| 664 |
+
fixed_k2 = modes == (K2.mode_id,)
|
| 665 |
+
fixed_profile = fixed_k3 or fixed_high_rate or fixed_k2
|
| 666 |
+
if (
|
| 667 |
+
not modes
|
| 668 |
+
or len(set(modes)) != len(modes)
|
| 669 |
+
or (not fixed_profile and modes[0] != 0)
|
| 670 |
+
):
|
| 671 |
+
raise ValueError(
|
| 672 |
+
"mode_ids must be H308/K2 alone or unique and begin with R0"
|
| 673 |
+
)
|
| 674 |
+
if any(mode not in (0, 1, 2, H308.mode_id, K2.mode_id) for mode in modes):
|
| 675 |
+
raise ValueError("mode_ids contain an unsupported schedule")
|
| 676 |
+
if hessian_policy not in HESSIAN_POLICIES:
|
| 677 |
+
raise ValueError(f"unsupported Hessian policy: {hessian_policy}")
|
| 678 |
+
if permutation_policy not in PERMUTATION_POLICIES:
|
| 679 |
+
raise ValueError(f"unsupported permutation policy: {permutation_policy}")
|
| 680 |
+
|
| 681 |
+
all_rows = select_expert_rows(samples, expert, partition.all)
|
| 682 |
+
fit_mask = _split_rows(all_rows, partition.fit)
|
| 683 |
+
confirmation_mask = _split_rows(all_rows, partition.confirmation)
|
| 684 |
+
fit_documents = int(torch.unique(all_rows.request_steps[fit_mask]).numel())
|
| 685 |
+
confirmation_documents = int(
|
| 686 |
+
torch.unique(all_rows.request_steps[confirmation_mask]).numel()
|
| 687 |
+
)
|
| 688 |
+
inputs = all_rows.inputs.float().to(device=device)
|
| 689 |
+
gates = all_rows.gates.float().to(device=device)
|
| 690 |
+
|
| 691 |
+
source_w1 = _load_source_matrix(store, layer, expert, "w1", device)
|
| 692 |
+
source_w3 = _load_source_matrix(store, layer, expert, "w3", device)
|
| 693 |
+
if all_rows.rows:
|
| 694 |
+
source_gate = F.linear(inputs, source_w1)
|
| 695 |
+
source_up = F.linear(inputs, source_w3)
|
| 696 |
+
source_middle = _source_middle(
|
| 697 |
+
source_gate,
|
| 698 |
+
source_up,
|
| 699 |
+
activation=activation,
|
| 700 |
+
)
|
| 701 |
+
del source_gate, source_up
|
| 702 |
+
else:
|
| 703 |
+
source_middle = torch.empty(
|
| 704 |
+
(0, global_h2.shape[0]), dtype=torch.float32, device=device
|
| 705 |
+
)
|
| 706 |
+
|
| 707 |
+
have_local_support = fit_documents >= min_fit_documents
|
| 708 |
+
fit_mask_device = fit_mask.to(device=device)
|
| 709 |
+
if have_local_support:
|
| 710 |
+
block_contexts, block_scores = permutation_policy_contexts(
|
| 711 |
+
source_middle[fit_mask_device],
|
| 712 |
+
gates[fit_mask_device],
|
| 713 |
+
policy=permutation_policy,
|
| 714 |
+
)
|
| 715 |
+
h13 = global_h13
|
| 716 |
+
covariance: dict[str, object] = {
|
| 717 |
+
"gate_square_sum": float(
|
| 718 |
+
gates[fit_mask_device].square().double().sum()
|
| 719 |
+
),
|
| 720 |
+
"h13_local_alpha": 0.0,
|
| 721 |
+
}
|
| 722 |
+
context_basis = (
|
| 723 |
+
f"official_source_{_activation_basis(activation)}_fit_documents:"
|
| 724 |
+
f"{permutation_policy}"
|
| 725 |
+
)
|
| 726 |
+
covariance = {
|
| 727 |
+
**covariance,
|
| 728 |
+
"fit_rows": int(fit_mask.sum()),
|
| 729 |
+
"fit_documents": fit_documents,
|
| 730 |
+
}
|
| 731 |
+
else:
|
| 732 |
+
block_contexts = fallback_block_contexts.clone()
|
| 733 |
+
block_scores = fallback_block_scores.clone()
|
| 734 |
+
h13 = global_h13
|
| 735 |
+
context_basis = "fallback_context_order_no_local_support"
|
| 736 |
+
covariance = {
|
| 737 |
+
"fit_rows": int(fit_mask.sum()),
|
| 738 |
+
"fit_documents": fit_documents,
|
| 739 |
+
"gate_square_sum": float(gates[fit_mask_device].square().sum()),
|
| 740 |
+
"h13_local_alpha": 0.0,
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
if fixed_k2:
|
| 744 |
+
block_contexts = torch.zeros_like(block_contexts)
|
| 745 |
+
context_basis = "uniform_k2_identity_transformed_coordinate_order"
|
| 746 |
+
can_confirm = confirmation_documents >= min_confirmation_documents
|
| 747 |
+
evaluated_modes = (
|
| 748 |
+
modes if fixed_profile or (have_local_support and can_confirm) else (0,)
|
| 749 |
+
)
|
| 750 |
+
evaluated_formats = tuple(
|
| 751 |
+
(r13, r2) for r13 in evaluated_modes for r2 in evaluated_modes
|
| 752 |
+
)
|
| 753 |
+
block_contexts_device = block_contexts.to(device=device, dtype=torch.long)
|
| 754 |
+
intermediate_conditioning = _folded_intermediate_conditioning(
|
| 755 |
+
block_scores.to(device=device),
|
| 756 |
+
block_contexts_device,
|
| 757 |
+
power=folded_scale_power,
|
| 758 |
+
)
|
| 759 |
+
if intermediate_conditioning is not None:
|
| 760 |
+
covariance["folded_scale_power"] = float(folded_scale_power)
|
| 761 |
+
covariance["folded_scale_min"] = float(intermediate_conditioning.min())
|
| 762 |
+
covariance["folded_scale_max"] = float(intermediate_conditioning.max())
|
| 763 |
+
context_basis += "+folded_128_channel_conditioning"
|
| 764 |
+
|
| 765 |
+
mode_specs = tuple(resolve_mode(mode) for mode in evaluated_modes)
|
| 766 |
+
guarded_reuse = layout == "qsrt_guarded_reuse"
|
| 767 |
+
if guarded_reuse and any(mode > 2 for mode in evaluated_modes):
|
| 768 |
+
raise ValueError("qsrt_guarded_reuse supports only R0 through R2")
|
| 769 |
+
upstream_layout: Layout = "importance_ordered" if guarded_reuse else layout
|
| 770 |
+
upstream_candidates = quantize_qsrt_matrix_candidates_batched(
|
| 771 |
+
{"w1": source_w1, "w3": source_w3},
|
| 772 |
+
block_contexts_device,
|
| 773 |
+
modes=mode_specs,
|
| 774 |
+
hessians={"w1": h13, "w3": h13},
|
| 775 |
+
layer=layer,
|
| 776 |
+
device=device,
|
| 777 |
+
shared_scale_scope=shared_scale_scope,
|
| 778 |
+
quantizer_module=quantizer_module,
|
| 779 |
+
codebook=codebook,
|
| 780 |
+
layout=upstream_layout,
|
| 781 |
+
ldlq_tf32=ldlq_tf32,
|
| 782 |
+
tailbite_context=tailbite_context,
|
| 783 |
+
transform_seeds=transform_seeds,
|
| 784 |
+
intermediate_conditioning=intermediate_conditioning,
|
| 785 |
+
)
|
| 786 |
+
del source_w1, source_w3
|
| 787 |
+
|
| 788 |
+
middle_by_r13 = _candidate_middle_by_r13(
|
| 789 |
+
inputs,
|
| 790 |
+
upstream_candidates,
|
| 791 |
+
evaluated_modes,
|
| 792 |
+
activation=activation,
|
| 793 |
+
)
|
| 794 |
+
h2_by_r13, h2_evidence = _conditional_h2_by_r13(
|
| 795 |
+
inputs,
|
| 796 |
+
gates,
|
| 797 |
+
fit_mask_device,
|
| 798 |
+
middle_by_r13,
|
| 799 |
+
global_h13=global_h13,
|
| 800 |
+
global_h2=global_h2,
|
| 801 |
+
device=device,
|
| 802 |
+
hessian_policy=hessian_policy,
|
| 803 |
+
have_local_support=have_local_support,
|
| 804 |
+
)
|
| 805 |
+
covariance.update(
|
| 806 |
+
{
|
| 807 |
+
"basis": (
|
| 808 |
+
"identity"
|
| 809 |
+
if hessian_policy == "identity"
|
| 810 |
+
else (
|
| 811 |
+
f"decoded_candidate_{_activation_basis(activation)}_"
|
| 812 |
+
"adaptive_shrinkage"
|
| 813 |
+
if have_local_support
|
| 814 |
+
else "identity_support_fallback"
|
| 815 |
+
)
|
| 816 |
+
),
|
| 817 |
+
"h2_by_r13": h2_evidence,
|
| 818 |
+
}
|
| 819 |
+
)
|
| 820 |
+
context_basis += f"+decoded_candidate_{_activation_basis(activation)}_h2"
|
| 821 |
+
|
| 822 |
+
source_w2 = _load_source_matrix(store, layer, expert, "w2", device)
|
| 823 |
+
if all_rows.rows:
|
| 824 |
+
reference_output = F.linear(source_middle, source_w2)
|
| 825 |
+
else:
|
| 826 |
+
reference_output = torch.empty(
|
| 827 |
+
(0, global_h13.shape[0]), dtype=torch.float32, device=device
|
| 828 |
+
)
|
| 829 |
+
down_candidates = _quantize_conditional_down_grid(
|
| 830 |
+
[source_w2],
|
| 831 |
+
[block_contexts_device],
|
| 832 |
+
[mode_specs],
|
| 833 |
+
[h2_by_r13],
|
| 834 |
+
layer=layer,
|
| 835 |
+
device=device,
|
| 836 |
+
shared_scale_scope=shared_scale_scope,
|
| 837 |
+
quantizer_module=quantizer_module,
|
| 838 |
+
codebook=codebook,
|
| 839 |
+
layout=layout,
|
| 840 |
+
ldlq_tf32=ldlq_tf32,
|
| 841 |
+
tailbite_context=tailbite_context,
|
| 842 |
+
transform_seeds_by_expert=[transform_seeds or {}],
|
| 843 |
+
)[0]
|
| 844 |
+
fit_sse: dict[tuple[int, int], torch.Tensor] = {}
|
| 845 |
+
confirmation_sse: dict[tuple[int, int], torch.Tensor] = {}
|
| 846 |
+
fit_reference = fit_counts = confirmation_reference = confirmation_counts = None
|
| 847 |
+
mode_coding: dict[tuple[int, int], dict[str, object]] = {}
|
| 848 |
+
for rate_pair in evaluated_formats:
|
| 849 |
+
r13, r2 = rate_pair
|
| 850 |
+
candidate_output = F.linear(
|
| 851 |
+
middle_by_r13[r13], down_candidates[r13][r2].reconstruction
|
| 852 |
+
)
|
| 853 |
+
fit_metric = (
|
| 854 |
+
functional_sse_by_request(
|
| 855 |
+
reference_output[fit_mask.to(device=device)],
|
| 856 |
+
candidate_output[fit_mask.to(device=device)],
|
| 857 |
+
gates[fit_mask.to(device=device)],
|
| 858 |
+
all_rows.request_steps[fit_mask],
|
| 859 |
+
partition.fit,
|
| 860 |
+
)
|
| 861 |
+
if int(fit_mask.sum())
|
| 862 |
+
else _empty_metrics(partition.fit)
|
| 863 |
+
)
|
| 864 |
+
confirmation_metric = (
|
| 865 |
+
functional_sse_by_request(
|
| 866 |
+
reference_output[confirmation_mask.to(device=device)],
|
| 867 |
+
candidate_output[confirmation_mask.to(device=device)],
|
| 868 |
+
gates[confirmation_mask.to(device=device)],
|
| 869 |
+
all_rows.request_steps[confirmation_mask],
|
| 870 |
+
partition.confirmation,
|
| 871 |
+
)
|
| 872 |
+
if int(confirmation_mask.sum())
|
| 873 |
+
else _empty_metrics(partition.confirmation)
|
| 874 |
+
)
|
| 875 |
+
fit_sse[rate_pair] = fit_metric[0]
|
| 876 |
+
confirmation_sse[rate_pair] = confirmation_metric[0]
|
| 877 |
+
if fit_reference is None:
|
| 878 |
+
fit_reference, fit_counts = fit_metric[1], fit_metric[2]
|
| 879 |
+
confirmation_reference, confirmation_counts = (
|
| 880 |
+
confirmation_metric[1],
|
| 881 |
+
confirmation_metric[2],
|
| 882 |
+
)
|
| 883 |
+
elif not (
|
| 884 |
+
torch.equal(fit_reference, fit_metric[1])
|
| 885 |
+
and torch.equal(fit_counts, fit_metric[2])
|
| 886 |
+
and torch.equal(confirmation_reference, confirmation_metric[1])
|
| 887 |
+
and torch.equal(confirmation_counts, confirmation_metric[2])
|
| 888 |
+
):
|
| 889 |
+
raise ValueError("mode candidates changed metric support or reference energy")
|
| 890 |
+
mode_coding[rate_pair] = _candidate_mode_evidence(
|
| 891 |
+
{
|
| 892 |
+
"w1": upstream_candidates["w1"][r13],
|
| 893 |
+
"w3": upstream_candidates["w3"][r13],
|
| 894 |
+
"w2": down_candidates[r13][r2],
|
| 895 |
+
}
|
| 896 |
+
)
|
| 897 |
+
del candidate_output
|
| 898 |
+
|
| 899 |
+
del source_w2
|
| 900 |
+
|
| 901 |
+
assert fit_reference is not None and fit_counts is not None
|
| 902 |
+
assert confirmation_reference is not None and confirmation_counts is not None
|
| 903 |
+
if fixed_profile:
|
| 904 |
+
fixed_mode = H308 if fixed_high_rate else (K2 if fixed_k2 else R0)
|
| 905 |
+
selection = RatePairSelection(
|
| 906 |
+
proposed_r13=fixed_mode.mode_id,
|
| 907 |
+
proposed_r2=fixed_mode.mode_id,
|
| 908 |
+
selected_r13=fixed_mode.mode_id,
|
| 909 |
+
selected_r2=fixed_mode.mode_id,
|
| 910 |
+
accepted=True,
|
| 911 |
+
reason=(
|
| 912 |
+
"fixed_22k3_2k4_high_rate_profile"
|
| 913 |
+
if fixed_high_rate
|
| 914 |
+
else (
|
| 915 |
+
"fixed_uniform_k2_profile"
|
| 916 |
+
if fixed_k2
|
| 917 |
+
else "fixed_uniform_k3_profile"
|
| 918 |
+
)
|
| 919 |
+
),
|
| 920 |
+
fit_documents=int(torch.count_nonzero(fit_counts)),
|
| 921 |
+
confirmation_documents=int(torch.count_nonzero(confirmation_counts)),
|
| 922 |
+
confirmation_relative_improvement=None,
|
| 923 |
+
confirmation_ci95=(None, None),
|
| 924 |
+
bootstrap_replicates_valid=0,
|
| 925 |
+
)
|
| 926 |
+
else:
|
| 927 |
+
selection = select_phase1_rate_pair(
|
| 928 |
+
fit_sse,
|
| 929 |
+
confirmation_sse,
|
| 930 |
+
fit_counts=fit_counts,
|
| 931 |
+
confirmation_counts=confirmation_counts,
|
| 932 |
+
modes=evaluated_formats,
|
| 933 |
+
min_fit_documents=min_fit_documents,
|
| 934 |
+
min_confirmation_documents=min_confirmation_documents,
|
| 935 |
+
minimum_improvement=minimum_improvement,
|
| 936 |
+
bootstrap_replicates=bootstrap_replicates,
|
| 937 |
+
seed=deterministic_expert_seed(layer, expert),
|
| 938 |
+
)
|
| 939 |
+
selected_r13, selected_r2 = selection.selected
|
| 940 |
+
selected_candidates = {
|
| 941 |
+
"w1": upstream_candidates["w1"][selected_r13],
|
| 942 |
+
"w3": upstream_candidates["w3"][selected_r13],
|
| 943 |
+
"w2": down_candidates[selected_r13][selected_r2],
|
| 944 |
+
}
|
| 945 |
+
selected_encodings = {
|
| 946 |
+
matrix: finalize_qsrt_matrix_candidate(
|
| 947 |
+
value,
|
| 948 |
+
layer=layer,
|
| 949 |
+
logical_trellis_schema=logical_trellis_schema,
|
| 950 |
+
codebook=codebook,
|
| 951 |
+
tailbite_context=tailbite_context,
|
| 952 |
+
)
|
| 953 |
+
for matrix, value in selected_candidates.items()
|
| 954 |
+
}
|
| 955 |
+
physical_permutation = selected_encodings["w1"].plan.physical_permutation
|
| 956 |
+
for matrix in ("w3", "w2"):
|
| 957 |
+
if not torch.equal(
|
| 958 |
+
selected_encodings[matrix].plan.physical_permutation,
|
| 959 |
+
physical_permutation,
|
| 960 |
+
):
|
| 961 |
+
raise ValueError("selected matrices do not share one physical permutation")
|
| 962 |
+
expert_encoding = QSRTExpertEncoding(
|
| 963 |
+
format=ExpertFormatSpec.compressed(selected_r13, selected_r2),
|
| 964 |
+
matrices=selected_encodings,
|
| 965 |
+
physical_permutation=physical_permutation,
|
| 966 |
+
coding={
|
| 967 |
+
"format": ExpertFormatSpec.compressed(
|
| 968 |
+
selected_r13, selected_r2
|
| 969 |
+
).name,
|
| 970 |
+
"r13": selected_r13,
|
| 971 |
+
"r2": selected_r2,
|
| 972 |
+
"codebook": codebook,
|
| 973 |
+
"tailbite_context": int(tailbite_context),
|
| 974 |
+
**_mode_evidence(selected_encodings),
|
| 975 |
+
},
|
| 976 |
+
)
|
| 977 |
+
return QSRTCandidateEncoding(
|
| 978 |
+
expert=expert_encoding,
|
| 979 |
+
selection=selection,
|
| 980 |
+
block_contexts=block_contexts.detach().cpu().to(torch.uint8).contiguous(),
|
| 981 |
+
block_scores=block_scores.detach().cpu().float().contiguous(),
|
| 982 |
+
context_basis=context_basis,
|
| 983 |
+
covariance=covariance,
|
| 984 |
+
evaluated_modes=evaluated_modes,
|
| 985 |
+
evaluated_formats=evaluated_formats,
|
| 986 |
+
fit_sse=fit_sse,
|
| 987 |
+
confirmation_sse=confirmation_sse,
|
| 988 |
+
fit_reference_energy=fit_reference,
|
| 989 |
+
confirmation_reference_energy=confirmation_reference,
|
| 990 |
+
fit_counts=fit_counts,
|
| 991 |
+
confirmation_counts=confirmation_counts,
|
| 992 |
+
mode_coding=mode_coding,
|
| 993 |
+
)
|
| 994 |
+
|
| 995 |
+
|
| 996 |
+
def encode_phase1_expert_batch(
|
| 997 |
+
store: MatrixStore,
|
| 998 |
+
samples: LayerSamples,
|
| 999 |
+
*,
|
| 1000 |
+
layer: int,
|
| 1001 |
+
experts: Sequence[int],
|
| 1002 |
+
partition: RequestPartition,
|
| 1003 |
+
global_h13: torch.Tensor,
|
| 1004 |
+
global_h2: torch.Tensor,
|
| 1005 |
+
fallback_block_contexts: torch.Tensor,
|
| 1006 |
+
fallback_block_scores: torch.Tensor,
|
| 1007 |
+
device: torch.device,
|
| 1008 |
+
shared_scale_scope: object,
|
| 1009 |
+
mode_ids: Sequence[int] = PHASE1_MODE_IDS,
|
| 1010 |
+
quantizer_module: object | None = None,
|
| 1011 |
+
min_fit_documents: int = 6,
|
| 1012 |
+
min_confirmation_documents: int = 4,
|
| 1013 |
+
bootstrap_replicates: int = 2_000,
|
| 1014 |
+
minimum_improvement: float = 0.0,
|
| 1015 |
+
logical_trellis_schema: str = SCHEMA,
|
| 1016 |
+
codebook: str = CODEBOOK_SQG_XOR_CHEB_T12,
|
| 1017 |
+
layout: SearchLayout = "importance_ordered",
|
| 1018 |
+
ldlq_tf32: bool = False,
|
| 1019 |
+
tailbite_context: int = 128,
|
| 1020 |
+
hessian_policy: HessianPolicy = "captured_blend",
|
| 1021 |
+
permutation_policy: PermutationPolicy = "h2_reverse",
|
| 1022 |
+
folded_scale_power: float = 0.0,
|
| 1023 |
+
transform_seeds_by_expert: Mapping[
|
| 1024 |
+
int, Mapping[str, QSRTTransformSeeds]
|
| 1025 |
+
] | None = None,
|
| 1026 |
+
coupled_intermediate_draws_by_expert: Mapping[int, int] | None = None,
|
| 1027 |
+
activation: ExpertActivation = "situ",
|
| 1028 |
+
) -> list[QSRTCandidateEncoding]:
|
| 1029 |
+
"""Encode several experts while batching their independent trellis work.
|
| 1030 |
+
|
| 1031 |
+
This preserves the single-expert calibration, dense-H, candidate scoring,
|
| 1032 |
+
and selection contracts. Only the execution schedule changes: all
|
| 1033 |
+
same-shape w1/w3 projections in the chunk are submitted together, followed
|
| 1034 |
+
by all w2 projections. The larger tile batches fill SM120 much more
|
| 1035 |
+
effectively than one expert's R1/R2 replacement records.
|
| 1036 |
+
"""
|
| 1037 |
+
|
| 1038 |
+
expert_ids = tuple(int(expert) for expert in experts)
|
| 1039 |
+
if not expert_ids or len(set(expert_ids)) != len(expert_ids):
|
| 1040 |
+
raise ValueError("expert batch must be nonempty and unique")
|
| 1041 |
+
if device.type != "cuda":
|
| 1042 |
+
raise ValueError("QSRT candidate encoding requires CUDA")
|
| 1043 |
+
if not 1 <= layer <= 92 or any(not 0 <= expert < 896 for expert in expert_ids):
|
| 1044 |
+
raise ValueError("invalid K3 layer/expert assignment")
|
| 1045 |
+
modes = tuple(int(mode) for mode in mode_ids)
|
| 1046 |
+
fixed_k3 = modes == (R0.mode_id,)
|
| 1047 |
+
fixed_high_rate = modes == (H308.mode_id,)
|
| 1048 |
+
fixed_k2 = modes == (K2.mode_id,)
|
| 1049 |
+
fixed_profile = fixed_k3 or fixed_high_rate or fixed_k2
|
| 1050 |
+
coupled = coupled_intermediate_draws_by_expert is not None
|
| 1051 |
+
_activation_basis(activation)
|
| 1052 |
+
if coupled:
|
| 1053 |
+
if modes not in (
|
| 1054 |
+
(R0.mode_id,),
|
| 1055 |
+
(H308.mode_id,),
|
| 1056 |
+
(K2.mode_id,),
|
| 1057 |
+
):
|
| 1058 |
+
raise ValueError(
|
| 1059 |
+
"coupled activation-boundary transforms currently support "
|
| 1060 |
+
"only a fixed K2, K3, or 22-K3/2-K4 schedule"
|
| 1061 |
+
)
|
| 1062 |
+
if folded_scale_power != 0.0:
|
| 1063 |
+
raise ValueError(
|
| 1064 |
+
"coupled activation-boundary transforms require "
|
| 1065 |
+
"folded_scale_power zero"
|
| 1066 |
+
)
|
| 1067 |
+
missing = set(expert_ids) - set(coupled_intermediate_draws_by_expert)
|
| 1068 |
+
extra = set(coupled_intermediate_draws_by_expert) - set(expert_ids)
|
| 1069 |
+
if missing or extra:
|
| 1070 |
+
raise ValueError("coupled draw map must cover exactly the expert batch")
|
| 1071 |
+
if (
|
| 1072 |
+
not modes
|
| 1073 |
+
or len(set(modes)) != len(modes)
|
| 1074 |
+
or (not fixed_profile and modes[0] != 0)
|
| 1075 |
+
):
|
| 1076 |
+
raise ValueError(
|
| 1077 |
+
"mode_ids must be H308/K2 alone or unique and begin with R0"
|
| 1078 |
+
)
|
| 1079 |
+
if any(mode not in (0, 1, 2, H308.mode_id, K2.mode_id) for mode in modes):
|
| 1080 |
+
raise ValueError("mode_ids contain an unsupported schedule")
|
| 1081 |
+
if hessian_policy not in HESSIAN_POLICIES:
|
| 1082 |
+
raise ValueError(f"unsupported Hessian policy: {hessian_policy}")
|
| 1083 |
+
if permutation_policy not in PERMUTATION_POLICIES:
|
| 1084 |
+
raise ValueError(f"unsupported permutation policy: {permutation_policy}")
|
| 1085 |
+
|
| 1086 |
+
prepared: list[dict[str, object]] = []
|
| 1087 |
+
upstream_sources: list[dict[str, torch.Tensor]] = []
|
| 1088 |
+
upstream_contexts: list[torch.Tensor] = []
|
| 1089 |
+
upstream_modes: list[tuple[object, ...]] = []
|
| 1090 |
+
upstream_hessians: list[dict[str, torch.Tensor]] = []
|
| 1091 |
+
upstream_conditioning: list[torch.Tensor | None] = []
|
| 1092 |
+
seed_maps = [
|
| 1093 |
+
(transform_seeds_by_expert or {}).get(expert, {}) for expert in expert_ids
|
| 1094 |
+
]
|
| 1095 |
+
|
| 1096 |
+
for expert in expert_ids:
|
| 1097 |
+
all_rows = select_expert_rows(samples, expert, partition.all)
|
| 1098 |
+
fit_mask = _split_rows(all_rows, partition.fit)
|
| 1099 |
+
confirmation_mask = _split_rows(all_rows, partition.confirmation)
|
| 1100 |
+
fit_documents = int(torch.unique(all_rows.request_steps[fit_mask]).numel())
|
| 1101 |
+
confirmation_documents = int(
|
| 1102 |
+
torch.unique(all_rows.request_steps[confirmation_mask]).numel()
|
| 1103 |
+
)
|
| 1104 |
+
inputs = all_rows.inputs.float().to(device=device)
|
| 1105 |
+
gates = all_rows.gates.float().to(device=device)
|
| 1106 |
+
|
| 1107 |
+
source_w1 = _load_source_matrix(store, layer, expert, "w1", device)
|
| 1108 |
+
source_w3 = _load_source_matrix(store, layer, expert, "w3", device)
|
| 1109 |
+
if all_rows.rows:
|
| 1110 |
+
source_gate = F.linear(inputs, source_w1)
|
| 1111 |
+
source_up = F.linear(inputs, source_w3)
|
| 1112 |
+
source_middle = _source_middle(
|
| 1113 |
+
source_gate,
|
| 1114 |
+
source_up,
|
| 1115 |
+
activation=activation,
|
| 1116 |
+
)
|
| 1117 |
+
del source_gate, source_up
|
| 1118 |
+
else:
|
| 1119 |
+
source_middle = torch.empty(
|
| 1120 |
+
(0, global_h2.shape[0]), dtype=torch.float32, device=device
|
| 1121 |
+
)
|
| 1122 |
+
|
| 1123 |
+
have_local_support = fit_documents >= min_fit_documents
|
| 1124 |
+
fit_mask_device = fit_mask.to(device=device)
|
| 1125 |
+
if bool(fit_mask.any()):
|
| 1126 |
+
block_contexts, block_scores = permutation_policy_contexts(
|
| 1127 |
+
source_middle[fit_mask_device],
|
| 1128 |
+
gates[fit_mask_device],
|
| 1129 |
+
policy=permutation_policy,
|
| 1130 |
+
)
|
| 1131 |
+
context_basis = (
|
| 1132 |
+
f"official_source_{_activation_basis(activation)}_fit_documents:"
|
| 1133 |
+
f"{permutation_policy}"
|
| 1134 |
+
)
|
| 1135 |
+
else:
|
| 1136 |
+
block_contexts = fallback_block_contexts.clone()
|
| 1137 |
+
block_scores = fallback_block_scores.clone()
|
| 1138 |
+
context_basis = "identity_logical_order_no_routed_fit_rows"
|
| 1139 |
+
|
| 1140 |
+
if have_local_support:
|
| 1141 |
+
h13 = global_h13
|
| 1142 |
+
covariance: dict[str, object] = {
|
| 1143 |
+
"gate_square_sum": float(
|
| 1144 |
+
gates[fit_mask_device].square().double().sum()
|
| 1145 |
+
),
|
| 1146 |
+
"h13_local_alpha": 0.0,
|
| 1147 |
+
}
|
| 1148 |
+
covariance = {
|
| 1149 |
+
**covariance,
|
| 1150 |
+
"fit_rows": int(fit_mask.sum()),
|
| 1151 |
+
"fit_documents": fit_documents,
|
| 1152 |
+
}
|
| 1153 |
+
else:
|
| 1154 |
+
h13 = global_h13
|
| 1155 |
+
covariance = {
|
| 1156 |
+
"fit_rows": int(fit_mask.sum()),
|
| 1157 |
+
"fit_documents": fit_documents,
|
| 1158 |
+
"gate_square_sum": float(
|
| 1159 |
+
gates[fit_mask.to(device=device)].square().sum()
|
| 1160 |
+
),
|
| 1161 |
+
"h13_local_alpha": 0.0,
|
| 1162 |
+
}
|
| 1163 |
+
|
| 1164 |
+
if coupled:
|
| 1165 |
+
# The coupled preactivation transform is not coordinatewise, so an
|
| 1166 |
+
# arbitrary neuron permutation cannot be pushed through it. Keep
|
| 1167 |
+
# LDLQ in the transformed logical order. K2 has one context; R0 K3
|
| 1168 |
+
# has 24 monotonically labelled equal-size contexts, whose stable
|
| 1169 |
+
# importance order is the identity permutation.
|
| 1170 |
+
context_count = resolve_mode(modes[0]).context_count
|
| 1171 |
+
if any(
|
| 1172 |
+
resolve_mode(mode).context_count != context_count
|
| 1173 |
+
for mode in modes
|
| 1174 |
+
):
|
| 1175 |
+
raise ValueError("coupled rate candidates change context cardinality")
|
| 1176 |
+
block_contexts = _coupled_logical_block_contexts(
|
| 1177 |
+
modes[0], block_contexts.numel()
|
| 1178 |
+
)
|
| 1179 |
+
context_basis = "coupled_transform_logical_coordinate_order"
|
| 1180 |
+
elif fixed_k2:
|
| 1181 |
+
block_contexts = torch.zeros_like(block_contexts)
|
| 1182 |
+
context_basis = "uniform_k2_identity_coordinate_order"
|
| 1183 |
+
can_confirm = confirmation_documents >= min_confirmation_documents
|
| 1184 |
+
evaluated_modes = (
|
| 1185 |
+
modes if fixed_profile or (have_local_support and can_confirm) else (0,)
|
| 1186 |
+
)
|
| 1187 |
+
mode_specs = tuple(resolve_mode(mode) for mode in evaluated_modes)
|
| 1188 |
+
contexts_device = block_contexts.to(device=device, dtype=torch.long)
|
| 1189 |
+
coupled_execution_basis: CoupledHadamardExecution | None = None
|
| 1190 |
+
coupled_source_w2: torch.Tensor | None = None
|
| 1191 |
+
coupled_reference_output: torch.Tensor | None = None
|
| 1192 |
+
if coupled:
|
| 1193 |
+
draw = int(coupled_intermediate_draws_by_expert[expert])
|
| 1194 |
+
spec = CoupledHadamardSpec(
|
| 1195 |
+
intermediate_draw=draw,
|
| 1196 |
+
activation=activation,
|
| 1197 |
+
)
|
| 1198 |
+
source_w2 = _load_source_matrix(store, layer, expert, "w2", device)
|
| 1199 |
+
transformed = encode_coupled_weights(
|
| 1200 |
+
(source_w1, source_w3, source_w2), spec
|
| 1201 |
+
)
|
| 1202 |
+
coupled_execution_basis = coupled_execution(transformed, spec)
|
| 1203 |
+
coupled_source_w2 = transformed[2]
|
| 1204 |
+
coupled_reference_output = (
|
| 1205 |
+
F.linear(source_middle, source_w2)
|
| 1206 |
+
if all_rows.rows
|
| 1207 |
+
else torch.empty(
|
| 1208 |
+
(0, global_h13.shape[0]),
|
| 1209 |
+
dtype=torch.float32,
|
| 1210 |
+
device=device,
|
| 1211 |
+
)
|
| 1212 |
+
)
|
| 1213 |
+
source_w1, source_w3 = transformed[:2]
|
| 1214 |
+
inputs = coupled_execution_basis.transform_inputs(inputs)
|
| 1215 |
+
h13 = coupled_execution_basis.transform_h13(h13)
|
| 1216 |
+
transformed_global_h2 = coupled_execution_basis.transform_h2(global_h2)
|
| 1217 |
+
context_basis += f"+coupled_hadamard_intermediate_draw_{draw}"
|
| 1218 |
+
else:
|
| 1219 |
+
transformed_global_h2 = global_h2
|
| 1220 |
+
intermediate_conditioning = _folded_intermediate_conditioning(
|
| 1221 |
+
block_scores.to(device=device),
|
| 1222 |
+
contexts_device,
|
| 1223 |
+
power=folded_scale_power,
|
| 1224 |
+
)
|
| 1225 |
+
if intermediate_conditioning is not None:
|
| 1226 |
+
covariance["folded_scale_power"] = float(folded_scale_power)
|
| 1227 |
+
covariance["folded_scale_min"] = float(
|
| 1228 |
+
intermediate_conditioning.min()
|
| 1229 |
+
)
|
| 1230 |
+
covariance["folded_scale_max"] = float(
|
| 1231 |
+
intermediate_conditioning.max()
|
| 1232 |
+
)
|
| 1233 |
+
context_basis += "+folded_128_channel_conditioning"
|
| 1234 |
+
prepared.append(
|
| 1235 |
+
{
|
| 1236 |
+
"expert": expert,
|
| 1237 |
+
"all_rows": all_rows,
|
| 1238 |
+
"fit_mask": fit_mask,
|
| 1239 |
+
"confirmation_mask": confirmation_mask,
|
| 1240 |
+
"inputs": inputs,
|
| 1241 |
+
"gates": gates,
|
| 1242 |
+
"source_middle": source_middle,
|
| 1243 |
+
"block_contexts": block_contexts,
|
| 1244 |
+
"block_scores": block_scores,
|
| 1245 |
+
"context_basis": context_basis,
|
| 1246 |
+
"covariance": covariance,
|
| 1247 |
+
"evaluated_modes": evaluated_modes,
|
| 1248 |
+
"evaluated_formats": tuple(
|
| 1249 |
+
(r13, r2)
|
| 1250 |
+
for r13 in evaluated_modes
|
| 1251 |
+
for r2 in evaluated_modes
|
| 1252 |
+
),
|
| 1253 |
+
"h13": h13,
|
| 1254 |
+
"global_h2_basis": transformed_global_h2,
|
| 1255 |
+
"coupled_execution": coupled_execution_basis,
|
| 1256 |
+
"coupled_source_w2": coupled_source_w2,
|
| 1257 |
+
"coupled_reference_output": coupled_reference_output,
|
| 1258 |
+
}
|
| 1259 |
+
)
|
| 1260 |
+
upstream_sources.append({"w1": source_w1, "w3": source_w3})
|
| 1261 |
+
upstream_contexts.append(contexts_device)
|
| 1262 |
+
upstream_modes.append(mode_specs)
|
| 1263 |
+
upstream_hessians.append({"w1": h13, "w3": h13})
|
| 1264 |
+
upstream_conditioning.append(intermediate_conditioning)
|
| 1265 |
+
|
| 1266 |
+
guarded_reuse = layout == "qsrt_guarded_reuse"
|
| 1267 |
+
if guarded_reuse and any(mode > 2 for mode in modes):
|
| 1268 |
+
raise ValueError("qsrt_guarded_reuse supports only R0 through R2")
|
| 1269 |
+
upstream_layout: Layout = "importance_ordered" if guarded_reuse else layout
|
| 1270 |
+
upstream_candidates = quantize_qsrt_matrix_expert_batch(
|
| 1271 |
+
upstream_sources,
|
| 1272 |
+
upstream_contexts,
|
| 1273 |
+
modes_by_expert=upstream_modes,
|
| 1274 |
+
hessians_by_expert=upstream_hessians,
|
| 1275 |
+
layer=layer,
|
| 1276 |
+
device=device,
|
| 1277 |
+
shared_scale_scope=shared_scale_scope,
|
| 1278 |
+
quantizer_module=quantizer_module,
|
| 1279 |
+
codebook=codebook,
|
| 1280 |
+
layout=upstream_layout,
|
| 1281 |
+
ldlq_tf32=ldlq_tf32,
|
| 1282 |
+
tailbite_context=tailbite_context,
|
| 1283 |
+
transform_seeds_by_expert=seed_maps,
|
| 1284 |
+
intermediate_conditioning_by_expert=upstream_conditioning,
|
| 1285 |
+
)
|
| 1286 |
+
_debug_assert_candidate_tree_closure(
|
| 1287 |
+
upstream_candidates,
|
| 1288 |
+
expert_ids,
|
| 1289 |
+
layer=layer,
|
| 1290 |
+
stage="post-upstream-encode",
|
| 1291 |
+
)
|
| 1292 |
+
del upstream_sources, upstream_hessians
|
| 1293 |
+
|
| 1294 |
+
down_sources: list[torch.Tensor] = []
|
| 1295 |
+
conditional_h2s: list[dict[int, torch.Tensor]] = []
|
| 1296 |
+
for item, upstream in zip(prepared, upstream_candidates):
|
| 1297 |
+
expert = int(item["expert"])
|
| 1298 |
+
source_w2 = item["coupled_source_w2"]
|
| 1299 |
+
if source_w2 is None:
|
| 1300 |
+
source_w2 = _load_source_matrix(store, layer, expert, "w2", device)
|
| 1301 |
+
source_middle = item["source_middle"]
|
| 1302 |
+
inputs = item["inputs"]
|
| 1303 |
+
gates = item["gates"]
|
| 1304 |
+
fit_mask = item["fit_mask"]
|
| 1305 |
+
evaluated_modes = item["evaluated_modes"]
|
| 1306 |
+
assert isinstance(source_middle, torch.Tensor)
|
| 1307 |
+
assert isinstance(inputs, torch.Tensor)
|
| 1308 |
+
assert isinstance(gates, torch.Tensor)
|
| 1309 |
+
assert isinstance(fit_mask, torch.Tensor)
|
| 1310 |
+
execution_basis = item["coupled_execution"]
|
| 1311 |
+
if execution_basis is None:
|
| 1312 |
+
middle_by_r13 = _candidate_middle_by_r13(
|
| 1313 |
+
inputs,
|
| 1314 |
+
upstream,
|
| 1315 |
+
evaluated_modes,
|
| 1316 |
+
activation=activation,
|
| 1317 |
+
)
|
| 1318 |
+
else:
|
| 1319 |
+
middle_by_r13 = {
|
| 1320 |
+
int(r13): execution_basis.decode_middle(
|
| 1321 |
+
inputs,
|
| 1322 |
+
upstream["w1"][int(r13)].reconstruction,
|
| 1323 |
+
upstream["w3"][int(r13)].reconstruction,
|
| 1324 |
+
)
|
| 1325 |
+
for r13 in evaluated_modes
|
| 1326 |
+
}
|
| 1327 |
+
h2_by_r13, h2_evidence = _conditional_h2_by_r13(
|
| 1328 |
+
inputs,
|
| 1329 |
+
gates,
|
| 1330 |
+
fit_mask.to(device=device),
|
| 1331 |
+
middle_by_r13,
|
| 1332 |
+
global_h13=item["h13"],
|
| 1333 |
+
global_h2=item["global_h2_basis"],
|
| 1334 |
+
device=device,
|
| 1335 |
+
hessian_policy=hessian_policy,
|
| 1336 |
+
have_local_support=(
|
| 1337 |
+
int(item["covariance"]["fit_documents"]) >= min_fit_documents
|
| 1338 |
+
),
|
| 1339 |
+
)
|
| 1340 |
+
item["middle_by_r13"] = middle_by_r13
|
| 1341 |
+
item["covariance"].update(
|
| 1342 |
+
{
|
| 1343 |
+
"basis": (
|
| 1344 |
+
"identity"
|
| 1345 |
+
if hessian_policy == "identity"
|
| 1346 |
+
else (
|
| 1347 |
+
f"decoded_candidate_{_activation_basis(activation)}_"
|
| 1348 |
+
"adaptive_shrinkage"
|
| 1349 |
+
if int(item["covariance"]["fit_documents"])
|
| 1350 |
+
>= min_fit_documents
|
| 1351 |
+
else "identity_support_fallback"
|
| 1352 |
+
)
|
| 1353 |
+
),
|
| 1354 |
+
"h2_by_r13": h2_evidence,
|
| 1355 |
+
}
|
| 1356 |
+
)
|
| 1357 |
+
item["context_basis"] = (
|
| 1358 |
+
str(item["context_basis"])
|
| 1359 |
+
+ f"+decoded_candidate_{_activation_basis(activation)}_h2"
|
| 1360 |
+
)
|
| 1361 |
+
all_rows = item["all_rows"]
|
| 1362 |
+
if item["coupled_reference_output"] is not None:
|
| 1363 |
+
reference_output = item["coupled_reference_output"]
|
| 1364 |
+
elif all_rows.rows:
|
| 1365 |
+
reference_output = F.linear(source_middle, source_w2)
|
| 1366 |
+
else:
|
| 1367 |
+
reference_output = torch.empty(
|
| 1368 |
+
(0, global_h13.shape[0]), dtype=torch.float32, device=device
|
| 1369 |
+
)
|
| 1370 |
+
item["reference_output"] = reference_output
|
| 1371 |
+
down_sources.append(source_w2)
|
| 1372 |
+
conditional_h2s.append(h2_by_r13)
|
| 1373 |
+
|
| 1374 |
+
down_candidates = _quantize_conditional_down_grid(
|
| 1375 |
+
down_sources,
|
| 1376 |
+
upstream_contexts,
|
| 1377 |
+
upstream_modes,
|
| 1378 |
+
conditional_h2s,
|
| 1379 |
+
layer=layer,
|
| 1380 |
+
device=device,
|
| 1381 |
+
shared_scale_scope=shared_scale_scope,
|
| 1382 |
+
quantizer_module=quantizer_module,
|
| 1383 |
+
codebook=codebook,
|
| 1384 |
+
layout=layout,
|
| 1385 |
+
ldlq_tf32=ldlq_tf32,
|
| 1386 |
+
tailbite_context=tailbite_context,
|
| 1387 |
+
transform_seeds_by_expert=seed_maps,
|
| 1388 |
+
)
|
| 1389 |
+
_debug_assert_candidate_tree_closure(
|
| 1390 |
+
upstream_candidates,
|
| 1391 |
+
expert_ids,
|
| 1392 |
+
layer=layer,
|
| 1393 |
+
stage="post-down-encode upstream recheck",
|
| 1394 |
+
)
|
| 1395 |
+
_debug_assert_candidate_tree_closure(
|
| 1396 |
+
down_candidates,
|
| 1397 |
+
expert_ids,
|
| 1398 |
+
layer=layer,
|
| 1399 |
+
stage="post-down-encode",
|
| 1400 |
+
)
|
| 1401 |
+
del down_sources, conditional_h2s
|
| 1402 |
+
|
| 1403 |
+
results: list[QSRTCandidateEncoding] = []
|
| 1404 |
+
for item, upstream, down in zip(
|
| 1405 |
+
prepared, upstream_candidates, down_candidates
|
| 1406 |
+
):
|
| 1407 |
+
expert = int(item["expert"])
|
| 1408 |
+
all_rows = item["all_rows"]
|
| 1409 |
+
fit_mask = item["fit_mask"]
|
| 1410 |
+
confirmation_mask = item["confirmation_mask"]
|
| 1411 |
+
inputs = item["inputs"]
|
| 1412 |
+
gates = item["gates"]
|
| 1413 |
+
source_middle = item["source_middle"]
|
| 1414 |
+
reference_output = item["reference_output"]
|
| 1415 |
+
evaluated_modes = item["evaluated_modes"]
|
| 1416 |
+
evaluated_formats = item["evaluated_formats"]
|
| 1417 |
+
assert isinstance(inputs, torch.Tensor)
|
| 1418 |
+
assert isinstance(gates, torch.Tensor)
|
| 1419 |
+
assert isinstance(source_middle, torch.Tensor)
|
| 1420 |
+
assert isinstance(reference_output, torch.Tensor)
|
| 1421 |
+
|
| 1422 |
+
fit_plan = _prepare_deferred_functional_rows(
|
| 1423 |
+
reference_output,
|
| 1424 |
+
gates,
|
| 1425 |
+
all_rows.request_steps,
|
| 1426 |
+
fit_mask,
|
| 1427 |
+
partition.fit,
|
| 1428 |
+
device=device,
|
| 1429 |
+
)
|
| 1430 |
+
confirmation_plan = _prepare_deferred_functional_rows(
|
| 1431 |
+
reference_output,
|
| 1432 |
+
gates,
|
| 1433 |
+
all_rows.request_steps,
|
| 1434 |
+
confirmation_mask,
|
| 1435 |
+
partition.confirmation,
|
| 1436 |
+
device=device,
|
| 1437 |
+
)
|
| 1438 |
+
deferred_fit_sse: list[torch.Tensor] = []
|
| 1439 |
+
deferred_confirmation_sse: list[torch.Tensor] = []
|
| 1440 |
+
format_order: list[tuple[int, int]] = []
|
| 1441 |
+
mode_coding: dict[tuple[int, int], dict[str, object]] = {}
|
| 1442 |
+
middle_by_r13 = item["middle_by_r13"]
|
| 1443 |
+
for rate_pair in evaluated_formats:
|
| 1444 |
+
r13, r2 = rate_pair
|
| 1445 |
+
candidate_output = F.linear(
|
| 1446 |
+
middle_by_r13[r13], down[r13][r2].reconstruction
|
| 1447 |
+
)
|
| 1448 |
+
execution_basis = item["coupled_execution"]
|
| 1449 |
+
if execution_basis is not None:
|
| 1450 |
+
candidate_output = execution_basis.decode_output(candidate_output)
|
| 1451 |
+
deferred_fit_sse.append(
|
| 1452 |
+
_defer_functional_row_sse(fit_plan, candidate_output)
|
| 1453 |
+
)
|
| 1454 |
+
deferred_confirmation_sse.append(
|
| 1455 |
+
_defer_functional_row_sse(confirmation_plan, candidate_output)
|
| 1456 |
+
)
|
| 1457 |
+
format_order.append(rate_pair)
|
| 1458 |
+
mode_coding[rate_pair] = _candidate_mode_evidence(
|
| 1459 |
+
{
|
| 1460 |
+
"w1": upstream["w1"][r13],
|
| 1461 |
+
"w3": upstream["w3"][r13],
|
| 1462 |
+
"w2": down[r13][r2],
|
| 1463 |
+
}
|
| 1464 |
+
)
|
| 1465 |
+
del candidate_output
|
| 1466 |
+
|
| 1467 |
+
fit_sse = _finish_deferred_functional_sse(
|
| 1468 |
+
fit_plan, format_order, deferred_fit_sse
|
| 1469 |
+
)
|
| 1470 |
+
confirmation_sse = _finish_deferred_functional_sse(
|
| 1471 |
+
confirmation_plan, format_order, deferred_confirmation_sse
|
| 1472 |
+
)
|
| 1473 |
+
fit_reference = fit_plan.reference_energy
|
| 1474 |
+
fit_counts = fit_plan.counts
|
| 1475 |
+
confirmation_reference = confirmation_plan.reference_energy
|
| 1476 |
+
confirmation_counts = confirmation_plan.counts
|
| 1477 |
+
if fixed_profile:
|
| 1478 |
+
fixed_mode = H308 if fixed_high_rate else (K2 if fixed_k2 else R0)
|
| 1479 |
+
selection = RatePairSelection(
|
| 1480 |
+
proposed_r13=fixed_mode.mode_id,
|
| 1481 |
+
proposed_r2=fixed_mode.mode_id,
|
| 1482 |
+
selected_r13=fixed_mode.mode_id,
|
| 1483 |
+
selected_r2=fixed_mode.mode_id,
|
| 1484 |
+
accepted=True,
|
| 1485 |
+
reason=(
|
| 1486 |
+
"fixed_22k3_2k4_high_rate_profile"
|
| 1487 |
+
if fixed_high_rate
|
| 1488 |
+
else (
|
| 1489 |
+
"fixed_uniform_k2_profile"
|
| 1490 |
+
if fixed_k2
|
| 1491 |
+
else "fixed_uniform_k3_profile"
|
| 1492 |
+
)
|
| 1493 |
+
),
|
| 1494 |
+
fit_documents=int(torch.count_nonzero(fit_counts)),
|
| 1495 |
+
confirmation_documents=int(torch.count_nonzero(confirmation_counts)),
|
| 1496 |
+
confirmation_relative_improvement=None,
|
| 1497 |
+
confirmation_ci95=(None, None),
|
| 1498 |
+
bootstrap_replicates_valid=0,
|
| 1499 |
+
)
|
| 1500 |
+
else:
|
| 1501 |
+
selection = select_phase1_rate_pair(
|
| 1502 |
+
fit_sse,
|
| 1503 |
+
confirmation_sse,
|
| 1504 |
+
fit_counts=fit_counts,
|
| 1505 |
+
confirmation_counts=confirmation_counts,
|
| 1506 |
+
modes=evaluated_formats,
|
| 1507 |
+
min_fit_documents=min_fit_documents,
|
| 1508 |
+
min_confirmation_documents=min_confirmation_documents,
|
| 1509 |
+
minimum_improvement=minimum_improvement,
|
| 1510 |
+
bootstrap_replicates=bootstrap_replicates,
|
| 1511 |
+
seed=deterministic_expert_seed(layer, expert),
|
| 1512 |
+
)
|
| 1513 |
+
selected_r13, selected_r2 = selection.selected
|
| 1514 |
+
selected_candidates = {
|
| 1515 |
+
"w1": upstream["w1"][selected_r13],
|
| 1516 |
+
"w3": upstream["w3"][selected_r13],
|
| 1517 |
+
"w2": down[selected_r13][selected_r2],
|
| 1518 |
+
}
|
| 1519 |
+
if os.environ.get("QSRT_DEBUG_CANDIDATE_CLOSURE") == "1":
|
| 1520 |
+
for value in selected_candidates.values():
|
| 1521 |
+
_debug_assert_candidate_state_closure(
|
| 1522 |
+
value,
|
| 1523 |
+
layer=layer,
|
| 1524 |
+
expert=expert,
|
| 1525 |
+
stage="post-scoring selected-candidate",
|
| 1526 |
+
)
|
| 1527 |
+
selected_encodings = {}
|
| 1528 |
+
for matrix, value in selected_candidates.items():
|
| 1529 |
+
try:
|
| 1530 |
+
selected_encodings[matrix] = finalize_qsrt_matrix_candidate(
|
| 1531 |
+
value,
|
| 1532 |
+
layer=layer,
|
| 1533 |
+
logical_trellis_schema=logical_trellis_schema,
|
| 1534 |
+
codebook=codebook,
|
| 1535 |
+
tailbite_context=tailbite_context,
|
| 1536 |
+
)
|
| 1537 |
+
except Exception as exc:
|
| 1538 |
+
raise RuntimeError(
|
| 1539 |
+
f"layer {layer} expert {expert} {matrix} finalization failed"
|
| 1540 |
+
) from exc
|
| 1541 |
+
physical_permutation = selected_encodings["w1"].plan.physical_permutation
|
| 1542 |
+
for matrix in ("w3", "w2"):
|
| 1543 |
+
if not torch.equal(
|
| 1544 |
+
selected_encodings[matrix].plan.physical_permutation,
|
| 1545 |
+
physical_permutation,
|
| 1546 |
+
):
|
| 1547 |
+
raise ValueError(
|
| 1548 |
+
"selected matrices do not share one physical permutation"
|
| 1549 |
+
)
|
| 1550 |
+
expert_encoding = QSRTExpertEncoding(
|
| 1551 |
+
format=ExpertFormatSpec.compressed(selected_r13, selected_r2),
|
| 1552 |
+
matrices=selected_encodings,
|
| 1553 |
+
physical_permutation=physical_permutation,
|
| 1554 |
+
coding={
|
| 1555 |
+
"format": ExpertFormatSpec.compressed(
|
| 1556 |
+
selected_r13, selected_r2
|
| 1557 |
+
).name,
|
| 1558 |
+
"r13": selected_r13,
|
| 1559 |
+
"r2": selected_r2,
|
| 1560 |
+
"codebook": codebook,
|
| 1561 |
+
"tailbite_context": int(tailbite_context),
|
| 1562 |
+
"coupled_hadamard": (
|
| 1563 |
+
None
|
| 1564 |
+
if item["coupled_execution"] is None
|
| 1565 |
+
else {
|
| 1566 |
+
"residual_block_size": item[
|
| 1567 |
+
"coupled_execution"
|
| 1568 |
+
].spec.residual_block_size,
|
| 1569 |
+
"preactivation_block_size": item[
|
| 1570 |
+
"coupled_execution"
|
| 1571 |
+
].spec.preactivation_block_size,
|
| 1572 |
+
"postactivation_block_size": item[
|
| 1573 |
+
"coupled_execution"
|
| 1574 |
+
].spec.postactivation_block_size,
|
| 1575 |
+
"residual_draw": item[
|
| 1576 |
+
"coupled_execution"
|
| 1577 |
+
].spec.residual_draw,
|
| 1578 |
+
"intermediate_draw": item[
|
| 1579 |
+
"coupled_execution"
|
| 1580 |
+
].spec.intermediate_draw,
|
| 1581 |
+
"activation": item[
|
| 1582 |
+
"coupled_execution"
|
| 1583 |
+
].spec.activation,
|
| 1584 |
+
}
|
| 1585 |
+
),
|
| 1586 |
+
**_mode_evidence(selected_encodings),
|
| 1587 |
+
},
|
| 1588 |
+
)
|
| 1589 |
+
results.append(
|
| 1590 |
+
QSRTCandidateEncoding(
|
| 1591 |
+
expert=expert_encoding,
|
| 1592 |
+
selection=selection,
|
| 1593 |
+
block_contexts=item["block_contexts"]
|
| 1594 |
+
.detach()
|
| 1595 |
+
.cpu()
|
| 1596 |
+
.to(torch.uint8)
|
| 1597 |
+
.contiguous(),
|
| 1598 |
+
block_scores=item["block_scores"].detach().cpu().float().contiguous(),
|
| 1599 |
+
context_basis=str(item["context_basis"]),
|
| 1600 |
+
covariance=item["covariance"],
|
| 1601 |
+
evaluated_modes=evaluated_modes,
|
| 1602 |
+
evaluated_formats=evaluated_formats,
|
| 1603 |
+
fit_sse=fit_sse,
|
| 1604 |
+
confirmation_sse=confirmation_sse,
|
| 1605 |
+
fit_reference_energy=fit_reference,
|
| 1606 |
+
confirmation_reference_energy=confirmation_reference,
|
| 1607 |
+
fit_counts=fit_counts,
|
| 1608 |
+
confirmation_counts=confirmation_counts,
|
| 1609 |
+
mode_coding=mode_coding,
|
| 1610 |
+
)
|
| 1611 |
+
)
|
| 1612 |
+
return results
|
| 1613 |
+
|
| 1614 |
+
|
| 1615 |
+
def candidate_tensor_name(
|
| 1616 |
+
layer: int, expert: int, matrix: str, part: str
|
| 1617 |
+
) -> str:
|
| 1618 |
+
if matrix not in MATRICES or part not in ("trellis", "suh", "svh"):
|
| 1619 |
+
raise ValueError("invalid QSRT candidate tensor component")
|
| 1620 |
+
return (
|
| 1621 |
+
f"language_model.model.layers.{layer}.block_sparse_moe.experts."
|
| 1622 |
+
f"{expert}.{matrix}.qsrt_{part}"
|
| 1623 |
+
)
|
| 1624 |
+
|
| 1625 |
+
|
| 1626 |
+
def selected_candidate_tensors(
|
| 1627 |
+
layer: int, expert: int, candidate: QSRTCandidateEncoding
|
| 1628 |
+
) -> dict[str, torch.Tensor]:
|
| 1629 |
+
"""Copy one selected physical payload to CPU for layer-shard assembly."""
|
| 1630 |
+
|
| 1631 |
+
tensors: dict[str, torch.Tensor] = {}
|
| 1632 |
+
for matrix, encoding in candidate.expert.matrices.items():
|
| 1633 |
+
for part in ("trellis", "suh", "svh"):
|
| 1634 |
+
tensors[candidate_tensor_name(layer, expert, matrix, part)] = (
|
| 1635 |
+
encoding.tensors[part].detach().cpu().contiguous()
|
| 1636 |
+
)
|
| 1637 |
+
return tensors
|
reproduction/closure/patches/qsrt/changed-files/qsrt/qsrt_coupled.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Exact coupled activation-boundary coordinates for QSRT expert matrices.
|
| 2 |
+
|
| 3 |
+
The transform interleaves gate and up rows before a normalized block-Hadamard
|
| 4 |
+
rotation. The decoder joins the two stored projections, cancels that rotation,
|
| 5 |
+
evaluates the coordinatewise SiTU activation, and rotates the resulting hidden
|
| 6 |
+
coordinates into the matching down-projection basis. Residual-side transforms
|
| 7 |
+
are layer-shared; the intermediate draw is expert-static.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import math
|
| 13 |
+
from dataclasses import dataclass
|
| 14 |
+
from typing import Literal
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
import torch.nn.functional as F
|
| 18 |
+
|
| 19 |
+
from qsrt.tp_simulator import situ
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
Tensor = torch.Tensor
|
| 23 |
+
WeightTriplet = tuple[Tensor, Tensor, Tensor]
|
| 24 |
+
ExpertActivation = Literal["situ", "silu"]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def expert_activation(
|
| 28 |
+
gate: Tensor,
|
| 29 |
+
up: Tensor,
|
| 30 |
+
*,
|
| 31 |
+
activation: ExpertActivation,
|
| 32 |
+
) -> Tensor:
|
| 33 |
+
"""Evaluate the model's exact coordinatewise gated activation.
|
| 34 |
+
|
| 35 |
+
Coupled Hadamard construction is valid for either activation because the
|
| 36 |
+
preactivation transform is inverted before this operation and the
|
| 37 |
+
postactivation transform is applied afterward. Keeping the choice in the
|
| 38 |
+
immutable transform specification prevents a Kimi SiTU candidate from
|
| 39 |
+
being replayed accidentally under GLM's standard SwiGLU contract.
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
if gate.shape != up.shape:
|
| 43 |
+
raise ValueError("gate and up projections must have matching shapes")
|
| 44 |
+
if activation == "situ":
|
| 45 |
+
return situ(gate, up)
|
| 46 |
+
if activation == "silu":
|
| 47 |
+
return F.silu(gate) * up
|
| 48 |
+
raise ValueError(f"unsupported expert activation: {activation}")
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@dataclass(frozen=True)
|
| 52 |
+
class CoupledHadamardSpec:
|
| 53 |
+
"""Immutable transform parameters carried by a coupled QSRT profile."""
|
| 54 |
+
|
| 55 |
+
residual_block_size: int = 512
|
| 56 |
+
preactivation_block_size: int = 128
|
| 57 |
+
postactivation_block_size: int = 128
|
| 58 |
+
residual_draw: int = 0
|
| 59 |
+
intermediate_draw: int = 0
|
| 60 |
+
activation: ExpertActivation = "situ"
|
| 61 |
+
|
| 62 |
+
def __post_init__(self) -> None:
|
| 63 |
+
for name, value in (
|
| 64 |
+
("residual_block_size", self.residual_block_size),
|
| 65 |
+
("preactivation_block_size", self.preactivation_block_size),
|
| 66 |
+
("postactivation_block_size", self.postactivation_block_size),
|
| 67 |
+
):
|
| 68 |
+
if value <= 0 or value & (value - 1):
|
| 69 |
+
raise ValueError(f"{name} must be a positive power of two")
|
| 70 |
+
if self.residual_draw != 0:
|
| 71 |
+
raise ValueError("the coupled profile fixes residual draw zero")
|
| 72 |
+
if not 0 <= self.intermediate_draw < 8:
|
| 73 |
+
raise ValueError("intermediate_draw must lie in 0..7")
|
| 74 |
+
if self.activation not in ("situ", "silu"):
|
| 75 |
+
raise ValueError("activation must be 'situ' or 'silu'")
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def block_hadamard(values: Tensor, *, block_size: int, dim: int = -1) -> Tensor:
|
| 79 |
+
"""Apply a normalized self-inverse block Walsh-Hadamard transform."""
|
| 80 |
+
|
| 81 |
+
if values.ndim == 0 or not torch.is_floating_point(values):
|
| 82 |
+
raise TypeError("block Hadamard input must be floating point")
|
| 83 |
+
if block_size <= 0 or block_size & (block_size - 1):
|
| 84 |
+
raise ValueError("block_size must be a positive power of two")
|
| 85 |
+
axis = dim % values.ndim
|
| 86 |
+
if values.shape[axis] % block_size:
|
| 87 |
+
raise ValueError("Hadamard axis must be divisible by block_size")
|
| 88 |
+
# Naturally routed experts can have an empty fit or confirmation fold.
|
| 89 |
+
# The transform is linear, so its action on an empty batch is the empty
|
| 90 |
+
# float32 tensor with the same shape. Returning before the butterfly
|
| 91 |
+
# reshape also avoids an ambiguous ``-1`` inference when numel is zero.
|
| 92 |
+
if values.numel() == 0:
|
| 93 |
+
return values.float().contiguous().clone()
|
| 94 |
+
output = values.float().movedim(axis, -1).contiguous().clone()
|
| 95 |
+
shape = output.shape
|
| 96 |
+
output = output.reshape(*shape[:-1], shape[-1] // block_size, block_size)
|
| 97 |
+
width = 1
|
| 98 |
+
while width < block_size:
|
| 99 |
+
paired = output.reshape(*output.shape[:-1], -1, 2, width)
|
| 100 |
+
left = paired[..., 0, :].clone()
|
| 101 |
+
right = paired[..., 1, :].clone()
|
| 102 |
+
paired[..., 0, :] = left + right
|
| 103 |
+
paired[..., 1, :] = left - right
|
| 104 |
+
output = paired.reshape(*output.shape)
|
| 105 |
+
width *= 2
|
| 106 |
+
return (
|
| 107 |
+
output.div_(math.sqrt(block_size))
|
| 108 |
+
.reshape(shape)
|
| 109 |
+
.movedim(-1, axis)
|
| 110 |
+
.contiguous()
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def rotation_signs(
|
| 115 |
+
length: int, *, draw: int, axis: int, device: torch.device
|
| 116 |
+
) -> Tensor:
|
| 117 |
+
"""Return one deterministic Rademacher vector from the frozen draw family."""
|
| 118 |
+
|
| 119 |
+
if length <= 0 or draw < 0:
|
| 120 |
+
raise ValueError("length must be positive and draw nonnegative")
|
| 121 |
+
if draw == 0:
|
| 122 |
+
return torch.ones(length, dtype=torch.float32, device=device)
|
| 123 |
+
generator = torch.Generator(device="cpu")
|
| 124 |
+
generator.manual_seed(
|
| 125 |
+
(0x6A09E667F3BCC909 * draw + 0xBB67AE8584CAA73B * axis)
|
| 126 |
+
& ((1 << 63) - 1)
|
| 127 |
+
)
|
| 128 |
+
return (
|
| 129 |
+
torch.randint(0, 2, (length,), generator=generator)
|
| 130 |
+
.mul_(2)
|
| 131 |
+
.sub_(1)
|
| 132 |
+
.float()
|
| 133 |
+
.to(device=device)
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def signed_block_hadamard(
|
| 138 |
+
values: Tensor,
|
| 139 |
+
*,
|
| 140 |
+
block_size: int,
|
| 141 |
+
signs: Tensor,
|
| 142 |
+
dim: int = -1,
|
| 143 |
+
inverse: bool = False,
|
| 144 |
+
) -> Tensor:
|
| 145 |
+
"""Apply ``D H`` to row vectors, or its exact inverse ``H D``."""
|
| 146 |
+
|
| 147 |
+
axis = dim % values.ndim
|
| 148 |
+
if signs.ndim != 1 or signs.numel() != values.shape[axis]:
|
| 149 |
+
raise ValueError("rotation signs do not align with the transformed axis")
|
| 150 |
+
shape = [1] * values.ndim
|
| 151 |
+
shape[axis] = signs.numel()
|
| 152 |
+
expanded = signs.to(device=values.device, dtype=torch.float32).reshape(shape)
|
| 153 |
+
if inverse:
|
| 154 |
+
return block_hadamard(values, block_size=block_size, dim=axis) * expanded
|
| 155 |
+
return block_hadamard(
|
| 156 |
+
values.float() * expanded, block_size=block_size, dim=axis
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def _validate_triplet(weights: WeightTriplet) -> tuple[int, int]:
|
| 161 |
+
w1, w3, w2 = weights
|
| 162 |
+
if w1.ndim != 2 or w3.ndim != 2 or w2.ndim != 2 or w1.shape != w3.shape:
|
| 163 |
+
raise ValueError("coupled weights require matching two-dimensional w1/w3")
|
| 164 |
+
intermediate, hidden = w1.shape
|
| 165 |
+
if tuple(w2.shape) != (hidden, intermediate):
|
| 166 |
+
raise ValueError("w2 must have shape [hidden, intermediate]")
|
| 167 |
+
if not all(torch.is_floating_point(value) for value in weights):
|
| 168 |
+
raise TypeError("coupled weights must be floating point")
|
| 169 |
+
if len({value.device for value in weights}) != 1:
|
| 170 |
+
raise ValueError("coupled weights must share one device")
|
| 171 |
+
return intermediate, hidden
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def encode_coupled_weights(
|
| 175 |
+
weights: WeightTriplet, spec: CoupledHadamardSpec
|
| 176 |
+
) -> WeightTriplet:
|
| 177 |
+
"""Reparameterize one full-precision expert into coupled coordinates."""
|
| 178 |
+
|
| 179 |
+
w1, w3, w2 = weights
|
| 180 |
+
intermediate, hidden = _validate_triplet(weights)
|
| 181 |
+
if hidden % spec.residual_block_size:
|
| 182 |
+
raise ValueError("hidden size does not close residual Hadamard blocks")
|
| 183 |
+
if 2 * intermediate % spec.preactivation_block_size:
|
| 184 |
+
raise ValueError("interleaved size does not close preactivation blocks")
|
| 185 |
+
if intermediate % spec.postactivation_block_size:
|
| 186 |
+
raise ValueError("intermediate size does not close postactivation blocks")
|
| 187 |
+
device = w1.device
|
| 188 |
+
input_signs = rotation_signs(hidden, draw=spec.residual_draw, axis=0, device=device)
|
| 189 |
+
pre_signs = rotation_signs(
|
| 190 |
+
2 * intermediate, draw=spec.intermediate_draw, axis=1, device=device
|
| 191 |
+
)
|
| 192 |
+
post_signs = rotation_signs(
|
| 193 |
+
intermediate, draw=spec.intermediate_draw, axis=2, device=device
|
| 194 |
+
)
|
| 195 |
+
output_signs = rotation_signs(
|
| 196 |
+
hidden, draw=spec.residual_draw, axis=3, device=device
|
| 197 |
+
)
|
| 198 |
+
interleaved = torch.stack((w1, w3), dim=1).reshape(2 * intermediate, hidden)
|
| 199 |
+
upstream = signed_block_hadamard(
|
| 200 |
+
signed_block_hadamard(
|
| 201 |
+
interleaved,
|
| 202 |
+
block_size=spec.residual_block_size,
|
| 203 |
+
signs=input_signs,
|
| 204 |
+
dim=1,
|
| 205 |
+
),
|
| 206 |
+
block_size=spec.preactivation_block_size,
|
| 207 |
+
signs=pre_signs,
|
| 208 |
+
dim=0,
|
| 209 |
+
)
|
| 210 |
+
down = signed_block_hadamard(
|
| 211 |
+
signed_block_hadamard(
|
| 212 |
+
w2,
|
| 213 |
+
block_size=spec.postactivation_block_size,
|
| 214 |
+
signs=post_signs,
|
| 215 |
+
dim=1,
|
| 216 |
+
),
|
| 217 |
+
block_size=spec.residual_block_size,
|
| 218 |
+
signs=output_signs,
|
| 219 |
+
dim=0,
|
| 220 |
+
)
|
| 221 |
+
return (
|
| 222 |
+
upstream[:intermediate].contiguous(),
|
| 223 |
+
upstream[intermediate:].contiguous(),
|
| 224 |
+
down.contiguous(),
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
@dataclass(frozen=True)
|
| 229 |
+
class CoupledHadamardExecution:
|
| 230 |
+
"""Calibration and scoring operations for one transformed expert basis."""
|
| 231 |
+
|
| 232 |
+
hidden: int
|
| 233 |
+
intermediate: int
|
| 234 |
+
spec: CoupledHadamardSpec
|
| 235 |
+
|
| 236 |
+
def _signs(self, axis: int, *, device: torch.device) -> Tensor:
|
| 237 |
+
lengths = (self.hidden, 2 * self.intermediate, self.intermediate, self.hidden)
|
| 238 |
+
draw = self.spec.residual_draw if axis in (0, 3) else self.spec.intermediate_draw
|
| 239 |
+
return rotation_signs(lengths[axis], draw=draw, axis=axis, device=device)
|
| 240 |
+
|
| 241 |
+
def transform_inputs(self, rows: Tensor) -> Tensor:
|
| 242 |
+
if rows.ndim != 2 or rows.shape[1] != self.hidden:
|
| 243 |
+
raise ValueError("input rows do not match the hidden dimension")
|
| 244 |
+
return signed_block_hadamard(
|
| 245 |
+
rows,
|
| 246 |
+
block_size=self.spec.residual_block_size,
|
| 247 |
+
signs=self._signs(0, device=rows.device),
|
| 248 |
+
dim=1,
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
def transform_h13(self, hessian: Tensor) -> Tensor:
|
| 252 |
+
if tuple(hessian.shape) != (self.hidden, self.hidden):
|
| 253 |
+
raise ValueError("H13 does not match the hidden dimension")
|
| 254 |
+
signs = self._signs(0, device=hessian.device)
|
| 255 |
+
return signed_block_hadamard(
|
| 256 |
+
signed_block_hadamard(
|
| 257 |
+
hessian,
|
| 258 |
+
block_size=self.spec.residual_block_size,
|
| 259 |
+
signs=signs,
|
| 260 |
+
dim=0,
|
| 261 |
+
),
|
| 262 |
+
block_size=self.spec.residual_block_size,
|
| 263 |
+
signs=signs,
|
| 264 |
+
dim=1,
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
def transform_h2(self, hessian: Tensor) -> Tensor:
|
| 268 |
+
if tuple(hessian.shape) != (self.intermediate, self.intermediate):
|
| 269 |
+
raise ValueError("H2 does not match the intermediate dimension")
|
| 270 |
+
signs = self._signs(2, device=hessian.device)
|
| 271 |
+
return signed_block_hadamard(
|
| 272 |
+
signed_block_hadamard(
|
| 273 |
+
hessian,
|
| 274 |
+
block_size=self.spec.postactivation_block_size,
|
| 275 |
+
signs=signs,
|
| 276 |
+
dim=0,
|
| 277 |
+
),
|
| 278 |
+
block_size=self.spec.postactivation_block_size,
|
| 279 |
+
signs=signs,
|
| 280 |
+
dim=1,
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
def decode_middle(self, rows: Tensor, w1: Tensor, w3: Tensor) -> Tensor:
|
| 284 |
+
return self.decode_middle_outputs(F.linear(rows, w1), F.linear(rows, w3))
|
| 285 |
+
|
| 286 |
+
def decode_middle_outputs(
|
| 287 |
+
self, transformed_w1: Tensor, transformed_w3: Tensor
|
| 288 |
+
) -> Tensor:
|
| 289 |
+
"""Close the coupled boundary from already-computed FC1 outputs.
|
| 290 |
+
|
| 291 |
+
This is the runtime/calibration seam used when the two upstream GEMMs
|
| 292 |
+
have their own operand precision, reconstruction law, or fused kernel.
|
| 293 |
+
It applies exactly the same inverse preactivation rotation, model
|
| 294 |
+
activation, and postactivation rotation as :meth:`decode_middle`.
|
| 295 |
+
"""
|
| 296 |
+
|
| 297 |
+
if (
|
| 298 |
+
transformed_w1.ndim != 2
|
| 299 |
+
or transformed_w3.ndim != 2
|
| 300 |
+
or transformed_w1.shape != transformed_w3.shape
|
| 301 |
+
or transformed_w1.shape[1] != self.intermediate
|
| 302 |
+
or transformed_w1.device != transformed_w3.device
|
| 303 |
+
):
|
| 304 |
+
raise ValueError(
|
| 305 |
+
"coupled upstream outputs must be aligned [rows, intermediate]"
|
| 306 |
+
)
|
| 307 |
+
transformed = torch.cat((transformed_w1, transformed_w3), dim=1)
|
| 308 |
+
recovered = signed_block_hadamard(
|
| 309 |
+
transformed,
|
| 310 |
+
block_size=self.spec.preactivation_block_size,
|
| 311 |
+
signs=self._signs(1, device=transformed.device),
|
| 312 |
+
dim=1,
|
| 313 |
+
inverse=True,
|
| 314 |
+
)
|
| 315 |
+
middle = expert_activation(
|
| 316 |
+
recovered[:, 0::2],
|
| 317 |
+
recovered[:, 1::2],
|
| 318 |
+
activation=self.spec.activation,
|
| 319 |
+
)
|
| 320 |
+
return signed_block_hadamard(
|
| 321 |
+
middle,
|
| 322 |
+
block_size=self.spec.postactivation_block_size,
|
| 323 |
+
signs=self._signs(2, device=transformed.device),
|
| 324 |
+
dim=1,
|
| 325 |
+
)
|
| 326 |
+
|
| 327 |
+
def decode_output(self, transformed: Tensor) -> Tensor:
|
| 328 |
+
if transformed.ndim != 2 or transformed.shape[1] != self.hidden:
|
| 329 |
+
raise ValueError("output rows do not match the hidden dimension")
|
| 330 |
+
return signed_block_hadamard(
|
| 331 |
+
transformed,
|
| 332 |
+
block_size=self.spec.residual_block_size,
|
| 333 |
+
signs=self._signs(3, device=transformed.device),
|
| 334 |
+
dim=1,
|
| 335 |
+
inverse=True,
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
def transform_outputs(self, rows: Tensor) -> Tensor:
|
| 339 |
+
"""Map caller-coordinate outputs into the coupled residual basis.
|
| 340 |
+
|
| 341 |
+
Calibration uses this forward counterpart of :meth:`decode_output`
|
| 342 |
+
when constructing a candidate-conditioned down cross term. The
|
| 343 |
+
candidate operand and source-model teacher must share the transformed
|
| 344 |
+
output coordinates before forming ``B = Q.T @ Y``.
|
| 345 |
+
"""
|
| 346 |
+
|
| 347 |
+
if rows.ndim != 2 or rows.shape[1] != self.hidden:
|
| 348 |
+
raise ValueError("output rows do not match the hidden dimension")
|
| 349 |
+
return signed_block_hadamard(
|
| 350 |
+
rows,
|
| 351 |
+
block_size=self.spec.residual_block_size,
|
| 352 |
+
signs=self._signs(3, device=rows.device),
|
| 353 |
+
dim=1,
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
def execute(self, rows: Tensor, weights: WeightTriplet) -> Tensor:
|
| 357 |
+
_validate_triplet(weights)
|
| 358 |
+
transformed_rows = self.transform_inputs(rows)
|
| 359 |
+
middle = self.decode_middle(transformed_rows, weights[0], weights[1])
|
| 360 |
+
return self.decode_output(F.linear(middle, weights[2]))
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
def coupled_execution(
|
| 364 |
+
weights: WeightTriplet, spec: CoupledHadamardSpec
|
| 365 |
+
) -> CoupledHadamardExecution:
|
| 366 |
+
intermediate, hidden = _validate_triplet(weights)
|
| 367 |
+
return CoupledHadamardExecution(hidden, intermediate, spec)
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
__all__ = [
|
| 371 |
+
"CoupledHadamardExecution",
|
| 372 |
+
"CoupledHadamardSpec",
|
| 373 |
+
"ExpertActivation",
|
| 374 |
+
"WeightTriplet",
|
| 375 |
+
"block_hadamard",
|
| 376 |
+
"coupled_execution",
|
| 377 |
+
"encode_coupled_weights",
|
| 378 |
+
"expert_activation",
|
| 379 |
+
"rotation_signs",
|
| 380 |
+
"signed_block_hadamard",
|
| 381 |
+
]
|
reproduction/closure/patches/qsrt/changed-files/tests/test_qsrt_coupled.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from qsrt.coupled_expert_study import (
|
| 6 |
+
CoupledTriplet,
|
| 7 |
+
encode_coupled_block_hadamard,
|
| 8 |
+
execute_coupled_block_hadamard,
|
| 9 |
+
)
|
| 10 |
+
from qsrt.qsrt_coupled import (
|
| 11 |
+
CoupledHadamardSpec,
|
| 12 |
+
block_hadamard,
|
| 13 |
+
coupled_execution,
|
| 14 |
+
encode_coupled_weights,
|
| 15 |
+
expert_activation,
|
| 16 |
+
rotation_signs,
|
| 17 |
+
signed_block_hadamard,
|
| 18 |
+
)
|
| 19 |
+
from qsrt.tp_simulator import situ
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def test_block_hadamard_preserves_empty_routed_batches() -> None:
|
| 23 |
+
values = torch.empty((0, 7, 16), dtype=torch.float16)
|
| 24 |
+
transformed = block_hadamard(values, block_size=8, dim=2)
|
| 25 |
+
|
| 26 |
+
assert transformed.shape == values.shape
|
| 27 |
+
assert transformed.dtype == torch.float32
|
| 28 |
+
assert transformed.numel() == 0
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test_production_coupled_transform_closes_and_matches_research_oracle() -> None:
|
| 32 |
+
generator = torch.Generator().manual_seed(871)
|
| 33 |
+
rows, hidden, intermediate = 9, 16, 12
|
| 34 |
+
weights = (
|
| 35 |
+
torch.randn(intermediate, hidden, generator=generator),
|
| 36 |
+
torch.randn(intermediate, hidden, generator=generator),
|
| 37 |
+
torch.randn(hidden, intermediate, generator=generator),
|
| 38 |
+
)
|
| 39 |
+
inputs = torch.randn(rows, hidden, generator=generator)
|
| 40 |
+
spec = CoupledHadamardSpec(
|
| 41 |
+
residual_block_size=8,
|
| 42 |
+
preactivation_block_size=8,
|
| 43 |
+
postactivation_block_size=4,
|
| 44 |
+
intermediate_draw=3,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
encoded = encode_coupled_weights(weights, spec)
|
| 48 |
+
execution = coupled_execution(encoded, spec)
|
| 49 |
+
output = execution.execute(inputs, encoded)
|
| 50 |
+
reference = torch.nn.functional.linear(
|
| 51 |
+
situ(
|
| 52 |
+
torch.nn.functional.linear(inputs, weights[0]),
|
| 53 |
+
torch.nn.functional.linear(inputs, weights[1]),
|
| 54 |
+
),
|
| 55 |
+
weights[2],
|
| 56 |
+
)
|
| 57 |
+
research_encoded = encode_coupled_block_hadamard(
|
| 58 |
+
CoupledTriplet(*weights),
|
| 59 |
+
block_size=8,
|
| 60 |
+
preactivation_block_size=8,
|
| 61 |
+
postactivation_block_size=4,
|
| 62 |
+
intermediate_rotation_draw=3,
|
| 63 |
+
)
|
| 64 |
+
research_output = execute_coupled_block_hadamard(
|
| 65 |
+
inputs,
|
| 66 |
+
research_encoded,
|
| 67 |
+
block_size=8,
|
| 68 |
+
preactivation_block_size=8,
|
| 69 |
+
postactivation_block_size=4,
|
| 70 |
+
intermediate_rotation_draw=3,
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
for actual, expected in zip(encoded, research_encoded.tensors(), strict=True):
|
| 74 |
+
assert torch.equal(actual, expected)
|
| 75 |
+
assert torch.allclose(output, reference, rtol=2e-5, atol=2e-5)
|
| 76 |
+
assert torch.allclose(output, research_output, rtol=2e-5, atol=2e-5)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def test_glm_silu_coupled_transform_closes_exact_expert_function() -> None:
|
| 80 |
+
generator = torch.Generator().manual_seed(5202)
|
| 81 |
+
rows, hidden, intermediate = 11, 24, 16
|
| 82 |
+
weights = (
|
| 83 |
+
torch.randn(intermediate, hidden, generator=generator),
|
| 84 |
+
torch.randn(intermediate, hidden, generator=generator),
|
| 85 |
+
torch.randn(hidden, intermediate, generator=generator),
|
| 86 |
+
)
|
| 87 |
+
inputs = torch.randn(rows, hidden, generator=generator)
|
| 88 |
+
spec = CoupledHadamardSpec(
|
| 89 |
+
residual_block_size=8,
|
| 90 |
+
preactivation_block_size=8,
|
| 91 |
+
postactivation_block_size=8,
|
| 92 |
+
intermediate_draw=6,
|
| 93 |
+
activation="silu",
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
encoded = encode_coupled_weights(weights, spec)
|
| 97 |
+
execution = coupled_execution(encoded, spec)
|
| 98 |
+
output = execution.execute(inputs, encoded)
|
| 99 |
+
gate = torch.nn.functional.linear(inputs, weights[0])
|
| 100 |
+
up = torch.nn.functional.linear(inputs, weights[1])
|
| 101 |
+
reference = torch.nn.functional.linear(
|
| 102 |
+
torch.nn.functional.silu(gate) * up,
|
| 103 |
+
weights[2],
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
assert torch.allclose(output, reference, rtol=2e-5, atol=2e-5)
|
| 107 |
+
|
| 108 |
+
transformed_input = execution.transform_inputs(inputs)
|
| 109 |
+
gate_output = torch.nn.functional.linear(transformed_input, encoded[0])
|
| 110 |
+
up_output = torch.nn.functional.linear(transformed_input, encoded[1])
|
| 111 |
+
middle_from_outputs = execution.decode_middle_outputs(
|
| 112 |
+
gate_output, up_output
|
| 113 |
+
)
|
| 114 |
+
middle_from_weights = execution.decode_middle(
|
| 115 |
+
transformed_input, encoded[0], encoded[1]
|
| 116 |
+
)
|
| 117 |
+
torch.testing.assert_close(
|
| 118 |
+
middle_from_outputs, middle_from_weights, rtol=0, atol=0
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
transformed_output = execution.transform_outputs(reference)
|
| 122 |
+
torch.testing.assert_close(
|
| 123 |
+
execution.decode_output(transformed_output), reference, rtol=2e-5, atol=2e-5
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def test_expert_activation_rejects_unknown_contract() -> None:
|
| 128 |
+
gate = torch.ones((2, 3))
|
| 129 |
+
up = torch.ones((2, 3))
|
| 130 |
+
|
| 131 |
+
try:
|
| 132 |
+
expert_activation(gate, up, activation="gelu") # type: ignore[arg-type]
|
| 133 |
+
except ValueError as exc:
|
| 134 |
+
assert "unsupported expert activation" in str(exc)
|
| 135 |
+
else:
|
| 136 |
+
raise AssertionError("unknown expert activation was accepted")
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def test_coupled_hessian_transforms_preserve_quadratic_forms() -> None:
|
| 140 |
+
generator = torch.Generator().manual_seed(91)
|
| 141 |
+
hidden, intermediate = 16, 12
|
| 142 |
+
weights = (
|
| 143 |
+
torch.randn(intermediate, hidden, generator=generator),
|
| 144 |
+
torch.randn(intermediate, hidden, generator=generator),
|
| 145 |
+
torch.randn(hidden, intermediate, generator=generator),
|
| 146 |
+
)
|
| 147 |
+
spec = CoupledHadamardSpec(
|
| 148 |
+
residual_block_size=8,
|
| 149 |
+
preactivation_block_size=8,
|
| 150 |
+
postactivation_block_size=4,
|
| 151 |
+
intermediate_draw=5,
|
| 152 |
+
)
|
| 153 |
+
execution = coupled_execution(encode_coupled_weights(weights, spec), spec)
|
| 154 |
+
rows = torch.randn(11, hidden, generator=generator)
|
| 155 |
+
hidden_rows = torch.randn(11, intermediate, generator=generator)
|
| 156 |
+
h13 = rows.T @ rows
|
| 157 |
+
h2 = hidden_rows.T @ hidden_rows
|
| 158 |
+
|
| 159 |
+
transformed_rows = execution.transform_inputs(rows)
|
| 160 |
+
post_signs = rotation_signs(
|
| 161 |
+
intermediate, draw=spec.intermediate_draw, axis=2, device=rows.device
|
| 162 |
+
)
|
| 163 |
+
transformed_hidden_rows = signed_block_hadamard(
|
| 164 |
+
hidden_rows,
|
| 165 |
+
block_size=spec.postactivation_block_size,
|
| 166 |
+
signs=post_signs,
|
| 167 |
+
dim=1,
|
| 168 |
+
)
|
| 169 |
+
transformed_hidden = execution.transform_h2(h2)
|
| 170 |
+
assert torch.allclose(
|
| 171 |
+
execution.transform_h13(h13),
|
| 172 |
+
transformed_rows.T @ transformed_rows,
|
| 173 |
+
rtol=2e-5,
|
| 174 |
+
atol=2e-5,
|
| 175 |
+
)
|
| 176 |
+
assert torch.allclose(
|
| 177 |
+
transformed_hidden,
|
| 178 |
+
transformed_hidden_rows.T @ transformed_hidden_rows,
|
| 179 |
+
rtol=2e-5,
|
| 180 |
+
atol=2e-5,
|
| 181 |
+
)
|
reproduction/closure/patches/qsrt/changes.patch
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/qsrt/pack/qsrt_candidates.py b/qsrt/pack/qsrt_candidates.py
|
| 2 |
+
index d7464fc..864b5c2 100644
|
| 3 |
+
--- a/qsrt/pack/qsrt_candidates.py
|
| 4 |
+
+++ b/qsrt/pack/qsrt_candidates.py
|
| 5 |
+
@@ -43,8 +43,10 @@ from qsrt.qsrt import (
|
| 6 |
+
from qsrt.qsrt_coupled import (
|
| 7 |
+
CoupledHadamardExecution,
|
| 8 |
+
CoupledHadamardSpec,
|
| 9 |
+
+ ExpertActivation,
|
| 10 |
+
coupled_execution,
|
| 11 |
+
encode_coupled_weights,
|
| 12 |
+
+ expert_activation,
|
| 13 |
+
)
|
| 14 |
+
from qsrt.pack.qsrt_encoder import (
|
| 15 |
+
Layout,
|
| 16 |
+
@@ -172,9 +174,22 @@ class QSRTCandidateEncoding:
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def _source_middle(
|
| 20 |
+
- gate_projection: torch.Tensor, up_projection: torch.Tensor
|
| 21 |
+
+ gate_projection: torch.Tensor,
|
| 22 |
+
+ up_projection: torch.Tensor,
|
| 23 |
+
+ *,
|
| 24 |
+
+ activation: ExpertActivation = "situ",
|
| 25 |
+
) -> torch.Tensor:
|
| 26 |
+
- return situ(gate_projection, up_projection)
|
| 27 |
+
+ return expert_activation(
|
| 28 |
+
+ gate_projection,
|
| 29 |
+
+ up_projection,
|
| 30 |
+
+ activation=activation,
|
| 31 |
+
+ )
|
| 32 |
+
+
|
| 33 |
+
+
|
| 34 |
+
+def _activation_basis(activation: ExpertActivation) -> str:
|
| 35 |
+
+ if activation not in ("situ", "silu"):
|
| 36 |
+
+ raise ValueError(f"unsupported expert activation: {activation}")
|
| 37 |
+
+ return f"post_{activation}"
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _folded_intermediate_conditioning(
|
| 41 |
+
@@ -407,13 +422,16 @@ def _candidate_middle_by_r13(
|
| 42 |
+
inputs: torch.Tensor,
|
| 43 |
+
upstream: dict[str, dict[int, QSRTMatrixCandidate]],
|
| 44 |
+
evaluated_modes: Sequence[int],
|
| 45 |
+
+ *,
|
| 46 |
+
+ activation: ExpertActivation = "situ",
|
| 47 |
+
) -> dict[int, torch.Tensor]:
|
| 48 |
+
- """Replay exact candidate reconstructions through Kimi's SiTU."""
|
| 49 |
+
+ """Replay exact candidate reconstructions through the model activation."""
|
| 50 |
+
|
| 51 |
+
return {
|
| 52 |
+
int(r13): _source_middle(
|
| 53 |
+
F.linear(inputs, upstream["w1"][int(r13)].reconstruction),
|
| 54 |
+
F.linear(inputs, upstream["w3"][int(r13)].reconstruction),
|
| 55 |
+
+ activation=activation,
|
| 56 |
+
)
|
| 57 |
+
for r13 in evaluated_modes
|
| 58 |
+
}
|
| 59 |
+
@@ -627,6 +645,7 @@ def encode_phase1_expert(
|
| 60 |
+
permutation_policy: PermutationPolicy = "h2_reverse",
|
| 61 |
+
folded_scale_power: float = 0.0,
|
| 62 |
+
transform_seeds: Mapping[str, QSRTTransformSeeds] | None = None,
|
| 63 |
+
+ activation: ExpertActivation = "situ",
|
| 64 |
+
) -> QSRTCandidateEncoding:
|
| 65 |
+
"""Encode and conservatively select one official-source expert.
|
| 66 |
+
|
| 67 |
+
@@ -674,7 +693,11 @@ def encode_phase1_expert(
|
| 68 |
+
if all_rows.rows:
|
| 69 |
+
source_gate = F.linear(inputs, source_w1)
|
| 70 |
+
source_up = F.linear(inputs, source_w3)
|
| 71 |
+
- source_middle = _source_middle(source_gate, source_up)
|
| 72 |
+
+ source_middle = _source_middle(
|
| 73 |
+
+ source_gate,
|
| 74 |
+
+ source_up,
|
| 75 |
+
+ activation=activation,
|
| 76 |
+
+ )
|
| 77 |
+
del source_gate, source_up
|
| 78 |
+
else:
|
| 79 |
+
source_middle = torch.empty(
|
| 80 |
+
@@ -697,7 +720,8 @@ def encode_phase1_expert(
|
| 81 |
+
"h13_local_alpha": 0.0,
|
| 82 |
+
}
|
| 83 |
+
context_basis = (
|
| 84 |
+
- f"official_source_post_situ_fit_documents:{permutation_policy}"
|
| 85 |
+
+ f"official_source_{_activation_basis(activation)}_fit_documents:"
|
| 86 |
+
+ f"{permutation_policy}"
|
| 87 |
+
)
|
| 88 |
+
covariance = {
|
| 89 |
+
**covariance,
|
| 90 |
+
@@ -762,7 +786,10 @@ def encode_phase1_expert(
|
| 91 |
+
del source_w1, source_w3
|
| 92 |
+
|
| 93 |
+
middle_by_r13 = _candidate_middle_by_r13(
|
| 94 |
+
- inputs, upstream_candidates, evaluated_modes
|
| 95 |
+
+ inputs,
|
| 96 |
+
+ upstream_candidates,
|
| 97 |
+
+ evaluated_modes,
|
| 98 |
+
+ activation=activation,
|
| 99 |
+
)
|
| 100 |
+
h2_by_r13, h2_evidence = _conditional_h2_by_r13(
|
| 101 |
+
inputs,
|
| 102 |
+
@@ -781,7 +808,8 @@ def encode_phase1_expert(
|
| 103 |
+
"identity"
|
| 104 |
+
if hessian_policy == "identity"
|
| 105 |
+
else (
|
| 106 |
+
- "decoded_candidate_post_situ_adaptive_shrinkage"
|
| 107 |
+
+ f"decoded_candidate_{_activation_basis(activation)}_"
|
| 108 |
+
+ "adaptive_shrinkage"
|
| 109 |
+
if have_local_support
|
| 110 |
+
else "identity_support_fallback"
|
| 111 |
+
)
|
| 112 |
+
@@ -789,7 +817,7 @@ def encode_phase1_expert(
|
| 113 |
+
"h2_by_r13": h2_evidence,
|
| 114 |
+
}
|
| 115 |
+
)
|
| 116 |
+
- context_basis += "+decoded_candidate_post_situ_h2"
|
| 117 |
+
+ context_basis += f"+decoded_candidate_{_activation_basis(activation)}_h2"
|
| 118 |
+
|
| 119 |
+
source_w2 = _load_source_matrix(store, layer, expert, "w2", device)
|
| 120 |
+
if all_rows.rows:
|
| 121 |
+
@@ -996,6 +1024,7 @@ def encode_phase1_expert_batch(
|
| 122 |
+
int, Mapping[str, QSRTTransformSeeds]
|
| 123 |
+
] | None = None,
|
| 124 |
+
coupled_intermediate_draws_by_expert: Mapping[int, int] | None = None,
|
| 125 |
+
+ activation: ExpertActivation = "situ",
|
| 126 |
+
) -> list[QSRTCandidateEncoding]:
|
| 127 |
+
"""Encode several experts while batching their independent trellis work.
|
| 128 |
+
|
| 129 |
+
@@ -1019,6 +1048,7 @@ def encode_phase1_expert_batch(
|
| 130 |
+
fixed_k2 = modes == (K2.mode_id,)
|
| 131 |
+
fixed_profile = fixed_k3 or fixed_high_rate or fixed_k2
|
| 132 |
+
coupled = coupled_intermediate_draws_by_expert is not None
|
| 133 |
+
+ _activation_basis(activation)
|
| 134 |
+
if coupled:
|
| 135 |
+
if modes not in (
|
| 136 |
+
(R0.mode_id,),
|
| 137 |
+
@@ -1079,7 +1109,11 @@ def encode_phase1_expert_batch(
|
| 138 |
+
if all_rows.rows:
|
| 139 |
+
source_gate = F.linear(inputs, source_w1)
|
| 140 |
+
source_up = F.linear(inputs, source_w3)
|
| 141 |
+
- source_middle = _source_middle(source_gate, source_up)
|
| 142 |
+
+ source_middle = _source_middle(
|
| 143 |
+
+ source_gate,
|
| 144 |
+
+ source_up,
|
| 145 |
+
+ activation=activation,
|
| 146 |
+
+ )
|
| 147 |
+
del source_gate, source_up
|
| 148 |
+
else:
|
| 149 |
+
source_middle = torch.empty(
|
| 150 |
+
@@ -1095,7 +1129,8 @@ def encode_phase1_expert_batch(
|
| 151 |
+
policy=permutation_policy,
|
| 152 |
+
)
|
| 153 |
+
context_basis = (
|
| 154 |
+
- f"official_source_post_situ_fit_documents:{permutation_policy}"
|
| 155 |
+
+ f"official_source_{_activation_basis(activation)}_fit_documents:"
|
| 156 |
+
+ f"{permutation_policy}"
|
| 157 |
+
)
|
| 158 |
+
else:
|
| 159 |
+
block_contexts = fallback_block_contexts.clone()
|
| 160 |
+
@@ -1156,7 +1191,10 @@ def encode_phase1_expert_batch(
|
| 161 |
+
coupled_reference_output: torch.Tensor | None = None
|
| 162 |
+
if coupled:
|
| 163 |
+
draw = int(coupled_intermediate_draws_by_expert[expert])
|
| 164 |
+
- spec = CoupledHadamardSpec(intermediate_draw=draw)
|
| 165 |
+
+ spec = CoupledHadamardSpec(
|
| 166 |
+
+ intermediate_draw=draw,
|
| 167 |
+
+ activation=activation,
|
| 168 |
+
+ )
|
| 169 |
+
source_w2 = _load_source_matrix(store, layer, expert, "w2", device)
|
| 170 |
+
transformed = encode_coupled_weights(
|
| 171 |
+
(source_w1, source_w3, source_w2), spec
|
| 172 |
+
@@ -1272,7 +1310,10 @@ def encode_phase1_expert_batch(
|
| 173 |
+
execution_basis = item["coupled_execution"]
|
| 174 |
+
if execution_basis is None:
|
| 175 |
+
middle_by_r13 = _candidate_middle_by_r13(
|
| 176 |
+
- inputs, upstream, evaluated_modes
|
| 177 |
+
+ inputs,
|
| 178 |
+
+ upstream,
|
| 179 |
+
+ evaluated_modes,
|
| 180 |
+
+ activation=activation,
|
| 181 |
+
)
|
| 182 |
+
else:
|
| 183 |
+
middle_by_r13 = {
|
| 184 |
+
@@ -1303,7 +1344,8 @@ def encode_phase1_expert_batch(
|
| 185 |
+
"identity"
|
| 186 |
+
if hessian_policy == "identity"
|
| 187 |
+
else (
|
| 188 |
+
- "decoded_candidate_post_situ_adaptive_shrinkage"
|
| 189 |
+
+ f"decoded_candidate_{_activation_basis(activation)}_"
|
| 190 |
+
+ "adaptive_shrinkage"
|
| 191 |
+
if int(item["covariance"]["fit_documents"])
|
| 192 |
+
>= min_fit_documents
|
| 193 |
+
else "identity_support_fallback"
|
| 194 |
+
@@ -1313,7 +1355,8 @@ def encode_phase1_expert_batch(
|
| 195 |
+
}
|
| 196 |
+
)
|
| 197 |
+
item["context_basis"] = (
|
| 198 |
+
- str(item["context_basis"]) + "+decoded_candidate_post_situ_h2"
|
| 199 |
+
+ str(item["context_basis"])
|
| 200 |
+
+ + f"+decoded_candidate_{_activation_basis(activation)}_h2"
|
| 201 |
+
)
|
| 202 |
+
all_rows = item["all_rows"]
|
| 203 |
+
if item["coupled_reference_output"] is not None:
|
| 204 |
+
@@ -1535,6 +1578,9 @@ def encode_phase1_expert_batch(
|
| 205 |
+
"intermediate_draw": item[
|
| 206 |
+
"coupled_execution"
|
| 207 |
+
].spec.intermediate_draw,
|
| 208 |
+
+ "activation": item[
|
| 209 |
+
+ "coupled_execution"
|
| 210 |
+
+ ].spec.activation,
|
| 211 |
+
}
|
| 212 |
+
),
|
| 213 |
+
**_mode_evidence(selected_encodings),
|
| 214 |
+
diff --git a/qsrt/qsrt_coupled.py b/qsrt/qsrt_coupled.py
|
| 215 |
+
index 6159771..4febf9b 100644
|
| 216 |
+
--- a/qsrt/qsrt_coupled.py
|
| 217 |
+
+++ b/qsrt/qsrt_coupled.py
|
| 218 |
+
@@ -11,6 +11,7 @@ from __future__ import annotations
|
| 219 |
+
|
| 220 |
+
import math
|
| 221 |
+
from dataclasses import dataclass
|
| 222 |
+
+from typing import Literal
|
| 223 |
+
|
| 224 |
+
import torch
|
| 225 |
+
import torch.nn.functional as F
|
| 226 |
+
@@ -20,6 +21,31 @@ from qsrt.tp_simulator import situ
|
| 227 |
+
|
| 228 |
+
Tensor = torch.Tensor
|
| 229 |
+
WeightTriplet = tuple[Tensor, Tensor, Tensor]
|
| 230 |
+
+ExpertActivation = Literal["situ", "silu"]
|
| 231 |
+
+
|
| 232 |
+
+
|
| 233 |
+
+def expert_activation(
|
| 234 |
+
+ gate: Tensor,
|
| 235 |
+
+ up: Tensor,
|
| 236 |
+
+ *,
|
| 237 |
+
+ activation: ExpertActivation,
|
| 238 |
+
+) -> Tensor:
|
| 239 |
+
+ """Evaluate the model's exact coordinatewise gated activation.
|
| 240 |
+
+
|
| 241 |
+
+ Coupled Hadamard construction is valid for either activation because the
|
| 242 |
+
+ preactivation transform is inverted before this operation and the
|
| 243 |
+
+ postactivation transform is applied afterward. Keeping the choice in the
|
| 244 |
+
+ immutable transform specification prevents a Kimi SiTU candidate from
|
| 245 |
+
+ being replayed accidentally under GLM's standard SwiGLU contract.
|
| 246 |
+
+ """
|
| 247 |
+
+
|
| 248 |
+
+ if gate.shape != up.shape:
|
| 249 |
+
+ raise ValueError("gate and up projections must have matching shapes")
|
| 250 |
+
+ if activation == "situ":
|
| 251 |
+
+ return situ(gate, up)
|
| 252 |
+
+ if activation == "silu":
|
| 253 |
+
+ return F.silu(gate) * up
|
| 254 |
+
+ raise ValueError(f"unsupported expert activation: {activation}")
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
@dataclass(frozen=True)
|
| 258 |
+
@@ -31,6 +57,7 @@ class CoupledHadamardSpec:
|
| 259 |
+
postactivation_block_size: int = 128
|
| 260 |
+
residual_draw: int = 0
|
| 261 |
+
intermediate_draw: int = 0
|
| 262 |
+
+ activation: ExpertActivation = "situ"
|
| 263 |
+
|
| 264 |
+
def __post_init__(self) -> None:
|
| 265 |
+
for name, value in (
|
| 266 |
+
@@ -44,6 +71,8 @@ class CoupledHadamardSpec:
|
| 267 |
+
raise ValueError("the coupled profile fixes residual draw zero")
|
| 268 |
+
if not 0 <= self.intermediate_draw < 8:
|
| 269 |
+
raise ValueError("intermediate_draw must lie in 0..7")
|
| 270 |
+
+ if self.activation not in ("situ", "silu"):
|
| 271 |
+
+ raise ValueError("activation must be 'situ' or 'silu'")
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def block_hadamard(values: Tensor, *, block_size: int, dim: int = -1) -> Tensor:
|
| 275 |
+
@@ -252,19 +281,46 @@ class CoupledHadamardExecution:
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
def decode_middle(self, rows: Tensor, w1: Tensor, w3: Tensor) -> Tensor:
|
| 279 |
+
- transformed = torch.cat((F.linear(rows, w1), F.linear(rows, w3)), dim=1)
|
| 280 |
+
+ return self.decode_middle_outputs(F.linear(rows, w1), F.linear(rows, w3))
|
| 281 |
+
+
|
| 282 |
+
+ def decode_middle_outputs(
|
| 283 |
+
+ self, transformed_w1: Tensor, transformed_w3: Tensor
|
| 284 |
+
+ ) -> Tensor:
|
| 285 |
+
+ """Close the coupled boundary from already-computed FC1 outputs.
|
| 286 |
+
+
|
| 287 |
+
+ This is the runtime/calibration seam used when the two upstream GEMMs
|
| 288 |
+
+ have their own operand precision, reconstruction law, or fused kernel.
|
| 289 |
+
+ It applies exactly the same inverse preactivation rotation, model
|
| 290 |
+
+ activation, and postactivation rotation as :meth:`decode_middle`.
|
| 291 |
+
+ """
|
| 292 |
+
+
|
| 293 |
+
+ if (
|
| 294 |
+
+ transformed_w1.ndim != 2
|
| 295 |
+
+ or transformed_w3.ndim != 2
|
| 296 |
+
+ or transformed_w1.shape != transformed_w3.shape
|
| 297 |
+
+ or transformed_w1.shape[1] != self.intermediate
|
| 298 |
+
+ or transformed_w1.device != transformed_w3.device
|
| 299 |
+
+ ):
|
| 300 |
+
+ raise ValueError(
|
| 301 |
+
+ "coupled upstream outputs must be aligned [rows, intermediate]"
|
| 302 |
+
+ )
|
| 303 |
+
+ transformed = torch.cat((transformed_w1, transformed_w3), dim=1)
|
| 304 |
+
recovered = signed_block_hadamard(
|
| 305 |
+
transformed,
|
| 306 |
+
block_size=self.spec.preactivation_block_size,
|
| 307 |
+
- signs=self._signs(1, device=rows.device),
|
| 308 |
+
+ signs=self._signs(1, device=transformed.device),
|
| 309 |
+
dim=1,
|
| 310 |
+
inverse=True,
|
| 311 |
+
)
|
| 312 |
+
- middle = situ(recovered[:, 0::2], recovered[:, 1::2])
|
| 313 |
+
+ middle = expert_activation(
|
| 314 |
+
+ recovered[:, 0::2],
|
| 315 |
+
+ recovered[:, 1::2],
|
| 316 |
+
+ activation=self.spec.activation,
|
| 317 |
+
+ )
|
| 318 |
+
return signed_block_hadamard(
|
| 319 |
+
middle,
|
| 320 |
+
block_size=self.spec.postactivation_block_size,
|
| 321 |
+
- signs=self._signs(2, device=rows.device),
|
| 322 |
+
+ signs=self._signs(2, device=transformed.device),
|
| 323 |
+
dim=1,
|
| 324 |
+
)
|
| 325 |
+
|
| 326 |
+
@@ -279,6 +335,24 @@ class CoupledHadamardExecution:
|
| 327 |
+
inverse=True,
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
+ def transform_outputs(self, rows: Tensor) -> Tensor:
|
| 331 |
+
+ """Map caller-coordinate outputs into the coupled residual basis.
|
| 332 |
+
+
|
| 333 |
+
+ Calibration uses this forward counterpart of :meth:`decode_output`
|
| 334 |
+
+ when constructing a candidate-conditioned down cross term. The
|
| 335 |
+
+ candidate operand and source-model teacher must share the transformed
|
| 336 |
+
+ output coordinates before forming ``B = Q.T @ Y``.
|
| 337 |
+
+ """
|
| 338 |
+
+
|
| 339 |
+
+ if rows.ndim != 2 or rows.shape[1] != self.hidden:
|
| 340 |
+
+ raise ValueError("output rows do not match the hidden dimension")
|
| 341 |
+
+ return signed_block_hadamard(
|
| 342 |
+
+ rows,
|
| 343 |
+
+ block_size=self.spec.residual_block_size,
|
| 344 |
+
+ signs=self._signs(3, device=rows.device),
|
| 345 |
+
+ dim=1,
|
| 346 |
+
+ )
|
| 347 |
+
+
|
| 348 |
+
def execute(self, rows: Tensor, weights: WeightTriplet) -> Tensor:
|
| 349 |
+
_validate_triplet(weights)
|
| 350 |
+
transformed_rows = self.transform_inputs(rows)
|
| 351 |
+
@@ -296,10 +370,12 @@ def coupled_execution(
|
| 352 |
+
__all__ = [
|
| 353 |
+
"CoupledHadamardExecution",
|
| 354 |
+
"CoupledHadamardSpec",
|
| 355 |
+
+ "ExpertActivation",
|
| 356 |
+
"WeightTriplet",
|
| 357 |
+
"block_hadamard",
|
| 358 |
+
"coupled_execution",
|
| 359 |
+
"encode_coupled_weights",
|
| 360 |
+
+ "expert_activation",
|
| 361 |
+
"rotation_signs",
|
| 362 |
+
"signed_block_hadamard",
|
| 363 |
+
]
|
| 364 |
+
diff --git a/tests/test_qsrt_coupled.py b/tests/test_qsrt_coupled.py
|
| 365 |
+
index 8f3f6a3..2fef347 100644
|
| 366 |
+
--- a/tests/test_qsrt_coupled.py
|
| 367 |
+
+++ b/tests/test_qsrt_coupled.py
|
| 368 |
+
@@ -12,6 +12,7 @@ from qsrt.qsrt_coupled import (
|
| 369 |
+
block_hadamard,
|
| 370 |
+
coupled_execution,
|
| 371 |
+
encode_coupled_weights,
|
| 372 |
+
+ expert_activation,
|
| 373 |
+
rotation_signs,
|
| 374 |
+
signed_block_hadamard,
|
| 375 |
+
)
|
| 376 |
+
@@ -75,6 +76,66 @@ def test_production_coupled_transform_closes_and_matches_research_oracle() -> No
|
| 377 |
+
assert torch.allclose(output, research_output, rtol=2e-5, atol=2e-5)
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
+def test_glm_silu_coupled_transform_closes_exact_expert_function() -> None:
|
| 381 |
+
+ generator = torch.Generator().manual_seed(5202)
|
| 382 |
+
+ rows, hidden, intermediate = 11, 24, 16
|
| 383 |
+
+ weights = (
|
| 384 |
+
+ torch.randn(intermediate, hidden, generator=generator),
|
| 385 |
+
+ torch.randn(intermediate, hidden, generator=generator),
|
| 386 |
+
+ torch.randn(hidden, intermediate, generator=generator),
|
| 387 |
+
+ )
|
| 388 |
+
+ inputs = torch.randn(rows, hidden, generator=generator)
|
| 389 |
+
+ spec = CoupledHadamardSpec(
|
| 390 |
+
+ residual_block_size=8,
|
| 391 |
+
+ preactivation_block_size=8,
|
| 392 |
+
+ postactivation_block_size=8,
|
| 393 |
+
+ intermediate_draw=6,
|
| 394 |
+
+ activation="silu",
|
| 395 |
+
+ )
|
| 396 |
+
+
|
| 397 |
+
+ encoded = encode_coupled_weights(weights, spec)
|
| 398 |
+
+ execution = coupled_execution(encoded, spec)
|
| 399 |
+
+ output = execution.execute(inputs, encoded)
|
| 400 |
+
+ gate = torch.nn.functional.linear(inputs, weights[0])
|
| 401 |
+
+ up = torch.nn.functional.linear(inputs, weights[1])
|
| 402 |
+
+ reference = torch.nn.functional.linear(
|
| 403 |
+
+ torch.nn.functional.silu(gate) * up,
|
| 404 |
+
+ weights[2],
|
| 405 |
+
+ )
|
| 406 |
+
+
|
| 407 |
+
+ assert torch.allclose(output, reference, rtol=2e-5, atol=2e-5)
|
| 408 |
+
+
|
| 409 |
+
+ transformed_input = execution.transform_inputs(inputs)
|
| 410 |
+
+ gate_output = torch.nn.functional.linear(transformed_input, encoded[0])
|
| 411 |
+
+ up_output = torch.nn.functional.linear(transformed_input, encoded[1])
|
| 412 |
+
+ middle_from_outputs = execution.decode_middle_outputs(
|
| 413 |
+
+ gate_output, up_output
|
| 414 |
+
+ )
|
| 415 |
+
+ middle_from_weights = execution.decode_middle(
|
| 416 |
+
+ transformed_input, encoded[0], encoded[1]
|
| 417 |
+
+ )
|
| 418 |
+
+ torch.testing.assert_close(
|
| 419 |
+
+ middle_from_outputs, middle_from_weights, rtol=0, atol=0
|
| 420 |
+
+ )
|
| 421 |
+
+
|
| 422 |
+
+ transformed_output = execution.transform_outputs(reference)
|
| 423 |
+
+ torch.testing.assert_close(
|
| 424 |
+
+ execution.decode_output(transformed_output), reference, rtol=2e-5, atol=2e-5
|
| 425 |
+
+ )
|
| 426 |
+
+
|
| 427 |
+
+
|
| 428 |
+
+def test_expert_activation_rejects_unknown_contract() -> None:
|
| 429 |
+
+ gate = torch.ones((2, 3))
|
| 430 |
+
+ up = torch.ones((2, 3))
|
| 431 |
+
+
|
| 432 |
+
+ try:
|
| 433 |
+
+ expert_activation(gate, up, activation="gelu") # type: ignore[arg-type]
|
| 434 |
+
+ except ValueError as exc:
|
| 435 |
+
+ assert "unsupported expert activation" in str(exc)
|
| 436 |
+
+ else:
|
| 437 |
+
+ raise AssertionError("unknown expert activation was accepted")
|
| 438 |
+
+
|
| 439 |
+
+
|
| 440 |
+
def test_coupled_hessian_transforms_preserve_quadratic_forms() -> None:
|
| 441 |
+
generator = torch.Generator().manual_seed(91)
|
| 442 |
+
hidden, intermediate = 16, 12
|