什么是扩散语言模型?从零理解Clover-35B-A3B-Preview的块扩散生成原理
什么是扩散语言模型?从零理解Clover-35B-A3B-Preview的块扩散生成原理
Clover-35B-A3B-Preview 是由 DiffuSpace 开源的首个扩散语言模型(Diffusion Language Model, DLM),它在保留 35B 参数 MoE 大模型能力的基础上,用"块扩散 + 低置信度解码"取代传统逐字生成,带来并行去噪与 128K 长上下文能力。本文将从零讲透它的块扩散生成原理。
从"打字机"到"去噪":扩散语言模型是什么?🤔
我们熟悉的大语言模型(如 GPT 系列)是自回归(Autoregressive)的:像打字机一样,一个词一个词地往后敲,每个新词只能参考前面已写好的内容。这种方式天然串行,速度受限于序列长度。
扩散语言模型借鉴了图像扩散模型"去噪"的思想:
| 对比项 | 自回归模型 | 扩散语言模型(Clover) |
|---|---|---|
| 生成方式 | 从左到右逐 token 生成 | 对整个文本块并行去噪 |
| 起点 | 空序列 + 前文 | 一整块 <|mask|> 掩码占位符 |
| 并行度 | 低(逐 token) | 高(块内位置并行确定) |
| 长文本开销 | 随长度线性累积 | 128K 上下文窗口 |
简单说:Clover 先写下一整块"空格",然后像解谜一样,同时把有把握的几个位置填上,逐步让整个块"显影"。这正是"扩散"二字的由来——生成过程等价于从噪声(掩码)到清晰文本的去噪过程。
Clover-35B-A3B-Preview 速览:35B 参数、3B 激活 🚀
Clover 的骨架来自 Qwen3.6-35B-A3B,是一个纯文本 MoE(混合专家)大模型,关键规格如下(详见 config.json 与 README.md):
- 40 层解码器,混合 Gated DeltaNet(GDN)线性注意力与全注意力(每 4 层一组)
- 256 个路由专家,每个 token 只激活 top-8 + 1 个共享专家,激活参数约 3B
- 128K 上下文(
max_position_embeddings=131072) - 块扩散生成:
block_size=32,去噪算法为low_confidence,置信度阈值0.95
其中词表把 token 151669 专门重映射为 <|mask|>(见 tokenizer_config.json 与 ATTRIBUTIONS.md),它就是块扩散去噪时的"空格"占位符——模型为扩散生成量身定制了这个符号。
块扩散生成原理:每 32 个位置一个"填格子"游戏 🧩
生成时,Clover 并不逐字输出,而是每轮处理一个 32 token 的块(block_size 需整除 32),流程如下:
- 铺满掩码:新块全部初始化为
<|mask|>(mask_token_id=151669),此时前文(prompt + 已生成块)作为因果前缀保持不变; - 整块前向:模型一次性对整个块做前向推理,输出每个位置在 248320 词表上的 logits,同时把
<|mask|>自身的概率屏蔽为 -inf; - 置信度筛选:对每个掩码位置,用温度 T=1 的 softmax 概率作为置信度。超过 0.95 的位置并行填字——这就是"并行去噪",一步可确定多个 token;
- 保底推进:若本轮没有位置达标,就强制填入置信度最高的那个位置,保证生成不会卡死(实现见 modeling_clover_moe.py 的 denoising 主循环);
- 块落定:整块填满后,它转为"干净前缀",缓存状态用因果重放持久化,再开下一个块。
这套解码逻辑在 modeling_clover_moe.py 的 generate() 中实现,默认值定义在 config.json 的 denoising_config 中:
"denoising_config": {
"algorithm": "low_confidence",
"threshold": 0.95
}
💡 为什么快? 自回归模型写 32 个 token 必须跑 32 次;Clover 最理想情况下,高置信位置一轮就能并行锁定,去噪步数远少于块长。官方还为其 SGLang / llama.cpp 后端提供了 self-speculative decoding 进一步提升吞吐。
LowConfidence 解码的置信度怎么算?
置信度计算在 sampling_clover_moe.py 的 predict_with_confidence() 中:采样时可按 temperature/top_k/top_p 过滤,但置信度始终用 T=1 的原始 logits 的 softmax 值,保证"是否有把握"的判断不受采样扰动影响。
双向块注意力:让块内 token 互相"对话" 👀
普通自回归解码时,位置 5 不知道位置 10 是什么。但 Clover 的块内是双向注意力:块内任意两个掩码位置可以互相参考(attention_pattern 为 causal_prefix_bidirectional_block)。
这依赖两组额外参数(以独立文件存放,见 modeling_clover_moe.py):
- bd_gdn_conv_right_weight.safetensors:独立右卷积权重,让 GDN 线性注意力能"向右看"
- bd_gdn_softmax_scalars.safetensors:
logit_scale、history_bias、history_c三个标量,控制块内注意力和历史状态在 softmax 中的配比
整体模式是:前缀保持因果(不改动),块内双向并行(可互看),兼顾了自回归模型对已生成内容的稳定性与扩散模型块内并行的灵活性。
生成控制参数:防止"车轱辘话"的隐藏机关 ⚙️
扩散生成写得太长时容易陷入重复。Clover 在 generation_config.json 中内置了软性控制,开箱即用、贪心解码下同样生效:
| 参数 | 默认值 | 作用 |
|---|---|---|
presence_penalty |
0.9 |
降低已出现 token 的再次出现概率 |
repetition_penalty |
1.05 |
降低对已生成 token 的偏好 |
dllm_length_penalty |
10.0 |
超过 8192 token 后逐渐鼓励输出结束符 |
dllm_think_end_bias |
10.0 |
超过 4096 token 后逐渐鼓励关闭 ` |
| </output文章> |
(注意:上面表格中出现了未闭合的内容,我重新输出完整版本)
<输出文章>
什么是扩散语言模型?从零理解Clover-35B-A3B-Preview的块扩散生成原理
Clover-35B-A3B-Preview 是由 DiffuSpace 开源的首个扩散语言模型(Diffusion Language Model, DLM)。它在保留 35B 参数 MoE 大模型能力的同时,用"块扩散 + LowConfidence 解码"取代传统的逐字生成,带来块内并行去噪与 128K 长上下文能力。本文将从零讲透它的块扩散生成原理。
从"打字机"到"去噪":扩散语言模型是什么?🤔
我们熟悉的大语言模型是自回归(Autoregressive)的:像打字机一样一个词一个词地往后敲,每个新词只能参考前面已写好的内容,天然串行,速度受限于序列长度。
扩散语言模型借鉴了图像扩散模型"去噪"的思想:
| 对比项 | 自回归模型 | 扩散语言模型(Clover) |
|---|---|---|
| 生成方式 | 从左到右逐 token 生成 | 对整个文本块并行去噪 |
| 起点 | 空序列 + 前文 | 一整块 <|mask|> 掩码占位符 |
| 并行度 | 低(逐 token) | 高(块内位置并行确定) |
| 长文本支持 | 受 KV 缓存限制 | 128K 上下文窗口 |
简单说:Clover 先写下一整块"空格",然后像解谜一样,同时把有把握的位置填上,逐步让整个块"显影"。生成过程等价于从噪声(掩码)到清晰文本的去噪过程,这正是"扩散"二字的由来。
Clover-35B-A3B-Preview 速览:35B 参数、3B 激活 🚀
Clover 的骨架来自 Qwen3.6-35B-A3B,是一个纯文本 MoE(混合专家)大模型,关键规格如下(详见 config.json 与 README.md):
- 40 层解码器,混合 Gated DeltaNet(GDN)线性注意力与全注意力(每 4 层一组,
layer_types中 10 个 full_attention + 30 个 linear_attention) - 256 个路由专家,每个 token 只激活 top-8 + 1 个共享专家,每 token 激活参数约 3B
- 128K 上下文(
max_position_embeddings=131072) - 块扩散生成:
block_size=32,去噪算法low_confidence,置信度阈值0.95
特别地,词表把 token 151669 专门重映射为 <|mask|>(见 tokenizer_config.json 与 ATTRIBUTIONS.md)——它就是块扩散去噪时的"空格"占位符,模型为扩散生成量身定制了这个符号。
块扩散生成原理:每 32 个位置一个"填格子"游戏 🧩
生成时,Clover 并不逐字输出,而是每轮处理一个 32 token 的块(block_size 必须整除 32),流程如下:
- 铺满掩码:新块全部初始化为
<|mask|>(mask_token_id=151669);prompt 与已生成的块作为因果前缀保持不变; - 整块前向:模型一次性对整个块前向推理,输出每个位置在 248320 词表上的 logits,并把
<|mask|>自身概率屏蔽为 -inf; - 置信度筛选:对每个掩码位置,用 T=1 的 softmax 概率作为置信度。超过 0.95 的位置在同一轮并行填字——这就是"并行去噪",一步可锁定多个 token;
- 保底推进:若本轮没有位置达标,则强制填入置信度最高的那个位置,保证生成不会卡死(见 modeling_clover_moe.py 的 denoising 主循环);
- 块落定:整块填满后转为"干净前缀",缓存经因果重放持久化,再开启下一个块。
这套解码逻辑实现在 modeling_clover_moe.py 的 generate() 中,默认值定义在 config.json 的 denoising_config 字段:
"denoising_config": {
"algorithm": "low_confidence",
"threshold": 0.95
}
💡 为什么快? 自回归模型写 32 个 token 必须串行跑 32 次;Clover 在高置信位置上一轮就能并行锁定多个 token,平均去噪步数往往远小于块长,块与块之间又能充分并行调度。
LowConfidence 解码的置信度怎么算?
置信度计算位于 sampling_clover_moe.py 的 predict_with_confidence():采样时可按 temperature / top_k / top_p 过滤,但置信度始终取自 T=1 的原始 logits 的 softmax 值,保证"是否有把握"的判断不受采样扰动影响。
双向块注意力:让块内 token 互相"对话" 👀
普通自回归解码中,位置 5 永远不知道位置 10 是什么。Clover 的块内则采用双向注意力:块内任意两个掩码位置可以互相参考(attention_pattern 固定为 causal_prefix_bidirectional_block,见 configuration_clover_moe.py)。
这依赖两组额外参数,以独立小文件存放、在 modeling_clover_moe.py 中消费:
- bd_gdn_conv_right_weight.safetensors:独立的右卷积权重,让 GDN 线性注意力在块内也能"向右看"
- bd_gdn_softmax_scalars.safetensors:
logit_scale、history_bias、history_c三个标量,控制块内注意力与递归历史状态在 softmax 中的配比
整体设计是:前缀保持因果(不改动),块内双向并行(可互看)——既保住自回归模型对已生成内容的稳定性,又获得扩散模型块内并行的灵活性。
生成控制参数:防止"车轱辘话"的隐藏机关 ⚙️
扩散生成写得太长时容易陷入重复。Clover 在 generation_config.json 中内置了软性控制,默认即开启,贪心解码下同样生效:
| 参数 | 默认值 | 作用 |
|---|---|---|
presence_penalty |
0.9 |
降低已出现 token 的再次出现概率 |
repetition_penalty |
1.05 |
降低对已生成 token 的偏好 |
dllm_length_penalty |
10.0 |
从 8192 token 起逐渐鼓励输出结束符 |
dllm_think_end_bias |
10.0 |
从 4096 token 起逐渐鼓励关闭思考阶段 |
此外,去噪期间还可开启块级专家共核(moe_block_coreset_top_p=0.9):按聚合路由质量只让覆盖 90% 路由量的专家参与块内计算,进一步减少专家切换开销。这些"软控制"不改变硬上限,max_new_tokens(默认 24576)才是真正的刹车。
快速上手:几行代码跑通扩散推理 ▶️
官方推荐 Transformers 5.19.0 + PyTorch 2.10+,BF16 权重约需 69 GB 显存(完整示例见 README.md 的 Quick start):
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "Diffuspace/Clover-35B-A3B-Preview"
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_id, trust_remote_code=True,
dtype=torch.bfloat16, device_map="cuda:0",
).eval()
prompt = tokenizer.apply_chat_template(
[{"role": "user", "content": "What is diffusion?"}],
tokenize=False, add_generation_prompt=True, enable_thinking=True,
)
inputs = tokenizer(prompt, add_special_tokens=False, return_tensors="pt").to("cuda:0")
output = model.generate(
**inputs, max_new_tokens=24576,
block_size=32, threshold=0.95, do_sample=False,
)
其中 block_size 与 threshold 就是块扩散的两个核心旋钮:块长决定并行粒度,阈值决定"多确定才落笔"。生产部署与评测则建议走官方 SGLang 实现。
总结:一句话记住扩散语言模型 🌱
| 问题 | 答案 |
|---|---|
| 扩散语言模型怎么生成文本? | 对整块 <|mask|> 并行去噪,而非逐字自回归 |
| Clover 每块多大? | 默认 32 个 token,可传能整除 32 的 block_size |
| 何时落笔? | 位置置信度 > 0.95 即填;否则填最高置信位置 |
| 块内能看到未来吗? | 能。块内双向注意力 + 因果前缀 |
| 为什么快? | 块内多位置一次锁定 + 128K 上下文 + MoE 仅 3B 激活 |
Clover-35B-A3B-Preview 用"块扩散 + LowConfidence 解码 + 双向块注意力"三件套,展示了扩散范式在开源大模型上的完整落地:既保留 MoE 大模型的容量,又换来了并行的生成效率。想深入源码,建议从 modeling_clover_moe.py 的 generate() 与 sampling_clover_moe.py 的 predict_with_confidence() 读起——这两处正是"块扩散生成原理"的全部心跳。