Transforms¶
An IR transform is a function with this shape:
flowchart TD
input_ir["IR"] --> transform["IR transform"]
transform --> output_ir["IR"]
The output is another executable IR. That one property is what makes composition ordinary Python function composition.
Transform |
Returned IR expects |
Returned IR produces |
Use when |
|---|---|---|---|
Batched leaves where |
Batched outputs. |
Run the same program over many examples. |
|
Original inputs plus input tangents. |
Original output plus output tangent. |
Push a proposed input change forward. |
|
Original inputs plus feedback on the output. |
Original output plus input feedback. |
Turn output critique into prompt/input critique. |
|
The same inputs as |
The same output as |
Overlap independent equations during async execution. |
|
The same inputs as |
The selected output shape, with unused leaves removed or replaced. |
Drop work that cannot affect the needed outputs. |
|
The same inputs as |
|
Score one concrete path with reached |
IR Transforms¶
batch(ir, /, *, in_axes=True) -> IR
batch vectorizes an IR over one or more input leaves. in_axes is a bool pytree matching the input structure: True means batched, False means broadcast.
Higher-order primitives can have their own batch rules too, including while_loop.[1]
batched = af.batch(ir)
outputs = batched.call(["DNA", "gravity", "recursion"])
pushforward(ir, /) -> IR
pushforward builds a forward-mode-style IR. The transformed IR takes primals and tangents, then returns output primals and output tangents.
pf = af.pushforward(ir)
output, tangent = pf.call(("topic",), ("make it concrete",))
pullback(ir, /) -> IR
pullback builds a reverse-mode-style IR. In autoform, cotangents are text feedback. The transformed IR takes the original inputs plus an output cotangent, then returns the output and input cotangents.
pb = af.pullback(ir)
output, input_feedback = pb.call(("topic",), "too abstract")
sched(ir, /, *, cond=None) -> IR
sched groups independent equations into parallel stages. The resulting IR can
still run with .call(...), but .acall(...) is where concurrent stages become
useful.
import asyncio
scheduled = af.sched(ir)
result = asyncio.run(scheduled.acall("topic"))
weighted(ir, /) -> IR
weighted turns an IR into a path scorer. The returned IR runs one concrete path and returns the original output plus the product of reached factor weights.
scored = af.weighted(ir)
output, path_weight = scored.call("topic", 0.8)
dce(ir, /, *, out_used=None) -> IR
dce removes equations that do not contribute to the selected output leaves.
trimmed = af.dce(ir)
result = trimmed.call("topic")
Composition¶
Composition works because every transform returns an IR:
transformed = af.batch(af.pullback(ir))
outputs, (topic_hints,) = transformed.call((topics,), critiques)
There is no special combined mode. pullback returns an IR; batch consumes that IR.
Order still matters:
Expression |
Meaning |
|---|---|
|
Run many independent pullback calls at once. Each input pairs with its own output feedback. |
|
Treat the whole batched function as the program receiving feedback. The cotangent matches the batched output. |
|
Score many candidate paths separately. The result contains one weight per candidate. |
|
Score one batched path. Reached factors across the batched execution multiply into one weight. |
Non-Transforms¶
Some nearby public APIs are intentionally not IR -> IR:
customis a decorator on traceable user functions. It marks a function boundary and lets transforms consult custom rules at that boundary. See Custom Rules.memoizeis a context manager. It caches primitive results within awithblock. See Cache Repeated Computations withmemoize.lm_clientis a context manager. It changes provider routing during execution. See Configure LiteLLM Routing.collectandinjectare context managers. They capture or replace checkpointed values during execution. See Intercepts.tagandfoldare context managers. They alter trace-time annotation or trace-time evaluation. See Tags and Fold.
The IR transforms reshape the IR. custom changes rule lookup at a boundary. Contexts wrap trace-time or execute-time behavior. They are complementary axes.
Transform and Execution Axes¶
The transform axis and execution axis compose independently:
import asyncio
transformed = af.batch(af.pullback(ir))
sync_result = transformed.call((topics,), critiques)
async_result = asyncio.run(transformed.acall((topics,), critiques))
The original function was not written as async def. Async execution is chosen when running the transformed IR. See Trace, IR, Execute for the execution split.