推理阶段可以说是模型应用过程价值最高的过程我们来了解一下目前工业界最流行的高吞吐、低延迟的 LLM 推理引擎-vLLM和低配版nano-vLLM。一、模型推理1、什么是大模型推理大模型推理是指将训练好的大型语言模型LLM部署到生产环境对用户输入的文本Prompt进行处理并生成响应文本Completion的过程。这与模型训练Training相对应训练是让模型学会知识推理是让模型应用知识。其实推理阶段是一个自回归生成Autoregressive Generation过程模型需要逐个 token 地生成输出每个新 token 的生成都依赖于之前所有生成的 token。2、大模型推理完整过程大模型推理可以划分为两个核心阶段Prefill预填充 和 Decode解码。2.1 Prefill 阶段理解输入当用户发送一个 Prompt时模型首先需要对整个输入进行处理。这个过程包含以下步骤第一步Tokenization分词。模型无法直接处理文字需要将 Prompt 转换为 token 序列。目前常见的大模型通常使用 Byte Pair EncodingBPE或 SentencePiece 等子词分词器一个 token 大约对应 0.75 个英文单词或 1.5-2 个中文字符。第二步Embedding嵌入。每个 token 会被映射为一个高维向量embedding通常维度为 4096、5120 或 8192。这些向量包含了 token 的语义信息。第三步Transformer 前向传播。输入的 token 序列会依次通过多层 Transformer 块包括多头自注意力Multi-Head Self-Attention和前馈网络Feed-Forward Network。在自注意力层中每个 token 需要注意序列中的其他所有 token计算它们之间的相关性attention score。这个阶段的计算量与输入序列长度的平方成正比因此 Prefill 是计算密集型compute-bound的。第四步生成第一个 token。经过所有 Transformer 层后模型会输出一个 logits 向量对应词表大小的概率分布通过采样策略如贪婪贪婪解码或温度采样从中选取一个 token 作为第一个输出。Prefill 阶段的特点是高度并行化由于输入的全部信息已知GPU 可以充分利用矩阵运算的并行能力。因此这一阶段的目标是最大化计算吞吐量。2.2 Decode 阶段逐字生成生成第一个 token 后模型进入自回归解码阶段。这个阶段的核心特点是串行生成每个新 token 必须等待前一个 token 生成完毕才能开始计算。第一步处理新 token。新生成的 token 与之前的 token 序列拼接作为下一轮的输入。第二步计算 attention。与 Prefill 不同Decode 阶段的 attention 计算更为复杂。每个新 token 需要attend到之前所有 token 的 Key 和 Value 向量即 KV Cache。随着序列增长KV Cache 越来越大而新 token 的计算量却相对较小只需要计算一个 token 的 Q、K、V。第三步生成下一个 token。经过完整的 Transformer 层后模型输出下一个 token 的概率分布。重复执行。这个过程会不断重复直到生成结束符EOS token、达到最大长度限制或被其他停止条件终止。Decode 阶段的瓶颈在于内存带宽memory-bandwidth bound虽然单步计算量不大但每一步都需要读取完整的 KV Cache。随着上下文越来越长内存访问量持续增长而 GPU 的内存带宽是有限的。因此Decode 阶段的目标是最大化内存访问效率。3、大模型推理的核心痛点3.1 显存容量瓶颈这是大模型推理最根本的制约因素。一个 70B 参数的模型使用 FP16 精度时需要约 140GB 显存才能加载。即使是最顶级的单卡如 H100 80GB也无法容纳这样的模型。因此实际部署通常需要多卡甚至多机并行。更严重的是KV Cache 会随着序列长度线性增长。假设一个请求的上下文长度为 4096 tokens每个 token 在每一层 Transformer 中都会产生 Key 和 Value 向量。假设模型有 80 层隐藏维度为 8192那么单条请求的 KV Cache 大小约为。这意味着同时处理 8 个这样的请求仅 KV Cache 就需要 80GB 显存。3.2 显存碎片化与低利用率传统推理框架如 Hugging Face Transformers对 KV Cache 采用连续分配策略。每个请求的 KV Cache 必须放在一块连续显存区域中但由于不同请求的序列长度差异巨大这种方式会造成严重的显存碎片化。研究表明传统方法的显存利用率通常只有 20-40%大量显存被浪费在无法利用的碎片空间中。二、推理优化上面提到了三个问题分别是显存容量显存利用率低Prefil和Decode干扰我们一步步来看。1、量化其实看到容量不够的第一反应就是压缩或者删减但是模型的参数是没办法舍弃的所以只能进行压缩这里就有了量化。量化本质上就是对AI模型的“数据压缩技术”用牺牲极小精度的代价换取极其巨大的空间节省和运行速度。1.1、量化原理默认情况下模型的一个参数是一个 32位的浮点数FP32占 4 个字节Bytes。那一个7B的模型参数量就是 70亿参数 × 4字节 28 GB。那么如果需要把模型塞进显卡起码需要30G的显存。所以需要对参数进行精度缩常见的精度有下面这些FP32Float32单精度浮点默认的训练标准极度精确极度吃资源。FP16 / BF16半精度浮点目前大多数模型发布时候的标准格式肉眼可见的性能下降但是显存直接省一半。INT8 8位整数。早期的主流压缩方案模型变小四倍基本不影响智商。INT44位整数。当前大模型的主流如GPTQAWQGGUF格式。只用4bit存一个参数模型缩水到原本的1/8大部分开源模型用INT4跑智商仍然在线。1.2、量化实现具体是怎么实现的你呢举例子FP32也就是32位浮点数INT8就是八位整数FP32的精度比较广他可以是非常长的小数的一个数字但是INT8只能是-128-127的整数位置只有256种可能。如何把连续的小数塞进256个位置里面采用映射法寻找比例尺找出原模型某一层参数里的最大值和最小值比如找到了最大值是12.7最小值-12.7。把这个[-12.7,12.7]的区间等分成256份。把原来的小数“四舍五入”塞进离它最近的格子里变成一个整数-128-127。记录下一个缩放因子。最后在推理计算的时候拿整数进行飞速计算算完以后再乘以缩放因子恢复成小数输出。1.3、量化现状目前量化常见的流派就是两种 PTQ训练后量化和 QAT量化感知训练。PTQ就是模型训练过程正常训练还是FP16或者FP32训练完后拿“大刀”直接把精度砍掉转化成INT4/INT8这样训练的模型会很快成本比较低缺点是会损失精度。QAT则是在训练模型的时候就假装自己在被量化。模型在学习过程中会自动调整去适应这种低精度就是极其耗钱耗时一般是大厂发布官方量化版才会用但是好处就是几乎不会损失精度模型能力保留的最好。其实量化也是有缺陷的首先就是重度量化下INT4及其以下模型在做复杂逻辑推理、写代码、算数时产生“幻觉”或胡说八道的概率会变高。其次是那些模型在训练时看的不多的偏门知识往往存在非常小的浮点数里面量化四舍五入直接抹零会导致模型忘记冷门知识并且有些显卡计算INT4 并不能直接计算要在计算的那一瞬间把数据还原成FP16 如果代码没有写好转换的时间反而比省下来的时间更长不考虑资源的情况下。2、显存碎片化上面已经仔细的讲过了PD的过程其实大家可以看到在Prefill预填充阶段阶段处理用户输入的 Prompt。这是一个一次性的矩阵乘法计算量极大此时是Compute-bound算力瓶颈--需要量化。在Decode解码阶段阶段模型自回归生成回答一次蹦一个字。这是Memory-bound访存/显存瓶颈这里我们需要的就是vLLM。一句话说清楚就是因为传统预分配机制造成显存大量浪费导致无法接入更多并发请求进而导致GPU 算力严重闲置所以需要 vLLM 用分页动态分配打碎显存彻底消除浪费把并发量顶上去最终释放出 GPU 真正的性能。2.1、 KV Cache上面我们提到过KV Cache就是计算当前Token的Attend的之前所有 Token 的 Key 和 Value 向量。具体就是在 Decode 阶段生成第 N 个 Token 时候Attention 机制要求当前的 Query (Q) 必须和前面 N-1 个Token 的 Key (K) 和 Value (V) 进行点积计算。如果不使用Cache那么每次生成新词都要把前面所以的词重新跑一遍模型来计算 K 和 V 。这及其浪费算力和时间。如果用上KV Cache这样就可以把前面计算过的 K 和 V 保存在 GPU 显存里。生成新词的时候只需要计算当前新词的 Q,K,V然后把新词的 K,V 追加到显存里面再拿当前的 Q 和显存里面完整的 K,V序列做计算即可。可能大家会好奇为什么KV Cache占用这么多的显存其实 KV Cache不是模型参数它是动态生成的中间状态。它的占用量是随请求数和序列长度线性爆炸的。计算公式显存占用 2K 和 VX 批次大小Batch SizeX 序列长度Seq LenX 层数LayersX 注意力头数HeadsX 头维度Head DimX 2FP16字节数。好比对于LLaMA-13B模型单个长度为8192的请求KV Cache就会吃掉约6 GB的显存。问题点就是GPU的算了非常强为了喂饱GPU我们需要增大Batch Size同时处理多个请求。但是因为 KV Cache 太大了稍微增加几个请求几十 GB 的显存就OOM了。显存不够导致Batch Size上不去再到GPU 算力闲置最后导致整体的吞吐量极低这就是问题Memory-bound访存/显存瓶颈。2.2、vLLMvLLMVery Large Language Model inference由 UC Berkeley 的 LMSYS Org 于 2023 年开源是目前最流行的开源推理引擎。其核心创新是 PagedAttention灵感来自操作系统虚拟内存的分页管理机制。vLLM的核心就是极高的吞吐量和极低的显存浪费。上面有介绍KV Cache其实一句话说白了这个东西就是模型为了“记住”上下文而暂存到显存里的数据罢了。传统的推理方式有很明显的漏洞在 vLLM 出现之前系统为了存放 KV Cache会采用“预分配”的策略。因为系统不知道用户这一句话最终会生成多长所以干脆一开始就给每个请求分配一块巨大的、连续的显存空间假设最多生成 2048 个词。这导致了极其严重的显存浪费内部碎片预分配了 2048 个词的空间但实际上只生成了 10 个词剩下的显存全空着别人也不能用。外部碎片内存被切得七零八落虽然总剩余空间很大但找不到一块“连续的大空间”给新请求用。结果就会导致传统引擎的显存中有60% - 80%都是被浪费掉的。显存满了就没法同时处理更多的请求并发量上不去导致吞吐量极低。而vLLM能够解决上面这些漏洞有两个核心点PagedAttention分页注意力和Continuous Batching连续批处理。1、PagedAttention虚拟内存映射PagedAttention 核心其实是将每个请求的 KV Cache 分割成固定大小的页Page/Block这些页可以在显存中非连续分布。当需要访问某个序列的 KV Cache 时通过一个页表Block Table来定位各个页的位置。具体做法就是vLLM 借鉴了操作系统的“分页内存”管理机制打破了“必须连续分配”的魔咒。Block块 vLLM 把显存切分成一个个固定大小的 Block。假设 1 个 Block 只能存 16 个 token 的 KV 数据。逻辑空间 vs 物理空间 对于模型来说它看到的文本是连续的逻辑连续但在 GPU 显存上这些数据被散落在不同的 Block 里物理不连续。Block Table块表 这是核心组件类似一本“目录字典”。它记录了“第 116 个词存在物理显存的 Block A第 1732 个词存在 Block D……现在的流程变成了这样进来一个请求vLLM 只给它分配1 个 Block16 个座位。生成到第 16 个词Block 满了。vLLM 立即在显存里随便找一个空的 Block不需要和上一个挨着分配给这个请求并在 Block Table 里登记一下。结果就是现在的显存分配过程成了按需分配基本是用多少给多少最后没填满的最多也就是最后一个Block (最多浪费15个Token)并且内部碎片问题基本为0。其次是现在的显存分配的Block无需连续因为有Block Table物理空间是不需要连在一起的外部碎片问题完全消失显存利用率提高到近96%。假设显存是一家餐厅有 100 个座位。来了一个客人Request A系统不知道他最终会带几个人来但是规则规定最多不能超过 20 人max_seq_len20。所以系统直接拉起警戒线给他锁定了 20 个连着的座位。内部碎片结果这个客人其实只有 5 个人生成了 5 个 token剩下的 15 个座位空着别人也不能坐这就叫内部碎片浪费严重。外部碎片餐厅里东空 5 个座位西空 3 个座位虽然加起来还有 30 个空座但此时来了一个需要 10 个连续座位的客人餐厅却安排不下只能让他排队。这就叫外部碎片。其实PagedAttention 还有一个关键的地方Copy-on-Write (写时复制与显存共享)在做并行采样比如同时生成 3 个不同的回答或 Beam Search 时前缀 prompt 是完全一样的。在以前3 个回答会复制 3 份 Prompt 的 KV Cache。而在 vLLM 中这 3 个回答指向同一个物理 Block。只有当它们生成的后续内容开始不一样时才会分配新的 Block 存放各自的新内容。我把这个过程举一个详细的例子大家就明白了在 vLLM 的底层为了实现物理显存和逻辑序列的解耦它维护了三个东西物理 Block (Physical Block)GPU 显存里真实的、固定大小的小块比如每块能存 16 个 Token 的 KV 数据。逻辑 Block 表 (Block Table)每个生成的句子Sequence都有一张自己的表。这张表记录了“我的第 1 块对应物理显存的第几块我的第 2 块对应物理显存的第几块……”引用计数器 (Reference Count)划重点这是 CoW 机制的灵魂。vLLM 会在后台监控每一个物理 Block 当前被几个序列Sequence指着。假设你输入了 Prompt“讲一个关于黑洞的故事。”并且你要求模型并行生成 3 个不同的回答Seq A, Seq B, Seq C。vLLM 收到请求计算这个 Prompt 的 KV Cache。假设这个 Prompt 刚好占满了 2 个物理 Block比如分配了物理 Block 0 和 Block 1。神操作来了vLLM不会把这个 Prompt 复制 3 份。它只会让 Seq A、Seq B、Seq C 的Block Table 都指向这两个相同的物理 Block。此时的底层状态是这样的Seq A 的 Block Table:[逻辑块0 - 物理Block 0], [逻辑块1 - 物理Block 1]Seq B 的 Block Table:[逻辑块0 - 物理Block 0], [逻辑块1 - 物理Block 1]Seq C 的 Block Table:[逻辑块0 - 物理Block 0], [逻辑块1 - 物理Block 1]引用计数 (Ref Count)物理Block 0 的计数是3物理Block 1 的计数也是3。所以在这一步他就能省略2/3的显存。现在模型要开始为一个字一个字往后生成了。因为是并行采样可能Seq A 生成了“在”Seq B 生成了“很久”Seq C 生成了“宇宙”它们生成了不同的词计算出了不同的新 KV Cache现在需要把这些新的 KV 数据写进显存里。假设之前的 物理Block 1 还没有装满比如容量是 16 个 TokenPrompt 只占了 10 个还有 6 个空位。按照正常逻辑系统应该顺着往下把新生成的词写进 物理Block 1 的剩余空间里。但是触发 Copy-on-Write (写时复制) 的时刻到了检查阶段Seq A 想要把新词“在”的 KV 数据写进 物理Block 1。系统一看卧槽物理Block 1 的引用计数是 3拦截并复制系统知道如果让 Seq A 直接写进去那 Seq B 和 Seq C 看到的 Prompt 就被污染了因为大家指着同一块地。于是系统拦截了这次写入。Copy复制vLLM 赶紧去显存里找一块全新且空闲的物理 Block假设是 物理Block 2然后把 物理Block 1 里原有的那 10 个 Prompt Token 的数据原封不动地 Copy 一份到 物理Block 2 里。修改指针并写入 (Write)vLLM 把 Seq A 的 Block Table 指针修改掉从指向 物理Block 1 改为指向新的 物理Block 2。此时Seq A 就可以安全地把新词“在”写进 物理Block 2 的第 11 个位置上了。更新引用计数老的 物理Block 1 的引用计数减 1变成 2。新的 物理Block 2 的引用计数设为 1。接下来Seq B 和 Seq C 如果也要写入也会经历类似的过程。当 Seq B 也要写入时发现 物理Block 1 计数是 2依然大于 1再次触发 Copy分配 物理Block 3计数减为 1。等到 Seq C 要写入时发现 物理Block 1 的计数已经变成 1 了这意味着没有别人在用了Seq C 就不需要复制直接写入 物理Block 1 即可。这样就会极大的减少显存占有。为什么这招在 Beam Search集束搜索中堪称神技在机器翻译或极其要求严谨的文本生成中我们经常用 Beam Search。Beam Search 的特点是它会生成一个词的多个候选项分叉如果某条分支走到后面发现概率太低就会砍掉Prune这个分支。传统框架的灾难每次分叉都要复制一遍整条句子的显存一旦砍掉分支刚才复制的显存又要清空极其浪费显存和内存带宽。vLLM 的优雅实现分叉时根本不复制数据只是让大家指向同一个 Block引用计数 1。继续生成时只有真要写不同数据时才复制当前的最后一个 Block之前的几十个 Block 依然共享。砍掉分支时只需要把该分支指向的所有物理 Block 的引用计数 -1。如果某个物理 Block 的计数变成了 0vLLM 就直接把它标记为空闲回收显存。总结vLLM 中的 Copy-on-Write 是通过“逻辑映射 物理引用计数”实现的。只要引用计数 就说明这块显存是“共享财产”谁想往里写新东西谁就得自己先 Copy 出来单过。通过这种机制无论并行生成多少个回答它们完全一致的前缀部分在物理显存中永远只有 1 份从而极其极限地压榨了 GPU 的显存。2、 连续批处理连续批处理是 vLLM 带来的另一项重大改进。核心点是不再要求批次中的所有序列同时开始和结束推理。当某个序列生成完毕遇到 EOS token 或达到最大长度时它的显存页立即被释放新的序列可以立即加入继续处理。我们先来看传统的批次处理过程假设现在系统收到了两个请求我们要把它们打包成一个 Batch 交给 GPU请求 APrompt 只有 5 个词最终只需生成2 个词就结束了比如问11等于几答等于 2。请求 BPrompt 有 5 个词最终需要生成100 个词比如问写一篇作文。灾难 1被迫对齐Padding 浪费GPU 计算需要规则的矩阵。在传统的 Static Batching 中为了把 A 和 B 拼在一起算必须把短的 A强行用无意义的占位符Padding补齐对齐到和 B 一样长。GPU 虽然在算但很大一部分算力都在算那些毫无意义的 Padding 数据。灾难 2同进同出时间浪费看看它们在 GPU 里的时间线每个 T 代表一个迭代步T1 (Prefill):GPU 同时处理 A 和 B 的 Prompt。T2 (Decode 1):生成了 A 的第 1 个词B 的第 1 个词。T3 (Decode 2):生成了 A 的第 2 个词B 的第 2 个词。此时 A 已经完全回答完毕了T4 ~ T101:B 还在苦哈哈地一个词一个词生成。结果在 T4 到 T101 这 98 个周期里请求 A 虽然已经算完了但它不能离开它的数据死死霸占着显存因为这个 Batch 还没结束。即使此时外面有新的“请求 C”在排队系统也无法把它塞进来。静态批处理的总结GPU 被迫做无用功Padding且严重占着茅坑不拉屎。vLLM 的 Continuous Batching迭代级连续批处理Continuous Batching 彻底打破了“Batch 必须是一个规则矩形”和“必须同进同出”的限制。它把调度的最小单位从“整个请求”缩小到了**“单个 Token 的迭代步Iteration”**。让我们看看 vLLM 是怎么处理刚刚那个场景并顺带接入新请求 C 的请求 A需生成 2 个词。请求 B需生成 100 个词。请求 C (新来的)需生成 5 个词在 T3 时刻到达。vLLM 的神级时间线T1 (Prefill):正常处理 A 和 B 的 Prompt吐出各自的第 1 个词。T2 (Decode):计算 A 和 B 的第 2 个词。请求 A 完成T3 时刻大洗牌vLLM 发现 A 结束了瞬间把 A 的结果返回给用户并彻底清空 A 占用的显存归还 Block。此时请求 C 刚刚到达。vLLM 不等 B 算完立刻把请求 C 塞进当前的 Batch 中。T3 (混合计算最核心的技术点):在 T3 这个迭代步里GPU 同时在做两件完全不同的事它在处理请求 B 的 Decode计算第 3 个词只需输入 1 个 Token。它同时在处理请求 C 的 Prefill处理 C 的整段 Prompt比如 20 个 Token。vLLM 会把这 1 个 Token 和 20 个 Token展平Flatten拼成一长串共 21 个 Token一次性扔给 GPU 算完T4 (Decode):GPU 现在同时计算 B 的第 4 个词和 C 的第 2 个词。T5 (Decode):GPU 同时计算 B 的第 5 个词和 C 的第 3 个词。整个过程就是GPU在不断的检查是否有刚刚生成最后一个词的请求如果有输出这个请求释放位置检查是否有新的请求如果有加入新请求以此循环往复之后系统会把当前所以的Token收集起来展平成一个一维数组GPU会对这个一维数组进行一次前向传播计算并把算出来的新词分发给对应的请求再到下一次循环。这样带来的好处首先是实现了真正的 0 Padding因为所有Token都被展平了GPU 只对真实存在的Token 进行计算完全消除了静态批处理中为了对齐而产生的空槽计算浪费其次GPU运算的吞吐量被极限压榨只要显存被 PageAttention 高效管理着还有空只要GPU算力还没到瓶颈。每个微小的空隙都会被瞬间塞进新的请求。GPU 永远处于高负荷高饱和状态。3、nano-vLLM其实Nano-vLLM是一个为了“教学和算法验证”而设计的极简版的纯Python/PyTorch的推理引擎。官方的vLLM为了追求极致的生产性能底层的PagedAttention是用C和CUDA汇编级别的自定义算子Kernels编写的并包含了复杂的异步网络通信和多卡分布式逻辑。这导致算法研究者极难看懂核心的调度逻辑。而Nano-vLLM则去除了所有硬件级别的权限优化、网络接口服务和分布式框架。它用最高级、最基础的PyTorch张量操作硬写了一遍vLLM的核心算法。1. PagedAttention 的原理实现显存管理的抽象在没有底层 CUDA 算子支持的情况下Nano-vLLM 是如何模拟“非连续显存的分页管理”的物理显存池的建立系统在初始化时会直接在 GPU 上开辟一个固定大小的、高维度的 PyTorch 张量Tensor这代表总显存。这个张量被逻辑上均匀切分成了多个“块Block”。索引即指针系统维护一个包含所有“可用块序号”的列表。当新请求进来时分配显存的本质就是从这个列表中弹出一个“序号”即逻辑指针分配给该请求而不是真的去底层划拨内存。块表Block Table跟踪每个请求都会维护自己的一个列表记录着自己被分配到的物理块序号例如 [5, 12, 42]。Attention 计算的妥协Gather 机制原生的 PyTorch 注意力机制无法直接计算散落的显存。因此在每次执行计算前Nano-vLLM 会根据块表的序号去总显存池中把对应的数据“收集Gather”并“拼接Concat”成一个连续的临时张量然后再输入给模型计算。(注这一步在官方 vLLM 中是通过底层算子直接飞线计算的Nano-vLLM 这里的拼接操作会导致性能极速下降但完美展示了数据映射的逻辑路线。)2. Continuous Batching 的原理实现调度逻辑的抽象Nano-vLLM 摒弃了复杂的异步并发控制采用了一个绝对同步的、基于迭代步Iteration的状态机驱动循环来展示连续批处理。三列队状态机系统严格维护三个队列Waiting等待中、Running计算中、Finished已完成。Token 级步进校验模型每生成一个 Token即经历一次完整的网络前向传播调度器就会强行暂停执行一次全局状态校验。即时踢出与填补在校验阶段系统遍历 Running 队列一旦发现某个请求刚刚生成的 Token 是“结束符EOS”立刻将其移入 Finished 队列并瞬间回收它所占用的所有物理块序号。紧接着系统评估当前剩余的空闲块数量。如果数量足够满足 Waiting 队列中第一个请求的初始需求就立刻将其移入 Running 队列并为其分配物理块序号。动态 Batch 重组完成上述队列更新后系统将当前 Running 队列中的所有请求数据提取出来重新打包成一个新的 Batch送入下一步的计算。其实Nano-vLLM更适合于那些需要去了解vLLM底层的去看源码并不适合在实际业务开发过程去使用只有vLLM才能更适用真实的工业实现上的应用。4、llama.cpp大模型爆发初期跑模型必须依赖几百 GB 的庞大 PyTorch 环境和昂贵的英伟达显卡。一位名叫 Georgi Gerganov 的极客受不了了他写出了 llama.cpp。它的目标极度纯粹剥离一切复杂的机器学习框架让大模型能在普通的笔记本电脑、MacBook 甚至手机上流畅运行。纯 C/C 编写没有任何 Python 依赖不需要装 PyTorch。GGUF 模型格式它自己发明了一套文件格式GGUF把模型的结构和权重全部打包在一个文件里专为快速加载优化。极低位量化支持将模型极限压缩到 4-bit 甚至 2-bit、3-bit极大降低了对内存RAM的需求。它是怎么实现的底层逻辑手撕算子作者和社区用纯 C 语言把大模型里的所有数学公式矩阵乘法、注意力机制重新写了一遍。内存映射mmap它利用操作系统的 mmap 技术把几十 GB 的 GGUF 模型文件直接映射到内存中。由于不需要走 Python 那套复杂的序列化/反序列化流程模型加载速度几乎是瞬间的。硬件指令集压榨既然没有英伟达 GPU它就去压榨 CPU 的极限。它深度调用了 CPU 的 SIMD 向量指令集如 Intel 的 AVX2ARM 的 NEON并且针对苹果的 M 系列芯片调用了 Apple Metal API把 Mac 变成了跑大模型性价比最高的神器。优势绝对的轻量级到处都能跑边缘设备、手机、PC。零依赖永不担心 Python 环境冲突。单用户本地体验无敌。劣势缺乏企业级的高并发调度能力如完美的 Continuous Batching。如果你把它部署在云端让 100 个人同时请求它会排队排到死。5、TensorRT-LLMTRT-LLMTensorRT-LLM是英伟达官方推出的企业级大语言模型推理框架它的核心是榨干英伟达自家显卡的所有性能。绝大多数框架如 vLLM是“解释型”的——启动程序加载模型来一个请求算一次。TensorRT-LLM 本质上是一个“编译器”编译阶段在服务启动前它会读取你的 HuggingFace 模型然后根据你当前机器插的具体显卡型号比如是 A100 还是 H100在底层使用 CUDA 汇编级别的代码把模型网络图死死地“焊”成一个专属的、极致优化的二进制文件叫做 Engine。融合计算比如原来的计算是D A x B C。标准流程是算完A x B 存入显存读取显存加上C 再存入显存。TRT-LLM 会写一个专门的 CUDA 算子在 GPU 芯片内部一次性把这三步做完绝不往显存里来回搬数据速度快到飞起.它的核心点就是AOT编译和算子融合。1、 AOT 编译机制从“边翻译边执行”到“死磕硬件的机器码”绝大多数框架如 vLLM 原生框架是基于 PyTorch 的属于动态图机制 (JIT/解释型)。解释型的运作方式就像一个同声传译。GPU 每算完一层Python 代码都会在 CPU 上发号施令“去显存拿数据 ——调个算子算一下 ——把结果写回显存”。Python 本身很慢这个“发号施令”的过程会产生巨大的延迟CPU 开销。TRT-LLM 的解法AOT (Ahead-of-Time) 提前编译TRT-LLM 不允许在运行时做任何动态决定。在启动服务之前你必须执行一个 trtllm-build 的命令。这个编译过程长达十几分钟到几个小时它在底层究竟干了什么剥离 Python生成静态图编译器把 HuggingFace 模型的所有网络层剥离出来去掉所有的 if/else 等动态逻辑固化成一张纯数学的有向无环图。从此推理过程不再需要 Python 的干预。极致的硬件 Auto-Tuning自动调优极其关键这是英伟达的绝活。同样是算一个矩阵乘法在 A100 显卡和 H100 显卡上最优的底层算法是完全不同的。在编译时TRT-LLM 会在你的当前这张显卡上进行成百上千次的微型跑分测试。它会尝试 10 种不同的 CUDA 指令排列组合看看哪种在“你的这张卡上”跑得最快。选出最快的那一种死死地“焊”进最终的二进制文件里。显存硬分配 (Memory Pre-allocation)在编译时你必须告诉它最大 Batch Size 是多少最大序列长度是多少编译器会直接在底层把这些显存空间的地址写死。运行时不再调用极其耗时的 cudaMalloc 去临时申请内存。结果编译出来的是一个叫 .engine 的黑盒二进制文件。只要一运行它就以接近光速的底层机器指令在跑。二、 算子融合 (Kernel Fusion)如何打破“内存墙”刚才说了GPU 最大的瓶颈是“等数据”。要理解这个得知道 GPU 里面有两种内存VRAM (全局显存)容量极大比如 80GB但读取速度非常慢相对而言。SRAM/寄存器 (芯片内缓存)就在计算单元CUDA Core/Tensor Core旁边容量极小只有几十 MB但读取速度快如闪电。普通框架的灾难不融合大模型里有一个常见的结构全连接层Linear —— 激活函数SiLU ——乘法Multiply。如果是普通框架计算 Linear把巨量数据从 VRAM 搬到 SRAM —— 计算 ——把中间结果写回 VRAM。算 SiLU把中间结果从 VRAM 搬回 SRAM —— 计算 ——再次写回 VRAM。算 Multiply把上一步结果搬进 SRAM ——计算 ——写回 VRAM。这就像一个极其低效的搬运工每次切完一根葱都要放回几十米外的冷库切下一根再跑去拿。TRT-LLM 的神技算子融合既然这段逻辑每次都是连在一起的英伟达工程师干脆手写了一段极其复杂的 CUDA 汇编代码把这三步融合成一个单一的算子 (Fused Kernel)。把巨量数据一次性从 VRAM 搬进 SRAM。在 SRAM 的寄存器内部光速算完 Linear接着算 SiLU接着算 Multiply。最后把最终结果一次性写回 VRAM。收益这一招直接干掉了 66% 的显存读写延迟I/O。在 TRT-LLM 中类似 QKV Attention 融合、MLP 融合、RMSNorm 融合无处不在这就是它吞吐量极度恐怖的根本原因。三、 In-flight Batching深度定制的连续批处理你之前问它和 vLLM 的 Continuous Batching 有啥区别。概念上完全一样但实现厚度完全不同。vLLM 的做法状态机和调度器是用Python写的维护 Waiting, Running 队列。虽然灵活但在并发极高每秒上万 Token时Python 的 GIL 锁和执行速度会成为拖累 GPU 的木桶短板。TRT-LLM 的做法调度逻辑全部下沉到C 甚至 CUDA 层面。它结合了前面提到的 AOT 编译。因为在编译时已经知道了显卡每一层算子的精确耗时调度器在插入新请求时能够做出更极致的“微秒级预判”。它的 Paged KV Cache分页显存管理是直接和英伟达硬件驱动层深度绑定的几乎没有任何软件抽象带来的损耗。可惜的是这个框架只能用在英伟达显卡上而且不适用于便携转移因为 AOT 编译是把模型焊死在特定硬件上的。如果你把在 A100 上编译好的 Engine 文件复制到一张 V100 或 RTX 4090 上跑会直接报错崩溃。因为底层机器指令集不匹配你必须在新显卡上重头编译。6、SGLang如果说vLLM是一把砍向“低并发、显存碎片化”的开山斧那么SGLang就是一把专门用来做手术的“极光手术刀”它精准地切中了当前大模型应用特别是 AI Agent 和 RAG 业务中最痛的一个痛点巨量且重复的上下文。在当前的 AI 真实业务场景中我们很少会让模型漫无目的地聊天我们通常是这样用它的RAG检索增强生成塞给模型 1 万字的《公司员工手册》然后问“请假流程是什么”多轮对话用户和机器人聊了 50 轮每一次新的提问都要把前面 50 轮的历史记录全部再发给模型一次。Few-shot少样本提示每次给模型发任务前先塞 10 个标准的问答例子让它照着学。vLLM 面对这种场景的致命弱点极其弱智的“失忆症”。当 100 个员工并发询问《公司员工手册》里的不同问题时在 vLLM 看来这是 100 个全新、独立的请求。vLLM 会把那 1 万字的员工手册在 GPU 里老老实实地重复计算 100 遍 KV Cache这极其浪费巨量的算力Prefill 阶段导致首字延迟TTFT非常高。1、RadixAttention如果说 vLLM 是给每个句子发不连续的“物理内存块”PagedAttention那么 SGLang 则是在 GPU 显存里直接种了一棵“公共前缀树”Radix Tree。它是怎么运作的底层逻辑1. 首次遭遇建树用户 A 发来请求[1万字手册] 问题年假几天SGLang 算完了这 1 万字的 KV Cache。此时它不会在计算完后就立刻清空这部分显存vLLM 会清空。相反它把这 1 万字作为“树干”永久挂在显存里的一棵目录树上并保持映射。2. 第二次遭遇瞬间命中树干用户 B 发来请求[1万字手册] 问题报销额度SGLang 的调度器一看卧槽前面 1 万字的 Token 序列在我的显存树里完全存在神操作来了SGLang直接跳过这 1 万字的巨量矩阵乘法计算Prefill直接把指针指向现成的物理显存块。它只需要计算最后那个短问题“报销额度”的 KV Cache。结果用户的首字生成时间TTFT从原本需要等 3 秒瞬间降到了 0.05 秒。3. 显存满了怎么办LRU 淘汰机制随着存的树枝越来越多GPU 显存总会满。SGLang 引入了经典的LRU最近最少使用算法。它会自动把最近最没人问的那段前缀 KV Cache 砍掉释放显存给新的热门话题用。2、 可编程的结构化控制 (DSL)SGLang 名字的全称是 Structured Generation Language结构化生成语言。它不仅仅是个服务器它还是一门针对大模型的 Python 专属编程语言。以前我们写 Prompt 只能用长字符串拼接String concatenation。如果我们要让大模型先推理 3 种可能再总结出 1 个 JSON 格式的结果我们只能靠“祈祷”它不要输出错乱的格式并用复杂的正则表达式去解析它。SGLang 前端的魔法多分支与强制生成魔法 1fork 和 join (并行思维树)假设你要模型回答一个极其复杂的数学题。在 SGLang 里你可以写一行代码 prompt.fork(3)大模型会在 GPU 底层瞬间分裂出 3 个平行的宇宙3 个回答分支。因为底层有 RadixAttention这 3 个分支完全共享题目的显存开销极小。然后你再用 join 把它们合并让模型对这 3 个分支进行打分选出最完美的一个。这种操作在其他框架里极其难以实现且效率低下。魔法 2强制正则表达式 (Regex/JSON Constrained Generation)如果你希望模型必须输出一段合法的 JSON或者必须输出一段有效的 Python 代码。SGLang 提供了一个叫 regex 的参数。在模型逐字生成Decode的底层SGLang 会介入其概率分布的计算。如果下一个字违反了 JSON 语法比如少了个大括号SGLang 会强制把这个错字的概率降为 0。结果输出的格式 100% 正确永远不需要担心大模型“幻觉”导致解析失败。对于构建AI Agent 系统需要模型反复思考、多分支决策或者做深度 RAG 知识库问答几十次带着同样的大型系统背景提问SGLang 是你唯一的也是最完美的答案。它能极其恐怖地省下算力并消除大文本堆积带来的卡顿延迟。热门的推理基本都在了其实最常用的就只是vLLM和SGLang基本这两个能完成文本对话模型常规的所有模型推理了。