尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

vLLM:借助分页注意力实现简单、快速且低成本的大语言模型服务

vLLM:借助分页注意力实现简单、快速且低成本的大语言模型服务 1、前期知识储备1.1、什么是自回归解码过程大语言模型的输出本质上是在计算下一个词出现的概率而这个词是来自模型自带的词典确切的说是token词典。自回归解码是大语言模型LLM在推理阶段生成回答时生成文本的核心机制。用一句话概括把模型自己的上一个输出当作下一个输入像接龙一样一个字或一个Token一个字地往外蹦直到说完为止。1. 数学本质条件概率在数学上模型生成整句话的概率被分解为每个位置的条件概率乘积P(今日, 天气, 真, 好) P(今日) × P(天气 | 今日) × P(真 | 今日, 天气) × P(好 | 今日, 天气, 真)这意味着当模型要生成第 NN 个词时它必须看过前 N−1N−1 个词。它永远只预测下一个最可能的词而不是一次性构思好整个段落。2. 具体的运行流程步步拆解假设用户输入“请推荐一本书。”模型内部执行以下循环Loop初始化将用户提示词“请推荐一本书”编码为向量矩阵作为初始上下文。第1步预测模型根据当前上下文计算词库中每个词的概率分布选出概率最高的词比如“《”。拼接关键将“《”追加到原文后形成新上下文“请推荐一本书《”。第2步预测模型基于新上下文预测下一个词比如“三”。循环往复继续生成“体”、“》”、“是”、“一”、“本”……直到模型生成一个特殊的结束标记EOSEnd of Sequence或者达到设定的最大输出长度循环停止。3. 为什么叫“自”回归Auto-regressive自Auto指的是模型用自己生成的输出作为下一步的输入数据。回归Regressive在统计学中回归指用历史数据预测未来数据。在这里模型利用历史生成的Token序列回归预测当前时刻的下一个Token。4. 如何决定“选哪个词”——解码策略模型每次预测都会给几万个词打分但不一定每次都选最高分否则会陷入重复和死板。这就引入了不同的解码策略属于“自回归”过程中的关键变量贪婪解码Greedy每次都选最高概率的词最快但容易陷入平庸。随机采样Sampling按概率分布随机抽奖增加多样性配合Temperature温度系数控制随机性大小。Top-K / Top-P核采样只从概率最高的前K个或前P%的候选词中采样既保证连贯性又避免废话。5. 自回归的致命痛点与优化自回归过程是串行的必须等第 NN 个词算完才能算第 N1N1 个词这导致痛点生成速度慢受限于内存带宽且误差会累积——如果前面写错了一个字后面会“将错就错”地圆下去产生幻觉。优化变体为了解决慢的问题业界提出了投机采样Speculative Decoding即用一个小模型快速“草稿”出多个词再由大模型一次性并行验证但这依然属于“逻辑上的自回归”只是工程上的提速。1.2、什么是张量[在自回归解码过程中输入给大语言模型的所有词元都会生成注意力键和值张量这些张量会被保存在GPU内存中用于生成下一个词元]1. 从维度上看由浅入深在编程和数学中张量的“维数Rank”决定了它的样子0维张量标量就是一个单独的数字比如5。1维张量向量一列数字比如[1, 2, 3, 4]这就像一个一维表格。2维张量矩阵一个平面的表格有行有列比如 Excel 里的[[1,2],[3,4]]。3维张量立方体像一堆表格叠在一起比如一张彩色图片RGB三通道长 x 宽 x 颜色。4维及以上就是更多维度的数据堆叠比如视频批量大小 x 通道数 x 高度 x 宽度。在大模型里所有数据——无论是你的“提示词”、模型的“字典编号”还是你提到的“键和值张量”——都必须转化成这种数字容器GPU 才能进行数学运算。2. 回到问题LLM 中的“键Key”和“值Value”张量长什么样在你提到的自回归解码中每一层注意力机制都会为输入的每个词元生成K 张量和V 张量。它们通常是3维或4维的高维数组大致结构是批次大小 注意力头数 序列长度 每个头的维度举个例子假设模型有 32 个注意力头当前已生成 100 个词元每个头的维度是 128那么这一个 K 张量里就包含了32 × 100 × 128 409,600个浮点数。这些数字密密麻麻地排列在 GPU 的显存VRAM里等待做矩阵乘法。3. 为什么要用“张量”这么复杂的东西物理意义你可能会有疑问为什么不干脆叫“矩阵”或“数组”因为“张量”自带“变换法则”。在深度学习框架如 PyTorch、TensorFlow中张量不仅存数字还自动记录了每个数字的数据类型精度、设备位置CPU/GPU以及最重要的——计算图梯度训练时用来反向传播。在推理解码时你提到的 K 和 V 张量之所以必须保存在 GPU 内存中正是因为它们是高维稠密数据。GPU 拥有数千个计算核心专为并行处理这种“大块头”的张量矩阵乘法而设计。CPU 处理这种数据会慢几十上百倍。4. 直观类比如果“文本”是盖楼“张量”就是钢筋水泥文本“我喜欢你”是抽象的符号。词表映射把“我”转为编号234这是把概念变成数字。张量Embedding K/V模型并不把“234”当作普通数字而是把它“撑开”成一个几百维的向量1维张量比如[0.12, -0.98, 0.45, ...]。自回归保存当生成下一个字时模型把前面所有字对应的 K/V 高维张量堆叠在一起变成一个巨大的 3D 张量一次性扔进 GPU 的矩阵乘法器里算出当前注意力分数。总结一句大白话张量就是大模型世界里通用的“乐高积木”。文本、图片、音频、甚至模型内部的记忆K/V缓存都被统一抽象成这种带维度的数字积木。GPU 的职责就是疯狂地、并行地把这些积木拿来做乘法加法矩阵运算从而算出下一个词的概率。1.3、KV Cache 机制KV Cache 之所以成为显存“大户”根源在于“必要”且“巨大”。它的存在是为了避免重复计算、加速推理但其规模会随模型和任务呈线性乃至爆发式增长。而 PagedAttention 则像一位高效的内存管家通过引入操作系统的分页技术从根本上解决了 KV Cache 带来的显存浪费和碎片化问题。KV Cache 之所以成为显存“大户”根源在于“必要”且“巨大”。它的存在是为了避免重复计算、加速推理但其规模会随模型和任务呈线性乃至爆发式增长。而 PagedAttention 则像一位高效的内存管家通过引入操作系统的分页技术从根本上解决了 KV Cache 带来的显存浪费和碎片化问题。为什么 KV Cache 会占用大量显存1. 它为什么是“必要”的在自回归解码中生成每个新 Token 时都需要计算它和之前所有 Token的注意力。如果不加缓存每生成一个新字都要把前面所有的 K 和 V 矩阵重新算一遍这会产生天文数字般的重复计算。KV Cache 的核心价值就是“以空间换时间”把之前算好的 K 和 V 矩阵存起来之后生成新 Token 时直接复用避免了重复计算。这虽然省下了计算时间却占用了宝贵的显存空间。2. 它究竟有多大用公式说话KV Cache 的大小并非固定它与Batch Size、序列长度以及模型本身的结构直接相关。单个 Token 的 KV Cache 计算公式单个 Token 的 KV Cache (字节) 2(K和V两组) ×层数×注意力头数×每个头的维度×数据类型字节数然后用它乘以总 Token 数就能得到最终占用总 KV Cache 大小 单个 Token 的 KV Cache × Batch Size × 序列长度举个例子感受一下“巨大”以 Llama 2 7B 模型为例它的配置大约是 32 层每层有 32 个注意力头每个头维度是 128。当使用 16 位浮点数2字节时一个 Token 的 KV Cache 就是2 × 32 × 32 × 128 × 2 524,288字节约0.5 MB。这看起来不大但如果批次大小Batch Size为 16且要处理长度为 4096 的序列那么总大小就是0.5 MB × 16 × 4096 32 GB。在极端情况下长文本、大BatchKV Cache 与模型权重的显存占比甚至可能达到9:1。这就好比一个 40GB 显存的 A100 显卡模型权重只占一小部分而 KV Cache 可能吃掉40% 甚至更多的显存。3. 为什么“浪费”和“碎片”问题严重除了体量大传统的内存分配方式为每个请求预先分配一块连续的最大可能空间还存在两个严重问题巨大的内部浪费Internal Fragmentation系统按最大可能长度如 4096 Token为每个请求预留显存。但实际对话可能很短导致预留但未使用的“内部碎片”大量浪费。研究表明这种浪费可高达40% 到 60%。严重的外部碎片External Fragmentation不同请求的序列长度各异它们申请和释放显存的时间也不同这会导致显存空间被分割成许多无法被有效利用的小块即“外部碎片”。PagedAttention 如何省显存PagedAttention 的核心思想很简单借鉴操作系统的虚拟内存和分页机制将 KV Cache 从“连续的大块”管理转变为“非连续的小块”管理。1. 核心机制分页与动态映射切分为块Block将显存预先划分为固定大小的“块”Block每个块能存储固定数量如 16 个Token 的 KV 数据。按需分配不再为整个请求预留连续空间。需要多少 Token就动态分配多少个块来存储。逻辑到物理的映射维护一个“块表”记录每个请求“逻辑上连续”的 Token实际存储在哪些“物理上可能不连续”的块中。这样一来每个请求的 KV Cache 就像一张散落在显存各处的拼图通过块表拼凑起来彻底解决了对“连续大块内存”的依赖。2. 它带来了哪些具体收益几乎消除内部浪费预留空间的浪费被限制在最后一个数据块内显存利用率从 40% 提升到 96% 以上。解决外部碎片所有块大小一致分配和释放变得简单从根本上杜绝了内存碎片化。支持内存共享多个请求如果共享相同的提示词前缀如系统提示词可以共用同一块 KV Cache 数据进一步节省显存。3. 带来的性能飞跃通过这些优化PagedAttention 实现了巨大的性能提升吞吐量提升相比传统方法vLLM 的吞吐量最高可提升 20-24 倍。并发能力增强在相同显存下可将并发请求数提升 2-8 倍。显存利用率GPU 显存的有效利用率提升了 40% 以上。总的来说KV Cache 的显存占用是模型规模和并发需求的“硬成本”而 PagedAttention 通过更智能的内存管理将这笔“硬成本”的利用效率提升到了接近理想的状态。1.4、分页注意力机制存储的块和普通序列有什么差异1. 内存分配方式最根本的差异传统序列预留式“先占地再入住”。只要用户发来请求系统就根据设定的最大长度比如2048个Token在显存中一次性划出一块连续的、足够大的空地。不管用户最后只说了一句“你好”还是写了一篇论文这块地都被占死了。分页块按需式“来多少人租多大房”。系统不提前预留。模型每生成16个Token就去显存的“空闲块池”里申请一个固定大小的块。生成到第50个Token就申请4个块48个Token用满第4块只存2个。2. 物理空间的连续性传统序列要求物理地址绝对连续。就像停车场必须给你连在一起的几个车位中间不能有别人的车。这导致当显存碎片化时明明总空闲很大但因为找不到连续的大块新请求会被硬生生拒绝OOM内存不足。分页块物理地址完全允许不连续。逻辑上的第1、2、3块在物理显存里可能分散在第100号、第5号、第888号块。连续性只存在于“块表”的逻辑索引中不存在于物理硬件上。3. 内存浪费碎片率传统序列巨大的内部浪费。假设最大长度2048用户只说了10个词那么剩下的2038个Token位置全是空的但显存已被占用利用率极低。业界统计这种浪费通常在40% - 80%之间。分页块仅末尾微小的浪费。浪费仅存在于最后一个块中比如块容量16只存了2个Token浪费了14个空位。整体显存利用率可以飙升至96% 以上。4. 存储的数据结构显存里长什么样传统序列在显存中是一块单一的、巨大的连续张量Tensor。读取时只需要一个起始地址指针后续地址直接偏移即可。分页块由“物理数据块” “逻辑块表Block Table”组成。物理数据块里存着真正的K/V数值逻辑块表则是一个数组如[5, 100, 888]专门记录这些散落块的实际物理编号。5. 面对多请求Batch时的并发能力传统序列为了减少碎片系统通常会强制所有请求对齐到同一个最大长度。比如Batch里有3个短句和1个长句为了合在一起计算系统会拼命给短句填充空白Padding导致计算资源和显存被大量无效数据浪费。分页块每个请求独立维护自己的块表长短不一。GPU在计算注意力时只需根据各自的块表去显存不同角落抓取真实数据无需填充Zero Padding因此可以塞进比传统方法多2-8倍的并发请求。分页块大小是固定的数量动态生成的。就是说第二个块没存满后面来的数据会接着存。所以除了最后一个块其他的块都是存满的2、原文地址vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention | vLLM Blog
返回列表