从工程角度彻底拆解 LLM 的"输入→处理→输出"全过程


全局流程一览

你输入的文字
    ↓
① Tokenization     文字 → Token ID 数字序列
    ↓
② Embedding        数字 → 高维向量
    ↓
③ Transformer      向量 → 深度处理(注意力 × N层)
    ↓
④ 输出层           最终向量 → 每个Token的概率分布
    ↓
⑤ 采样策略         概率分布 → 选出下一个Token
    ↓
⑥ 循环生成         重复④⑤直到结束
    ↓
⑦ Detokenization   Token ID → 文字输出

第一步:Tokenization(分词)

文字不是直接处理的,要先切成 Token

Token 不等于"字"或"词"

import tiktoken
enc = tiktoken.get_encoding("cl100k_base")  # GPT-4 的编码器

text = "Hello, 你好,FreeSWITCH!"
tokens = enc.encode(text)

# 结果:
# tokens = [9906, 11, 57668, 53901, 3922, 11, 12Frees, WITCH, 0]
# 注意:英文单词可能1个词=1个token
#       中文通常1-2个字=1个token
#       生僻词/专有名词可能被拆开

Tokenization 可视化

输入:  "大模型理解语义"

Tokenization:
  "大" → 5927
  "模" → 6835  
  "型" → 578
  "理解" → 10951    ← 常见词组合为一个Token
  "语义" → 11229

输入:  "FreeSWITCH"

Tokenization:
  "Free" → 11828
  "SW"   → 9854
  "IT"   → 1222
  "CH"   → 5870   ← 生僻词被拆成多个子词

为什么这样设计?

字符级别:词汇表太大,序列太长
词语级别:未登录词(新词/专业词)无法处理
Token级别(BPE算法):
  ✅ 词汇表可控(约 32k~100k)
  ✅ 未知词可拆分子词处理
  ✅ 平衡了序列长度和表达能力

第二步:Embedding(向量化)

每个 Token ID → 一个高维向量(如 4096 维)

# 概念上的映射(实际是矩阵查表)
embedding_table = {
    5927:  [0.12, -0.45, 0.78, 0.33, ...],  # "大" 的向量
    6835:  [0.09, -0.41, 0.82, 0.29, ...],  # "模" 的向量
    10951: [0.31,  0.15, 0.55, 0.71, ...],  # "理解" 的向量
    # ... 几万个Token各有一个向量
}

位置编码(Position Encoding)

模型本身不知道词的顺序,需要显式加入位置信息:

  "狗咬人"  ≠  "人咬狗"

向量 = Token向量 + 位置向量

位置0的向量: [sin(0/10000^0), cos(0/10000^0), ...]
位置1的向量: [sin(1/10000^0), cos(1/10000^0), ...]
...

这样模型就能区分同一个词在不同位置的含义

第三步:Transformer 处理(核心)

这是理解发生的地方,多层堆叠的注意力机制

整体结构

输入向量序列  [v1, v2, v3, v4, v5]
      ↓
  Layer 1:  Multi-Head Attention → Feed Forward
      ↓
  Layer 2:  Multi-Head Attention → Feed Forward
      ↓
  Layer 3:  Multi-Head Attention → Feed Forward
      ↓
  ...(GPT-4 约 96 层)
      ↓
输出向量序列  [v1', v2', v3', v4', v5']
(每个向量都融合了全局上下文信息)

核心:Self-Attention 自注意力

每个 Token 向其他所有 Token "询问":你和我有多相关?

句子:"我用 Python 写了一个爬虫"

处理 "爬虫" 这个词时:
  对 "我"     的注意力权重: 0.05
  对 "用"     的注意力权重: 0.08
  对 "Python" 的注意力权重: 0.52  ← 高!Python爬虫强关联
  对 "写"     的注意力权重: 0.21
  对 "了"     的注意力权重: 0.04
  对 "一个"   的注意力权重: 0.06
  对 "爬虫"   的注意力权重: 0.04
注意力计算公式:

Attention(Q, K, V) = softmax(QKᵀ / √d_k) × V

Q(Query):我想要什么信息?
K(Key):  我能提供什么信息?
V(Value):我实际的信息内容

通俗理解:
  Q × K  → 计算相关度得分(点积)
  softmax → 得分变成概率(归一化)
  × V    → 加权融合各Token的信息

Multi-Head:多角度理解

单个注意力头 = 从一个角度看关系
多头注意力   = 同时从多个角度看

"苹果很甜" 这句话:

Head 1(语法关系):苹果 ←主语→ 甜
Head 2(语义关系):苹果 → 水果 → 味道 → 甜
Head 3(指代关系):苹果指的是食物还是品牌?
Head 4(情感极性):甜 → 正面评价

8~96个头的结果拼接 → 更丰富的表示

Feed Forward:知识存储层

注意力层 = 信息"路由"和"融合"
前馈层   = 知识"存储"和"激活"

研究发现:FFN 层的神经元存储了具体知识
  某个神经元激活 → "这里在讨论编程"
  某个神经元激活 → "这是Python语法"
  某个神经元激活 → "需要调用函数知识"

FFN = 一个巨大的模糊键值存储
      输入向量 → 激活相关知识神经元 → 输出增强向量

各层学到的东西不同

底层(Layer 1~10):
  → 语法、词法、基本句式结构
  → "这是名词" "这是动词短语"

中层(Layer 11~50):
  → 语义关系、实体识别
  → "这是一个Python函数" "这里有个Bug"

高层(Layer 51~96):
  → 抽象推理、任务理解、意图判断
  → "用户想要我帮他调试代码"
  → "需要解释错误原因并给出修复方案"

第四步:输出层(概率计算)

最后一层向量 → 词汇表上的概率分布

# 最终输出是一个巨大的概率向量
vocab_size = 100000  # GPT-4 词汇表大小

# 经过线性层 + softmax
logits = final_vector @ output_matrix   # [vocab_size]
probs  = softmax(logits)

# 结果示例(预测下一个Token):
{
  "def":      0.312,   # 最高概率
  "class":    0.218,
  "import":   0.156,
  "print":    0.089,
  "for":      0.043,
  "你好":      0.0001,
  "苹果":      0.00001,
  # ... 10万个Token各有一个概率
}

第五步:采样策略(如何选Token)

有了概率分布,怎么选下一个词?策略很关键!

Temperature(温度)

# Temperature 控制概率分布的"尖锐"程度

原始概率:def(0.31) class(0.22) import(0.16) ...

Temperature = 0.1(接近贪心,确定性强):
  def(0.92) class(0.06) import(0.02)
  → 几乎必选 def,输出稳定可预测
  → 适合:代码生成、数学计算

Temperature = 1.0(原始分布):
  def(0.31) class(0.22) import(0.16)
  → 正常随机采样
  → 适合:通用对话

Temperature = 1.5(更随机):
  def(0.18) class(0.17) import(0.16) ...
  → 更多样化但可能不准确
  → 适合:创意写作、头脑风暴

Top-P(核采样)

# 只从累计概率达到 P 的候选集中采样

Top-P = 0.9:
  def(0.31) → 累计 0.31
  class(0.22) → 累计 0.53
  import(0.16) → 累计 0.69
  print(0.09) → 累计 0.78
  for(0.07) → 累计 0.85
  while(0.05) → 累计 0.90  ← 截止,只在这6个中采样

  剩余 99994 个Token被直接排除

Top-K

# 只从概率最高的 K 个候选中采样
Top-K = 50 → 每次只在概率最高的50个Token中选择

第六步:自回归循环生成

每次只生成一个Token,然后把它加回输入,循环往复

初始输入:  "用Python写一个快速排序"

第1步:
  输入 → [用, Python, 写, 一个, 快速, 排序]
  预测 → "def"(概率最高)
  输出:  def

第2步:
  输入 → [用, Python, 写, 一个, 快速, 排序, def]
  预测 → "quick"
  输出:  def quick

第3步:
  输入 → [..., def, quick]
  预测 → "_"
  输出:  def quick_

...持续循环...

最终输出:
  def quick_sort(arr):
      if len(arr) <= 1:
          return arr
      pivot = arr[len(arr) // 2]
      ...
这就是为什么LLM:
  ✅ 生成是流式的(一个词一个词出来)
  ✅ 不能修改已生成的内容
  ❌ 不能提前知道自己要说多长
  ❌ 有时会"说到一半跑偏"

软件开发场景的特殊机制

System Prompt 的作用

┌─────────────────────────────────────────────┐
│ System: 你是一个资深Python工程师,            │
│         代码需要包含类型注解和注释             │  ← 设定角色和约束
├─────────────────────────────────────────────┤
│ User: 写一个二分查找函数                      │  ← 用户输入
├─────────────────────────────────────────────┤
│ Assistant:                                  │  ← 模型续写这里
└─────────────────────────────────────────────┘

System Prompt 本质上是:
  在高层Transformer层激活特定的"角色知识"神经元
  让后续所有生成都在这个上下文约束下进行

代码理解:为什么能读懂代码?

# 训练数据包含 GitHub 上数亿行代码
# 模型学会了代码的语义结构:

def calculate_tax(income: float) -> float:
    """计算个人所得税"""
    if income <= 5000:
        return 0
    ...

模型从大量代码中学会了:
  ✅ 函数名 calculate_tax → 和税收相关
  ✅ 参数 income → 收入,float类型
  ✅ 5000 → 可能是起征点(结合上下文)
  ✅ 整体逻辑 → 阶梯税率模式

为什么能发现 Bug?

输入代码:
  for i in range(len(arr)):
      for j in range(len(arr)):  ← 模型注意力锁定这里
          if arr[j] > arr[j+1]:  ← j+1 可能越界!
              arr[j], arr[j+1] = arr[j+1], arr[j]

模型在高层 Attention 中:
  "j+1" 的注意力 → 关联到 "range(len(arr))" 
  → 激活"数组越界"相关神经元
  → 生成:"当 j = len(arr)-1 时,j+1 越界"

工程实践:开发者需要知道的

Context Window(上下文窗口)

模型处理的Token总数是有上限的:

GPT-3.5:  4k  tokens  ≈  3000 汉字
GPT-4:    128k tokens ≈  96000 汉字
Claude 3: 200k tokens ≈  150000 汉字

超出上限:
  ❌ 早期内容被"遗忘"(滑动窗口)
  ❌ 或直接报错

实践建议:
  代码审查:一次不要贴太多代码
  长文档:先摘要再提问
  长对话:重要信息要在每轮重申

Prompt 工程的本质

好的Prompt = 激活正确的"知识神经元" + 约束输出空间

对比:
  ❌ 差的Prompt: "帮我写代码"
     → 激活方向模糊,输出不可控

  ✅ 好的Prompt: 
     "用Python3,使用类型注解,
      写一个处理CSV文件的函数,
      需要处理编码异常,
      返回DataFrame,
      给出使用示例"
     → 精确激活:Python + 类型注解 + CSV + 异常处理 + Pandas 知识域

Temperature 在开发中的选择

场景                    推荐Temperature
─────────────────────────────────────
代码生成/补全              0.0 ~ 0.2
Bug修复                   0.0 ~ 0.3
技术文档撰写               0.3 ~ 0.5
代码注释生成               0.2 ~ 0.4
架构方案建议               0.5 ~ 0.7
头脑风暴/创意命名           0.7 ~ 1.0

完整流程总结

开发者输入:
"这段Python代码有什么Bug?
 def div(a, b): return a/b"
        ↓
① Tokenize:
   ["这", "段", "Python", "代码", "有", "什么",
    "Bug", "def", "div", "(", "a", ",", "b", ")",
    ":", "return", "a", "/", "b"]
        ↓
② Embedding:每个Token → 4096维向量
        ↓
③ Transformer(96层):
   底层:识别Python语法结构
   中层:理解这是一个除法函数
   高层:理解用户在问Bug → 激活"代码审查"模式
         注意力聚焦到 "a/b" → 激活"除零错误"知识
        ↓
④ 输出层:计算每个Token的概率
        ↓
⑤ 采样(Temperature=0.2):确定性输出
        ↓
⑥ 自回归生成:
   "这" → "段" → "代码" → "存在" → "除零" → "错误" ...
        ↓
最终输出:
"这段代码存在除零错误(ZeroDivisionError)。
 当 b=0 时程序会崩溃。
 修复方案:
 def div(a, b):
     if b == 0:
         raise ValueError('除数不能为零')
     return a / b"

本质的一句话

LLM 的"理解"= 将文字压缩成高维向量,在注意力机制的动态路由下激活相关知识,再通过逐Token采样将知识解压为文字输出

它不是在"查答案",而是在用数十亿参数构成的巨大函数,将你的输入映射到最可能的输出空间——这个映射过程,就是我们所说的"理解与生成"。