Debugging#
LEAPP provides multiple lines of defense against an incorrectly traced graph. The goal is confidence that the traced graph is numerically correct.
When |
Use |
|---|---|
You want numeric confidence in an export |
|
Validation failed and you need the recorded ops |
|
You need to tell tracing from export from patching |
|
Confirm the export#
compile_graph(validate=True) checks that each exported node matches
the traced Python on the inputs captured during tracing. LEAPP runs the
exported model, compares outputs with
torch.allclose(..., rtol=..., atol=...), logs any deviation, and
returns a dict mapping node names to results.
leapp.compile_graph(
validate=True,
rtol=1e-3,
atol=1e-5,
strict=True,
)
strict=True raises if any node fails. strict=False still runs
validation, but leaves the result dict available. Nodes with no compiled
model, such as non_traced or dry-run cases, are skipped and treated
as successful.
A mismatch log includes the node and output names, the sample index, tolerances, shapes and dtypes, value ranges, and absolute-difference percentiles. Several samples are what make those numbers meaningful: one pair of tensors cannot characterize error, and it cannot show whether a value was captured because it is computed or because it happened to be constant on the first step.
Run more than one step#
Warning
Loop the annotated policy. A graph that matches the first observation can still have inlined a live input as a constant, and a single sample says nothing about error on any other input.
leapp.start(name="policy_graph", max_cached_io=5)
for _ in range(5):
obs = annotate.input_tensors("policy", {"obs": next_obs()})
action = policy(obs)
annotate.output_tensors(
"policy", {"action": action}, export_with="jit",
)
leapp.stop()
leapp.compile_graph(validate=True, strict=True)
Each iteration is stored as a sample, up to max_cached_io.
compile_graph(validate=True) replays the export against every one.
Use inputs that look like deployment: different state, commands, and
timing, not copies of the first frame. If a later step fails while the
first passes, the log names that sample N.
Re-entry also checks names, shapes, dtypes, and connectivity against
the first step. Those failures show up during tracing, not at
compile_graph().
Inspect what was captured#
Every leapp.start() call writes a LEAPP log file in the graph output
directory:
leapp.start("debug_policy", save_path="exports")
creates exports/debug_policy/log.txt. The file includes DEBUG
output. The console is quieter and shows warnings and errors. Pass
verbose=True to stream the same detail to the console:
leapp.start("debug_policy", save_path="exports", verbose=True)
# ... run your traced code ...
leapp.stop()
leapp.compile_graph(validate=True, verbose=True)
leapp.start(..., verbose=True) covers trace-time diagnostics.
leapp.compile_graph(..., verbose=True) covers compilation, export,
and validation.
FX graph#
For each traced node, LEAPP writes the full torch.fx.Graph to the
log after building the node’s fx.GraphModule. Search for
Compiled graph module for <node name> in log.txt:
[DEBUG]: Compiled graph module for policy:
graph():
%obs : [num_users=1] = placeholder[target=obs]
%linear : [num_users=1] = call_module[target=linear](args = (%obs,), kwargs = {})
%tanh : [num_users=1] = call_function[target=torch.tanh](args = (%linear,), kwargs = {})
return tanh
[DEBUG]: Graph module inputs: [...]
[DEBUG]: Graph module outputs: [...]
This is the graph LEAPP hands to the export backend. Use it to check
that annotated inputs became placeholders, that operations recorded as
the operators you expected, that unused inputs were trimmed, and that
the graph outputs match what you passed to annotate.output_tensors().
Graph image#
On Python 3.11+, leapp.compile_graph(visualize=True) writes a PNG
of the node graph, which is the fastest check that nodes connected the
way you intended and the dtypes are as you expect.
Isolate the failure#
These flags turn pieces of a LEAPP session off so you can tell whether a bad result comes from tracing, from export, or from LEAPP’s own patches.
Skip export#
Tracing is what produces the values that carry graph connectivity, so it cannot be skipped while still producing a graph. Export can.
leapp.start(..., dry_run=True)traces every node and writes YAML, but exports no models. Use it to inspect boundaries, I/O, and connectivity without paying export cost.leapp.start(..., non_traced=["some_node"])does the same for listed nodes only: they still appear in the graph and still connect to neighbors, but they produce no model artifact.export_with=Noneon a singleoutput_tensors()call skips export for that node only.
Warp graphs still run the annotated path a second time and still report a segment that diverges between the two runs. Only APIC capture is skipped, because nothing consumes the captured bundle when the node is not exported.
leapp.start("debug_graph", dry_run=True)
# ... run your traced code ...
leapp.stop()
leapp.compile_graph()
Disable session patches#
leapp.start(..., global_patching=False) turns off the conversions
LEAPP patches for the tracing session (for example NumPy and torch
interop). Use this only to test whether those patches are involved in
a failure.