aisimulate-core 0.12.0

Engine-neutral inference simulation, deterministic replay, and performance modeling
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
<!--
SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
-->

# Phase 2 Execution Plan — Rust-default flip and Python latency-path removal

**Status (2026-08-14): PR-1 (#1454), PR-2 (#1496) and PR-2.5 (#1508) MERGED;
PR-3 (#1521 — the PR carrying this text) delivers the ENGINE-STEP-path
retirement and is in flight until it merges — see "PR-3 disposition"
below.** The original PR-3 scope ("delete the per-call query stack
wholesale") was revised after FPM (#1384) landed a live consumer of that
stack; #1461's Rust FPM port (Op::FpmForward + fpm_sol.rs) then let PR-3
delete the Python FPM walk too.

## PR-3 disposition (2026-08-14) — what was deleted, what was kept, and why

**Deleted (the engine-step path, both op-level and FPM):**

- `base_backend`'s Python step branches: the phase runners
  (`_run_context_phase` / `_run_generation_phase`), `run_mixed`'s three-pass
  composition, `_get_fpm_mix_step_latency`, the encoder `op.query()`
  fallback loop, and the `RustEngineUnsupportedError` "parity by
  delegation" rescue arms — an inexpressible op graph is a hard error now
  (the opspec coverage tripwire keeps that unreachable for shipped ops). A
  non-`PerfDatabase` database on a step surface raises `TypeError` (the
  compiled engine resolves perf data from disk by identity). En route, the
  two #1461 leftover guards that still forced FPM static/decode onto the
  Python walk were closed (rust-first, verified answer-preserving to full
  precision on the synthetic parity fixture).
- `fpm_forward.py`'s query machinery: `query`/`query_totals`/
  `query_pass_baseline`, the parquet+sidecar loader and validators, the
  perf_interp configs, and `_oplevel_sol_fn` (the per-op DatabaseMode.SOL
  roofline closure). The Rust core owns FPM end to end
  (`perf_database/fpm_forward.rs`, `operators/fpm_forward.rs`,
  `operators/fpm_sol.rs`); `FPMForwardOp` keeps only the construction
  surface `_to_opspec` and the memory model consume.
- The `"python"` value of `engine_step_backend` became a warn-once
  deprecation NO-OP (routes to the compiled engine; accepted one release
  cycle, then dropped). Unknown values now raise. The gate keeps only the
  non-`PerfDatabase` delegation (consumed by the AFD orchestration).
- The live-Python golden capture harness (`regenerate_goldens.py` + guard
  tests): the goldens are frozen artifacts; `pin_goldens.py` appends/
  refreshes records from the live rust engine (provenance-marked
  `post_freeze_pins`), making the golden diff the review artifact. The FPM
  parity class freezes its Python-side references inline
  (`_FPM_*_FROZEN`) since its dataset is generated per-run.
- The relative Rust-vs-Python CI perf gate and the benchmark's python arm;
  the python-vs-rust support-matrix compare machinery
  (`scan_rust_parity.py`, `--compare-engine-step-backends`); the
  `prediction_regression_gate` python pin flipped to rust.
- (Correction, cleanup PR: an earlier revision listed the dead `Mamba2`
  composite as deleted here. It was NOT — PR-3 kept it as a deprecated
  five-leg composite on the query whitelist; it was deleted with the
  per-call shims in the deprecation-cleanup PR.)

**Kept — the PER-CALL query stack (`operations/*.py` `query()` +
`_query_*_table`, `perf_database.query_*`, `perf_interp/`,
`util_empirical.py`) stays intact.** Its load-bearing consumers:

1. **AFD comm ops** (`afd_transfer.py`, permanent Python orchestration)
   query `query_nccl` / `query_p2p` / `query_mem_op` EMPIRICALLY, and
   `_sum_latency` keeps its `op.query()` fallback loop.
2. **`tools/sanity_check/validate_database.ipynb`** (+ its e2e) exercises
   10 `PerfDatabase.query_*` methods per-call, including the SOL_FULL
   raw-tuple diagnostic — which therefore stays as-is.
3. **Internal couplings** that make partial carving unsafe: GEMM's silicon
   path re-queries SOL (fp8_static floor), `_correct_sol` needs table
   lookups at load time, mamba has no mode dispatch, MSA's empirical path
   divides DSA-SOL by DSA-SILICON.

(FPM was the third hard dependent when PR-3 was first scoped — its
roofline queried every op-level op in SOL — but #1461 moved that to
`fpm_sol.rs`, which is what unlocked deleting the walk.)

**Sequel ladder (tracked in #1357 Phase 3):**

1. **PR-4 — notebook re-oracle** (independent of PR-3; can run in
   parallel): re-oracle `validate_database.ipynb` onto the per-op
   evaluation FFI (needs a small FFI addition if the sol_math/sol_mem
   decomposition plots are to survive; rust computes both components
   internally). This is the prerequisite that unblocks the per-call
   deletion — the notebook is the `query_*` facade's biggest live
   consumer.
2. **PR-5 — per-call query-stack retirement** (needs PR-3 + PR-4): delete
   the per-call stack family-by-family (#1357's thin-delegation shape),
   retiring `query_*`, the empirical/silicon table bodies, and
   `util_empirical`'s math (keep the provenance constants) — with the AFD
   comm-table queries re-pointed at the op-list FFI or kept as the last
   per-call island. The deprecated `Mamba2` composite's disposition lands
   here too. **DONE — this PR.** Landed in one PR rather than
   family-by-family: the pinned pre-retirement baseline
   (`tests/cross_package/test_query_shim_baseline.py`, 120 cases captured
   from the Python math before deletion) made the whole-surface swap
   verifiable at once. `query_*`/`Operation.query` survive one release as
   engine-routed deprecation shims (5 tombstones raise: the two GEMM
   overhead sub-table queries, the two legacy deepep walks, and the
   per-phase `query_trtllm_alltoall`); AFD's three query points and the
   `Mamba2` composite's five legs evaluate standard twin ops through the
   single-op plumbing.
3. **PR-6 — data-plane FFI + weight physics** (independent of the
   deprecation window; runs while the PR-5 shims bake). **DONE — this
   PR.** The engine table view (`perf_database/table_view.rs`,
   `AicEngine.table_view_json`) re-folds the raw parquet sources into
   every retired Python loader's exact nested-dict shape — values, key
   TYPES (rehydrated to enums/ints/tuples in
   `sdk/engine_table_view.py`), insertion order, and empty subtrees —
   and every `PerfDatabase._<family>_data` attribute now binds it, so
   the notebook grids, support matrix, task validation gate, and every
   other consumer kept their code unchanged. Per-op weight physics moved
   to `Op::weight_bytes` (batch FFI `weights_ops_json`); the base
   `Operation.get_weights` routes there and the per-class `_weights`
   math is deleted. The `_GEMM/_MOE_QUANT_UTIL_LEVEL` dicts are
   projections of the Rust tables. Verified by a pinned pre-deletion
   baseline (7 databases × every table attribute + support matrices +
   per-op weights, `tests/cross_package/test_data_plane_baseline.py`;
   retired in the deprecation-cleanup PR once the equivalence landed).
   Deferred to the cleanup PR from the original cut: the Python-side shared-layer
   source resolution (`_build_op_sources` still feeds the engine's
   source map — moving it is orthogonal to the view work), kv-cache
   bytes (model-level polymorphism, needs a model-geometry spec), and
   the moe/moe_comm/dsa/dsv4 parsers, which survive as TEST-ONLY
   schema-contract fixtures for the collector suite's format handshake
   (no production path parses perf files in Python anymore).
4. **Deprecation-cleanup PR — removal + pyo3 op unification**
   (time-locked). **DONE — this PR.** Landed exactly as planned, in four
   segments:
   - *Deprecated-surface removal:* the `"python"` `engine_step_backend`
     value, `PerfDatabase.query_*` / `Operation.query` (shims and
     tombstones alike), `_evaluate_single_op`'s re-moding dimension, and
     the `Mamba2` composite. `_evaluate_single_op` survives as the
     PERMANENT internal single-op plumbing behind the AFD twins and the
     fallback loop.
   - *Source resolution:* `_build_op_sources` moved to the engine
     (`perf_database/source_resolution.rs`; schema v13 —
     `EngineConfig` dropped the Python-resolved `perf_db_sources` map for
     `enable_shared_layer` + `strict_provenance` policy flags; structured
     resolver warnings re-emit through the Python warn-once registries).
   - *pyo3 op unification:* the Rust op structs ARE the Python classes
     (`py_ops.rs`, 32 families + the base); `models/*.py` construct them
     directly, `operations/*.py` keeps thin data-plane shells, and the
     `_to_opspec` serializer family, the per-op weight FFI wrapper and
     the two-sided `ENGINE_SPEC_SCHEMA_VERSION` sync are deleted
     (`engine_spec_schema_version()` is the single owner; ops
     self-serialize via `_spec_json` / `ops_json_from_ops`, which refuses
     the RetiredDeepEp dispatch tombstone). The pickle prerequisite
     resolved via `__getnewargs_ex__` (shell identity survives
     `ProcessPoolExecutor` fork+spawn).
   - *Baseline + parser retirement:* both migration baselines retired
     with the surfaces they froze (`query_shim_baseline.json` with the
     shims; `data_plane_baseline.json` + its codec once the last parser
     left) — succeeded by the synthetic-parquet view-shape suites, which
     don't depend on shipped data staying byte-stable. The last Python
     perf parser (`load_dsv4_sparse_op_data`) retired when the
     `_dsv4_csa_topk_calib_data` view attribute landed; the collector
     contract tests assert against the engine view / frozen schema
     literals.

   Still deferred (unchanged from PR-6): kv-cache bytes (model-level
   polymorphism, needs a model-geometry spec) and the AFD orchestration
   quartet (Python-side by design; per-op values already cross the
   engine's single-op plumbing — see the `afd_transfer.py` module TODO
   for the port triggers).

**Post-PR-5 invariant (the single-oracle rule):** per-op performance VALUES
(latency, energy, SOL decomposition) are computed ONLY by the compiled
engine (`aic-core/rust/aiconfigurator-core`). Python owns model/topology
composition, model-config loading and shared-layer SOURCE SELECTION, and
orchestration — never estimation math, and (since PR-6) never
perf-data parsing: the engine loads and serves the performance tables,
Python consumes them through the table-view FFI. New
per-op access goes through the op-list FFI (`EngineHandle.evaluate_ops_json`
/ `evaluate_ops_sol_json`), the per-phase surface (`run_static_per_op`), or
whole runs; there is no supported per-call Python query surface after the
shims' window closes. Enforced by
`tests/cross_package/test_single_oracle_contract.py` (frozen shim surface,
no `_query_*_table`/`get_sol`/`get_empirical` defs, whitelisted `query`
overrides, `perf_interp` stays deleted) and mirrored in `.coderabbit.yaml`
path instructions; the `.claude/rules/rust-core/parity.md` Rule 2 update landed with this
migration at maintainer direction.

The keep/delete inventory and Gate-3 text below are retained as the
original plan of record; where they conflict with this disposition, the
disposition wins.

**Status (2026-08-06): PR-1 (#1454) MERGED 2026-08-05; PR-2 in flight.**
PR-2 closes every gap listed below and lands the Gate-2 golden anchor. State
of the former gaps:

- **Energy/power crosses the FFI (PR-2).** Rust perf leaves carry the
  measured `{latency, power, energy}` triple (`perf_interp.rs::LeafValue`);
  power blends with the Python engine's exact semantics (linear lerp /
  median util-hold / IDW site mean — NOT energy-lerp), and per-op
  `energy_wms` rides the op-list FFI. The PR-1 power routing carve-out and
  the static-path "run the Python phase runners for energy only" double
  evaluation are both DELETED; the Python `query()` layer is no longer a
  live dependency of rust-routed runs. Known dormant gap: the WideEP/deepep
  table families remain latency-only on the Rust side — no shipped wideep
  parquet carries power columns today, so both engines report 0.0 there;
  thread it when wideep power data first ships (#1439 territory).
- **AFD** sources its per-op values from the compiled engine via the thin
  op-list evaluation FFI (`evaluate_context_ops` / `evaluate_generation_ops`,
  index-addressed into the compiled spec); the A/F partitioning, stride
  integration, and the five synthetic comm ops stay Python-side permanently.
  (Correction to the earlier text: AFD never routed through
  `RustEngineUnsupportedError` fallback — its decode pipeline called
  `Operation.query()` directly in `AFDInferenceSession._sum_latency`; that
  call site is now the FFI insertion point, with the Python loop as
  fallback.) The VL encoder phase likewise: shape math stays Python, per-op
  values come from `evaluate_ops_json` (encoder ops are deliberately NOT in
  the compiled spec).
- **SOL is ported; SOL_FULL is a Python-side per-call diagnostic (PR-2).**
  The compiled engine dispatches `DatabaseMode::Sol` per family in front of
  the already-ported SOL formulas; `_RUST_SUPPORTED_DATABASE_MODES` includes
  SOL, emptying the gate's mode-based delegation. SOL_FULL is declared a
  permanently Python-side PER-CALL diagnostic (#1357's alternative
  disposition): `query_*(..., database_mode=SOL_FULL)` keeps returning the
  raw `(sol_time, sol_math, sol_mem)` tuple the sanity-check notebook
  plots, while mode entry rejects it as a DEFAULT mode (it has never worked
  there — the phase runners cannot consume a bare tuple). The per-call
  branches are engine-step-dead and delete together with `query()` in PR-3,
  which must also decide the sanity notebook's replacement oracle.

## Revised PR sequence (2026-08-01) — supersedes the P0–P4 table below

| # | PR | Scope | Maps to |
| --- | --- | --- | --- |
| **PR-1** | Rust default | Flip default resolution to the compiled engine; power-carrying and non-`PerfDatabase` databases delegate to the Python step; explicit `"rust"`/`"python"` keep force semantics (`"python"` is the escape hatch, retained one release). Propagate `engine_step_backend` into the internal `RuntimeConfig` constructions (`run_mixed` passes 1–3, `_get_genonly_step_latency`) so the escape hatch binds the whole composition. Forced-rust full-matrix scan as merge evidence. No deletion. Bake one release cycle. | P0+P1 |
| **PR-2** | Golden anchor + gap closure — **DELIVERED by this PR** | Goldens captured from the live Python path (engine-step 346 case/surface records incl. error classes, compile-engine references, per-op dicts for the compile-engine subset; byte-idempotent `regenerate_goldens.py`); parity suites rewired to Rust-vs-golden with anti-vacuous guards. Energy crosses the FFI (power carve-out and the static energy double-run deleted). The op-list evaluation FFI restores the user-facing per-op breakdown (real op names + per-op energy + real provenance tags replace the `rust_engine_step_*` synthetic keys) and feeds AFD `_sum_latency` / the VL encoder phase. SOL ported; SOL_FULL kept as a Python-side per-call diagnostic (default-mode entry rejects it). Issue #1456 (site-transfer tie-break order) fixed and anchored by smoke cases. | P2 + gaps |
| **PR-3** | Delete + retire switch | Delete per the keep/delete inventory below; `"python"` value becomes a warning no-op (deprecation shim = folded P4); propose the `.claude/rules` dual-implementation → golden-diff rewrite (human-owned, proposal only). | P3+P4 |

Strictly sequential. PR-2 may start once PR-1 merges; PR-3 waits for PR-1 to
bake one release cycle. Rationale for 3 (not 2, not 5): goldens must merge
green from the live Python path *before* any deletion (a golden captured in
the deletion PR risks self-referential regeneration and reverts with it), and
the flip must bake separately from deletion so a post-flip drift still has a
one-env-var rollback; conversely P0's heavy lifting shipped in #1355 and P4 is
a one-line shim, so neither deserves a standalone PR anymore.

**Done since the original draft:** the 2026-07 opt-in-rust parity scan
completed (`parity-scan-report.md`: gate CLOSED, 0 REGRESSION — historical
evidence for the opt-in path, distinct from PR-1's own forced-rust scan,
which reruns against the flipped default as its merge evidence); engine-step
parity, compile-engine parity, and perf gates in CI; #1355 (SILICON audit +
HYBRID/EMPIRICAL port) merged.

## Motivation

Phase 1.5 (#1200) made Python build the op list and Rust execute it, and
deleted the **Rust** model layer (`models/`, `backends/`, `factory.rs`, …).
#1201 added the capacity API. Both are merged.

But the symmetric duplication is still live, with the polarity flipped:
the **Python** latency-execution stack — `operations/*.py` `query()` bodies,
the `perf_database.py` latency-query methods, and the Python op-walk in
`backends/base_backend.py` — now mirrors the Rust `operators/` +
`perf_database/` + `engine/` code. Two engines compute the same step latency.

The Rust path became the DEFAULT in PR-1 (#1454), and PR-2 closed the
remaining delegations: `should_use_rust_engine_step`
(`sdk/rust_engine_step.py`) now keeps only the explicit `"python"` escape
hatch and the non-`PerfDatabase` duck-type delegation. The paragraphs below
describe the pre-Phase-2 starting point this plan set out to remove.

Phase 2 makes Rust the default execution path and removes the duplicated
Python latency code.

## Goal

> Rust is the only step-latency engine. Python keeps model construction,
> the OpSpec walk, the agg-sweep scheduler, and the memory model — and
> drops the `Operation.query()` latency math that Rust already owns.

After Phase 2:
- `engine_step_backend` defaults to `"rust"`. The `"python"` value is
  retained one release as an escape hatch, then removed.
- `operations/*.py` keeps `__init__` / `get_weights()` / attribute storage
  (load-bearing for `compile_engine` and the memory model); the `query()`
  methods and their query-time helpers are deleted.
- `perf_database.py` keeps data loaders, `system_spec`, and weight/metadata
  accessors; the latency-query methods (`query_gemm`, `query_*`) are deleted.
- `base_backend.py` keeps the agg-sweep scheduler, `_get_memory_usage`, and
  the Rust-routing branch; the Python step-latency branch
  (`_run_context_phase` / `_run_generation_phase`) is deleted.

## Why this is safe (primary-source evidence)

The deletable scope was verified, not assumed:

1. **`Operation.query()` has exactly one consumer — the Python op-walk.**
   A repo-wide `.query(` grep across `memory.py`, `inference_summary.py`,
   `vllm_backend.py` returns empty; the only callers are inside
   `base_backend._run_context_phase` / `_run_generation_phase`. Those are the
   branch Rust replaces.
2. **The memory model does not use `query()`.**
   `base_backend._get_memory_usage` (`:1344`) sums `op.get_weights()`
   (config-time) and reads `database.system_spec["misc"][…]` (DB metadata).
   No latency query. So `get_weights()` and the perf-DB metadata accessors
   must stay; the latency-query methods need not.
3. **`compile_engine` reads instance attributes, never `query()`.**
   The E0 OpSpec audit proved every Rust `Op`
   field maps to a build-time Python `Operation` attribute (`op._n`,
   `op._scale_factor`, …). `_to_opspec` walks attributes; deleting `query()`
   does not touch it.

## Keep / delete inventory

Every candidate is **partial** — none of these files is deleted whole.

| Module | LoC (file) | Delete | Keep | Notes |
| --- | --- | --- | --- | --- |
| `sdk/operations/*.py` | 11 952 | `query()` methods + query-time helpers (the latency math, dominant fraction) | `__init__`, attribute storage, `get_weights()`, `get_*` config accessors | `compile_engine` (`_to_opspec`) + memory model depend on the kept parts. |
| `sdk/backends/base_backend.py` | 1 429 | Python step-latency branch: `_run_context_phase`, `_run_generation_phase`, the `else` after `should_use_rust_engine_step`, Python mixed/decode-step math | agg-sweep scheduler (`run_agg`, `find_best_agg_result_under_constraints`), `_get_memory_usage`, Rust-routing branch, cache-key helpers | After the flip the Python branch is dead; the scheduler still drives and calls the Rust step helpers. |
| `sdk/perf_database.py` | 2 474 | latency-query methods (`query_gemm`/`query_attention`/`query_moe`/… — callers were `operations.query()`, now deleted) | parquet loaders, `system_spec`, weight/metadata accessors, support-matrix reads | Consumed by memory model, support matrix, `task.py`, `predict*` — those stay. |
| `sdk/interpolation.py` | 785 | nothing yet | all | Still used by `perf_database.py` (kept readers). The `operations/*.py` callers go away, but it is not orphaned. Re-audit at delete time. |
| `sdk/inference_session.py` | 1 888 | nothing structural | all | `run_static`/`run_agg` are thin delegations to `base_backend`; the op-walk is not here. Stays as orchestration. |
| `sdk/rust_engine_step.py` | 479 | the `should_use_rust_engine_step` gate (once `"python"` is removed) | the Rust step-estimate helpers + handle cache | Becomes unconditional; the live bridge. |

Rough net: ~3–5 kLoC removed from Python, 0 added. (Measure the exact
`query()`-only fraction at implementation time via an AST pass; file totals
above are the upper bound.)

## Gates

Three hard gates, in order. Do not collapse them into one PR.

### Gate 1 — Default flip, scan- and DRIFT-gated

Flip `engine_step_backend` default to `"rust"`. Before merge:
- Full-matrix scan must hold the Phase 1.5 bar: `STRICT_PASS >= 1906`,
  `REGRESSION == 0`.
- The residual DRIFT entries (current list in the completed scan,
  `parity-scan-report.md`) were held out of Phase 1.5 scope.
  A *global* default flip silently ships them.
  Each must be either resolved or **formally accepted** (listed by family,
  with the >5% throughput delta documented) as a precondition. Decide and
  record: is the flip global, or staged per-family (the `rust_engine_step.py:382`
  comment hints some families already default to Rust)?
- The smoke harness (`parity_tests/test_engine_step_parity.py`,
  `test_compile_engine_parity.py` — 346 golden-backed case/surface pairs as
  of PR-2) passes bit-identical-or-within-tolerance.

**No deletion in this PR.** Both engines stay; only the default changes.

### Gate 2 — Golden capture (replace the live differential oracle) — SATISFIED (PR-2)

The parity tests compared **Python vs Rust live**; deleting the Python path
would have destroyed the regression detector future Rust changes rely on.
PR-2 delivered:
- Python `run_static` / step-latency outputs captured as golden fixtures
  across the full smoke matrix (`parity_tests/goldens/`, including error
  classes — error symmetry is a first-class golden value — plus per-op
  latency/energy/source dicts for the compile-engine subset).
- `test_engine_step_parity.py` / `test_compile_engine_parity.py` assert
  **Rust vs golden**; `regenerate_goldens.py` (Python side pinned, thread
  caps pinned, byte-idempotent) is the sanctioned regeneration path.
  Anti-vacuous guard tests prove the comparison detects drift.
- The rewiring exposed and fixed a vacuous comparison: the mixed-step
  helper's bare `RuntimeConfig` had rust-routed the "python" side since the
  PR-1 flip (silent self-comparison); it is now pinned to the Python step.

### Gate 3 — Delete the duplicated Python latency code

Only after Gates 1–2 hold and have soaked one release cycle:
- **Per-op breakdown precondition — SATISFIED (PR-2)**: the op-list
  evaluation FFI exposes per-op `(name, latency_ms, energy_wms, source)`
  and the bridge populates the `InferenceSummary` per-op dicts with real op
  names, energies, and provenance tags; the compile-engine parity subset
  anchors the per-op dicts against goldens.
- Delete per the keep/delete table.
- Remove the `"python"` value of `engine_step_backend` (and the
  `should_use_rust_engine_step` gate); the CLI/SDK arg becomes deprecated
  no-op for one cycle, then dropped.
- Re-run goldens (Gate 2) + smoke harness; numbers unchanged.

## PR sequence (SUPERSEDED — see "Revised PR sequence (2026-08-01)" above)

| # | PR | Lands | Gated? |
| --- | --- | --- | --- |
| **P0** | DRIFT triage | Resolve or formally accept the residual DRIFT entries; record the decision alongside `parity-scan-report.md`. No code beyond fixes. | — |
| **P1** | Default flip | `engine_step_backend` defaults to `"rust"`; `"python"` retained. Full scan + smoke green. Both engines present. | **GATE 1** |
| **P2** | Golden oracle | Capture Python goldens; rewire parity tests to Rust-vs-golden. | **GATE 2** |
| **P3** | Delete Python latency path | `operations/*.py` `query()`, `base_backend` Python branch, `perf_database` latency-query methods. Re-audit `interpolation.py`. | **GATE 3** |
| **P4** | Retire the switch | Remove `should_use_rust_engine_step` + the `"python"` arg value (deprecation cycle elapsed). | parity re-run |

P0 → P1 → P2 → P3 → P4, strictly sequential. P2 may start once P1 is in.

## Out of scope

- Re-running collectors or regenerating perf-DB data.
- Perf-DB schema or support-matrix CSV format changes.
- CLI / generator / Pareto behaviour changes (the flip is internal to `sdk/`).
- The Dynamo-side `estimate_num_gpu_blocks` rewrite — completed downstream in
  the Dynamo repo; no longer tracked here.
- The `#1208` OOM-budget-sharing follow-up.

## Risks

| Risk | Mitigation |
| --- | --- |
| Default flip silently regresses a DRIFT family. | P0 gate: triage/accept the residual DRIFT (4 in the completed scan) before P1. |
| Deleting Python destroys the differential oracle. | Gate 2: capture goldens + rewire tests before any deletion. |
| `query()` deletion nicks a kept consumer (memory / OpSpec walk). | AST pass to confirm `query()` has no caller outside the deleted branch; `get_weights()` / attrs / loaders explicitly retained. |
| `interpolation.py` assumed dead but perf-DB still uses it. | Marked "keep, re-audit"; not in P3's delete set without a fresh consumer grep. |
| `rust_engine_step` handle cache or rayon introduces non-determinism once it is the only path. | Smoke harness runs `RAYON_NUM_THREADS=1` and `=8`, asserts identical output (carried over from Phase 1.5 E5). |
| PR-3 deletes the Python step while the Rust path still collapses the per-op breakdown to a synthetic key — SDK users permanently lose op-level latency analysis. | RESOLVED (PR-2): per-op values cross the op-list evaluation FFI and populate `InferenceSummary` with real names/energies/sources; golden-anchored. |
| WideEP/deepep tables are latency-only on the Rust side; the MoE-dispatch wrapper sums only `.latency_ms` of its inner comm results (discarding energy the comm loaders already carry); DSA's `has_power` gate is per-source in Rust vs first-source in Python. Energy silently diverges if power data ships for those tables before the ports. | All dormant today (no shipped comm/wideep/DSA parquet carries power; both engines report 0.0 on every affected path — verified). Thread them together with the first relevant power drop (#1439 territory). |
| Residual exact-tie enumeration-order gaps of the #1456 class (grid_hold `nearest_key` toward-smaller-key; attention `_ref_head_size` / DSA `bs_slice` sorted-vs-insertion order) — all self-documented in code, all fire only on bitwise-equal distance ties. | Pre-existing (not introduced by Phase 2). Fix with the same load-order-record pattern as the GEMM site fix if a real tie ever surfaces in the scan. |

## Acceptance criteria

1. **Default is Rust** with full scan `STRICT_PASS >= 1906`, `REGRESSION == 0`,
   and the residual DRIFT entries (4 in the completed scan) resolved or accepted.
2. **Goldens replace the live oracle**; parity tests are Rust-vs-golden and green.
3. **Python `query()` latency math removed**; `compile_engine` and
   `_get_memory_usage` unaffected (their kept dependencies proven).
4. **No CLI / generator / Pareto behaviour change.**
5. **LoC discipline:** net −3 to −5 kLoC on `sdk/`; `sdk/models/` unchanged.

## Pointers

- Completed parity scan (DRIFT list, gate status): `parity-scan-report.md`.
- Scan procedure: `parity-scan-runbook.md`.
- Engine-step / compile-engine / perf gates in CI: `.github/workflows/build-test.yml`.
- The opt-in switch this plan flips: `sdk/rust_engine_step.py`
  (`should_use_rust_engine_step`).
- Empirical-layer port that gates the global default flip: issue #1333.
- Architecture reference: `design_doc.html`.