Skip to content

Compile Profiling

PyPTO includes built-in compile profiling that records wall-clock timings at each stage of the compilation pipeline — from frontend parsing through compiler passes and code generation to on-device execution.

Quick Start

Option 1: Environment Variable

PYPTO_COMPILE_PROFILING=1 python3 my_program.py

Unsetting this variable (or setting it to 0) releases the environment-created profiler on the next lookup, so ordinary JIT calls can reuse cached artifacts again. An explicit with CompileProfiler() context remains active until its scope ends, independently of the environment variable.

Option 2: ir.compile() Parameter

output_dir = ir.compile(program, profiling=True)
# Results are written to output_dir/report/pipeline_profile.{txt,json}

Option 3: Context Manager

from pypto.compile_profiling import CompileProfiler

with CompileProfiler() as prof:
    @pl.program
    class MyProgram:
        ...
    ir.compile(MyProgram, ...)

print(prof.summary())
prof.to_json("profile.json")

Option 4: RunConfig

from pypto import ir
from pypto.runtime import RunConfig

config = RunConfig(compile_profiling=True)
compiled = ir.compile(MyProgram, **config.compile_kwargs())
# The report lands in `<compiled.output_dir>/report/`.

Output

Human-Readable Summary (pipeline_profile.txt)

PyPTO Compile Profile
======================
Total: 2.847s

  parse                            0.023s  ( 0.8%)
  passes                           1.204s  (42.3%)
    UnrollLoops                     0.012s  ( 0.4%)
    ConvertToSSA                    0.034s  ( 1.2%)
    ...
    AllocateMemoryAddr              0.156s  ( 5.5%)
  codegen                           0.418s  (14.7%)
    kernel_codegen:my_kernel        0.312s  (11.0%)
    orchestration_codegen           0.106s  ( 3.7%)
  device_execution                  1.202s  (42.2%)

Structured JSON (pipeline_profile.json)

{
  "total_seconds": 2.847,
  "stages": [
    {"name": "parse", "seconds": 0.023, "children": []},
    {"name": "passes", "seconds": 1.204, "children": [
      {"name": "UnrollLoops", "seconds": 0.012, "children": []},
      {"name": "ConvertToSSA", "seconds": 0.034, "children": []}
    ]},
    {"name": "codegen", "seconds": 0.418, "children": [
      {"name": "kernel_codegen:my_kernel", "seconds": 0.312, "children": []},
      {"name": "orchestration_codegen", "seconds": 0.106, "children": []}
    ]}
  ]
}

Stage Hierarchy

These are all the stages the profiler can record. A single run reports only those its entry point reaches:

Stage Description
compile Full compilation (wraps ir.compile())
parse @pl.program decorator AST parsing
passes Pass pipeline execution
Per-pass stages Individual pass timings (e.g., UnrollLoops, AllocateMemoryAddr)
codegen Code generation
kernel_codegen:<name> Per-kernel PTO/ptoas codegen
orchestration_codegen Orchestration C++ codegen
golden_write Golden reference file generation
device_execution On-device compilation and execution (Simpler)

When using ir.compile() directly, only passes and codegen (with sub-stages) are recorded.

Programmatic API

from pypto.compile_profiling import CompileProfiler, get_active_profiler

# Check if profiling is active (explicit or via env var)
prof = get_active_profiler()

# Use as context manager
with CompileProfiler() as prof:
    # Record custom stages
    with prof.stage("my_custom_stage"):
        do_something()

    # Access results
    data = prof.to_dict()      # dict
    text = prof.summary()      # human-readable string
    json_str = prof.to_json()  # JSON string
    prof.to_json("out.json")   # write to file
    prof.write_report("dir/")  # write both .txt and .json

Overhead

When profiling is not enabled (the default), the overhead is a single CompileProfiler.current() null-check per stage boundary — effectively zero.

When profiling is enabled, each stage records two time.perf_counter() calls (sub-microsecond on modern hardware).

  • Runtime DFX (RunConfig.enable_chip_swimlane, enable_dump_args, enable_pmu, enable_dep_gen) drives Simpler's per-task diagnostic artefacts — swimlane records, tensor I/O dumps, AICore PMU CSVs, and simpler dep_gen edges. The four flags are independent, share <work_dir>/dfx_outputs/ as their output root, and are orthogonal to compile profiling. See 03-runtime-dfx.md.