Appearance
Layer3 EncoderLayer
覆盖
Encoder.forward()循环中单次attn_layer(x, ...)调用。EncoderLayer 由两个残差块组成:第一残差块(Self-Attention + Add & Norm)和第二残差块(FFN + Add & Norm)。注意力由AttentionLayer完成,细节见 [[04-Layer4-AttentionLayer]]。
§1 在父层中的位置
Encoder.forward() 循环体:
python
for attn_layer in self.attn_layers:
x, attn = attn_layer(x, attn_mask=attn_mask, tau=tau, delta=delta)
attns.append(attn)- 输入:
x (3, 9, 8) - 输出:
x (3, 9, 8),attn = None(iTransformer 不输出 attention weights)
§2 I/O 接口定义
| 参数 | shape(toy) | 含义 |
|---|---|---|
输入 x | (3, 9, 8) | B=3,token_count=9,d_model=8 |
输出 x | (3, 9, 8) | 两轮残差更新后的 token 表示 |
输出 attn | None | output_attention=False 时为 None |
§3 顺序图
§4 语义分组图
§5 逐步精读
§5.0 完整原始代码
python
def forward(self, x, attn_mask=None, tau=None, delta=None):
new_x, attn = self.attention(x, x, x, attn_mask=attn_mask, tau=tau, delta=delta)
x = x + self.dropout(new_x)
y = x = self.norm1(x)
y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1))))
y = self.dropout(self.conv2(y).transpose(-1, 1))
return self.norm2(x + y), attn§5.1 宏观逻辑
用小例子(B=1,token=3,d_model=4,d_ff=8)串起来:
① 自注意力残差块
x (1,3,4) → AttentionLayer(Q=K=V=x) → new_x (1,3,4)
x = x + dropout(new_x) ← 残差
y = x = norm1(x) ← norm1 的输出同时赋给 x 和 y(共享引用)
② FFN 残差块
y (1,3,4)
→ y.transpose(-1,1) (1,4,3) ← Conv1d 需要 (B,C,L) 顺序
→ conv1(8→16, k=1) (1,8,3)
→ gelu + dropout (1,8,3)
→ conv2(16→8, k=1) (1,4,3)
→ .transpose(-1,1) (1,3,4) ← 还原 (B,L,C) 顺序
→ dropout (1,3,4) = y 新值
return norm2(x + y), attn ← x 仍是 norm1 后的值,加上 FFN 的 y
shape 全程 (1,3,4) 不变。为什么 y = x = norm1(x) 看起来奇怪?
这是 Python 链式赋值:先算右边 norm1(x) 得到新 tensor,然后把 x 和 y 同时指向它。效果是 x 变成了 norm1 后的值,y 也是这个值的引用(共享内存)。后续 FFN 在 y 上操作,不会影响 x,因为 FFN 的赋值 y = ... 让 y 指向新的 tensor,不再与 x 共享。
y = x = expr 的引用语义
y = x = expr 的引用语义Python 链式赋值
y = x = norm1(x)不是先让 y=x 再让 x=expr。执行顺序是:(1) 计算norm1(x)→ 新 tensor T;(2) 将x绑定到 T;(3) 将y绑定到 T。此后x和y指向同一对象,但一旦y = 新操作(y)被执行,y就指向新对象,x不变。因此"共享开始,FFN 操作后分离"是这段代码的准确描述。
§5.2 步骤一:AttentionLayer(自注意力)
python
new_x, attn = self.attention(x, x, x, attn_mask=attn_mask, tau=tau, delta=delta)self.attention 是 AttentionLayer 实例,Q=K=V=x,全是同一输入 → Self-Attention。
- 输入:
x (3, 9, 8) - 输出:
new_x (3, 9, 8),attn = None
语义:9 个变量 token 两两计算注意力分数,每个 token 更新为其他 token(包括自身)的加权组合。捕捉跨变量相关性。
详见 [[04-Layer4-AttentionLayer]]。
§5.3 步骤二:第一残差 + norm1
python
x = x + self.dropout(new_x)
y = x = self.norm1(x)第一行 — 残差加法:
self.dropout(new_x):训练时随机置零部分元素,推理时恒等x = x + dropout(new_x):残差连接,将注意力输出加回输入
输入 x (3,9,8),残差后仍 (3,9,8)。
toy 数值(batch=0,token=0 即 var_0):
x[0, 0, :] 原始值:[x0, x1, ..., x7](8维,var_0 的嵌入)
new_x[0, 0, :]:AttentionLayer 输出,8维,编码了 var_0 对其他变量的关注结果
x[0, 0, :] += new_x[0, 0, :] ← 加法在8个维度上逐元素执行第二行 — LayerNorm:
输出仍是 (3,9,8),x 和 y 同时指向这个新 tensor。
§5.4 步骤三:FFN — Conv1d(8→16→8, kernel=1)
python
y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1))))
y = self.dropout(self.conv2(y).transpose(-1, 1))Conv1d(kernel=1) = Position-wise Linear
Conv1d(in_channels=8, out_channels=16, kernel_size=1)对序列每个位置独立做 8→16 的线性变换,等价于nn.Linear(8, 16)但输入格式不同。Conv1d 要求(B, C, L),因此需要 transpose 把 d_model 轴移到 C 位置。
y.transpose(-1, 1) 的含义:
y (3, 9, 8) → transpose(-1, 1) 交换 dim=1 和 dim=-1(即 dim=2)→ (3, 8, 9)
- 维度语义从
(B, token_count, d_model)变为(B, d_model, token_count) - token_count=9 现在在 L(长度)位置,d_model=8 在 C(通道)位置
- Conv1d 把"通道"8 升到 16,"长度"9 不变
逐行 shape 追踪(全局 toy):
y (3, 9, 8) ← norm1 输出
y.transpose(-1, 1) (3, 8, 9) ← Conv1d 需要 (B,C,L)
conv1(...) (3, 16, 9) ← Conv1d(8→16, k=1),每个 token 独立升维
activation(gelu) (3, 16, 9) ← 元素级 GELU
dropout (3, 16, 9) ← 随机置零
= y 新值
conv2(y) (3, 8, 9) ← Conv1d(16→8, k=1),降维回 d_model
.transpose(-1, 1) (3, 9, 8) ← 还原 (B, token_count, d_model)
dropout (3, 9, 8) ← 随机置零
= y 最终toy 数值(batch=0,token=0):
y[0, :, 0](transpose 后)= var_0 token 的 8 维向量,作为 C 维输入
conv1 对这 8 个数做 8→16 线性变换(8组不同的权重向量各做一次点积+bias)
gelu 激活:gelu(x) = x × Φ(x),负值被压缩近零
conv2 对 16 个数做 16→8 线性变换
transpose 后 y[0, 0, :] = var_0 FFN 输出的 8 维向量§5.5 步骤四:第二残差 + norm2
python
return self.norm2(x + y), attnx:步骤二 norm1 后的值,(3, 9, 8)y:FFN 输出,(3, 9, 8)x + y:残差相加,(3, 9, 8)self.norm2:LayerNorm(8),归一化,(3, 9, 8)
toy 数值(batch=0,token=0):
x[0, 0, :] = norm1 后的 8 维向量(约零均值,近单位方差)
y[0, 0, :] = FFN 后的 8 维向量
(x + y)[0, 0, :] = 逐元素相加
norm2((x+y))[0, 0, :] = 再次归一化,作为本层最终输出§6 初始化参数(toy)
EncoderLayer.__init__() 中的关键参数(iTransformer 传入值):
| 参数 | 值 | 含义 |
|---|---|---|
d_model | 8 | token 维度 |
n_heads | 4 | 注意力头数 |
d_ff | 16 | FFN 隐层维度(= d_model × 2,toy 中取 16) |
dropout | 配置值 | Dropout 概率 |
activation | 'gelu' | FFN 激活函数 |
conv1 | Conv1d(8, 16, 1) | FFN 升维层 |
conv2 | Conv1d(16, 8, 1) | FFN 降维层 |
norm1, norm2 | LayerNorm(8) | 两处归一化 |
attention | AttentionLayer(...) | Self-Attention,见下层 |
§7 下钻子组件
| 组件 | 输入 shape | 输出 shape | 下层文档 |
|---|---|---|---|
AttentionLayer | (3, 9, 8) | (3, 9, 8) | [[04-Layer4-AttentionLayer]] |