ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

vLLM|约束解码 + 高性能吞吐,从原理到实战

vLLM|约束解码 + 高性能吞吐,从原理到实战 第一章底层基石 —— 主流大模型分词器详解1.1 为什么分词器对LLM推理很重要模型只能处理数字文本必须先经过 Tokenizer 转化为 Token ID 序列。原始文本 “Hello, world!” → Tokenizer 切分 编码 → Token IDs [15496, 995, 0] → Embedding 向量查表 → 语言模型 Transformer有三种切分粒度字符级、词级、子词级。现在的llm都用的子词级字符级词表极小但是序列极长、并且字符本身没有语义词级序列最短但是词表巨大并且新词是UNK。理想的Tokenizer要满足三个目标零 UNK 覆盖率不要出现[UNK]高压缩率 token尽量少多语言公平性避免中文几个字就消耗大量token而英文同样语义只消耗很少token。分词器直接决定 token 序列长短token 越多上下文窗口消耗越大KV‑Cache 占用的显存就越高出现[UNK]会丢失原始文本信息而后面 vLLM 的约束解码能力更是完全运行在 token 粒度之上。理解分词算法是看懂 LLM 推理底层逻辑的第一步。1.2 BPE 原始算法BPEByte‑Pair Encoding字节对编码最早并不是为大模型发明的它是一种无损数据压缩算法后来被引入NLP领域做子词分词。核心思想自底向上贪心合并先把训练集全部文本按空格切分成独立单词再把每个单词拆解成最基础的字符序列。统计所有相邻字符对的出现频次。选出出现频率最高的字符对合并成一个新子词单元加入词表。重复上面统计‑合并的过程直到词表达到预设大小。⚠️关键前置约束原始BPE必须依赖空格预先切分单词单词边界是提前确定好的算法本身不会处理句子。优势实现简单训练、编码解码速度快高频片段会被合并为完整token取得不错的压缩效果子词由训练数据统计得来贴合真实文本分布。劣势强依赖预分词需要依靠空格切分单词对中文、日文这类没有天然空格分隔的语言处理很麻烦。存在OOV/UNK问题遇到训练中从未见过的生僻字符、特殊Unicode符号比如这个表情如果词表里没有对应单元就只能输出未知token[UNK]丢失信息。单词边界被固化切分行为高度依赖预分词结果预分词出错后续BPE合并结果也跟着出错。早期部分开源模型使用原始BPE现在生成式大模型基本不再直接使用它更多是作为BBPE的理论原型。1.3 BBPEByte‑Level BPE字节级BPEBPE的缺陷Unicode 覆盖不完全。边缘字符仍出现UNK词表大小爆炸。多语言模型要覆盖所有的字符难以管理新 emoji 在部署后会出现UNKUnicode字符不管是中文、日文、emoji还是生僻符号最终都能编码成1~4个UTF‑8字节。而字节总共只有256种可能全部在初始词表里。BBPE就是在BPE的基础上把初始词表换成了256字节值。实现了零UNK。比如说同样语义的问候语英文hello占 5 个 UTF‑8 字节中文你好占 6 个 UTF‑8 字节阿拉伯语问候则要 10 个 UTF‑8 字节BBPE 会直接在这些原始字节之上执行 BPE 合并操作。1.4 WordPieceBPE、BBPE都是按共现频次选择子词对进行合并而WordPiece走了另一条路线它不再看单纯出现次数而是以最大化训练集语言模型似然概率作为合并判断标准由Google提出最知名使用者就是BERT系列编码器模型。WordPiece使用打分公式score(A,B)freq(AB)freq(A)×freq(B)score(A,B)\frac{freq(AB)}{freq(A) \times freq(B)}score(A,B)freq(A)×freq(B)freq(AB)​分子越大AB 常见分母越小A、B 单独罕见说明合并这对更能减少不确定性。1.5 SentencePieceBPE / WordPiece 需要先按空格/标点预切分文本预分词步骤这一步依赖语言规则中文无空格。SentencePiece的做法是把空格替换为特殊字符 ▁U2581文本变成纯字符流。算法直接在原始 Unicode 上运行不需要任何语言特定预处理。比如输入“我 like 大模型”子词切分结果示例[我, ▁like, ▁大, 模型]1.6 UnigramBPE 从小词表出发不断合并自下而上Unigram 从大词表出发不断剪枝自上而下核心思想是优先删掉那些删掉之后整体效果几乎没怎么变坏的子词。1.7 分词算法横向对比小结bbpe分词方法现在是主流。对比维度BPE(Sennrich原版)BBPE(字节级BPE)WordPieceUnigram初始词表字符256字节字符大词表剪枝合并策略频次最大频次最大似然最大EM 剪枝UNK风险极低存在UNK零UNK极低极低存在UNK分词确定性确定确定确定可概率化多语言友好中高中高第二章KV Cache——vLLM高吞吐背后的底层根基2.1 推理性能的关键指标指标全称含义参考标准/说明TTFTTime to First Token从发送请求到收到第一个token的时间 500ms 为优秀影响用户体验感知TPOTTime Per Output TokenDecode阶段每生成一个token的平均耗时 50ms 流畅体验Throughput系统吞吐量单位时间内处理的总token数所有并发请求之和高并发部署核心指标单位token/sMFUModel FLOPs UtilizationGPU实际算力利用率 vs 峰值算力Decode通常 20%Prefill可达40%核心矛盾低延迟 / 高吞吐 / 低显存三者难以同时最优 —— 推理优化的本质是在三角约束中找最佳平衡点。2.2 LLM自回归推理两个阶段Prefill 和 Decode推理的流程是这样Prefill 阶段处理 prompt输入整段 promptn 个 token一次性进入模型计算对所有 n 个位置并行做 Attention输出下一个 token 的概率分布特点算力密集compute-boundDecode 阶段逐 token 生成输入上一步生成的 1 个 token计算对当前位置做 Attention看前面所有 token输出下一个 token循环到 EOS特点访存密集memory-bound2.3 朴素推理的致命冗余每一步重复计算K/V朴素推理过程把已有的 t 个 token 重新输入模型每一层都重新计算所有 t 个位置的 Q、K、V做完整 Attention取最后一个位置的输出代价前 t-1 个位置的 K、V 在上一步早就算过这一步又算了一遍 → 纯粹浪费2.4 KV Cache引入的问题问题类型具体表现产生原因影响显存压力巨大KV Cache占用随序列长度线性增长长上下文/高并发场景下显存占用甚至超过模型权重每个请求都需要存储全部历史token的K、V向量显存成为推理瓶颈限制并发数和上下文长度内部碎片按max_seq_len预分配连续显存但实际生成长度远小于最大值大量空间闲置传统实现为每个请求预留最大长度的连续内存块显存利用率低实际情况可能浪费一半的显存调度低效静态Batch一批请求必须全部生成结束才统一释放KV缓存短请求完成后原地等待长请求传统静态批处理同进同出的调度机制GPU算力大量空转闲置吞吐量上不去2.5 vLLM如何改造KV CachePagedAttention 分页注意力复刻OS虚拟内存分页机制不再连续分配一整块的显存。先把显存按size划分在需要用到显存的时候按需分配显存块。2.6 Continuous Batching 连续批处理一个token生成后立刻开始运行新的请求2.7 本章小结KV Cache解决自回归重复计算问题是LLM推理基础但原生连续分配静态批处理带来严重碎片、低利用率、调度低效PagedAttention借鉴OS虚拟内存分页解决显存碎片、利用率、前缀缓存复用拉高并发上限Continuous Batching借鉴OS抢占式调度解决GPU算力空转拉高整体吞吐量一句话分工PagedAttention负责省显存让更多请求装下Continuous Batching负责吃满GPU让请求跑得更快第三章vLLM实战——本地项目复现3.1 环境搭建我在autodl云平台租的5090显卡做的实验。用到社区镜像3.2 吞吐量对比实验bench_throughput.py[A] transformers 串行逐条循环处理请求每次只向模型输入 1 条 prompt这条完整生成完毕之后才处理下一条。GPU 同一时刻只在计算 1 个请求大部分算力处于空闲状态。KV 缓存处理完一条就完整释放没有批处理加速。[B] transformers 静态 batch24把 50 条请求按每 24 条切分成若干个固定分组一组一组送入model.generate()。同一组最多 24 条同时推理组内请求做 padding 补齐长度。必须等待本组内全部 24 条请求全部生成结束才释放显存、再执行下一组。只要组内存在生成较慢的长请求其余已经完成的请求会占用位置和 KV 缓存GPU 算力空等产生尾部算力空洞。batch 开到 24已经充分利用 32GB 显存相比串行有巨大提升但依然受静态批处理机制限制。[C] vLLM 批处理Continuous Batching PagedAttention把全部 50 条请求一次性提交给 vLLM。连续批处理Continuous Batching不以整组请求为调度单位以单步 token 迭代做调度。某条请求生成完成立刻将它剔除马上补入排队的新请求不需要等待一整批全部跑完。PagedAttention 分页 KV 缓存KV 缓存拆成独立小块 block 管理请求结束立刻回收 block消除内存碎片显存利用率大幅提升。内部动态调整同时活跃的请求数量充分打满 GPU 算力。结果对比模式总耗时QPS请求/秒tokens/s相对 vLLM[A] transformers 串行43.10s1.16870.02×[B] transformers batch244.76s10.517750.18×[C] vLLM 批处理0.84s59.1941341.00×3.3 约束解码实战四大模式逐个拆解3.3.1 guided_choice 枚举约束每步解码时屏蔽非法 token 的 logits输出了“查”之后就只能输出“股价”、“财报”、“新闻”了。INTENT_CHOICES[查股价,查财报,查新闻,对比分析,其他]defrun_with_guided_choice(user_msg:str)-tuple[str,float]:guided_choice 约束底层 FSM 屏蔽非法 tokent0time.time()respclient.chat.completions.create(modelMODEL,messages[{role:system,content:SYSTEM_PROMPT},{role:user,content:user_msg},],temperature0,max_tokens10,extra_body{guided_choice:INTENT_CHOICES},# ★ 关键参数)returnresp.choices[0].message.content.strip(),time.time()-t0运行结果指标 裸 prompt guided_choice ---------------------------------------------------------------------- 输出合法在枚举内 248/257 (96%) 257/257 (100%) 预测正确 55/257 (21%) 57/257 (22%) 平均延迟秒 0.041 0.0423.3.2 guided_regex 正则约束DATE_REGEXr\d{4}-\d{2}-\d{2}defrun_generate(system:str,user:str,regex:str|NoneNone)-tuple[str,float]:t0time.time()extra{guided_regex:regex}ifregexelse{}respclient.chat.completions.create(modelMODEL,messages[{role:system,content:system},{role:user,content:user},],temperature0,max_tokens30,extra_bodyextra,)returnresp.choices[0].message.content.strip(),time.time()-t0运行结果 输入 裸 prompt guided_regex --------------------------------------------------------------------------- 2024年5月12日 ✓ 2024-05-12 ✓ 2024-05-12 2023/12/1 下午开会 ✗ 2023-12-01 14:00:00 ✓ 2023-12-01 三月三号我去北京 ✓ 2017-03-03 ✓ 2017-03-03 2024.11.30 是截止日期 ✓ 2024-11-30 ✓ 2024-11-30 明天假设今天是2026-05-11 ✓ 2027-04-12 ✓ 2027-04-12 2024 年 10 月的第一天 ✓ 2024-10-01 ✓ 2024-10-01 --------------------------------------------------------------------------- 格式合法率裸 prompt 5/6 (83%) | guided_regex 6/6 (100%)3.3.3 response_format JSON 约束保证输出json格式defrun(user:str,mode:str)-tuple[str,float]:mode: raw | json_objectkwargs{}ifmodejson_object:kwargs[response_format]{type:json_object}t0time.time()respclient.chat.completions.create(modelMODEL,messages[{role:system,content:SYSTEM_PROMPT},{role:user,content:user},],temperature0,max_tokens150,**kwargs,)returnresp.choices[0].message.content.strip(),time.time()-t0运行结果 200 条测试结果 指标 裸 prompt response_format ------------------------------------------------------------ 合法 JSON 200/200 (100%) 200/200 (100%) 有 sentiment 字段 200/200 (100%) 200/200 (100%) sentiment 值合法 200/200 (100%) 200/200 (100%) 有 confidence 字段 200/200 (100%) 200/200 (100%) 有 keywords 字段 200/200 (100%) 200/200 (100%) 裸 prompt也是100%但是依靠模型自觉3.3.3 guided_json JSON Schema约束比json约束更定制化不光保证json格式还能约束取值范围等内容INTENT_SCHEMA{type:object,properties:{company:{type:string,description:公司全称如 招商银行、贵州茅台,},year:{type:integer,minimum:2015,maximum:2025,},metric:{type:string,enum:[营收,净利润,ROE,毛利率,总资产,经营现金流],},},required:[company,year,metric],additionalProperties:False,}defrun_generate(user_msg:str,mode:str)-tuple[str,float]:mode: raw | guided_json | response_formatextra{}kwargs{}ifmodeguided_json:extra{guided_json:INTENT_SCHEMA}elifmoderesponse_format:kwargs{response_format:{type:json_object}}t0time.time()respclient.chat.completions.create(modelMODEL,messages[{role:system,content:SYSTEM_PROMPT},{role:user,content:user_msg},],temperature0,max_tokens120,extra_bodyextra,**kwargs,)returnresp.choices[0].message.content.strip(),time.time()-t0运行结果指标 裸 prompt response_format guided_json ------------------------------------------------------------------------------ 合法 JSON 9/9 (100%) 9/9 (100%) 9/9 (100%) 字段齐全 9/9 (100%) 9/9 (100%) 9/9 (100%) year 在 2015~2025 8/9 (89%) 8/9 (89%) 9/9 (100%) metric 在枚举内 9/9 (100%) 9/9 (100%) 9/9 (100%) jsonschema 完全通过 8/9 (89%) 8/9 (89%) 9/9 (100%) 结论 response_format 只保证是 JSON不保证字段名、类型、枚举正确 guided_json 是唯一 100% 保证 schema 合法的方式 vllm在function call的例子里很有用可以做到约束模型给出合规的json输出3.4 约束解码原理简述FSM有限状态机在每一步token采样时用FSM过滤掉不符合规则的候选token从生成源头杜绝非法输出首次构建Schema有FSM初始化开销缓存后速度恢复正常第四章总结本文从分词器底层讲起深入KV Cache与vLLM两大核心优化技术并通过本地实验复现验证了vLLM的两大工程价值第一极致的推理吞吐性能。PagedAttention借鉴操作系统虚拟内存分页思想将KV Cache切分为离散的内存块按需分配、即时回收大幅缓解传统连续内存分配带来的显存碎片问题Continuous Batching则以单步token迭代为调度单位完成的请求立刻退出、新请求随时补入避免静态批处理的算力空转。本地实测数据显示在Qwen2-0.5B模型、50条请求的场景下vLLM的吞吐速度达到原生transformers串行的47倍是transformers静态batch24的5.3倍充分体现了两项技术协同带来的性能收益。第二可靠的约束解码能力。vLLM支持枚举、正则、JSON、JSON Schema等多种约束模式通过FSM有限状态机在token生成的每一步屏蔽非法候选从源头保证输出格式合规。实验表明裸prompt和OpenAI标准的response_format虽然能保证JSON语法合法但在字段取值范围、枚举约束等细节上仍有89%左右的通过率而guided_json可以做到100%符合Schema定义。对于Agent工具调用、结构化数据抽取等对输出格式有严格要求的场景这是公有API无法提供的工程价值。最后回到一个实际问题要不要自建vLLM如果你的业务有高并发、大批量、数据敏感、强结构化输出的需求vLLM是非常值得投入的方案如果只是小流量原型验证、追求快速上线、不需要强格式约束公有API依然是更省事的选择。vLLM不是银弹但它在性能和可控性两个维度上给了开发者一个非常有竞争力的自建推理选项。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表