Skip to main content
rule

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.

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

What 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 jit function with a suitable jax.lax operation.
  • 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.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 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

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