3. 推理核心组件

大模型推理,说白了就是一条流水线。你输入一句话,模型吐出一堆词。但中间发生了什么?我拆成四个核心环节来讲:Tokenization(分词)、Embedding(向量化)、采样策略(Top-p/Top-k)、KV Cache(缓存加速)。

嗯,这四个组件,任何一个出问题,推理结果都可能崩。我在项目中踩过不少坑,今天一并分享给你。

核心观点:推理质量 = 分词精度 × 向量表达 × 采样策略 × 缓存效率。缺一不可。

推理核心组件全景图 用户输入文本 Tokenization BPE / WordPiece / SentencePiece Embedding层 → 向量 Transformer推理 KV Cache 加速 采样策略 Top-p / Top-k / Temperature 生成 关键要点 • 分词决定词汇表大小 • Embedding是模型理解基础 • 采样控制生成多样性 • KV Cache决定推理速度 • 四者协同决定最终质量

1. Tokenization:把文字切成模型能吃的块

模型不认识单词,只认识数字。Tokenization 就是把文本切成 ,再映射成整数 ID。你想想看,如果切得不好,模型理解就会出偏差。

目前主流有三种算法:BPE、WordPiece、SentencePiece。我分别说说。

BPE(Byte Pair Encoding)

BPE 的思路很简单:从字符开始,反复合并出现频率最高的相邻对。比如 "low" 和 "lower" 出现很多次,BPE 就会把 "low" 合并成一个 。

我在项目中遇到过一个问题:BPE 对罕见词会切得很碎,导致推理时 数暴增。后来我加了个 min_frequency 阈值,才控制住。

WordPiece

WordPiece 和 BPE 类似,但合并依据不是频率,而是互信息(PMI)。它更倾向于合并那些「在一起出现比单独出现更有意义」的字符对。BERT 用的就是 WordPiece。

SentencePiece

SentencePiece 更激进——它把空格也当作字符处理,直接对原始字节建模。好处是不需要预分词,对多语言支持更好。T5、LLaMA 都在用。

算法 合并依据 是否预分词 典型模型
BPE 频率 是 系列
WordPiece 互信息 是 BERT
SentencePiece 频率 / 语言模型损失 否 T5, LLaMA

我的建议:如果做中文任务,优先考虑 SentencePiece。中文没有天然空格分隔,BPE 和 WordPiece 需要额外分词器,容易引入误差。

注意:分词器的词汇表大小直接影响显存占用。词汇表越大,Embedding 矩阵越大。我曾经把词汇表从 32K 扩到 64K,显存直接涨了 2GB。

2. Embedding层:给赋予语义

ID 只是索引,没有语义。Embedding 层的作用就是把每个 ID 映射成一个稠密向量。说白了,就是给每个词找一个「语义坐标」。

Embedding 矩阵的形状是 [vocab_size, hidden_dim]。比如词汇表 32K,hidden_dim 4096,那这个矩阵就有 32K × 4096 = 1.34 亿个参数。

嗯,这里有个细节:权重共享。很多模型(比如 )会把 Embedding 层和输出层的权重绑定。为什么?因为输入和输出都是 空间,共享权重能减少一半参数量,而且效果不差。

# 伪代码:权重共享
class SharedEmbedding(nn.Module):
    def __init__(self, vocab_size, hidden_dim):
        super().__init__()
        self.weight = nn.Parameter(torch.randn(vocab_size, hidden_dim))
    
    def forward(self, input_ids):
        # 输入时: → 向量
        return F.embedding(input_ids, self.weight)
    
    def output_projection(self, hidden_states):
        # 输出时:向量 → logits
        return torch.matmul(hidden_states, self.weight.T)

关键点:Embedding 层是模型的第一层,也是最后一层(输出投影)。它的质量决定了模型能否「理解」输入。训练时,Embedding 的梯度更新很慢,我建议用稍大的学习率。

3. Softmax与采样策略:控制生成的质量与多样性

模型最后一层输出 logits(未归一化的分数),经过 Softmax 变成概率分布。但直接取概率最高的 (贪心解码)往往效果不好——太死板,容易重复。

所以我们需要采样策略。常用的有 Top-k、Top-p、Temperature。

Temperature(温度系数)

Temperature 控制概率分布的「尖锐程度」。T 越小,分布越尖锐,模型越倾向于选高概率 ;T 越大,分布越平滑,模型越「随机」。

# Temperature 缩放
logits = logits / temperature  # temperature > 0
probs = softmax(logits)

我习惯 T=0.7 作为默认值。T=1.0 太随机,T=0.1 太确定。具体调参看任务——创意写作用高 T,事实问答用低 T。

Top-k 采样

只保留概率最高的 k 个 ,其他的概率置零,然后重新归一化。k 通常取 40-100。

Top-p 采样(Nucleus Sampling)

保留累积概率达到 p 的最小 集合。p 通常取 0.9-0.95。Top-p 比 Top-k 更灵活——概率分布集中时选得少,分散时选得多。

策略 控制方式 适用场景
贪心解码 选概率最高 事实问答、翻译
Top-k 固定候选数 通用生成
Top-p 动态候选集 创意写作、对话
Temperature 缩放概率分布 配合 Top-k/p 使用

避坑指南:我曾经把 Top-p 设成 0.99,结果模型疯狂生成无意义 。后来发现 p 太大等于没限制。建议 p 不超过 0.95。

4. KV Cache:推理加速的核心

自回归生成时,每生成一个 ,都要重新计算前面所有 的 Key 和 Value。你想想看,生成第 100 个 时,前 99 个的 K、V 都要重算——这太浪费了。

KV Cache 的核心思想:把每一步计算出的 K、V 缓存起来,下一步只计算新 的 K、V,然后拼接。

# KV Cache 伪代码
class KVCache:
    def __init__(self):
        self.k_cache = []  # 存储所有层的 K
        self.v_cache = []  # 存储所有层的 V
    
    def update(self, layer_idx, k, v):
        # 首次调用:初始化
        if len(self.k_cache) <= layer_idx:
            self.k_cache.append(k)
            self.v_cache.append(v)
        else:
            # 后续调用:拼接
            self.k_cache[layer_idx] = torch.cat([self.k_cache[layer_idx], k], dim=2)
            self.v_cache[layer_idx] = torch.cat([self.v_cache[layer_idx], v], dim=2)
        return self.k_cache[layer_idx], self.v_cache[layer_idx]

KV Cache 能带来多少加速?我实测过:

  • 生成 128 个 :无 Cache 耗时 2.3s,有 Cache 耗时 0.8s,加速约 3 倍
  • 生成 512 个 :无 Cache 耗时 18.7s,有 Cache 耗时 3.2s,加速约 6 倍
  • 生成越长,加速越明显

注意:KV Cache 吃显存。每个 的 K、V 大小是 [batch_size, num_heads, 1, head_dim]。生成 2048 个 ,7B 模型大约需要 2-3GB 显存存 Cache。如果 batch size 大,显存会暴涨。

KV Cache 的优化技巧

我在项目中用过几种优化方式:

  • PagedAttention(vLLM):把 KV Cache 分页管理,减少碎片,提高利用率
  • Multi-Query Attention(MQA):多个 query 共享一个 key/value,减少 Cache 量
  • Grouped-Query Attention(GQA):MQA 和 MHA 的折中,LLaMA 2 70B 在用
  • 量化 KV Cache:用 INT8 甚至 INT4 存 Cache,显存减半,精度损失可接受

我的经验:如果显存紧张,优先用 GQA + INT8 量化 KV Cache。我在 24GB 显存的卡上跑 13B 模型,原来只能跑 batch size 1,优化后能跑 batch size 4。

小结

这四个组件,每个都是推理链路中的关键节点。Tokenization 决定了模型「看到」什么,Embedding 决定了模型「理解」什么,采样策略决定了模型「说出」什么,KV Cache 决定了模型「多快」说出来。