flashrt-structures 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flashrt_structures/__init__.py +174 -0
- flashrt_structures/adapters/__init__.py +55 -0
- flashrt_structures/adapters/diffusers_attention.py +237 -0
- flashrt_structures/adapters/diffusers_rotary_attention.py +239 -0
- flashrt_structures/adapters/factored_qk_norm_rope.py +252 -0
- flashrt_structures/adapters/factored_two_way_attention.py +99 -0
- flashrt_structures/adapters/gemma_attention.py +226 -0
- flashrt_structures/adapters/packed_qkv_rope.py +342 -0
- flashrt_structures/adapters/packed_stream_qk_norm_rope.py +376 -0
- flashrt_structures/adapters/qwen_per_head_qk_norm_rope.py +231 -0
- flashrt_structures/adapters/sglang_engine.py +207 -0
- flashrt_structures/adapters/transformers_attention_interface.py +73 -0
- flashrt_structures/adapters/transformers_gated_delta.py +152 -0
- flashrt_structures/adapters/transformers_gated_delta_fused.py +96 -0
- flashrt_structures/adapters/vllm_engine.py +424 -0
- flashrt_structures/adjudicate.py +85 -0
- flashrt_structures/aot.py +191 -0
- flashrt_structures/autobuild.py +2052 -0
- flashrt_structures/beta/__init__.py +43 -0
- flashrt_structures/beta/conform.py +94 -0
- flashrt_structures/beta/joins.py +113 -0
- flashrt_structures/beta/negotiate.py +84 -0
- flashrt_structures/beta/ports.py +140 -0
- flashrt_structures/decisions.py +80 -0
- flashrt_structures/discover.py +623 -0
- flashrt_structures/explain.py +70 -0
- flashrt_structures/frontdoor.py +572 -0
- flashrt_structures/gates.py +465 -0
- flashrt_structures/guard.py +421 -0
- flashrt_structures/handle.py +189 -0
- flashrt_structures/impls/__init__.py +219 -0
- flashrt_structures/impls/adaln_producer/__init__.py +8 -0
- flashrt_structures/impls/adaln_producer/broker.py +116 -0
- flashrt_structures/impls/adaln_producer/fused.py +388 -0
- flashrt_structures/impls/adarms_stack/__init__.py +8 -0
- flashrt_structures/impls/adarms_stack/fp8_chain.py +832 -0
- flashrt_structures/impls/adarms_stack/region.py +102 -0
- flashrt_structures/impls/attention_core/__init__.py +132 -0
- flashrt_structures/impls/attention_core/fa2_seqused.py +458 -0
- flashrt_structures/impls/attention_core/fa4_cute.py +154 -0
- flashrt_structures/impls/attention_core/fa4_fp8.py +178 -0
- flashrt_structures/impls/attention_core/masked_mha.py +158 -0
- flashrt_structures/impls/attention_core/two_way_fa2.py +220 -0
- flashrt_structures/impls/cadence_static/__init__.py +18 -0
- flashrt_structures/impls/cadence_static/buffers.py +122 -0
- flashrt_structures/impls/cadence_static/cross_attention.py +187 -0
- flashrt_structures/impls/chain_elements.py +89 -0
- flashrt_structures/impls/decode_loop/__init__.py +0 -0
- flashrt_structures/impls/decode_loop/fp8_kv.py +206 -0
- flashrt_structures/impls/decode_loop/mtp_speculative.py +245 -0
- flashrt_structures/impls/decode_loop/whole_step.py +852 -0
- flashrt_structures/impls/decoder_block/__init__.py +6 -0
- flashrt_structures/impls/decoder_block/attn_sublayer.py +110 -0
- flashrt_structures/impls/decoder_block/fused.py +167 -0
- flashrt_structures/impls/decoder_ffn/__init__.py +0 -0
- flashrt_structures/impls/decoder_ffn/fp8_static.py +310 -0
- flashrt_structures/impls/decoder_ffn/fp8_static.yaml +22 -0
- flashrt_structures/impls/decoder_ffn/w4a16_static.py +221 -0
- flashrt_structures/impls/decoder_ffn/w8a16_static.py +183 -0
- flashrt_structures/impls/dit_stack/__init__.py +11 -0
- flashrt_structures/impls/dit_stack/fp4_chain.py +417 -0
- flashrt_structures/impls/dit_stack/region.py +86 -0
- flashrt_structures/impls/fixed_iter/__init__.py +29 -0
- flashrt_structures/impls/fixed_iter/openpi.py +264 -0
- flashrt_structures/impls/fixed_iter/protocol.py +94 -0
- flashrt_structures/impls/gated_delta_core/__init__.py +3 -0
- flashrt_structures/impls/gated_delta_core/fused_layer.py +545 -0
- flashrt_structures/impls/gated_delta_core/hub_v3.py +152 -0
- flashrt_structures/impls/graph_lowering/__init__.py +27 -0
- flashrt_structures/impls/graph_lowering/pi052_denoise.py +179 -0
- flashrt_structures/impls/graph_lowering/protocol.py +76 -0
- flashrt_structures/impls/graph_lowering/qwen3_vl.py +364 -0
- flashrt_structures/impls/linear_proj/__init__.py +0 -0
- flashrt_structures/impls/linear_proj/fp8_static.py +270 -0
- flashrt_structures/impls/linear_proj/nvfp4_balance.py +131 -0
- flashrt_structures/impls/linear_proj/nvfp4_dynamic.py +182 -0
- flashrt_structures/impls/linear_proj/w8a16_static.py +230 -0
- flashrt_structures/impls/modnorm_qkv_chain/__init__.py +0 -0
- flashrt_structures/impls/modnorm_qkv_chain/fp8_ptok_table.py +291 -0
- flashrt_structures/impls/moe_experts/__init__.py +9 -0
- flashrt_structures/impls/moe_experts/nvfp4_dynamic.py +208 -0
- flashrt_structures/impls/moe_experts/nvfp4_w4a16.py +129 -0
- flashrt_structures/impls/norm_fused/__init__.py +3 -0
- flashrt_structures/impls/norm_fused/bf16.py +94 -0
- flashrt_structures/impls/norm_fused/fp8_producer.py +84 -0
- flashrt_structures/impls/patch_projection/__init__.py +3 -0
- flashrt_structures/impls/patch_projection/bf16_flat.py +139 -0
- flashrt_structures/impls/prefill_tower/__init__.py +10 -0
- flashrt_structures/impls/prefill_tower/fp8_chain.py +955 -0
- flashrt_structures/impls/prefill_tower/region.py +99 -0
- flashrt_structures/impls/qk_norm_rope/__init__.py +12 -0
- flashrt_structures/impls/qk_norm_rope/per_head_gqa.py +199 -0
- flashrt_structures/impls/qk_norm_rope/projection_bf16.py +165 -0
- flashrt_structures/impls/qkv_pack/__init__.py +5 -0
- flashrt_structures/impls/qkv_pack/bf16.py +110 -0
- flashrt_structures/impls/qkv_pack/fp8_static.py +435 -0
- flashrt_structures/impls/qkv_pack/nvfp4_balance.py +218 -0
- flashrt_structures/impls/qkv_rope/__init__.py +3 -0
- flashrt_structures/impls/qkv_rope/packed_bias_bf16.py +143 -0
- flashrt_structures/impls/step_table.py +113 -0
- flashrt_structures/impls/vision_ffn/__init__.py +0 -0
- flashrt_structures/impls/vision_ffn/fp8_static.py +261 -0
- flashrt_structures/impls/vision_ffn/nvfp4_balance.py +211 -0
- flashrt_structures/impls/vision_tower/__init__.py +7 -0
- flashrt_structures/impls/vision_tower/fp8_chain.py +533 -0
- flashrt_structures/impls/vision_tower/region.py +91 -0
- flashrt_structures/matrix.py +126 -0
- flashrt_structures/points.py +368 -0
- flashrt_structures/prequantized.py +131 -0
- flashrt_structures/quantize_on_adopt.py +94 -0
- flashrt_structures/recipe.py +438 -0
- flashrt_structures/regions.py +208 -0
- flashrt_structures/schemes.py +490 -0
- flashrt_structures/stages.py +298 -0
- flashrt_structures/storage.py +255 -0
- flashrt_structures/swap.py +398 -0
- flashrt_structures/workspace.py +110 -0
- flashrt_structures-0.2.0.dist-info/METADATA +140 -0
- flashrt_structures-0.2.0.dist-info/RECORD +122 -0
- flashrt_structures-0.2.0.dist-info/WHEEL +5 -0
- flashrt_structures-0.2.0.dist-info/licenses/LICENSE +202 -0
- flashrt_structures-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
"""Qualification gates — parity judgment against a structure's reference.
|
|
2
|
+
|
|
3
|
+
The harness is structure-agnostic. Implementations follow the structure
|
|
4
|
+
calling convention: required boundary inputs in declared order, then
|
|
5
|
+
weight tensors in slot order, then variant selections and any optional
|
|
6
|
+
boundary inputs as keyword arguments — the same signature the reference
|
|
7
|
+
implementation exposes.
|
|
8
|
+
|
|
9
|
+
A qualification produces a machine-readable record whose ``plan_digest``
|
|
10
|
+
binds the spec content, variant, resolved workload dims, thresholds,
|
|
11
|
+
implementation identity, and environment. A record certifies exactly one
|
|
12
|
+
execution plan; change any component and the record no longer applies.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import pathlib
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Any, Callable, Mapping
|
|
22
|
+
|
|
23
|
+
import torch
|
|
24
|
+
|
|
25
|
+
from flash_rt.catalog.registry import StructureSpec, _CATALOG_DIR
|
|
26
|
+
from flash_rt.core.parity import parity_metrics as _parity_metrics
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class QualificationCase:
|
|
31
|
+
"""One workload to qualify: boundary inputs, weights, and variant."""
|
|
32
|
+
|
|
33
|
+
inputs: Mapping[str, torch.Tensor]
|
|
34
|
+
weights: Mapping[str, torch.Tensor]
|
|
35
|
+
variant: Mapping[str, str] = field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def solve_dims(
|
|
39
|
+
spec: StructureSpec,
|
|
40
|
+
inputs: Mapping[str, torch.Tensor],
|
|
41
|
+
weights: Mapping[str, torch.Tensor],
|
|
42
|
+
) -> dict[str, int]:
|
|
43
|
+
"""Resolve symbolic dims from actual tensors, rejecting inconsistency."""
|
|
44
|
+
dims: dict[str, int] = {}
|
|
45
|
+
|
|
46
|
+
def bind(declared: list[str], tensor: torch.Tensor, what: str) -> None:
|
|
47
|
+
if tensor.ndim != len(declared):
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"{what}: expected rank {len(declared)} {declared}, "
|
|
50
|
+
f"got shape {tuple(tensor.shape)}"
|
|
51
|
+
)
|
|
52
|
+
for name, size in zip(declared, tensor.shape):
|
|
53
|
+
if dims.setdefault(name, int(size)) != int(size):
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"{what}: dim {name}={int(size)} conflicts with "
|
|
56
|
+
f"{name}={dims[name]} resolved earlier"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
for entry in spec.boundary["inputs"]:
|
|
60
|
+
tensor = inputs.get(entry["name"])
|
|
61
|
+
if tensor is None:
|
|
62
|
+
if entry.get("optional", False):
|
|
63
|
+
continue
|
|
64
|
+
raise ValueError(f"missing required input: {entry['name']!r}")
|
|
65
|
+
bind(entry["dims"], tensor, f"input {entry['name']!r}")
|
|
66
|
+
for entry in spec.weights:
|
|
67
|
+
slot = entry["slot"]
|
|
68
|
+
if slot not in weights:
|
|
69
|
+
raise ValueError(f"missing weight slot: {slot!r}")
|
|
70
|
+
bind(entry["dims"], weights[slot], f"weight {slot!r}")
|
|
71
|
+
return dims
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _call(spec: StructureSpec, fn: Callable[..., Any],
|
|
75
|
+
case: QualificationCase, *, bound: bool = False) -> torch.Tensor:
|
|
76
|
+
"""Invoke ``fn`` per the structure calling convention.
|
|
77
|
+
|
|
78
|
+
``bound=False`` targets full-signature callables (the reference):
|
|
79
|
+
required inputs, then weight slots, with variants and optional inputs
|
|
80
|
+
as keywords. ``bound=True`` targets bound implementations whose
|
|
81
|
+
weights and variant were baked in at bind time: inputs only.
|
|
82
|
+
"""
|
|
83
|
+
args: list[torch.Tensor] = []
|
|
84
|
+
kwargs: dict[str, Any] = {} if bound else dict(case.variant)
|
|
85
|
+
for entry in spec.boundary["inputs"]:
|
|
86
|
+
tensor = case.inputs.get(entry["name"])
|
|
87
|
+
if entry.get("optional", False):
|
|
88
|
+
if tensor is not None:
|
|
89
|
+
kwargs[entry["name"]] = tensor
|
|
90
|
+
else:
|
|
91
|
+
args.append(tensor)
|
|
92
|
+
if not bound:
|
|
93
|
+
args.extend(case.weights[slot] for slot in spec.weight_slots)
|
|
94
|
+
return fn(*args, **kwargs)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def parity_metrics(got: torch.Tensor, want: torch.Tensor) -> dict[str, float]:
|
|
98
|
+
"""Cosine / max-abs / p99-abs between an implementation and a truth."""
|
|
99
|
+
return _parity_metrics(got, want)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ---- which metric a host's output deserves ----------------------------
|
|
103
|
+
#
|
|
104
|
+
# A cosine over the whole output tensor is the right measure for a host
|
|
105
|
+
# whose output *is* the answer — an action chunk, a hidden state, a
|
|
106
|
+
# feature map. It is the wrong measure for a host whose output is a
|
|
107
|
+
# distribution over a vocabulary, and measuring both is what showed why.
|
|
108
|
+
# Same bindings, same weights, only the prompt length changed:
|
|
109
|
+
#
|
|
110
|
+
# tokens cosine over all logits top-1 agreement
|
|
111
|
+
# 15 0.9991 93.3%
|
|
112
|
+
# 360 0.9450 99.4%
|
|
113
|
+
#
|
|
114
|
+
# The two move in opposite directions with length. Aggregated over every
|
|
115
|
+
# position, the cosine is dominated by positions that never drive a
|
|
116
|
+
# decision, so it tracks sequence length more than output fidelity, while
|
|
117
|
+
# the quantities generation actually depends on — the last position, top-1
|
|
118
|
+
# agreement, per-token KL — are stable across both lengths.
|
|
119
|
+
#
|
|
120
|
+
# So the metric is not a library-wide constant. It belongs to the host's
|
|
121
|
+
# output type, and the gate has to select it rather than assume one.
|
|
122
|
+
|
|
123
|
+
OUTPUT_KINDS = ("values", "distribution")
|
|
124
|
+
|
|
125
|
+
#: Band edges for the headline accuracy metric. ``>= BAND_PASS`` is clean,
|
|
126
|
+
#: ``>= BAND_WARN`` is the recorded WARN band, and below that is ``low``.
|
|
127
|
+
#:
|
|
128
|
+
#: ``low`` warns; it does not refuse. Low-precision execution is
|
|
129
|
+
#: increasingly the intent rather than a defect — a W4A4 or MXFP4 host sits
|
|
130
|
+
#: here by design, and a layer that hard-refused at a fixed cosine would be
|
|
131
|
+
#: deciding something only the caller can. So the band, the number and the
|
|
132
|
+
#: calibration method are reported and said out loud, and whether that is
|
|
133
|
+
#: acceptable belongs to the deployment.
|
|
134
|
+
BAND_PASS, BAND_WARN = 0.999, 0.995
|
|
135
|
+
|
|
136
|
+
#: Band edges for distribution outputs (language hosts), judged on token
|
|
137
|
+
#: agreement. Cosine-grade edges do not transfer: a language model's
|
|
138
|
+
#: headline is "does it pick the same next token", and a clean static
|
|
139
|
+
#: W8A8 measured on real text sits at 0.95-0.98 agreement with the
|
|
140
|
+
#: per-seam structure gates all passing — that is the honest level of the
|
|
141
|
+
#: quantisation, not damage. Damage looks like agreement falling through
|
|
142
|
+
#: the floor while seam-level parity stays fine. So: >= 0.95 is ``pass``
|
|
143
|
+
#: (the quantisation grade measured when every structure qualifies),
|
|
144
|
+
#: >= 0.85 is ``warn``, and below that is ``low``.
|
|
145
|
+
DIST_BAND_PASS, DIST_BAND_WARN = 0.95, 0.85
|
|
146
|
+
|
|
147
|
+
#: no hard accuracy floor by default, for the reason above. Pass ``floors=``
|
|
148
|
+
#: to impose one — that is the caller stating a requirement, which is the
|
|
149
|
+
#: only place such a number can honestly come from.
|
|
150
|
+
DEFAULT_FLOORS: dict[str, dict[str, float]] = {
|
|
151
|
+
"values": {},
|
|
152
|
+
"distribution": {},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def infer_output_kind(output: Any) -> str:
|
|
157
|
+
"""``"distribution"`` if the host returns logits, else ``"values"``.
|
|
158
|
+
|
|
159
|
+
Read off the object the host actually returned rather than guessed
|
|
160
|
+
from its class name: a forward that hands back something carrying
|
|
161
|
+
``logits`` is scoring a vocabulary, whatever the model is called.
|
|
162
|
+
Callers who know better pass the kind explicitly.
|
|
163
|
+
"""
|
|
164
|
+
if output is None:
|
|
165
|
+
return "values"
|
|
166
|
+
if hasattr(output, "logits") and torch.is_tensor(
|
|
167
|
+
getattr(output, "logits")):
|
|
168
|
+
return "distribution"
|
|
169
|
+
if isinstance(output, Mapping) and torch.is_tensor(
|
|
170
|
+
output.get("logits")):
|
|
171
|
+
return "distribution"
|
|
172
|
+
return "values"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def distribution_metrics(got: torch.Tensor,
|
|
176
|
+
want: torch.Tensor) -> dict[str, float]:
|
|
177
|
+
"""Agreement metrics for logits over a vocabulary.
|
|
178
|
+
|
|
179
|
+
``top1_agreement`` is the fraction of positions choosing the same
|
|
180
|
+
token, ``last_position_cosine`` scores the position generation reads,
|
|
181
|
+
and ``kl_per_token`` is summed over the vocabulary and averaged over
|
|
182
|
+
positions — nats per token, not per batch. (``reduction="batchmean"``
|
|
183
|
+
divides by the batch dimension, which for a single sequence is one,
|
|
184
|
+
and reports the whole sequence's KL as if it were one token's.)
|
|
185
|
+
"""
|
|
186
|
+
if got.shape != want.shape:
|
|
187
|
+
raise ValueError(
|
|
188
|
+
f"output shape mismatch: impl {tuple(got.shape)} vs "
|
|
189
|
+
f"reference {tuple(want.shape)}")
|
|
190
|
+
got_f, want_f = got.double(), want.double()
|
|
191
|
+
flat_got = got_f.reshape(-1, got_f.shape[-1])
|
|
192
|
+
flat_want = want_f.reshape(-1, want_f.shape[-1])
|
|
193
|
+
top1 = (flat_got.argmax(-1) == flat_want.argmax(-1)).double().mean()
|
|
194
|
+
last = torch.nn.functional.cosine_similarity(
|
|
195
|
+
flat_got[-1], flat_want[-1], dim=0)
|
|
196
|
+
kl = torch.nn.functional.kl_div(
|
|
197
|
+
flat_got.log_softmax(-1), flat_want.log_softmax(-1),
|
|
198
|
+
log_target=True, reduction="none").sum(-1).mean()
|
|
199
|
+
return {
|
|
200
|
+
"top1_agreement": float(top1),
|
|
201
|
+
"last_position_cosine": float(last),
|
|
202
|
+
"kl_per_token": float(kl),
|
|
203
|
+
"max_abs": float((got_f - want_f).abs().max()),
|
|
204
|
+
"positions": int(flat_got.shape[0]),
|
|
205
|
+
# kept as evidence, deliberately not thresholded: this is the
|
|
206
|
+
# number whose length dependence is the reason for this function
|
|
207
|
+
"cosine_all_positions": float(torch.nn.functional.cosine_similarity(
|
|
208
|
+
got_f.flatten(), want_f.flatten(), dim=0)),
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def metrics_for(kind: str, got: torch.Tensor,
|
|
213
|
+
want: torch.Tensor) -> dict[str, float]:
|
|
214
|
+
"""Score ``got`` against ``want`` the way ``kind`` should be scored."""
|
|
215
|
+
if kind not in OUTPUT_KINDS:
|
|
216
|
+
raise ValueError(f"unknown output kind: {kind!r} "
|
|
217
|
+
f"(expected one of {OUTPUT_KINDS})")
|
|
218
|
+
if kind == "distribution":
|
|
219
|
+
return distribution_metrics(got, want)
|
|
220
|
+
return _parity_metrics(got, want)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def passes(metrics: Mapping[str, float],
|
|
224
|
+
floors: Mapping[str, float]) -> tuple[bool, str]:
|
|
225
|
+
"""Judge scored metrics against floors; report the first shortfall.
|
|
226
|
+
|
|
227
|
+
``cosine``-like names and agreement fractions are floors; anything
|
|
228
|
+
named for an error or a divergence is a ceiling. Naming the metric
|
|
229
|
+
that failed is the point — "refused" with no number attached is how a
|
|
230
|
+
refusal turns into folklore.
|
|
231
|
+
"""
|
|
232
|
+
for name, floor in floors.items():
|
|
233
|
+
if name not in metrics:
|
|
234
|
+
raise ValueError(
|
|
235
|
+
f"floor on a metric that was not measured: {name!r} "
|
|
236
|
+
f"(have {sorted(metrics)})")
|
|
237
|
+
value = metrics[name]
|
|
238
|
+
ceiling = any(tok in name for tok in ("abs", "kl", "err"))
|
|
239
|
+
if (value > floor) if ceiling else (value < floor):
|
|
240
|
+
return False, (f"{name}={value:.6f} "
|
|
241
|
+
f"{'above' if ceiling else 'below'} {floor}")
|
|
242
|
+
return True, ""
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def headline(kind: str) -> str:
|
|
246
|
+
"""The metric a band is read off for this output kind."""
|
|
247
|
+
return "top1_agreement" if kind == "distribution" else "cosine"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def band_of(metrics: Mapping[str, float], kind: str) -> str:
|
|
251
|
+
"""``pass`` / ``warn`` / ``low`` for the headline metric of ``kind``.
|
|
252
|
+
|
|
253
|
+
None of the three is a refusal. A static per-tensor scale calibrated
|
|
254
|
+
from a handful of frames gives a workload's parity, not a host's, and
|
|
255
|
+
at four-bit weights the honest number is simply lower — so the band is
|
|
256
|
+
recorded next to the calibration method and the sample count rather
|
|
257
|
+
than collapsed into a yes or no. ``low`` is the band a caller should
|
|
258
|
+
look at before deploying, not one this layer rejects for them.
|
|
259
|
+
"""
|
|
260
|
+
value = metrics.get(headline(kind))
|
|
261
|
+
if value is None:
|
|
262
|
+
return "unknown"
|
|
263
|
+
hi, lo = ((DIST_BAND_PASS, DIST_BAND_WARN)
|
|
264
|
+
if kind == "distribution" else (BAND_PASS, BAND_WARN))
|
|
265
|
+
if value >= hi:
|
|
266
|
+
return "pass"
|
|
267
|
+
return "warn" if value >= lo else "low"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def band_note(metrics: Mapping[str, float], kind: str,
|
|
271
|
+
calibration: Mapping[str, Any] | None = None) -> str:
|
|
272
|
+
"""One line a caller can act on: the band, the number, how it was got."""
|
|
273
|
+
key = headline(kind)
|
|
274
|
+
value = metrics.get(key)
|
|
275
|
+
worst = metrics.get("max_abs")
|
|
276
|
+
parts = [f"band {band_of(metrics, kind)}",
|
|
277
|
+
f"{key}={value:.6f}" if value is not None else f"{key}=n/a"]
|
|
278
|
+
if worst is not None:
|
|
279
|
+
parts.append(f"max_abs={worst:.4g}")
|
|
280
|
+
if calibration:
|
|
281
|
+
parts.append(
|
|
282
|
+
f"from {calibration.get('samples')} sample(s), "
|
|
283
|
+
f"{calibration.get('method')}")
|
|
284
|
+
return ", ".join(parts)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _spec_digest(spec: StructureSpec) -> str:
|
|
288
|
+
path = _CATALOG_DIR / spec.name / "structure.yaml"
|
|
289
|
+
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _environment() -> dict[str, str]:
|
|
293
|
+
env = {"torch": torch.__version__}
|
|
294
|
+
if torch.cuda.is_available():
|
|
295
|
+
env["device"] = torch.cuda.get_device_name(0)
|
|
296
|
+
major, minor = torch.cuda.get_device_capability(0)
|
|
297
|
+
env["arch"] = f"sm_{major}{minor}"
|
|
298
|
+
else:
|
|
299
|
+
env["device"] = "cpu"
|
|
300
|
+
return env
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def qualify_parity(
|
|
304
|
+
spec: StructureSpec,
|
|
305
|
+
impl: Callable[..., Any],
|
|
306
|
+
case: QualificationCase,
|
|
307
|
+
*,
|
|
308
|
+
impl_id: str,
|
|
309
|
+
thresholds: Mapping[str, float],
|
|
310
|
+
bound: bool = False,
|
|
311
|
+
) -> dict[str, Any]:
|
|
312
|
+
"""Judge ``impl`` against the structure reference on one workload.
|
|
313
|
+
|
|
314
|
+
``thresholds`` maps metric name to its passing bound (``cosine`` is a
|
|
315
|
+
floor, absolute-error metrics are ceilings). Every thresholded metric
|
|
316
|
+
must pass for a PASS verdict; metrics without thresholds are recorded
|
|
317
|
+
as evidence only. Set ``bound=True`` when ``impl`` was produced by an
|
|
318
|
+
implementation's ``bind`` and takes boundary inputs only.
|
|
319
|
+
"""
|
|
320
|
+
for key in case.variant:
|
|
321
|
+
if key not in spec.variants:
|
|
322
|
+
raise ValueError(f"unknown variant key: {key!r}")
|
|
323
|
+
workload = solve_dims(spec, case.inputs, case.weights)
|
|
324
|
+
reference = spec.reference()
|
|
325
|
+
|
|
326
|
+
want = _call(spec, reference, case)
|
|
327
|
+
got = _call(spec, impl, case, bound=bound)
|
|
328
|
+
metrics = _parity_metrics(got, want)
|
|
329
|
+
|
|
330
|
+
passed = True
|
|
331
|
+
for name, bound in thresholds.items():
|
|
332
|
+
if name not in metrics:
|
|
333
|
+
raise ValueError(f"threshold on unknown metric: {name!r}")
|
|
334
|
+
ok = metrics[name] >= bound if name == "cosine" else metrics[name] <= bound
|
|
335
|
+
passed = passed and ok
|
|
336
|
+
|
|
337
|
+
x_dtype = next(iter(case.inputs.values())).dtype
|
|
338
|
+
record = {
|
|
339
|
+
"structure": f"{spec.name}@{spec.version}",
|
|
340
|
+
"spec_digest": _spec_digest(spec),
|
|
341
|
+
"impl": impl_id,
|
|
342
|
+
"variant": dict(case.variant),
|
|
343
|
+
"workload": {**workload, "dtype": str(x_dtype)},
|
|
344
|
+
"env": _environment(),
|
|
345
|
+
"gate": "parity",
|
|
346
|
+
"metrics": metrics,
|
|
347
|
+
"thresholds": dict(thresholds),
|
|
348
|
+
"verdict": "PASS" if passed else "FAIL",
|
|
349
|
+
}
|
|
350
|
+
record["plan_digest"] = "sha256:" + hashlib.sha256(
|
|
351
|
+
json.dumps(
|
|
352
|
+
{k: record[k] for k in
|
|
353
|
+
("structure", "spec_digest", "impl", "variant", "workload",
|
|
354
|
+
"env", "thresholds")},
|
|
355
|
+
sort_keys=True,
|
|
356
|
+
).encode("utf-8")
|
|
357
|
+
).hexdigest()
|
|
358
|
+
return record
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def env_lock() -> dict[str, Any]:
|
|
362
|
+
"""The environment a receipt was earned in, reconstructible.
|
|
363
|
+
|
|
364
|
+
A receipt without its environment cannot be re-run: a silent torch
|
|
365
|
+
downgrade broke two GROOT receipts before this existed. The lock
|
|
366
|
+
carries the exact versions of the packages that decide numerics,
|
|
367
|
+
plus a digest over the full installed set — enough to detect any
|
|
368
|
+
drift, small enough to live in every record.
|
|
369
|
+
"""
|
|
370
|
+
import hashlib as _hashlib
|
|
371
|
+
import importlib.metadata as _md
|
|
372
|
+
import platform as _platform
|
|
373
|
+
|
|
374
|
+
key = {}
|
|
375
|
+
for pkg in ("torch", "transformers", "diffusers", "kernels",
|
|
376
|
+
"compressed-tensors", "safetensors", "numpy"):
|
|
377
|
+
try:
|
|
378
|
+
key[pkg] = _md.version(pkg)
|
|
379
|
+
except _md.PackageNotFoundError:
|
|
380
|
+
pass
|
|
381
|
+
frozen = "\n".join(sorted(
|
|
382
|
+
f"{d.metadata['Name']}=={d.version}"
|
|
383
|
+
for d in _md.distributions() if d.metadata["Name"]))
|
|
384
|
+
lock = {
|
|
385
|
+
"python": _platform.python_version(),
|
|
386
|
+
"packages": key,
|
|
387
|
+
"pip_freeze_sha256": _hashlib.sha256(
|
|
388
|
+
frozen.encode("utf-8")).hexdigest(),
|
|
389
|
+
}
|
|
390
|
+
try:
|
|
391
|
+
import torch as _torch
|
|
392
|
+
|
|
393
|
+
lock["cuda"] = _torch.version.cuda
|
|
394
|
+
if _torch.cuda.is_available():
|
|
395
|
+
lock["device"] = _torch.cuda.get_device_name(0)
|
|
396
|
+
except Exception:
|
|
397
|
+
pass
|
|
398
|
+
return lock
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def verify_record(record: Mapping[str, Any]) -> bool:
|
|
402
|
+
"""Recompute a record's digest; ``False`` means tampered or torn.
|
|
403
|
+
|
|
404
|
+
Two digest recipes exist in the wild: qualification records digest
|
|
405
|
+
a fixed key subset, probe records digest the whole record as it
|
|
406
|
+
stood before the digest (and before the env lock) was added. A
|
|
407
|
+
record verifying under either recipe is intact."""
|
|
408
|
+
stated = str(record.get("plan_digest", ""))
|
|
409
|
+
if not stated.startswith("sha256:"):
|
|
410
|
+
return False
|
|
411
|
+
body = {k: v for k, v in record.items()
|
|
412
|
+
if k not in ("plan_digest", "env_lock")}
|
|
413
|
+
whole = "sha256:" + hashlib.sha256(
|
|
414
|
+
json.dumps(body, sort_keys=True).encode("utf-8")).hexdigest()
|
|
415
|
+
if whole == stated:
|
|
416
|
+
return True
|
|
417
|
+
subset_keys = ("structure", "spec_digest", "impl", "variant",
|
|
418
|
+
"workload", "env", "thresholds")
|
|
419
|
+
if all(k in record for k in subset_keys):
|
|
420
|
+
subset = "sha256:" + hashlib.sha256(
|
|
421
|
+
json.dumps({k: record[k] for k in subset_keys},
|
|
422
|
+
sort_keys=True).encode("utf-8")).hexdigest()
|
|
423
|
+
if subset == stated:
|
|
424
|
+
return True
|
|
425
|
+
return False
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def check_env(record: Mapping[str, Any]) -> list[str]:
|
|
429
|
+
"""Name every way the current environment drifts from a receipt's.
|
|
430
|
+
|
|
431
|
+
Empty list = re-runnable as-is. A receipt without a lock is itself
|
|
432
|
+
a finding."""
|
|
433
|
+
lock = record.get("env_lock")
|
|
434
|
+
if not lock:
|
|
435
|
+
return ["record carries no env_lock"]
|
|
436
|
+
now = env_lock()
|
|
437
|
+
drift = []
|
|
438
|
+
for pkg, ver in (lock.get("packages") or {}).items():
|
|
439
|
+
cur = now["packages"].get(pkg)
|
|
440
|
+
if cur != ver:
|
|
441
|
+
drift.append(f"{pkg}: receipt {ver}, current {cur}")
|
|
442
|
+
if lock.get("python") != now["python"]:
|
|
443
|
+
drift.append(f"python: receipt {lock.get('python')}, "
|
|
444
|
+
f"current {now['python']}")
|
|
445
|
+
if (lock.get("pip_freeze_sha256") != now["pip_freeze_sha256"]
|
|
446
|
+
and not drift):
|
|
447
|
+
drift.append("installed set differs (freeze digest mismatch)")
|
|
448
|
+
return drift
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def save_record(record: Mapping[str, Any], directory: str | pathlib.Path) -> pathlib.Path:
|
|
452
|
+
"""Write one qualification record as JSON, named by its plan digest.
|
|
453
|
+
|
|
454
|
+
Every record is stamped with the environment lock unless the caller
|
|
455
|
+
already supplied one."""
|
|
456
|
+
if "env_lock" not in record:
|
|
457
|
+
record = {**record, "env_lock": env_lock()}
|
|
458
|
+
directory = pathlib.Path(directory)
|
|
459
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
460
|
+
digest = record["plan_digest"].split(":", 1)[1][:16]
|
|
461
|
+
path = directory / f"{record['gate']}_{digest}.json"
|
|
462
|
+
with open(path, "w", encoding="utf-8") as handle:
|
|
463
|
+
json.dump(record, handle, indent=2, sort_keys=True)
|
|
464
|
+
handle.write("\n")
|
|
465
|
+
return path
|