Skip to content

tinygrad backend

backend="tinygrad" captures a lazy tinygrad forward from its UOp graph. The backend is a preview (maturity="preview"): it fills the shared BackendProtocol, runs the shared conformance suite, and ships its scorecard, but its spellings may still move before the label flips to stable.

Requirement Value
Distribution tinygrad
Supported range >=0.13.0, <0.14
Pinned CI version 0.13.0
Install extra torchlens[tinygrad]
Materialization lazy (realize-once rule applies)
Device DEV=PYTHON for live payloads, replay validation and derived gradients

Tutorial

Trace a pure tinygrad callable, read the recorded ops, and validate the trace by replay. The capture evaluates the forward's lazy graph once, after the forward returns, and every payload comes from that one evaluation. Run this in a fresh process with DEV=PYTHON.

import os

os.environ["DEV"] = "PYTHON"

from tinygrad import Tensor
import torchlens as tl

weight = Tensor([[0.1, -0.2, 0.3], [0.4, 0.5, -0.6], [-0.7, 0.8, 0.9], [1.0, -1.1, 1.2]])
bias = Tensor([0.0, 0.1, -0.1])

def mlp(x):
    return (x.matmul(weight) + bias).relu()

x = Tensor([[-1.0, -0.5, 0.25, 1.0], [0.5, 1.5, -2.0, 0.75]])
trace = tl.trace(mlp, x, backend="tinygrad")

print(len(trace), "ops recorded")
print(trace.output_ops[0].out.numpy())          # equals mlp(x).numpy()
assert trace.validate_forward_pass([mlp(x)]) is True

The protocol object is the surface the conformance suite drives. Its validate returns the replay status object with per-op decisions instead of a boolean:

protocol = tl.backends.spec("tinygrad").protocol()
status = protocol.validate(trace, [mlp(x)])
print(status.state)          # "passed"
print(status.message)        # names the first offending op when it fails

Refusals are typed. A request the preview cannot honour raises BackendUnsupportedError with a code from the shared registry before any framework side effect:

from torchlens.errors import BackendUnsupportedError

try:
    tl.trace(
        mlp,
        x,
        backend="tinygrad",
        capture=tl.options.CaptureOptions(module_identity_mode="nope"),
    )
except BackendUnsupportedError as error:
    print(error.code)        # "module_identity_mode_unsupported"

Layers with random default init (tinygrad.nn.Linear, Conv2d) capture as they are: their weights are realized in the capture's single evaluation, so the trace and the random-number state afterwards match one eager forward. Random numbers drawn inside the traced callable (Tensor.rand, dropout) refuse rng_state_in_graph, since replay would draw them again; draw them before capture and pass them in. When comparing a capture with an eager forward, read the eager output before reading tinygrad's RNG counters: in tinygrad itself, realizing the counters first changes the values a still-lazy random weight takes.

Can and cannot

The table equals the backend's capability summary; untested means the flag is False and its refusal cell has not yet passed on the pinned CI leg (not yet proven, not broken).

Capability State Notes
has_validation_replay supported UOp replay plus parent perturbation on DEV=PYTHON payloads
payload_materialization supported array_payloads via .tlspec; one realization per capture (realize-once)
intermediate_derived_grads supported opt-in through GradOptions(intermediate_grads=True)
backward_capture unsupported refusal backward_capture_unsupported
fastlog unsupported refusal fastlog_unsupported
can_intervene unsupported refusal interventions_unsupported
rng_replay unsupported refusal rng_replay_unsupported
has_streaming unsupported refusal streaming_unsupported
structure_only_capture unsupported refusal structure_only_capture_unsupported
Closure constant with a readable buffer supported recorded as a parentless buffer op with payload (tinygrad_constant_kind="closure")
Literal producer (Tensor.ones, Tensor.full, scalar const) supported parentless op with payload (tinygrad_constant_kind="literal")
Closure constant built from a NumPy array (Tensor(ndarray), not yet realized) supported realized in the capture's single evaluation; a value tinygrad cannot realize refuses constant_unrealizable naming the op
Random-init layers (tinygrad.nn default init) supported weights realized in the capture's single evaluation; values and RNG state equal one eager forward
In-forward random numbers (Tensor.rand, dropout inside the traced callable) unsupported refusal rng_state_in_graph
random_seed= unsupported refusal explicit_seed_unsupported
Trace options outside module_identity_mode, grad_options unsupported refusal trace_option_unsupported
Mid-capture Tensor.realize() or TinyJit execution unsupported refusal mid_capture_execution_unsupported
Input mutation (Tensor.assign(), Tensor.replace(), setitem on an input) unsupported refusal input_mutation_unsupported
A callable that returns no tinygrad Tensor unsupported refusal tensor_output_required
No positional tinygrad Tensor input unsupported refusal tinygrad_input_required
Replay validation or derived gradients on an audit-only trace unsupported refusal live_payloads_required

Scorecard

torchlens/backends/tinygrad/scorecard.json is written by the pinned CI leg (python -m tests.conformance.scorecard --backend tinygrad --write) and surfaces as the backend_scorecard:tinygrad row of tl.report.doctor(). The roster behind it is tests/conformance/_rosters/tinygrad.py: ten fixed-weight config_built rows, three random_init rows (default-init Linear, Conv2d and LayerNorm), and two ecosystem rows that run only under TORCHLENS_CONFORMANCE_ECOSYSTEM=1.