FAQ and Known Limitations¶
Questions that come up repeatedly, and the constraints behind them.
Writing kernels¶
Why did my kernel run and write nothing?
out = pl.add(a, b) rebinds a Python name; it does not write the output parameter. Use
out[:] = pl.add(a, b), which is sugar for pl.assemble. This is the single most common
first bug, and it fails silently — the run succeeds and the output stays zero. See
Syntax.
Why is a plain range rejected?
The DSL needs to know whether a loop is a compile-time unroll, a device-side loop, or a
parallel dispatch. Use pl.range / pl.unroll / pl.parallel / pl.pipeline /
pl.spmd. See Control Flow.
Why can't I collect TaskIds in a Python list?
The body is traced, not executed, so tids.append(tid) is not a DSL operation. Use
pl.array.create(n, pl.TASK_ID) and index-assign. See
Declaring an edge.
Why does pl.dynamic fail on my @pl.jit entry?
A dynamic axis belongs on an InCore kernel's annotation. Orchestration-level work on a
dynamic-shaped tensor reaches InitMemRef, which needs a constant dim. Keep the kernel
@pl.jit.incore and let the entry stay dynamic while delegating to it. See
Types.
Compiling¶
TypeError: got an unexpected keyword argument from compile().
compile()'s positional arguments are the kernel's own. Compile options travel in
config=RunConfig(...). See Compiling.
The worker rejects my compiled program.
The artifact's platform must match the worker's. Compile for the platform you dispatch
on.
ptoas compilation failed: with an empty message.
That is a crash in the ptoas binary rather than a rejection of your IR. Point PTOAS_ROOT
at a working version.
Running¶
Why is run()'s time so much larger than the kernel?
RunResult.execution_time is total wall clock — compile, golden generation and validation
included. For the device/host split use pypto.runtime.benchmark, whose BenchmarkStats
carries device_wall_us and host_wall_us. See Host.
Why does passing a DeviceTensor to a @pl.jit kernel fail?
A DeviceTensor carries no shape or dtype for the specializer to read. Dispatch a
compiled program instead. See Running.
A capacity error names a ring I never configured.
Ring choice follows scope depth — min(scope_depth, 3) — so a deeply nested kernel
concentrates work on ring 3 and a flat one puts everything on ring 0. Measure with
enable_scope_stats before resizing. See Memory.
Numbers¶
Split-K results differ between runs. Atomic accumulation order across cores is not fixed. Expect last-place differences; that is correct behaviour, not a bug. See Precision.
A correct FP16 kernel fails allclose.
The default rtol=1e-5 is wrong for FP16 inputs, which carry about three decimal digits.
Match the tolerance to the input precision before investigating the kernel.
Is the multi-hop cast lossy?
No. INT32 -> FP32 -> FP16 on A5 is bit-identical to a single-step conversion under the
same rounding and overflow behaviour — the page proves it with a runnable check. See
Precision.
Known limitations¶
| Limitation | Detail |
|---|---|
Two co-live MemRef slots under PTOAS |
Rejected at codegen: ptoas guards only the first multi_tile_get of an iteration. One slot live per iteration is the shape to write |
Hard syncall needs full occupancy |
A partial launch deadlocks on device (507018); PyPTO rejects it at compile time. Use mode=pl.SyncAllMode.SOFT at partial occupancy |
| Ring allreduce is not a one-argument change | It needs an explicit [2*(NR-1)+1, NR] INT32 signal. src may be ragged (any numel, not necessarily divisible by NR) with a static or dynamic shape. See Collectives |
memory_planner=PTOAS and the memory map |
Allocation passes are skipped, so pass dumps carry no offsets for the tool to draw |
| Doc code blocks are backend-specific | The manual's runnable blocks execute on a2a3sim; A5-only behaviour is argued rather than executed |
See Also¶
- Feature matrix — what each backend supports.
- Tools — the debugging surface behind most of these answers.
- Getting started — if you are hitting several of these at once.