Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0d751f5
convert: add diffusion_gemma4 (block-diffusion Gemma 4) conversion su…
lnigam Jun 1, 2026
8d0feb8
model: register diffusion-gemma4 arch (reuses gemma4 graph)
lnigam Jun 1, 2026
b329cf3
model: diffusion-gemma4 bidirectional graph + self-conditioning input
lnigam Jun 1, 2026
03e53ea
model: wire self-conditioning MLP into the diffusion-gemma4 graph
lnigam Jun 1, 2026
dfe6e77
examples: block-diffusion generation CLI for diffusion-gemma4
lnigam Jun 2, 2026
9b7d75b
diffusion-gemma4: self-conditioning input channel (feedback)
lnigam Jun 3, 2026
3f12024
diffusion-gemma4: prompt conditioning via prefix attention
lnigam Jun 3, 2026
16c4d37
diffusion-gemma4: apply the chat template to the prompt
lnigam Jun 3, 2026
191e8dc
diffusion-gemma4: fix prompt conditioning (keep prompt tokens active)
lnigam Jun 3, 2026
589fc2f
diffusion-gemma4: greedy read-out of the final canvas
lnigam Jun 3, 2026
f231fd6
diffusion-gemma4: extract the clean final answer
lnigam Jun 3, 2026
d718e8f
diffusion-gemma4: offload to GPU by default
lnigam Jun 3, 2026
b014b82
diffusion_gemma4: KV-cache reuse for block-diffusion generation
lnigam Jun 8, 2026
c01ee56
diffusion-gemma (v7): rename arch + multimodal (gemma4 vision) support
lnigam Jun 8, 2026
2e49867
diffusion-gemma cli: -n controls block count, generation timing, inli…
lnigam Jun 8, 2026
cbb6987
diffusion-gemma: place precomputed transposed embedding on the offloa…
lnigam Jun 8, 2026
dbb2002
diffusion-gemma cli: report encoder-phase prefill timing
lnigam Jun 8, 2026
bbd8abd
diffusion-gemma: top-k host sampling (top-k softmax/entropy/self-cond…
lnigam Jun 8, 2026
e8b1b22
diffusion-gemma: sparse top-k self-conditioning via on-device F16 gather
lnigam Jun 8, 2026
8b1d4a1
diffusion-gemma: OpenAI-compatible HTTP server (llama-server analogue)
lnigam Jun 8, 2026
7df4b25
diffusion-gemma: add CUDA graph GPU sampling server path
lnigam Jun 9, 2026
61e43a1
diffusion-gemma: keep self-cond sampling on device
lnigam Jun 9, 2026
e5aabf4
diffusion-gemma: keep denoising loop on device
lnigam Jun 9, 2026
ed78362
diffusion-gemma: use output flag for persistent inputs
lnigam Jun 9, 2026
5b15dfd
diffusion-gemma: checkpoint device-loop early stop
lnigam Jun 9, 2026
27fe91a
diffusion-gemma: make device early stop every step
lnigam Jun 9, 2026
df9a66f
examples: update diffusion gemma mtmd bitmap loading
lnigam Jun 9, 2026
b7f4bfc
incorporating unsloth model related changes
lnigam Jun 11, 2026
66a924e
diffusion-gemma: add tuning options and server accounting
lnigam Jun 11, 2026
500ce4e
diffusion: add max denoising loop switch
lnigam Jun 11, 2026
7d362cc
diffusion: wire CUDA sampling optimization controls
lnigam Jun 11, 2026
0a098ec
ggml-cuda: add fused diffusion sampling kernels
lnigam Jun 11, 2026
4627948
diffusion: keep final denoise token copy
lnigam Jun 11, 2026
fbf9b61
diffusion: default to fused CUDA full softmax
lnigam Jun 11, 2026
a969058
diffusion-gemma: enable fast CUDA defaults
lnigam Jun 11, 2026
2e6695d
diffusion-gemma: add CUDA MMQ tile override flag
lnigam Jun 11, 2026
3ecb5b4
llama : scope diffusion graph paths to diffusion gemma
lnigam Jun 11, 2026
f9538b7
fix flake8_lint failure
lnigam Jun 11, 2026
273a992
avoid no-repack
lnigam Jun 11, 2026
dd0cf04
fix chat template issues impacting the mean denoising steps in GGUF t…
lnigam Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 145 additions & 1 deletion common/arg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ static std::initializer_list<enum llama_example> mmproj_examples = {
LLAMA_EXAMPLE_MTMD,
LLAMA_EXAMPLE_SERVER,
LLAMA_EXAMPLE_CLI,
LLAMA_EXAMPLE_DIFFUSION,
};

static std::string read_file(const std::string & fname) {
Expand Down Expand Up @@ -2229,7 +2230,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.image.emplace_back(item);
}
}
).set_examples({LLAMA_EXAMPLE_MTMD, LLAMA_EXAMPLE_CLI}));
).set_examples({LLAMA_EXAMPLE_MTMD, LLAMA_EXAMPLE_CLI, LLAMA_EXAMPLE_DIFFUSION}));
add_opt(common_arg(
{"--image-min-tokens"}, "N",
"minimum number of tokens each image can take, only used by vision models with dynamic resolution (default: read from model)",
Expand Down Expand Up @@ -3838,6 +3839,149 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
string_format("enable visual diffusion mode (show progressive generation) (default: %s)", params.diffusion.visual_mode ? "true" : "false"),
[](common_params & params) { params.diffusion.visual_mode = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--no-diffusion-gpu-sampling"},
"disable CUDA block-diffusion sampling fast path",
[](common_params & params) { params.diffusion.gpu_sampling = false; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--no-diffusion-device-selfcond"},
"disable device-resident block-diffusion self-conditioning",
[](common_params & params) {
params.diffusion.device_self_cond = false;
params.diffusion.fused_self_cond_embd = false;
}
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--no-diffusion-device-denoise-loop"},
"disable device-side block-diffusion canvas and stop-state updates",
[](common_params & params) { params.diffusion.device_denoise_loop = false; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--run-max-denoising-step"},
"skip device stop-state polling and run the full block-diffusion denoising schedule",
[](common_params & params) { params.diffusion.run_max_denoising_step = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-pin-host-outputs"},
"register compact diffusion output buffers as pinned host memory",
[](common_params & params) { params.diffusion.pin_host_outputs = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-self-cond-top-k"}, "N",
string_format("block-diffusion sparse self-conditioning width (default: %d)", params.diffusion.self_cond_top_k),
[](common_params & params, int value) { params.diffusion.self_cond_top_k = value; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-input-gpu-groups"}, "N",
string_format("bitmask of block-diffusion decoder input groups assigned to GPU backend (default: %u)", params.diffusion.input_gpu_groups),
[](common_params & params, int value) { params.diffusion.input_gpu_groups = (uint32_t) std::max(value, 0); }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-default-top-k"}, "N",
"block-diffusion top-k used when --top-k is not explicitly provided",
[](common_params & params, int value) { params.diffusion.default_top_k = value; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-force-top-k"}, "N",
"block-diffusion server: override per-request top_k when N > 0",
[](common_params & params, int value) { params.diffusion.force_top_k = value; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-fused-self-cond-embd"},
"use fused device self-conditioning embedding input for block diffusion",
[](common_params & params) { params.diffusion.fused_self_cond_embd = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--no-diffusion-fused-self-cond-embd"},
"disable fused device self-conditioning embedding input for block diffusion",
[](common_params & params) { params.diffusion.fused_self_cond_embd = false; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-fuse-final-softcap"},
"move final logit softcap into the CUDA diffusion sampling kernel",
[](common_params & params) { params.diffusion.fuse_final_logit_softcap = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--no-diffusion-fuse-final-softcap"},
"disable fused final logit softcap in the CUDA diffusion sampling kernel",
[](common_params & params) { params.diffusion.fuse_final_logit_softcap = false; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-separate-encoder-decoder"},
"build separate block-diffusion encoder and decoder graph variants",
[](common_params & params) { params.diffusion.separate_encoder_decoder = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-cuda-direct-self-cond"},
"write CUDA diffusion self-conditioning directly into decoder graph inputs",
[](common_params & params) { params.diffusion.cuda_direct_self_cond = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--no-diffusion-cuda-direct-self-cond"},
"disable direct CUDA diffusion self-conditioning writes into decoder graph inputs",
[](common_params & params) { params.diffusion.cuda_direct_self_cond = false; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-cuda-final-tokens-on-stop"},
"copy final diffusion tokens only when the device stop condition is reached",
[](common_params & params) { params.diffusion.cuda_final_tokens_on_stop = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--no-diffusion-cuda-final-tokens-on-stop"},
"copy final diffusion tokens every denoising step",
[](common_params & params) { params.diffusion.cuda_final_tokens_on_stop = false; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-cuda-fused-top-k-sample"},
"fuse CUDA diffusion top-k selection and sampling",
[](common_params & params) { params.diffusion.cuda_fused_top_k_sample = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--no-diffusion-cuda-fused-top-k-sample"},
"disable fused CUDA diffusion top-k selection and sampling",
[](common_params & params) { params.diffusion.cuda_fused_top_k_sample = false; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-cuda-parallel-full-softmax"},
"parallelize CUDA diffusion full-vocab sampling when top-k is 0",
[](common_params & params) { params.diffusion.cuda_parallel_full_softmax = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-cuda-fused-full-softmax"},
"fuse CUDA diffusion full-vocab softmax sampling and self-conditioning",
[](common_params & params) { params.diffusion.cuda_fused_full_softmax = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--no-diffusion-cuda-fused-full-softmax"},
"disable fused CUDA diffusion full-vocab softmax sampling path",
[](common_params & params) { params.diffusion.cuda_fused_full_softmax = false; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--no-diffusion-cuda-fast-top-k"},
"disable CUDA diffusion CUB/fast top-k selection path",
[](common_params & params) { params.diffusion.cuda_fast_top_k = false; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-cuda-mmq-max-x"}, "N",
"set GGML_CUDA_MMQ_MAX_X for Ada/Blackwell CUDA MMQ tile selection (-1 = leave env, 0 = disable override)",
[](common_params & params, int value) { params.diffusion.cuda_mmq_max_x = value; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--top-k-start"}, "N",
"block-diffusion: anneal top-k from N at the first (high-entropy) denoising step (with --top-k-end)",
[](common_params & params, int value) { params.diffusion.top_k_start = value; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--top-k-end"}, "N",
"block-diffusion: anneal top-k to N at the last denoising step (with --top-k-start)",
[](common_params & params, int value) { params.diffusion.top_k_end = value; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--top-k-tail-correction"},
"block-diffusion: use the exact full-vocab entropy (logsumexp) for the accept/stop signal under top-k",
[](common_params & params) { params.diffusion.top_k_tail_correction = true; }
).set_examples({ LLAMA_EXAMPLE_DIFFUSION }));
add_opt(common_arg(
{"--diffusion-eps"}, "F",
string_format("epsilon for timesteps (default: %.6f)", (double) params.diffusion.eps),
Expand Down
5 changes: 5 additions & 0 deletions common/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,11 @@ struct llama_context_params common_context_params_to_llama(const common_params &
cparams.op_offload = !params.no_op_offload;
cparams.swa_full = params.swa_full;
cparams.kv_unified = params.kv_unified;
cparams.diffusion_self_cond_top_k = params.diffusion.self_cond_top_k;
cparams.diffusion_input_gpu_groups = params.diffusion.input_gpu_groups;
cparams.diffusion_fused_self_cond_embd = params.diffusion.fused_self_cond_embd;
cparams.diffusion_fuse_final_logit_softcap = params.diffusion.fuse_final_logit_softcap;
cparams.diffusion_separate_encoder_decoder = params.diffusion.separate_encoder_decoder;

cparams.type_k = params.cache_type_k;
cparams.type_v = params.cache_type_v;
Expand Down
28 changes: 28 additions & 0 deletions common/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,11 @@ struct common_params_vocoder {
struct common_params_diffusion {
int32_t steps = 128;
bool visual_mode = false;
bool gpu_sampling = true; // use CUDA diffusion sampling fast path when available
bool device_self_cond = true; // keep diffusion self-conditioning state on device
bool device_denoise_loop = true; // update diffusion canvas/stop state on device
bool run_max_denoising_step = false; // skip device stop polling and run the full denoising schedule
bool pin_host_outputs = false; // register compact D2H output buffers as pinned host memory

float eps = 0; // epsilon for timesteps
int32_t block_length = 0; // block length for generation
Expand All @@ -399,6 +404,29 @@ struct common_params_diffusion {

float cfg_scale = 0; // classifier-free guidance scale
bool add_gumbel_noise = false; // add gumbel noise to the logits if temp > 0.0

// block-diffusion (diffusion-gemma) top-k host sampling knobs
int32_t top_k_start = 0; // anneal top-k from this (first/high-entropy step) ...
int32_t top_k_end = 0; // ... to this (last step); both > 0 enables annealing
bool top_k_tail_correction = false; // use exact full-vocab entropy for accept/stop
int32_t default_top_k = 0; // top-k used when --top-k is not explicitly provided
int32_t force_top_k = 0; // server: override per-request top_k when > 0
int32_t self_cond_top_k = 256; // sparse self-conditioning gather width
uint32_t input_gpu_groups = 63; // decoder input tensor groups assigned to GPU backend

// CUDA diffusion sampling fast-path knobs.
bool cuda_fast_top_k = true;
bool cuda_direct_self_cond = true;
bool cuda_final_tokens_on_stop = true;
bool cuda_fused_top_k_sample = true;
bool cuda_parallel_full_softmax = false;
bool cuda_fused_full_softmax = true;
int32_t cuda_mmq_max_x = -1; // -1 = leave GGML_CUDA_MMQ_MAX_X unchanged

// Diffusion graph-shape knobs.
bool fused_self_cond_embd = true;
bool fuse_final_logit_softcap = true;
bool separate_encoder_decoder = false;
};

// reasoning API response format (not to be confused as chat template's reasoning format)
Expand Down
3 changes: 3 additions & 0 deletions conversion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
"Gemma4AssistantForCausalLM": "gemma",
"Gemma4ForConditionalGeneration": "gemma",
"Gemma4ForCausalLM": "gemma",
"DiffusionGemma4ModelForBlockDiffusion": "diffusion_gemma",
"DiffusionGemmaForBlockDiffusion": "diffusion_gemma",
"Gemma4UnifiedForConditionalGeneration": "gemma",
"Gemma4UnifiedAssistantForCausalLM": "gemma",
"GemmaForCausalLM": "gemma",
Expand Down Expand Up @@ -251,6 +253,7 @@
"CogVLMForCausalLM": "cogvlm",
"DeepseekOCR2ForCausalLM": "deepseek",
"DeepseekOCRForCausalLM": "deepseek",
"DiffusionGemmaForBlockDiffusion": "gemma",
"DotsOCRForCausalLM": "dotsocr",
"Exaone4_5_ForConditionalGeneration": "exaone",
"Gemma3ForConditionalGeneration": "gemma",
Expand Down
96 changes: 96 additions & 0 deletions conversion/diffusion_gemma.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from __future__ import annotations

import json
from typing import Iterable

from torch import Tensor

from .base import ModelBase, SentencePieceTokenTypes
from .gemma import Gemma4Model
import gguf


@ModelBase.register("DiffusionGemma4ModelForBlockDiffusion", "DiffusionGemmaForBlockDiffusion")
class DiffusionGemmaModel(Gemma4Model):
model_arch = gguf.MODEL_ARCH.DIFFUSION_GEMMA

def _create_vocab_sentencepiece(self):
tokens, scores, toktypes = super()._create_vocab_sentencepiece()

def looks_control(s: str) -> bool:
return (s in ("<s>", "</s>")
or (s.startswith("<|") and s.endswith(">"))
or (s.startswith("<") and s.endswith("|>")))

for i, tok in enumerate(tokens):
s = tok.decode("utf-8", "ignore") if isinstance(tok, (bytes, bytearray)) else str(tok)
if toktypes[i] in (SentencePieceTokenTypes.NORMAL, SentencePieceTokenTypes.USER_DEFINED) and looks_control(s):
toktypes[i] = SentencePieceTokenTypes.CONTROL
return tokens, scores, toktypes

def set_gguf_parameters(self):
self.hparams.setdefault("num_kv_shared_layers", 0)
self.hparams.setdefault("hidden_size_per_layer_input", 0)

super().set_gguf_parameters()
self.gguf_writer.add_causal_attention(False)

canvas_length = self.find_hparam(["canvas_length"], optional=False)
if canvas_length is None or int(canvas_length) <= 0:
raise ValueError("DiffusionGemma conversion requires a positive root canvas_length")
self.gguf_writer.add_diffusion_canvas_length(int(canvas_length))

gen_cfg_path = self.dir_model / "generation_config.json"
if gen_cfg_path.is_file():
with open(gen_cfg_path, encoding="utf-8") as f:
gen_cfg = json.load(f)
sampler_cfg = gen_cfg.get("sampler_config", {})
if "max_denoising_steps" in gen_cfg:
self.gguf_writer.add_diffusion_eb_max_steps(int(gen_cfg["max_denoising_steps"]))
if "t_min" in gen_cfg:
self.gguf_writer.add_diffusion_eb_t_min(float(gen_cfg["t_min"]))
if "t_max" in gen_cfg:
self.gguf_writer.add_diffusion_eb_t_max(float(gen_cfg["t_max"]))
if "entropy_bound" in sampler_cfg:
self.gguf_writer.add_diffusion_eb_entropy_bound(float(sampler_cfg["entropy_bound"]))
if "stability_threshold" in gen_cfg:
self.gguf_writer.add_diffusion_eb_stability_threshold(int(gen_cfg["stability_threshold"]))
if "confidence_threshold" in gen_cfg:
self.gguf_writer.add_diffusion_eb_confidence_threshold(float(gen_cfg["confidence_threshold"]))

@classmethod
def filter_tensors(cls, item):
name, gen = item

if name.endswith("layer_scalar"):
name = name + ".weight"

return super().filter_tensors((name, gen))

def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
if "vision" in name or "embed_vision" in name:
return

if name.startswith("model.encoder.layers.") and "layer_scalar" in name:
yield (self.format_tensor_name(gguf.MODEL_TENSOR.ENC_LAYER_OUT_SCALE, bid), data_torch)
return

if name.startswith("model.encoder."):
return

if name.startswith("model.decoder.self_conditioning."):
sub = name[len("model.decoder.self_conditioning."):]
sc_map = {
"pre_norm.weight": gguf.MODEL_TENSOR.SC_PRE_NORM,
"gate_proj.weight": gguf.MODEL_TENSOR.SC_GATE,
"up_proj.weight": gguf.MODEL_TENSOR.SC_UP,
"down_proj.weight": gguf.MODEL_TENSOR.SC_DOWN,
}
if sub in sc_map:
yield (self.format_tensor_name(sc_map[sub]), data_torch)
return

if name.startswith("model.decoder."):
name = "model." + name[len("model.decoder."):]

yield from super().modify_tensors(data_torch, name, bid)
Loading
Loading