Skip to content
10 changes: 5 additions & 5 deletions .claude/skills/fmt-coding-style/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ the long line — when a statement cannot be split into ops (a single
120 columns instead of trailing one keyword onto a continuation line.

```python
# before — 96 + 15 columns split across two lines
# before — 92 + 23 columns split across two lines
next_hidden_spec = TensorSpec("next_pre_hc_hidden", [N_RANKS, T, HC_MULT, D], torch.float32,
is_output=True)
init_value=torch.zeros)

# after — 112 columns, one line
next_hidden_spec = TensorSpec("next_pre_hc_hidden", [N_RANKS, T, HC_MULT, D], torch.float32, is_output=True)
# after — 116 columns, one line
next_hidden_spec = TensorSpec("next_pre_hc_hidden", [N_RANKS, T, HC_MULT, D], torch.float32, init_value=torch.zeros)
```

Past ~120, go back to Rule 1: split the op, or group the arguments as below.
Expand Down Expand Up @@ -163,7 +163,7 @@ Structures that stay one-item-per-line:

- the **kernel signature** — one annotated parameter per line, already the
convention;
- the **harness call**, `run_jit(...)` / `run(...)` — leave it exactly as it is.
- the **harness call**, `run(...)` — leave it exactly as it is.
One option per line is what makes it easy to toggle `compile_cfg` /
`runtime_cfg` / `rtol` / `compare_fn` entries while testing;
- list literals whose elements are themselves full-width expressions, such as
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/test-with-golden/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ snapshot-layout, invalidation, and CLI-wiring reference.
whose specs, inputs, golden logic, and intended numerics remain unchanged.
Do not use it for a precision investigation that needs fixture, seed,
dtype, rounding, quantization, or reference changes.
2. Inspect the target's `__main__` and its `run` or `run_jit` call. Check
2. Inspect the target's `__main__` and its `run` call. Check
whether both `--save-data` and `--golden-data` are already exposed.
3. If either flag is missing, add only the minimal argparse option and keyword
forwarding shown in the canonical guide. Do not change the default run:
Expand Down
29 changes: 14 additions & 15 deletions docs/debug-and-tune/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ does), [performance-tuning.md](performance-tuning.md) (perf), and
[precision-tuning.md](precision-tuning.md) (numerical fidelity — cast modes,
dtype alignment, the `error_distribution` sweep).

The harness exposes most of these as both a `run` / `run_jit` kwarg and a
The harness exposes most of these as both a `run` kwarg and a
CLI flag; a typical model `__main__` wires them up like:

```python
Expand All @@ -18,7 +18,7 @@ parser.add_argument(
)
parser.add_argument("--enable-dep-gen", action="store_true")
...
result = run_jit(
result = run(
fn=indexer_test,
specs=build_tensor_specs(...),
golden_fn=golden_indexer,
Expand All @@ -43,18 +43,17 @@ location. Most compile failures are fixed at the cited site without any
further tooling; read the message before reaching for the heavier
mechanisms below.

- **Compile failure** — scripts using `golden.run` call `ir.compile`
directly, whose default `dump_passes=True` writes per-pass IR under
`build_output/<...>/passes_dump/`. `golden.run_jit` constructs a
`RunConfig`, whose default is `dump_passes=False`; pass
`compile_cfg=dict(dump_passes=True)` when you need the same files. Diff the
last clean pass against the first failing one to see which pass rejected
the IR. `report/` holds scheduling diagnostics.
- **PTOAS failure** — the error quotes the `.pto` op. With `golden.run`,
- **Compile failure** — a `@pl.jit` kernel compiles through a `RunConfig`,
whose default is `dump_passes=False`; pass `compile_cfg=dict(dump_passes=True)`
to write per-pass IR under `build_output/<...>/passes_dump/`. A `@pl.program`
kernel goes through `ir.compile`, whose default `dump_passes=True` writes
those files already. Diff the last clean pass against the first failing one to
see which pass rejected the IR. `report/` holds scheduling diagnostics.
- **PTOAS failure** — the error quotes the `.pto` op.
`compile_cfg=dict(skip_ptoas=True)` keeps the raw `.pto` MLIR and stops
before the C++ wrapper, isolating whether the regression is in PyPTO's
IR-to-MLIR path or in PTOAS. `skip_ptoas` is not a `RunConfig` field and
therefore is not accepted by `golden.run_jit`.
IR-to-MLIR path or in PTOAS. It is an `ir.compile` kwarg, not a `RunConfig`
field, so it works on a `@pl.program` kernel only.
- **Runtime crash** — rerun on the matching simulator (`-p a2a3sim` /
`a5sim`); it gives more diagnostic output than the device backend and
reproduces most lowering bugs.
Expand Down Expand Up @@ -183,7 +182,7 @@ pl.dump_tag(h_tile_i8) # capture this one tensor under partial dump
```

```python
run_jit(..., runtime_cfg=dict(platform=..., enable_dump_args=1))
run(..., runtime_cfg=dict(platform=..., enable_dump_args=1))
```

`pl.dump_tag` works on plain function args and on internal
Expand All @@ -202,7 +201,7 @@ python -m simpler_setup.tools.dump_viewer <build_output/.../dfx_outputs/args_dum
```

Pass the dump dir **explicitly** — with no argument the viewer looks under
`./outputs/*/args_dump`, but `run_jit` writes to
`./outputs/*/args_dump`, but `run` writes to
`build_output/<...>/dfx_outputs/args_dump`.

This section is the dump *mechanism*. For the end-to-end
Expand Down Expand Up @@ -240,7 +239,7 @@ round-trip, which drops the read-dep and lets the downstream task race).

| Symptom | Tool | Kwarg / flag |
|---------|------|--------------|
| Compile / PTOAS error | `passes_dump/`; `skip_ptoas` for `golden.run` only | `compile_cfg=dict(dump_passes=True)`; with `run`, `compile_cfg=dict(skip_ptoas=True)` |
| Compile / PTOAS error | `passes_dump/`; `skip_ptoas` on a `@pl.program` kernel only | `compile_cfg=dict(dump_passes=True)`; `compile_cfg=dict(skip_ptoas=True)` |
| Need to reproduce on the same inputs | golden-data replay (§2) | `golden_data=` / `--golden-data` |
| Iterating on generated `.cpp` / `.pto` | runtime-dir reuse (§3) | `runtime_dir=` / `--runtime-dir` |
| Run hangs / deadlocks (§4) | device log | `runtime_cfg["log_level"]="v0"` + `ASCEND_PROCESS_LOG_PATH` |
Expand Down
4 changes: 2 additions & 2 deletions docs/debug-and-tune/performance-tuning.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ compute core.
## Measuring — the benchmark loop (`PYPTO_BENCH`)

Tuning needs a number before and after. Set `PYPTO_BENCH=1` and every
`run` / `run_jit` call in the process times the kernel on device after its
`run` call in the process times the kernel on device after its
correctness dispatch — no `--benchmark` flag, no edit to the model file:

```bash
Expand Down Expand Up @@ -366,7 +366,7 @@ python models/deepseek_v4_flash_mtp/decode_sparse_attn.py -p a2a3 -d 0 --enable-
```

Not every kernel exposes `--enable-pmu`; a kernel that does not can still be
captured by passing `runtime_cfg={"enable_pmu": 2}` to its `run` / `run_jit`
captured by passing `runtime_cfg={"enable_pmu": 2}` to its `run`
call (the harness bundles it into the runtime's DFX options).

For a per-kernel intra-core swimlane, use
Expand Down
2 changes: 1 addition & 1 deletion docs/debug-and-tune/precision-tuning.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ comparator from the golden harness to see the precision distribution instead:
```python
from golden import error_distribution

run_jit(
run(
...,
compare_fn={
"x_next": error_distribution(), # measure, never fails
Expand Down
4 changes: 2 additions & 2 deletions docs/debug-and-tune/ring-heap-and-scope-stats.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ prefill — size the rings explicitly. Each knob takes either a scalar
this ring at the compile default". Precedence is just two tiers now:
`RunConfig` field > compile default.

**This works on the L3 (distributed) path only.** golden's `run_jit` forwards
**This works on the L3 (distributed) path only.** golden's `run` forwards
whatever `runtime_cfg` keys are `RunConfig` fields to the per-rank dispatch,
which builds a `CallConfig` and transcribes the ring sizes into
`runtime_env`:
Expand All @@ -175,7 +175,7 @@ Live examples: [`models/deepseek_v4_pro/prefill_fwd.py`](../../models/deepseek_v
An L2 (single-chip) entry cannot size its rings today, whichever way it is
dispatched:

- Through golden's `run_jit`, the single-chip path calls
- Through golden's `run`, the single-chip path calls
`execute_compiled(work_dir, args, ...)`, which has no ring parameters — a
`runtime_cfg["ring_heap"]` raises `TypeError`.
- Through `CompiledProgram.__call__(*args, config=RunConfig(ring_heap=...))`,
Expand Down
2 changes: 1 addition & 1 deletion docs/get-started/first-kernel.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The file contains four parts:
1. a module-level `@pl.jit` kernel;
2. `TensorSpec` and `ScalarSpec` inputs for the Golden Harness;
3. a PyTorch golden function;
4. a CLI that calls `golden.run_jit` and exits non-zero on failure.
4. a CLI that calls `golden.run` and exits non-zero on failure.

## Run on the A2/A3 simulator

Expand Down
2 changes: 1 addition & 1 deletion docs/get-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ re-cloned rather than reused in place.
## Verify

```bash
python -c "import pypto, torch; from golden import run, run_jit; print(torch.__version__)"
python -c "import pypto, torch; from golden import run; print(torch.__version__)"
python examples/beginner/hello_world.py -p a2a3sim
```

Expand Down
2 changes: 1 addition & 1 deletion docs/models/deepseek_v4_pro/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ python models/deepseek_v4_pro/synthetic_token_loop.py --variant flash \

The full prefill and decode programs carry runtime `num_tokens` and
`moe_epoch_base` scalars in their compiled ABI. Their `ScalarSpec`s use
`compile_runtime=True`, so `run_jit` passes `pl.RUNTIME` during
`compile_runtime=True`, so `run` passes `pl.RUNTIME` during
signature-driven compilation instead of folding the initial values into
generated task arguments. `num_tokens` follows the real prompt/decode row
count, while callers advance the epoch scalar by
Expand Down
2 changes: 1 addition & 1 deletion docs/pypto-coding/distributed-programming.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ The harness compiles an L3 program when `compile_cfg` carries a
```python
from pypto.ir.distributed_compiled_program import DistributedConfig

result = run_jit(
result = run(
fn=l3_moe, # the @pl.jit.host driver
specs=build_tensor_specs(),
golden_fn=golden_moe,
Expand Down
15 changes: 8 additions & 7 deletions docs/pypto-coding/pypto-coding-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,14 @@ Every tensor the golden test compares **must** declare an explicit direction on
the **orchestration entry** — the `@pl.jit` entry, its `@pl.jit.host` driver, or
the `@pl.function(type=Opaque)` / `Orchestration` method. A plain `pl.Tensor` is
treated as `In`: the runtime skips its device→host copy-back, so the tensor
**reads back as all-zeros on the host** and golden silently fails. The direction
is decided by the tensor's `TensorSpec`:

| `TensorSpec` | meaning | annotation |
|--------------|---------|------------|
| `is_output=True`, no `init_value` | pure output (write-only) | `pl.Out[pl.Tensor[...]]` |
| `is_output=True` **and** `init_value` | inout — read-modify-write (e.g. a paged KV cache the kernel reads history from and appends to; recurrent state) | `pl.InOut[pl.Tensor[...]]` |
**reads back as all-zeros on the host** and golden silently fails. The
annotation is the only place direction is declared — the harness reads it back
off the compiled artifact, so a `TensorSpec` never restates it:

| annotation | meaning | `TensorSpec` |
|------------|---------|--------------|
| `pl.Out[pl.Tensor[...]]` | pure output (write-only); validated | no `init_value` needed — the host buffer is not uploaded |
| `pl.InOut[pl.Tensor[...]]` | inout — read-modify-write (e.g. a paged KV cache the kernel reads history from and appends to; recurrent state); validated | `init_value` is the uploaded initial state |

Annotate the **entry only**. `@pl.jit.inline` sub-kernels keep bare `pl.Tensor`:
they are spliced at the call site before SSA conversion, so a parameter is
Expand Down
59 changes: 32 additions & 27 deletions docs/run-and-validate/compile-runtime-workflow.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Compile and Runtime Workflow

What usually happens when you run `python <kernel>.py -p <platform>`.
Most examples and model harnesses use `golden.run` for `@pl.program` kernels
or `golden.run_jit` for module-level `@pl.jit` kernels. A few specialized
smoke, artifact-regeneration, and external-runtime drivers call PyPTO's
compile/runtime APIs directly; their `__main__` blocks are the authority for
what a command actually validates.
Examples and model harnesses go through `golden.run`, which takes a kernel of
either form — a module-level `@pl.jit` function or a `@pl.program` class — and
picks its compile path from it. A few specialized smoke, artifact-regeneration, and
external-runtime drivers call PyPTO's compile/runtime APIs directly; their
`__main__` blocks are the authority for what a command actually validates.

## CLI shape

Expand All @@ -19,8 +19,8 @@ parser.add_argument("--enable-chip-swimlane", action="store_true")
args = parser.parse_args()

result = run(
program=build_qwen3_decode_program(...), # @pl.program class
specs=build_tensor_specs(...), # ordered TensorSpec / ScalarSpec list
fn=qwen3_decode, # module-level @pl.jit function
specs=build_tensor_specs(...), # TensorSpec / ScalarSpec, in fn's param order
golden_fn=golden_qwen3_decode, # PyTorch reference
compile_cfg=dict(dump_passes=True),
runtime_cfg=dict(platform=args.platform, device_id=args.device,
Expand All @@ -29,11 +29,10 @@ result = run(
)
```

A kernel written as a module-level `@pl.jit` function calls **`run_jit`**
instead, passing `fn=<jit_function>` in place of `program=`. Both entry points
share tensor specs, golden computation, runtime dispatch, and validation, but
their `compile_cfg` fields and defaults differ; see
[Compile configuration](#compile-configuration).
A kernel built as a `@pl.program` class passes that program as `fn=`
instead. Both forms share tensor specs, golden computation, runtime dispatch,
and validation; only the compile step and the accepted `compile_cfg` fields
differ, see [Compile configuration](#compile-configuration).

| Flag | Purpose |
|------|---------|
Expand Down Expand Up @@ -83,10 +82,10 @@ each phase, so the console log is the authoritative trace of what ran:

### 1. Compile (pypto)

Driven by the **pypto** repo. `run` calls
`pypto.ir.compile(program, backend_type=..., **compile_cfg)` directly.
`run_jit` builds a `pypto.runtime.RunConfig` from `compile_cfg` and calls
`fn.compile(..., config=...)`, which specializes the JIT function before
Driven by the **pypto** repo. For a `@pl.program` kernel, `run` calls
`pypto.ir.compile(program, backend_type=..., **compile_cfg)` directly. For a
`@pl.jit` kernel it builds a `pypto.runtime.RunConfig` from `compile_cfg` and
calls `fn.compile(..., config=...)`, which specializes the JIT function before
entering the same IR compiler. Both paths run a **pass pipeline** followed by
a **codegen pipeline** and normally write
`build_output/<ProgramName>_<timestamp>/`.
Expand All @@ -96,9 +95,9 @@ a **codegen pipeline** and normally write
`PassManager.get_strategy(strategy).run_passes(program, ...)` runs an
ordered sequence of passes that progressively rewrites the IR. The exact
pass list changes often — consult the pypto repo for the current pipeline,
and look at `passes_dump/` when `dump_passes` is enabled. Direct `run`
compilation inherits `ir.compile`'s enabled default; `run_jit` inherits
`RunConfig`'s disabled default.
and look at `passes_dump/` when `dump_passes` is enabled. The direct
`@pl.program` path inherits `ir.compile`'s enabled default; the `@pl.jit` path
inherits `RunConfig`'s disabled default.

The end state, regardless of which passes ran, is the same:

Expand Down Expand Up @@ -157,7 +156,8 @@ build_output/<ProgramName>_<ts>/

#### Compile configuration

For **`run`**, `compile_cfg` is forwarded to `ir.compile`. Common fields are:
For a **`@pl.program`** kernel, `compile_cfg` is forwarded to `ir.compile`.
Common fields are:

| `compile_cfg` field | Purpose |
|---|---|
Expand All @@ -173,8 +173,8 @@ The harness derives `backend_type` and `platform` from
`runtime_cfg["platform"]` unless the direct compile configuration already
supplies them.

For **`run_jit`**, `compile_cfg` must instead contain fields accepted by
`pypto.runtime.RunConfig`. The JIT layer maps its compile-side fields into
For a **`@pl.jit`** kernel, `compile_cfg` must instead contain fields accepted
by `pypto.runtime.RunConfig`. The JIT layer maps its compile-side fields into
`ir.compile`:

| `compile_cfg` field | Compiler mapping |
Expand All @@ -186,9 +186,10 @@ For **`run_jit`**, `compile_cfg` must instead contain fields accepted by
| `distributed_config`, `analyze_auto_scopes_for_deps`, `memory_planner` | Forwarded when set. |

`output_dir`, `profiling`, `skip_ptoas`, and `verification_level` are not
`RunConfig` field names and therefore cannot be copied unchanged from a
`run` call into `run_jit`. Unknown fields raise while constructing
`RunConfig`; unknown direct-compiler fields raise in `ir.compile`.
`RunConfig` field names, so a `compile_cfg` written for a `@pl.program` kernel
cannot be copied unchanged onto a `@pl.jit` one. Unknown fields raise while
constructing `RunConfig`; unknown direct-compiler fields raise in
`ir.compile`.

To stop after compile without touching the device, see `compile_only` under
[Skipping phases](#skipping-phases).
Expand All @@ -198,7 +199,11 @@ To stop after compile without touching the device, see `compile_only` under
Each entry of `specs` is a `TensorSpec` (named tensor, shape, dtype,
direction) or a `ScalarSpec` (named scalar, dtype, value); see
`golden/spec.py`. The list is ordered to match the parameter order of the
top opaque function. For each entry, allocate a torch tensor:
top opaque function — single-chip and distributed alike. The harness compares
the spec names against the compiled artifact's parameters element by element
and fails with `compiled parameter ABI mismatch (parameter order ...)` before
allocating anything; it never rebinds a mis-ordered list by name. For each
entry, allocate a torch tensor:

- Pure inputs and inout initial values are filled via `spec.create_tensor()`
(`init_value=None` creates zeros; random data requires an explicit factory
Expand Down Expand Up @@ -358,7 +363,7 @@ false; an uncaught compile/runtime exception is already a nonzero failure.

## Skipping phases

`run` / `run_jit` knobs that short-circuit the pipeline:
`run` knobs that short-circuit the pipeline:

| Knob | Effect |
|------|--------|
Expand Down
Loading
Loading