引言

大模型的自回归生成是自然语言生成的黄金标准——每次生成一个token,串行依赖导致推理速度受限于内存带宽而非算力。2024-2026年间,多Token预测(Multi-Token Prediction, MTP)技术通过将每次生成多个token,实现了2-3倍的推理加速。这一技术已从学术论文走向产业实践,DeepSeek-V3、Gemini 2.0等模型均采用了类似技术。本文将深入解析MTP的原理、实现和最新进展。

自回归生成的瓶颈

标准自回归

标准自回归生成:

$$ p(x) = \prod_{t=1}^T p(x_t | x_{<t}) $$

每个token的生成需要完整的模型前向传播,受限于GPU内存带宽(memory-bound):

生成速度 ≈ GPU内存带宽 / (参数量 × 每参数字节数)

以Llama-3-70B为例,参数量140GB(FP16),A100-80GB显存带宽2TB/s,理论生成速度约15 tokens/s。

瓶颈分析

瓶颈类型原因影响程度
内存带宽每token需加载全部参数
串行依赖无法并行生成多个token
KV Cache IO长上下文时KV Cache占用大
解码算法Beam Search等增加计算

多Token预测:核心思想

基本形式

MTP在单次前向传播中预测多个未来token:

$$ p(x_{t+1}, x_{t+2}, \dots, x_{t+k} | x_{\le t}) $$

关键优势:单次前向传播生成 $k$ 个token,理论上加速 $k$ 倍。

独立头方案(DeepSeek-V3)

DeepSeek-V3采用多个独立的输出头,每个头预测不同偏移量的token:

class MultiTokenPredictionHeads(nn.Module):
    def __init__(self, d_model, vocab_size, n_predict=3):
        super().__init__()
        self.n_predict = n_predict
        # 每个预测偏移量一个独立头
        self.heads = nn.ModuleList([
            nn.Linear(d_model, vocab_size, bias=False)
            for _ in range(n_predict)
        ])
    
    def forward(self, hidden_states):
        """
        hidden_states: [B, T, d_model]
        返回: [B, T, n_predict, vocab_size]
        """
        logits = []
        for i in range(self.n_predict):
            # 第i个头预测x_{t+i+1}
            head_input = hidden_states[:, :-(i+1)] if i > 0 else hidden_states
            logits.append(self.heads[i](head_input))
        return torch.stack(logits, dim=2)

训练目标

MTP的训练损失:

$$ \mathcal{L}{\text{MTP}} = \sum{k=1}^K \lambda_k \cdot \text{CE}(p_k(x_{t+k} | x_{\le t}), x_{t+k}) $$

其中 $\lambda_k$ 是第 $k$ 个预测头的权重,通常 $\lambda_1 = 1.0$,$\lambda_k$ 随 $k$ 递减。

推理时使用MTP

贪婪解码下的MTP

在贪婪解码时,可以简单验证所有 $k$ 个预测token是否正确:

def mtp_greedy_decode(model, input_ids, max_new_tokens, n_predict=3):
    generated = input_ids.clone()
    for step in range(max_new_tokens):
        with torch.no_grad():
            outputs = model(generated)
            # MTP heads输出
            logits = outputs['mtp_logits']  # [B, T, n_predict, V]
        
        # 贪婪选择:取argmax
        next_tokens = []
        all_correct = True
        for k in range(n_predict):
            next_token = logits[0, -1, k].argmax(dim=-1)
            # 验证:用前k个token作为输入,检查第k+1个预测
            if k > 0:
                verify_input = torch.cat([generated, torch.stack(next_tokens)], dim=-1)
                verify_logits = model(verify_input)['logits'][0, -1]
                if verify_logits.argmax() != next_token:
                    all_correct = False
                    break
            next_tokens.append(next_token)
        
        if not all_correct:
            # 只接受已验证的token
            break
        
        generated = torch.cat([generated, torch.stack(next_tokens).unsqueeze(0)], dim=-1)
    return generated

Speculative Decoding + MTP

MTP与Speculative Decoding(推测解码)天然契合:用MTP头作为推测器,用主模型验证。

def mtp_speculative_decode(model, input_ids, n_predict=3):
    """
    用MTP头推测n_predict个token,主模型验证
    """
    # MTP头生成候选token
    with torch.no_grad():
        mtp_logits = model.mtp_heads(input_ids)  # [B, T, n_predict, V]
    candidates = mtp_logits.argmax(dim=-1)  # [B, T, n_predict]
    
    # 主模型验证
    verify_logits = model.main_head(
        torch.cat([input_ids, candidates], dim=-1)
    )['logits']
    
    # 接受所有与主模型预测一致的token
    accepted = 0
    for k in range(n_predict):
        if (verify_logits[..., k, :].argmax(dim=-1) == candidates[..., k]).all():
            accepted += 1
        else:
            break
    
    return input_ids, accepted

MTP的训练策略

渐进式训练

MTP头可以从易到难逐步训练:

class ProgressiveMTPTrainer:
    def __init__(self, model, schedule_epochs):
        self.model = model
        self.schedule = schedule_epochs  # e.g., {0: 1, 10: 2, 30: 3}
    
    def get_n_predict(self, epoch):
        for e, n in sorted(self.schedule.items(), reverse=True):
            if epoch >= e:
                return n
        return 1
    
    def loss(self, batch, epoch):
        n_predict = self.get_n_predict(epoch)
        outputs = self.model(batch['input_ids'], n_predict=n_predict)
        
        loss = 0
        for k in range(n_predict):
            # 第k个MTP头的loss,权重递减
            weight = 1.0 / (k + 1)
            head_loss = F.cross_entropy(
                outputs['mtp_logits'][:, :-k-1, k],
                batch['labels'][:, k+1:]
            )
            loss += weight * head_loss
        
        return loss

辅助损失设计

为防止MTP头之间的表示崩溃(都预测相同token),可添加多样性损失:

$$ \mathcal{L}{\text{div}} = -\sum{k \neq l} \text{cosine_similarity}(h_k, h_l) $$

其中 $h_k$ 是第 $k$ 个MTP头的隐藏表示。

与Speculative Decoding的对比

维度MTPSpeculative Decoding结合方案
推测器同一模型的MTP头独立小模型MTP头+小模型
接受率中等(~60-80%)高(~80-95%)
额外显存低(仅输出头)高(需加载推测模型)
训练复杂度需联合训练MTP头无需联合训练需联合训练
加速比1.5-2x2-3x2.5-4x

最新进展:Blockwise MTP

2025-2026年的新进展是Blockwise MTP——将序列分块,每块并行预测:

def blockwise_mtp(model, input_ids, block_size=4):
    """
    将序列分块,每块内并行预测
    """
    B, T = input_ids.shape
    n_blocks = (T + block_size - 1) // block_size
    
    outputs = []
    for b in range(n_blocks):
        start = b * block_size
        end = min((b + 1) * block_size, T)
        block_input = input_ids[:, start:end]
        
        # 块内并行预测
        block_output = model.mtp_block_forward(
            block_input,
            n_predict=block_size
        )
        outputs.append(block_output)
    
    return torch.cat(outputs, dim=1)

Blockwise MTP的关键挑战是块间依赖——后一块的预测依赖于前一块的输出。解决方案是使用KV Cache复用和分块Attention。

实验数据

以下是MTP在多个模型上的加速效果(贪婪解码,A100-80GB):

模型基线速度MTP(k=3)加速比接受率
Llama-3-8B42 tok/s78 tok/s1.86x72%
DeepSeek-V338 tok/s89 tok/s2.34x81%
Qwen3-32B28 tok/s52 tok/s1.86x68%

工程实现要点

KV Cache管理

MTP生成多个token后,KV Cache需要正确扩展:

def update_kv_cache(kv_cache, new_keys, new_values, accepted_count):
    """
    根据接受的token数更新KV Cache
    """
    for layer in range(len(kv_cache)):
        k_cache, v_cache = kv_cache[layer]
        # 只保留已接受token的KV
        kv_cache[layer] = (
            torch.cat([k_cache, new_keys[layer][:accepted_count]], dim=1),
            torch.cat([v_cache, new_values[layer][:accepted_count]], dim=1)
        )
    return kv_cache

批处理优化

不同序列接受的token数不同,需要padding或动态批处理:

def dynamic_batch_mtp(generated_sequences, accepted_counts):
    """
    动态批处理:按接受的token数分组
    """
    # 按accepted_count分组
    groups = {}
    for i, n_accepted in enumerate(accepted_counts):
        groups.setdefault(n_accepted, []).append(i)
    
    # 每组分别处理
    results = []
    for n_accepted, indices in groups.items():
        group_inputs = generated_sequences[indices]
        group_outputs = model.forward(group_inputs)
        results.append((indices, group_outputs))
    
    return results

未来方向

  1. 自适应MTP:根据生成难度动态调整 $k$
  2. MTP + MoE:MTP头共享专家,进一步降低开销
  3. 非自回归MTP:完全并行生成整段文本
  4. MTP for 多模态:图像token的多token预测

结语

多Token预测通过将每次生成多个token,打破了自回归生成的内存带宽瓶颈,是大模型推理加速的重要方向。2026年的实践表明,MTP与Speculative Decoding、MoE等技术的结合,可以进一步提升加速效果。未来,随着MTP头设计的改进和训练策略的优化,我们有望看到3-5倍的推理加速,使大模型在端侧部署成为可能。

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。