# 03 算子详解

算子就是神经网络图中的“动作”。先区分两种语言：

- **模型语言**：例如 Conv、Concat、PSA，描述数学关系和 tensor 形状。
- **硬件语言**：例如 line buffer、bank、DMA、UOP，描述怎样在有限 SRAM 和时钟周期中完成它。

## 1. 卷积家族

### Conv 1x1

对每个像素，把输入通道向量乘以一组权重并求和：

```text
y[oc] = sum(x[ic] * w[oc, ic]) + bias[oc]
```

它不看邻居像素，主要改变通道数。硬件最适合让 banked SRAM 连续供给 HWC16，MAC 阵列并行处理多个输出通道。`conv_w8a8_banked_engine.sv` 的真实 first-Conv tile 对拍证明了一个受限窗口的计算路径。

### Conv 3x3

对当前像素周围的 3x3 邻域和所有输入通道求和。它能提取边缘、纹理和局部形状。硬件使用滑动窗口和行缓冲，避免把每个窗口复制成巨大的 im2col 矩阵。padding 和 stride 决定窗口怎样移动。

### Depthwise Conv 3x3

每个通道各自做 3x3 卷积，不与其他通道混合。计算量较小，但通道级并行和边界处理更敏感，通常交给 `depthwise_conv_w8a8_pixel.sv` 这样的专用路径。

### Bias、累加和 Requant

W8×A8 的很多乘积先累加成 INT32；不能每个乘法后就截断，否则误差会很大。bias 也先以 INT32 加入，最后使用 scale、multiplier 和 shift 把结果压回 A8：

```text
y_a8 = clip(round(acc_int32 * mult / 2^shift), -128, 127)
```

这一步决定量化网络和 FP32 网络的数值差异，必须和 Python reference、RTL 的舍入与饱和规则一致。

## 2. 激活与逐元素算子

### SiLU

SiLU 的公式是 `y = x * sigmoid(x)`。它让网络保留小的负值，同时对大正值接近线性。硬件通常用有限大小的 LUT 近似 sigmoid，而不是放一个昂贵的浮点指数单元。项目已有 HWC16 LUT RTL 和 Python reference，但全图量化校准仍未签核。

### Sigmoid

把 logit 变成 0 到 1 的概率：`sigmoid(x) = 1/(1+e^-x)`。检测头用它得到类别或置信度。定点实现要决定输入范围、LUT 分段、输出精度和饱和策略。

### Add / residual

两个形状相同的 tensor 逐元素相加，常用于残差连接。如果两个输入的 A8 scale 不同，不能直接把字节相加，必须先做 scale 对齐或在 accumulator 域相加。

### Multiply

逐元素相乘，常见于门控、SiLU 或 scale。硬件需要考虑乘积位宽以及结果重新量化。

## 3. 空间与布局算子

### MaxPool 5x5 / SPPF

MaxPool 在窗口内取最大值，扩大感受野且不需要乘法。SPPF 会连续使用 5x5 池化并拼接多路结果，让网络看到更大的上下文。硬件用 line buffer 保存窗口，比较器树替代乘法阵列。

### Resize nearest 2x

最近邻放大把一个输入像素复制到 2x2 输出位置。它不需要新数值，只需要地址生成器重复读取。项目用 `hwc16_nearest2x_address.sv` 处理这种 view，尽量不复制整块 tensor。

### Concat

沿通道维把多个 tensor 接起来。数学上没有计算，难点是不同来源的地址和通道对齐。项目倾向用 segmented view 和 descriptor 表示拼接，避免真的搬运整块数据。

### Reshape / Transpose / Split / Slice

这些操作经常只改变“怎么看数据”的方式。若内存顺序允许，可以做成零字节 alias；若需要改变通道或 token 顺序，就必须由地址生成器或 DMA 真正搬运。图上的 view 不等于硬件上免费，最终要由 compiler 和 RTL 共同证明。

## 4. PSA 注意力算子

PSA 可以先按三个步骤理解：

1. **QK dot**：query 向量和 key 向量做点积，得到相似度分数。
2. **Online Softmax**：每行做减最大值、指数近似和归一化，得到概率。
3. **AV**：用概率加权 value 向量，得到输出 O。

项目把 Q/K/V/O 分块放在 SRAM 中，不物化完整的 `256x256` probability matrix。`psa_qk_dot32.sv` 使用 32-lane 点积，`psa_online_softmax_q8.sv` 维护行级 max/sum，`psa_av_mac64.sv` 做共享的 64-value 累加。

PSA 目前已有真实形状可 elaboration 的 scheduler、QKVO SRAM adapter 和原语回归；INT32 O 的完整 requant、QK/V scheduler 全连接和全图 traversal 仍是缺口。

## 5. 检测头算子

### Box decode

把网络输出的距离或坐标编码转换成 `x1,y1,x2,y2` 等真实框坐标。它涉及 stride、网格位置和 letterbox 逆变换，数值格式要和训练/导出配置一致。

### Top-K

网络可能为 5376 个候选产生分数，但最终只需 300 个结果。Top-K 维护一个小候选集合：新分数高于当前最小值就替换它。在线 Top-K 可以避免保存完整 score tensor，显著降低 SRAM 压力。

### Gather / replay store

先保存通过第一阶段筛选的候选行，等 box、class 或跨 die 数据到齐后再 gather。`d3_e2e_tail_replay_store.sv` 用 rank-to-slot 映射保留被准入的行。

## 6. 初学者如何判断一个算子“完成”

至少要有四个层次的证据：

1. Python reference 与边界条件测试。
2. 独立 RTL testbench 的逐元素或逐 token 对拍。
3. 与前后算子连接后的代表性链路对拍。
4. 全模型、代表性图像、资源、时延和异常恢复证据。

当前仓库许多算子已达到前两层，部分达到第三层；不能因为一个 `tb_*.sv` 显示 `TEST PASSED` 就说整个 YOLO26n 已完成。
