Skip to content

[discrete diffusion] Add DiffusionGemma pipeline and schedulers#13986

Merged
kashif merged 45 commits into
huggingface:mainfrom
kashif:diffusion-gemma-schedulers
Jun 25, 2026
Merged

[discrete diffusion] Add DiffusionGemma pipeline and schedulers#13986
kashif merged 45 commits into
huggingface:mainfrom
kashif:diffusion-gemma-schedulers

Conversation

@kashif

@kashif kashif commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Adds a DiffusionGemma block-diffusion pipeline, alongside the schedulers already on this branch (discrete DDIM, entropy bound, and a uniform mode for block refinement).

DiffusionGemma is an encoder-decoder block-diffusion model: the encoder reads the prompt into a KV cache and the decoder denoises a fixed-size canvas by cross-attending to it. The pipeline runs the outer canvas loop and the inner denoising loop, sampling candidates each step, committing the most confident ones via BlockRefinementScheduler in uniform corruption mode, and renoising the rest. Structure mirrors the LLaDA2 and dflash (#13699) pipelines.

The model itself lives in transformers as DiffusionGemmaForBlockDiffusion (released in 5.12.0).

Tested:

  • pipeline unit tests pass (plumbing, callbacks, output types)
  • the pipeline drives the real tiny checkpoint end to end without error

Quality on the full google/diffusiongemma-26B-A4B-it checkpoint still needs a GPU run.

@github-actions github-actions Bot added size/L PR with diff > 200 LOC documentation Improvements or additions to documentation tests utils pipelines schedulers labels Jun 18, 2026
@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@zucchini-nlp zucchini-nlp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looking great! A couple questions from quick skimming

Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py Outdated
Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py Outdated
Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py Outdated
@kashif kashif mentioned this pull request Jun 18, 2026
6 tasks

@yiyixuxu yiyixuxu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks for the PR! i left a few comments

I reviewed this through the lens of diffuser convention/style. If some of these choices are intentional to keep things familiar for Transformers users, let me know, and we can figure out the right balance together

def __call__(
self,
prompt: str | list[str] | None = None,
messages: list[dict[str, str]] | None = None,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think between prompt and messages, we only need accept prompt since it's a really cheap into messages

it's just this, no?

messages = [{"role": "user", "content": prompt}]

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.

Makes sense. The one wrinkle is image prompts, which we pass through messages today, so I'll fold the prompt/messages simplification into the image input rework so single-image and text both stay clean. Coming in a follow-up.

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.

Made prompt the primary input and dropped the tokenized intermediates. Kept messages for raw multi-turn/multimodal conversations (per the thread below with zucchini), and added a raw image arg for the simple prompt+image case, so it is all raw inputs now.

Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py Outdated
Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py Outdated
kashif and others added 4 commits June 19, 2026 09:35
Adds optional Gibbs corrector sweeps after each predictor step for
uniform diffusion, recovering the LOO denoiser in closed form so it
works on the released checkpoint with no retraining.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kashif kashif requested a review from dg845 June 20, 2026 08:16
The denoiser is a Transformers model, so adapters (LoRA, DoRA, ...) load
through its native PEFT integration rather than the diffusers LoRA loader.
Also dispatch the predictor-corrector by scheduler capability instead of class.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread src/diffusers/schedulers/scheduling_entropy_bound.py
@kashif kashif force-pushed the diffusion-gemma-schedulers branch from 01d4990 to 1f46257 Compare June 23, 2026 11:00
Comment thread docs/source/en/api/pipelines/diffusion_gemma.md

@zucchini-nlp zucchini-nlp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for working on it, i think the overall latency and quality matches model released in transformers!

@dg845

dg845 commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

When I tried out the example script:

import torch
from transformers import AutoProcessor, DiffusionGemmaForBlockDiffusion
from diffusers import BlockRefinementScheduler, DiffusionGemmaPipeline

model_id = "google/diffusiongemma-26B-A4B-it"
dtype = torch.bfloat16

model = DiffusionGemmaForBlockDiffusion.from_pretrained(model_id, dtype=dtype, device_map="auto")
# model = DiffusionGemmaForBlockDiffusion.from_pretrained(model_id, dtype=dtype)
processor = AutoProcessor.from_pretrained(model_id)
scheduler = BlockRefinementScheduler()

pipe = DiffusionGemmaPipeline(model=model, scheduler=scheduler, processor=processor)
pipe.model.model.decoder = torch.compile(pipe.model.model.decoder, mode="reduce-overhead", fullgraph=True)
# pipe.to("cuda")

output = pipe(
    prompt="Why is the sky blue?",
    gen_length=256,
    num_inference_steps=48,
    cache_implementation="static",
    generator=torch.Generator("cuda").manual_seed(42),
)

print(output.texts[0])

I get the following torch.compile error from the script:

torch._dynamo.exc.Unsupported: Skip calling `torch.compiler.disable()`d function
  Explanation: Skip calling function `<function AlignDevicesHook.pre_forward at 0x73a280162950>` since it was wrapped with `torch.compiler.disable` (reason: None)
  Hint: Remove the `torch.compiler.disable` call

The script works if I either don't torch.compile the model decoder, or use torch.compile without device_map="auto" (using the commented out code above). So I think the torch.compile + device_map="auto" case is what's causing the error, can you look into it?

EDIT: the error can also be mitigated by using fullgraph=False.

Comment thread .codegraph/.gitignore Outdated
Comment thread docs/source/en/api/pipelines/diffusion_gemma.md

from diffusers import BlockRefinementScheduler, DiffusionGemmaPipeline

model_id = "google/diffusiongemma-26B-A4B-it"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@yiyixuxu @dg845 like the rest of our diffusers checkpoints repositories, where we have pipeline components coming from a different repo, could we make this pipeline a diffusers-style checkpoint with something like:

model = DiffusionGemmaForBlockDiffusion.from_pretrained(model_id, dtype=torch.bfloat16, device_map="auto")
processor = AutoProcessor.from_pretrained(model_id)
scheduler = BlockRefinementScheduler()

pipe = DiffusionGemmaPipeline(model=model, scheduler=scheduler, processor=processor)
pipe.save_pretrained(...)

Comment thread docs/source/en/api/pipelines/diffusion_gemma.md Outdated
Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py Outdated
Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py
Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py
Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py Outdated
Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py Outdated
# Encode the tokens not yet in the cache (the whole prompt on the first block, the last committed canvas
# afterwards), so the decoder reuses the encoder KV cache instead of re-encoding the full sequence.
cached_len = past_key_values.get_seq_length()
torch.compiler.cudagraph_mark_step_begin()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need this? We usually don't use CUDAgraph markers like this explicitly in our pipelines.

@sayakpaul

Copy link
Copy Markdown
Member

@claude could you review this PR and also comment on the usage of the torch ops usage pattern from the lens of efficiency?

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error —— View job


I'll analyze this and get back to you.

Comment thread src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py Outdated
Comment thread src/diffusers/schedulers/scheduling_entropy_bound.py

@dg845 dg845 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for your work on this PR!

@kashif kashif merged commit 21ba394 into huggingface:main Jun 25, 2026
15 checks passed
@kashif kashif deleted the diffusion-gemma-schedulers branch June 25, 2026 06:22
DN6 pushed a commit that referenced this pull request Jul 1, 2026
* Add discrete DDIM and entropy bound schedulers and a uniform mode for block refinement

* Add DiffusionGemma block-diffusion pipeline

* Add DiffusionGemma pipeline tests and docs

* Put DiffusionGemma docs under the Text pipelines section

* Add static cache and fullgraph-compiled decoder path to DiffusionGemma pipeline

* Compile decoder externally for the static cache path instead of a pipeline flag

* Prefill the encoder once into a reusable cache and sync default denoising steps

* Support image prompts by forwarding pixel_values to the encoder prefill

* Restyle docstrings to satisfy doc-builder

* Sort the new scheduler and pipeline exports

* Let any of the three schedulers drive the pipeline

* Document the schedulers and updated defaults in the pipeline docs

* Sort the scheduler dummy objects

* Set scheduler sampling knobs on the scheduler config, not the pipeline call

* Accept raw prompt/image/messages instead of pre-tokenized model inputs

* Add leave-one-out predictor-corrector to DiscreteDDIM scheduler

Adds optional Gibbs corrector sweeps after each predictor step for
uniform diffusion, recovering the LOO denoiser in closed form so it
works on the released checkpoint with no retraining.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Forward PEFT adapter API on the DiffusionGemma pipeline

The denoiser is a Transformers model, so adapters (LoRA, DoRA, ...) load
through its native PEFT integration rather than the diffusers LoRA loader.
Also dispatch the predictor-corrector by scheduler capability instead of class.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix callback kwargs gathering on Python < 3.12

Build callback_kwargs with a loop instead of a dict comprehension, whose
own scope hides locals() on pre-3.12 (PEP 709), causing KeyError: 'canvas'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Speed up diffusion gemma sampling to match transformers

Add adaptive stopping (stop a block once its prediction is stable and
confident) and make the decoder compile cudagraph-safe via
cudagraph_mark_step_begin + logits clone. ~175 -> ~372 tok/s. Also align
the decoder mask with the new transformers#46654 layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Enable adaptive stopping by default

Default confidence_threshold to 0.005 to match the released checkpoint
and transformers, so the speedup is on out of the box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fold corrector sweeps into the step budget

Run fewer predictor steps and spend the freed forwards on the corrector,
so predictor-corrector sampling costs the same total forwards as plain
ancestral (~2x faster), matching the paper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Commit the converged prediction on adaptive stop

Ancestral schedulers like DiscreteDDIM only clean the canvas on the final
step, so stopping early left noise tokens. Use the denoiser argmax instead,
which is the converged answer and matches the canvas for commit schedulers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* anneal sampling temperature and fix static cache decoder mask

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Update src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py

Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>

* Update src/diffusers/schedulers/scheduling_entropy_bound.py

Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>

* Update src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py

Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>

* address dg845 review comments

* fix entropy scheduler temperature scaling

* update decoder mask for new transformers

* self-condition on the temperature-shaped logits

* move temperature annealing into EntropyBoundScheduler

* self-condition on the entropy scheduler's shaped logits

* show torch.compile + static cache in the usage example

* removed wrong commit

* Update src/diffusers/pipelines/diffusion_gemma/pipeline_diffusion_gemma.py

Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>

* address review comments

* expose pred_logits on all schedulers

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>
Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation pipelines schedulers size/L PR with diff > 200 LOC tests utils

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants