Celeris-1 这个名称被许多人注意到时通常带着一组数字2,082 output tokens/s。简单说它是一款 diffusion 架构的 LLM对外给出的 benchmark 成绩是每秒可以输出 2082 个 token。对于这样一个把生成速度写进标题的模型有价值的讨论不是停留在“好快”这个结论上而是要先拆开三条线索diffusion LLM 和自回归 LLM 的生成逻辑到底差在哪里output tokens/s 这个指标是怎么算出来的如果你想在本地复现、验证或者拿它和别的模型做公平对比需要准备哪些条件。下面按这个顺序展开。1. diffusion LLM 为什么敢把“输出速度”写进标题1.1 自回归模型怎么生成文本绝大多数主流 LLM 采用自回归架构。生成一句话时模型一次只预测下一个 token然后把新 token 拼到已有序列后面再重复这个过程。伪代码是prompt The capital of France is for _ in range(max_new_tokens): next_token model(prompt) prompt prompt next_token这里的核心特征是串行依赖第 5 个 token 必须等前 4 个 token 产生后才能预测。即使底层 GPU 很强也很难让一个序列里不同位置的 token 并行生成。框架层面用 KV cache、continuous batching、投机解码等技巧来缓解但“逐 token 推进”这一基本约束没有变。串行依赖带来两个后果。第一生成 512 个 token 至少需要 512 次模型前向计算。第二输出越长延迟近似线性增长。这也是为什么自回归模型在“吞吐”上很难做出惊艳数字除非用大 batch 把多个请求塞进一次前向。1.2 扩散模型换了一条生成路线扩散语言模型diffusion LLM不是逐 token 预测而是先把文本映射到连续向量空间在训练阶段逐步向向量加噪声让模型学会从噪声恢复出原始向量生成阶段从一个随机噪声向量出发执行多步去噪最终得到一个完整序列的向量表示再通过解码器映射回离散 token。关键区别在于去噪过程可以同时优化序列中所有位置而不是从左到右一个个推。因此 diffusion LLM 天然具备“整段并行生成”的潜力一次前向就能对所有 token 的位置同时修正。这是它能挑战自回归推理速度的根本原因。需要说明的是diffusion LLM 并不是没有代价。文本本身是离散符号长度不固定语义又强依赖于顺序把离散 token 变成连续空间再变回来会出现信息损失去噪步数太少生成质量会明显下降步数太多单次生成又要执行很多次网络推理速度优势反而会被抵消。所以衡量这类模型时不能用“理论单步快”代替“实际生成长度下的端到端时间”。1.3 diffusion 与 flow matching 的关系很多人先接触的是 stable diffusion 这类图像生成模型再用同一套心智模型去看 diffusion LLM从而会看到 flow matching 这个词。可以这样理解传统 score-based diffusion 学习一个把噪声分布逐步变换到数据分布的随机微分方程采样时通常需要几十步。flow matching 学习一个速度场目标是让样本从先验分布沿近似直线轨迹移动到数据分布因此采样步数可以更少有些场景可以压到 4 到 16 步。所以很多新的扩散语言模型会在“生成步数”上做文章。步数少单条样本生成速度就快。标题里的 2082 tokens/s 如果来自一个低步数去噪模型也是常见路线。看到这个数字时第一反应不应该是“模型好强”而是“这是在多少去噪步数、多少 batch、多少精确度下测出来的”。1.4 Celeris-1 在标题里的定位Celeris 来自拉丁语有“快速”的含义Celeris-1 这个命名本身就暗示它是一款以生成效率为卖点的模型。标题只点明了两件事架构是 diffusion LLM成绩是 2082 output tokens/s。它没有给出完整的 benchmark 条件没有给出生成质量得分也没有给出硬件型号。因此下面的内容会聚焦一个更通用的工程问题当别人给你一个“XX 模型达到了 XXX tokens/s”的结论时你该怎样判断它是否可信、是否可复现、是否适合你的场景。2. 解读 2,082 output tokens/s 前先把指标口径拆开2.1 tokens/s 的三种常见口径同样是“每秒 token 数”不同场景含义完全不同。最常见的三种口径如下。口径计算方式通常出现在示例含义单请求生成吞吐单个请求产生的输出 token 数除以该请求生成耗时本地跑模型时手动统计用户等待 1000 token 输出大约 0.48 秒聚合吞吐一个 batch 或一个服务进程内所有请求的总输出 token 数除以总耗时vLLM、TGI 等推理框架服务器每秒向所有用户输出 2082 个 token纯解码吞吐只统计模型前向解码阶段不统计 prefill、采样、tokenizer芯片厂商或框架微基准模型计算单元的理论速度第三类最容易造成误读。很多公开数字会把“prefill 阶段处理输入的时间”排除只算生成 token 的纯解码时间。这对自回归模型是测量标准做法之一但对扩散模型需要更小心因为它的“prefill”不是一次输入编码而是一整段序列的初始噪声和条件编码被算进生成时间里还是被排除在外会显著影响结果。2.2 2082 tokens/s 能告诉我们什么不能告诉我们什么如果把 2082 tokens/s 理解为“系统持续输出 token 的聚合速率”它告诉你在测试条件下这套模型和硬件每秒可以完成 2082 个 token 的生成。如果输出长度为 1000 token那么单个请求的理想生成时间约为1000 / 2082 ≈ 0.48 秒这个计算很诱人但它只有在“单请求独占全部吞吐”时才成立。真实推理服务通常同时处理多个请求2082 tokens/s 可能是 32 个请求共享的结果。共享时单个请求感受到的时间会明显高于 0.48 秒。它不能告诉你的东西包括输入 prompt 有多长prefill 花掉多少时间去噪步数是多少步数增加后速度降到多少生成质量如何是否适合代码、数学、对话等场景是用什么 GPU 测出来的换成消费级显卡会变成多少是贪婪解码还是带采样采样对结果影响有多大。所以看到 2082 这个数字正确的第一动作是寻找测试条件而不是直接相信“比自回归模型快”。2.3 吞吐高和延迟低不是一回事在线服务里用户真正感知到的是延迟而不是系统吞吐。一个服务器可以把 100 个请求聚合在一起令总吞吐很高但每个请求都必须等待 batch 中其他请求处理得差不多才能返回。吞吐高只代表服务器能压榨硬件不代表每个用户都能秒回。扩散 LLM 还有一个特殊性质它不是逐步输出 token而是先产生一个完整序列的连续表示再整体解码成文本。因此它通常不具备“首 token 快速返回”的流式体验。自回归模型可以第一个 token 很快出来然后像打字机一样持续输出diffusion 模型更接近“等一整段生成好了再整体给用户”。在对话产品里这个体验差异对用户耐心影响很大。2.4 对比其他模型时的速查表对比维度自回归 LLMDiffusion LLM生成方式逐 token 自回归整段向量并行去噪输出延迟曲线随长度近似线性增长主要由去噪步数决定流式输出通常支持通常不支持或需要额外设计聚合吞吐潜力依赖 batch、KV cache 优化去噪步数少时有优势质量稳定性成熟生态完善仍在快速发展需逐任务验证benchmark 复杂点prefill 和 decode 分开统计去噪步数、映射/解码策略影响巨大这张表是通用对比不代表 Celeris-1 一定满足其中所有特性具体模型要对齐实现细节后再下结论。3. 自己动手搭一个 diffusion LLM 吞吐基准脚本3.1 基准测试的最小目标先保证“可复现”复现一个 2082 tokens/s 的数字可能涉及大量环境条件。你不需要第一步就完全复现官方成绩但至少要搭一个能回答下面问题的脚本在当前 GPU 上这个模型实际每秒输出多少 token输出长度设成 256、512、1024 时速度如何变化batch_size 和去噪步数改变后吞吐差多少相同配置下跑 10 次结果是否稳定只要结果能稳定复现这个脚本就已经有工程价值。3.2 环境准备和依赖如果目的是验证一个 Hugging Face 风格的模型常用环境如下。注意不同 diffusion LLM 的加载接口可能差异很大下面是一份通用模板落地前要确认目标模型自己的调用方式。项目建议说明GPUNVIDIA 显卡显存建议 16GB 以上8GB 只能跑极小模型或极短序列驱动较新的 NVIDIA 驱动影响 CUDA 可用能力CUDA11.8 或 12.x与 PyTorch 版本匹配Python3.10 或 3.11较新版本兼容性更好PyTorch2.x支持 torch.compile 和自动混合精度Transformers4.x 最新稳定版不一定所有 diffusion LM 都走该接口推理框架vLLM / TensorRT-LLM 视需要只有模型支持时才可用安装示例pip install torch --index-url https://download.pytorch.org/whl/cu121 pip install transformers accelerate sentencepiece安装后先确认 PyTorch 能看到 GPUimport torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果torch.cuda.is_available()返回 False不要继续跑 benchmark先解决驱动和 CUDA 版本匹配问题。3.3 一个最小 benchmark 脚本结构下面脚本用于自回归模型的吞吐测试可以扩展到支持generate接口的模型。如果你的 diffusion LLM 使用自定义 pipeline只要替换run_once内部调用即可。import time import statistics import argparse import torch from transformers import AutoModelForCausalLM, AutoTokenizer def count_tokens(text, tokenizer): return len(tokenizer.encode(text, add_special_tokensFalse)) def run_once(model, tokenizer, prompt, max_new_tokens, device): inputs tokenizer(prompt, return_tensorspt).to(device) start time.perf_counter() with torch.no_grad(): outputs model.generate( **inputs, max_new_tokensmax_new_tokens, do_sampleFalse, ) elapsed time.perf_counter() - start input_len inputs[input_ids].shape[1] completion_ids outputs[0][input_len:] completion_text tokenizer.decode(completion_ids, skip_special_tokensTrue) new_tokens count_tokens(completion_text, tokenizer) return new_tokens, elapsed def main(): parser argparse.ArgumentParser() parser.add_argument(--model, defaultmodel-name) parser.add_argument(--prompt, defaultHello, explain what is a database.) parser.add_argument(--max-new-tokens, typeint, default512) parser.add_argument(--runs, typeint, default10) args parser.parse_args() device cuda if torch.cuda.is_available() else cpu tokenizer AutoTokenizer.from_pretrained(args.model) model AutoModelForCausalLM.from_pretrained(args.model, torch_dtypetorch.float16).to(device) model.eval() # warmup排除显存分配、CUDA kernel 加载等因素 run_once(model, tokenizer, args.prompt, args.max_new_tokens, device) token_counts [] elapsed_list [] for _ in range(args.runs): new_tokens, elapsed run_once( model, tokenizer, args.prompt, args.max_new_tokens, device ) token_counts.append(new_tokens) elapsed_list.append(elapsed) # 每次运行实际生成的 token 数可能略有差异统一按实际输出数计算 tps per_run_tps [ t / e for t, e in zip(token_counts, elapsed_list) ] print(fmodel: {args.model}) print(fmax_new_tokens: {args.max_new_tokens}) print(fruns: {args.runs}) print(favg tokens/run: {statistics.mean(token_counts):.1f}) print(favg elapsed: {statistics.mean(elapsed_list):.4f}s) print(fmedian tps: {statistics.median(per_run_tps):.2f}) print(fp95 tps: {sorted(per_run_tps)[int(len(per_run_tps) * 0.95) - 1]:.2f}) if __name__ __main__: main()运行python benchmark_llm.py --model meta-llama/Llama-3.2-1B-Instruct --max-new-tokens 512 --runs 10脚本会把 10 次结果的中位数作为主要参考。中位数比平均值更抗偶发波动。3.4 关键指标计算逻辑上面的脚本统计的是“单请求端到端吞吐”tokens_per_second 实际新生成的 token 数 / 从模型 generate 开始到结束的耗时这个口径包含了 prefill 和 decode也包含了 tokenizer 解码时间。对于衡量用户体感延迟它是可靠的对于衡量模型计算速度它偏保守。官方 benchmark 如果写 2082 tokens/s很可能是另一个口径所以本地数字小于官方数字不一定是错误先对齐统计口径。如果你的模型是 diffusion LLM还要额外记录去噪步数。例如# 伪代码表示如何把去噪步数纳入输出 for step in range(num_inference_steps): noise_pred denoising_network(latent, step) latent update_latent(latent, noise_pred, step)去噪步数num_inference_steps在最后的结果表里必须出现否则 2082 tokens/s 无法被解读。3.5 输出记录模板建议 benchmark 脚本自动输出下面的字段方便后续对比字段示例值model_nameCeleris-1 或其他模型名model_versionv1 / checkpoint 编号gpu_nameNVIDIA A100 80GBcuda_version12.1torch_version2.4.0dtypefloat16 / bfloat16batch_size1 / 8 / 32prompt_length128 tokensmax_new_tokens512num_inference_steps8diffusion 模型median_tps1450.3p95_tps1368.2avg_gpu_memory28640 MB没有这些字段任何人拿到你的 2082 也无法复现。4. 哪些参数会改变 tokens/s配置时不能拍脑袋4.1 batch_size聚合吞吐和单请求延迟的矛盾batch_size 是影响聚合吞吐最直接的因素。GPU 的并行能力需要足够多的计算单位才能吃满一个请求往往无法填满 GPU。把多个请求拼成一个 batch 可以提升总吞吐代价是单个请求的等待时间变长。实际压测时不能只测 batch_size1 就宣称模型能达到多少 tokens/s也不要只测 batch_size32 就让用户以为所有请求都这么快。正确做法是扫描一组 batch 大小记录聚合吞吐和平均单请求延迟两条曲线。4.2 输入长度和输出长度输入长度影响 prefill 阶段耗时输出长度影响生成阶段总耗时。基准测试如果不固定长度得出的 tps 会失去意义。比如输出 32 个 token 时prefill 占比大tps 偏低输出 2048 个 token 时生成占比大tps 更接近纯吞吐。对比两个模型的 tps 时必须在相同的输入长度和输出长度范围内进行。4.3 精度FP16、BF16、FP32精度显存占用常见吞吐表现需要注意FP32高低主要用于调试不适合压测FP16中等通常明显高于 FP32可能溢出需要套用稳定训练技巧BF16中等与 FP16 接近数值范围更大推荐在较新 GPU 上优先尝试INT8 / INT4 量化低可能更高但不一定线性提升需要看模型实现和算子支持不要在比较结果时混用精度。一个 FP16 模型的 2082 tokens/s 和一个 INT8 量化模型的 2000 tokens/s 并不等价前者代表“原模型在正常精度下的表现”后者代表“压缩后的表现”。4.4 去噪步数diffusion LLM 特有的权衡对 diffusion LLM 来说去噪步数是决定速度的核心参数。去噪步数从 32 降到 8生成耗时可能变成原来的四分之一但质量会下降。不同模型对步数的敏感度不同有些模型 4 步就能有不错效果有些模型少于 16 步会明显退化。因此看 benchmark 时必须同时看质量和步数。如果为了刷高 tokens/s 把步数压得很低但生成结果已经不可接受这个数字对实际产品没有意义。4.5 编译和推理框架的加速效果PyTorch 2.x 的torch.compile、TensorRT-LLM、vLLM 都能在部分模型上带来明显加速。但这意味着 benchmark 结果不仅反映模型本身也反映推理框架。同一模型在原生 PyTorch 下可能是 800 tokens/s在优化过的框架下可能是 1600 tokens/s。对比时必须记录推理框架和优化选项。比如model torch.compile(model, modereduce-overhead)开启编译后首次运行会非常慢因为要触发 kernel 融合和代码生成必须预热后再统计。参数速查表如下参数对速度的影响方式对质量的影响benchmark 建议batch_size提升聚合吞吐一般无直接影响分别测试 1、8、32max_new_tokens改变生成阶段占比长文质量需额外评估固定 512 或 1024dtype影响显存带宽和计算速度低精度可能损失数值精度记录并统一num_inference_steps生成耗时近似线性变化步数越低越容易劣化测试 4、8、16、32torch.compile缩短单次前向耗时一般无影响同一模型记录是否开启采样/贪婪改变解码路径和计算量直接影响结果分布固定同一策略5. 跑 benchmark 常见的坑和排查顺序5.1 现象生成 token 数统计得虚高有些脚本直接统计outputs.shape[1] - input_ids.shape[1]然后用这个数字除以耗时。这对于单 token 一个 id 的模型是准确的但如果模型使用了特殊 token、重复前缀或 tokenizer 合并逻辑最终解码出来的文本长度可能比 id 数短也可能更长。统计口径不一致会让对比失真。推荐做法是统一把输出序列 decode 成文本再用同一个 tokenizer 重新编码统计实际有效 token 数。同时也要注意 tokenizer 有没有add_special_tokensTrue不同设置会影响统计结果。5.2 现象同样脚本两次结果差异很大常见原因包括没有 warmup第一次运行包含了 CUDA 初始化和显存分配GPU 被其他进程占用没有固定随机种子采样导致每次输出长度不同机器散热触发降频使用了共享 GPU其他用户影响性能。建议至少跑 10 次取中位数并观察结果分布。如果抖动超过 10%要先排查环境噪音。5.3 现象显存 OOM 或首次运行特别慢OOM 通常发生在 batch_size 过大或输入过长。解决顺序是先降低 batch_size再降低max_length再看是否需要开启梯度检查点或切换到更低精度。首次运行特别慢不一定代表实际性能。PyTorch 首次跑某个 shape 的算子时会触发初始化torch.compile也会做 kernel 编译。正确的做法是把第一次运行当作预热不纳入统计。5.4 现象diffusion 模型结果不可复现扩散模型生成过程带有随机性即使固定torch.manual_seed不同 CUDA 版本、PyTorch 版本、同一个 kernel 的不同实现也可能产生不同结果。不要简单把“两次生成文本不同”判定为 bug先确认随机种子和采样参数是否一致。如果目标是测速度可以固定num_inference_steps、固定输出长度策略、固定 seed并把生成结果是否一致作为 sanity check。5.5 排错顺序表问题现象常见原因检查方式处理建议tps 数字异常高统计窗口太短未计入 prefill检查计时起点从模型调用开始计时覆盖完整推理tps 数字异常低未开启任何加速dtype 为 FP32查看日志中的精度切换为 BF16/FP16考虑编译优化两个模型对比不公平输入长度、输出长度、batch 不同核对运行记录统一 prompt 长度和生成长度每次都出不同结果随机采样、GPU 波动固定 seed 多次运行取中位数报告分位数OOMbatch 或序列过长查看显存峰值降低 batch 和长度或换更大显存官方 2082 本地跑不出来硬件、框架、步数、batch 不同对照官方配置先复现官方条件再尝试优化6. 怎么把 benchmark 结果用于模型选型和后续优化6.1 先分清“单请求体验”和“系统吞吐”如果你的场景是客服、聊天、RAG 问答用户单次请求只输出几百 token这时单请求延迟比聚合吞吐更重要。你需要关注“从用户发请求到收到完整响应”的端到端时间而不是服务端每秒输出多少 token。如果场景是离线批处理、批量生成文章、数据标注几万个请求同时跑这时聚合吞吐更重要因为它直接决定总机时和成本。6.2 把生成质量与速度拆开评估速度再高质量不达标也没有用。建议在同一批测试集上同时记录质量指标和速度指标。质量指标至少要看任务完成率回答是否符合指令文本连贯性是否有重复、乱码、逻辑断裂领域准确率代码、数学、医疗、法律等专业任务长文本稳定性生成 1024 到 4096 token 时是否仍然可读。不能只对比 tokens/s至少要形成“同一质量水平下谁更快”的结论。6.3 什么时候优先考虑 diffusion LLM当满足以下条件时diffusion LLM 的高吞吐优势会真正体现输出长度较长自回归模型需要几十次前向累积时间较长可以接受“整体生成不流式返回”的交互方式对生成质量要求可以在低去噪步数下满足有足够推理框架支持不需要从零造轮子硬件和 batch 规模能发挥并行优势。典型候选场景包括批量内容生成、离线摘要、结构化数据转换、代码补全批处理等。6.4 什么时候仍然要选自回归模型如果产品需要高交互性、逐 token 流式体验或者对生成质量非常敏感自回归模型仍是稳妥选择。因为它的生态最成熟推理框架最多调试资料最丰富。不要因为一个 benchmark 数字就推翻已经稳定的技术路线。6.5 发布 benchmark 时的记录清单如果你自己发布模型的跑分或者在企业内部对比模型建议至少记录下面清单记录项是否必须说明GPU 型号和数量必须决定结论适用范围CUDA、PyTorch、推理框架版本必须影响复现模型版本和权重来源必须避免不同 checkpoint 混淆输入 prompt 长度必须prefill 时长受此影响输出长度或 max_new_tokens必须影响生成阶段batch_size必须聚合吞吐与单请求延迟都要记录去噪步数diffusion 模型必须对质量影响大精度必须FP16/BF16/INT8 结果不可直接比是否启用编译/量化必须优化方式不同结果差异大运行次数和统计口径建议中位数、p95 比单次结果可靠GPU 实际占用/温度建议帮助判断异常波动7. 学习扩散语言模型下一步可以走哪条路7.1 从代码层面理解去噪循环想真正理解 diffusion LLM建议先不看复杂工程而是自己实现一个极小的连续空间生成示例构造一个简单数据分布添加高斯噪声训练一个 MLP 预测噪声再用去噪循环采样。跑通后再迁移到文本场景你会更容易理解为什么去噪步数、噪声调度、速度场会对生成时间和质量产生那么大影响。可以参考的思路是把num_inference_steps和flow matching对照起来看一个强调步数压缩一个强调轨迹更直两者目标都是“用更少迭代得到可接受结果”。7.2 关注离散化与解码器设计文本是离散的而扩散过程通常在连续空间进行因此“连续向量如何变成离散 token”是 diffusion LLM 的核心难点。可以重点关注两个方向模型输出的连续表示如何被映射到词表是否会出现词表中没有的“中间状态”输出长度如何确定是预设固定长度、动态停止还是先用自回归模型定长度再生成内容。这些问题直接影响最终质量和实际吞吐。只看标题里的 tokens/s会错过这些更关键的设计点。7.3 适合动手的小练习建议按下面顺序做几个小任务用现有的自回归 LLM 跑通上面的 benchmark 脚本记录 baseline找一个公开的 diffusion 语言模型或示例仓库按官方 README 跑通生成在固定 prompt、固定输出长度下改变去噪步数观察 tps 和质量变化用表格整理“步数、耗时、质量主观评分”三者关系再回到 Celeris-1 的 2082 tokens/s尝试还原它的 benchmark 条件。完成这些练习后你看待“XX 模型达到 XX tokens/s”这句话时会更冷静。一个数字只有在指标口径、硬件、框架、生成参数全部对齐的情况下才具备跨模型比较的价值。对 diffusion LLM 来说去噪步数和 batch_size 是无论如何都要写清楚的两个字段缺失任何一个吞吐成绩都只能作为参考不能作为选型依据。