flashrt-structures 0.2.0__py3-none-any.whl

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