How Needle 2 Implements Rotary Position Embedding (RoPE): A Complete Code Walkthrough
Needle 2 implements Rotary Position Embedding (RoPE) through two core functions—precompute_rope_freqs and apply_rope—that rotate query and key tensors by pre-computed angle frequencies to encode relative position information.
RoPE has become the standard positional encoding method in modern transformer architectures. In Needle 2, the implementation follows the original paper's formulation while optimized for JAX/Flax execution. This article examines the complete RoPE pipeline, from frequency pre-computation to tensor rotation in the attention forward pass.
Core RoPE Functions in needle/model/architecture.py
The RoPE implementation centers on two utilities located in needle/model/architecture.py. These functions handle the mathematical operations that embed positional information into attention projections.
precompute_rope_freqs: Building the Frequency Tables
The precompute_rope_freqs function generates cosine and sine lookup tables for all positions up to a maximum sequence length. This pre-computation amortizes the angle calculation cost across the forward pass.
def precompute_rope_freqs(head_dim, seq_len, theta=10000.0):
freqs = 1.0 / (theta ** (jnp.arange(0, head_dim, 2).astype(jnp.float32) / head_dim))
t = jnp.arange(seq_len).astype(jnp.float32)
angles = jnp.outer(t, freqs)
return jnp.cos(angles), jnp.sin(angles)
Source: [architecture.py](https://github.com/cactus-compute/needle/blob/main/needle/model/architecture.py#L97-L101)
The function operates in four steps:
- Frequency computation: Divides the head dimension into pairs and computes
θ^(-2i/d)for indicesi - Position vector: Creates a range
[0, seq_len)representing token positions - Outer product: Combines positions with frequencies to produce all angle values
- Trigonometric outputs: Returns
(cos(angles), sin(angles))for later use
The default theta value of 10000.0 matches the original RoPE paper, with common variants using 100000.0 for longer context windows.
apply_rope: Rotating Query and Key Tensors
The apply_rope function performs the actual rotary transformation on attention tensors. It accepts pre-computed cosine/sine tables and applies the 2D rotation to each position-head pair.
def apply_rope(x, cos, sin):
T = x.shape[2]
half = x.shape[-1] // 2
cos = cos[:T][None, None, :, :]
sin = sin[:T][None, None, :, :]
x1 = x[..., :half]
x2 = x[..., half:]
return jnp.concatenate([x1 * cos - x2 * sin,
x2 * cos + x1 * sin],
axis=-1).astype(x.dtype)
Source: [architecture.py](https://github.com/cactus-compute/needle/blob/main/needle/model/architecture.py#L104-L111)
The rotation follows standard complex multiplication: treating (x1, x2) as a complex number, it multiplies by (cos, sin) to rotate by angle θ_t. The implementation broadcasts efficiently across batch and head dimensions.
Integration in the Attention Forward Pass
RoPE is applied to queries and keys immediately after linear projection in the decoder. The relevant logic resides in needle/model/decode.py.
Frequency Table Initialization
Cosine and sine tables are generated once per forward pass based on configuration:
cos, sin = precompute_rope_freqs(head_dim, max_len, config.rope_theta) # line 261
Source: [decode.py](https://github.com/cactus-compute/needle/blob/main/needle/model/decode.py#L261-L263)
Application to Q and K Tensors
After projecting input to query and key spaces, both tensors undergo rotary transformation:
# In needle/model/decode.py
q = apply_rope(q, cos_s, sin_s) # line 73
k = apply_rope(k, cos_s, sin_s) # line 73
This placement ensures positional information enters before the attention score computation, enabling the relative position inductive bias that makes RoPE effective.
Practical Usage Examples
Generating RoPE Tables
from needle.model.architecture import precompute_rope_freqs
head_dim = 64 # per-head dimension (must be even)
seq_len = 1024 # maximum sequence length for the batch
cos, sin = precompute_rope_freqs(head_dim, seq_len, theta=100000.0)
Applying RoPE to Attention Tensors
from needle.model.architecture import apply_rope
import jax.numpy as jnp
# Dummy query tensor: shape (batch, heads, time, dim)
q = jnp.ones((2, 8, 16, head_dim))
# Apply rotary embedding (cos & sin must cover the time dimension)
q_rot = apply_rope(q, cos, sin)
Full Inference Context
from needle.model.decode import decode_cfg, init_kv_cache
from needle.model.architecture import precompute_rope_freqs, apply_rope
cfg = TransformerConfig(rope_theta=100000.0, num_heads=8, d_model=512, max_seq_len=2048)
head_dim = cfg.d_model // cfg.num_heads
cos, sin = precompute_rope_freqs(head_dim, cfg.max_seq_len, cfg.rope_theta)
# Inside the attention block
q = (x @ lp["q"]).reshape(B, S, H, head_dim).transpose(0, 2, 1, 3)
k = (x @ lp["k"]).reshape(B, S, cfg.num_kv_heads, head_dim).transpose(0, 2, 1, 3)
q = apply_rope(q, cos, sin)
k = apply_rope(k, cos, sin)
Key Implementation Files
| File | Purpose |
|---|---|
needle/model/architecture.py |
Houses RoPE utilities precompute_rope_freqs and apply_rope |
needle/model/decode.py |
Invokes RoPE operations during self-attention forward pass |
needle/model/run.py |
Orchestrates inference pipeline utilizing RoPE-enabled attention |
Summary
- RoPE in Needle 2 uses a clean two-function design: frequency pre-computation and tensor rotation
precompute_rope_freqsgenerates position-dependent cosine/sine tables with configurable base frequencyapply_ropeimplements exact 2D rotation on query and key tensors of shape[B, H, T, D]- The implementation integrates at lines 73 and 261-263 of
decode.py, applied post-projection and pre-attention - Both flash-attention and fallback kernels are supported through JAX-compatible operations
Frequently Asked Questions
What is the default theta value in Needle 2's RoPE implementation?
The default theta value is 10000.0, matching the original RoPE paper. This can be overridden via config.rope_theta to support longer contexts—common alternatives include 100000.0 for extended sequence lengths.
Why does apply_rope split the tensor in half?
The split enables pairwise 2D rotation. RoPE treats adjacent dimension pairs as complex numbers; splitting into x1 and x2 allows applying the rotation matrix [cos, -sin; sin, cos] to each pair independently.
Does Needle 2 cache RoPE frequencies across layers?
Frequencies are pre-computed once per forward pass (line 261 in decode.py) and reused for all layers in that pass. This design avoids redundant computation while maintaining flexibility for varying sequence lengths.
Is RoPE applied to value tensors in Needle 2?
No—only queries and keys receive RoPE. Values retain absolute position encoding implicitly through their dependence on positionally-encoded queries and keys in the attention operation. This follows standard practice in RoPE-based architectures.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →