Skip to content

compiler: interleaved backward runs from the last virtual stage back; the spectral policy is bound by a hash chain - #627

Merged
gstoner merged 3 commits into
mainfrom
agent/interleaved-backward-order
Aug 25, 2026
Merged

gstoner merged 3 commits into
mainfrom
agent/interleaved-backward-order

Conversation

@gstoner

@gstoner gstoner commented Aug 25, 2026

Copy link
Copy Markdown
Owner

The two follow-ups recorded during the #626 review — plus a third defect the first one exposed.

1. Interleaved backward order (the generator defect)

_build_interleaved computed bwd_clock = fwd_clock + p*v — a constant offset — so backward inherited forward's ascending virtual-stage order, the opposite of gradient flow. Measured (p=2, v=2, micro-batch 0): stage 0 B at clock 4 … stage 3 B at clock 7, i.e. stage s's backward ran before the stage s+1 backward that produces its input gradient.

The stage term is now mirrored — (V-1-stage) + mb + V — so micro-batch 0 runs F 0→1→2→3, B 3→2→1→0. Crucially the makespan is bit-identical (first backward clock still V, last still 2V+m-2), which is what makes this an ordering fix rather than a schedule change. Verified across five configurations: zero dependency violations, makespan unchanged, and the rank/clock collision count identical to the old generator (a pre-existing modelling property of this analytical builder — measured, and deliberately left alone).

Because the edge is expressible again, the "known limitation" recorded in #626 is deleted and the backward gradient edge is now required like any other; an inversion fails closed.

2. Decoupled schedules carried fabricated cross-stage edges

Found while making (1) fail closed. A decoupled stage owns a self-contained objective and trains directly from data — zero cross-stage coupling is its defining property — yet the carrier asserted cross-stage forward edges (which silently passed the old ordering filter) and dropped the backward ones. Now only per-rank program order and each stage's own forward→backward edge remain.

3. Spectral policy: comparison → hash chain

Verifying consumed attributes against a carried declaration stops a lone attribute edit, but not an edit of both. Investigating showed the producer already had a complete chain: schedule_digest = sha256(payload) → payload names object_id "spectral:<sha256(identity)>" → identity carries workspace_bytes / native_entry / normalization. So instead of inventing a scheme, the consumer now re-derives that chain with llvm::SHA256 over the two preimages the module carries. The decisive test edits the attribute and its declaration together — the case the previous fix would have accepted — and it is now rejected at the second hash link.

Evidence (Strix Halo)

ninja all targets clean · IR lit 356 passed / 0 failed · spectral suite 12/12 with tessera-opt live (including co-edit and payload-swap rejections) · pipeline suites 56 passed · full unit suite 16,427 passed.

The 42 remaining failures are the recorded environmental baseline (Apple lane + rocm fp8 + NVIDIA-dialect-off rows). I verified this rather than assuming: stashing the change and re-running the affected files gives an identical failure set, so this change introduces none.

mypy 0 · ruff clean · dashboards in sync.

Remaining from the four-area sweep: the W4 admissible-effects packet family, still unstarted.

🤖 Generated with Claude Code

… the spectral policy is bound by a hash chain

Two follow-ups recorded during the #626 review, plus a third defect the
first one exposed.

1. Interleaved backward order. `_build_interleaved` computed
   `bwd_clock = fwd_clock + p*v` — a CONSTANT offset — so backward
   inherited forward's ASCENDING virtual-stage order, the opposite of
   gradient flow: stage s's backward ran before the stage s+1 backward
   that produces its input gradient (p=2/v=2, micro-batch 0: stage 0 B at
   clock 4 ... stage 3 B at clock 7). The stage term is now mirrored:
   `(V-1-stage) + mb + V`. Gradients flow from the last virtual stage back
   to the first, and the makespan is BIT-IDENTICAL (first backward clock
   still V, last still 2V+m-2), so this is an ordering fix, not a schedule
   change. Verified over five configurations: zero dependency violations,
   makespan unchanged, and the rank/clock collision count identical to the
   old generator (a pre-existing modelling property of this analytical
   builder, deliberately untouched).

   The backward gradient edge is therefore expressible again, so the
   "known limitation" recorded in #626 is deleted and that edge is
   REQUIRED like any other — an inversion now fails closed.

2. Decoupled schedules carried fabricated cross-stage edges — found while
   making (1) fail closed. A decoupled stage owns a self-contained
   objective and trains directly from data; zero cross-stage coupling is
   its defining property, yet the carrier asserted cross-stage forward
   edges (which silently passed the old ordering filter) and dropped the
   backward ones. Only per-rank program order and each stage's own
   forward->backward edge remain.

3. Spectral policy binding. Verifying consumed attributes against a
   carried declaration stops a lone attribute edit but not an edit of
   BOTH. It turns out the producer already had a complete chain —
   `schedule_digest = sha256(payload)`, the payload names
   `object_id "spectral:<sha256(identity)>"`, and the identity carries
   workspace_bytes/native_entry/normalization — so rather than invent a
   scheme, the consumer now RE-DERIVES that chain with llvm::SHA256 over
   the two preimages the module carries. Editing an attribute and its
   declaration together now breaks the second hash link.

Gates (Strix Halo): ninja all targets clean; IR lit 356 passed / 0 failed;
spectral suite 12/12 with tessera-opt live (including the co-edit and
payload-swap rejections); pipeline suites 56 passed; full unit suite 16427
passed. The 42 remaining failures are the recorded environmental baseline
(Apple lane + rocm fp8 + NVIDIA-dialect-off rows): the failure sets with
and without this change are IDENTICAL, verified by stashing. mypy 0; ruff
clean; dashboards in sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f6b756f1ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread python/tessera/compiler/scheduled_spectral.py
gstoner and others added 2 commits August 25, 2026 10:29
…tity contract (PR #627 review)

New sync key SPECTRAL-PAYLOAD-CHAIN-2026-08-25. The change adds two
required preimages to the shared Schedule->Tile spectral contract and is
verification-only (no kernel, launch parameter, or numerical result
changes): rocm parity validated exact-device on gfx1151 and x86 validated
on the AVX-512 host (44 device-lane tests plus the 12/12 spectral suite
with tessera-opt live); nvidia and apple not-applicable-today with the
architecture-specific reason and the obligation they inherit on adoption.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… invariants as tests

Mathematical audit of this PR found one of its own claims false. I stated
the reordering left the (rank, clock) collision count identical to the old
generator; that was generalised from three configurations which happened to
be unchanged. Swept over p<=6, v<=5, m<=12: unchanged in 235/360, INCREASED
in 125 (at most 9, mean +1), never decreased. The builder is an analytical
bubble model, not a resource-feasible timeline — it already placed several
steps on one (rank, clock) slot — so this changes no execution claim, but
the note was wrong and is corrected in place.

Dependency correctness and makespan are unaffected, and are now proved
rather than sampled. With V = p*v:
  clock_F(s,mb) = s + mb,  clock_B(s,mb) = 2V-1-s+mb
  T1 forward chain:      clock_F(s) - clock_F(s-1) = 1 > 0
  T2 backward chain:     clock_B(s) - clock_B(s+1) = 1 > 0  (old: -1, inverted)
  T3 backward after own forward: difference 2V-1-2s, minimised at s=V-1 -> 1
  T4 span endpoints V and 2V+m-2, identical to the pre-fix generator
test_pipeline_schedule_carrier.py checks all four exhaustively over the
parameter space and pins the generator to the closed forms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gstoner

gstoner commented Aug 25, 2026

Copy link
Copy Markdown
Owner Author

Mathematical correctness audit of this PR — including a correction to one of its own claims.

Correction: the collision claim was wrong

I wrote that the reordering leaves the (rank, clock) collision count "identical to the old generator." That was generalised from three configurations — which, swept properly, turn out to be among the unchanged ones. Over p ≤ 6, v ≤ 5, m ≤ 12 (360 configs):

count
unchanged 235
new has more 125 (max +9, mean +1)
new has fewer 0

What it does and doesn't mean: this builder is an analytical bubble model, not a resource-feasible timeline — it already placed several steps on one (rank, clock) slot (up to 160 in one config). So no execution claim changes, and dependency correctness and makespan are untouched. But the note was false, and it's corrected in place (b8a1c0e7), with making the model resource-feasible flagged as separate pre-existing work.

The ordering fix is now proved, not sampled

The clock model is exact integer arithmetic, so with V = p·v, clock_F(s,mb) = s + mb, clock_B(s,mb) = 2V−1−s+mb:

  • T1 (forward chain): clock_F(s) − clock_F(s−1) = 1 > 0.
  • T2 (backward chain): clock_B(s) − clock_B(s+1) = 1 > 0 — stage s strictly follows s+1. The old formula gives −1, so the inversion this PR fixes is provable, not merely observed.
  • T3 (backward after its own forward): difference is 2V−1−2s, minimised at s = V−1 giving exactly 1 > 0 for every V ≥ 1.
  • T4 (makespan): endpoints V and 2V+m−2identical to the pre-fix generator, so bubble ratio cannot move.

All four are now exhaustive tests, plus one pinning the generator to the closed forms.

Audit of the merged polygamma tower (while I was at it)

Against independent references — scipy on the positive axis, and the recurrence ψ⁽ⁿ⁾(x) = ψ⁽ⁿ⁾(x+1) − (−1)ⁿn!/x^{n+1} on the negative axis (independent of our reflection formula):

  • n = 2…6: max rel err 7e-16 … 6e-15 on (0.02, 40).
  • n = 1: 6e-11 — the displaced hand helper's own precision, matching the tolerance documented when it was carried verbatim.
  • n = 0: a headline 2.2e-8 that is an artifact, not a defect — it sits at digamma's root x ≈ 1.4616 where the relative metric divides by ~1.7e-4. Absolute error there is 3.9e-12; max absolute over the range 6.8e-12; max relative excluding the zero 2.7e-10.
  • Negative axis (the reflection branch): 4e-16 … 5e-12 across n = 0…4.

CI was green before these two commits; re-running now.

🤖 Generated with Claude Code

@gstoner

gstoner commented Aug 25, 2026

Copy link
Copy Markdown
Owner Author

Already addressed — the assessment landed in b1e6777, pushed before this review comment was posted (the bot reviewed the earlier commit f6b756f1).

Sync key SPECTRAL-PAYLOAD-CHAIN-2026-08-25 is present in all four queues, with architecture-specific outcomes rather than one repeated sentence:

  • x86 — parity validated (AVX-512 host). x86 is the producer target the spectral suite actually exercises (target="x86", arch zen5-avx512): 12/12 with tessera-opt live, including the new co-edit and payload-swap rejections.
  • ROCm — parity validated (exact-device, gfx1151). Ran the compiled spectral + spectral-backward device lanes plus the x86 spectral lane — 44 tests, unchanged. The change is verification-only: no kernel, launch parameter, or numerical result moves.
  • NVIDIA — not applicable today, with the reason: no NVIDIA Target consumer exists for the scheduled spectral program (dialect off by default here; the physical spectral lanes are x86/gfx1151). It inherits the required preimages when such a consumer lands.
  • Apple — not applicable today, same shape, plus the note that matters for whoever adds a Metal consumer: the contract now requires both preimages on the module, not just the digest.

The entry also covers this PR's other shared change — the interleaved pipeline carrier ordering — since ROCm consumes that carrier through the shared pipeline passes (phase4 lit 6/6 green).

🤖 Generated with Claude Code

@gstoner
gstoner merged commit b4d5ded into main Aug 25, 2026
17 checks passed
@gstoner
gstoner deleted the agent/interleaved-backward-order branch August 25, 2026 15:51
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