一个 Token 的完整旅程:从输入到输出,揭秘大模型内部工作流程 🚀

导读:你是否好奇,当你在聊天框输入一句话时,大模型内部发生了什么?本文将带你追踪一个 token 从进入模型到生成回复的完整旅程,揭开 LLM 的神秘面纱!
在这里插入图片描述


🗺️ 前言:为什么要理解 Token 的旅程?

在之前的三篇文章中,我们深入拆解了 Transformer 的各个组件(Attention、FFN、Embedding 等)。但单独看每个部分就像只看汽车的零件——你知道发动机怎么工作,却不知道整辆车如何行驶。

真正的 LLM 是一条精密的流水线:文字进去,概率出来,一步步生成流畅的回复。

今天,我们将以第一视角追踪一个 token 的完整旅程,看看它从输入到输出都经历了什么!这将帮助你:

  • 🎯 建立全局认知:理解各组件如何协同工作
  • 🔧 定位优化点:知道性能瓶颈在哪里
  • 💡 启发创新思路:在合适的环节尝试改进

准备好了吗?让我们开始这场奇妙的旅程!✨


📥 第一站:输入层 - 文字的"数字化" transformation

1.1 Tokenizer:把文字切成模型能理解的碎片

当你输入 “我想吃苹果” 时,模型并不能直接理解这些汉字。首先需要经过 Tokenizer(分词器) 把文本切成一个个 token(词元)。

在这里插入图片描述

原始输入: "我想吃苹果"
         ↓ Tokenizer
Token序列: ["我", "想", "吃", "苹果"]
Token ID:  [1024, 2048, 3072, 5432]
三种 Tokenization 策略对比
策略 示例 优点 缺点
字符级 “我”|“想”|“吃”|“苹”|“果” 词汇表小 序列长,效率低
词语级 “我”|“想”|“吃”|“苹果” 语义清晰 未登录词问题
子词级 (BPE/WordPiece) “我”|“想”|“吃”|“苹果” 平衡灵活 实现复杂

现代大模型的选择:绝大多数采用 子词级别 的 Tokenization(如 BPE、Unigram LM),在词汇量(通常 30K-100K)和表达能力之间取得平衡。

💡 小知识:GPT-4 的词表大小约 100K,LLaMA 约 32K。中文 token 通常比英文更"贵",因为一个汉字的信息密度更高。

1.2 Embedding:查表获得语义向量

每个 token ID 会通过 Embedding 层 查找对应的向量表示:

# 伪代码示例:Embedding 过程
vocab_size = 32000      # 词表大小
embedding_dim = 4096    # 向量维度(LLaMA-2-7B)

# 1. 查找 token 对应的 ID
token_id = vocab["苹果"]  # 例如: 5432

# 2. 从 embedding 矩阵中查表
embedding_matrix = nn.Embedding(vocab_size, embedding_dim)
token_embedding = embedding_matrix[token_id]  # 得到 4096 维向量

这个 4096 维的向量 包含了 “苹果” 这个词的语义信息:

  • 它是一种水果 🍎
  • 也可以是科技公司 📱
  • 上下文会决定具体含义
Embedding 的本质

可以把 Embedding 看作一个巨大的查找表

Token ID → [0.12, -0.45, 0.78, ..., 0.33] (4096 维)

训练过程中,这个表会不断更新,让语义相近的词向量距离更近(比如 “苹果” 和 “梨” 的向量会很相似)。

在这里插入图片描述

1.3 RoPE 位置编码:告诉模型"顺序很重要"

Transformer 有个致命缺陷:它本身不知道 token 的顺序

"我想吃苹果" 和 "苹果吃我想" 
对纯 Attention 来说是一样的 ❌

所以需要加入 位置编码(Position Encoding)

# 最终输入向量 = Token Embedding + 位置编码
final_input = token_embedding + position_encoding(position=3)
为什么选择 RoPE?

现代大模型(LLaMA、PaLM 等)普遍使用 RoPE(Rotary Position Embedding,旋转位置编码),原因是:

特性 传统位置编码 RoPE
相对位置感知 ❌ 弱 ✅ 强
外推能力 ❌ 差 ✅ 好
计算复杂度 O(n) O(n)

RoPE 的核心思想:通过旋转矩阵给不同位置的 token 注入角度信息,让模型能更好地捕捉相对位置关系。

在这里插入图片描述

到这一步,模型拿到的是同时包含 “什么意思” 和 “在什么位置” 的输入向量,可以进入核心加工厂了!


🔄 第二站:Transformer Block - 核心加工厂

接下来,向量会进入堆叠的 Transformer Block(可能有 32 层、64 层甚至更多)。每个 Block 主要做三件事:

在这里插入图片描述

2.1 Attention:Token 之间互相"交流信息"

Self-Attention(自注意力机制) 让每个 token 都能看到其他 token,建立上下文联系:

输入: "我想吃苹果"

Attention 过程:
"我" ←→ "想"  (代词找动词)
"想" ←→ "吃"  (助动词找主要动词)
"吃" ←→ "苹果" (动词找宾语)
Attention 的三大职责
  1. 从别人那里拿信息:每个 token 收集其他 token 的相关信息
  2. 建立依赖关系:识别语法结构(主谓宾、定状补)
  3. 带上下文回家:融合全局信息形成新的表示
关键技术优化

在实际工程中,Attention 有多个重要优化:

技术 作用 效果
KV Cache 缓存历史计算的 K 和 V 推理速度提升 2-3 倍
FlashAttention 优化内存访问模式 显存减少 20%,速度提升 3 倍
Multi-Head 多头并行关注不同方面 表达能力大幅提升
Grouped Query 分组共享 Key/Value 平衡性能和效果

🔍 深入理解:Attention 的输出仍然是 4096 维向量,但现在每个向量都融合了整个句子的上下文信息。

2.2 FFN(前馈网络):对每个 Token 单独"深加工"

Attention 之后,每个 token 会经过 Feed-Forward Network(前馈神经网络)

# 简化的 FFN 结构(LLaMA 使用 SwiGLU)
def forward(x):
    # 第一步:升维(4096 → 11008)
    x = Linear1(x)  
    
    # 第二步:非线性激活(SwiGLU)
    x = swiglu_activation(x)  
    
    # 第三步:降维(11008 → 4096)
    x = Linear2(x)  
    
    return x
FFN 的核心职责
  • 信息再加工:把 Attention 收集的混合信息进行深度处理
  • 非线性变换:引入复杂的函数映射能力
  • 知识存储模型大部分参数其实在这里! 💪

⚠️ 惊人事实:在 LLaMA-2-7B 中,FFN 占据了约 67% 的参数(约 47 亿参数),而 Attention 只占约 25%。

前沿优化:MoE(Mixture of Experts)

传统 FFN 每次推理都要激活所有参数,而 MoE 采用稀疏激活:

传统 FFN:所有样本都用同一组参数
MoE FFN:不同样本路由到不同的"专家"网络

优势

  • 参数量可以扩大 10 倍(如 Mixtral 8x7B)
  • 推理成本只增加 2-3 倍
  • 效果显著提升

2.3 残差连接 + LayerNorm:让深层网络能"跑稳"

# Pre-Norm 架构(现代模型主流)
output = attention_input + Attention(LayerNorm(attention_input))
output = ffn_input + FFN(LayerNorm(ffn_input))

这两个组件至关重要:

组件 作用 不用的后果
残差连接 让信息跨层传递,防止梯度消失 超过 10 层就训练不动
LayerNorm 稳定每层的输入分布 训练震荡,难以收敛

⚠️ 没有这两样,几十层甚至上百层的网络,梯度早崩了!这是 Transformer 能堆这么深的关键秘诀

2.4 层层递进:从字面到任务的抽象升级

Transformer Block 会堆叠很多次,不同层学习不同抽象级别的信息:

第 1-8 层(低层):学字面信息
├─ 词性标注(名词、动词...)
├─ 局部语法(短语结构)
└─ 基础语义(词义消歧)

第 9-24 层(中层):学句法结构
├─ 从句关系
├─ 指代消解("他"指的是谁)
└─ 逻辑关系(因果、转折)

第 25-32 层(高层):学任务目标
├─ 整体语义理解
├─ 意图识别
└─ 任务特定特征(翻译、问答等)

🎯 类比理解:就像阅读理解,先看字词(低层),再看句子结构(中层),最后理解主旨(高层)。


📤 第三站:输出层 - 隐藏状态变概率

3.1 Final Hidden State → Logits

经过所有 Transformer Block 后,最后一个 token 的 final hidden state 会被送到输出层:

# 假设最后一个 token 的隐藏状态
last_hidden_state = [0.23, -0.56, 0.89, ..., 0.12]  # 4096 维

# 通过线性层映射到词表大小
vocab_size = 32000
logits = Linear(last_hidden_state)  # 输出 32000 维向量

32000 维的 logits 对应词表中每个词的"得分"。

3.2 Softmax:变成概率分布

import torch
probabilities = torch.softmax(logits, dim=-1)

假设词表里只有几个词,预测 “我想吃___” 后面可能得到:

概率分布:
- "苹果":8%
- "饭":15%
- "面":5%
- "火锅":25%
- "烧烤":12%
- "..." :35%(其他所有词)

📊 可视化想象:就像一个有 32000 个格子的长条,每个格子代表一个词,高度代表被选中的概率。

3.3 解码策略:决定选哪个词

有了概率分布,还需要解码策略来选择下一个 token。不同的策略会产生不同的效果:

策略 原理 适用场景 示例
Greedy 永远选概率最大的 确定性任务(翻译) “火锅”(25%)
Top-k 从前 k 个中随机采样 需要多样性 从 top 50 中选
Top-p 累积概率达 p 的集合中采样 对话生成 p=0.9 动态调整
Temperature 调节概率分布的平滑度 控制创造性 T=0.7 平衡
Temperature 的神奇效果
# Temperature 如何改变概率分布
def apply_temperature(logits, temperature):
    scaled_logits = logits / temperature
    return softmax(scaled_logits)
  • T → 0:趋近 Greedy,输出确定但可能单调
  • T = 1:原始概率分布
  • T → ∞:趋近均匀分布,完全随机

🎲 实际应用:ChatGPT 默认 T≈0.7,代码生成 T≈0.2,创意写作 T≈1.0。

3.4 Autoregressive:一个字一个字"滚"出来

选完 token 后,把它加回上下文,继续预测下一个:

第 1 步: 输入 "我想吃" 
        → 预测 → 选中 "火锅" (25%)

第 2 步: 输入 "我想吃火锅" 
        → 预测 → 选中 "," (40%)

第 3 步: 输入 "你想吃火锅," 
        → 预测 → 选中 "你" (18%)

第 4 步: 输入 "你想吃火锅,你" 
        → 预测 → 选中 "要" (35%)

... 循环直到遇到结束符 </s>

🎬 聊天回复就是这样一个字一个字"滚"出来的! 这就是为什么长回复需要更多时间。


🔧 工程视角:知道模块职责,才知道优化哪里

理解完整流程后,我们可以针对性地优化各个模块:

性能优化地图

模块 瓶颈 优化方向 典型技术 加速效果
Attention 计算复杂度 O(n²) KV Cache、FlashAttention PagedAttention、Ring Attention 2-5 倍
FFN 参数量大 MoE、量化 Mixtral、AWQ、GPTQ 2-10 倍
Embedding 词表查找 共享嵌入、量化 - 1.2 倍
解码 串行生成 并行解码、推测采样 Speculative Decoding 2-3 倍
内存 KV Cache 占用 分页管理、压缩 vLLM、PagedAttention 显存减半

实际案例分析:vLLM 的优化策略

vLLM 是目前最快的推理引擎之一,它的核心优化:

  1. PagedAttention:像操作系统管理内存一样管理 KV Cache
  2. Continuous Batching:动态批处理不同长度的请求
  3. Kernel 融合:减少 GPU kernel 启动开销

效果:相比 HuggingFace Transformers,吞吐量提升 24 倍!🚀


🎯 总结:建立一张工程地图

通过追踪一个 token 的完整旅程,我们建立了这样的认知地图:

在这里插入图片描述

📝 输入文本
   ↓
🔪 Tokenizer 切分(BPE/WordPiece)
   ↓
📊 Embedding + RoPE 位置编码
   ↓
🔄 [Transformer Block × N 层]
   ├─ 🔍 Attention(交互信息,建立上下文)
   ├─ ⚙️ FFN(深度加工,存储知识)
   └─ 🔗 残差 + LayerNorm(稳定训练)
   ↓
📤 输出层线性映射(4096 → 32000)
   ↓
📈 Softmax 转概率分布
   ↓
🎲 解码策略选择(Greedy/Top-p/Temperature)
   ↓
✅ 下一个 Token
   ↓
🔄 (循环直到生成结束符)

理解全貌的四大价值

  1. 📍 定位问题:知道每个环节的职责,bug 出现时能快速定位
  2. 🚀 性能优化:找到瓶颈所在(通常是 Attention 或 FFN)
  3. 🔬 创新改进:在合适的地方尝试新方法(如在 FFN 用 MoE)
  4. 💰 成本控制:理解为什么某些操作"贵"(如长上下文的 KV Cache)

💡 延伸阅读与实战建议

深入学习的经典论文

  1. Attention 机制:《Attention Is All You Need》(2017) - Transformer 开山之作
  2. RoPE 位置编码:《RoFormer: Enhanced Transformer with Rotary Position Embedding》
  3. FlashAttention:《FlashAttention: Fast and Memory-Efficient Exact Attention》
  4. MoE 架构:《Switch Transformers: Scaling to Trillion Parameter Models》
  5. 推理优化:《vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention》

动手实践建议

初级:可视化理解
# 使用 transformers 库查看中间层输出
from transformers import AutoModel, AutoTokenizer

model = AutoModel.from_pretrained("bert-base-chinese")
tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")

inputs = tokenizer("我想吃苹果", return_tensors="pt")
outputs = model(**inputs, output_hidden_states=True)

# 查看每一层的隐藏状态
for i, hidden_state in enumerate(outputs.hidden_states):
    print(f"Layer {i}: shape={hidden_state.shape}")
中级:性能分析
# 使用 pytorch profiler 分析瓶颈
import torch.profiler

with torch.profiler.profile(
    activities=[torch.profiler.ProfilerActivity.CUDA],
    record_shapes=True
) as prof:
    model.generate(inputs)

print(prof.key_averages().table(sort_by="cuda_time_total"))
高级:自定义优化
  • 实现简单的 KV Cache
  • 尝试不同的解码策略
  • 实验量化(INT8/INT4)效果

🏷️ 标签

#LLM #Transformer #DecoderOnly #Attention #FFN #AI原理 #大模型 #技术科普 #推理优化 #干货 #NLP #深度学习


📢 互动话题

💬 你在实践中遇到过哪些推理性能瓶颈?
💬 你对哪个环节的优化最感兴趣?

欢迎在评论区留言讨论!如果觉得这篇文章对你有帮助,请点赞 👍、收藏 ⭐、转发分享~ 😊

下一篇预告:我们将深入探讨 KV Cache 的实现细节和优化技巧,敬请期待!


作者简介:专注于大模型底层原理与工程优化,致力于让 AI 技术更易懂、更易用。

版权声明:本文为原创内容,转载请注明出处。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐