多 Token 预测
一次预测多个 token,加速生成
类型: 学习 | 语言: Python | 🏷 前置:《原生稀疏注意力》(本系列第 16 篇)
学习目标
- 理解多 token 预测的原理
- 实现多 token 预测头
- 训练多 token 预测模型
- 评估加速效果
- 分析多 token 预测的权衡
多 token 预测概述
多 token 预测(Multi-Token Prediction)是指模型一次预测多个未来的 token,而不是逐个预测。
标准预测的问题
- 逐个预测:每次只预测下一个 token
- 推理慢:需要多次前向传播
- 串行依赖:无法并行生成
多 token 预测的优势
- 并行生成:一次预测多个 token
- 加速推理:减少前向传播次数
- 更好表示:学习更丰富的上下文表示
实现
基本架构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import torch import torch.nn as nn import torch.nn.functional as F
class MultiTokenPrediction(nn.Module): def __init__(self, vocab_size, d_model, n预测头=4): super().__init__() self.d_model = d_model self.n预测头 = n预测头 self.transformer = Transformer(d_model) self.prediction_heads = nn.ModuleList([ nn.Linear(d_model, vocab_size) for _ in range(n预测头) ]) def forward(self, x): hidden = self.transformer(x) predictions = [] for head in self.prediction_heads: pred = head(hidden) predictions.append(pred) return torch.stack(predictions, dim=1)
|
训练目标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def multi_token_loss(predictions, targets, n预测头=4): """多 token 预测损失""" batch_size, seq_len, vocab_size = predictions.shape total_loss = 0 for i in range(n预测头): pred = predictions[:, i, :-(i+1), :] target = targets[:, i+1:] loss = F.cross_entropy( pred.reshape(-1, vocab_size), target.reshape(-1) ) total_loss += loss return total_loss / n预测头
|
自回归生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @torch.no_grad() def generate_multi_token(model, prompt, max_new_tokens, n预测头=4): """多 token 预测生成""" model.eval() input_ids = tokenizer.encode(prompt, return_tensors="pt") generated = input_ids.clone() while generated.shape[1] < max_new_tokens: predictions = model(generated) next_tokens = [] for i in range(min(n预测头, max_new_tokens - generated.shape[1])): pred = predictions[:, i, -1, :] probs = F.softmax(pred, dim=-1) token = torch.multinomial(probs, 1) next_tokens.append(token) next_tokens = torch.cat(next_tokens, dim=1) generated = torch.cat([generated, next_tokens], dim=1) return generated
|
高级架构
层级预测头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class HierarchicalPrediction(nn.Module): def __init__(self, vocab_size, d_model, n预测头=4): super().__init__() self.d_model = d_model self.n预测头 = n预测头 self.transformer = Transformer(d_model) self.prediction_heads = nn.ModuleList() for i in range(n预测头): head = nn.Sequential( nn.Linear(d_model, d_model // 2), nn.GELU(), nn.Linear(d_model // 2, vocab_size) ) self.prediction_heads.append(head) self.context_fusion = nn.Linear(d_model * n预测头, d_model) def forward(self, x): hidden = self.transformer(x) predictions = [] context = hidden for i, head in enumerate(self.prediction_heads): fused = self.context_fusion( torch.cat([hidden] + [p.argmax(dim=-1).unsqueeze(-1).expand(-1, -1, self.d_model) for p in predictions], dim=-1) ) if predictions else hidden pred = head(fused) predictions.append(pred) return torch.stack(predictions, dim=1)
|
自适应预测长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class AdaptiveMultiTokenPrediction(nn.Module): def __init__(self, vocab_size, d_model, max预测头=8): super().__init__() self.d_model = d_model self.max预测头 = max预测头 self.transformer = Transformer(d_model) self.prediction_heads = nn.ModuleList([ nn.Linear(d_model, vocab_size) for _ in range(max预测头) ]) self.length_predictor = nn.Sequential( nn.Linear(d_model, d_model // 4), nn.GELU(), nn.Linear(d_model // 4, 1), nn.Sigmoid() ) def forward(self, x): hidden = self.transformer(x) length_pred = self.length_predictor(hidden[:, -1, :]) predicted_length = (length_pred * self.max预测头).int().item() predicted_length = max(1, min(predicted_length, self.max预测头)) predictions = [] for i in range(predicted_length): pred = self.prediction_heads[i](hidden) predictions.append(pred) return torch.stack(predictions, dim=1), predicted_length
|
训练策略
渐进式训练
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| def progressive_training(model, train_data, config): """渐进式训练:从 1 个预测头逐渐增加到 n 个""" for epoch in range(config["epochs"]): current_n_heads = min(epoch // config["heads_per_epoch"] + 1, config["max_heads"]) print(f"Epoch {epoch + 1}: 使用 {current_n_heads} 个预测头") for batch in train_data: predictions = model(batch["input_ids"]) predictions = predictions[:, :current_n_heads] loss = multi_token_loss(predictions, batch["input_ids"], current_n_heads) loss.backward() optimizer.step() optimizer.zero_grad()
|
样本权重
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def weighted_multi_token_loss(predictions, targets, n预测头=4): """带权重的多 token 预测损失""" batch_size, seq_len, vocab_size = predictions.shape total_loss = 0 for i in range(n预测头): pred = predictions[:, i, :-(i+1), :] target = targets[:, i+1:] weight = 1.0 / (i + 1) loss = F.cross_entropy( pred.reshape(-1, vocab_size), target.reshape(-1) ) * weight total_loss += loss return total_loss / n预测头
|
评估加速效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| def evaluate_speedup(model_standard, model_multi, test_prompts, n预测头=4): """评估加速效果""" start_time = time.time() for prompt in test_prompts: inputs = tokenizer.encode(prompt, return_tensors="pt") model_standard.generate(inputs, max_new_tokens=100) standard_time = time.time() - start_time start_time = time.time() for prompt in test_prompts: generate_multi_token(model_multi, prompt, max_new_tokens=100, n预测头=n预测头) multi_time = time.time() - start_time speedup = standard_time / multi_time print(f"标准模型时间: {standard_time:.2f}s") print(f"多 token 模型时间: {multi_time:.2f}s") print(f"加速比: {speedup:.2f}x") return speedup
|
质量评估
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def evaluate_quality(model_standard, model_multi, eval_dataset, n预测头=4): """评估生成质量""" ppl_standard = compute_perplexity(model_standard, eval_dataset) ppl_multi = compute_perplexity(model_multi, eval_dataset) samples_standard = [generate(model_standard, p) for p in eval_dataset[:100]] samples_multi = [generate_multi_token(model_multi, p, 100, n预测头) for p in eval_dataset[:100]] bleu_standard = compute_bleu(samples_standard, eval_dataset[:100]) bleu_multi = compute_bleu(samples_multi, eval_dataset[:100]) print(f"标准模型 - 困惑度: {ppl_standard:.4f}, BLEU: {bleu_standard:.4f}") print(f"多 token 模型 - 困惑度: {ppl_multi:.4f}, BLEU: {bleu_multi:.4f}")
|
权衡分析
精度-速度权衡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| def analyze_tradeoff(n预测头_list, eval_data): """分析不同预测头数量的权衡""" results = [] for n_heads in n预测头_list: model = MultiTokenPrediction(vocab_size, d_model, n_heads) train_model(model, train_data) perplexity = compute_perplexity(model, eval_data) speedup = measure_speedup(model, n_heads) results.append({ "n_heads": n_heads, "perplexity": perplexity, "speedup": speedup, "efficiency": speedup / perplexity }) print(f"预测头数: {n_heads}") print(f" 困惑度: {perplexity:.4f}") print(f" 加速比: {speedup:.2f}x") print(f" 效率: {speedup / perplexity:.4f}") return results
|
内存使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| def analyze_memory_usage(n预测头_list): """分析内存使用""" results = [] for n_heads in n预测头_list: model = MultiTokenPrediction(vocab_size, d_model, n_heads) n_params = sum(p.numel() for p in model.parameters()) memory_mb = n_params * 4 / 1024 / 1024 results.append({ "n_heads": n_heads, "n_params": n_params, "memory_mb": memory_mb }) print(f"预测头数: {n_heads}") print(f" 参数量: {n_params:,}") print(f" 内存: {memory_mb:.2f}MB") return results
|
最佳实践
- 选择合适的预测头数量:通常 2-4 个预测头效果最好
- 使用渐进式训练:从 1 个预测头开始,逐渐增加
- 平衡精度和速度:根据应用需求选择
- 监控生成质量:确保多 token 预测不降低质量
总结
多 token 预测通过一次预测多个 token,显著加速了自回归生成。关键组件包括多个预测头、训练目标和生成策略。需要权衡预测头数量、生成速度和质量。
下一步
下一课将介绍 DualPipe 并行,优化流水线并行。
📚 本文改编自 AI Engineering from Scratch(MIT License · 作者 Rohit Ghumare),中文内容来自官方中文镜像。原课程共 503 课 · 20 阶段 · 免费开源,教程网站见 aiengineeringfromscratch.com。