Skip to content

Add bd3lm - #54

Merged
dhruvdcoder merged 52 commits into
dhruvdcoder:mainfrom
rjagalamari:add-bd3lm
Aug 30, 2026
Merged

dhruvdcoder merged 52 commits into
dhruvdcoder:mainfrom
rjagalamari:add-bd3lm

Conversation

@rjagalamari

Copy link
Copy Markdown
Contributor

What

Adds BD3-LM (Block Discrete Denoising Diffusion Language Model) to xlm-models,
ported from the kuleshov-group reference implementation.

Contribution type

  • Maintained model (xlm-models/)

Labels

model, enhancement

Changes

  • New xlm-models/bd3lm/ package
  • Configs for seq2seq (star easy/medium/hard) and unconditional pre-training (OWT)
  • Model sizes tiny, small, medium
  • Fine-tuning from the released BD3-LM checkpoints via +pretrained=auto
  • Confidence-based decoding

Testing

Verified by running the eval configs: 1.000 exact match on
star-small and star-medium, at batch sizes 1, 4 and 16.
Fine-tuning from kuleshov-group/bd3lm-owt-block_size4 loads and trains.

Documentation

  • xlm-models/bd3lm/README.md
    • docs/models/bd3lm.md

Related issues

@cursor

cursor Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review: pretrained checkpoint loading vs LLaDA/Dream, plus a generation smoke test

Looked specifically at (1) whether loading kuleshov-group/bd3lm-owt-* is the same pathway LLaDA/Dream use, and (2) whether a loaded OWT checkpoint produces a sensible continuation of a made-up prompt.

1. Loading path: this is a new pathway for the same objective

LLaDA and Dream load released Hub weights through the shared xLM inference/train loader:

  • experiment sets hub.repo_id (and the official tokenizer from that same repo)
  • skip_init_weights: true
  • load_model_for_inference / lightning_traindownload_model_weightsload_model_weights_into_model(..., strict=True)
  • state-dict keys match 1:1 because those models wrap/vendor the HF architecture (HF checkpoints load without key prefixing)

BD3LM does not use that path. It adds a model-local loader:

  • +pretrained=auto → top-level pretrained_from: kuleshov-group/bd3lm-owt-block_size${block_size}
  • Bd3lmModel.__init__ calls load_pretrained_bd3lm(), which downloads the repo itself and strips a backbone. prefix
  • a second, parallel offline path (convert_hf_checkpoint.py + model_only_checkpoint_path) can feed the shared loader, but only after a converted local file exists

That prefix strip is a real difference versus LLaDA/Dream. The released checkpoint is an HF BD3LM module whose tensors are named backbone.blocks.0.attn_qkv.weight; this port flattened the DDiT to blocks.0.attn_qkv.weight. A hub.repo_id=kuleshov-group/bd3lm-owt-block_size4 strict load therefore fails:

model keys:   blocks.0.adaLN_modulation.weight
hub keys:     backbone.blocks.0.adaLN_modulation.weight

After strip_backbone_prefix, the same shared helper succeeds with strict=True (131/131 tensors, vocab 50258). So the conversion is justified, but it did not need a new Hydra group or an in-__init__ download. Two ways to stay on the LLaDA/Dream path:

  1. Keep a self.backbone submodule so Hub keys already match, then use hub.repo_id + strict_model_only_load: false for the extra sampling_eps_* buffers.
  2. Convert once (or remap inside load_model_weights_into_model) and load via hub.repo_id / model_only_checkpoint_path after construction, with skip_init_weights.

The in-__init__ loader also skips skip_init_weights / init_dtype, reimplements Hub download, and will fight the shared loader if someone later sets hub.repo_id as well (init remaps, then train/generate tries to load the unconverted file).

2. Tokenizer mismatch on the advertised OWT fine-tune path

The released checkpoint is GPT-2 + one mask token: vocab_embed.embedding is (50258, 768), mask id 50257. That is what the kuleshov-group sampler does (mask_index = tokenizer.vocab_size, then vocab_size += 1).

experiment=owt_bd3lm uses xlm.datamodule.GPT2TokenizerFast, which adds six special tokens (<|cls|>, <|bos|>, <|pad|>, <|unk|>, <|mask|>, <|sep|>). Measured:

tokenizer len() mask id
HF GPT-2 (official BD3LM) 50257 (+1 mask → 50258) 50257
xLM GPT2TokenizerFast 50263 50261 (`<

+pretrained=auto on owt_bd3lm therefore skips the three vocab tensors (shape mismatch) and leaves embedding/lm-head random. LLaDA/Dream avoid this by loading the checkpoint’s own tokenizer (load_auto_tokenizer / DreamTokenizer.from_pretrained on the Hub repo). Skipping vocab tensors is correct for star-graph fine-tunes (vocab 20); it is not correct for the GPT-2 OWT checkpoint this flag is documented to load.

Bd3lmPredictor also has no generate(prompts: List[str]), so job_type=demo / cli_demo.py cannot drive it the way ILM/FlexMDM do. I called _sample directly.

3. Inference smoke test (kuleshov-group/bd3lm-owt-block_size4)

CPU, block_size=4, 32-token continuation, first-hitting + prob_diff confidence. Prompt:

Once upon a time, in a small village near the sea,

A. PR loader + official vocab 50258 / mask 50257 (131/131 tensors loaded):

Once upon a time, in a small village near the sea, Western Canadian University quarterback Taylor Paul played his first game in a pro varsity jersey, this time as a redshirt. Tim Woking has just revealed an unexpected

This is coherent English and typical of a small OWT LM (topic drift from fairy-tale prefix into news/sports). The port + remapped weights do generate, when the tokenizer matches the checkpoint.

B. PR loader + owt_bd3lm’s xLM GPT2TokenizerFast (128/131 loaded; embeddings skipped):

Once upon a time, in a small village near the sea, vik ChefEnjoy myth Ethiopianifestyle hotlybledon demonstrate Henry credit Mostly Nationsut Moj herself Hard Fei contingency licensee accounting Slug swimming …

Unusable. Same weights, wrong vocab/mask.

Suggested follow-ups

  1. Load released OWT checkpoints through hub.repo_id (after prefix remap, or by keeping backbone.), not via +pretrained=auto inside __init__.
  2. For OWT, use a GPT-2 tokenizer whose vocab is 50258 with mask id 50257 — do not use xlm.datamodule.GPT2TokenizerFast as-is.
  3. Add Bd3lmPredictor.generate() if demo/CLI continuation is in scope; today only predict(batch) exists.

Happy to turn (1)+(2) into a follow-up patch if wanted.

Added instructions for unconditional generation using bd3lm.
Made the tokenizer match the released checkpoints and added an inference config for unconditional generation.

Ran gen. ppl on kuleshov-group/bd3lm-owt-block_size4 at length 1024 and got 25.74
over 300 samples, against 25.70 in the paper.
Added instructions for converting and generating with the released checkpoint.
Clarify usage instructions for converting HF checkpoints.
Updated docstring to clarify loss computation logic.
Removed redundant explanation about token rows in YAML.
Updated various sections for clarity and consistency, including loss handling and config details.
Clarified descriptions of `indices` and `var_length` parameters. Updated instructions for using `eval.model_only_checkpoint_path` and added details about verification conditions.
Moved the transformer under self.backbone so the released checkpoint names
match ours 1:1, and hub.repo_id can load them directly like LLaDA does.
Generation is now one command with +pretrained=auto, and the converter is gone.
Removed redundant explanations about diffusion denoising, loss on padding, and predictor behavior. Updated sections for clarity and conciseness.
Comment on lines +1 to +8
# @package _global_
# Load the released OWT checkpoint matching your block_size.
#
# xlm job_type=train ... +pretrained=auto # block_size=4 -> block_size4
# xlm job_type=eval ... +pretrained=auto block_size=8 # -> block_size8
#
hub:
repo_id: kuleshov-group/bd3lm-owt-block_size${block_size}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename this overlay from pretrained=auto to pretrained=kuleshov_group_bd3lm (file kuleshov_group_bd3lm.yaml). auto hides which checkpoint family this is; the name should say it loads the kuleshov-group OWT releases.

Also restrict it to OWT (owt_bd3lm / owt_bd3lm_inference). Do not use it on star.

Comment thread xlm-models/bd3lm/model_bd3lm.py Outdated
Comment on lines +859 to +871

own = self.state_dict()
mismatched = []
for key in [k for k in state_dict if k.startswith(prefix)]:
tail = key[len(prefix):]
if tail in own and own[tail].shape != state_dict[key].shape:
mismatched.append((tail, tuple(own[tail].shape), tuple(state_dict.pop(key).shape)))
if mismatched:
log = logging.getLogger(__name__).warning
log("[bd3lm] %d tensor(s) skipped on shape and will train from scratch - "
"usually a vocabulary difference:", len(mismatched))
for name, ours, theirs in mismatched:
log("[bd3lm] %s: model %s vs checkpoint %s", name, ours, theirs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please drop this shape-mismatch skip. We should not support loading kuleshov-group/bd3lm-owt-* onto star (or any non-GPT-2 vocab). Star trains from scratch; transferring English LM blocks while randomly initializing embed/lm-head is not a path we want.

Keep the sampling_eps_* drop above if a strict OWT hub.repo_id load still needs it. Just do not special-case a 20-vs-50258 vocab.

Comment thread xlm-models/bd3lm/README.md Outdated
Comment on lines +97 to +123
## Fine-tuning from a released checkpoint

Add one flag:

```bash
xlm job_type=train job_name=my_finetune \
experiment=star_medium_bd3lm \
+pretrained=auto
```

The checkpoint is derived from `block_size`, so `block_size=8` pulls the block_size8
weights. Four are compatible, all 768 / 12 blocks / 12 heads (`bd3lm_small`) on the
GPT-2 vocabulary:

| block size | checkpoint |
|---|---|
| 4 | `kuleshov-group/bd3lm-owt-block_size4` |
| 8 | `kuleshov-group/bd3lm-owt-block_size8` |
| 16 | `kuleshov-group/bd3lm-owt-block_size16` |
| 1024 | `kuleshov-group/bd3lm-owt-block_size1024-pretrain` |

On a task whose vocabulary is not GPT-2's, the three vocabulary-sized tensors cannot
transfer. They are skipped and reported, and the transformer blocks still load:

```
[bd3lm] 3 tensor(s) skipped on shape and will train from scratch - usually a vocabulary difference:
[bd3lm] backbone.vocab_embed.embedding: model (27, 768) vs checkpoint (50258, 768)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this star + +pretrained=auto fine-tune path. Star should not load the released OWT checkpoint.

Document the overlay only on OWT generation / continued LM training, and use +pretrained=kuleshov_group_bd3lm once the config is renamed. Same change in docs/models/bd3lm.md (the matching “Fine-tuning from a released checkpoint” section).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes:
Removed the star fine-tuning on released OWT checkpoints
Renamed auto.yaml to kuleshov_group_bd3lm.yaml

Removed loading the released checkpoint for star, and renamed the
pre-trained yaml used for OWT inference.
Removed NaN checks and associated error handling from transformer block.
Removed NaN check and associated print statements before processing blocks.
Removed NaN loss debug checks from loss calculation.
Removed NaN check from model output.
Removed unnecessary blank lines to improve code readability.
Added entropy-based stopping criteria to model configuration.
Added model configuration for sampling entropy control.
Added model configuration with sampling settings.
Removed commented-out code for clarity.
Refactor entropy computation to handle each row individually and update stopping conditions.
Refactor _compute_entropy method to compute entropy for the entire input at once instead of row by row.
Added new imports for predictors, data modules, and noise schedule.
Added new types and predictors for unconditional and sequence-to-sequence predictions.
Removed unused imports from the __init__.py file.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 37.79%. Comparing base (6ae3855) to head (09af859).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #54   +/-   ##
=======================================
  Coverage   37.79%   37.79%           
=======================================
  Files         131      131           
  Lines       12416    12416           
  Branches     1711     1711           
=======================================
  Hits         4693     4693           
  Misses       7430     7430           
  Partials      293      293           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dhruvdcoder
dhruvdcoder merged commit 34e3601 into dhruvdcoder:main Aug 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants