def draw_transformer_architecture(n_layers=2):
"""Draw a diagram of the decoder-only transformer architecture."""
fig, ax = plt.subplots(1, 1, figsize=(10, 14))
ax.set_xlim(-1, 11)
ax.set_ylim(-1, 20)
ax.axis('off')
ax.set_aspect('equal')
# Colors for each concept
C = {
'embed': '#4ECDC4', # teal
'rope': '#FFE66D', # yellow
'attn': '#FF6B6B', # red
'ffn': '#95E1D3', # light green
'norm': '#C4B5FD', # purple
'residual': '#94A3B8', # gray
'output': '#F97316', # orange
'softmax': '#FB923C', # light orange
}
def box(x, y, w, h, label, color, sublabel=None, fontsize=11):
rect = mpatches.FancyBboxPatch((x, y), w, h, boxstyle='round,pad=0.15',
facecolor=color, edgecolor='#333', linewidth=1.5)
ax.add_patch(rect)
ax.text(x + w/2, y + h/2 + (0.15 if sublabel else 0), label,
ha='center', va='center', fontsize=fontsize, fontweight='bold')
if sublabel:
ax.text(x + w/2, y + h/2 - 0.25, sublabel,
ha='center', va='center', fontsize=8, color='#555')
def arrow(x1, y1, x2, y2, color='#333'):
ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
arrowprops=dict(arrowstyle='->', color=color, lw=1.5))
# --- Input tokens ---
y = 0
ax.text(5, y, '"the cat sat on the mat"', ha='center', va='center',
fontsize=13, fontfamily='monospace',
bbox=dict(boxstyle='round,pad=0.3', facecolor='#F1F5F9', edgecolor='#333'))
ax.text(9, y, 'β token IDs', ha='left', fontsize=9, color='#666')
# --- Embedding ---
y = 1.5
arrow(5, 0.4, 5, y)
box(2, y, 6, 0.9, 'Token Embedding', C['embed'], 'Concept 1: integers β vectors')
ax.text(9, y + 0.45, f'nn.Embedding({len(VOCAB)}, dim)', ha='left', fontsize=9,
color='#666', fontfamily='monospace')
# --- RoPE ---
y = 3.0
arrow(5, 2.5, 5, y)
box(2, y, 6, 0.9, '+ RoPE Positional Encoding', C['rope'], 'Concept 2: inject token order')
ax.text(9, y + 0.45, 'rotate Q, K vectors', ha='left', fontsize=9, color='#666')
# --- Transformer blocks ---
block_start = 4.5
block_h = 4.5
for i in range(n_layers):
by = block_start + i * (block_h + 0.8)
arrow(5, by - 0.4, 5, by)
# Block border
block_rect = mpatches.FancyBboxPatch((1.2, by - 0.1), 7.6, block_h + 0.2,
boxstyle='round,pad=0.2', facecolor='#F8FAFC', edgecolor='#94A3B8',
linewidth=1.5, linestyle='--')
ax.add_patch(block_rect)
ax.text(1.5, by + block_h - 0.1, f'Block {i+1}', fontsize=10,
fontweight='bold', color='#64748B')
ax.text(8.5, by + block_h - 0.1, 'Concept 13', fontsize=8, color='#94A3B8', ha='right')
# RMSNorm
ny1 = by + 0.2
box(2.5, ny1, 5, 0.7, 'RMSNorm', C['norm'], 'Concept 11')
# Attention
ay = ny1 + 1.1
arrow(5, ny1 + 0.7, 5, ay)
box(2.5, ay, 5, 0.7, 'Multi-Head Attention', C['attn'], 'Concepts 3-9: QΒ·KβsoftmaxβV')
# Residual arrow for attention
ax.annotate('', xy=(2.0, ay + 0.35), xytext=(2.0, ny1 + 0.35),
arrowprops=dict(arrowstyle='->', color=C['residual'], lw=2,
connectionstyle='arc3,rad=-0.5'))
ax.text(0.8, ay - 0.1, 'x + f(x)', fontsize=8, color=C['residual'], ha='center')
# RMSNorm 2
ny2 = ay + 1.1
arrow(5, ay + 0.7, 5, ny2)
box(2.5, ny2, 5, 0.7, 'RMSNorm', C['norm'], 'Concept 11')
# FFN
fy = ny2 + 1.1
arrow(5, ny2 + 0.7, 5, fy)
box(2.5, fy, 5, 0.7, 'SwiGLU FFN', C['ffn'], 'Concept 12: per-token transform')
# Residual arrow for FFN
ax.annotate('', xy=(2.0, fy + 0.35), xytext=(2.0, ny2 + 0.35),
arrowprops=dict(arrowstyle='->', color=C['residual'], lw=2,
connectionstyle='arc3,rad=-0.5'))
ax.text(0.8, fy - 0.1, 'x + f(x)', fontsize=8, color=C['residual'], ha='center')
# --- Final norm ---
ny = block_start + n_layers * (block_h + 0.8) - 0.1
arrow(5, ny - 0.5, 5, ny)
box(2.5, ny, 5, 0.7, 'RMSNorm', C['norm'], 'Concept 11: final normalization')
# --- Output head ---
oy = ny + 1.2
arrow(5, ny + 0.7, 5, oy)
box(2, oy, 6, 0.9, 'Output Head (Linear)', C['output'], 'Concept 14: dim β vocab_size')
ax.text(9, oy + 0.45, f'β logits ({len(VOCAB)} scores)', ha='left', fontsize=9, color='#666')
# --- Softmax ---
sy = oy + 1.4
arrow(5, oy + 0.9, 5, sy)
box(2, sy, 6, 0.9, 'Softmax β Next Token', C['softmax'], 'Concept 15: probabilities β sample')
ax.set_ylim(-0.8, sy + 1.6)
ax.set_title('Decoder-Only Transformer (LLaMA-style)', fontsize=15, fontweight='bold', pad=15)
plt.tight_layout()
plt.show()
draw_transformer_architecture(n_layers=2)