Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

6.2 KiB
Raw Permalink Blame History

Gate Cascade (5-Gate Correctness Middleware)

Every code change the Recommendation / specialist agents propose is validated by a strict 5-gate cascade before the change is considered accepted. The cascade runs as middleware — not inside any agent — so no agent can reorder or skip gates. The Correctness agent receives an immutable GateVerdict and narrates it; that's the only agent contact surface.

Source of truth: perfxpert/runtime/gate_cascade.py (spec §5, §5.0).

Cross-links:

The 5 gates

Strict order — ALL must pass:

# Gate Tool delegated to Reject condition
1 Compile / Run tools.compile.build Build failure (non-zero exit)
2 SOL sanity (anti-Sakana) tools.sol.sanity_check See below — two-tier
3 Bitwise / Numeric tools.patch.verify_output Output drift > tolerance
4 Regression (weighted-geomean) tools.regression.compare_runs See below — three conditions
5 Test Anchors tools.anchors.check Any prior-pass test now fails

Gates 1, 3, 4, and 5 invoke EXECUTION-class tools; see mcp-server.md for why this matters. Gate 2 invokes a READ_ONLY tool but is owned by the middleware layer for verdict-coherence reasons.

Two-tier SOL check (Gate 2)

Gate 2 is the primary anti-Sakana defense against reward-hacking (LLM claims a wildly unrealistic speedup to satisfy the optimization target).

It runs in two tiers:

  1. Hard cap on speedup — SOL_MAX_REASONABLE_SPEEDUP = 50.0. Any claimed_speedup > 50× is rejected immediately, regardless of architecture or absolute FLOPS. This catches gross reward-hacking with no GPU-specific setup required.

  2. Absolute-FLOPS check (optional) — when achieved_flops_per_sec is known, the gate compares it to the architecture's published peak (via arch.lookup_peaks). Any optimized run that claims flops > peak is rejected.

Either failing tier produces GateVerdict(status="reject", failing_gate="sol", ...).

Regression gate (Gate 4) — three reject paths

The regression gate passes only if all three of these are satisfied:

Check Threshold Constant
Any individual hot kernel regressed > 10% → reject HOT_KERNEL_INDIVIDUAL_THRESHOLD_PCT
Weighted-geomean degradation on tail > 5% → reject TAIL_GEOMEAN_THRESHOLD_PCT
Total regression vs baseline > 3% → reject REGRESSION_NOISE_THRESHOLD_PCT

"Hot kernel" = top-K covering 80% of cumulative runtime, plus any kernel ≥ 3% individually (spec §5 definition). Weighted-geomean is the noise-robust metric for the tail of non-hot kernels: sensitive enough to catch cross-the-board regressions that individual thresholds miss, but immune to single-run jitter.

The gate returns status="regressed" (not "reject") to distinguish "code is semantically correct but slower" from "code is broken".

Test-anchor gate (Gate 5) — last line of defense

Test anchors are a small set of known-good unit or integration tests that must stay green across every optimization cycle. If any anchor test that previously passed now fails, the gate returns status="reject", failing_gate="anchors".

Anchors are NOT the full test suite — they're a curated subset chosen to catch regressions the earlier gates miss (e.g. numerical drift inside a tolerance band that the bitwise gate accepted).

Verdict propagation

evaluate() / run_gate_cascade() return a single immutable GateVerdict:

from perfxpert.runtime.gate_cascade import run_gate_cascade, GateInput

# Minimum required fields — everything else is optional and defaults to
# None. In this shape the cascade runs gates 2 (SOL) + 4 (regression)
# only; gates 1/3/5 short-circuit when their optional inputs are absent.
# For a full 5-gate run, supply source_file + diff_payload (compile),
# verify_output_baseline/new (bitwise), and test_anchor_baseline/new.
verdict = run_gate_cascade(GateInput(
    kernel_name="my_kernel",
    claimed_speedup=1.15,
    arch="gfx942",
    baseline_runtime_ns=1_000_000,
    achieved_runtime_ns=870_000,
    patch_sha="abc123",
))

print(verdict.status)         # 'pass' | 'reject' | 'regressed'
print(verdict.failing_gate)   # None on pass; gate name on reject
print(verdict.detail)         # Human-readable reason
print(verdict.delta_pct)      # Only meaningful for regression verdicts

Statuses:

  • pass — all 5 gates green; the patch is accepted.
  • reject — a non-regression gate failed; failing_gate says which.
  • regressed — the code compiled + ran + matched output but was measurably slower. The Recommendation agent treats this as a weaker rejection (may try a different technique from the same specialist's knowledge file).

Note that GateVerdict is a frozen dataclass — agents that receive it cannot mutate it. The Correctness agent's fence slice (perfxpert/agents/fence/correctness.md) explicitly instructs it to narrate detail verbatim and never override status.

Debug-loop caps (spec §5)

To bound cost in agentic-LLM mode, the cascade is invoked under three session-level caps:

Constant Value Meaning
MAX_OPTIMIZATION_CYCLES_PER_KERNEL 5 How many patch-then-verify rounds per hot kernel before giving up
MAX_CONSECUTIVE_FAILURES 3 Stop after 3 consecutive verdicts ≠ pass
MAX_SESSION_LLM_TURNS 100 Hard cap on total LLM turns in one session

These constants are verified at runtime; see tests/test_runtime/test_gate_cascade.py for coverage.

Why middleware, not agent

Design-review C1 in the spec: if the Correctness agent ran the gates, it could re-order or skip them. Keeping gates in middleware means the verdict is structurally incapable of being tampered with by any agent — the adversarial model includes the LLM itself. This invariant is test-enforced in tests/test_agents/test_correctness.py (see test_correctness_has_no_gate_tools), which asserts Correctness's AgentSpec.tools allowlist contains zero EXECUTION tools.