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_model scalars versus 8 × 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 H preserves norms and enables aggressive quantization without gradient issues
  • Drop-in replacement: Identical interface to standard FFN, automatically wired in SimpleAttentionNetwork blocks

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →