A modular PyTorch transformer for research. Every component, including attention, FFN, normalization, positional encoding, residual connection, optimizer, and dataset, is registered by name and selected from a YAML. Swap anything without touching the trainer.
Supports both encoder-decoder summarization (MeetingBank, Multi-News) and decoder-only causal-LM pretraining at the ~500M-parameter scale (FineWeb-Edu, FSDP, bf16, KV-cache generation).
Started as a hand-rolled transformer for meeting summarization. Trying any new attention or connection variant meant editing several files. This rewrite makes the base composable: experiments are one YAML each, components are decoupled, and the trainer doesn't know what attention you picked.
pip install -e .
# default: residual transformer on MeetingBank
python -m src.cli.train
# ~500M decoder-only pretrain on FineWeb-Edu
python -m src.cli.train +experiment=pretrain_500m
# mix and match anything inline
python -m src.cli.train attention=gqa_rope feedforward=swiglu normalization=rmsnormSame four steps for every kind (attention, FFN, norm, optimizer, dataset, ...):
-
Write the class. Inherit from the base, decorate with
@<KIND>.register("name").# src/components/attention/my_attn.py @ATTENTION.register("my_attn") class MyAttention(AttentionBase): ...
-
Import the module in the package's
__init__.pyso the decorator runs at import time. -
Add
configs/attention/my_attn.yamlwithname: my_attnand any kwargs. -
Use it:
python -m src.cli.train attention=my_attn.
No trainer or builder edits needed.
| Group | Choices |
|---|---|
| Attention | mha, gqa, gqa_rope, mqa, sliding_window, sliding_gqa, gemma3_hybrid, csa, hca, mla, msa, kda. See docs/attention.md for the variant notes and diagrams. |
| FFN | relu, swiglu, geglu. See docs/feedforward.md. |
| Normalization | layernorm, rmsnorm. See docs/normalization.md. |
| Positional | sinusoidal, rope, alibi, rope/nope hybrid. See docs/positional.md. |
| Connection | residual, residual_sandwich, hyperconnection, mhc. See docs/connections.md. |
| Optimizer | adamw, muon_adamw, lion, adafactor, ademamix, mars_adamw. See docs/optimizers.md. |
| Scheduler | cosine_warmup, linear_warmup, inverse_sqrt_warmup, polynomial_warmup, wsd, none. See docs/schedulers.md. |
| Dataset | meetingbank, multi_news, fineweb_edu, c4, wikitext, wikipedia. See docs/datasets.md. |
Plus: bf16/fp16 autocast, gradient accumulation, torch.compile, DDP/FSDP, HF Hub push, KV-cache .generate() across every attention variant, Rich TUI.
Evaluation metrics include loss, perplexity, token/top-5 accuracy, ROUGE, BLEU, throughput, latency, and peak memory. What they measure and how they're computed are documented in docs/metrics.md.
Added Kimi Delta Attention with FLA's chunkwise KDA kernel on CUDA, while keeping the exact recurrence as the CPU reference. The 20-epoch MeetingBank run finished all 12,920 optimizer updates in about one hour on the local RTX 4060 Laptop GPU. It used bf16, physical batch size 1, and eight-step gradient accumulation for an effective batch size of 8.
This is the unsmoothed train/loss series from the completed run. The final logged training loss was 1.3462.
The final checkpoint was not the best one, so I evaluated every checkpoint that the run actually saved on the full MeetingBank validation split:
| saved epoch | optimizer step | validation loss |
|---|---|---|
| 0 | 646 | 3.3420 |
| 4 | 3,230 | 2.5381 |
| 8 | 5,814 | 2.5980 |
| 12 | 8,398 | 2.7164 |
| 16 | 10,982 | 2.8413 |
| 19 | 12,920 | 2.9069 |
Epoch 4 is the checkpoint on the Hub. Later epochs still raise token accuracy a little, but validation cross-entropy gets worse, so uploading epoch 19 just because it was last would hide the overfitting.
Future runs do this selection while training: validation runs halfway through every epoch and again at epoch end, and *_best.pt is overwritten only when current_val_loss < best_val_loss. That keeps one useful checkpoint on disk instead of another 354 MiB file every few epochs.
The published checkpoint was then evaluated through the same benchmark_hf_collection.py path used for the other attention models: core metrics over the complete 861-batch validation loader and generation metrics over 128 greedy summaries.
evaluation quality![]() |
generation quality![]() |
throughput![]() |
quality vs. efficiency![]() |
| metric | KDA epoch 4 |
|---|---|
| validation loss | 2.5381 |
| perplexity | 12.6559 |
| token accuracy | 0.5497 |
| top-5 accuracy | 0.7400 |
| ROUGE-1 | 0.2556 |
| ROUGE-2 | 0.0853 |
| ROUGE-L | 0.2055 |
| BLEU | 7.90 |
| evaluation throughput | 4,789 tok/s |
| generation throughput | 92.86 tok/s |
| average forward latency | 13.17 ms |
| peak CUDA memory | 189.5 MB |
| parameters | 30,896,560 |
| checkpoint size | 370.9 MB |
The raw checkpoint ranking and standalone outputs live in benchmarks/kda. KDA is also merged into the combined loss, quality, throughput, and tradeoff charts in benchmarks/attention. The best checkpoint, exact 12,920-point loss CSV/SVG, config, tokenizer, architecture image, and metric-bearing model card are published at Pradheep1647/meeting_summarization_kda-meetingbank-bs8-e20-bf16-4 and included in the transformer-lab collection.
The first cut of MSA (minimax sparse attention) was close to the paper but not exact. fix(attention): align msa with paper corrected it. To check the fix was actually worth it I trained both versions on the same footing: MeetingBank causal summarization, 20 epochs, batch 8, fp32, lr 1e-4, and identical seed/data. I then evaluated the final checkpoints on the validation split (core metrics over 100 batches, ROUGE/BLEU over 16 generated summaries).
| metric | old (2d018af) |
new (e1a3421) |
Δ new−old |
|---|---|---|---|
| eval loss | 2.484 | 2.571 | +0.087 |
| perplexity | 11.99 | 13.08 | +1.09 |
| token acc | 0.518 | 0.502 | −0.017 |
| top-5 acc | 0.744 | 0.732 | −0.012 |
| ROUGE-1 | 0.084 | 0.177 | +0.093 |
| ROUGE-2 | 0.050 | 0.102 | +0.052 |
| ROUGE-L | 0.083 | 0.173 | +0.090 |
| BLEU | 2.20 | 6.77 | +4.58 |
| eval tok/s | 7186 | 7278 | +92 |
The new implementation is the one from the official MiniMax tech report; the old one was my own approximation of it. Two things changed in msa.py, and both are about dropping my shortcuts in favour of what the report actually specifies:
-
how the block selector gets trained. my old version kept hard top-k for the forward pass but added the index branch's block log-probs straight onto the sparse attention logits (
block_score_bias), purely sow_iq/w_ikwould get gradient from the LM loss. it works, but it contaminates the attention the model actually uses. every value ends up weighted by a blend of real query-key affinity and a coarse block-level score. the report doesn't do that. the new version detaches the index branch entirely, keeps the forward logits pureq·k, and trains the selector with a separate KL loss (kl_alignment_loss) that matches the index distribution to the full attention's. the selector learns to predict which blocks real attention wants instead of leaking into it. -
the local block is now mandatory. old top-k just took the k highest-scoring blocks, so the block containing the query itself could get dropped when the index scores were noisy. the new one reserves a slot for the local block and fills the rest with the best non-local blocks, exactly as in the report. for a decoder the most recent tokens are the ones you can least afford to miss.
with that, the numbers make sense. under teacher forcing the old score-bias acts like a mild prior: the gold token is handed over at every step, nothing goes off the rails, and the extra bias even nudges perplexity slightly lower. that is why old looks a hair better on eval loss / ppl / token accuracy. but that regime never stresses the selector. the moment you generate free-running, the two fixes pay off: clean attention, a selector trained to mimic it, and guaranteed local context mean errors stop compounding. that's the ~2× ROUGE and ~3× BLEU jump (ROUGE-L 0.083 → 0.173, BLEU 2.2 → 6.8). throughput is identical, so it's a pure correctness win. the teacher-forced numbers that "favour" the old impl are an artifact of the crutch, not a real edge. judge attention on generation, not perplexity.
pytest tests/ -qMIT. See LICENSE.





