微信扫码
添加专属顾问
深入了解大模型推理背后的高效机制,从张量维度变化到连续批处理与Paged Attention的巧妙设计。 核心内容: 1. 以Llama 3为例,追踪推理过程中每个计算环节的张量维度变化 2. vLLM调度器如何通过连续批处理解决请求步调不一致问题 3. Paged Attention机制如何实现KV Cache的显存高效管理
看了很多的文章和视频,我以为我理解大模型的工作原理了,直到看了vLLM的代码,我发现很多地方理解的太过表面。因此花了大概2个月的业余时间,深入阅读了vLLM的源码,本文算是对于学习代码的一个总结。另外由于当前主流LLM都是 Decoder-Only 架构,本文会聚焦LLM,不会像网络上其他介绍Transformers的文章从原始论文的 Encoder-Decoder架构讲起。
在阅读 vLLM 源码的过程中发现,追踪推理过程中每一步的张量维度变化,对于理解大模型工作原理非常有用。因此,本文将以 Llama 3为例,介绍推理过程中的每个计算环节,在每个计算环节都会标注Tensor的维度变化。
我们知道通过批处理可以提高GPU的利用率:本质是提升计算强度,即通过复用权重数据来均摊内存访问开销;而减少Kernel启动开销与通过海量warp充分发挥隐藏延迟仅是随之而来的次要优化。而批处理内的请求要步调一致,同时开始,同时结束。而对于LLM这类生成式任务,其核心矛盾在于每个请求的输出序列长度是不可预测且差异巨大的。那怎么办呢?
有没有可能将调度的从request level下沉到token level呢? 恭喜你发明了continuous batching。
那每个请求的KV Cache显存申请是不是应该也是token level,不要一次申请所有的显存。搞一个地址数组(block table)来维护每个请求的KV Cache地址就好? 恭喜你发明了Paged Attention。
没错,以上两个概念是当今大模型得以高性能运行的关键。
在vLLM调度器的视角中,不存在Prefill 阶段和Decode阶段的区分。 每个请求主要关注:
num_computed_tokens:已经计算过的 token 数(含 Prefix Cache 命中的部分)
num_tokens:该请求当前总共拥有的 token 数(prompt + 已生成的 output)
每一步调度的目标就是:让num_computed_tokens追上num_tokens。差多少就调度多少——当然要受限于 token 预算。
调度受4个硬性约束限制:
最大并发请求数
self.max_num_running_reqs = scheduler_config.max_num_seqs
token budget 单步最多计算多少 token(所有请求之和),控制 GPU 计算量上限
self.max_num_scheduled_tokens = (
self.scheduler_config.max_num_scheduled_tokens # 优先用这个
if self.scheduler_config.max_num_scheduled_tokens
else self.scheduler_config.max_num_batched_tokens # fallback
)
模型最大序列长度
self.max_model_len = model_config.max_model_len
是否还有空闲的KV Cache blocks
如上图所示,vLLM的每轮推理都会基于token level调度,后续的执行的输入都是类似input_ids这种打平的token数组,即Flattened。(这里的调度逻辑是在Tokenize之后, 本文后续的num_sched_tokens都对应上图中的total_num_scheduled_tokens)
vLLM启动时会申请显存 kv_cache shape为:[num_layers, 2, num_blocks, block_size, num_kv_heads, head_dim],每一层key_cache和value_cache的shape都是[num_blocks, block_size, num_kv_heads, head_dim]。
block_table会记录每个请求分配到的物理块ID,shape为[max_num_reqs, max_num_blocks_per_req],请求0分配了块[5, 8, 12],请求1分配了块[3, 7]
为什么
slot_mapping和block_table不需要num_layers维度?如果一个请求的第 25 个 Token 被分配到了物理块 ID 为
8的位置,那么在模型的第 0 层到第 31 层,这个 Token 的 KV 值都会被存储在物理块8的相同位置。
而推理过程中,通过slot_mapping告诉kernel 把新产生的 KV 写到哪个 slot,通过block_table告诉 kernel 去哪些物理 block 读取 KVCache。
# slot_idx对应token对应的最终存储位置: slot_idx = block_idx * block_size + block_offset
const int64_t slot_idx = slot_mapping[token_idx];
const int64_t block_idx = slot_idx / block_size; // 计算块索引
const int64_t block_offset = slot_idx % block_size; // 计算块内偏移
比如:
block_size = 16(每个块存储16个token),请求0的block_table为:[5, 8, 12],token位置为25,计算过程:
block_idx = 25 // 16 = 1(属于第1个虚拟块) block_idx = block_table[0, 1] = 8(从页表查找物理块ID) block_offset = 25 % 16 = 9 slot_idx = 8 * 16 + 9 = 137
PagedAttention 的虚拟页表机制解决了显存碎片的问题,极大地提升了 GPU 的显存利用率,是支撑 Continuous Batching 高性能推理的基础。
然而PagedAttention 引入的 block_table 间接寻址机制,打破了一个请求在物理显存上的绝对连续性。当 Attention Kernel 跨越 block 读取历史 KVCache 时,会触发离散访存(Uncoalesced Access),这在底层对 Memory Controller 是非常不友好的,同时也会导致 L2 Cache 的命中率下降,带来一定的带宽折损。当然,系统设计从来都是 Trade-off。
vLLM 通过设置合理的 block_size(默认通常是 16)来缓解这个问题:在一个 block 内部的 16 个 Token 的物理显存依然是连续的,能够保证高效率的合并访存。相比于因显存碎片导致的OOM,牺牲少部分的访存带宽换取整个系统吞吐量(Throughput)的大幅跃升,在 LLM 推理的整体逻辑中是划算的。
对用户输入提示词进行分词并转换为数字表示。目前主流 LLM 几乎全部使用 BPE(Byte Pair Encoding)。
BPE的词表训练方式如下:
Embedding Lookup 本质上是一个查表操作,它将一维的 Token ID 数组[num_sched_tokens]直接转化为 [num_sched_tokens, hidden_size] 的特征矩阵,作为进入大模型真正意义上的数学输入。
[batch_size, seq_len] | [V, hidden_size] | [batch_size, seq_len, hidden_size] |
[num_sched_tokens] | [V, hidden_size] | [num_sched_tokens, hidden_size] |
将 Batch 中所有请求的有效 Token 拼接为一维长向量,彻底消除 Padding。
为了保证大模型在神经网络中的数值稳定性,必须引入特征归一化(Normalization)机制。与传统 LayerNorm 要求同时缩放与平移不同,为了提升计算效率,现代大模型广泛为采用了 RMSNorm :砍掉平移操作,仅沿特征维度对数据进行尺度缩放,能够有效防止前向传播中的方差膨胀与硬件数值溢出,从而极大缓解反向传播时的梯度异常(消失或爆炸)问题,为深层网络的稳定训练提供基础保障。
[num_sched_tokens, hidden_size] | [hidden_size] | [num_sched_tokens, hidden_size] |
[num_sched_tokens, hidden_size] | [hidden_size,qkv_proj_size] | [num_sched_tokens, qkv_proj_size] | 4096 (Q) + 1024 (K) + 1024 (V) = 6144 |
| 备注 | ||||
|---|---|---|---|---|
[num_sched_tokens, hidden_size] | [num_sched_tokens,num_heads,head_dim] | |||
[num_sched_tokens, kv_channels] | [num_sched_tokens,num_kv_heads,head_dim] | |||
[num_sched_tokens, kv_channels] | [num_sched_tokens,num_kv_heads,head_dim] |
注:是一个块对角旋转矩阵,实际实现中通过元素级 sin/cos 运算避免矩阵乘法。
这个阶段的大概流程: QKV 按列拆分 -> 对Q、K进行RoPE计算 -> Q、K、V Reshape多头视角 -> 然后reshape_and_cache按照slot_mapping Scatter写入Paged KVCache。
RoPE 的核心思想就是:用旋转的角度来代表 Token 的位置。 虽然不是矩阵乘法,sin/cos 属于超越函数,在 GPU 上由 SM 内的特殊函数单元(SFU, Special Function Unit)执行。SFU 的吞吐量通常仅为 FP32 ALU 的 1/4。所以VLLM中不在 RoPE kernel实时计算sin/cos ,而是引擎初始化时按模型配置预计算好 cos_sin_cache,shape 为 [max_position_embeddings, rotary_dim]。 RoPE kernel 运行时按 positions 索引出对应的 cos/sin 行,与 Q/K 做逐元素乘加即可。本质是用一份小表(典型大小几 MB 到几十 MB)换掉每次 forward 几百万次的 sin/cos 调用——经典的空间换时间 /访存换算力。
读取请求对应 KV Cache 进行 FlashAttention 计算。
Softmax 的核心原理可以用一句话概括:将一组任意的实数(通常称为 Logits),转化为一套总和为 1 的概率分布,同时放大差异。ps: 这里的为head_dim。
感觉Attention的逻辑是整个LLM推理最复杂的地方了:
Attention 的数学逻辑并不复杂,但是为了提升计算强度,打破内存墙,业界在这里做了非常多的优化,比如FlashAttention;
其他阶段基本都是Token维度打平的,而Attention计算必须要在请求维度进行运算。
特别注意: 在进入Attention kernel之前,需要从全局Flattened视角[num_sched_tokens, ...]切换到请求维度视角[batch_size, ...]。这是因为Attention计算本质上是请求内部的操作,不同请求的KV Cache不能交叉。vLLM/FlashAttention通过cu_seqlens(累积序列长度)数组来实现这种变长序列的请求级隔离,而无需真正Reshape为[batch_size, ...]的规整张量。 这里的K和V Shape算子逻辑上为[batch_size, num_kv_heads, seq_len, head_dim],而不同请求的seq_len肯定不一致。实际上在cuda算子层已经确保了不同请求不会分配到同一个 thread block,即保证这里的运算是请求维度隔离的。
K的Shape为[seq_lens, num_kv_heads, head_dim],包含当前请求所有历史的KCache,seq_lens是所有历史KV对应的Token长度,但物理上,由于Paged Attention机制,这些历史K Cache并非连续存储,而是离散分布在HBM的物理块中(即底层实际Shape为[num_blocks, block_size, num_kv_heads, head_dim])。Kernel在执行时,会通过 block_table 动态间接寻址,在片上SRAM中将其拼凑为逻辑上的连续序列参与计算。
V的Shape为[seq_lens, num_kv_heads, head_dim],包含当前请求所有历史的VCache
Q的Shape为[query_lens, num_heads, head_dim],query_lens是本次Q对应的Token长度
[seq_lens, num_kv_heads, head_dim]变为[seq_lens, num_heads, head_dim]逻辑上等价于将1个KV Head的数据广播给4个Q Head;物理实现中通过索引映射kv_head_idx = q_head_idx // (num_heads // num_kv_heads)避免显存复制。 | ||
[query_lens, num_heads, seq_lens] | ||
[query_lens, num_heads, seq_lens] | ||
V的Shape为 [seq_lens, num_heads, head_dim] -> [query_lens, num_heads, head_dim] |
Attention 的数学逻辑非常清晰,但在大模型推理(尤其是长文本 Prefill 阶段)中,如果严格按照数学公式分步执行,会在 GPU 的 HBM(全局显存)中产生庞大的中间矩阵 (注意力分数)和 (Softmax概率),其 的内存访问量会严重影响性能,即所谓的内存墙问题。
为了打破这个瓶颈,vLLM 默认采用 FlashAttention 及其变体(如 Flash-Decoding)。FlashAttention 的核心思想是 Kernel 融合 (Kernel Fusion) 与 分块计算 (Tiling)。但分块计算面临一个致命的数学障碍:Softmax 操作需要知道全局的最大值和指数和才能计算,这通常要求完整的中间矩阵驻留在显存中。FlashAttention 巧妙地引入了 Online Softmax 算法,通过维护局部的当前最大值(Running Max)和当前指数和(Running Sum),并在读入新块时利用动态缩放因子(Rescaling Factor)对旧结果进行修正。
正是凭借 Online Softmax,FlashAttention 成功解除了全局数据依赖,将从 Q-K 点积、Softmax 到乘 V 的整个逻辑步骤,融合成了一个单一的 CUDA Kernel。在物理执行上,它一块一块地在 SRAM 中完成计算,彻底避免了将庞大的中间矩阵 和 读写 HBM,从而大幅打破了内存墙限制。
Prefill 阶段,seq_lens 和 query_lens 都是num_prompt_tokens。当前 Prompt 计算出的 KV 向量先被 reshape_and_cache_flash 按 slot_mapping 写入 Paged KV Cache,随后 attention kernel 通过block_table 从 cache 中读取 K/V 完成本层 attention 计算;这份写入同时也作为该序列的 KV Cache,供后续 decode 步骤复用。
vLLM v0中prefill和decode 走两条独立分支:prefill 通过
attn_metadata.is_prompt == True调flash_attn_varlen_func,kernel直接读临时的物理上连续的HBM buffer 里的 K/V,不读 cache;decode 走paged_attention_v1/v2,K/V 全部从 cache 读。v1 把两路统一成一次flash_attn_varlen_func调用,K/V 全部走 paged cache + block_table,代价是 prefill 多一次"写 cache → 读 cache"的间接寻址,换来对 prefix cache / chunked prefill 的天然支持。只有v0版本 FlashAttention不读KV Cache,因为 v0 没有 prefix cache / chunked prefill。
V1中即便某些 backend(如 FlashInfer、MLA-sparse)把混合 batch 物理拆分成prefill / decode 两个独立 kernel 调用,prefill 仍然从 paged cache 读 K/V - 因为 v1 需要支持 prefix cache 和 chunked prefill 。
Decode阶段,seq_lens为已计算的历史 token 总数, query_lens为1。
对比Prefill和Decode,可以发现:
query_lens = 1),若无优化,此时QKV_Proj、Attention、O_Proj、MLP全都退化为矩阵向量乘法(GEMV)。虽然计算量不大,但需要把巨大的 KV Cache和模型权重 从 HBM 搬运到 SRAM,非常吃显存带宽,因此Decode阶段是典型的访存密集型(Memory-bound)。而 Continuous Batching 的价值在于在Prefill实现请求内复用模型权重的基础上,进一步通过token-level的调度让多个请求复用模型权重,将 QKV_Proj、O_Proj、MLP 重新变回矩阵乘法,极大地摊薄了读取权重的显存开销。
但是Attention则比较特殊,由于每个请求必须读取自己独占的 KV Cache(无法跨请求共享),而且Decode每次只处理一个 Token,Attention 计算始终无法均摊访存开销。此外,由于当前 Query 必须与所有历史 Key/Value 交互,哪怕有KVCache的存在,其计算与访存量也是随着上下文长度 呈 线性膨胀。这里特别注意其实Prefill阶段每次处理num_prompt_tokens个Token,Attention 计算是可以均摊访存开销的。
ps: 所以其实我们日常所说的Prefill是Compute-bound,Decode是Memory-bound,主要是基于 1. 从 HBM 读取模型权重的访存摊销, 2. 从 HBM 读取模型KV的访存摊销。
[num_sched_tokens, num_heads, head_dim] | [num_sched_tokens, hidden_size] |
| 目标矩阵 | 计算公式 | 输入 维度 | 权重矩阵 维度 | 输出结果维度 | 备注 |
|---|---|---|---|---|---|
[num_sched_tokens, hidden_size] | [hidden_size, hidden_size] | [num_sched_tokens, hidden_size] |
不要推翻重来,而是在原有的基础上学习“修正值”(Delta)。如果没有 ResNet 证明网络可以堆到 100 层以上且不梯度消失,后来的 BERT (24层) 和 GPT (96层+) 根本不敢设计得那么深。它是“Scaling Law”在架构上的物理基础。
| Residual Add | [num_sched_tokens, hidden_size] | [num_sched_tokens, hidden_size] | [num_sched_tokens, hidden_size] |
实际vLLM中,Residual Add会和下一步的RMSNorm融合成一个 CUDA kernel。
对 再进行一次归一化。在 vLLM 中,通常与上一层残差相加(Residual Add)融合,减少 HBM 读写。
通常 intermediate_size 是 hidden_size 的 4 倍左右 (或 8/3 倍)。
[num_sched_tokens, hidden_size] | [hidden_size, intermediate_size] | [num_sched_tokens, intermediate_size] | ||
[num_sched_tokens, hidden_size] | [hidden_size, intermediate_size] | [num_sched_tokens, intermediate_size] |
vLLM 工程实现(Fused Gate/Up)
为了减少 GPU 核函数启动(Kernel Launch)次数并充分利用内存带宽,vLLM 在加载模型时会将 和 按列拼接。这样原本两次中等规模的矩阵乘法,就变成了一次宽矩阵 GEMM。
[num_sched_tokens, hidden_size] | [hidden_size, 2*intermediate_size] | [num_sched_tokens, 2*intermediate_size] |
SiLU(Gate) * Up。vLLM 使用 silu_and_mul kernel 在片上(SRAM)直接输出点乘结果,极大节省了显存带宽。
[num_sched_tokens, intermediate_size] | [intermediate_size, hidden_size] | [num_sched_tokens, hidden_size] |
x = x + FFN_Output
class LlamaMLP(nn.Module):
def __init__(self, config):
super().__init__()
# 通常 intermediate_size 是 hidden_size 的 4 倍左右 (或 8/3 倍)
self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
self.act_fn = nn.SiLU()
def forward(self, x):
# 1. Gate 和 Up 分别进行 Linear 变换
# 2. Gate 经过 SiLU 激活
# 3. 两者相乘 (Element-wise multiplication) -> 这就是 GLU (Gated Linear Unit)
# 4. 最后经过 Down 投影回原维度
return self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
将隐藏层特征映射到整个词表空间,输出未归一化的原始得分,即 Logits。
在前面的 Transformer Block 中,Prefill 阶段的 num_sched_tokens 是所有 Prompt 长度的总和。但实际上vLLM LM Head只需要每个请求序列的最后一个 Token 的特征用来预测下一个词,因此进入LM Head前会执行一次 Gather 操作。(Speculative Decoding、prompt_logprobs除外)
更近一步,Prefill阶段Transformer Block的最后一层,attention 输出是 [num_sched_tokens, hidden_dim],但实际上只需要最后一个 token 的 hidden_state 来做 logits 计算(因为之后没有更多的 layer 需要完整的 hidden_states 了)。所以理论上最后一层的MLP(以及 LayerNorm)可以只对需要采样的 token 计算,节省大量计算。
如果只需要最后一个 token 的生成结果,那么在最后一层(Last Layer):
必须要算的部分:所有 token 的 和 (因为这层的 KV cache 要存下来,供后续 Decode 阶段使用)。 理论上可以只算最后一个 token: 的投影、Attention 的计算(其实退化成了 Decode 阶段的 Flash-Decoding 计算)、LayerNorm、以及庞大的 MLP。
但是vLLM中最后一层还是老老实实把所有 query_lens 都算了一遍,本质上是工程与物理极限的 Trade-off。
| LM Head | [num_reqs, hidden_size] | [hidden_size, vocab_size] | [num_reqs, vocab_size] | num_reqs以 Llama-3-8B 为例, vocab_size 高达 128256,这是一个极庞大的矩阵,vLLM 通常会使用张量并行(TP)按列切分该权重以加速计算。 |
如果用户请求了 logprobs(对数概率),Sampler 会在任何处理之前,先对原始 logits 做一次 log_softmax 快照保存。这样返回给用户的概率分布反映的是模型的真实输出,不受后续 penalties / temperature 等采样参数的干扰。
{
"token": "Hello", // 实际被选中的 token
"logprob": -0.01523, // 该 token 的对数概率 (log_softmax 的结果)
"bytes": [72, 101, ...], // token 的 UTF-8 字节表示
"top_logprobs": [ // 概率最高的前 N 个 token (用户通过 logprobs=N 指定)
{
"token": "Hello",
"logprob": -0.01523 // ln(0.985) ≈ -0.015, 即 98.5% 概率
},
{
"token": "Hi",
"logprob": -4.32185 // ln(0.013) ≈ -4.32, 即 1.3% 概率
}
]
}
在得到原始 Logits 后、进行 Softmax 概率转化前,会经过一系列后处理操作,用于干预最终的生成结果:
"positive" / "negative" / "neutral" 对应的 token。ABCD。每个请求的采样策略可以是Greedy或Random,由用户设置的 temperature 决定
@cached_property
def sampling_type(self) -> SamplingType:
if self.temperature argmax,保存为 greedy_sampled。Temperature Scaling:Logits = Logits / Temperature。
: 拉大分数差距。高分越明显,低分被压得更低。模型变得保守、确定。: 缩小分数差距。分布趋向均匀。模型变得随机、有创造力(但也容易胡说八道)。
对于 的 greedy 请求,此处会用 跳过除法(避免除以零),它们的最终结果由 Step 1 的
greedy_sampled决定。
Argmax-invariant Processors(如 min_p):
min_p是目前唯一的 argmax-invariant 处理器。其规则为:概率不到token最高概率的 min_p倍的 token,全部淘汰。>公式:阈值 = ,低于阈值的 token 得分设为 。
示例(
min_p = 0.1,,阈值 = 0.04):min_p 的核心价值在于自适应:
最高概率的 token 自身永远不会被砍(因为 ),所以 argmax 结果不变。
Top-K 截断:只保留分数最高的 个 Token,其余得分全部设为 。
Top-P Masking:按分数从高到低排序并累加 softmax 概率,保留累积概率刚好超过 的前若干 Token,尾部 Token 得分设为 。
Softmax + Gumbel-Max 采样 确保结果的随机性 公式: ,其中
Gumbel-Max 用加噪求最大值替代了传统的轮盘赌算法。它将原本需要串行计算前缀和的采样过程,转化为完全独立的 element-wise 并行运算,打破了超大词表下的全局数据依赖。
probs = logits.softmax(dim=-1)
q.exponential_()
probs.div_(q).argmax(dim=-1)
最终选择:逐请求按 temperature 决定使用哪个结果
final = where(temperature output得到 Next Token ID,并将其追加到 Input 序列中,进入下一轮循环。
是的,戛然而至。本文主要讲述了经典的大模型推理的全流程,想要真正理解大模型的推理流程理解shape的变化真的很重要。笔者有意克制发散的思维,专注整个推理流程,很多的原理和概念都没有提。后续可以围绕这个基础展开,
比如数学基础:Softmax、RMSNorm原理、RoPE
比如Attention计算遇到了内存墙问题
导致了 DeepSeek MLA/DSA、线性注意力等的产生,本质是减少KVCache的读取;
导致了FlashAttention的产生的,本质上是利用IO Aware、重计算提升计算强度;
比如为什么要MoE scaling law模型不断增大,计算量随参数线性增大,而参数占比的大头又在MLP层,那就把MLP层拆分,美其名曰不同专家,每次执行仅激活部分参数, 其本质就是将现有FFN层拆分成多个,然后增加路由,每次可以激活不同专家组。
比如模型太大了,TP、PP、DP、EP、SP、CP,了解了本文的数据流shape变化,TP理解就变的相当容易。
Column Parallelism (列切分): Fused QKV 和 FFN 的 Fused Gate/Up 都是列切分。例如 QKV 的权重维度在单卡上会变成 [hidden_size, qkv_proj_size / TP_size],输出也相应变成 [num_sched_tokens, qkv_proj_size / TP_size]。
Row Parallelism (行切分): O_Proj 和 FFN 的 Down_Proj 是行切分。权重变为 [hidden_size / TP_size, hidden_size]。计算完成后,通过 AllReduce 算子来聚合跨卡结果。
比如长上下文时, chunked prefill 到 Flash-Decoding 到 SP 本质是不同维度的prompt的拆分,不过是在解决不同问题,chunked prefill 避免超长 Prompt的新请求影响其他请求,无论是正在处理的 decode 请求还是待处理的其他 prefill 请求, Flash-Decoding 通过 KV 维度的切块(Split-K)提升了 Decode 阶段的 SM(流处理器)利用率,SP (Sequence Parallelism) 则是解决单卡显存塞不下超长上下文的问题。(Flash-Decoding,Sequence Parallelism靠Log-Sum-Exp实现跨 SM或者跨卡的归一化)
再来感叹一句,单从推理来看,大模型的原理其实真没那么高深莫测,最难的数学公式怕是要数RoPE了。但是AI Infra的东西真的多~~ 根本学不完,不像agent方向整天重命名上下文工程🐶。(提示词工程、function calling、 rag、mcp、memory、agent开发、skills、openclaw、上下文工程、harness工程、hermes)
| 标准变量名 | 数值 (Llama-3-8B) | 物理含义说明 |
|---|---|---|
num_kv_heads*head_dim)。 | ||
hidden_size + kv_channels*2)。 | ||
intermediate_size*2)。 | ||
| 标准变量名 | 数值 (Llama-3-8B) | 物理含义说明 |
|---|---|---|
LLM是有多层的,以Llama3 8B为例模型的权重组成如下:
| 组件 | 权重名称 | 参数量维度 | 占比 | 本质与说明 |
|---|---|---|---|---|
| Embedding | embed_tokens.weight | [128256, 4096] | ||
| Attention | q_proj | [4096, 4096] | 4 个 Linear 矩阵,无 bias | |
k_proj, v_proj | [1024, 4096] | |||
o_proj | [4096, 4096] | |||
| MLP | gate_proj, up_proj | [14336, 4096] | 3 个 Linear 矩阵,无 bias | |
down_proj | [4096, 14336] | |||
| Input Norm | input_layernorm.weight | [4096] | ||
| Post-Attn Norm | post_attention_layernorm.weight | [4096] | ||
| Final Norm | norm.weight | [4096] | ||
| LM Head | lm_head.weight | [128256, 4096] |
当 KV Cache blocks 不够分配时,调度器会抢占最低优先级的 RUNNING 请求——释放其全部 KV Cache、将 num_computed_tokens 重置为 0、放回 WAITING 队列。被抢占的请求下次被调度时需要重新从头 Prefill。
v0 时代,vLLM 有两种 preemption 策略:
SWAP:把被抢占请求的 KV blocks 从 GPU HBM 搬到 host RAM 暂存,资源宽裕时再 swap in 回 GPU。 RECOMPUTE:直接释放 GPU blocks,重新加入 waiting 队列走prefill 重算一遍。 v1 把 SWAP 路径完全砍掉(
LLM(swap_space=...)已废弃并 warn),Scheduler._preempt_request只保留 RECOMPUTE 一条路。原因:
PCIe 双向搬运 + host RAM 占用的开销,在 FA + 大 batch 下常常比重算一次 prefill 还贵 chunked prefill 让重算可以分多步"追上来",摊平开销 架构上少一条路径,prefix caching / chunked prefill /continuous batching 的交互更干净
def _preempt_request(self, request, ...):
self.kv_cache_manager.free(request) # 释放 KV Cache
request.status = RequestStatus.PREEMPTED
request.num_computed_tokens = 0 # 重置,下次需要重新计算
self.waiting.prepend_request(request) # 放回等待队列
这体现了调度器的核心原则:已在运行的请求优先级高于等待中的请求。
发生抢占意味着 KV Cache 已经非常紧张。此时不应该再引入新的 WAITING 请求,因为那会进一步加剧内存压力,可能触发更多抢占,形成恶性循环。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-08-19
爆火插件让DeepSeek V4 Pro 0813性能拉满,全面超越 Fable 5!
2026-08-18
迈向生产级 AgenticOps:STAROps 如何构建可泛化的根因定位能力
2026-08-18
DeepSeek Harness 能力 Seams 全景:可替换能力清单与三角色
2026-08-18
让 Coding Agent 开始「记住过去」:MemoraX Code 的长期记忆系统
2026-08-18
1M的AI生命 - 解读context
2026-08-17
研究了 DeepSeek Harness 之后,我发现它是冲着干翻 VS Code 来的
2026-08-17
软件僵死,Harness is alive
2026-08-17
DeepSeek 做 Harness → WorkBuddy 的窗口期还能有多久?
2026-06-04
2026-06-10
2026-05-21
2026-05-28
2026-06-22
2026-05-21
2026-05-28
2026-05-27
2026-06-12
2026-06-07
2026-08-17
2026-08-13
2026-08-13
2026-08-06
2026-08-05
2026-08-05
2026-07-31
2026-07-30
欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。
在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。
一、 定义
本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。
会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。
知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。
二、 账号注册与登录
登录方式:本网站支持以下登录方式,您可根据实际情况选择:
微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。
手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。
账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。
实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。
未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。
三、 服务内容与规范
知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。
服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。
禁止行为:您在使用服务时不得实施以下行为:
利用技术手段批量爬取、下载、转存知识库内容;
将知识库内容用于商业目的或未经授权地向第三方传播;
干扰本网站正常运行或侵犯其他用户合法权益;
发布违法违规信息或从事违反公序良俗的活动。
四、 知识产权声明
权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。
有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。
侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。
五、 个人信息保护
我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。
您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。
您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。
六、 免责声明
内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。
不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。
第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。
七、 违约责任
如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。
如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。
八、 法律适用与争议解决
本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。
因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。
九、 其他
本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。
本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。
我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。