已训练 Transformer LLM 的输入和输出:篇一
#2025/12/29
目录
核心概念:超级“自动补全”函数
如果把已经训练好的 LLM 看作一个 Python 函数,它不是一个直接把“问题”映射到“答案”的魔法转换器。
本质上,它是一个通过无限循环调用的 next_token_predictor(下一个词元预测器)。
宏观视角:它是个生成器 (Generator)
在应用层(API 调用)看,它像是一个接收字符串并返回字符串的函数:
prompt = "给 Sarah 写封邮件道歉..."
response = model.generate(prompt)
# 输出: "Dear Sarah, I'm writing to..."
但在底层实现逻辑上,它其实是一个循环(Loop),每次只吐出一个词。
💻 伪代码逻辑
def generate_text(prompt):
current_text = prompt
while True:
# 1. 核心步骤:模型接收当前所有文本,预测下一个最可能的词
# 注意:模型每次只能预测 1 个词元!
next_token = model.predict_next_token(current_text)
# 2. 拼接:把生成的词加到输入后面,作为下一次的输入
current_text += next_token
# 3. 终止条件:遇到特殊的“结束符”或达到最大长度
if next_token == "<|endoftext|>":
break
yield next_token # 流式输出给用户看
2. 微观视角:前向传播 (Forward Pass)
文档中的 图3-2 描述了“一次生成一个词元”的过程。我们可以用 Python 的 List 操作来直观理解这个状态变化。
假设用户输入是 "Hello",模型要补全 "world!"。
Step 1: 第一次前向传播
- 输入 (Input):
["Hello"]
- 模型内部:
- 经过几十层神经网络计算…
- 输出 (Output):
- 模型计算出词表中所有词的概率,发现
" world"的概率最高。
- 模型计算出词表中所有词的概率,发现
- 结果:
- 生成词元
" world"
- 生成词元
Step 2: 第二次前向传播 (注意:输入变长了!)
- 输入 (Input):
["Hello", " world"]<– 看这里,上一步的输出变成了这一步的输入 → 这就是自回归
- 模型内部:
- 再次经过几十层神经网络计算…
- 输出 (Output):
- 预测下一个词,发现
"!"概率最高。
- 预测下一个词,发现
- 结果:
- 生成词元
"!"
- 生成词元
Step 3: 第三次前向传播
- 输入 (Input):
["Hello", " world", "!"]
- 输出 (Output):
"<EOS>"(End of Sequence,结束标记)
- 结果:
- 循环终止。
3. 数据结构视角:输入输出到底是什么?
作为工程师,你可能关心数据的 Shape(形状)和 Type(类型)
📥 输入 (Input)
并不是直接吃 String,而是吃 Token IDs(整数列表)。
- 形式:
Tensor(Batch_Size, Sequence_Length)
- 例子:
[101, 7592, 2088, ...](/post/7lwt26p9tq.html#101,-7592,-2088,-)(对应 “Write an email…”)
📤 输出 (Output)
模型输出的不是直接的“词”,而是概率分布(Logits)。
- 形式:
Tensor(Batch_Size, Sequence_Length, Vocabulary_Size)
- 含义:
- 对于序列中的每一个位置,模型都会输出一个它是词表中任意一个词的概率。
- 关键点:
- 在生成文本时,我们只关心最后一个位置的概率分布,因为那代表了“下一个词”是谁。
# 假设词表大小是 50000
# 输入是 5 个词
logits = model(input_ids)
# logits.shape 是 [1, 5, 50000]
# 我们只取最后一个词的预测向量
next_token_logits = logits[0, -1, :] # shape: [50000]
# 从这 50000 个分数里选最高的(或采样),得到下一个词的 ID
next_token_id = argmax(next_token_logits)
4. 总结
三点:
- 无状态变有状态:
- 虽然模型架构本身是无状态函数(
y = f(x)),- 但在生成过程中,我们通过把
y拼回到x后面,手动维护了一个“状态”,构成了自回归(Autoregressive) 循环。
- 但在生成过程中,我们通过把
- 虽然模型架构本身是无状态函数(
- Append 操作:
- LLM 生成文本的过程,本质上就是不断的
List.append()操作。
- LLM 生成文本的过程,本质上就是不断的
- 计算量:
- 生成 100 个词,意味着模型要运行 100 次完整的
前向传播(虽然有 KV Cache 等优化,但逻辑上是运行了 100 次)。 - 这就是为什么生成长文比读取长文要慢得多的原因。
- 生成 100 个词,意味着模型要运行 100 次完整的