Example Audit: SimpleMLP — Source-to-Kernel Provenance
Generated: 2026-07-15 14:22 | Issue: torch-spyre#2574
One worked example of the provenance audit (see the README for how to run it): SimpleMLP traced in-process through a single cache-defeated torch.compile. Measurement-only — every table and the lineage graph below are computed from the captured compile-path objects.
This is a committed example snapshot: the Generated timestamp and the debug_handle ids are specific to this run and machine (the id is a content hash of the source location, which includes an absolute path), so they change when regenerated — run audit.py to reproduce it for your own setup.
Source → Kernel Lineage
How each source line flows through the pipeline — Source → FX pre-grad → FX post-grad → LoopLevelIR pre-pass → LoopLevelIR post-pass → OpSpec → SuperDSC. A fan-out is a decomposition (e.g. linear → permute + mm + add); a fan-in is a fusion (several OpSpec ops → one kernel). An op with no source of its own attaches to every source-bearing producer whose buffer it consumes (multi-source). Node/IR transformation only — field survival is the matrix below.
flowchart LR
subgraph src_sg["Source"]
src_x___self_fc1_x_["x = self.fc1(x)"]
src_x___self_fc2_x_["x = self.fc2(x)"]
src_x___torch_relu_x_["x = torch.relu(x)"]
end
subgraph pg_sg["FX pre-grad"]
pg_x["x · linear"]
pg_x_1["x_1 · relu"]
pg_x_2["x_2 · linear"]
end
subgraph post_sg["FX post-grad"]
post_permute["permute"]
post_mm_default_1["mm_default_1"]
post_add_tensor_1["add_tensor_1"]
post_relu["relu"]
post_permute_1["permute_1"]
post_mm_default["mm_default"]
post_add_tensor["add_tensor"]
end
subgraph lpre_sg["LoopLevelIR pre-pass"]
lpre_op0["op0"]
lpre_op1["op1"]
lpre_op2["op2"]
lpre_op3["op3"]
lpre_op4["op4"]
end
subgraph lpost_sg["LoopLevelIR post-pass"]
lpost_op0["op0"]
lpost_op1["op1"]
lpost_op2["op2"]
lpost_op3["op3"]
lpost_op4["op4"]
end
subgraph ops_sg["OpSpec"]
ops_op0["op0 · batchmatmul"]
ops_op1["op1 · add"]
ops_op2["op2 · relufwd"]
ops_op3["op3 · batchmatmul"]
ops_op4["op4 · add"]
end
subgraph sd_sg["SuperDSC"]
sd_sdsc_fused_addmm_linear_relu_0["sdsc_fused_addmm_linear_relu_0"]
end
src_x___self_fc1_x_ --> pg_x
src_x___torch_relu_x_ --> pg_x_1
src_x___self_fc2_x_ --> pg_x_2
pg_x --> post_permute
pg_x --> post_mm_default_1
pg_x --> post_add_tensor_1
pg_x_1 --> post_relu
pg_x_2 --> post_permute_1
pg_x_2 --> post_mm_default
pg_x_2 --> post_add_tensor
post_mm_default_1 --> lpre_op0
post_permute --> lpre_op0
post_add_tensor_1 --> lpre_op1
post_relu --> lpre_op2
post_mm_default --> lpre_op3
post_permute_1 --> lpre_op3
post_add_tensor --> lpre_op4
lpre_op0 --> lpost_op0
lpre_op1 --> lpost_op1
lpre_op2 --> lpost_op2
lpre_op3 --> lpost_op3
lpre_op4 --> lpost_op4
lpost_op0 --> ops_op0
lpost_op1 --> ops_op1
lpost_op2 --> ops_op2
lpost_op3 --> ops_op3
lpost_op4 --> ops_op4
ops_op0 --> sd_sdsc_fused_addmm_linear_relu_0
ops_op1 --> sd_sdsc_fused_addmm_linear_relu_0
ops_op2 --> sd_sdsc_fused_addmm_linear_relu_0
ops_op3 --> sd_sdsc_fused_addmm_linear_relu_0
ops_op4 --> sd_sdsc_fused_addmm_linear_relu_0
Stage × Field Matrix
✓ present & non-empty on all instances ◐ on some (n/total) ✗ reachable here but measured empty/absent (dropped) – not created yet here, or carried indirectly inside another field (see that field’s row).
Every column tests population (the field exists and carries non-empty content; 0 counts as content, None/[]/{}/"" do not). Own-slot cells are measured directly; whether an absent field reads carried indirectly (–) or dropped (✗) at the OpSpec / SuperDSC columns is derived from what debug_handle retains, not measured.
The Layer column marks whether a field lives on the FX node (FX), the IR ComputedBuffer (IR), or the debug_handle (Spyre). The two IR columns are the same LoopLevelIR before and after the Spyre pre-scheduling passes: LoopLevelIR (pre-pass) is the lowered IR entering them, LoopLevelIR (post-pass) is after they mutate it in place. These map to issue #2574’s “Inductor passes” → “LoopLevelIR”.
Layer |
Field |
FX Graph (pre-grad) |
FX Graph (post-grad) |
LoopLevelIR (pre-pass) |
LoopLevelIR (post-pass) |
OpSpec |
SuperDSC JSON |
|---|---|---|---|---|---|---|---|
FX |
|
✓ |
◐ 3/7 |
– |
– |
– |
– |
FX |
|
◐ 2/3 |
◐ 2/7 |
– |
– |
✗ |
✗ |
FX |
|
✓ |
◐ 3/7 |
– |
– |
✗ |
✗ |
FX |
|
– |
✓ |
– |
– |
– |
– |
FX |
|
– |
◐ 3/7 |
– |
– |
✗ |
✗ |
IR |
|
– |
– |
✓ |
✓ |
– |
– |
IR |
|
– |
– |
✓ |
✓ |
– |
– |
IR |
|
– |
– |
✗ |
✗ |
✗ |
✗ |
IR |
|
– |
– |
◐ 3/5 |
◐ 3/5 |
– |
– |
Spyre |
|
– |
– |
– |
– |
✓ |
✓ |
Stage 2a — FX Graph (pre-grad): 3 compute nodes
Dynamo traces the model into an FX graph; each node still carries its Python-source metadata.
Tracked fields: stack_trace (user source file:line), nn_module_stack (owning nn.Module path), source_fn_stack (source fn/op that produced it), original_aten (ATen op it lowered from), from_node (producing pass/transform chain).
All observed node.meta keys: ['example_value', 'mutation_region_id', 'nn_module_stack', 'source_fn_stack', 'stack_trace'].
Node |
target |
|
|
|
|
|
source line |
|---|---|---|---|---|---|---|---|
|
|
|
|
|
✗ |
✗ |
|
|
|
|
✗ |
|
✗ |
✗ |
|
|
|
|
|
|
✗ |
✗ |
|
Stage 2b — FX Graph (post-grad): 7 compute nodes
AOTAutograd/Inductor lower and decompose the graph (e.g. linear → permute + mm + add); synthesized nodes keep only part of the metadata.
Tracked fields: stack_trace (user source file:line), nn_module_stack (owning nn.Module path), source_fn_stack (source fn/op that produced it), original_aten (ATen op it lowered from), from_node (producing pass/transform chain).
All observed node.meta keys: ['from_node', 'mutation_region_id', 'nn_module_stack', 'original_aten', 'seq_nr', 'source_fn_stack', 'stack_trace', 'tensor_meta', 'val'].
Node |
target |
|
|
|
|
|
source line |
|---|---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
✗ |
✗ |
✗ |
|
✗ |
— |
|
|
✗ |
✗ |
✗ |
|
✗ |
— |
|
|
|
✗ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
✗ |
✗ |
✗ |
|
✗ |
— |
|
|
✗ |
✗ |
✗ |
|
✗ |
— |
Stage 3 — LoopLevelIR (pre-pass): 5 operations
FX nodes lower into LoopLevelIR ComputedBuffers entering the Spyre pre-scheduling passes.
Tracked fields: origins (FX nodes that lowered into this buffer), origin_node (single representative FX node (nullable)), traceback (IR-node creation-site traceback), get_stack_traces (source lines derived from origins).
All observed ComputedBuffer attributes: ['annotations', 'data', 'layout', 'name', 'operation_name', 'origin_node', 'origins', 'traceback'].
Op |
|
|
|
|
|---|---|---|---|---|
|
|
|
✗ |
✓ |
|
|
|
✗ |
✗ |
|
|
|
✗ |
✓ |
|
|
|
✗ |
✓ |
|
|
|
✗ |
✗ |
Stage 4 — LoopLevelIR (post-pass): 5 operations
The same IR after the pre-scheduling passes mutate it in place.
Tracked fields: origins (FX nodes that lowered into this buffer), origin_node (single representative FX node (nullable)), traceback (IR-node creation-site traceback), get_stack_traces (source lines derived from origins).
All observed ComputedBuffer attributes: ['annotations', 'data', 'dim_hints', 'layout', 'name', 'op_it_space_splits', 'operation_name', 'origin_node', 'origins', 'traceback'].
Op |
|
|
|
|
|---|---|---|---|---|
|
|
|
✗ |
✓ |
|
|
|
✗ |
✗ |
|
|
|
✗ |
✓ |
|
|
|
✗ |
✓ |
|
|
|
✗ |
✗ |
Stage 5 — OpSpec: 5 ops
Each scheduled ComputedBuffer becomes an OpSpec (the device op); debug_handle carries source provenance onto it, while the origins / origin_node shown here are read from the input buffer (not stored on OpSpec).
Tracked fields: origins (FX nodes that lowered into this buffer), origin_node (single representative FX node (nullable)), debug_handle (source-to-kernel record (id, source, aten_op, ir_chain, fused_from)).
OpSpec declared fields: ['op', 'is_reduction', 'iteration_space', 'args', 'op_info', 'tiled_symbols', 'symbolic_dim_bounds', 'debug_handle'].
Spyre op |
buffer |
|
|
|
source line |
|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
— |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
— |
Stage 6 — SuperDSC: 5 sdsc_*.json files (1 kernel)
Each OpSpec is serialized to a sdsc_*.json kernel spec; the debug_handle travels with it (JSON key debug_handle_), resolving each kernel back to source.
Tracked (per debug_handle_): id (stable content hash), source (file:line), aten_op, fused_from (constituent handles when fused).
debug_handle_ present in 5/5 files, non-null in 5/5.
All observed debug_handle keys: ['aten_op', 'fused_from', 'fusion_context', 'id', 'ir_chain', 'source'].
Kernel |
|
Spyre op |
|
source line |
|
|
|---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
— |
— |
|
|
|
|
|
|
— |
|
|
|
|
|
|
|
|
|
|
|
|
— |
— |
|
fused_from lists the constituent handles’ ATen ops (MLIR FusedLoc-style lineage). A — source line means the handle resolves only to an ATen op (its FX node carried no stack_trace), not that the handle is absent.
sdsc_fused_addmm_linear_relu_0
buffers (5):
op0,op1,op2,op3,op4fx origins:
add_tensor,add_tensor_1,mm_default,mm_default_1,permute,permute_1,relukernel metadata:
# Topologically Sorted Source Nodes: [x, x_1, x_2], Original ATen: [aten.linear, aten.addmm, aten.relu]sdsc_*.jsonfiles: 5debug_handle_non-null: 5/5