jax — Cursor rule for JAX: coding practices and setup
A Cursor rule for Python projects using JAX, covering functional purity, array types, compilation behavior, random keys, and gradient tests.
.cursor/rules/jax.mdcWhat jax does
This rule targets Python files matching **/*.py. It guides the coding agent around JAX transformations such as jit, grad, vmap, and pmap. The recommendations include passing state through function arguments, updating arrays with .at[], and using JAX-compatible control flow instead of stateful Python iterators. These patterns address common sources of incorrect results and compilation problems.
Numerical and performance guidance covers explicit float32 constants, consistent array shapes, and batching with vmap. When input shapes change, JAX may compile a function again; the rule suggests keeping shapes stable or considering static_argnums for non-array arguments that determine them. Random number generation should use jax.random with separately split PRNG keys.
The rule also encourages jax.Array type annotations, pytest for unit and integration tests, and jax.test_util.check_grads for derivative checks. It is intended for code that uses JAX, and ordinary Python loops inside jit-compiled functions can interfere with compilation. JAX control-flow primitives such as lax.scan, lax.while_loop, and lax.cond are recommended for those cases.
What it helps with
- Remove mutable global state from JAX functions
- Replace in-place array assignments with functional updates
- Rewrite Python loops using JAX control-flow primitives
- Avoid recompilation caused by changing array shapes
- Test gradients for custom numerical operations
Example prompts for the agent
- Review my Python files against the JAX rule and refactor functions that rely on mutable global state.
- Replace the Python loop inside this
jitfunction with a suitablejax.laxoperation. - Check my PRNG key handling and add gradient tests for the custom function.
How to install jax
Manually: put the files in place
.cursor/rules/jax.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/jax.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/jax.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/jax.mdManually: put the files in place
.clinerules/jax.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about jax
How do I install jax in Cursor?
Add the rule file at .cursor/rules/jax.mdc in your project. It targets files matching **/*.py.
What does the jax rule do?
It guides JAX code authoring and review, with recommendations for pure functions, immutable array updates, numerical types, compilation, random key handling, and gradient validation.
How can I avoid repeated compilation in JAX?
Keep input array shapes consistent across calls. When a non-array argument determines a shape, consider using static_argnums with jit.
Source text
jax.mdc · CC0-1.0
---
description: Definitive guidelines for writing high-performance, functionally pure, and maintainable JAX code, focusing on common pitfalls and optimal patterns for accelerators.
globs: **/*.py
---
# jax Best Practices
JAX is the backbone of our AI/ML and numerical computing projects. Adhere to these principles for high-performance, reproducible, and maintainable JAX code.
## 1. Functional Purity: The Absolute Core
JAX transformations (`jit`, `grad`, `vmap`, `pmap`) operate exclusively on **functionally pure** code. This means functions must be free of side-effects: all inputs explicit, all results returned.
* **Avoid mutable global state:** JAX captures global values at first `jit` compilation, leading to stale values.
❌ BAD:
```python
g = 0
def impure_uses_globals(x):
return x + g # `g` is captured at first jit
# ... later g = 10, but jit(impure_uses_globals) still uses g=0
```
✅ GOOD: Pass all state explicitly.
```python
def pure_uses_globals(x, g_val):
return x + g_val
# ... pass g_val=0, then g_val=10
```
* **No in-place array mutation:** JAX arrays are immutable. Use the `.at[]` syntax for functional updates.
❌ BAD:
```python
import jax.numpy as jnp
arr = jnp.zeros((3,3))
arr[1, :] = 1.0 # TypeError!
```
✅ GOOD:
```python
import jax.numpy as jnp
arr = jnp.zeros((3,3))
updated_arr = arr.at[1, :].set(1.0) # Returns a new array
```
* **Avoid Python iterators in `jit`ted code:** Iterators introduce state.
❌ BAD:
```python
from jax import jit
def sum_iterator(it):
total = 0
for x in it: # Python loop with iterator
total += x
return total
# jit(sum_iterator)(iter(range(10))) # Will fail or give unexpected results
```
✅ GOOD: Use JAX control flow primitives.
```python
from jax import lax
import jax.numpy as jnp
def sum_array(arr):
# Use lax.scan or lax.fori_loop for JAX-compatible loops
return lax.fori_loop(0, arr.shape[0], lambda i, x: x + arr[i], 0)
# jit(sum_array)(jnp.arange(10))
```
## 2. Numerical Type Discipline
Prioritize `float32` for performance on accelerators. Avoid implicit `float64` promotion.
* **Explicit `dtype` for constants:**
❌ BAD: Implicitly typed Python floats can lead to `float64` promotion.
```python
import jax.numpy as jnp
x = jnp.ones(5, dtype=jnp.float32)
y = x * 2.0 # 2.0 is a Python float, can cause unwanted promotion
```
✅ GOOD: Use 0-D `jnp.array` with explicit `dtype` or `jnp.float32()`.
```python
import jax.numpy as jnp
x = jnp.ones(5, dtype=jnp.float32)
y = x * jnp.array(2.0, dtype=jnp.float32)
# Or more concisely:
z = x * jnp.float32(2.0)
```
## 3. Performance Considerations: Stable Compilation
Prevent costly recompilations and leverage XLA effectively.
* **Static shapes:** Keep input shapes static or pass them via `static_argnums` to `jit`. Dynamic shapes force recompilation.
❌ BAD:
```python
from jax import jit
def dynamic_shape_func(x):
return x.sum()
# jit(dynamic_shape_func)(jnp.ones(5))
# jit(dynamic_shape_func)(jnp.ones(10)) # Recompiles!
```
✅ GOOD:
```python
from jax import jit
@jit
def static_shape_func(x):
return x.sum()
# Call with consistent shapes
# static_shape_func(jnp.ones(5))
# static_shape_func(jnp.ones(5)) # Uses cached compilation
```
If shapes *must* vary, consider `static_argnums` for non-array arguments that determine shape.
* **JAX control flow primitives:** Always use `lax.scan`, `lax.while_loop`, `lax.cond` inside `jit`ted functions. Python control flow breaks XLA compilation.
❌ BAD:
```python
from jax import jit
@jit
def python_loop(x, n):
for _ in range(n): # Python loop inside jit
x = x * 2
return x
```
✅ GOOD:
```python
from jax import jit, lax
@jit
def jax_loop(x, n):
# lax.fori_loop(start, stop, body_fn, init_val)
return lax.fori_loop(0, n, lambda i, val: val * 2, x)
```
* **Vectorization with `vmap`:** For data-parallel batching, `vmap` is your tool.
```python
from jax import vmap
import jax.numpy as jnp
def elementwise_op(x, y):
return x + y # Operates on scalars or single elements
batched_op = vmap(elementwise_op)
# batched_op(jnp.array([1,2,3]), jnp.array([4,5,6])) # Applies elementwise
```
## 4. Code Organization and Randomness
* **Standard Imports:**
```python
import jax
import jax.numpy as jnp
import jax.random as jr
import jax.lax as lax
```
* **Randomness Management:** Use `jax.random` and explicitly split PRNGKeys. Never reuse a key.
❌ BAD:
```python
import jax.random as jr
key = jr.PRNGKey(0)
val1 = jr.normal(key, (5,)) # Key used
val2 = jr.normal(key, (5,)) # Key reused, not independent!
```
✅ GOOD:
```python
import jax.random as jr
key = jr.PRNGKey(0)
key, subkey1 = jr.split(key)
val1 = jr.normal(subkey1, (5,))
key, subkey2 = jr.split(key)
val2 = jr.normal(subkey2, (5,)) # Independent random values
```
## 5. Type Hints
Use standard Python type hints, especially `jax.Array` for JAX arrays. This improves readability and enables static analysis.
```python
import jax.numpy as jnp
from jax import Array, jit
@jit
def add_arrays(a: Array, b: Array) -> Array:
"""Adds two JAX arrays."""
return a + b
# Example usage:
# result = add_arrays(jnp.ones(5), jnp.ones(5))
```
## 6. Testing Approaches
* **`pytest` is standard:** Use `pytest` for unit and integration tests.
* **Gradient checking:** For custom operations or complex functions, use `jax.test_util.check_grads` to verify gradients.
```python
import jax
import jax.numpy as jnp
from jax.test_util import check_grads
def my_complex_func(x):
return jnp.sin(x) * jnp.exp(x)
# Test gradients numerically vs. analytically
# check_grads(my_complex_func, (jnp.array(1.0),), order=1)
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc