| name | tinygrad |
| description | [Applies to: **/*] This guide provides opinionated, actionable best practices for developing with tinygrad, focusing on lazy execution, performance, and code structure. |
| source | cursor_mdc |
tinygrad Best Practices
tinygrad prioritizes minimalism, performance, and a clear computation graph. Adhering to these guidelines ensures your code is efficient, readable, and aligns with the framework's core philosophy.
1. Embrace Lazy Evaluation
tinygrad builds a computation graph that only executes when explicitly requested. This is the single most important concept for correctness and performance.
Explicitly Realize Tensors
Always call .realize() or .numpy() to force computation. Forgetting this is the most common pitfall.
❌ BAD: Silent no-op
from tinygrad import Tensor
a = Tensor.rand(100)
b = Tensor.rand(100)
c = a + b
✅ GOOD: Force computation
from tinygrad import Tensor
a = Tensor.rand(100)
b = Tensor.rand(100)
c = a + b
c.realize()
print(c.numpy())
Group Realizations for Fusion
When multiple tensors depend on a shared computation, realize them together to allow tinygrad's scheduler to fuse kernels and avoid redundant memory traffic.
❌ BAD: Sequential realization, missed fusion opportunities
from tinygrad import Tensor
a = Tensor.rand(100)
b = Tensor.rand(100)
c = Tensor.rand(100)
out1 = a + b + c
out2 = a + b - c
out1.realize()
out2.realize()
✅ GOOD: Parallel realization for kernel fusion
from tinygrad import Tensor
a = Tensor.rand(100)
b = Tensor.rand(100)
c = Tensor.rand(100)
out1 = a + b + c
out2 = a + b - c
Tensor.realize(out1, out2)
2. Optimize Model Structure and Parameters
Keep models simple and leverage tinygrad's parameter management.
Use get_parameters for Optimizers
Always use tinygrad.nn.state.get_parameters to collect all trainable parameters for your optimizer. Manually listing parameters is error-prone and not scalable.
❌ BAD: Manual parameter listing
from tinygrad import Tensor
from tinygrad.nn.optim import SGD
class MyModel:
def __init__(self):
self.l1_w = Tensor.rand(10, 10, requires_grad=True)
self.l1_b = Tensor.zeros(10, requires_grad=True)
def __call__(self, x): return x @ self.l1_w + self.l1_b
net = MyModel()
opt = SGD([net.l1_w, net.l1_b], lr=1e-3)
✅ GOOD: Automatic parameter collection
from tinygrad import Tensor
from tinygrad.nn.optim import SGD
from tinygrad.nn.state import get_parameters
class MyModel:
def __init__(self):
self.l1_w = Tensor.rand(10, 10, requires_grad=True)
self.l1_b = Tensor.zeros(10, requires_grad=True)
def __call__(self, x): return x @ self.l1_w + self.l1_b
net = MyModel()
opt = SGD(get_parameters(net), lr=1e-3)
Keep Imports Minimal
Adhere to tinygrad's minimalist ethos. Only import what you need.
❌ BAD: Over-importing
import numpy as np
import time
from tinygrad.helpers import Timing, getenv
from tinygrad.nn import optim, state
from tinygrad import Tensor, dtypes, Device
✅ GOOD: Targeted imports
import numpy as np
from tinygrad import Tensor, dtypes
from tinygrad.helpers import Timing
from tinygrad.nn.optim import SGD
from tinygrad.nn.state import get_parameters
3. Performance Profiling
Measure, don't guess. Use tinygrad's built-in tools to identify bottlenecks.
Use Timing for Benchmarking
Always wrap performance-critical sections with Timing to get accurate execution times, especially when comparing different approaches.
❌ BAD: Inaccurate time.time() for lazy ops
import time
from tinygrad import Tensor
a = Tensor.rand(1000, 1000)
b = Tensor.rand(1000, 1000)
start = time.time()
c = a @ b
print(f"Time: {time.time() - start:.6f}s")
✅ GOOD: Use Timing for accurate measurement
from tinygrad import Tensor
from tinygrad.helpers import Timing
a = Tensor.rand(1000, 1000)
b = Tensor.rand(1000, 1000)
with Timing("Matrix multiplication"):
c = a @ b
c.realize()
4. Handle Load/Store Operations
tinygrad does not natively support load/store ops to simplify backend porting. Implement them using arange masks.
Implement Custom Load/Store with arange
If you need conditional writes or complex indexing, use Tensor.arange to create masks.
❌ BAD: Attempting direct conditional assignment (not supported)
✅ GOOD: Using where with arange for conditional logic
from tinygrad import Tensor, dtypes
def sparse_update(x: Tensor, indices: Tensor, values: Tensor) -> Tensor:
mask = (Tensor.arange(x.numel(), dtype=dtypes.int32, requires_grad=False) == indices.flatten().unsqueeze(1)).any(axis=1).reshape(x.shape)
return mask.where(values, x)
5. Reproducibility and Development Workflow
Maintain a disciplined workflow for robust and reproducible experiments.
Commit Before Experiments
Always commit your code before running experiments. This ensures you can trace results back to a specific Git hash.
❌ BAD: Running experiments on uncommitted changes
python train.py --epochs 10 --lr 0.001
✅ GOOD: Version control for reproducibility
git add .
git commit -m "Experiment: Initial MNIST training with Leaky ReLU"
python train.py --epochs 10 --lr 0.001 --git_hash $(git rev-parse HEAD)