tinygrad-notes

Following the Metal runtime

For a fully executed example, see the transpose/pad/reduction trace. Its captured Metal source uses vector loads and stores with a single thread, and its metadata records the actual Apple7 target. This makes the distinction between logical Tensor dimensions and dispatch dimensions concrete: a five-element result does not require a five-thread launch.

All tutorials · Updated September 21, 2026 · tinygrad 8ad8f73

Metal execution has distinct compilation, pipeline creation, command encoding, and completion steps. TinyJit can reuse/batch execution, but pipeline creation itself is not the definition of JIT.

The current implementation is runtime/ops_metal.py, with source rendering in renderer/cstyle.py and graph support in runtime/graph/metal.py.

Source to executable pipeline

MetalRenderer produces Metal source. MetalCompiler.compile uses Apple’s compiler service to produce an MTLB library. It selects a language version based on macOS and checks the returned library markers.

MetalProgram receives a device and TinyELF, creates a Metal library from its bytes, locates the entry function, and creates a compute pipeline state. Compilation and pipeline loading have separate costs and lifetimes.

Encoding a launch

The ordinary MetalProgram.__call__ checks the local workgroup size, creates a command buffer and compute encoder, sets the pipeline, binds buffers and scalar arguments, dispatches threadgroups, ends encoding, and commits.

The runtime retains in-flight command buffers. When waiting is requested, it checks completion and can return the GPU elapsed time. MetalDevice.synchronize waits for outstanding work and collects profiling information when enabled.

The launch is asynchronous unless a relevant wait/synchronization path is taken. Timing only the Python call can measure submission rather than GPU completion.

Shared memory still needs ordering

MetalAllocator uses shared storage and exposes host access where available. Shared addressability does not mean CPU and GPU accesses can race safely. The copy-in/copy-out helpers synchronize around host access; transfer code has its own completion behavior.

Use a supported Mac to run the introduction example with:

DEV=METAL DEBUG=4 python3 example.py

Inspect the emitted kernel and compare its result. For repeated workloads, use the TinyJit example, then inspect whether graph batching applies. Metal graph support is conditional; the runtime disables it for certain virtualized devices.

The source walkthrough identifies the actual implementation; it does not infer performance or hardware coverage from a screenshot of an older version.

Original chapter by Di Zhu: historical version.