TriDLM-124M-split / README.md
AntonXue's picture
TriDLM split: final checkpoint (20260829-1047-qk1-lr1e-4, step 100000)
6611f0e verified
|
Raw History Blame Contribute Delete
4.6 kB
---
license: apache-2.0
language: en
library_name: transformers
tags: [masked-diffusion, diffusion-language-model, gpt2, openwebtext, triangular-attention]
datasets: [Skylion007/openwebtext]
---
# TriDLM-124M-split
A 124M-parameter **masked-diffusion language model** (MDLM-style absorbing-state denoiser) with **every layer: even heads causal, odd heads anti-causal**, one of four arms of the
TriDLM experiment: can a diffusion LM keep its quality when every attention head is *triangular* (causal or anti-causal), so that
attention costs ~L²/2 instead of L²? Code, training logs and the full experiment record: https://github.com/AntonXue/TriDLM (`_claude/`).
## The four arms (identical model, data order, masks and recipe; only the per-head attention mask differs)
| repo | attention | val NELBO (nats/token) | ppl | train tok/s (8×GH200) |
|---|---|---|---|---|
| `EER6/TriDLM-124M-bidir` | all heads bidirectional (the stock MDLM control) | 3.374 | 29.2 | 2.60M |
| `EER6/TriDLM-124M-split` | every layer: even heads causal, odd heads anti-causal | 3.395 | 29.8 | 2.73M |
| `EER6/TriDLM-124M-alt` | even layers all-causal, odd layers all-anti-causal | 3.406 | 30.2 | 2.55M |
| `EER6/TriDLM-124M-causal` | all heads causal (lower-bound control) | 4.940 | 139.7 | 2.73M |
NELBO = the exact-count masked-diffusion ELBO (equal in expectation to the MDLM 1/t objective) on all 110,451 OWT-validation blocks.
`split` trails `bidir` by 0.020 nats (~2 % ppl) and `alt` by 0.032; `causal` (no right context) is the expected lower bound.
Generation metrics (gen-ppl under gpt2-large, MAUVE-256) per sampler and prompting condition are in the GitHub record.
## Model
GPT-2-small denoiser ported from nanoGPT (12 layers, 768 d, 12 heads, learned absolute positions, LayerNorm without bias, GELU,
tied embeddings), vocab 50304 = gpt2's 50257 + `[MASK]` (id 50257) + pads, **no next-token shift: position i predicts token i**, and
**per-head QK-norm** (LayerNorm on q and k over the head dim). QK-norm is essential: without it every triangular arm diverged from
attention-logit growth (max |q·k/√d| in layers 1–3 grew from ~40 to 10⁶ by 25–30k steps at both lr 3e-4 and 1e-4, while the
bidirectional arm stayed at ~42); with it all four arms train cleanly.
## Training
OpenWebText (gpt2 BPE, 1024-token packed blocks with EOS separators), **100k steps × 512 × 1024 = 52.4B tokens** (5.9 epochs),
AdamW lr 1e-4 (β 0.9/0.95, wd 0.1, clip 1.0), warmup 1000 → 89,000 stable → 10,000 cosine decay to 0, bf16, seed 1. Loss = mean over
rows of the mean cross-entropy over the masked positions of each row, with the mask count n ~ U{1..L} stratified across the batch
and the mask set a uniform n-subset; `[MASK]`/pad logits are −∞ (SUBS). Trained on 8 GH200 nodes (1 GPU each) in ~5.5 h.
## Usage
```python
import torch
from transformers import AutoModel, AutoTokenizer
tok = AutoTokenizer.from_pretrained("EER6/TriDLM-124M-split") # gpt2 BPE + [MASK] = 50257
model = AutoModel.from_pretrained("EER6/TriDLM-124M-split", trust_remote_code=True, attn_impl="sdpa").cuda().eval()
# attn_impl="sdpa": dense masks, any torch >= 2.1; "flex" (block-sparse, needs torch >= 2.5 + Triton) is numerically identical.
x0 = tok("The quick brown fox", return_tensors="pt")["input_ids"].cuda() # (B, L <= 1024)
mask = torch.rand(x0.shape, device=x0.device) < 0.5
xt = torch.where(mask, torch.full_like(x0, tok.mask_token_id), x0)
logits = model(xt).logits # (B, L, 50304); position i predicts token i
logits[..., 50257:] = float("-inf") # SUBS: never predict [MASK] or pads
```
Sampling: use the random-order ancestral sampler (`eval.py: ancestral_sample`) — gen-ppl 56 ± 3 under gpt2-large at NFE 1024 for
`bidir`. Confidence-ordered decoding (argmax *or* sampled tokens, threshold or one-at-a-time) collapses into repetition on these
base models; MAUVE catches it. The `causal`/`split`/`alt` checkpoints must be evaluated through their own `attn_mode` (saved in
the config); never run them with a full mask.
## Notes
- The training set (mdlm-vibes' OWT prep) contains one block with a stray `[MASK]` id; it makes one logged loss value `inf` per epoch
and has no effect on the gradients.
- No EMA; the final checkpoint is the post-decay endpoint. The end-of-stable-phase full checkpoint (step 90k, optimizer included) is
kept offline for WSD extensions.
- Ported model code (MIT, nanoGPT) is included as `modeling_tridlm.py`; this repo is apache-2.0.