Provenance Audit — Analysis
Companion to
provenance_audit.md(the measurement-only report) for torch-spyre#2574. Reference model: 1-hidden-layer MLP (128→256→128, fp16), torch 2.11. This document interprets the measured numbers; all claims trace to the audit capture (provenance_capture_raw.json), the emittedsdsc_*.jsonbundles, or cited source.⚠️ Specific to one audit run —
provenance_audit.mdGenerated:2026-06-24 19:17. That report is regenerated on every run; if its timestamp no longer matches the one above, the counts and findings here may be stale. Re-read against the currentprovenance_audit.mdand update this file.
0. Changes since the previous run
Two measured differences from the earlier capture — both explained by main
advancing between runs (restickify is a deterministic, layout-driven decision,
so this is a compiler-version effect, not randomness), and neither changes the
core finding:
No
restickifysplit this run. LoopLevelIR holds 5 operations; an earliermaininserted 2restickifybuffers (5 → 7), since optimized away for this model. Sooriginsreads 5/5 here, not 7/7.origin_nodeis fully present (✅ 5/5). The earlier run showed it nulled on both matmul buffers (◐ 5/7); here it survives on all five ops. That its coverage shifts across compiler versions is exactly why provenance should rideorigins— the complete set of contributing nodes (stable 5/5) — not the single optionalorigin_nodepointer (fx.Node | None, nullable by design).
Unchanged: the genuine drop is still OpSpec → SuperDSC JSON. The
OpSpec dataclass declares no provenance field, so nothing source-related
reaches the emitted JSON.
1. Field semantics
How each field is structured and how it comes to exist (copied from an upstream node, derived on demand, or generated fresh):
Field |
Represents |
Observed type |
Populated by |
Copied / derived / generated |
|---|---|---|---|---|
|
the user source |
|
Dynamo, trace time |
copied across decomposition via |
|
the |
|
Dynamo |
copied; on module-call nodes only (the two |
|
the source function/op that produced the node |
|
Dynamo |
copied; tracks the source op, survives on anchor nodes |
|
the ATen op this node lowered from (e.g. |
|
Inductor, post-grad |
generated per node; present on all 7 post-grad nodes — the most robust FX field |
|
the pass/transform chain that produced the node |
|
Inductor |
generated as nodes are transformed; present on the anchor nodes (3/7 here) and run-dependent (see §6) |
|
the set of FX nodes that lowered into this buffer |
|
Inductor IR lowering |
generated when fx nodes lower to a |
|
a single representative FX node for this buffer |
|
Inductor IR |
copied; run-dependent — present 5/5 this run, but nulled on both matmul buffers (5/7) in the earlier run |
|
the IR node’s creation-site traceback |
|
Inductor IR |
the attribute exists on the IR node but is never populated for this model — an unused provenance slot ( |
|
source lines derived from the buffer’s |
|
|
derived on demand from |
2. Metadata flow
nn.Module source ── file:line, module tree (ground truth)
│ Dynamo
FX Graph (pre-grad) ─ stack_trace, nn_module_stack, source_fn_stack
│ AOTAutograd: addmm decomposition (linear → permute + mm + add)
│ • anchor nodes (permute/relu) keep stack_trace/nn_module_stack/source_fn_stack
│ • synthesized mm/add keep only original_aten ← partial loss #1
FX Graph (post-grad) + original_aten (all 7), from_node (3/7, run-dependent)
│ Inductor lowering → ComputedBuffer.origins
LoopLevelIR (pre-pass) origins (5/5), origin_node (5/5) entering the passes
│ ↓ pre-scheduling passes mutate in place (no restickify this run; 5 ops)
LoopLevelIR (post-pass) origins (5/5); origin_node (5/5 this run; 5/7 earlier)
│ get_stack_traces() derived from origins (3/5)
│ SpyreKernel.create_op_spec
OpSpec ────────────── the OpSpec dataclass has no provenance field, so origins
│ (still on the buffer feeding create_op_spec) is discarded ← DROP #2
│ codegen/superdsc.py (parse_op_spec / compile_op_spec)
SuperDSC JSON ─────── no provenance field emitted ← DROP #3
(get_kernel_metadata assembles full source mapping at
define_kernel, emitted only as a wrapper comment)
3. Exact drop locations
addmmdecomposition (AOTAutograd).aten.addmm→mm+add; the synthesizedmm/addnodes carryoriginal_atenbut notstack_trace/nn_module_stack/source_fn_stack. Evidence: post-gradmm_default*/add_tensor*have those three fieldsnull(a partial loss —original_atenstill resolves them toaten.addmm).OpSpec carries nothing (DROP #2 — the real drop).
create_op_spec(torch_spyre/_inductor/spyre_kernel.py:516) reads the inputComputedBuffer.origins, but theOpSpecdataclass (torch_spyre/_inductor/op_spec.py:67) declares onlyop, is_reduction, iteration_space, args, op_info, tiled_symbols— no field to hold provenance. Soorigins(present 5/5 on the buffer) is dropped.Serialization emits nothing (DROP #3).
parse_op_spec/compile_op_spec(torch_spyre/_inductor/codegen/superdsc.py) consume onlyOpSpecfields. Confirmed by direct inspection of the emitted bundles (§4):provenance in JSON: ❌for all 5sdsc_*.jsonfiles.
Note on
origin_node: in the earlier run it was nulled on the two matmul buffers between entry and exit ofCustomPreSchedulingPasses(torch_spyre/_inductor/passes.py), reading ◐ 5/7. This run it survives 5/5. Because its coverage is schedule-dependent andoriginsstrictly supersedes it, the audit does not chase the exact nulling pass (see §5/§6).
4. What the SuperDSC JSON actually contains
Two kernels are emitted, 5 sdsc_*.json files in total (one per OpSpec):
sdsc_fused_addmm_linear_relu_0— 4 buffers (op0–op3), kernel metadataOriginal ATen: [aten.linear, aten.addmm, aten.relu], source nodes[x, x_1, x_2]; 4sdsc_*.jsonfiles.sdsc_fused_addmm_1— 1 buffer (op4), kernel metadataOriginal ATen: [aten.addmm]; 1sdsc_*.jsonfile.
Each file is purely the device-execution spec — iteration space, labeled
tensors, the compute op (opFuncName/exUnit), the schedule tree (HBM
allocations + device addresses), and core/work-slice maps. No stack_trace,
origins, origin_node, original_aten, from_node, or any source reference
appears in any file — the factual confirmation of DROP #3. The only
source-aware artifact at this stage is the wrapper comment that
get_kernel_metadata(node_schedule) assembles at define_kernel (the
Original ATen / source-node line above), which is emitted as a code comment,
not into the SDSC JSON.
5. If we add a debug_handle at OpSpec, what can we derive it from?
At create_op_spec the input ComputedBuffer carries origins
(OrderedSet[fx.Node]), present on 5/5 ops. From each origin node’s .meta:
original_aten— present on all origins → always derivable.stack_trace/nn_module_stack/source_fn_stack— present on anchor origins (matmul, relu); absent on the bias-addorigins (op1,op4), which can therefore only be sourced to theiroriginal_aten.
Recommended carrier: origins. Do not use origin_node (run-dependent —
5/5 here but 5/7 earlier) or from_node (run-dependent, §6). Equivalently,
get_kernel_metadata(node_schedule) at define_kernel already aggregates this
per kernel and is keyed by kernel_name — e.g. sdsc_fused_addmm_linear_relu_0
→ Original ATen: [aten.linear, aten.addmm, aten.relu], source nodes
[x, x_1, x_2]. Phase 2 (#2575): add a debug_handle field to OpSpec,
populate it from origins in create_op_spec, and serialize it through
codegen/superdsc.py. This mirrors upstream’s
_inductor_kernel_provenance_debug_handle (an int handle) in
torch/_inductor/debug.py.
6. Caveats for implementation
from_nodeis run-dependent. This audit (a normal compile) shows it on 3/7 post-grad nodes — the anchor nodes; it rises to 7/7 when debug/provenance-tracking is active. Do not assume it exists in production compiles.origin_nodeis run-dependent. Present 5/5 this run, but nulled on the matmul buffers (5/7) in the earlier run. Coverage depends on the pass schedule, soorigins(5/5 in both runs) is the stable carrier.tracebackis an unused slot. The attribute exists on the IR node (confirmed viahasattrin the capture) but is never populated for this model — so it reads ❌ (reachable but empty) at the IR stages, not ➖. A profiler could populate it, but nothing does today.Op count is schedule-dependent. This run produced 5 LoopLevelIR ops; the earlier run produced 7 (2
restickifybuffers). A regression suite (#2581) should assert provenance coverage (every op carriesorigins), not a fixed op count.Upstream
inductor_provenance_tracking_node_mappings.jsonis not emitted by the Spyre backend today; Phase 3b should emit a compatible artifact sotlparsetooling works (RFC 0601 §4.7).