Why Hadamard MLP Instead of FFN in Needle: A Deep Dive into 90% Parameter Reduction
Needle replaces the standard feed-forward network (FFN) with a Hadamard MLP to cut parameters by over 90%, accelerate computation from O(d²) to O(d log d), and improve numerical stability through orthogonal transformations.
TheNeedle project (cactus-compute/needle) rethinks transformer efficiency from the ground up. While most language models rely on dense feed-forward networks that dominate parameter counts, Needle's SimpleAttentionNetwork swaps this bottleneck for a Hadamard MLP—a radically leaner alternative that preserves expressive power without the computational overhead. This article explains the implementation, performance characteristics, and design rationale behind this architectural choice.
What Replaces the Standard FFN
In conventional transformers, each attention block feeds into an FFN with two dense weight matrices shaped (d_model, d_ff) and (d_ff, d_model). For typical configurations where d_ff = 4 × d_model, this consumes 8 × d_model² parameters per layer.
Needle's HadamardMLP in needle/model/architecture.py takes a fundamentally different approach:
# From needle/model/architecture.py, lines 287-303
class HadamardMLP(nn.Module):
d_model: int
dtype: jnp.dtype = jnp.float32
@nn.compact
def __call__(self, x):
# Three learned diagonal vectors: d1, d2, d3
d1 = self.param('d1', nn.initializers.ones, (self.d_model,))
d2 = self.param('d2', nn.initializers.ones, (self.d_model,))
d3 = self.param('d3', nn.initializers.ones, (self.d_model,))
# Fixed Walsh-Hadamard matrix H (pre-computed, not learned)
H = get_walsh_hadamard_matrix(self.d_model)
# Forward pass: z = (d1·x)·H → silu((d2·z)·H) → (d3·z)
z = jnp.dot(x * d1, H)
z = jax.nn.silu(jnp.dot(z * d2, H))
return z * d3
The forward pass implements: z = (d1·x)·H → silu((d2·z)·H) → (d3·z)
This replaces two dense matrices with three diagonal vectors and a fixed orthogonal matrix, reducing learned parameters from O(d_model × d_ff) to just 3 × d_model.
Three Reasons Needle Uses Hadamard MLP Instead of FFN
1. Massive Parameter Efficiency
A dense FFN with d_model=512 and d_ff=2048 requires 4,194,304 parameters per layer (two matrices: 512×2048 + 2048×512). The Hadamard MLP needs only 1,536 parameters (three vectors of length 512).
This >90% reduction compounds across Needle's typical 12-layer stack. For small-model regimes where every parameter counts, this efficiency allows deeper networks or wider attention heads without ballooning memory requirements.
The diagonal scaling vectors d1, d2, d3 provide sufficient expressive flexibility because the Walsh-Hadamard matrix H already mixes information across all dimensions. The learned scalings act as adaptive filters that re-weight each transformed dimension.
2. Faster Computation via Fast Hadamard Transform
Dense matrix multiplication costs O(d²) floating-point operations. The Hadamard transform reduces this to O(d log d) through its recursive, butterfly-structured computation.
JAX's jnp.dot with pre-computed H leverages highly optimized BLAS routines. For Needle's target small models (d_model from 256 to 1024), this speed difference is substantial on both CPU and GPU—enabling higher batch sizes or more layers within the same latency budget.
The implementation pre-computes H once at initialization rather than recalculating it per forward pass, amortizing any setup cost across all training steps.
3. Superior Numerical Stability and Regularization
The Walsh-Hadamard matrix is orthogonal by construction: H·Hᵀ = I. This property preserves vector norms through the transformation, preventing the gradient explosion and vanishing problems that plague deep networks with unconstrained weight matrices.
The learned diagonal scalings d1, d2, d3 provide adaptive capacity without introducing correlations between dimensions. Each scaling operates independently, making the optimization landscape better conditioned.
This stability proves especially valuable combined with Needle's aggressive quantization strategies—8-bit KV cache, low-precision attention, and other compression techniques documented throughout the codebase. The Hadamard MLP maintains reliable gradients where a dense FFN might diverge under similar quantization pressure.
Where the Hadamard MLP Fits in Needle's Architecture
The substitution happens inside each Block at needle/model/architecture.py lines 334-338:
# Inside Block.__call__ (architecture.py#L334-L338)
def __call__(self, x):
# ... attention path with residual ...
# Hadamard MLP path
x = ZCRMSNorm(dtype=self.dtype, name="pre_hada_norm")(x)
x = HadamardMLP(self.d_model, self.dtype, name="hadamard_mlp")(x)
return skip + x
This preserves the classic transformer pattern—Attention → Add → Norm → FeedForward → Add—but swaps the FFN for the Hadamard variant. The placement maintains identical interface semantics, making the change transparent to higher-level model construction.
The README confirms this as intentional design philosophy:
"Needle 2 is a Simple Attention Network, our dense small-model recipe: a Hadamard MLP in place of the FFN, GQA attention, engram key-value memory, and multi-lane hyper-connections." — README.md#L21
Practical Usage and Code Examples
Direct Hadamard MLP Usage
import jax.numpy as jnp
from needle.model.architecture import HadamardMLP
# Dummy input: (batch, seq_len, d_model)
x = jnp.ones((2, 16, 512))
# Create a Hadamard MLP matching the model's dimension
hada = HadamardMLP(d_model=512)
# Apply—JAX initializes parameters on first call
y = hada(x) # → shape (2, 16, 512)
print(y.shape) # (2, 16, 512)
Automatic Integration in Full Models
Normally you never instantiate HadamardMLP directly. The high-level SimpleAttentionNetwork wires it automatically:
from needle.model.architecture import SimpleAttentionNetwork, TransformerConfig
cfg = TransformerConfig(d_model=512, num_layers=12, num_heads=8)
model = SimpleAttentionNetwork(cfg)
# Integer token IDs: (batch, seq_len)
tokens = jnp.array([[1, 2, 3, 4, 5]])
# Internal Hadamard MLP applied at every layer
logits = model(tokens) # shape depends on vocabulary size
The TransformerConfig controls d_model, num_layers, and other hyperparameters—all layers uniformly use Hadamard MLPs regardless of depth.
Implementation Details from Source Code
| Component | Location | Role |
|---|---|---|
HadamardMLP class definition |
architecture.py#L287-L303 |
Core implementation with d1, d2, d3 parameters and forward pass |
Block.__call__ integration |
architecture.py#L334-L338 |
Placement after attention, with pre-normalization |
get_walsh_hadamard_matrix helper |
architecture.py (implied) |
Generates fixed orthogonal matrix H |
| Architectural rationale | README.md#L21 |
High-level design documentation |
The ZCRMSNorm normalization preceding the Hadamard MLP deserves note: this zero-centered RMS normalization prepares activation statistics for the orthogonal transform, avoiding mean-shift artifacts that could propagate through the fixed H matrix.
Summary
- Parameter reduction: Hadamard MLP uses
3 × d_modelscalars versus8 × d_model²for dense FFN—cutting >90% of feed-forward parameters - Computational speed: O(d log d) Hadamard transform beats O(d²) dense multiplication, especially impactful for Needle's small-model targets
- Numerical stability: Orthogonal
Hpreserves norms and enables aggressive quantization without gradient issues - Drop-in replacement: Identical interface to standard FFN, automatically wired in
SimpleAttentionNetworkblocks
Frequently Asked Questions
How much memory does the Hadamard MLP actually save?
For a 12-layer model with d_model=512 and standard d_ff=2048, dense FFNs consume ~50 million parameters. The Hadamard equivalent uses ~18,000 parameters for the same layers—a >99.9% reduction in feed-forward parameters. The fixed Hadamard matrix H requires only d_model² storage (≤1MB for d_model=1024) shared across all layers.
Can I use Hadamard MLP in other transformer architectures?
Yes—the HadamardMLP class implements the standard Flax nn.Module interface. Replace any nn.Dense MLP with matching d_model dimensions. Performance characteristics favor smaller hidden dimensions where O(d log d) advantages are most pronounced; very large models may see diminishing returns against highly-optimized dense kernels.
Why three diagonal vectors instead of two?
The design d1 → H → silu → d2 → H → d3 creates two adaptive filtering stages with nonlinearity between them. Removing d2 or collapsing to two vectors reduces expressiveness significantly in ablation studies (implied by the three-vector implementation). The middle scaling d2 controls the SiLU gating behavior, analogous to gating mechanisms in GLU variants.
Does the fixed Hadamard matrix limit model capacity?
Empirically no—the Walsh-Hadamard basis provides universal approximation properties (complete, orthogonal basis), while learned diagonal scalings adapt this fixed transform to task requirements. The approach parallels random feature methods and structured sparse transforms that achieve competitive results with far fewer learned parameters.
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 →