Skip to main content
rule

tinygrad — Cursor rule: what it does, how to install

A Cursor rule for tinygrad covers lazy execution, parameter handling, profiling, tensor updates, and reproducible experiments.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/tinygrad.mdc
Open on GitHub

What tinygrad does

The rule targets every file through the **/* glob. It asks the coding agent to make computation explicit with .realize() or .numpy(), batch related tensor realizations, and pass model parameters to an optimizer through get_parameters.

It also recommends focused imports, timing with Timing, and an arange plus where pattern for load/store-style updates. The workflow includes committing code before experiments and recording the Git hash with results. A missing .realize() or .numpy() call leaves the operation as a graph node, so the computation does not run.

What it helps with

  • check whether tinygrad operations are actually executed
  • collect trainable model parameters for an optimizer
  • measure lazy tensor code with an accurate timer
  • replace unsupported conditional tensor updates
  • make experiments traceable to a Git commit

Example prompts for the agent

  • Review this tinygrad code and point out operations that need .realize() or .numpy().
  • Refactor the model so its optimizer receives parameters from get_parameters, then look for missed tensor fusion.
  • Find load/store-style updates in this project and rewrite them with arange and where.

How to install tinygrad

Manually: put the files in place

.cursor/rules/tinygrad.mdc

Frontmatter: description, globs, alwaysApply

Step by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules

Questions about tinygrad

How do I install tinygrad in Cursor?

Place the rule file at .cursor/rules/tinygrad.mdc. Cursor will then apply it to files matched by **/*.

What does the tinygrad rule do?

It guides the agent on lazy execution, grouped tensor realization, optimizer parameter collection, profiling, conditional updates, and reproducible experiments.

Why must tinygrad computation be realized explicitly?

tinygrad builds a computation graph instead of executing each operation immediately. Call .realize() or .numpy() when the result needs to be computed.

Source text

tinygrad.mdc · CC0-1.0
---
description: This guide provides opinionated, actionable best practices for developing with tinygrad, focusing on lazy execution, performance, and code structure.
globs: **/*
---
# 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**
```python
from tinygrad import Tensor
a = Tensor.rand(100)
b = Tensor.rand(100)
c = a + b # This does not compute anything
# c is just a graph node, not a computed value
```

✅ **GOOD: Force computation**
```python
from tinygrad import Tensor
a = Tensor.rand(100)
b = Tensor.rand(100)
c = a + b
c.realize() # Forces the computation of 'c'
print(c.numpy()) # Also forces computation and converts to numpy array
```

### 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**
```python
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() # Computes a+b+c
out2.realize() # Recomputes a+b, then computes a+b-c
```

✅ **GOOD: Parallel realization for kernel fusion**
```python
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) # Allows tinygrad to fuse (a+b) computation
```

## 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**
```python
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) # Easy to miss parameters
```

✅ **GOOD: Automatic parameter collection**
```python
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) # Robust and scalable
```

### Keep Imports Minimal

Adhere to tinygrad's minimalist ethos. Only import what you need.

❌ **BAD: Over-importing**
```python
import numpy as np
import time
from tinygrad.helpers import Timing, getenv # getenv is rarely needed
from tinygrad.nn import optim, state # Import specific classes
from tinygrad import Tensor, dtypes, Device # Device is often implicit
```

✅ **GOOD: Targeted imports**
```python
import numpy as np # Only if explicitly using numpy
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**
```python
import time
from tinygrad import Tensor
a = Tensor.rand(1000, 1000)
b = Tensor.rand(1000, 1000)
start = time.time()
c = a @ b # This is lazy, time.time() will be misleadingly fast
print(f"Time: {time.time() - start:.6f}s")
# Output: Time: 0.000123s (incorrect)
```

✅ **GOOD: Use `Timing` for accurate measurement**
```python
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() # Ensure computation is part of the timing
# Output: Matrix multiplication: 0.012345s (example)
```

## 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)**
```python
# This pattern is not directly supported in tinygrad
# x[mask] = value
```

✅ **GOOD: Using `where` with `arange` for conditional logic**
```python
from tinygrad import Tensor, dtypes

def sparse_update(x: Tensor, indices: Tensor, values: Tensor) -> Tensor:
  # Example: update specific elements in x with values based on indices
  # This is a simplified conceptual example, actual implementation
  # for complex sparse updates would be more involved.
  # For a real-world example, see sparse_categorical_crossentropy in tinygrad.
  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) # Conditionally replace elements
```

## 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**
```bash
# You run your training script
python train.py --epochs 10 --lr 0.001

# Later, you can't reproduce the exact code that generated the results.
```

✅ **GOOD: Version control for reproducibility**
```bash
git add .
git commit -m "Experiment: Initial MNIST training with Leaky ReLU"
# Get the commit hash: git rev-parse HEAD
# Store this hash with your experiment logs and results.
python train.py --epochs 10 --lr 0.001 --git_hash $(git rev-parse HEAD)
```

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Similar IDE rules

All IDE rules for AI and ML →

From the same set240

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor