YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Assignment 1 Report — Transformers from Scratch, Ablations, and BLT
1. Introduction and Task
- Task: sequence-to-sequence deciphering. Source = binary cipher strings
(8 bits per plaintext character); target = plaintext English
(
brown_cipher.txt/brown_plain.txt, 5,000 line-aligned pairs). - Goal: implement a full encoder–decoder transformer from basic PyTorch ops
(no
nn.Transformer/nn.MultiheadAttention) and run a controlled five-way ablation over positional encoding, attention, normalization, and tokenization (Table 1 in the assignment). - Cipher (own analysis): repeating-key XOR with key
ANLP2026, i.e.cipher_byte[i] = ord(plain[i]) XOR key[i % 8]. Equivalent to eight conflict-free substitution tables indexed byposition mod 8. The mapping is exactly solvable given absolute position (or a recoverable start anchor), and source byte i aligns 1:1 with plaintext character i.
2. Implementation Overview
2.1 Core modules (all from scratch)
- Scaled dot-product attention: softmax(QKᵀ/√d_k)V with additive −inf
masking (
src/models/attention.py). Mask convention: 1/True = attend. - MHA: 8 heads, learned Q/K/V/O projections, heads split/merged by reshape.
- GQA: 8 query heads share 2 KV heads; K/V projected to n_kv·d_head and
expanded with
repeat_interleave. Cuts KV projection parameters 4×. - Positional encodings:
- C1/C3/C4/C5: sinusoidal absolute PE added to token embeddings.
- C2: RoPE applied to Q and K in self-attention and cross-attention (target queries and source keys rotated by their own positions). Source sequences also begin with BOS so relative rotations have a clear line start. Sinusoidal PE is not added when RoPE is active.
- Normalization: LayerNorm and RMSNorm from elementary ops; Pre-LN
everywhere (
x = x + sublayer(norm(x))). - FFN: position-wise 256→1024→256 with GELU.
- Seq2Seq transformer: 4 encoder + 4 decoder layers, d_model 256, embeddings scaled by √d_model, padding + causal masks, untied output head.
2.2 BLT modules (C5)
- LocalEncoder (bytes → patches): byte embedding (d_local 128) + sinusoidal PE + 2 light transformer layers, then masked mean-pooling over fixed 4-byte patches, projected to d_model 256.
- Global transformer: same 4+4 stack as C1, but on patch sequences (length ÷4).
- LocalDecoder (patches → bytes): global decoder outputs are shifted right by one patch (learned start-patch), upsampled 4×, combined with byte embeddings, passed through 2 light causal layers, then a byte head.
- Causality: (1) patch-level causal mask in the global decoder, and (2) byte-level causal mask in the target-side local encoder. Without (2), bidirectional local attention leaks future bytes into patch vectors before the global causal mask can help (Section 5.4).
2.3 Tokenization
- Subword (C1–C4): from-scratch BPE (whitespace pre-tokenization,
</w>end-of-word marker), vocab 64 (7 merges; ~1.16 characters per token). Compact vocab keeps targets near character-aligned with the cipher while remaining “standard subword.” Trained on the train split only. Source: each 8-bit group → id 0–255 (+ specials), with BOS and EOS. - Token-free (C5): raw UTF-8 bytes on both sides, vocab 260.
2.4 Training setup
Shared across all five configs: batch 8, AdamW (lr 3e-4, weight decay 0.01), 1,000-step warmup + cosine decay, grad clip 1.0, dropout 0.1, 30 epochs, CE ignoring PAD, seed 42. Split 4,000 / 500 / 500. Joint truncation to 510 characters (room for BOS+EOS). Hardware: RTX 3060 Laptop (6 GB). WandB logging; best-val checkpoints for HuggingFace upload.
3. Evaluation Protocol
All numbers use greedy decoding on the 500-example test set.
- Bit-level accuracy: decode to text, re-encode pred/ref as 8 bits/char, compare position-wise over the longer length. Tokenizer-independent → primary metric across C1–C5.
- Sequence accuracy: exact string match.
- Levenshtein distance: mean character edit distance.
- BLEU / ROUGE-1/2/L: tokenized models only (C1–C4). BLEU = sacreBLEU score on the 0–100 scale.
- Efficiency: tokens/sec and peak GPU memory in training, s/epoch, and wall-clock greedy decode time.
4. Results
4.1 Main table (test set, greedy decoding)
| Config | Bit Acc ↑ | Seq Acc ↑ | Levenshtein ↓ | BLEU ↑ | ROUGE-1 ↑ | ROUGE-L ↑ | Val loss ↓ | Params |
|---|---|---|---|---|---|---|---|---|
| C1 base | 0.800 | 0.028 | 18.1 | 70.2 | 0.861 | 0.860 | 0.172 | 7.47M |
| C2 RoPE | 0.989 | 0.850 | 0.28 | 99.4 | 0.993 | 0.993 | 0.0016 | 7.47M |
| C3 GQA | 0.763 | 0.0 | 45.9 | 46.7 | 0.733 | 0.730 | 0.260 | 6.29M |
| C4 RMSNorm | 0.805 | 0.038 | 11.8 | 77.9 | 0.898 | 0.898 | 0.109 | 7.47M |
| C5 BLT | 0.971 | 0.002 | 24.2 | n/a | n/a | n/a | 0.106* | 8.80M |
*C5 loss is per-byte, not per-subword — not directly comparable to C1–C4.
4.2 Efficiency table
| Config | s/epoch ↓ | Train tokens/s ↑ | Peak GPU mem (MB) ↓ | Greedy decode, 500 ex. (s) ↓ |
|---|---|---|---|---|
| C1 base | 85.4 | 16,589 | 2,615 | 451 |
| C2 RoPE | 88.8 | 15,963 | 2,611 | 487 |
| C3 GQA | 80.7 | 17,550 | 2,601 | 416 |
| C4 RMSNorm | 83.2 | 17,026 | 2,538 | 449 |
| C5 BLT | 39.0 | 42,035 (bytes) | 1,207 | 293 |
(C5 tokens/s counts bytes; s/epoch is the fair speed comparison.)
Plots: bar_bit_accuracy.png, bar_mean_levenshtein.png, bar_bleu.png,
c1_vs_c5_memory.png, c1_vs_c5_speed.png, c1_vs_c5_epoch_time.png, plus
train/val curves from *_history.json / WandB.
5. Analysis and Discussion
5.1 Representation matters as much as the transformer stack
An earlier BPE vocab of 4,000 left C1–C4 at BLEU <1 and val CE 4.8 even
though the architecture could overfit a single batch to BLEU 100. Large-vocab
BPE (3.8 chars/token) destroyed the cipher’s 1:1 positional alignment and
forced the decoder to recover soft alignments while classifying among 4,000
tokens. Switching to a compact 64-token BPE (~1.16 chars/token), adding
source BOS, and enabling RoPE on cross-attention raised C1 to BLEU 70.2
with on-topic decipherment (see outputs/c1_base_samples.json). Include a
train/val loss curve here.
5.2 Single-component ablations vs C1
- C2 (RoPE): best overall (BLEU 99.4, sequence accuracy 85%, val loss 0.0016). With BOS and cross-attention RoPE, relative source–target offsets make this monotonic cipher nearly trivial; sample predictions are near-exact.
- C3 (GQA): fewer parameters (6.29M vs 7.47M) and fastest subword epochs (80.7s), at a clear quality cost (BLEU 46.7 vs 70.2). Expected capacity/speed tradeoff for 2 shared KV heads on a small model.
- C4 (RMSNorm): better than base (BLEU 77.9 vs 70.2, val 0.109 vs 0.172, slightly less memory). Dropping mean-centering / bias helps here.
- C5 (BLT): strongest bit accuracy among non-RoPE models (0.971) and lowest peak memory (1,207MB). Predictions are readable with scattered character errors; BLEU/ROUGE are n/a without a tokenizer.
5.3 BLT tradeoffs (C5 vs C1) — required focus
- Quality: bit accuracy 0.971 vs 0.800. Levenshtein 24.2 vs 18.1 — compact-BPE C1 is slightly cleaner on edit distance; C5 wins on bits.
- Memory: 1,207MB vs 2,615MB (−54%). Global layers run on patch sequences (length ÷4).
- Training speed: 39.0 vs 85.4 s/epoch (−54%).
- Inference: C5 greedy decode is faster than long compact-BPE decode here (293s vs 451s for 500 examples); patching amortizes work even though generation is per-byte.
- Metrics: report BLEU/ROUGE only for C1–C4, as the assignment specifies.
5.4 Causality bug found and fixed in BLT (strong viva material)
An early C5 run had suspiciously low teacher-forced loss but degenerate greedy output (“deeee…”). The target-side local encoder used bidirectional byte self-attention before patch pooling, so patch vectors already contained future bytes; the patch-level causal mask downstream could not undo that leakage. Fix: byte-level causal masking in the target-side local encoder (source side stays bidirectional). Perturbation check: changing byte 100 leaves logits at positions <100 unchanged. After the fix (and the shared training fixes above), final C5 reaches val loss 0.106 and bit accuracy 0.971. Lesson: in hierarchical decoders, causality must hold at every level, not only the top.
5.5 Limitations / future work (short)
- Joint truncation to 510 characters discards the long tail of lines (median length ~554); full-line BLEU against untruncated references would be capped even with perfect known-prefix predictions.
- Greedy decoding only (per assignment); beam search would likely improve C1/C3/C5 further.
- BLT uses fixed 4-byte patches; entropy-based dynamic patching (original BLT paper) is the natural next step.
- Compact BPE (64) is still “standard subword” but near character-level; report this design choice explicitly.
6. Reproducibility
bash scripts/run_all.shtrains and evaluates all five configs sequentially with thermal cooldowns (configs/*.yaml, seed 42).- WandB run links: [TODO]. HuggingFace checkpoint repo: [TODO].
- Per-config metrics:
outputs/<config>_metrics.json; samples:outputs/<config>_samples.json; aggregate:outputs/results_table.md.
References (excluded from page count)
- Vaswani et al., Attention Is All You Need, 2017.
- Su et al., RoFormer: Enhanced Transformer with Rotary Position Embedding, 2021.
- Ainslie et al., GQA: Training Generalized Multi-Query Transformer Models, 2023.
- Zhang & Sennrich, Root Mean Square Layer Normalization, 2019.
- Pagnoni et al., Byte Latent Transformer: Patches Scale Better Than Tokens, 2024.
- Sennrich et al., Neural Machine Translation of Rare Words with Subword Units, 2016.