Qwen Architecture › Core Architectural Components
Language AI HandbookAll chapters Qwen Architecture › Core Architectural ComponentsOne section · Sections in this chapter4 / 11 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
This section builds your understanding of Qwen's key innovations step by step. We'll start with the attention mechanism, where Qwen makes its most impactful efficiency choice, then explore the feed-forward network and positional encoding. For each component, we'll first build intuition about the problem being solved, then examine the mathematical formulation, and finally see it in code.
Grouped-Query Attention
To understand why Qwen uses Grouped-Query Attention (GQA), we first need to understand the memory bottleneck in transformer inference.
The main bottleneck is KV cache growth.
During autoregressive generation, transformers must store the key (K) and value (V) vectors for all previously generated tokens. This "KV cache" allows the model to attend to its full history without recomputing everything at each step. For a model with layers, attention heads, head dimension , and sequence length , the cache requires:
For Qwen-72B generating a 4096-token sequence in FP16, this exceeds 40GB just for the cache. The insight behind GQA is that not every query head needs its own unique key-value representation, so we can share KV heads across groups of query heads to sharply reduce this memory burden.

The solution: sharing key-value heads
While LLaMA-7B uses standard multi-head attention (MHA), Qwen employs Grouped-Query Attention (GQA) across all model sizes. GQA strikes a balance between the quality of MHA and the efficiency of Multi-Query Attention (MQA).

In standard multi-head attention, each query head has its own dedicated key and value heads. GQA groups multiple query heads together to share a single set of key-value heads. This reduces memory requirements while preserving most of the representational capacity.
Understanding the attention variants
Before diving into the formulas, let's build intuition about the three attention variants:
-
Multi-Head Attention (MHA): The original design. Each query head gets its own private key-value pair. Maximum expressiveness, but maximum memory cost. Think of it as giving each employee their own filing cabinet.
-
Multi-Query Attention (MQA): The extreme efficiency approach. All query heads share a single key-value pair. Minimal memory, but information gets compressed. Like having everyone share one filing cabinet.
-
Grouped-Query Attention (GQA): The balanced solution. Small groups of query heads share key-value pairs. Qwen uses this because it retains most of MHA's quality while achieving most of MQA's efficiency gains.
The mathematical formulation
Now we can understand the formal definition. The overall GQA computation concatenates the outputs from all attention heads and projects them back to the model dimension:
where:
- : the QKV tensors derived from the input hidden states
- : the output of the -th attention head, capturing what information token should attend to
- : the total number of query heads (e.g., 32 for Qwen-7B, 64 for Qwen-72B)
- : the output projection matrix of shape that recombines head outputs
- : concatenation along the head dimension, stacking all head outputs together
Each individual head computes attention using its query projection but shares key-value projections with other heads in its group. This is where the efficiency gain comes from:
where:
- : the query input for head (unique to each head)
- : the query projection matrix for head (unique to each head)
- : a grouping function that maps query head to its corresponding KV group index
- : the key and value inputs for the group containing head (shared within group)
- : the shared key and value projection matrices for that group
How grouping works in practice
The grouping function determines which query heads share KV heads. For a model with query heads and key-value heads:
Let's trace through two concrete examples:
-
Qwen-7B: 32 query heads, 32 KV heads (ratio 1:1). Here , so each query head has its own KV head. This is equivalent to standard MHA, the safe choice for a smaller model.
-
Qwen-72B: 64 query heads, 8 KV heads (ratio 8:1). Every 8 consecutive query heads share the same KV head. Query heads 0-7 share KV head 0, heads 8-15 share KV head 1, and so on. This gives an 8× reduction in KV cache memory.
Why this works
The intuition for why grouping works is that different query heads often look for similar patterns in the keys and values. By forcing them to share, we impose a useful regularization. Empirically, researchers found that models with GQA ratios up to 8:1 show minimal quality degradation while achieving substantial memory and speed improvements.
Summary of advantages
- Reduced KV cache memory: Fewer KV heads means smaller cache during generation (8× reduction for Qwen-72B)
- Faster inference: Less memory bandwidth needed to load KV cache, directly improving tokens/second
- Maintained quality: Grouping preserves most of MHA's representational capacity through shared but still diverse key-value representations
Attention with Bias
Having understood how Qwen organizes its attention heads, we now turn to a subtler design choice: whether to include bias terms in the attention projections.
The design question
When projecting hidden states to QKV tensors, we have a choice:
LLaMA follows the trend of removing all bias terms, arguing that the weight matrices alone have sufficient capacity and that removing bias simplifies the model. Qwen takes the opposite stance, adding bias to the QKV projections.
Why bias might help
The intuition is subtle but important. Bias terms give a learned "default" activation that doesn't depend on the input. In attention, this means:
- Queries can have a baseline direction they search in, independent of the current token
- Keys can have a baseline "signature" that makes certain patterns more or less likely to match
- Values can contribute baseline information even before considering specific content
Think of it like this: without bias, attention patterns must emerge entirely from token-token interactions. With bias, the model can learn that "queries from this head should generally attend more to earlier positions" or "values should include this baseline information regardless of content."
The parameter cost
This flexibility comes at minimal cost. For Qwen-7B:
Compared to the total attention parameters per layer (~67M), this is about 0.02%. Qwen's researchers found this negligible overhead gives measurable quality improvements, particularly in tasks requiring fine-grained attention patterns.
Implementation
Unlike LLaMA which removes all bias terms from linear projections, Qwen adds bias to the QKV projections:
import torch
import torch.nn.functional as F
from torch import nn
class QwenAttention(nn.Module):
"""Qwen attention with QKV bias and optional GQA"""
def __init__(
self,
hidden_size: int,
num_attention_heads: int,
num_kv_heads: int = None,
):
super().__init__()
self.hidden_size = hidden_size
self.num_heads = num_attention_heads
self.num_kv_heads = num_kv_heads or num_attention_heads
self.head_dim = hidden_size // num_attention_heads
self.num_kv_groups = self.num_heads // self.num_kv_heads
# Qwen uses bias=True for Q, K, V projections
self.q_proj = nn.Linear(
hidden_size, self.num_heads * self.head_dim, bias=True
)
self.k_proj = nn.Linear(
hidden_size, self.num_kv_heads * self.head_dim, bias=True
)
self.v_proj = nn.Linear(
hidden_size, self.num_kv_heads * self.head_dim, bias=True
)
self.o_proj = nn.Linear(
self.num_heads * self.head_dim, hidden_size, bias=False
)
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
batch_size, seq_len, _ = hidden_states.shape
# Project to Q, K, V with bias
query = self.q_proj(hidden_states)
key = self.k_proj(hidden_states)
value = self.v_proj(hidden_states)
# Reshape for attention
query = query.view(batch_size, seq_len, self.num_heads, self.head_dim)
key = key.view(batch_size, seq_len, self.num_kv_heads, self.head_dim)
value = value.view(
batch_size, seq_len, self.num_kv_heads, self.head_dim
)
# Transpose for batch matrix multiplication
query = query.transpose(1, 2) # (batch, num_heads, seq_len, head_dim)
key = key.transpose(1, 2)
value = value.transpose(1, 2)
# Expand KV for GQA if needed
if self.num_kv_groups > 1:
key = key.repeat_interleave(self.num_kv_groups, dim=1)
value = value.repeat_interleave(self.num_kv_groups, dim=1)
# Scaled dot-product attention
scale = self.head_dim**-0.5
attn_weights = torch.matmul(query, key.transpose(-2, -1)) * scale
# Causal mask
causal_mask = torch.triu(
torch.ones(seq_len, seq_len, device=hidden_states.device)
* float("-inf"),
diagonal=1,
)
attn_weights = attn_weights + causal_mask
attn_weights = F.softmax(attn_weights, dim=-1)
attn_output = torch.matmul(attn_weights, value)
# Reshape and project output
attn_output = attn_output.transpose(1, 2).contiguous()
attn_output = attn_output.view(batch_size, seq_len, -1)
return self.o_proj(attn_output)
# Test the attention module
hidden_size = 4096
num_heads = 32
num_kv_heads = 32 # Qwen-7B uses 1:1 ratio
attention = QwenAttention(hidden_size, num_heads, num_kv_heads)
x = torch.randn(2, 64, hidden_size)
output = attention(x)Qwen Attention Module: Input shape: torch.Size([2, 64, 4096]) Output shape: torch.Size([2, 64, 4096]) Attention configuration: Hidden size: 4096 Query heads: 32 KV heads: 32 Head dimension: 128 Parameter breakdown: Total attention parameters: 67,121,152 Bias parameters (Q, K, V): 12,288 Bias as % of attention: 0.02%
The output shape matches the input shape, confirming the attention module correctly processes the sequence while preserving dimensions. The bias parameters add only 0.02% overhead to the attention layer, but Qwen's researchers found this small addition improves attention pattern learning. The 128-dimensional heads (4096 / 32) give sufficient capacity for complex attention patterns. While LLaMA removes biases for simplicity and to reduce parameters, Qwen found that the small parameter increase (less than 0.1% of total) gives real quality improvements.
SwiGLU Feed-Forward Network
After attention aggregates information across positions, the feed-forward network (FFN) processes each position independently, adding nonlinearity and increasing the model's representational capacity. Here we explore why Qwen chose SwiGLU over simpler alternatives.
The evolution of feed-forward design
The original Transformer used a simple two-layer FFN with ReLU activation:
This works, but researchers discovered that gated variants perform better. The key insight: instead of applying a single nonlinearity, use one transformation to decide what information to pass through (the gate) and another to decide which information is available (the values).
The gating mechanism
SwiGLU introduces element-wise multiplication between two parallel branches:
Let's unpack each part:
-
Gate branch (): Projects the input to an intermediate dimension, then applies Swish activation. The Swish output ranges from near-zero (suppress) to unbounded positive (amplify).
-
Value branch (): Projects the input to the same intermediate dimension. These are the "candidate" values that might pass through.
-
Element-wise product (): For each element, the gate controls how much of the corresponding value passes through. Near-zero gate means that value is suppressed; large positive gate means that value is amplified.
The Swish activation
Why Swish rather than ReLU for the gate? Swish gives smooth, non-monotonic gating:
Key properties that make Swish effective:
- Smooth gradients: Unlike ReLU's hard cutoff at zero, Swish transitions gradually, improving gradient flow during training
- Non-monotonic: For small negative inputs, Swish outputs small negative values before approaching zero. This allows the gate to preserve subtle negative signals
- Self-gating: The term acts as a soft switch on the term itself

The complete MLP
After the gated transformation, we project back to the model dimension:
where has shape . The intermediate dimension is typically rather than the standard . This adjustment accounts for SwiGLU having three projection matrices instead of two, keeping the total parameter count similar to a standard FFN.
Implementation
Like LLaMA, Qwen uses the SwiGLU activation function in its feed-forward layers:
class QwenMLP(nn.Module):
"""Qwen MLP with SwiGLU activation"""
def __init__(self, hidden_size: int, intermediate_size: int):
super().__init__()
self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# SwiGLU: Swish(gate) * up, then down projection
return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
# Qwen-7B dimensions
hidden_size = 4096
intermediate_size = 11008 # ~2.7x hidden_size
mlp = QwenMLP(hidden_size, intermediate_size)
x = torch.randn(2, 64, hidden_size)
output = mlp(x)Qwen MLP (SwiGLU): Input shape: torch.Size([2, 64, 4096]) Output shape: torch.Size([2, 64, 4096]) Dimensions: Hidden size: 4096 Intermediate size: 11008 Expansion ratio: 2.69x Parameter breakdown: Gate projection: 45,088,768 Up projection: 45,088,768 Down projection: 45,088,768 Total MLP parameters: 135,266,304
Analyzing the output
The MLP parameters dominate each transformer layer, accounting for roughly two-thirds of per-layer parameters. The 2.69x expansion ratio (rather than the standard 4x) compensates for SwiGLU's three projections instead of two, keeping total parameter count comparable to standard FFN layers while giving the gating mechanism's quality benefits.
Why gating matters
The key insight is that not all intermediate features are equally useful for every input. In a standard FFN, every intermediate neuron contributes to every output (modulated only by ReLU killing negative values). With SwiGLU, the gate branch learns when each intermediate feature should contribute, creating input-dependent computation paths. This is a form of learned sparsity that improves model quality without increasing inference cost.
RoPE: Rotary Positional Embeddings
Transformers process sequences in parallel, treating each position identically. This is computationally efficient but creates a problem: how does the model know that token 5 comes before token 10? The answer lies in positional encoding, and Qwen uses Rotary Position Embeddings (RoPE), the same approach as LLaMA.
The positional encoding challenge
Early transformers used absolute positional embeddings: learn a vector for position 0, another for position 1, and so on. This has two problems:
- Length limitation: The model can only handle positions it saw during training
- No relative reasoning: Positions 10-15 look completely different from positions 100-105, even though the relationship (5 tokens apart) is identical
What we really want is a way to encode position such that attention scores depend on relative distance between tokens, not their absolute positions.
The rotation insight
RoPE reaches this by encoding position as a rotation in embedding space. The key insight is that when two rotations are composed, the result depends only on their angular difference:
If we rotate queries by their position and keys by their position, then when we compute attention (which involves a dot product between queries and keys), the rotation effect depends only on the relative distance.
How rotation encodes position
For each position , we construct a rotation matrix that rotates pairs of dimensions in the embedding space. The rotation angle depends on both the position and the dimension pair:
where indexes the dimension pair (there are pairs for dimension ) and is the position. Lower-frequency rotations (larger ) encode longer-range position information; higher-frequency rotations (smaller ) encode fine-grained local position.

The relative position property
The key property emerges when we compute attention scores. Given a query at position and a key at position :
The attention score depends only on , the rotation corresponding to the relative distance . This means:
- Tokens 5 positions apart always have the same relative encoding, regardless of absolute position
- The model can potentially generalize to longer sequences than it was trained on
- Nearby tokens have similar rotations; distant tokens have dissimilar rotations
Implementation
Qwen uses the same RoPE implementation as LLaMA for positional encoding, which encodes position through rotation of query and key vectors:
def precompute_freqs_cis(dim: int, max_seq_len: int, theta: float = 10000.0):
"""Precompute rotary embedding frequencies"""
freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: dim // 2].float() / dim))
t = torch.arange(max_seq_len, device=freqs.device)
freqs = torch.outer(t, freqs).float()
return torch.polar(torch.ones_like(freqs), freqs)
def apply_rotary_emb(
xq: torch.Tensor, xk: torch.Tensor, freqs_cis: torch.Tensor
):
"""Apply rotary embeddings to query and key tensors"""
xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2))
xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2))
freqs_cis = freqs_cis[: xq_.shape[1], :].unsqueeze(0).unsqueeze(2)
xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(3)
xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(3)
return xq_out.type_as(xq), xk_out.type_as(xk)
# Demonstrate RoPE
head_dim = 128
max_len = 2048
freqs_cis = precompute_freqs_cis(head_dim, max_len)
# Sample Q and K
batch, seq_len, n_heads = 2, 64, 32
q = torch.randn(batch, seq_len, n_heads, head_dim)
k = torch.randn(batch, seq_len, n_heads, head_dim)
q_rope, k_rope = apply_rotary_emb(q, k, freqs_cis)RoPE (Rotary Positional Embeddings): Head dimension: 128 Max sequence length: 2048 Before RoPE: Q shape: torch.Size([2, 64, 32, 128]) K shape: torch.Size([2, 64, 32, 128]) After RoPE: Q shape: torch.Size([2, 64, 32, 128]) K shape: torch.Size([2, 64, 32, 128]) RoPE encodes relative position through rotation, letting length extrapolation beyond training length.
Understanding the code
The implementation uses complex number arithmetic as an efficient way to apply 2D rotations. The precompute_freqs_cis function generates the rotation angles for each position and dimension pair, stored as complex numbers (cosine + i·sine). The apply_rotary_emb function then applies these rotations by treating pairs of real dimensions as complex numbers and multiplying.
This approach is mathematically equivalent to applying rotation matrices but is much more efficient on modern hardware. The key insight remains: after rotation, attention scores between queries and keys naturally encode relative position, letting the model to reason about token relationships regardless of their absolute positions in the sequence.