Runtime¶
After scheduling, Ren plans memory, lowers scheduled work into executable units, and runs backend programs. Read Scheduling for the pass sequence that produces scheduled kernels, or Engine overview for the shorter path from user API to backend execution.
Realization path¶
The realization path is:
Poly.realize()
-> Poly._schedule_and_lower()
-> schedule(SINK(...))
-> plan_memory(...)
-> make_exec_units(...)
-> run_exec_units(...)
Poly.realize(a, b, ...) realizes multiple outputs together. That lets the scheduler see shared intermediate work instead of compiling each output in isolation.
Memory planning¶
plan_memory(...) computes first and last use for scheduled buffers. The planner packs intermediate buffers that do not need stable identity into one arena per device using a TLSF-style allocator simulation.
The practical effect is that temporaries whose live ranges do not overlap can reuse the same backing storage. Ren still leaves some buffers alone when callers or later executable units rely on a stable object/base relationship.
Ren keeps buffers out of the arena when identity matters:
- final outputs;
- already allocated buffers;
- copy and buffer-view buffers;
- explicit no-opt buffers.
The planner returns a scheduled list with buffer objects remapped to views into shared backing storage.
Backends¶
The Python backend interprets the lowered kernel AST with NumPy and Montgomery helpers. Use it for correctness checks, debugging, and CPU-only development.
The CUDA backend renders kernels, compiles them with NVRTC, builds launch specs, and runs them on CUDA queues. It also has backend graph support for JIT replay when a captured program is eligible.
Copies and views¶
Copies lower to either device transfer or host-mediated copy depending on allocator support and device type.
BUFFER_VIEW lowers to a validation/binding exec unit. It does not launch a normal compute kernel; it ensures the view points at the expected base storage.
JIT capture and replay¶
ren.jit works at the realized-buffer boundary:
realize input Polys
enter capture mode and call function
collect output Poly values
realize while recording ExecUnits
replan captured intermediates
build input replacement tables
create backend graph runner if possible
On replay, JIT swaps current input buffers into the captured plan. After the run, JIT clears input slots so the plan does not retain stale buffers.
The main constraints are:
- the jitted function must receive and return at least one structure containing a
Poly; - explicit
realize()and random polynomial generation inside the jitted body are rejected; - top-level scalar arguments are frozen with the captured plan and must not change across replays;
- the captured output structure is reused, so feedback loops rely on the replay alias guard to preserve read-before-write semantics.
See ren.jit replay for examples, cache-key rules, and failure modes.
Files to read¶
ren/poly.py:Poly.realize()and_schedule_and_lower().ren/engine/realize.py:make_exec_units()andrun_exec_units().ren/engine/memory.py: arena planning and buffer remapping.ren/engine/jit.py: capture, replay, input replacement, and alias handling.ren/backends/python/andren/backends/cuda/: backend execution paths.