Skip to content

BD3-LM — Block Discrete Denoising Diffusion Language Model

1. Overview

bd3lm integrates BD3-LM into xLM. The backbone is a DDiT-style Transformer with rotary positional embeddings.

It supports:

  • unconditional pre-training
  • supervised seq2seq training
  • fine-tuning from the released BD3-LM checkpoints on HuggingFace
@inproceedings{arriola2025block,
  title     = {Block Diffusion: Interpolating Between Autoregressive and Diffusion Language Models},
  author    = {Marianne Arriola and Aaron Gokaslan and Justin T Chiu and Zhihan Yang and Zhixuan Qi and Jiaqi Han and Subham Sekhar Sahoo and Volodymyr Kuleshov},
  booktitle = {The Thirteenth International Conference on Learning Representations},
  year      = {2025},
  url       = {https://arxiv.org/abs/2503.09573}
}

Package: xlm-models/bd3lm/. See xlm-models/bd3lm/README.md.

2. Files at a glance

Module Public classes / helpers
model_bd3lm.py Bd3lmModel, DDiTBlock, DDiTBlockCausal, DDiTFinalLayer, Rotary, EmbeddingLayer, TimestepEmbedder, block_diff_mask, DDiT
loss_bd3lm.py Bd3lmLoss
predictor_bd3lm.py Bd3lmPredictor, Bd3lmUnconditionalPredictor
datamodule_bd3lm.py DefaultBd3lmCollator, Bd3lmSeq2SeqCollator, Bd3lmSeq2SeqPredCollator, Bd3lmUnconditionalPredCollator, Bd3lmEmptyDataset, print_batch_bd3lm
noise_schedule.py Bd3lmNoise, LogLinearNoise, CosineNoise, ExpNoise, LogarithmicNoise
metrics_bd3lm.py seq2seq_exact_match_update_fn, seq2seq_token_accuracy_update_fn, mean_metric_update_fn, perplexity_metric_update_fn
types_bd3lm.py Bd3lmBatch, Bd3lmSeq2SeqBatch, Bd3lmLossDict, Bd3lmPredictionDict, Bd3lmModel (Protocol)

3. Architecture

A DDiT-style Transformer with rotary embeddings and a block-causal attention mask. Three sizes from the reference implementation are available:

model= hidden blocks heads cond_dim
bd3lm_tiny 256 8 8 64
bd3lm_small (= bd3lm, default) 768 12 12 128
bd3lm_medium 1024 24 16 128

model.config.model.length is prompt_size + target_size, and attn_backend defaults to sdpa so the model runs without flash-attn.

forward(
    indices: Tensor,                          # (B, 2L) cat(x_t, x_0) in training
    sigma: Optional[Tensor],                  # (B,) noise level; zeroed when
                                              #   algo.time_conditioning=false
    attention_mask: Optional[Tensor] = None,  # (B, L); None when nothing is padded
    positions: Optional[Tensor] = None,       # (B, L); None means plain arange
    sample_mode: bool = False,
    store_kv: bool = False,
) -> Tensor                                   # (B, L, vocab_size)

indices is the noisy and clean sequences concatenated, which is why it is twice the length in training: each noisy block attends to the clean tokens of the blocks before it. Only the noisy half is returned as logits.

4. Batch contract

Both training collators emit:

Field Shape Notes
x0 (B, L) clean sequence
xt (B, L) noised sequence
input_ids (B, 2L) cat(xt, x0), what the model consumes
attention_mask (B, L) 1 = real, 0 = pad
loss_mask (B, L) 1 where the position is [MASK] and should be scored
target_ids (B, L) seq / (B, T) s2s the clean tokens — unshifted, no -100
loss_scale (B, L) from the noise schedule
sigma (B, 1) per-example noise level

5. Loss

Bd3lmLoss computes the diffusion NLL over the masked positions.

loss_on_padding (declared once in the model_type config and read by both the loss and the collator) controls whether answer-side PAD takes part in diffusion and in the loss.

6. Collators

Config Class Role
default_bd3lm DefaultBd3lmCollator pre-training: noises the whole sequence
seq2seq_bd3lm Bd3lmSeq2SeqCollator keeps the prompt clean, noises the answer
seq2seq_pred_bd3lm Bd3lmSeq2SeqPredCollator prompt → prediction batch
unconditional_pred_bd3lm Bd3lmUnconditionalPredCollator all-[MASK] canvas for unconditional generation

7. Predictor

Bd3lmPredictor implements the semi-autoregressive sampler: blocks are generated left to right, and within each block one position is unmasked per step.

Key (under model.config.sampling) Default Meaning
confidence_decoding true unmask the most confident position rather than a random one
confidence prob_diff scoring criterion: top_prob, prob_diff, entropy
first_hitting true first-hitting sampler (Zheng et al., 2025)
var_length true stop at EOS instead of filling the window
nucleus_p 0.9 nucleus filtering within a block
kv_cache false cache finalised blocks instead of re-encoding the prefix

Setting confidence_decoding=false gives the reference implementation's uniformly random unmasking.

8. Metrics

Metric Where
accumulated_loss train / val / test, both model_types
perplexity val / test, bd3lm_unconditional only
exact_match, token_accuracy val / test prediction, bd3lm (seq2seq) only

For unconditional sample quality rather than modelling quality, use experiment=owt_bd3lm_inference, which wires up xLM's post-hoc generative perplexity evaluator against GPT-2 Large. See Generative perplexity.

9. Configs / experiments

Hydra configs under xlm-models/bd3lm/configs/:

Config Role
model/bd3lm{,_tiny,_small,_medium}.yaml architecture + algo + sampling
model_type/bd3lm.yaml seq2seq: loss, predictor, exact-match metrics
model_type/bd3lm_unconditional.yaml loss + perplexity, unconditional predictor
experiment/star_{easy,medium,hard}_bd3lm.yaml seq2seq training
experiment/star_{easy,medium,hard}_bd3lm_inference.yaml matching eval configs
experiment/owt_bd3lm.yaml unconditional pre-training on OpenWebText
experiment/owt_bd3lm_inference.yaml unconditional generation + generative perplexity
datamodule/owt_bd3lm_pred.yaml generation-only datamodule (downloads nothing)
pretrained/kuleshov_group_bd3lm.yaml released checkpoint from the Hub, chosen by block_size
datasets/bd3lm_empty_pred.yaml blank rows for unconditional generation

The package is registered in xlm_models.json ("bd3lm": "bd3lm").

Seq2seq training

Star-graph path finding, in three difficulties:

experiment dataset prompt / target
star_easy_bd3lm dhruveshpatel/star-small 28 / 12
star_medium_bd3lm dhruveshpatel/star-medium 36 / 12
star_hard_bd3lm dhruveshpatel/star-hard 116 / 24
xlm job_type=train job_name=my_run experiment=star_medium_bd3lm

Evaluate with the matching _inference config:

xlm job_type=eval job_name=my_eval \
  experiment=star_medium_bd3lm_inference \
  eval.ckpt_path=/path/to/last.ckpt

Unconditional pre-training

xlm job_type=train job_name=my_run experiment=owt_bd3lm

Unconditional generation

owt_bd3lm_inference generates from an empty [MASK] sequence and scores the samples with GPT-2 Large.

xlm job_type=eval job_name=my_gen experiment=owt_bd3lm_inference +pretrained=kuleshov_group_bd3lm

10. Results

experiment exact match token accuracy
star_easy (dhruveshpatel/star-small) 1.000 1.000
star_medium (dhruveshpatel/star-medium) 1.000 1.000

Verified at batch sizes 1, 4 and 16, and on star-medium additionally with block_size 4 and kv_cache=true.

Generative perplexity, released checkpoint

kuleshov-group/bd3lm-owt-block_size4 loaded through owt_bd3lm_inference, generating at length 1024 and scored by GPT-2 Large:

samples Gen. PPL
this implementation 300 25.74
reference, Table 7 300 25.70

Sampling matches scripts/gen_ppl/genppl_bd3lm.sh in the reference repo: batch size 1, nucleus_p=0.9, first_hitting=true, var_length=false, kv_cache=true, algo.T=5000, and confidence_decoding=false so the sampler unmasks at random as the reference does.