Skip to content

D-TRACT-SHAPE-1: cold shape folding and ON × WHEN × DO admission of the shape ops - #1853

Merged
AdaWorldAPI merged 4 commits into
mainfrom
ccr-0161f442-un1mrc
Oct 30, 2026
Merged

AdaWorldAPI merged 4 commits into
mainfrom
ccr-0161f442-un1mrc

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Lab probe follow-up to #1850–#1852 in crates/tract-harvest-probe. Plan: .claude/plans/d-tract-harvest-0-native-algebra-bridge-v1.md §0.2.

What changes

Classification. fold::classify puts every value in one of four classes: Const (depends only on source constants), Shape (constants plus fully known input shapes), Symbolic (a shape with a symbolic dimension) or Data. On Jina pinned to [1,10], the five requested ops break down as follows.

op Const Shape Symbolic Data
Unsqueeze 0 830 0 58
Shape 0 430 0 0
Gather 0 371 0 2
Concat 0 228 0 56
Reshape 0 58 0 170

Unpinned, Tract's analyse fails at /Flatten, so the Symbolic class is tested on the shape_sym fixture instead.

Folding. At resolution, a node runs once only if it is Const or Shape and either all its inputs are known constants, or it is Shape over a fully known shape. It runs through its own registration: WHEN must be TRUE, then the same DO a run would call. Folded outputs become plan constants that later guards read. A run whose inputs differ from the resolved facts is refused before any step runs. Symbolic and data-dependent nodes stay dynamic.

Fact identity (FactSources::merged). A Tract fact is now accepted for a source value only when exactly one outlet carries that value's label and that outlet is the value's source producer under Tract's naming rule (dr3::admit, dr3::source_producers). A matching label alone is no longer enough.

  • Typed stage: 1,988 of 2,546 facts accepted, 558 rejected (557 from outlets Tract renames during typing, 1 label on two outlets).
  • Inference stage: 0 rejected, so coverage does not change.

Two smaller frontend changes:

  • Tract's TDim (what Tract's Shape emits) is read as int64, the ONNX type.
  • A Tract panic is now a frontend refusal. An out-of-range constant Gather index makes Tract index out of bounds in core/src/ops/array/gather.rs:75.

Registry v2 (lab classids only; no production ClassId minted). All registrations keep the positional arguments and the ON/DO relations.

op classid DO
Shape F32/I64/I32/Bool 0x0F60–63 —
Gather on int64 data 0x0F64 ndarray select
Unsqueeze 0x0F65/66 —
Concat 0x0F67/68 ndarray::concatenate
Reshape 0x0F69/6A —
Cast i64→i64 identity 0x0F11 —
Erf 0x0F33 ndarray::hpc::vml::vserf (Bounded, ≤ 2 ULP)

Scalar-Y Where now calls ndarray::simd::blend_i32_scalar with no new kernel. AttrRule.value: None lets an integer attribute (an axis) be a DO parameter.

Coverage census (Jina, pinned [1,10])

[WHEN-true, WHEN-not-true, unregistered]:

4,440 nodes fold in 7.3 ms (facts take 221 ms). 1,972 folded values are still read by nodes that don't fold (about 20 KB).

Per op:

  • Unsqueeze 888/0/0
  • Shape 430/0/0
  • Gather 371/0/2 (the 2 are f32 embedding data and bool data)
  • Concat 284/0/0
  • Reshape 170/56/2. The 56 are UNKNOWN because their target comes from unregistered int64 arithmetic.

Every registered shape-class node that doesn't fold is blocked upstream, never by its own WHEN. The unregistered shape-class nodes are int64 arithmetic: Mul 115, ConstantOfShape 59, Equal 58, Where 58, Div 56, Range 4, Expand 2 and a few singletons.

Measured

ORT 1.29.0, ORT_DISABLE_ALL, frozen in fixtures/tiny/ort.json; AVX-512 host, target-cpu=native.

  • Fidelity: shape_fold matches ORT bit for bit. Erf is within 2 ULP of ORT inside the graph. On the 4,115-point Erf sweep (dense [-6,6] plus signed zeros, infinities, NaN and subnormals) it is at most 2 ULP from both f64 erf rounded to f32 and ORT; the special values are exact. The earlier fixtures are unchanged.
  • Cold: shape_fold tile+fold+resolve 12.4 µs (2 steps, 4 folded). Jina slice 20 µs (9 steps, 4 folded Constants).
  • Hot:
    • shape_fold: 1.7 µs, 37 allocations.
    • Fixture A: 3.3 µs, 55 allocations (56 before the scalar-Y change).
    • Erf, 4,096 elements: 8.5 µs vs 2.0 µs in Tract. vserf alone takes 7.9 µs.

Tests and disable runs

40 tests pass with --include-ignored (Jina fixtures present). Each disable run below turns its named test red:

  • no folding
  • label treated as authority
  • two symbols aliased
  • Reshape UNKNOWN treated as TRUE
  • symbolic shape classed as known
  • no stale-input check
  • Gather range always TRUE
  • no panic catch
  • Shape end ignored

One check is not load-bearing and the code says so: the fold's dependency-class skip, because a Symbolic or Data node never has all its inputs known. Re-pinned: full_graph_facts_and_coverage, the RMSNorm slice (now 9 steps + 4 folded), and unregistered_op_is_refused (now Acosh).

Decisions needed

  1. int64 shape arithmetic (Mul, Div, Add, Equal, Where, Range) has no ndarray function. Its semantics must be decided before an exact ndarray request: wrap or refuse on overflow, truncating division, refuse on division by zero. That gap is what keeps the remaining 1,160 nodes unregistered and the 56 UNKNOWN.
  2. Axis as a DO parameter under one classid per (op, dtype), or one classid per axis value.
  3. Erf is admitted as Bounded at 2 ULP. vserf is about 4× slower than Tract's erf on this host, which is worth raising with the ndarray session.
  4. ConstantOfShape and Expand map onto ndarray from_elem / broadcast and are not registered here.

Multi-tile orchestration and full Jina MatMul execution remain separate gates.


Generated by Claude Code

… registrations

Classify every value by what it depends on (constant, known input shape,
symbolic shape, data) and fold, at resolution, every node whose result is
invariant under the source and the resolved input facts, through the
node's own registered WHEN and DO. Symbolic and data-dependent nodes stay
dynamic; a run whose inputs differ from the resolved facts is refused.

A Tract fact is admitted for a source value only from the outlet that is
the value's source producer and only if one outlet carries the label; a
matching label alone is no longer authority. Tract's TDim (its type for
shape values) is read as int64. A Tract panic during analyse or typing is
a frontend refusal instead of a crash.

Registry v2 adds Shape, Gather (int64 data), Unsqueeze, Concat, Reshape,
Cast i64->i64 and Erf (ndarray vserf); scalar-Y Where uses
blend_i32_scalar. Attribute rules can admit an integer parameter (an
axis) without fixing its value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
…y skip is not a guard

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 43e3d98f-f745-4205-b196-26b9442fa654
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review October 30, 2026 10:12
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T10:14:11.202315Z 4ea1c12 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Same data (verified equal after parsing); the file drops from about
13,000 lines to 680.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
@AdaWorldAPI
AdaWorldAPI merged commit 183151c into main Oct 30, 2026
12 checks passed
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.

2 participants