Repository navigation
feat(native-decode): auto-derive io kv/state_pairs from graph ports (#384) - #573
Conversation
…384) Stock qwen3.x linear-attention hybrid exports (dense GQA + recurrent conv_state/recurrent_state layers under a Scan body), incl. qwen3.6-27b-int4, declare no model.io block, so native load failed at token-input resolution and could not classify the recurrent running-state ports. Add an additive loader fallback: when io is None, derive the decoder I/O topology from the graph ports. - genai-config: extract decoder_state_from_patterns (pure core of strict_decoder_state) and add pub GenAiConfig::derive_decoder_io_from_graph, reusing the guarded suffix-based recurrent-state derivation. A StateShapeMatch param keeps strict_decoder_state Exact (unchanged) while the fallback allows a fully-symbolic present.* state shape to pair with its concrete past_* input. - engine loader: build ModelGraphInfo from session ports and engage the derived spec ONLY when it yields >=1 recurrent state pair. Declared io always wins; pure-dense decoders (0 state pairs) keep their existing path unchanged. Correctness: auto-derived native CUDA decode of the STOCK 27b (no overlay) is byte-identical to the native CPU fp32 oracle (ORT-CUDA crashes on this class), reproducing the exact #384 re-probe sequence [11751,13,271,248068,...]. Runs eager (~5.9 tok/s): Scan declines CUDA-graph capture (separate perf lane). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #573 +/- ##
==========================================
- Coverage 81.33% 80.74% -0.59%
==========================================
Files 315 315
Lines 123688 123734 +46
Branches 123688 123734 +46
==========================================
- Hits 100597 99913 -684
- Misses 19018 19749 +731
+ Partials 4073 4072 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
✅ Benchmarks — No RegressionComparison of criterion micro-benchmarks: PR head vs merge-base, measured on the same runner in the same job (base first → PR second).
Visual flags: Host infoWhat this cannot catch
|
|
VERDICT: APPROVE Independent review of #573 (auto-derive decoder io kv/state_pairs from graph ports). Reviewed detached at head b2e4a43 in an isolated worktree; read-only, nothing committed. 1. Diff scope + additive-fallback proof — PASS
2. AllowSymbolic relaxation — PASS (cannot over-match, cannot leak into strict path)
Crucially, AllowSymbolic can NOT cause over-derivation: which ports become state pairs is decided by 3. Over-derivation guard is non-vacuous — PASS (mutation has teeth)Unit tests: 27b layout -> 32 kv-pairs + 96 state_pairs; qwen3-0.6b dense -> kv only, 0 state_pairs. All 29 genai-config tests pass (incl. Independent mutation: I disabled the KV exclusion in 4. Byte-identical 27B native vs CPU oracle — RAN, PASSEDRan Result (test passed, 5552s; CPU oracle ~92min, CUDA 19s): Byte-identical, and matches the expected sequence exactly. Note: the test asserts cuda==cpu and len==16; it does not itself hard-code the expected IDs, but the observed IDs match the brief's expectation. 5. No-regression + fmt — PASS
SummaryStrictly-additive fallback, gated to the recurrent-hybrid case declared-io cannot already cover; declared-io path untouched. AllowSymbolic is scoped to the graph fallback, relaxes only shape-equality on name-selected pairs, and cannot leak into the Exact strict path or over-derive dense/cross-attn KV into state. Guard proven to have teeth by mutation. 27B stock native CUDA decode is byte-identical to the CPU fp32 oracle. Ships the token-parity Justin cares about with zero regression risk to currently-loadable models. |
Transpose used launch_metadata, which per call did alloc_raw + htod (perm/stride upload) + synchronize() + free_raw. The synchronize() only guards the immediate free but serializes the stream on every one of the ~624 Transpose calls per qwen3.6-27b decode step, in both the eager (Scan-child) and captured paths, and left Transpose reporting CaptureSupport::Unsupported so it could never fold into the parent CUDA graph. Migrate Transpose to the PersistentMetadata + launch_persistent_metadata pattern already blessed for Expand and Slice (#443/#543): cache the perm/stride buffer, upload/sync once at warm-up, then run with no per-call alloc/htod/sync. Once warmed on its exact shape/dtype/perm signature the kernel reports Supported and errors on mid-capture signature change (identical guard to Expand). Same transpose_bytes kernel and identical metadata bytes -> byte-exact. Measured on stock qwen3.6-27b-int4 (auto-derived io, #573): decode 165.27 -> 156.74 ms/tok (~5.4%, ~1.05x), token IDs byte-identical to the CPU fp32 oracle (ORT-CUDA crashes on this model). Does not beat ORT alone; this is one op of the Lever-2 non-Scan swarm. Tile deferred (separate PR): it shares launch_metadata but also reads repeats device->host via host_ints, needing repeats-warming. Tests: new transpose_warmed_metadata_captures_and_matches_eager EP unit test (unwarmed declines -> warms -> capture+replay byte-identical to eager); full construction_gpu suite 19/19; 27b autoderive oracle e2e and qwen3-0.6b e2e both byte-identical. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
What this unlocks
Stock qwen3.x linear-attention hybrid exports (dense GQA layers interleaved with recurrent conv_state/recurrent_state layers under a Scan body) — including Justin's named qwen3.6-27b-int4 — now load and decode natively with NO hand-written io: overlay. The native loader auto-derives the decoder I/O topology from the ONNX graph ports when the package declares no model.io block.
Before this, the stock 27b export (whose inference_metadata.yaml carries no io block) failed native load at token-input resolution ("3 ports match: input_ids, attention_mask, position_ids"), and its recurrent layers expose no dense KV, so shape inference cannot classify the running-state ports. Lineage: #384 recurrent-state lane.
The change (blast radius: loader fallback + genai-config helper + tests + docs)
Correctness bar (byte-identical vs CPU fp32 oracle)
ORT-CUDA crashes on this model class (internal stl_vector assertion), so there is no ORT reference; the oracle is our own native CPU fp32 backend, which already threads recurrent state.
Honest caveats
Decision note: .squad/decisions/inbox/cohaagen-autoderive-io.md
Working as Cohaagen (EP/runtime engineer). Please review before merge (auto-merge intentionally NOT enabled).