Debugging¶
Legacy Python API examples:
platform=calls on this page are retained for compatibility. They emitDeprecationWarning; new code should usebackend_typeandexecution_mode. See the target mapping.
Error types, log levels, and reading the IR the compiler produced.
Concept¶
PyPTO separates two kinds of failure, and the distinction is the first thing to read off an error. A user error means the input was invalid — a shape that cannot work, an operator that does not support the dtype — and the message names what to change. An internal error means an invariant the compiler maintains was violated, which is a compiler bug whatever your input was.
Both arrive as Python exceptions. What separates them is the type and the wording.
Quickstart: read the IR the compiler produced¶
import pypto.language as pl
import torch
from pypto.runtime import RunConfig
CFG = RunConfig(platform="__PLATFORM__")
torch.manual_seed(0)
A = torch.randn(64, 128, dtype=torch.float32)
@pl.jit
def scale(a: pl.Tensor, out: pl.Out[pl.Tensor]):
with pl.at(level=pl.Level.CORE_GROUP):
out[:] = pl.mul(a, 2.0)
return out
prog = scale.lower(A, torch.zeros(64, 128)) # passes only: no codegen, no artifacts
src = prog.as_python() # the lowered IR, as DSL source
# What comes back is post-pass IR, not what you wrote: the pl.at scope has been
# outlined into its own InCore function.
assert "@pl.program" in src
assert "def scale_incore_0(" in src
assert "pl.at" not in src
print(prog.as_python(concise=True)[:200]) # concise= drops intermediate type annotations
lower() is the cheap form — it runs the passes and returns the Program without writing
anything. as_python() is available on a Program or a single Function; a JITFunction
has none of its own, because the IR does not exist until a specialization does.
Expect the IR not to look like your source. The assertions above are the point: by the
time the passes have run, pl.at is gone — the region became scale_incore_0, an AIV
function with a SubWorker role, and its tiles carry explicit pl.MemRef allocations.
Reading a dump means reading that form, not yours.
Mechanics¶
Error types¶
| Type | Means | What to do |
|---|---|---|
pypto.Error |
Base of the PyPTO hierarchy | — |
ValueError / TypeError / IndexError |
A user error raised by a CHECK |
Fix the input; the message states what was expected and what arrived |
pypto.InternalError |
An invariant broke — a compiler bug | File it, with the IR that reproduces it |
PartialCodegenError |
Codegen produced some kernels and failed on others | The report names which; usually a ptoas rejection |
An internal error says so in its text (Internal error: ...) and carries the source
location of the check that failed. That location is inside the compiler, not inside your
kernel — the DSL span, when there is one, is printed alongside it.
Log levels¶
NONE / FATAL / ERROR / WARN / INFO / EVENT / DEBUG, increasing in volume.
INFO is the default.
Environment variables¶
| Variable | Controls |
|---|---|
PYPTO_VERIFY_LEVEL |
Default IR verification level when no PassContext sets one |
PYPTO_WARNING_LEVEL |
Default diagnostic phase gate |
PYPTO_PROG_BUILD_DIR |
Base directory for generated artifacts (default build_output) |
PYPTO_EMIT_PTO_LOC |
Carry DSL source locations into the emitted .pto |
PYPTO_COMPILE_PROFILING |
Per-stage compile timing |
PYPTO_EMIT_DEBUG_RUNNER |
Emit the standalone debug runner beside the artifacts |
Each is a default only: an explicit argument or an active PassContext overrides it.
Pass dumps¶
dump_passes=PassDumpLevel.EXPLICIT writes passes_dump/NN_after_<PassName>.py in
execution order. Two pages read them: this one, for "which pass changed my IR", and the
memory map, for what the allocation looks like.
Diffing two adjacent dumps is the mechanical way to attribute a change to a pass.
CompiledProgram.validate_ir automates the semantic half of that comparison — see
Precision, including the note about its tolerances.
Round-tripping IR through text¶
A program can be written out as DSL source and read back:
| Direction | Call |
|---|---|
| IR → text | program.as_python() |
| text → IR | pl.parse_program(code) |
| file → IR | pl.loads_program(path) |
examples/utils/parse_from_text.py is the worked version. The round trip is also what
VerificationLevel.ROUNDTRIP exercises on every pass, which is why it is slow enough to be
opt-in.
Edge Cases¶
| Symptom | Likely cause | Fix |
|---|---|---|
JITFunction has no as_python |
The IR does not exist until specialization | kernel.lower(*args).as_python() |
| Error names a compiler file, not your kernel | It is an InternalError |
File a bug; do not work around it |
ptoas compilation failed: with an empty message |
The ptoas binary crashed | Point PTOAS_ROOT at a working version |
No passes_dump/ |
lower() writes no artifacts |
Use compile() with dump_passes= |
An internal error is not yours to route around. Silencing it with a shape change or a different spelling hides an invariant violation that will resurface somewhere less convenient.
See Also¶
- Torch codegen — running the IR's semantics on the host.
- Memory map — the other reader of pass dumps.
- Precision — the workflow these tools plug into.
- Error handling — the
CHECK/INTERNAL_CHECKcontract behind the types above.