Skip to content

Track B: tiered fidelity contracts - #18

Merged
aryan5v merged 3 commits into
mainfrom
tiered-fidelity-contracts
Aug 2, 2026
Merged

Track B: tiered fidelity contracts#18
aryan5v merged 3 commits into
mainfrom
tiered-fidelity-contracts

Conversation

@aryan5v

@aryan5v aryan5v commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Track B of the roadmap. Unblocks Tracks A (attention backends) and C (caching), neither of which can promote under a byte_equal-only world.

The problem

parity.policy contracts the numeric axis. For a fused kernel meant as a drop-in replacement, byte_equal is correct. For an alternative attention backend (different accumulation order) or a cross-step cache (skips recomputing a step outright), it is a wall: both are rejected before they are ever benchmarked.

Widening the tolerance until they fit is the R4 failure exactly — four VAE artifacts on rcp.approx.ftz.f32 reached packaging carrying up to 131072.0 of absolute error at match=true. A tolerance wide enough to admit a cache hit is wider still.

So this doesn't widen the numeric axis. It adds a second one.

The tiers

tier name contract who lives here
1 exact bitwise identity, via the parity policy fused kernels, CUDA-graph replay
2 perceptual frames perceptually indistinguishable at fixed seed attention backends, schedule transforms
3 advisory measured and recorded, never gating discovery campaigns

A workload declaring nothing gets tier 1. Tier 3 never auto-promotes.

The one subtle bit

At tier 2 the fidelity verdict replaces the parity check in GenerationOutcome.decide() rather than stacking on it. A tier-2 candidate fails byte_equal by construction; gating on parity first would quarantine every attention and caching candidate before its evidence was read.

Tier 1 behaviour is unchanged down to the wording of the quarantine reason — test_tier_1_behaviour_is_unchanged_by_the_tier_machinery pins it.

Refusing to guess

Thresholds are per-workload and never invented by the harness (the rule resolve_leaf_tolerance already enforces; a 1.3B at 480p and a 14B at 720p don't share a perceptual noise floor). Beyond that:

  • a perceptual budget with no threshold or no frame set is rejected at construction — a tier with no bar is no contract;
  • missing evidence holds the artifact;
  • evidence from a different frame set or seed is refused, not discounted — it answers a different question;
  • a metric with no backend installed is absent, and holds any budget gating on it. Substituting a default would rebuild the R4 hole with a new number in it.

Promotions record the signed margin per metric (positive always = passing, whichever way the metric runs), so a perceptual promotion is auditable from the manifest alone.

Harness

Aggregation is worst-frame, not mean — a cache perfect on 7 frames and destroying the 8th averages to a comfortable pass, and one broken frame is exactly what a schedule transform produces.

  • SSIM computed directly from numpy (Wang et al. 2004, 11×11 Gaussian, σ=1.5) so tier 2 has no optional dependency in its common path. Cross-checked against scikit-image to ~1e-16.
  • LPIPS via pluggable backend; absent when uninstalled.
  • VBench score passed in from its isolated stage — nothing on the promotion path imports its model zoo.

Exit criteria

Both covered as tests:

  • test_intentionally_lossy_control_is_rejected_at_tier_2 — the control is fast (1.21×), cleanly dispatched, classified improved. Everything the speed gate looks at says promote. It is quarantined on SSIM/LPIPS alone.
  • test_known_good_approximate_artifact_passes_with_recorded_margins — a bit-inexact but perceptually clean artifact promotes, margins in the manifest.

Testing

Verified on real GPUs (the earlier laptop run could not execute anything that
imports torch). Same suite, three configurations:

commit environment passed failed
2b1da46 (main, baseline) Modal H100, sm90 770 2
30e9bc9 (this branch) Modal H100, sm90 805 2
30e9bc9 (this branch) SLURM GB200, sm100 805 2

805 − 770 = 35, exactly the tests added here. The two failures
(test_fx_region_capture[symbolic], test_gpu_smoke[matmul]) reproduce
identically at the merge-base and on both architectures, so they are
pre-existing on main and unrelated to this change — established by running
the baseline rather than by arguing the change could not have caused them.

sm100 is included because it is the arch the promoted artifacts actually
target; Modal only offers sm90, so an H100-only result would not have covered
it.

modal/verify_tiered_fidelity.py carries the harness, including the
baseline_suite entrypoint used for the comparison above.

Still not exercised: the adapters.py wiring that carries a budget from a
workload manifest to the gate has not been run against a real campaign — that
needs a tier-2 workload and a full generation, which is Track A/C work.

Note on Track D

While checking premises I found two that no longer hold, both documented in docs/LTX_V1_R4_ROOT_CAUSE.md §7–9:

  • The 3.1ms dispatch tax is already fixed — FX replay was replaced with torch.cuda.CUDAGraph capture (chosen over a compiler backend because a graph replay is bitwise-identical by construction). Gate 5 passes at 1.2514× median, 15 runs/arm, SLURM 999.
  • The four quarantined VAE artifacts will not flip. §4 does the arithmetic: 6.20% of e2e at ~1.11× each → 1.0064× maximum at zero dispatch overhead, against a 1.01× target. They also break byte_equal deterministically. Both disqualifiers are independent of dispatch cost.

Summary by CodeRabbit

  • New Features

    • Added tiered fidelity contracts: exact, perceptual, and advisory.
    • Added perceptual validation using frame comparisons and quality metrics such as SSIM, LPIPS, and VBench.
    • Workload manifests can now define fidelity thresholds, frame sets, and seeds.
    • Generation results record fidelity budgets, verdicts, measurements, and promotion decisions.
  • Documentation

    • Added guidance for configuring and validating tiered fidelity requirements.

`parity.policy` contracts the numeric axis, and for a fused kernel meant as a
drop-in replacement `byte_equal` is the right answer. It is the wrong answer
for the two families this project is moving toward: an alternative attention
backend changes the accumulation order of the attention product, and a
cross-step cache skips recomputing a step entirely. Neither is bit-exact, and
under `byte_equal` both are rejected before they are ever benchmarked.

The tempting fix is to widen the tolerance until they fit. That is the R4
failure exactly -- four VAE artifacts built on `rcp.approx.ftz.f32` reached
packaging carrying up to 131072.0 of absolute error at `match=true`, because a
tolerance wide enough to admit them was wide enough to admit anything. A
tolerance wide enough to admit a cache hit is wider still.

So the numeric axis is not widened. A second axis is added:

  tier 1 `exact`      bitwise identity, decided by the parity policy
  tier 2 `perceptual` frames perceptually indistinguishable at a fixed seed
  tier 3 `advisory`   measured and recorded, never gating, never auto-promoted

A workload declaring nothing gets tier 1 -- failing closed is the point.

At tier 2 the fidelity verdict *replaces* the parity check in
GenerationOutcome.decide(), because a tier-2 candidate fails `byte_equal` by
construction and gating on parity first would quarantine every attention and
caching candidate before its evidence was read. Tier 1 behaviour is unchanged
down to the wording of the quarantine reason.

Thresholds are declared per workload and never invented by the harness, the
same rule resolve_leaf_tolerance already enforces: a 1.3B model at 480p and a
14B at 720p do not share a perceptual noise floor. A `perceptual` budget with
no threshold or no frame set is rejected at construction -- a tier with no bar
is not a weaker contract, it is no contract.

Evidence handling refuses to guess. Missing evidence holds the artifact;
evidence from a different frame set or seed is refused rather than discounted,
being evidence about a different question; a metric with no backend installed
is reported absent and holds any budget gating on it. Promotions record the
signed margin for every gated metric, positive always meaning passing, so a
perceptual promotion can be audited from the manifest alone.

The harness aggregates worst-frame rather than mean: a cache that is perfect on
seven frames and destroys the eighth averages to a comfortable pass, and a
single broken frame is precisely what a schedule transform produces. SSIM is
computed directly from numpy so tier 2 has no optional dependency in its common
path, and is cross-checked against scikit-image to ~1e-16. LPIPS loads through
a pluggable backend; VBench's score is passed in from its isolated stage so
nothing on the promotion path imports its model zoo.

Exit criteria are covered by tests: an intentionally-lossy control artifact --
fast, cleanly dispatched, classified `improved` -- is quarantined at tier 2 on
its SSIM/LPIPS alone, and a bit-inexact but perceptually clean artifact is
promoted with its margins recorded.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@aryan5v, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ae44275-15fc-41f8-a9fe-78d13a7e1abe

📥 Commits

Reviewing files that changed from the base of the PR and between ebc1590 and 39d1355.

📒 Files selected for processing (1)
  • tests/test_fx_region_capture.py
📝 Walkthrough

Walkthrough

The change adds tiered fidelity contracts, perceptual frame comparison, workload manifest configuration, fidelity-aware artifact promotion, persisted evidence, automated tests, and Modal verification commands.

Changes

Tiered fidelity verification

Layer / File(s) Summary
Fidelity contracts and perceptual measurement
autokernel/verification/fidelity.py, autokernel/verification/perceptual.py, autokernel/verification/__init__.py
Adds exact, perceptual, and advisory contracts; validates budgets and evidence; evaluates thresholds; compares frame sets with SSIM, optional LPIPS, and supplied VBench scores; exports the new APIs.
Workload fidelity configuration
autokernel/workload/types.py, autokernel/workload/__init__.py, docs/TIERED_FIDELITY.md
Adds FidelitySpec and optional manifest fidelity configuration. Parses and serializes tiers, thresholds, frame sets, and seeds. Documents the contract and measurement rules.
Artifact fidelity gating and evidence
autokernel/optimize/adapters.py, autokernel/artifact/finalizer.py, autokernel/artifact/types.py
Passes workload budgets and perceptual evidence through finalization. Uses fidelity verdicts for promotion decisions and persists non-exact budget, verdict, and measurement data in generation evidence.
Automated fidelity validation
tests/test_fidelity_tiers.py, tests/test_perceptual_harness.py, modal/verify_tiered_fidelity.py
Adds coverage for tier semantics, evidence validation, metric aggregation, manifest round-tripping, promotion behavior, and exact-tier compatibility. Adds Modal commands for tier-gate, baseline, and full-suite runs.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WorkloadManifest
  participant FinalizationAdapter
  participant GenerationOutcome
  participant FidelityEvaluator
  participant ArtifactEvidence
  WorkloadManifest->>FinalizationAdapter: provide fidelity configuration
  FinalizationAdapter->>GenerationOutcome: pass budget and perceptual evidence
  GenerationOutcome->>FidelityEvaluator: evaluate fidelity contract
  FidelityEvaluator-->>GenerationOutcome: return verdict and metric margins
  GenerationOutcome->>ArtifactEvidence: record non-exact fidelity evidence
Loading

Possibly related PRs

  • aryan5v/motionkernel#7: Shares workload manifest and workload export changes extended here with fidelity specifications.
  • aryan5v/motionkernel#11: Shares GenerationEvidence changes extended here with tiered fidelity evidence.
  • aryan5v/motionkernel#17: Shares artifact finalization, generation evidence, workload manifests, and verification exports extended here with fidelity gating.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding tiered fidelity contracts.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tiered-fidelity-contracts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The tier machinery is deliberately CPU-only -- the gate must be decidable
without a GPU, an image backend, or a pretrained network. That property meant
the local run could not execute the parts of the suite that hard-import torch,
so 21 tests were failing purely on torch's absence and 5 modules could not be
collected. Those tests are not about fidelity tiers, but they are exactly the
ones that would catch a tier change breaking the CLI or the built-in specs.

`baseline_suite` runs the same suite at the merge-base so a failure can be
shown pre-existing rather than argued to be. That distinction is the whole
point: reasoning about whether a change "could plausibly" have caused a
failure is not evidence, and this repository has already paid once for
accepting a conclusion that was never measured.
@aryan5v

aryan5v commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

test_unknown_aliasing_fails_closed_in_every_mode[symbolic] fails on main, not
because of anything in this branch: the fixture calls
torch.as_strided(x, x.shape, x.stride()), and from torch 2.8 symbolic tracing
hands as_strided Proxy objects where it requires concrete ints. The trace
raises TypeError before any region exists, so the aliasing detector never runs
and the old assertion -- region is not None -- cannot hold.

Not capturing at all is strictly safer than capturing an aliasing-unsafe
module, so that outcome is now accepted, but only for the mode that genuinely
cannot get further, and only when capture recorded why it failed. An empty
result with no recorded failure would look identical to a silent no-op and is
still an error.

The detector stays under the original strict assertion for export and dynamo,
which is where its coverage actually lives. This narrows where the assertion
applies to match where the tracer can reach; it does not lower the bar for any
mode that can.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (8)
autokernel/verification/perceptual.py (3)

190-218: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Cache the LPIPS network, and replace the assigned lambda.

compare_frame_sets calls lpips_backend() on every invocation (Line 287). Each call constructs lpips_module.LPIPS(net="alex") and loads pretrained AlexNet weights. A campaign that compares many artifacts pays that load repeatedly. Cache the backend so the weights load once per process.

Ruff also flags Line 214 (E731): assign a def rather than a lambda.

⚡ Proposed change
+import functools
+
+
+@functools.lru_cache(maxsize=1)
 def lpips_backend() -> Callable[[Any, Any], float]:
         # LPIPS wants NCHW in [-1, 1].
-        to_nchw = lambda a: torch.from_numpy(a).permute(0, 3, 1, 2).float() * 2 - 1
+        def to_nchw(a: Any) -> Any:
+            return torch.from_numpy(a).permute(0, 3, 1, 2).float() * 2 - 1
+
         with torch.no_grad():
             return float(network(to_nchw(ref), to_nchw(cand)).item())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/perceptual.py` around lines 190 - 218, Update
lpips_backend to cache the initialized LPIPS network/backend for reuse across
calls, ensuring pretrained AlexNet weights are loaded only once per process
while preserving MetricUnavailable behavior. Replace the to_nchw lambda inside
_score with a local def and keep the existing tensor conversion and scoring
behavior unchanged.

Source: Linters/SAST tools


282-294: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

An LPIPS scorer failure escapes as an unhandled exception.

Lines 291-294 call scorer without error handling. The docstring at Lines 258-261 declares only PerceptualError. A backend failure inside torch, for example an out-of-memory error during inference, therefore propagates out of compare_frame_sets unchanged.

The downstream adapter catches only FidelityError and TypeError (autokernel/optimize/adapters.py Lines 78-102), so such an error aborts the campaign after the GPU time is already spent. Treating a failed measurement as absent keeps the documented behavior: the budget gating on LPIPS then holds the artifact.

♻️ Proposed change
         if scorer is not None:
-            worst_lpips = max(
-                scorer(ref_frames[index], cand_frames[index])
-                for index in range(count)
-            )
+            try:
+                worst_lpips = max(
+                    scorer(ref_frames[index], cand_frames[index])
+                    for index in range(count)
+                )
+            except PerceptualError:
+                raise
+            except Exception:
+                # A backend that fails mid-measurement yields no number. The
+                # metric is absent, never substituted, so a budget gating on
+                # LPIPS holds the artifact.
+                worst_lpips = None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/perceptual.py` around lines 282 - 294, Update the
LPIPS scoring path in compare_frame_sets so failures while invoking scorer or
aggregating its results are caught and treated as an absent measurement by
leaving worst_lpips as None. Preserve the existing MetricUnavailable handling
and ensure only the documented PerceptualError contract escapes from this
function.

136-152: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use the separable form of the Gaussian window.

_gaussian_window builds the 11×11 kernel as an outer product of a 1-D kernel, so the filter is separable. _filter2d then applies it as a dense 2-D correlation at O(k²) = 121 multiply-adds per output pixel.

ssim calls _filter2d six times per channel, and compare_frame_sets calls ssim once per frame. For an 8-frame 720p RGB set that is 144 passes over roughly 0.9M pixels each, near 1.6e10 multiply-adds in NumPy. Two 1-D passes reduce this to 22 multiply-adds per pixel, about 5.5× less work, with identical results.

⚡ Proposed change
-def _gaussian_window(np: Any) -> Any:
+def _gaussian_window_1d(np: Any) -> Any:
     coords = np.arange(_WINDOW_SIZE, dtype=np.float64) - (_WINDOW_SIZE - 1) / 2.0
     kernel = np.exp(-(coords**2) / (2.0 * _WINDOW_SIGMA**2))
     kernel /= kernel.sum()
-    return np.outer(kernel, kernel)
+    return kernel
 
 
 def _filter2d(np: Any, plane: Any, window: Any) -> Any:
-    """Valid-mode 2-D correlation, written with strides to avoid scipy."""
+    """Valid-mode 2-D correlation of a separable Gaussian, without scipy."""
     height, width = plane.shape
-    k = window.shape[0]
+    k = _WINDOW_SIZE
     if height < k or width < k:
         raise PerceptualError(
             f"frames are {height}x{width}, smaller than the {k}x{k} SSIM window"
         )
-    windows = np.lib.stride_tricks.sliding_window_view(plane, (k, k))
-    return np.einsum("ijkl,kl->ij", windows, window)
+    rows = np.lib.stride_tricks.sliding_window_view(plane, k, axis=1)
+    horizontal = rows @ window
+    columns = np.lib.stride_tricks.sliding_window_view(horizontal, k, axis=0)
+    return columns @ window

ssim then passes the 1-D kernel from _gaussian_window_1d.

Verify the numeric equivalence against the scikit-image cross-check the documentation mentions in docs/TIERED_FIDELITY.md Line 116.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/perceptual.py` around lines 136 - 152, Replace the
dense 2-D Gaussian path in _gaussian_window and _filter2d with separable
filtering using a normalized 1-D kernel from _gaussian_window_1d, applying
horizontal and vertical correlations while preserving valid-mode dimensions and
size validation. Update ssim to pass the 1-D kernel through this path, and
verify numerical equivalence against the documented scikit-image cross-check.
autokernel/verification/fidelity.py (1)

321-342: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer the workload spec's own budget() accessor.

WorkloadManifest.fidelity holds a FidelitySpec (autokernel/workload/types.py Line 746), which is neither a FidelityBudget nor a Mapping. The call therefore lands in the duck-typed branch at Lines 335-342 and rebuilds the budget field by field. FidelitySpec.budget() (autokernel/workload/types.py Lines 628-639) already returns the validated budget. If a field is added to FidelitySpec later, the duck-typed branch drops it silently.

♻️ Proposed change
         if isinstance(fidelity, Mapping):
             return cls.from_dict(fidelity)
+        to_budget = getattr(fidelity, "budget", None)
+        if callable(to_budget):
+            budget = to_budget()
+            if isinstance(budget, FidelityBudget):
+                return budget
         return cls(
             tier=str(getattr(fidelity, "tier", EXACT)),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/fidelity.py` around lines 321 - 342, Update
FidelityBudget.from_workload to detect the workload’s FidelitySpec and return
its validated budget via the spec’s budget() accessor, before the field-by-field
fallback branch. Preserve existing handling for FidelityBudget, mappings, and
workloads without fidelity.
autokernel/workload/types.py (2)

83-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

_FIDELITY_FIELDS duplicates the field set in FidelityBudget.from_dict.

autokernel/verification/fidelity.py Lines 300-307 declares the same six field names inline. The FidelitySpec docstring at Lines 590-592 states that validation lives in FidelityBudget so the contract and its gate cannot drift apart, but this field list drifts independently of it.

Adding a field to one location and not the other makes one layer reject what the other accepts. Export the set from fidelity.py and reuse it here.

♻️ Proposed direction

In autokernel/verification/fidelity.py, name the set and use it in from_dict:

#: Fields a declared fidelity budget may carry. The workload manifest layer
#: reuses this so the two cannot drift apart.
BUDGET_FIELDS = frozenset(
    {"tier", "min_ssim", "max_lpips", "min_vbench", "frame_set", "seed"}
)

Then in this file:

-_FIDELITY_FIELDS = {
-    "tier",
-    "min_ssim",
-    "max_lpips",
-    "min_vbench",
-    "frame_set",
-    "seed",
-}
+# Imported lazily inside FidelitySpec.from_dict to match the existing
+# deferred-import pattern and avoid a package import cycle.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/workload/types.py` around lines 83 - 90, Export a shared
`BUDGET_FIELDS` set from `FidelityBudget.from_dict`’s module in
`autokernel/verification/fidelity.py`, and update `FidelityBudget.from_dict` to
use it for validation. Replace the local `_FIDELITY_FIELDS` definition in the
workload types module with an import and reuse `BUDGET_FIELDS`, preserving the
existing field names and validation behavior.

628-631: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore the real return type on budget().

The annotation is "Any", but the docstring states the method returns a FidelityBudget. Callers and type checkers lose the type. The deferred import is the cause, and TYPE_CHECKING resolves it without a runtime import.

♻️ Proposed change

Add at module scope:

if TYPE_CHECKING:
    from ..verification.fidelity import FidelityBudget

Then:

-    def budget(self) -> "Any":
+    def budget(self) -> "FidelityBudget":
         """Return the validated :class:`FidelityBudget` this spec describes."""
         from ..verification.fidelity import FidelityBudget
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/workload/types.py` around lines 628 - 631, Update the budget
method’s return annotation from "Any" to "FidelityBudget", and add a
module-scope TYPE_CHECKING import for FidelityBudget so typing resolves without
introducing a runtime import. Preserve the existing runtime deferred import and
validation behavior in budget.
autokernel/verification/__init__.py (1)

45-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document or re-export the perceptual backend APIs.

autokernel/verification/__init__.py re-exports FrameSet, MetricUnavailable, PerceptualError, compare_frame_sets, and ssim from perceptual.py, but perceptual.py also exports lpips_backend and vbench_backend without package-level re-exports. If callers are expected to use those backend factories, add them to the package surface; otherwise remove them from autokernel/verification/perceptual.py’s __all__.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/__init__.py` around lines 45 - 65, Align the package
API for the backend factories: either re-export lpips_backend and vbench_backend
from autokernel.verification alongside the existing perceptual symbols, or
remove them from perceptual.py’s __all__ if they are not intended for callers.
Keep the chosen public API consistent between perceptual.py and the package
initializer.
modal/verify_tiered_fidelity.py (1)

144-155: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

tier_gates_only requests a GPU for CPU-only tests.

This function runs only tests/test_fidelity_tiers.py and tests/test_perceptual_harness.py. Both files' own headers state they are "Numpy only -- no torch" and "never import torch." Requesting gpu="H100!" for this run adds scheduling latency and cost without benefit, and works against the function's own stated purpose: "Faster feedback loop while iterating on the contract itself."

Drop the GPU requirement for this function.

♻️ Proposed fix
-@app.function(image=image, gpu="H100!", timeout=15 * 60, scaledown_window=60)
+@app.function(image=image, timeout=15 * 60, scaledown_window=60)
 def tier_gates_only() -> str:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modal/verify_tiered_fidelity.py` around lines 144 - 155, Remove the GPU
requirement from the tier_gates_only function decorator while preserving its
timeout, scaling, test selection, and return behavior; this CPU-only test path
should no longer request H100 resources.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@autokernel/optimize/adapters.py`:
- Around line 96-100: Update the PerceptualEvidence construction in the
measurement parsing flow to fail closed when raw lacks stage_status: require the
key explicitly or use the existing non-success/held status value, never default
to "ok". Preserve successful handling when a valid stage_status is present and
ensure missing evidence remains held without crashing the campaign.

In `@autokernel/verification/fidelity.py`:
- Around line 118-132: Update _check_threshold to accept a subject label and use
it in validation error messages instead of always saying “fidelity threshold.”
Pass the existing threshold subject from threshold callers, and call it with
subject="perceptual evidence" from PerceptualEvidence.__post_init__ so malformed
measurements identify the harness output.
- Around line 527-564: Add an optional expected frame-count field to
FidelityBudget and FidelitySpec, and include it in _FIDELITY_FIELDS so
configuration can declare the contract. In the fidelity verification flow
alongside the frame_set and seed checks, reject evidence when the declared count
is set and evidence.frames_compared differs, returning a held FidelityVerdict
with evidence_required=True. Preserve existing behavior when no frame count is
declared.

In `@autokernel/verification/perceptual.py`:
- Around line 162-168: Update the public ssim input handling to call
_as_float_array on reference and candidate directly before adding the leading
axis, rather than applying [None, ...] to the raw arguments. Preserve the
existing labels and subsequent shape validation while ensuring list and tuple
inputs raise PerceptualError through the conversion path.
- Around line 263-275: Update compare_frame_sets to validate that reference.name
and candidate.name match, alongside the existing seed and shape checks. Raise
PerceptualError with both names when they differ, before producing comparison
evidence so evaluate_fidelity cannot accept a substituted frame set.
- Around line 121-133: Update _as_float_array to enforce its documented float64
[0, 1] output for both integer and floating-point inputs. Do not silently scale
floating-point values or normalize signed integers using only iinfo.max;
instead, validate the converted range and raise PerceptualError with the
existing label when any finite value falls outside [0, 1], while preserving the
non-finite check.
- Around line 296-303: Clamp worst_ssim to the documented valid range of [-1.0,
1.0] immediately before constructing PerceptualEvidence, preserving the computed
value when it is already in range and preventing tiny floating-point overshoots
from reaching __post_init__._check_threshold().

In `@docs/TIERED_FIDELITY.md`:
- Around line 93-101: Update the evidence object example in TIERED_FIDELITY.md
to include the stage_status field emitted by PerceptualEvidence.as_dict,
preserving the existing frame_set, seed, and frames_compared fields.

---

Nitpick comments:
In `@autokernel/verification/__init__.py`:
- Around line 45-65: Align the package API for the backend factories: either
re-export lpips_backend and vbench_backend from autokernel.verification
alongside the existing perceptual symbols, or remove them from perceptual.py’s
__all__ if they are not intended for callers. Keep the chosen public API
consistent between perceptual.py and the package initializer.

In `@autokernel/verification/fidelity.py`:
- Around line 321-342: Update FidelityBudget.from_workload to detect the
workload’s FidelitySpec and return its validated budget via the spec’s budget()
accessor, before the field-by-field fallback branch. Preserve existing handling
for FidelityBudget, mappings, and workloads without fidelity.

In `@autokernel/verification/perceptual.py`:
- Around line 190-218: Update lpips_backend to cache the initialized LPIPS
network/backend for reuse across calls, ensuring pretrained AlexNet weights are
loaded only once per process while preserving MetricUnavailable behavior.
Replace the to_nchw lambda inside _score with a local def and keep the existing
tensor conversion and scoring behavior unchanged.
- Around line 282-294: Update the LPIPS scoring path in compare_frame_sets so
failures while invoking scorer or aggregating its results are caught and treated
as an absent measurement by leaving worst_lpips as None. Preserve the existing
MetricUnavailable handling and ensure only the documented PerceptualError
contract escapes from this function.
- Around line 136-152: Replace the dense 2-D Gaussian path in _gaussian_window
and _filter2d with separable filtering using a normalized 1-D kernel from
_gaussian_window_1d, applying horizontal and vertical correlations while
preserving valid-mode dimensions and size validation. Update ssim to pass the
1-D kernel through this path, and verify numerical equivalence against the
documented scikit-image cross-check.

In `@autokernel/workload/types.py`:
- Around line 83-90: Export a shared `BUDGET_FIELDS` set from
`FidelityBudget.from_dict`’s module in `autokernel/verification/fidelity.py`,
and update `FidelityBudget.from_dict` to use it for validation. Replace the
local `_FIDELITY_FIELDS` definition in the workload types module with an import
and reuse `BUDGET_FIELDS`, preserving the existing field names and validation
behavior.
- Around line 628-631: Update the budget method’s return annotation from "Any"
to "FidelityBudget", and add a module-scope TYPE_CHECKING import for
FidelityBudget so typing resolves without introducing a runtime import. Preserve
the existing runtime deferred import and validation behavior in budget.

In `@modal/verify_tiered_fidelity.py`:
- Around line 144-155: Remove the GPU requirement from the tier_gates_only
function decorator while preserving its timeout, scaling, test selection, and
return behavior; this CPU-only test path should no longer request H100
resources.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 67c3d1a0-c0cc-41bc-af79-bcc76233ab90

📥 Commits

Reviewing files that changed from the base of the PR and between 2b1da46 and ebc1590.

📒 Files selected for processing (12)
  • autokernel/artifact/finalizer.py
  • autokernel/artifact/types.py
  • autokernel/optimize/adapters.py
  • autokernel/verification/__init__.py
  • autokernel/verification/fidelity.py
  • autokernel/verification/perceptual.py
  • autokernel/workload/__init__.py
  • autokernel/workload/types.py
  • docs/TIERED_FIDELITY.md
  • modal/verify_tiered_fidelity.py
  • tests/test_fidelity_tiers.py
  • tests/test_perceptual_harness.py

Comment on lines +96 to +100
ssim=raw.get("ssim"),
lpips=raw.get("lpips"),
vbench=raw.get("vbench"),
stage_status=str(raw.get("stage_status", "ok")),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

stage_status defaults to "ok" instead of failing closed.

Every other field in this constructor call fails closed when absent: frame_set defaults to "", which PerceptualEvidence.__post_init__ rejects as non-empty-required, and seed/frames_compared default to None, which fails the integer check. stage_status, however, defaults to "ok" on Line 99. If a measurement producer omits this key (a schema mismatch or a partial write), the evidence is treated as a completed, successful measurement instead of being held.

This contradicts the function's own docstring: "a broken measurement must hold the artifact, not crash the campaign." A missing stage_status is exactly the "missing evidence" case that the rest of this PR is built to fail closed on.

Default to a value that keeps the gate closed, or require the key explicitly.

🛡️ Proposed fix
             lpips=raw.get("lpips"),
             vbench=raw.get("vbench"),
-            stage_status=str(raw.get("stage_status", "ok")),
+            stage_status=str(raw.get("stage_status", "failed")),
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ssim=raw.get("ssim"),
lpips=raw.get("lpips"),
vbench=raw.get("vbench"),
stage_status=str(raw.get("stage_status", "ok")),
)
ssim=raw.get("ssim"),
lpips=raw.get("lpips"),
vbench=raw.get("vbench"),
stage_status=str(raw.get("stage_status", "failed")),
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/optimize/adapters.py` around lines 96 - 100, Update the
PerceptualEvidence construction in the measurement parsing flow to fail closed
when raw lacks stage_status: require the key explicitly or use the existing
non-success/held status value, never default to "ok". Preserve successful
handling when a valid stage_status is present and ensure missing evidence
remains held without crashing the campaign.

Comment on lines +118 to +132
def _check_threshold(name: str, value: Any) -> float | None:
if value is None:
return None
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise FidelityError(f"fidelity threshold {name} must be a number, got {value!r}")
number = float(value)
if not math.isfinite(number):
raise FidelityError(f"fidelity threshold {name} must be finite, got {value!r}")
low, high = _METRIC_RANGES[name]
if not low <= number <= high:
raise FidelityError(
f"fidelity threshold {name}={number!r} is outside the metric's "
f"range [{low}, {high}]"
)
return number

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the validation message reflect the caller.

_check_threshold also validates measured values in PerceptualEvidence.__post_init__ (Line 386). A malformed measurement then reports "fidelity threshold ssim must be a number", which points the reader at the workload contract instead of the harness output. Pass a label so each caller names its own subject.

♻️ Proposed change
-def _check_threshold(name: str, value: Any) -> float | None:
+def _check_threshold(name: str, value: Any, *, subject: str = "fidelity threshold") -> float | None:
     if value is None:
         return None
     if isinstance(value, bool) or not isinstance(value, (int, float)):
-        raise FidelityError(f"fidelity threshold {name} must be a number, got {value!r}")
+        raise FidelityError(f"{subject} {name} must be a number, got {value!r}")
     number = float(value)
     if not math.isfinite(number):
-        raise FidelityError(f"fidelity threshold {name} must be finite, got {value!r}")
+        raise FidelityError(f"{subject} {name} must be finite, got {value!r}")
     low, high = _METRIC_RANGES[name]
     if not low <= number <= high:
         raise FidelityError(
-            f"fidelity threshold {name}={number!r} is outside the metric's "
+            f"{subject} {name}={number!r} is outside the metric's "
             f"range [{low}, {high}]"
         )
     return number

Then call it with subject="perceptual evidence" from PerceptualEvidence.__post_init__.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/fidelity.py` around lines 118 - 132, Update
_check_threshold to accept a subject label and use it in validation error
messages instead of always saying “fidelity threshold.” Pass the existing
threshold subject from threshold callers, and call it with subject="perceptual
evidence" from PerceptualEvidence.__post_init__ so malformed measurements
identify the harness output.

Comment on lines +527 to +564
if budget.frame_set and evidence.frame_set != budget.frame_set:
return FidelityVerdict(
tier=budget.tier,
passed=False,
reason=(
"held: perceptual evidence was measured on frame set "
f"{evidence.frame_set!r}, but the budget contracts "
f"{budget.frame_set!r}"
),
evidence_required=True,
)
if budget.seed is not None and evidence.seed != budget.seed:
return FidelityVerdict(
tier=budget.tier,
passed=False,
reason=(
f"held: perceptual evidence was measured at seed {evidence.seed}, "
f"but the budget contracts seed {budget.seed}"
),
evidence_required=True,
)

missing = [
metric
for metric in budget.declared_metrics
if evidence.value_for(metric) is None
]
if missing:
return FidelityVerdict(
tier=budget.tier,
passed=False,
reason=(
"held: the budget gates on "
f"{', '.join(missing)} but the harness measured "
f"{', '.join(evidence.available_metrics) or 'nothing'}"
),
evidence_required=True,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Tier 2 verifies frame-set identity but not frame count.

The gate checks frame_set and seed, then checks that every declared metric has a value. It never checks evidence.frames_compared. FidelityBudget declares no expected frame count, so evidence measured over one frame satisfies a budget contracted on wan-1.3b-fixed-seed-8.

This matters because evidence does not always come from compare_frame_sets. autokernel/optimize/adapters.py Lines 78-102 builds PerceptualEvidence directly from a validation-result mapping and reads frames_compared verbatim. The count-mismatch guard in compare_frame_sets (autokernel/verification/perceptual.py Lines 271-275) does not cover that path, and it only compares candidate against reference, not against the contracted frame set.

Add a declared frame count to the budget and enforce it here, in the same way frame_set and seed are enforced.

🛡️ Proposed direction
     if budget.seed is not None and evidence.seed != budget.seed:
         ...
+    if budget.frames is not None and evidence.frames_compared != budget.frames:
+        return FidelityVerdict(
+            tier=budget.tier,
+            passed=False,
+            reason=(
+                f"held: perceptual evidence covers {evidence.frames_compared} "
+                f"frames, but the budget contracts {budget.frames}"
+            ),
+            evidence_required=True,
+        )

This requires a new frames: int | None field on FidelityBudget, in _FIDELITY_FIELDS (autokernel/workload/types.py Lines 83-90), and on FidelitySpec.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/fidelity.py` around lines 527 - 564, Add an optional
expected frame-count field to FidelityBudget and FidelitySpec, and include it in
_FIDELITY_FIELDS so configuration can declare the contract. In the fidelity
verification flow alongside the frame_set and seed checks, reject evidence when
the declared count is set and evidence.frames_compared differs, returning a held
FidelityVerdict with evidence_required=True. Preserve existing behavior when no
frame count is declared.

Comment on lines +121 to +133
if np.issubdtype(array.dtype, np.integer):
info = np.iinfo(array.dtype)
scale = float(info.max)
array = array.astype(np.float64) / scale
else:
array = array.astype(np.float64)

if not np.all(np.isfinite(array)):
# A NaN frame would make every metric NaN and every comparison
# meaningless. R4's comparator reported max_abs_error=nan while
# allclose returned True; that class of bug does not get a second run.
raise PerceptualError(f"{label}: frames contain NaN or infinite values")
return array

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

_as_float_array does not enforce the [0, 1] range it documents.

The docstring at Line 108 states the result is float64 in [0, 1]. Two input classes break that:

  1. Float input is never scaled or checked. Frames stored as float32 in [0, 255] pass through unchanged. ssim then applies c1 and c2 derived from a dynamic range of 1.0 (Lines 171-172). Those stabilizers become negligible relative to the data, and the returned SSIM is inflated. An inflated SSIM promotes an artifact, which is the fail-open direction the module docstring rejects at Lines 21-24.
  2. Signed integer input is scaled by info.max only. For int8, info.max is 127 and the minimum is -128, so the result spans [-1.008, 1.0], not [0, 1].

Reject or explicitly handle out-of-range input rather than scaling on an assumption.

🛡️ Proposed fix
     if np.issubdtype(array.dtype, np.integer):
         info = np.iinfo(array.dtype)
+        if info.min < 0:
+            raise PerceptualError(
+                f"{label}: signed integer frames ({array.dtype}) are ambiguous; "
+                "convert to unsigned integer or float in [0, 1] first"
+            )
         scale = float(info.max)
         array = array.astype(np.float64) / scale
     else:
         array = array.astype(np.float64)
 
     if not np.all(np.isfinite(array)):
         # A NaN frame would make every metric NaN and every comparison
         # meaningless. R4's comparator reported max_abs_error=nan while
         # allclose returned True; that class of bug does not get a second run.
         raise PerceptualError(f"{label}: frames contain NaN or infinite values")
+
+    low, high = float(array.min()), float(array.max())
+    if low < 0.0 or high > 1.0:
+        raise PerceptualError(
+            f"{label}: frames must be in [0, 1] after normalization, "
+            f"got [{low}, {high}]"
+        )
     return array
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if np.issubdtype(array.dtype, np.integer):
info = np.iinfo(array.dtype)
scale = float(info.max)
array = array.astype(np.float64) / scale
else:
array = array.astype(np.float64)
if not np.all(np.isfinite(array)):
# A NaN frame would make every metric NaN and every comparison
# meaningless. R4's comparator reported max_abs_error=nan while
# allclose returned True; that class of bug does not get a second run.
raise PerceptualError(f"{label}: frames contain NaN or infinite values")
return array
if np.issubdtype(array.dtype, np.integer):
info = np.iinfo(array.dtype)
if info.min < 0:
raise PerceptualError(
f"{label}: signed integer frames ({array.dtype}) are ambiguous; "
"convert to unsigned integer or float in [0, 1] first"
)
scale = float(info.max)
array = array.astype(np.float64) / scale
else:
array = array.astype(np.float64)
if not np.all(np.isfinite(array)):
# A NaN frame would make every metric NaN and every comparison
# meaningless. R4's comparator reported max_abs_error=nan while
# allclose returned True; that class of bug does not get a second run.
raise PerceptualError(f"{label}: frames contain NaN or infinite values")
low, high = float(array.min()), float(array.max())
if low < 0.0 or high > 1.0:
raise PerceptualError(
f"{label}: frames must be in [0, 1] after normalization, "
f"got [{low}, {high}]"
)
return array
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/perceptual.py` around lines 121 - 133, Update
_as_float_array to enforce its documented float64 [0, 1] output for both integer
and floating-point inputs. Do not silently scale floating-point values or
normalize signed integers using only iinfo.max; instead, validate the converted
range and raise PerceptualError with the existing label when any finite value
falls outside [0, 1], while preserving the non-finite check.

Comment on lines +162 to +168
np = _numpy()
ref = _as_float_array(reference[None, ...], label="reference")[0]
cand = _as_float_array(candidate[None, ...], label="candidate")[0]
if ref.shape != cand.shape:
raise PerceptualError(
f"frame shape mismatch: reference {ref.shape} vs candidate {cand.shape}"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

ssim requires an ndarray despite accepting Any.

Lines 163-164 apply [None, ...] to the raw arguments before _as_float_array converts them. NumPy indexing syntax is not valid on a list or tuple, so ssim([[...]], [[...]]) raises TypeError instead of PerceptualError. ssim is public: it is listed in __all__ at Line 48 and re-exported from autokernel/verification/__init__.py at Line 127.

Convert first, then add the axis.

🛡️ Proposed fix
     np = _numpy()
-    ref = _as_float_array(reference[None, ...], label="reference")[0]
-    cand = _as_float_array(candidate[None, ...], label="candidate")[0]
+    ref = _as_float_array(np.asarray(reference)[None, ...], label="reference")[0]
+    cand = _as_float_array(np.asarray(candidate)[None, ...], label="candidate")[0]

This matches the pattern already used in lpips_backend at Lines 211-212.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
np = _numpy()
ref = _as_float_array(reference[None, ...], label="reference")[0]
cand = _as_float_array(candidate[None, ...], label="candidate")[0]
if ref.shape != cand.shape:
raise PerceptualError(
f"frame shape mismatch: reference {ref.shape} vs candidate {cand.shape}"
)
np = _numpy()
ref = _as_float_array(np.asarray(reference)[None, ...], label="reference")[0]
cand = _as_float_array(np.asarray(candidate)[None, ...], label="candidate")[0]
if ref.shape != cand.shape:
raise PerceptualError(
f"frame shape mismatch: reference {ref.shape} vs candidate {cand.shape}"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/perceptual.py` around lines 162 - 168, Update the
public ssim input handling to call _as_float_array on reference and candidate
directly before adding the leading axis, rather than applying [None, ...] to the
raw arguments. Preserve the existing labels and subsequent shape validation
while ensuring list and tuple inputs raise PerceptualError through the
conversion path.

Comment on lines +263 to +275
if reference.seed != candidate.seed:
raise PerceptualError(
f"frame sets were generated at different seeds "
f"({reference.seed} vs {candidate.seed}); a perceptual comparison "
f"is only meaningful at a fixed seed"
)
ref_frames = _as_float_array(reference.frames, label="reference")
cand_frames = _as_float_array(candidate.frames, label="candidate")
if ref_frames.shape != cand_frames.shape:
raise PerceptualError(
f"frame set shape mismatch: reference {ref_frames.shape} vs "
f"candidate {cand_frames.shape}"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

compare_frame_sets does not check that the two frame sets have the same name.

The function rejects a seed mismatch (Lines 263-268) and a shape mismatch (Lines 271-275). It never compares reference.name against candidate.name. Line 297 then records candidate.name as the evidence frame_set.

A candidate named wan-1.3b-fixed-seed-8 compared against a reference from a different frame set therefore produces evidence that satisfies a budget contracting wan-1.3b-fixed-seed-8. evaluate_fidelity checks the recorded name against the budget (autokernel/verification/fidelity.py Lines 527-537), so it cannot detect the substitution. This is the same defect class the FrameSet docstring rejects at Lines 87-89.

🛡️ Proposed fix
     if reference.seed != candidate.seed:
         raise PerceptualError(
             f"frame sets were generated at different seeds "
             f"({reference.seed} vs {candidate.seed}); a perceptual comparison "
             f"is only meaningful at a fixed seed"
         )
+    if reference.name != candidate.name:
+        raise PerceptualError(
+            f"frame sets have different names ({reference.name!r} vs "
+            f"{candidate.name!r}); the evidence would be recorded under the "
+            f"candidate's name and would not describe this reference"
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if reference.seed != candidate.seed:
raise PerceptualError(
f"frame sets were generated at different seeds "
f"({reference.seed} vs {candidate.seed}); a perceptual comparison "
f"is only meaningful at a fixed seed"
)
ref_frames = _as_float_array(reference.frames, label="reference")
cand_frames = _as_float_array(candidate.frames, label="candidate")
if ref_frames.shape != cand_frames.shape:
raise PerceptualError(
f"frame set shape mismatch: reference {ref_frames.shape} vs "
f"candidate {cand_frames.shape}"
)
if reference.seed != candidate.seed:
raise PerceptualError(
f"frame sets were generated at different seeds "
f"({reference.seed} vs {candidate.seed}); a perceptual comparison "
f"is only meaningful at a fixed seed"
)
if reference.name != candidate.name:
raise PerceptualError(
f"frame sets have different names ({reference.name!r} vs "
f"{candidate.name!r}); the evidence would be recorded under the "
f"candidate's name and would not describe this reference"
)
ref_frames = _as_float_array(reference.frames, label="reference")
cand_frames = _as_float_array(candidate.frames, label="candidate")
if ref_frames.shape != cand_frames.shape:
raise PerceptualError(
f"frame set shape mismatch: reference {ref_frames.shape} vs "
f"candidate {cand_frames.shape}"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/perceptual.py` around lines 263 - 275, Update
compare_frame_sets to validate that reference.name and candidate.name match,
alongside the existing seed and shape checks. Raise PerceptualError with both
names when they differ, before producing comparison evidence so
evaluate_fidelity cannot accept a substituted frame set.

Comment on lines +296 to +303
return PerceptualEvidence(
frame_set=candidate.name,
seed=candidate.seed,
frames_compared=count,
ssim=worst_ssim,
lpips=worst_lpips,
vbench=vbench_score,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Reproduce the SSIM implementation and check for >1.0 overshoot
# on identical frames across dtypes, sizes, and content.
set -euo pipefail

pip install --quiet "numpy==2.2.0"

FILE=$(fd --type f 'perceptual.py' autokernel 2>/dev/null | head -1)
echo "Reviewing: ${FILE:-not found}"
[ -n "${FILE:-}" ] && sed -n '136,190p' "$FILE"

python - <<'PY'
import numpy as np

_WINDOW_SIZE, _WINDOW_SIGMA, _K1, _K2 = 11, 1.5, 0.01, 0.03

def window():
    c = np.arange(_WINDOW_SIZE, dtype=np.float64) - (_WINDOW_SIZE - 1) / 2.0
    k = np.exp(-(c**2) / (2.0 * _WINDOW_SIGMA**2))
    k /= k.sum()
    return np.outer(k, k)

def filt(plane, w):
    v = np.lib.stride_tricks.sliding_window_view(plane, (11, 11))
    return np.einsum("ijkl,kl->ij", v, w)

def ssim(ref, cand):
    w = window()
    c1, c2 = _K1**2, _K2**2
    scores = []
    for ch in range(ref.shape[-1]):
        x, y = ref[..., ch], cand[..., ch]
        mx, my = filt(x, w), filt(y, w)
        mxs, mys, mxy = mx*mx, my*my, mx*my
        sx = filt(x*x, w) - mxs
        sy = filt(y*y, w) - mys
        sxy = filt(x*y, w) - mxy
        num = (2*mxy + c1) * (2*sxy + c2)
        den = (mxs + mys + c1) * (sx + sy + c2)
        scores.append(float(np.mean(num / den)))
    return float(sum(scores) / len(scores))

worst = 0.0
rng = np.random.default_rng(0)
for trial in range(40):
    h = int(rng.integers(11, 96)); wd = int(rng.integers(11, 96))
    ch = int(rng.integers(1, 4))
    frame = rng.random((h, wd, ch))
    s = ssim(frame, frame.copy())
    worst = max(worst, s)
    if s > 1.0:
        print(f"OVERSHOOT trial={trial} shape=({h},{wd},{ch}) ssim={s!r} excess={s-1.0:.3e}")

# Also test flat and near-flat frames, where variance terms are tiny.
for value in (0.0, 0.5, 1.0):
    flat = np.full((64, 64, 3), value)
    s = ssim(flat, flat.copy())
    worst = max(worst, s)
    print(f"flat={value} ssim={s!r} >1.0={s > 1.0}")

print(f"max observed SSIM on identical frames: {worst!r}")
print(f"exceeds 1.0: {worst > 1.0}")
PY

Repository: aryan5v/motionkernel

Length of output: 999


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate relevant files and print snippets/tests without installing external dependencies.
file=$(git ls-files | awk '/autokernel\/verification\/perceptual\.py$/{print; exit}')
echo "FILE=$file"
if [ -n "$file" ]; then
  wc -l "$file"
  sed -n '120,210p' "$file"
  echo "---"
  sed -n '280,310p' "$file"
fi

echo "--- search usages/defaults ---"
rg -n "class PerceptualEvidence|def _check_threshold|def ssim|frames_compared|ssim" autokernel/verification -S || true

Repository: aryan5v/motionkernel

Length of output: 7550


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether NumPy without pip is available for a quick numerical probe.
python3 - <<'PY'
try:
    import numpy as np
    print(f"numpy={np.__version__}")
except Exception as exc:
    print(f"numpy_unavailable={type(exc).__name__}: {exc}")
PY

if python3 - <<'PY'
import numpy as np
PY
then
  python3 - <<'PY'
import numpy as np

_WINDOW_SIZE, _WINDOW_SIGMA, _K1, _K2 = 11, 1.5, 0.01, 0.03

def window():
    c = np.arange(_WINDOW_SIZE, dtype=np.float64) - (_WINDOW_SIZE - 1) / 2.0
    k = np.exp(-(c**2) / (2.0 * _WINDOW_SIGMA**2))
    k /= k.sum()
    return np.outer(k, k)

def filt(plane, w):
    v = np.lib.stride_tricks.sliding_window_view(plane, (11, 11))
    return np.einsum("ijkl,ki->ij", v, w)

def ssim(ref, cand):
    w = window()
    c1, c2 = _K1**2, _K2**2
    scores = []
    for ch in range(ref.shape[-1]):
        x, y = ref[..., ch], cand[..., ch]
        mx, my = filt(x, w), filt(y, w)
        mxs, mys, mxy = mx*mx, my*my, mx*my
        sx = filt(x*x, w) - mxs
        sy = filt(y*y, w) - mys
        sxy = filt(x*y, w) - mxy
        num = (2*mxy + c1) * (2*sxy + c2)
        den = (mxs + mys + c1) * (sx + sy + c2)
        scores.append(float(np.mean(num / den)))
    return float(sum(scores) / len(scores))

worst = 0.0
rng = np.random.default_rng(0)
overshoots = []
for trial in range(1000):
    h = int(rng.integers(11, 96)); wd = int(rng.integers(11, 96))
    ch = int(rng.integers(1, 4))
    frame = rng.random((h, wd, ch))
    s = ssim(frame, frame.copy())
    worst = max(worst, s)
    if s > 1.0:
        overshoots.append((trial, h, wd, ch, s, s - 1.0))

for value in (0.0, 0.5, 1.0, 1e-16):
    flat = np.full((64, 64, 3), value)
    s = ssim(flat, flat.copy())
    worst = max(worst, s)
    print(f"flat={value} ssim={s!r} >1.0={s > 1.0} excess={s-1.0:.3e}")

print(f"max observed SSIM on identical frames: {worst!r}")
print(f"exceeds 1.0: {worst > 1.0}")
print(f"overshoot_count: {len(overshoots)}")
for row in overshoots[:10]:
    print(row)
PY
fi

Repository: aryan5v/motionkernel

Length of output: 339


🌐 Web query:

scikit image structural_similarity float error near 1.0 identical images documentation

💡 Result:

The common issue where skimage.metrics.structural_similarity produces unexpected results (including potential errors or incorrect indices near 1.0) when using floating-point images is primarily caused by the incorrect automatic estimation of the data_range parameter [1][2][3]. For floating-point image data, the function attempts to guess the data_range based on the image's data type. Because scikit-image's internal dtype_range defines a range of [-1.0, 1.0] for float types, the function often defaults to a data_range of 2.0 instead of the 1.0 commonly expected for normalized image data [1][2]. This incorrect range estimation can lead to artificially high or otherwise inaccurate SSIM values, even for identical images [2]. To resolve this, you should always explicitly specify the data_range argument when working with floating-point images [1][3]. Recommended practices: 1. Specify data_range: Pass the scalar difference between the maximum and minimum possible values of your image data (e.g., data_range=1.0 if your data is in the [1] range, or data_range=255 if it is in the range) [1][4][5]. 2. Use peak-to-peak if unsure: If you do not know the theoretical data range, you can use the peak-to-peak value of your input image as a proxy, such as data_range=image.ptp() [6]. 3. Avoid invalid arguments: Ensure you pass a scalar value to data_range. Passing a tuple or other sequence, such as (1.0, 1.0), will cause a TypeError (e.g., "can't multiply sequence by non-int of type float") [6]. For more accurate results that better match standard implementations like those by Wang et al., it is also recommended to set gaussian_weights=True, sigma=1.5, and use_sample_covariance=False along with an explicit data_range [1][4][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a 'TIERED_FIDELITY.md|perceptual_harness.py' . | sed 's#^\./##'
echo "---"
sed -n '1,150p' docs/TIERED_FIDELITY.md
echo "---"
if [ -x "$(command -v ast-grep)" ]; then
  ast-grep outline autokernel/verification/fidelity.py --match PerceptualEvidence --view expanded || true
  ast-grep run --pattern '$CLASS.__post_init__($$CALL)' --lang python autokernel/verification/fidelity.py || true
fi
echo "---"
sed -n '118,135p' autokernel/verification/fidelity.py
sed -n '337,405p' autokernel/verification/fidelity.py

Repository: aryan5v/motionkernel

Length of output: 9764


Ensure SSIM evidence cannot construct out of range

PerceptualEvidence.__post_init__ runs _check_threshold() on ssim, and ssim has range [-1.0, 1.0]. This path can be invalid when the SSIM numerator/denominator accumulate as an exact 1.0 plus a tiny floating-point overshoot. If the documented ~1e-16 agreement with scikit-image means this can happen, clamp worst_ssim before returning PerceptualEvidence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@autokernel/verification/perceptual.py` around lines 296 - 303, Clamp
worst_ssim to the documented valid range of [-1.0, 1.0] immediately before
constructing PerceptualEvidence, preserving the computed value when it is
already in range and preventing tiny floating-point overshoots from reaching
__post_init__._check_threshold().

Comment thread docs/TIERED_FIDELITY.md
Comment on lines +93 to +101
```json
"fidelity": {
"budget": {"tier": "perceptual", "tier_number": 2, "min_ssim": 0.98, ...},
"verdict": {"passed": true, "margins": [
{"metric": "ssim", "value": 0.9912, "threshold": 0.98, "margin": 0.0112, "passed": true},
{"metric": "lpips", "value": 0.0071, "threshold": 0.02, "margin": 0.0129, "passed": true}]},
"evidence": {"frame_set": "wan-1.3b-fixed-seed-8", "seed": 1234, "frames_compared": 8}
}
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The evidence example omits stage_status.

PerceptualEvidence.as_dict always emits stage_status (autokernel/verification/fidelity.py Lines 406-411). Line 99 shows frame_set, seed, and frames_compared with no ellipsis, so a reader treats it as the complete object. Line 95 does use ... for the truncated budget, which makes the omission on Line 99 read as deliberate completeness.

📝 Proposed fix
-  "evidence": {"frame_set": "wan-1.3b-fixed-seed-8", "seed": 1234, "frames_compared": 8}
+  "evidence": {"frame_set": "wan-1.3b-fixed-seed-8", "seed": 1234,
+               "frames_compared": 8, "stage_status": "ok"}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```json
"fidelity": {
"budget": {"tier": "perceptual", "tier_number": 2, "min_ssim": 0.98, ...},
"verdict": {"passed": true, "margins": [
{"metric": "ssim", "value": 0.9912, "threshold": 0.98, "margin": 0.0112, "passed": true},
{"metric": "lpips", "value": 0.0071, "threshold": 0.02, "margin": 0.0129, "passed": true}]},
"evidence": {"frame_set": "wan-1.3b-fixed-seed-8", "seed": 1234, "frames_compared": 8}
}
```
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/TIERED_FIDELITY.md` around lines 93 - 101, Update the evidence object
example in TIERED_FIDELITY.md to include the stage_status field emitted by
PerceptualEvidence.as_dict, preserving the existing frame_set, seed, and
frames_compared fields.

@aryan5v
aryan5v merged commit 5313269 into main Aug 2, 2026
4 checks passed
aryan5v added a commit that referenced this pull request Aug 2, 2026
aryan5v added a commit that referenced this pull request Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant