并行处理与上下文长度:AI 的架构是如何处理输入的 → 超强多线程
#2025/12/29 #ai
目录
- 1. 并行 → 并行处理,同时开工
- 2. 上下文长度 = 内存缓冲区大小 (
MAX_BUFFER_SIZE) - 3. 每一条“跑道”(计算流)里发生了什么?
- 4. 关键逻辑:虽然都算了,但只用最后一个
- 5. 每个词元的“旅程“
- 6. 总结
1. 并行 → 并行处理,同时开工
以前的老模型(如 RNN)处理文本像写
for循环,必须处理完第 1 个词,才能处理第 2 个,再处理第 3 个……效率很低。

- 直观理解:想象一个巨大的工厂流水线。
- 输入:“
Write an email to Sarah”(5 个词元)。 - 模型会瞬间开启 5 条平行的传送带 → 并行计算
- 每个词元跳上自己的传送带,同时开始向前跑,经过层层神经网络的处理。
- 输入:“
又比如: “Write an email” → 后面有备注
- 流水线 1 处理
"Write" - 流水线 2 处理
"Write an" - 流水线 3 处理
"Write an email"
虽然每个词元有自己的通道,但在
"注意力"步骤时,它们会互相交流信息(比如 “it” 会去问 “Sarah” 指的是谁)。
1.1. 一些疑问❓
① 流水线 2 处理 "Write an" ,其实不对,而是每次都拿到了一个词,但因为注意力掩码机制 , 虽然每次拿到一个词,但可以回头看,所以 效果 和 流水线 2 处理 "Write an" 是一样的
② 但即使是并行的,也得等到所有并行都 resolve ,统一集合后才能进入下一层
③ 流水线 2 可以回头看流水线 1,是否意味着它依赖于流水线 1
- 是“数据依赖”,但不是“执行等待”
- 在下一层时,上一层完全的
Promise.all了,已经都存档到,去读就好了 - 所以是: 它们是在“读存档”,而不是“等实时计算”
2. 上下文长度 = 内存缓冲区大小 (MAX_BUFFER_SIZE)
你可能经常听说某个模型支持 “4K” 或 “128K” 上下文。这在代码里其实就是一个最大数组长度或缓冲区限制。
- 定义:
- 模型的上下文长度(Context Length),就是它
同时能开启多少条“计算跑道”。 - 上下文长度 (Context Length) 就是模型一次性能并行处理的最大 Token 数量。
- 模型的上下文长度(Context Length),就是它
- 硬件限制:
- 如果模型是 4K(4096)上下文,意味着它有 4096 条跑道。
- 如果你输入 4000 个词,它能全部并行处理。
- 如果你输入 5000 个词,多出来的 1000 个就没有跑道可用,模型就会“报错”或必须截断输入。
[!info]
注意:这个“总 token 数”通常指 输入 + 已经生成的输出 一起算在内,举个例子
- 第 1 轮:模型看
["Write", "an", "email"](3个词),算出第 4 个词。- 第 10 轮:模型看
["Write", "an", "email", ... + 9个新词](12个词),算出第 13 个词。- 第 N 轮:模型必须看着 (原本的输入 + 之前所有生成出来的词),才能保证写出的文章前后通顺。
- 所以,占用的内存(Token 数)= 你给它的 + 它刚写出来的。
3. 每一条“跑道”(计算流)里发生了什么?
- 起点(Input):词元不是光秃秃进去的,它携带了两个信息打包成的向量:
- 身份信息(我是什么词,比如 “Cat”)
- 位置信息(我是第几个词,比如 index=5)
- 注:这一步叫“嵌入 + 位置编码”。
- 过程(Process):
- 数据流经神经网络层 → 有很多很多层
- 虽然大家各跑各的道,但在中间的某些站点(称为注意力层),
- 不同的跑道之间会“
交换情报”(比如“it”这个词的跑道会去问“dog”那个跑道的含义),但总体架构是独立的流。
- 终点(Output):
每条跑道的尽头,都会输出一个新的向量,代表处理后的结果- 最后得到一个
"处理过的向量",包含了上下文信息,用于预测下一个词元。
另一个向量作为模型处理的结果出现,如下图

图 3-9:每条处理流接收一个向量作为输入,并生成一个大小相同的
最终结果向量
不太明白,请看后文 每个词元的旅程
4. 关键逻辑:虽然都算了,但只用最后一个
这是最容易让程序员困惑的地方:
- 现象:
- 当你输入 “Write an email”(假设 3 个 Token)让模型写信时
- 模型其实并行输出了 3 个结果向量。
- 取舍:
- 但是,为了预测下一个字是什么
- 我们只关心最后一条流(对应 “
email” 那条)的输出向量。
- LM Head 的工作:
- 这个最后的向量会被送入
LM Head(语言建模头),转换成概率,猜出下一个词是 “to”。
- 这个最后的向量会被送入
既然只用最后一个,为什么前面的一起算了?是浪费算力吗?
- 不是浪费:
- 虽然我们不直接用前面流的输出结果,但最后一条流在计算时,需要通过
- 注意力机制(Attention)去“回头看”前面的流。
- 虽然我们不直接用前面流的输出结果,但最后一条流在计算时,需要通过
5. 每个词元的“旅程“
5.1. 什么是“一个向量作为输入“?
当一个词元(比如 “Write”)进入 Transformer 时:
# 假设模型的隐藏维度是 3072
输入词元: "Write"
↓
首先转换成向量(嵌入向量):
[0.23, -0.45, 0.78, ..., 1.23] # 一共 3072 个数字
这个向量包含:
- 词元本身的嵌入(这个词的基本含义)
- 位置信息(它在句子中的位置)
5.2. 什么是“最终结果向量“?
这个词元的向量会穿过所有的 Transformer 层(比如 32 层),每一层都会对它加工处理:
输入向量 [3072 个数字]
↓ 穿过 Transformer 块 1(注意力 + 前馈网络)
中间向量 [3072 个数字] # 融合了其他词元的信息
↓ 穿过 Transformer 块 2
中间向量 [3072 个数字]
↓ ...
↓ 穿过 Transformer 块 32
输出向量 [3072 个数字] # 这就是"最终结果向量"
关键点:
- 大小相同:输入是
3072 个数字,输出也是 3072 个数字 - 内容完全不同:
- 虽然数量一样,但经过处理后的值已经大变样了
5.3. 为什么说“大小相同“?
# 以 Phi-3 模型为例
输入矩阵形状: [1, 5, 3072]
批次 词元数 隐藏维度
输出矩阵形状: [1, 5, 3072]
批次 词元数 隐藏维度(大小没变!)
每个词元位置:
- 输入:一个 3072 维的向量
- 输出:还是一个 3072 维的向量
这就像:
- 你把一张 A4 纸(输入向量)放进复印机
- 出来的还是一张 A4 纸(输出向量)
- 但纸上的内容已经改变了(融合了上下文信息)
5.4. 这个“最终结果向量“有什么用? → 只用最后一个输出向量 来做预测
对于生成任务,只有最后一个词元的输出向量会被送到“语言建模头“去预测下一个词:
句子: "Write an email"
↓ ↓ ↓
输出向量1 输出向量2 输出向量3
↓
只有这个被用来预测下一个词!
↓
语言建模头
↓
预测结果: "to" (概率 40%)
但是! 前面词元的输出向量虽然不直接用于预测,但它们在注意力步骤中被后面的词元“回头看“,从而影响最终的预测。
5.5. 用代码理解
# 实际的数据流
input_text = "Write an email"
tokens = tokenizer(input_text) # 分词: ["Write", "an", "email"]
# 每个词元变成一个向量(3072 维)
input_vectors = embed(tokens)
# 形状: [3, 3072]
# 3 个词元
# 每个词元 3072 个数字
# 穿过 32 层 Transformer
output_vectors = transformer_layers(input_vectors)
# 形状还是: [3, 3072] ← 大小相同!
# 还是 3 个词元
# 每个还是 3072 个数字
# 但具体的数值已经完全改变了
# 只用最后一个词元的输出向量预测下一个词
next_token_logits = language_head(output_vectors[2]) # 取第 3 个
# 输出: [32064] ← 词表中每个词元的分数
6. 总结
| 概念 | 通俗解释 |
|---|---|
| 输入向量 | 词元进入模型时的数字表示(包含词义 + 位置信息) |
| 最终结果向量 | 词元经过所有 Transformer 层处理后的数字表示 |
| “大小相同” | 输入是 N 个数字,输出也是 N 个数字(比如都是 3072 个) |
| “作为模型处理的结果” | 这个向量融合了上下文信息,变得更“聪明“了 |
| 只用最后一个 | 虽然每个词元都有输出向量,但只有最后一个直接用于预测下一个词 |