> 本课要训练的是一个读取冻结模型内部表示的 SAE。图片、问题和已有答案先经过 MLLM；SAE 学习重构其中一个位置的 hidden state，并把它表示成少量 feature 的组合。下面的交互图是可操作的教学模拟，feature 编号、名称与激活值均不是实测结果。

本课以 **Qwen/Qwen3.5-9B** 和 **google/gemma-4-E4B-it** 为例，目标是为图文指令数据建立可解释的去重与覆盖指纹。先回答最关键的问题：**一个模型、一个 hook、一个共同训练的 SAE，可以让图片位置和文本位置共用一个 65,536 维字典；两个模型不能共用同一个 SAE。字典也没有预先划分“图片区”和“文本区”。**

模型与实现核对日期：2026-11-06。E4B 的模型链接是 [google/gemma-4-E4B-it](https://huggingface.co/google/gemma-4-E4B-it)，不是 E2B。Qwen 家族的 GitHub 地址虽然已经指向 Qwen3.8，本课固定使用 [Qwen3.5-9B checkpoint](https://huggingface.co/Qwen/Qwen3.5-9B)。本文的层位、数据配比与验收门槛是**待验证的实验起点**，没有论文证明它们在这两个模型上最优。

## 01 · 跟随一张图片，走进模型

图中用一张包含两个红苹果、一个杯子和 SALE 字牌的教学插画，配合问题与答案。切换模型、hook 或 token 来源，观察张量形状与 SAE 输入如何改变；点击“播放一次”沿着前向计算逐步查看。**图片不会经过文本 tokenizer；进入语言主干的是视觉编码后的向量，占据序列中的图片位置。**

```mllm-sae
flow
```

主实验选 **C：语言主干完整 block 的输出**。这个位置保留了残差流中的综合信息，适合同时研究视觉内容、提问方式与答案信息。选择它是因为研究目标是整条图文指令的数据选择，而不是单独解释某个 attention 或 MLP 分支。

### 三种 hook，各自回答不同问题

这里的路径相对于 Hugging Face 的完整 `ForConditionalGeneration` 对象，层号均从 0 开始。config 给出尺寸与层数；模块路径要结合实现源码确认，启动时再用 `named_modules()` 和一次前向校验。以下形状来自配置与源码推导，本文没有运行这两个模型的权重。[Qwen 配置](https://huggingface.co/Qwen/Qwen3.5-9B/blob/main/config.json) · [Gemma 配置](https://huggingface.co/google/gemma-4-E4B-it/blob/main/config.json) · [Qwen 实现](https://github.com/huggingface/transformers/blob/fd3cf0140de03f7619ac95b5cfed05b711ff9e22/src/transformers/models/qwen3_5/modeling_qwen3_5.py) · [Gemma 实现](https://github.com/huggingface/transformers/blob/fd3cf0140de03f7619ac95b5cfed05b711ff9e22/src/transformers/models/gemma4/modeling_gemma4.py)

| 位置与目的 | Qwen3.5-9B：模块输出 | Gemma 4 E4B：模块输出 | SAE 想发现什么？ |
| --- | --- | --- | --- |
| **A · 视觉编码器末层，pool/merge 之前** | `model.visual.blocks.26` → `[N_patch, 1152]` | `model.vision_tower.encoder.layers.15` → `[B_img, P_max, 768]`，先去掉 padding patch | 物体、颜色、纹理、区域及视觉语义；保留较细的空间位置。它没有读取问题。可选独立 16K 字典。 |
| **B · 视觉桥接之后，进入 LLM 之前** | `model.visual.merger` → `[N_visual, 4096]` | `model.embed_vision` → `[N_visual, 2560]`，前面已经完成视觉 pooling 与去 padding | 空间压缩后、适配语言主干的视觉信息。它仍没有读取问题。可选独立 32K 字典。 |
| **C · 主配置：完整语言 block 输出** | **`model.language_model.layers.23` → `[B, L, 4096]`** | **`model.language_model.layers.29` → `[B, L, 2560]`** | 从图片 I、问题 Q、答案 Ans 位置分别采样；期待得到视觉偏好、语言偏好、任务相关或跨模态 feature。每个模型独立训练一个 **65,536** 维字典。 |
| 层位对照 | 独立比较 `layers.15 / 23 / 27` | 独立比较 `layers.23 / 29 / 35` | 固定数据、预算、字典宽度与稀疏度；用去重误删率、覆盖和下游 SFT 选择，不凭“越深越好”判断。 |
| 最后归一化输出：额外对照 | `model.language_model.norm` → `[B,L,4096]` | `model.language_model.norm` → `[B,L,2560]` | 预测输出前的表示，分布与中间残差不同，需要另训 SAE 与归一化统计。 |

**“完整 block 输出”不等于 `mlp.down_proj` 输出。** Qwen 的 block 输出已经加回 residual；Gemma 还包含 MLP 后归一化、每层输入分支（PLE）与 layer scalar。若 hook 在 `down_proj` 或 `self_attn.o_proj`，得到的只是分支贡献，不能沿用 C 的解释与统计。

### 两个主干的差异，为什么会影响 hook

| 配置 | Qwen3.5-9B | Gemma 4 E4B |
| --- | --- | --- |
| 视觉编码器 | 27 blocks，hidden = 1,152，patch = 16 | 16 layers，hidden = 768，patch = 16 |
| 视觉压缩 | 2×2 patches 合并，再映射到 4,096；本 checkpoint 无 DeepStack | 3×3 空间 pooling，去 padding，再归一化投影到 2,560 |
| 语言主干 | 32 blocks，hidden = 4,096；每 3 个 Gated DeltaNet 接 1 个 full attention | 42 blocks，hidden = 2,560；每 5 个 sliding attention 接 1 个 full attention |
| 本课 C hook | 第 24 个 block：零基编号 23，full attention block 之后 | 第 30 个 block：零基编号 29，full attention block 之后 |
| 容易混淆之处 | `layers.22` 是 `linear_attn`，没有该层的 `self_attn` | PLE 的 256 维是每层额外输入，不是 residual 宽度；后 18 层存在 KV sharing，模型不是简单重复标准 Transformer |

示例图片为正方形：Qwen 若处理后为 512×512，则有 1,024 个视觉 patch，经 merger 得到 256 个视觉 token。Gemma 按当前 processor 的 `max_soft_tokens=280` 处理正方形时，resize 到 768×768：2,304 个真实 patch，pad 到 2,520；pooling 得到 280 个槽，去掉 24 个 padding 槽后，实际有 256 个视觉 token。**280 是这里的预算，不保证每张图都有 280 个有效 token。** 长宽比变化时数量也会变化，代码必须读实际 mask，不能硬编码图中的 256。[Gemma processor 源码](https://github.com/huggingface/transformers/blob/fd3cf0140de03f7619ac95b5cfed05b711ff9e22/src/transformers/models/gemma4/image_processing_gemma4.py)

## 02 · 模型与 SAE，各自吃什么、输出什么

| 对象 | 输入 | 输出 | 训练时更新谁？ |
| --- | --- | --- | --- |
| MLLM | 图片像素、官方 chat template 编码的 `input_ids`、attention mask，以及 processor 生成的视觉位置/网格元数据 | 完整模型可输出下一 token 的 logits：Qwen `[B,L,248320]`；Gemma `[B,L,262144]`。采集激活时可只运行 `model.model(...)`，省掉 LM head | **全部冻结**，`eval()`、`no_grad()`、`use_cache=False` |
| hook | C 位置当前一次 forward 的 block 输出 `[B,L,d]` | 按位置标签与有效 mask 选取、打散后的 `[N,d]` | hook 只读取并 `detach`，不替换模型计算 |
| SAE encoder | 固定归一化后的一个 hidden vector `x ∈ R^d` | 非负稀疏 `z ∈ R^65536`，每 token 最多 k 个非零值 | 更新 SAE encoder |
| SAE decoder | 同一 token 的稀疏系数 z | 重构 `x_hat ∈ R^d`；反归一化得到 `h_hat` | 更新 SAE decoder；目标是原 hidden，不是图片像素或答案文本 |
| 数据选择 | 每条样本的所有有效 token 的 z，以及 I/Q/Ans 来源标签 | 三份样本指纹、激活 feature 集合、相似候选与新增覆盖 | 不自动把“激活多”解释成质量高 |

`d` 是一个 token 向量的坐标数，不是概念数；第 17 个 hidden 维度不天然代表“红色”。SAE 希望把原来叠加在这些坐标里的信息，分解为更容易检验的字典方向。但单个 feature 仍可能多义、碎片化，或者只反映位置与格式。

### 图片位置也在语言主干里，但不因此变成文本

对一条 `图片 → 用户问题 → 已有答案` 的样本，给每个位置保留来源标签：

| 池 | 选择哪些位置 | 该位置能看到什么 | 用于数据选择的意义 |
| --- | --- | --- | --- |
| **I：image slots** | processor 展开后真正被视觉向量替换的 image token；排除图片边界标记 | 图片视觉编码与此前序列上下文；**不能看到后面的问题** | 图像内容与视觉语义；同图不同问，I 不应被当成问题理解指纹 |
| **Q：user text** | 图片之后的真实问题文本 token | 图片及截至当前位置的问题前缀 | 问题意图、视觉属性查询、关系/计数/OCR 等任务；前面的 Q token 看不到后面的词 |
| **Ans：assistant text** | 数据集里已有答案的真实文本 token，teacher forcing 输入 | 图片、问题、此前答案以及当前位置已输入的答案 token | 回答内容、知识和表达方式；**不是答案正确性的证明** |

以上因果解释适用于本课的图在前、问题在后排序及这两个 checkpoint 的配置。Gemma E4B 的 `use_bidirectional_attention` 为 `null`，不要把其他 Gemma 变体的图像双向 mask 套过来。视觉编码器内部可全局读取图片，不代表 LLM 图片位置可以读取未来的问题。

本课的单图样本统一把首轮 user 内容规范为 `[image, question_text]`，再使用官方 chat template；保留正文和真实轮次，并记录这一排序规则。部分来源把问题文本放在图片之前，直接沿用会让前置 Q token 看不到图片，不能套用上表。多图或顺序具有特殊含义的样本留待独立方案处理。

**不把所有位置不加区分地喂给 SAE。** 丢弃 padding、system、角色/轮次/图片边界等模板 token；保留真实 I/Q/Ans。图片较多并不应自动获得更高权重。先按来源与图片抽样，再在各池中限额均匀抽 token。真实多轮保留对话历史；同一图片附带的独立 VQA 问答应拆成独立样本，不能拼成虚假的对话。

Ans 是否参与取决于目标：本课筛选的是**完整 SFT 样本**，所以参与；若只研究“输入图片 + 问题”的表示，就去掉 Ans，并用相同条件重训/重评。用于验证最终任务的参考答案不能混入 SAE 训练集。teacher forcing 只是读已有训练答案，不会自动让错误答案变正确。

## 03 · 用什么数据：先覆盖目标分布

SAE 训练集是**产生 hidden 的输入语料**；SAE 的重构目标则由冻结模型提供，不需要人工 feature 标签。以 LLaVA 风格的单图指令数据为目标，采用“广泛图文概念 + 属性/关系问答 + 少量 OCR + 指令格式”的组合。这里先做英文图文；面向中文或医学、图表等新领域时，要替换相应份额并重新验证，不能宣称这份配比普遍最优。

**每个模型、每个主 SAE 的正式上限为 200M 次有效激活暴露**，先用包含在其中的 20M 做 pilot。一次暴露是一条 `[d]` 向量进入一次 SAE 更新；它不是原始文本 token 数，也不是完整 MLLM forward 的序列 token 数。重复抽样计入暴露预算；同时记录独立图片数、独立文本 token 数与 forward 总量。Qwen 与 Gemma 各训一个主 SAE，总上限 400M，A/B 可选实验另计。

| 来源 | 使用的 train 数据 | 激活预算占比 | 每模型上限 | 为什么放进来 |
| --- | --- | --- | --- | --- |
| 广泛图片描述 | [pixparse/cc3m-wds](https://huggingface.co/datasets/pixparse/cc3m-wds) · [镜像](https://hf-mirror.com/datasets/pixparse/cc3m-wds) | 20% | 40M | 物体、场景、长尾概念；WebDataset 内含图片和 caption |
| 自然图像描述 | [jxie/coco_captions](https://huggingface.co/datasets/jxie/coco_captions) · [镜像](https://hf-mirror.com/datasets/jxie/coco_captions) | 15% | 30M | 接近 COCO/LLaVA 的图像域；同图多 caption 按 image ID 分组 |
| 细粒度描述 | [the_cauldron](https://huggingface.co/datasets/HuggingFaceM4/the_cauldron)，`localized_narratives` · [镜像](https://hf-mirror.com/datasets/HuggingFaceM4/the_cauldron) | 10% | 20M | 更细的实体、动作与关系；文本长时限制每样本取样数 |
| 常见视觉问答 | 同上，`vqav2` | 20% | 40M | 从“描述什么”扩展到“在问什么” |
| 组合关系问答 | [vikhyatk/gqa](https://huggingface.co/datasets/vikhyatk/gqa)，`train_balanced` · [镜像](https://hf-mirror.com/datasets/vikhyatk/gqa) | 15% | 30M | 属性、空间关系和组合问题；展开每图的独立 QA |
| 知识相关问答 | `the_cauldron/aokvqa` | 5% | 10M | 图像线索与常识结合 |
| 图片中的文字 | `the_cauldron/textvqa` | 5% | 10M | OCR 与基于图片文字的问答 |
| 目标格式桥接 | [HuggingFaceH4/llava-instruct-mix-vsft](https://huggingface.co/datasets/HuggingFaceH4/llava-instruct-mix-vsft) · [镜像](https://hf-mirror.com/datasets/HuggingFaceH4/llava-instruct-mix-vsft) | 10% | 20M | 真实 messages/images 格式；按子任务分层，避免固定模板支配 |
| **合计** | 每个来源交错抽样 | **100%** | **200M** | 这是一份工程混合方案，不是某篇论文的原配方 |

表中镜像是仓库访问入口，不保证当前位置的下载速度或所有文件直连；部分请求会重定向到 Hugging Face。固定数据 revision、split、过滤规则与许可证记录。H4 的混合数据**不是原始 LLaVA-Instruct-150K**。原始 [LLaVA-Instruct-150K](https://huggingface.co/datasets/liuhaotian/LLaVA-Instruct-150K) 的标注还需要配套 COCO 图片，不能只下载对话 JSON 就开始抽视觉激活。

**来源配比与 token 池配比是两层抽样。** 在每个来源中，先以 I 50% / 文本 50% 为起点；问答来源的文本份额在 Q 与 Ans 中等分，caption 来源的文本份额给真实 caption/Ans，排除人为添加的固定“Describe this image”请求。短文本不能靠反复重复几个 token 填满预算，应扩大图片/样本覆盖并报告实际比例。无需额外加纯文本，图文样本已有大量文本位置；纯文本是后续泛化对照，不能给 A/B 产生视觉激活。

入库前完成：坏图/空文本检查 → 统一 RGB 与官方 template → 图片 ID、精确 hash 和近重复分组 → 排除最终评测图片及其近重复 → 按图片组切分 90/5/5 train/validation/test，seed=42。COCO、VQAv2、LLaVA 等来源可能复用图片，不能按行随机切分；某些第三方 train split 也包含 COCO val 图片。额外报告与候选筛选池的重叠；以未见图片/来源的评估检验泛化。

## 04 · 从原始数据到训练更新

每个 token 独立成为一个 SAE 训练样本，混合后打散。源图片和对话不会丢失：它们的 ID、token 位置与来源标签随缓存保存，后续可以回看某个 feature 激活最强的原图区域与文本。

```mllm-sae
training
```

**冻结 MLLM → forward → hook → 采样 → 固定归一化 → SAE → 重构 loss → 仅更新 SAE。** SAE 并不串接在正常生成路径中，不要求 MLLM 学会使用它；冻结模型每次提供原始 hidden target。不要在 `no_grad()` 内运行需要更新的 SAE，只有 MLLM 的激活采集放在其中。

### 训练配方：先跑得对，再扩大预算

| 参数 | 本课主 SAE 的起点 | 必须记录或验证的事项 |
| --- | --- | --- |
| hook | Qwen C=`layers.23`；Gemma C=`layers.29` | 完整输出、零基层号；层位对照分别训练，不能混成一个字典 |
| 输入宽度 / 字典宽度 | Qwen 4096 → 65536；Gemma 2560 → 65536 | 两套 encoder/decoder、统计和 feature ID；65K 是 65,536，不是词表 |
| 稀疏化 | `TopK(ReLU(u), k=64)`，对照 k=32/128 | 每 token 最多 k 个非零，不是整个 batch 共用 k；零值不算激活 |
| 重构 | 线性 decoder，decoder 每列单位 L2 范数；每步投影更新后重新归一化 | 避免字典尺度任意漂移；encoder 初始化为 decoder 转置，此后不绑权 |
| 归一化 | 用至少 100k 个代表性 train 向量估计向量均值 μ 与一个标量 s；固定 `x=(h−μ)/s` | I/Q/Ans 使用同一组统计；按真实抽样比例估计。不同模型/hook 各估各的；不逐 token 单位化 |
| 优化器 | Adam，lr=1e−4，β=(0.9,0.999)，eps=1e−8，weight decay=0，grad clip=1 | 200 步 warmup 后常数 lr，按 validation 早停；不要把它写成已验证最优 |
| 全局 batch | 4096 个激活向量；例如 microbatch 512 × 8 梯度累积 | 不是 4096 张图；多卡时计入全部 rank 的有效向量 |
| 精度 | 冻结主模型与缓存 BF16；SAE 权重、归一化和 loss 用 FP32 起步 | 不先量化主模型；量化会改变 SAE 的训练对象 |
| 激活缓存 | 约 65,536 向量的 shuffle buffer；按 source/pool 打散 | 只缓存需要的位置；记录样本 ID、模型/processor revision、hook、mask 与统计 hash |
| 预算 | 20M pilot，正式最多 200M 暴露/模型/主 hook | batch 4096 对应约 48,828 次更新；早停时报告实际暴露与重复率 |
| dead features | feature 在最近 10M 次全局暴露内无 `z>1e−6` 记为 dead | 未积累完整窗口前不按该规则宣布死亡；按池分别记录使用率 |
| AuxK | dead-feature 辅助重构，系数 1/32；`k_aux=min(2048, dead_count)` | 两个主 SAE 使用同一明确配置；不是所有库的默认值，采用下述定义 |

定义 `D ∈ R^(d×K)` 为 decoder 字典，encoder 为 `W ∈ R^(K×d)`。每个 `D[:,j]` 是第 j 个 feature 的重构方向；`z_j` 是当前 token 使用它的强度。

$$
x=(h-\mu)/s,\quad s^2=\mathbb{E}_{train}\|h-\mu\|_2^2/d
$$

$$
u=W(x-b_{dec})+b_{enc},\quad z=\operatorname{TopK}_{64}(\operatorname{ReLU}(u)),\quad \hat{x}=Dz+b_{dec}
$$

$$
\mathcal L_{rec}=\operatorname{mean}_b\frac{\|x_b-\hat{x}_b\|_2^2}{d}
$$

为使 AuxK 的实现没有歧义：`e=stop_gradient(x−x_hat)`；只在 dead 集合内从 `ReLU(u)` 选取最多 `k_aux` 个系数，记为 `z_dead`；辅助重构为 `D z_dead`，**不加 decoder bias**。

$$
\mathcal L_{aux}=\operatorname{mean}_b\frac{\|e_b-Dz_{dead,b}\|_2^2}{\|e_b\|_2^2+10^{-8}},\qquad
\mathcal L=\mathcal L_{rec}+\tfrac1{32}\mathcal L_{aux}
$$

无 dead feature 时令辅助项为 0；初始 bias 为 0。TopK 已控制稀疏度，本配方不再叠加一个未经调参的 L1 惩罚。这个归一化辅助 loss 是本课选定的实现约定；不要把它与其他 SAE 仓库的不同 reduction、残差归一化或 batch TopK 默认值混用。TopK、dead-feature 处理及重构/行为多维评估的依据可参见 [Scaling and evaluating sparse autoencoders](https://arxiv.org/abs/2406.04093)。

65K 不意味着训练代价小：两块稠密权重约为 `2×d×K` 参数。Qwen SAE 约 5.37 亿参数，Gemma SAE 约 3.36 亿；FP32 参数、梯度及 Adam 两份矩状态合计约 8.6 GB / 5.4 GB（十进制），**尚未计主模型、activation、临时张量与框架开销**。先测显存；不足时减 microbatch、累积梯度，或将 32K 作为明确记录的独立配置。65K 在两个模型上的扩张倍数分别是 16× 和 25.6×，不能据此视为容量完全匹配的科学对照。

### 可运行的 hook 烟雾检查

下面代码只验证“模型确实经过该模块、形状正确、image mask 与序列对齐”，不是完整训练器。需要可加载模型的 GPU 环境、支持这两个模型的 Transformers 版本，以及 Gemma 已获准的模型访问。先对安装版本运行，再锁定版本与模型 revision；本文没有执行权重加载。

```python
import torch
from PIL import Image
from transformers import AutoProcessor, Qwen3_5ForConditionalGeneration, Gemma4ForConditionalGeneration

which = "qwen"  # 改成 "gemma"
specs = {
    "qwen": ("Qwen/Qwen3.5-9B", Qwen3_5ForConditionalGeneration, 23, 4096),
    "gemma": ("google/gemma-4-E4B-it", Gemma4ForConditionalGeneration, 29, 2560),
}
model_id, cls, layer, d = specs[which]
processor = AutoProcessor.from_pretrained(model_id)
model = cls.from_pretrained(model_id, dtype=torch.bfloat16, device_map="auto")
model.eval().requires_grad_(False)
path = f"model.language_model.layers.{layer}"
modules = dict(model.named_modules())
assert path in modules, path
print(path, type(modules[path]).__name__)

image = Image.open("example.jpg").convert("RGB")
messages = [
    {"role": "user", "content": [
        {"type": "image", "image": image},
        {"type": "text", "text": "What is in this image?"}]},
    {"role": "assistant", "content": [
        {"type": "text", "text": "Replace this with the dataset's actual answer."}]},
]
batch = processor.apply_chat_template(
    messages, tokenize=True, add_generation_prompt=False,
    return_dict=True, return_tensors="pt",
).to(model.device)
cache = {}
def capture(_module, _args, output):
    h = output[0] if isinstance(output, tuple) else output
    assert h.ndim == 3 and h.shape[-1] == d
    cache["h"] = h.detach()

handle = modules[path].register_forward_hook(capture)
try:
    cache.clear()  # 每次 forward 都清空，避免误用上一样本的激活
    with torch.no_grad():
        _ = model.model(**batch, use_cache=False)
    h = cache.pop("h")
    ids = batch["input_ids"].to(h.device)
    valid = batch["attention_mask"].to(h.device).bool()
    image_mask = ids.eq(model.config.image_token_id) & valid
    assert h.shape[:2] == ids.shape
    assert image_mask.any(), "图文样本应存在真实 image slots"
    print("all:", tuple(h.shape), "image:", tuple(h[image_mask].shape))
    # Q / Ans 用模板渲染时记录的角色正文跨度对齐，不能用“非 image”代替。
    # 抽样后再离开 no_grad，用 FP32 SAE 计算 loss/backward/optimizer.step。
finally:
    handle.remove()
```

生产数据处理需要补齐 Q/Ans mask：记录 chat template 中每个消息正文的 token span，与 processor 展开 image slots 后的序列对齐；支持 assistant mask 的模板可利用该能力，但要检查 mask 是否全零、是否覆盖真实答案。用单图、纯文本、独立 QA、真实多轮和 padding 样本逐个检查。**不要通过 decoded 文本里搜索答案字符串定位**，重复词与特殊 token 会造成错位。对齐完成后，assert 三个池互斥、均不含 padding，且所有入池位置都有来源记录。

第一轮限定单图、文本上限约 512 token、总序列上限 2048；超过上限的样本显式跳过并记录比例，不截断半个图像 span。Qwen 固定 processor 的像素预算（如 65,536–1,048,576）；Gemma 固定 `max_soft_tokens=280` 起步；两者保留长宽比，缓存与正式筛选使用相同配置。OCR 受分辨率影响，后续提高预算必须重验表示分布。

## 05 · 一个 65K 字典，里面到底装什么

字典不是预先命名的概念表，也不是按比例分给图片/文本的内存。**同一个 C hook 上的 I、Q、Ans 向量，共享同一套 encoder、decoder 和 65,536 个 feature ID。** 抽样时 50% 图像位置并不意味着训练后恰好 50% feature 属于图片。

```mllm-sae
dictionary
```

点击 feature 查看它在三类位置上的示意激活。改变 k 只改变每个 token 的稀疏预算，不改变字典槽位数。图中把 65,536 个槽缩略为一个 256×256 网格；点亮的是合成示例，数值不构成实际模型的 feature 发现。

| 训练后可能观察到的 feature | 如何发现与命名 | 不能据此直接断言什么 |
| --- | --- | --- |
| 视觉偏好：颜色、形状、物体、局部区域 | I 池激活多，回看不同原图的高激活 patch/visual token，并做区域遮挡 | 激活区域不是严格因果定位；深层视觉 token 已可能读取整图 |
| 文本偏好：词法、句式、回答格式 | Q/Ans 池激活多，查看不同来源的高激活文本跨度 | 不等于“无用”；任务形式本身可以贡献数据多样性 |
| 任务/融合相关：计数问题、文字读取、属性判断 | Q/Ans 在有图条件下有稳定激活，换图/换问后变化合理 | “位于文本 token”不等于“只含文本信息” |
| 跨模态候选：图片中的苹果与文字 apple | 同一 feature 在相关图片和文本上都激活，并通过对照排除模板/位置 | 共用字典不保证同义图文自动对齐到同一 feature；一个 feature 也可能多义 |
| 格式、位置、噪声或 dead feature | 激活与模板、长度、数据源或数值异常强相关，或长期不激活 | 不能把所有 65K 槽当成已解释的 65K 语义概念 |

**训练时不分配语义类别；训练后才统计。** 对每个 feature 记录 I/Q/Ans 的激活概率、强度、source 分布与 top examples，按池样本数归一化；允许多个标签、未解释与噪声类别。需要跨模态语义一致性时，增加图文对应、图像替换、问题改写和 feature 干预实验。VL-SAE 使用专门的显式对齐与模态 decoder 来推动一致概念；普通混合 TopK SAE 不自动获得同样性质。[VL-SAE](https://arxiv.org/html/2510.21323v1)

一轮建议抽查 100 个活跃 feature，每个回看至少 20 个**不同图片组/样本**的 top examples，并包含随机激活与负例。对主 hook，固定同图与前缀、只改后置问题时，I 应在数值容差内保持不变，而 Q/Ans 可以改变；固定问题换图、遮挡相关区域，则检验文本位置的 feature 是否确实依赖视觉。

## 06 · 怎样拿这些特征筛数据

先对所有有效 token **逐个编码成 z，再聚合**。先平均 hidden 再经过 SAE 会改变非线性稀疏编码的结果。对每条样本分别保存 `v_I, v_Q, v_Ans`：各池 `mean(z)` 后 L2 归一化用于相似度；另外保存超过 feature 阈值的激活集合，用于覆盖。

| 目标 | 可实施的步骤 | 关键边界 |
| --- | --- | --- |
| 精确去重 | 图片 hash + 规范化问题/答案 hash，先去完全重复 | 不要仅凭图片相同删除问答 |
| 语义重复候选 | 在各池稀疏指纹上找相似邻居，再联合 I/Q/Ans 判定 | 同图不同问、同问不同答案必须进入标注校准集；不设一个凭空选的通用阈值 |
| 语义冲突 | I、Q 相似而 Ans 不同，标记为“待核验” | 可能是互补答案，也可能是错误；SAE 相似度不裁决事实 |
| 多样性选择 | 每次优先加入可靠 feature 的新增覆盖；结合图像组、来源/任务配额和样本或 SFT-token 成本 | 高频模板、罕见噪声不应获得额外奖励；长样本不能仅凭更多 token 占便宜 |
| 最终检验 | 在固定样本量或 SFT-token 预算下，与随机、hash 去重及 dense embedding 选择对比 | 原始 feature 覆盖数不能直接跨不同 SAE/不同字典比较；必须看下游收益和误删 |

**TopK 的每 token 非零数接近固定，因此“激活总次数多”不是多样性分数。** 相似度阈值与覆盖阈值在 held-out 校准集上选择，并检查稀有任务与回答长度的偏差。给每个 feature 保存数据源证据；新覆盖只在活跃、稳定且排除明显模板/噪声的集合中计分。

## 07 · 什么结果才算可以使用

不要只看总 MSE。总体 loss 很好可能只是图片位置占多数，把问题与答案的重构问题掩盖了。

| 验收层面 | 本课建议的启动门槛 / 比较方式 | 不通过时先检查 |
| --- | --- | --- |
| 数据与张量 | hook 命中；d、token 数、I/Q/Ans mask 均通过断言；跨 split 无图片组泄漏 | 模型版本、模板跨度、padding、旧 cache、图像与对话错配 |
| 稀疏与活性 | L0≤64；完整 10M 暴露窗口后 dead 比例先以≤10%作为排查门槛 | 输入统计、学习率、初始化、AuxK、单一来源过采样 |
| 重构 | validation 总 FVU 先以≤0.15、各池≤0.25为排查门槛；按池用自己的验证均值计算分母 | 不同池尺度/难度、k 太小、字典不足；门槛不是论文公认标准 |
| 功能保持 | 真实用 `h_hat` 替换 hook 输出，teacher-forced answer CE 增幅先以≤0.1 nat/token、任务准确率下降≤1pp为排查线 | 必须重跑后续模型；不能加回原重构残差，那会把误差隐藏掉 |
| 特征含义 | top examples + 随机/负例 + 换图/换问/遮挡对照；记录不能解释的 feature | 位置、格式、长度和来源偏差；不能凭一张热力图命名 |
| 去重误删 | 至少 500 对人工校准样本，覆盖同图不同问、同问不同答与近重复图 | 按任务与来源报告 precision/recall；阈值只在校准集上调 |
| 选择是否有用 | 固定预算做随机、dense、SAE 对照；至少 3 个随机种子，报告均值与波动 | 是否仅靠特定来源/长度配额获益；是否因测试集重叠造成虚高 |

FVU 用 `Σ||h−h_hat||² / Σ||h−mean(h)||²`；按 I/Q/Ans 分别算，低方差池单独报告绝对误差，避免不稳定比值。所有数值门槛都是诊断起点，最终能否用于选择数据，要由误删率、任务保留与下游 SFT 决定。

训练结束至少保存：SAE 权重与 bias、μ/s、K/k、模型与 processor revision、Transformers 版本、完整 hook 路径、数据 split/hash/采样配置、实际预算、按池验证指标、feature 使用统计与 top-example 索引。没有这些信息，一个权重文件很难被正确复用。

## 08 · 哪些来自研究，哪些是本课的选择

| 依据 | 能支持的结论 | 本课没有从中推出的结论 |
| --- | --- | --- |
| [SAE-V，ICML 2025，PKU 团队](https://proceedings.mlr.press/v267/lou25b.html) | 多模态内部 feature 可以帮助分析对齐，并用于数据筛选 | 不证明 Qwen3.5-9B layer 23、Gemma E4B layer 29 或本课混合比例最优 |
| [VL-SAE，NeurIPS 2025，ICT/UCAS](https://proceedings.neurips.cc/paper_files/paper/2025/hash/407106f4b56040b2e8dcad75a6e461e5-Abstract-Conference.html) | 跨模态统一概念需要专门考虑表征/激活对齐 | 不把普通混合 SAE 的同一 feature ID 自动视为已对齐语义 |
| [OpenAI：Scaling and evaluating sparse autoencoders](https://arxiv.org/abs/2406.04093) | TopK 控制稀疏度，dead latents 与功能评估都重要 | 不把文本 SAE 的所有默认值当成多模态已验证配方 |
| [Qwen-Scope，Qwen 团队](https://arxiv.org/html/2605.11887v1) | SAE 特征可用于冗余、覆盖与数据相关分析 | 不把它当成已公开验证本课图文混合数据与两个指定 hook 的证据 |
| 本课方案 | 两模型分开训练；C 为主、A/B 为对照；I/Q/Ans 共用每模型的 65K 字典；200M 上限 | 仍需运行、校准与消融；图中的 feature 值不是测量结果 |

读完本课后，应能把四件事分开：**输入图片/文本是什么，冻结模型产生的 hidden 是什么，SAE 学到的字典方向是什么，数据选择使用的样本指纹是什么。** 下一步是先用 20M pilot 检查这些边界，再决定是否扩大预算。
