Repository navigation
Add CoreAIProbedInspector: read intermediates as extra model outputs - #134
Open
cymbalrush wants to merge 12 commits into
Open
cymbalrush wants to merge 12 commits into
cymbalrush wants to merge 12 commits into
Conversation
CoreAIInspector relies on the runtime's intermediate logger, which does not fire for work placed on the neural engine. This copies the program, appends the requested operations' values to the graph's outputs, compiles the copy and runs it, so an intermediate comes back like any other output on any compute unit. The rewrite lives in probes.py and does not need the Python runtime. The inspector keeps compiled copies per set of probed operations.
cymbalrush
marked this pull request as draft
October 19, 2026 23:25
…ction Thread an optional state argument through the inspectors, validator and comparator so stateful models can be debugged. CoreAIProbedInspector now compares each probe set's outputs and state with an unprobed run, within rtol and atol, and reduces the set when they move or when the probed copy fails to compile or run. Operations that cannot be read this way are reported in perturbing_ops and failed_ops. create_comparator_for_programs can read target values with the probed inspector via target_inspector='probed'.
PerturbationPolicy and TargetInspector replace string options and keep the string values working. The probed inspector tests now cover its main behaviors.
benchmark, benchmark_coreai_program and compare_runs take an optional state. Every run, warmup included, starts from a fresh copy, so no run is timed on the state a previous one left.
Add module_output_op_ids: which Core AI operations hold each module instance's output. A module's output is a torch node that something outside the module reads; the operation holding it is the one realizing that node (the largest torch id in its location). The module is left out rather than guessed when the value is not certain: no or several realizing operations, a multi-output node, or a reader that left no operation of its own (batch norm folded into a conv). Read stack traces through fused locations. get_stack_trace returns nothing for a fused location, which core-elide-broadcast builds for an nn.Linear's matmul, so those operations had no module. operation_stack_traces returns every stack on an operation, and operation_stack_trace the one it belongs to; module paths and the benchmarker use the latter. Record the FX graph while converting (TorchConverter.torch_graph) so a module's output node can be found, which the Core AI program alone cannot say. Normalise OpResult.owner to a plain Operation before the isinstance checks. Some Core AI builds return the typed op view, which failed them, so no location or output map was recorded at all.
Take a module path, resolve the Core AI operations that certainly hold its output with module_output_op_ids, and return their values from a CoreAIProbedInspector or a CoreAIInspector. Raise ModuleOutputUnknownError when the module is absent or its output is not certain, rather than guess.
module_output_op_ids and get_module_outputs no longer need the converter or the FX graph. The converter now records, on each graph's location, a `source_nodes` metadata: the ATen target of every node converted into the graph and the id of its output node. It survives compilation, is saved with the asset and comes back out of a loaded model. An operation is owned by the largest torch id in its location that is not shape-only. A module's output is an owned operation that an operation of another module reads, or that returns the program's output. The module is left out when an id between the operation's owner and its reader's owner shows up in no operation and is not known to be shape-only: a node was folded away without a trace (batch norm into a conv), or its kind is unknown. Without the table no id is known to be shape-only and only the root may take the program's output, so fewer modules are reported; with no debug info none.
Split module_output_op_ids out of modules.py into module_outputs.py, built from a _Boundaries class and a three-way _Verdict (the value stays, leaves, or is not certain) instead of one long function. The behaviour is unchanged by the move. Two changes recover modules that were left out: - a value only written to state (create_token, read_handle, write_handle) is not a module output and no longer makes a module uncertain, so a stateful model with several outputs keeps its root; - expand returns its input's values, so it is shape-only like view and no longer blocks a boundary it sits on. Move the module-output tests to test_module_outputs.py and the stack-trace tests to test_source_annotator.py. Add a test that a stateful two-head model returns its two heads, matching torch.
…ison A search consumes its strategy: ExhaustiveStrategy yields its single batch once and keeps what it was told, LevelOrderStrategy narrows its depth range and keeps its results. Validator.check and Comparator.compare reused the strategy as it was, so a second call on the same object yielded nothing (the comparator compared no operations and reported no failures) or returned the first call's findings (the validator reported no infinities for an input that has them). Add SearchStrategy.reset, implemented for both strategies and used by their constructors, and call it at the start of each search. Add tests that call a validator and a comparator repeatedly with other inputs and state, which fail without the reset, and tests for the strategies' reset.
cymbalrush
marked this pull request as ready for review
October 23, 2026 01:13
The node table on a graph's location was found by scanning the printed location for its brace-delimited data. Find the entry by name in the location's metadata list and print that attribute alone instead, so MLIR parses exactly the table. A build whose locations do not list their metadata has no table to read, so the reduced behaviour applies there. Drop the scanner and its tests, and test that a program converted without debug info reports no modules.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add CoreAIProbedInspector: read intermediates as extra model outputs
Why
CoreAIInspector reads intermediates through the runtime's intermediate logger, which does not fire for work placed on the neural engine, so on the ANE it returns None for every op. The ANE does not natively expose intermediates, so the only route today is to make them outputs of the model.
How it works
CoreAIProbedInspector(program, function_name="main", specialization_options=None, max_cached_models=4) implements the usual Inspector interface, so it drops in where CoreAIInspector is used. For get_intermediates_for_ops(op_ids, inputs, state=None):
The rewrite lives in probes.py and does not need the Python runtime, so it can be reused elsewhere:
Other behaviors:
Be careful: probed values are not the original model's values
Every probe changes the model, so treat results as error-prone.
Not included
Module-level probing, which would choose probe points at module boundaries, is not part of this PR. Module attribution is incomplete for some ops. This PR probes whichever op IDs it is given.
Testing
tests/debugging/test_probed_inspector.py (7 tests) covers:
They pass on the default runtime. I also ran the first 5 on the neural-engine setting, but the cache tests were added afterwards and have only run on the default runtime. They use a small fp16 toy model. I have not run this on a large model or a real KV-cache model, and I have not measured the compile-time saving.