← 返回论文列表
🧬 推荐系统基础概念 · 深度讲解

Semantic ID 完全手册

从头讲清楚 SID 的生产过程、训练机制,以及如何注入协同过滤信号

RQ-VAE 量化 VQ-VAE 原理 CF 对齐 LETTER / QARM / DAS
🧬
§1 SID 是什么?
Semantic ID(SID)是一种把物品(或用户)的多模态语义内容,通过量化(quantization)压缩成一组层次化整数编码的技术。它的最终形态是一个短短的 code 序列,比如 [5, 31, 142],可以直接被推荐系统当成"有语义的 ID"来用。

为什么需要 SID?

传统推荐系统用的 ID 特征(比如广告 ID = ad_12345678)本质上只是一个随机编号,不带任何语义信息。两个"内容相似"的广告可能 ID 相差甚远。

传统 ID 的问题

  • 纯粹是数字编号,无语义
  • 冷启动广告(新 ID)没有 embedding,无法被模型识别
  • 内容相似的物品,ID 之间没有任何关联
  • 不同模态(图文视频)的信息无法融合

SID 的优势

  • 由多模态内容(图文视频)派生,携带语义
  • 层次化结构:前缀相同 = 内容相关
  • 离散稀疏 ID,可接入 embedding table,可学习
  • 冷启动友好:内容相似就有相似 SID
  • 可以和其他稀疏 ID 做 feature crossing

SID 长什么样?

以 DAS 论文为例,每个广告的 SID 是三层 code 序列($L=3$):

🧩 举例:一个广告的 SID

广告 I(空气炸锅)经过 SID 量化后得到:SID = [5, 31, 142]

  • 第 1 层 code = 5:粗粒度语义,比如"厨房用品类"
  • 第 2 层 code = 31:中粒度,比如"小型电器"
  • 第 3 层 code = 142:细粒度,比如"空气炸锅具体款式"

接入下游 CTR 模型时,用 Prefix-Ngram 方式展开成三个稀疏特征:

  • ad_l1 = 5
  • ad_l2 = "5_31"
  • ad_l3 = "5_31_142"

每个特征都有一个 16 维可学习 embedding,粗粒度 + 细粒度同时建模。

💡 一句话定位 SID 在系统中的角色 SID 是"用多模态内容生成的、有语义的、层次化的、可学习的稀疏 ID"。它不是 dense embedding(不是一个浮点向量直接用),而是离散编号——这样它就能像普通 ID 特征一样接入任何推荐系统,同时又携带了内容语义。
⚙️
§2 SID 的生产流程

生产一个 SID 需要两大步骤:① 提取多模态内容 embedding② 量化成离散 code 序列

SID 生产全流程
多模态原始数据
图文/视频/标题/OCR
LLM
摘要 + 提炼
PLM(如 BGE M3)
编码成连续 embedding $s$
RQ-VAE
量化成 SID $[c_1,c_2,c_3]$

2.1 内容 embedding 提取

量化之前,首先要把物品(广告/视频/商品)和用户的多模态内容压缩成一个固定维度的连续向量 $s$。

  • 1
    多模态数据整理成文本 广告的标题、OCR 文字、ASR 字幕、视觉描述(图片描述)拼成一段结构化文本。用户的基本信息 + 历史行为也转成文本 prompt。
  • 2
    LLM 精炼(可选但推荐) 把上面那段文本送进大语言模型(GPT / Qwen 等),让它生成一段质量更高的摘要,去掉噪声、突出重点。
    🧩 举例

    广告原始数据:标题"超值特卖!网红空气炸锅限时折扣",OCR 识别到"容量 3.5L 定时功能 360°加热",图片描述"银色小型厨房电器"。

    LLM 摘要后变成:"一款 3.5L 容量的小型家用空气炸锅,支持定时和 360 度均匀加热,主打厨房烹饪健康少油"。

    这段摘要语义更纯净,后续 embedding 质量更好。

  • 3
    PLM 编码成 embedding 把摘要文本送进一个预训练语言模型(比如 DAS 里用的 fine-tuned BGE M3),输出一个连续向量: $$s_i = \text{item\_encoder}(t_i) \in \mathbb{R}^{d}$$

    其中 $d$ 通常是 256 或 1024。这个 $s_i$ 就是接下来要被量化的"原材料"。

⚠️ 注意:这个 embedding 本身不会直接用 你可能会问:为什么不直接把 $s_i$ 当稠密特征塞进模型?原因是:
  • 不可学习:冻结 embedding 无法根据下游任务调整
  • 维度太高:256 或 1024 维对大规模工业系统来说太重
  • 无法做 feature crossing:稠密向量不能像 ID 特征那样和其他特征做交叉
所以需要进一步量化成离散 ID——这就是 VQ-VAE / RQ-VAE 的作用。

2.2 VQ-VAE 原理:什么是向量量化?

在理解 RQ-VAE 之前,先搞懂最基础的 VQ-VAE(Vector Quantized VAE)

VQ-VAE 工作流程
输入 $s_i$
连续向量
Encoder
MLP/Transformer
latent $h_i$
连续
argmin 查 Codebook
找最近 code
量化结果 $e_{c}$
连续,离散 index $c$
Decoder
重建 $\hat{s}_i$

Codebook(码本) 是一组可学习的向量集合 $\{e_1, e_2, \ldots, e_N\} \subset \mathbb{R}^d$,共 $N$ 个码字。

量化的操作是:找 Codebook 里和 $h_i$ 最近的那个码字:

$$c = \arg\min_{k \in \{1,...,N\}} \|h_i - e_k\|^2$$

选出来的 $c$(整数 index)就是 SID 的一位编码;选出来的 $e_c$(连续向量)用来后续 decoder 重建。

🧩 直觉比喻:把颜色量化成有限个色号

想象你有一张高清照片(连续 RGB 值),但你只能用 512 种固定颜色(Codebook)来画它。量化就是:对照片里的每个像素,找 512 种颜色里最接近的那个,用它的编号代替原始颜色值。

SID 量化也是一样:把一个 256 维的连续语义向量,映射到 512 个码字里最接近的那个,用它的 index 代替原始向量。

VQ-VAE 的局限:只有一层 Codebook,量化误差大,而且 $N$ 太小时表达能力不足,$N$ 太大时 codebook 会崩塌(大量码字没人用)。

2.3 RQ-VAE:层次化残差量化

RQ-VAE(Residual Quantization VAE) 是 VQ-VAE 的改进版,用多层 Codebook 逐步量化残差,精度更高、表达能力更强。

⚠️ 关于 Encoder 的说明

RQ-VAE 内部有一个 Encoder(MLP 或 CNN),负责把原始 MLLM embedding $s_i$(256 维)映射到量化空间的 latent 表示 $h_i$(与 codebook 码字同维度,32 维)。

量化(argmin)在 $h_i$ 上进行,不是直接在 256 维的 $s_i$ 上进行。原始 RQ-VAE 论文(CVPR 2022)的公式是:$Z = E(X)$,先 encode 再 quantize。DAS 论文的公式简写为 $r_0 = s$,实际含义是 $r_0 = h_i = \text{Encoder}(s_i)$。

因此:$s_i$ 是 256 维的内容语义向量;$h_i$ 是 encoder 输出的 32 维 latent;量化在 $h_i$ 维度的空间里发生。

RQ-VAE 三层量化($L=3$)
输入 $s_i$(256维)
↓ Encoder(MLP)
$h_i$(32维),$r_0 = h_i$
第 1 层
$c_1 = \text{argmin}\|r_0-e_k\|^2$
$r_1 = r_0 - e_{c_1}$
第 2 层
$c_2 = \text{argmin}\|r_1-e_k\|^2$
$r_2 = r_1 - e_{c_2}$
第 3 层
$c_3 = \text{argmin}\|r_2-e_k\|^2$
$r_3 = r_2 - e_{c_3}$
离散 SID $[c_1, c_2, c_3]$
三个整数
+
连续量化表示 $z_i = e_{c_1}+e_{c_2}+e_{c_3}$
三个 code 向量之和(sum-pool)
符号说明(DAS 配置)
  • $L=3$:层数(codebook 数量)
  • $N=512$:每层 codebook 大小(512 个码字)
  • $d=32$:每个码字向量的维度(也是 encoder 输出 latent $h_i$ 的维度)
  • $r_0 = h_i = \text{Encoder}(s_i)$:初始残差,是 encoder 把 256 维 $s_i$ 映射成 32 维后的 latent
  • $r_l = r_{l-1} - e_{c_l}$:每层的残差(上一层残差减去已量化码字向量)
  • 总编码空间:$512^3 \approx 1.3$ 亿种 SID 组合
🧩 完整举例:空气炸锅广告的 RQ-VAE 量化过程

假设广告 I(空气炸锅)经过 PLM 得到 $s_i$(256 维),再经 RQ-VAE 内部 Encoder 得到 $h_i$(32 维):

  1. Encoder:$s_i$(256 维)→ Encoder(MLP)→ $h_i$(32 维)。$r_0 = h_i$。
  2. 第 1 层:在 512 个码字里找和 $r_0$ 最近的,选中 $e_5$(代表"厨房家电"类)。残差 $r_1 = r_0 - e_5$
  3. 第 2 层:对残差 $r_1$ 在 512 个码字里找最近的,选中 $e_{31}$(代表"小型电器")。残差 $r_2 = r_1 - e_{31}$
  4. 第 3 层:对残差 $r_2$ 在 512 个码字里找最近的,选中 $e_{142}$(代表该广告的具体款式特征)。

最终:

  • 离散 SID:[5, 31, 142](三个整数)
  • 连续量化表示:$z_i = e_5 + e_{31} + e_{142}$(三个 32 维向量相加,结果还是 32 维)

$z_i$ 是连续的(可以求梯度),SID 是离散的(传给下游模型的实际编号)。

💡 $z_i$ 和 SID $[c_1,c_2,c_3]$ 的关系是什么?
  • SID = 三个整数 index,比如 [5, 31, 142]。这是最终给下游模型用的"标签"。
  • $z_i$ = 三个码字向量相加,是连续表示。这是训练时用来计算 loss、做对齐的中间量。
  • $z_i$ 依赖 codebook 里选中的那三个向量 $e_5, e_{31}, e_{142}$。如果 codebook 更新了,$z_i$ 也会变。

可以理解为:SID 是"地址",$z_i$ 是"地址对应的内容"。

🏋️
§3 训练过程详解(完整版)

本节从训练哪些参数、损失函数、梯度如何流动、codebook 坍塌如何防止、初始化、EMA vs 梯度法、Rotation Trick、超参调优、训练监控九个维度全面讲解 RQ-VAE 的训练。内容综合自 VQ-VAE 原始论文、ICML 2023 STE 分析论文、Fifty et al. 2024 Rotation Trick 论文,以及 TIGER/DAS 的工业实践。

3.0 整体架构与训练目标

RQ-VAE 由三部分组成,训练时三者联合端到端优化

RQ-VAE 前向传播(训练时)
输入 embedding $s$
(如 256 维 MLLM 向量)
Encoder (MLP)
latent $z_e$
(如 32 维)
残差量化 × L 层
离散 ID $[c_1,\ldots,c_L]$
量化向量 $z_q = \sum e_{c_l}$
Decoder (MLP)
重建 $\hat{s}$
训练目标:让重建尽量还原原始 embedding,同时让 encoder 输出和 codebook 码字尽量靠近,防止量化精度退化。

3.1 训练哪些参数?

① Encoder 权重

把原始 embedding $s$(如 256 维)压缩到量化空间(如 32 维)的 MLP,含 Linear + BatchNorm + ReLU。

参与训练重建损失 (STE 路径)commitment loss 双路更新。

② Codebook 向量 $\{e_k\}$

每层 $K$(如 256)个 $d$ 维可学习向量($L \times K$ 个总共)。

参与训练 有两种更新方式:梯度法(codebook loss)或 EMA 法(不走 optimizer,见 §3.4)。

③ Decoder 权重

从量化向量 $z_q = \sum_{l} e_{c_l}$($d$ 维)重建回原始维度的对称 MLP。

参与训练 只由 重建损失 $\|s - \hat{s}\|^2$ 更新,commitment/codebook loss 对其无梯度。

④ argmin 本身

"找最近 code"$c_l = \arg\min_k \|r_{l-1} - e_k\|^2$ 是离散的、不可微的

不可微 通过 STE 或 Rotation Trick 绕过,见 §3.3。

3.2 损失函数拆解

VQ-VAE / RQ-VAE 的标准训练损失分两部分(DAS 也沿用此框架):

$$\mathcal{L} = \underbrace{\|s - \hat{s}\|^2}_{L_{\text{recon}}} + \underbrace{\sum_{l=1}^{L}\Bigl[\|\,\text{sg}[r_{l-1}] - e_{c_l}\|^2 + \beta\,\|r_{l-1} - \text{sg}[e_{c_l}]\|^2\Bigr]}_{L_{\text{vq}}}$$
符号说明
  • $s$:输入 embedding;$\hat{s}$:decoder 重建的 embedding
  • $r_{l-1}$:第 $l$ 层量化的输入残差($r_0 = z_e = \text{Encoder}(s)$,$r_l = r_{l-1} - e_{c_l}$)
  • $e_{c_l}$:第 $l$ 层 codebook 中被选中的码字向量
  • $\text{sg}[\cdot]$:stop-gradient,代码中为 .detach()
  • $\beta$:commitment 系数,典型值 0.25~1.0(见 §3.7 超参调优)

量化损失包含两个独立的 sg-loss 项,梯度分别流向不同模块:

损失项公式更新谁作用
Codebook loss $\|\text{sg}[r_{l-1}] - e_{c_l}\|^2$ Codebook $e_{c_l}$
(encoder 被 sg 冻结)
把 codebook 向量拉向 encoder 输出的均值中心
Commitment loss $\beta\,\|r_{l-1} - \text{sg}[e_{c_l}]\|^2$ Encoder 参数
(codebook 被 sg 冻结)
迫使 encoder 输出"承诺"靠近 codebook,防止乱漂
Reconstruction loss $\|s - \hat{s}\|^2$ Encoder + Decoder
(通过 STE/Rotation Trick)
保证量化后能还原原始语义信息
⚠️ 原始 RQ-VAE(Lee et al. 2022)vs VQ-VAE 的差异

原始 RQ-VAE 论文(CVPR 2022)的损失只有单向 commitment loss(只训 encoder),codebook 用 EMA 更新,不通过梯度 loss。
VQ-VAE(Van Den Oord 2017)则是双 sg-loss,codebook 和 encoder 各有一项,通过梯度联合更新。
DAS 采用 VQ-VAE 风格的双 sg-loss。两种方式实践中效果相近,EMA 法更稳定但需单独维护 moving average 状态。

3.3 梯度如何穿越不可微的量化操作?(STE vs Rotation Trick)

量化的核心是 $\arg\min$,这是离散的,梯度为零或未定义。有两种主流解法:

方法一:Straight-Through Estimator(STE)

VQ-VAE 的方案:前向传播用量化值 $e_c$,反向传播"假装" $e_c = z_e$(直通梯度)。

$$z_q^{\text{STE}} = z_e + \underbrace{(e_c - z_e)}_{\text{停止梯度}} = e_c \quad \text{前向用 } e_c$$ $$\frac{\partial z_q^{\text{STE}}}{\partial z_e} = \mathbf{I} \quad \text{(梯度直通,假装量化没发生)}$$

PyTorch 实现(一行):

# Straight-Through Estimator
z_q = z_e + (e_c - z_e).detach()  # 前向=e_c,反向梯度从e_c跳到z_e
# 等价于: z_q.backward() 时,梯度直接流向 z_e,不经过 argmin
STE 的缺陷:当 $z_e$ 和 $e_c$ 距离较大时(量化误差大),STE 引入的梯度误差也大。ICML 2023 论文证明,$\Delta_{\text{gap}} \propto \|z_e - e_c\|$。这正是 codebook 坍塌的间接原因——坍塌后只剩几个 code,量化误差反而小了,STE"看起来"工作正常,但实际语义已退化。

方法二:Rotation Trick(Fifty et al. 2024,更新方案)

Rotation Trick 用一个旋转矩阵 $R$ 来精确地把 $z_e$ "旋转"到 $e_c$ 的方向,从而实现真正连续可微的梯度估计,彻底解决 STE 的梯度误差问题。

$$z_q^{\text{rot}} = R \cdot z_e \quad \text{其中 } R \text{ 满足 } R \cdot z_e = e_c \cdot \frac{\|z_e\|}{\|e_c\|}$$ $$\frac{\partial z_q^{\text{rot}}}{\partial z_e} = R \quad \text{(旋转矩阵 Jacobian,信息保全)}$$

Eugene Yan 的实战实验表明:用 Rotation Trick 替换 STE 后,codebook 利用率显著提升,unique ID 比例更高。目前 vector-quantize-pytorch 库已内置支持。

三种梯度方案对比
方案梯度误差稳定性实现复杂度推荐场景
STE(传统)较大(与量化误差成比例)一般极低(1行代码)快速实验基线
EMA 更新 codebookSTE 估计 encoder 梯度高(codebook 更新平滑)中等工业落地首选
Rotation Trick(新)最小(无近似误差)最高中等追求最优质量

3.4 Codebook 的两种更新方式:梯度法 vs EMA 法

方式 A:梯度下降(Gradient Descent)

把 codebook 向量当作普通的 nn.Parameter,通过 codebook loss $\|\text{sg}[z_e] - e_c\|^2$ 的梯度更新。

# 梯度法(标准 VQ-VAE 写法)
codebook_loss = F.mse_loss(z_e.detach(), e_c)  # 对 e_c 有梯度,对 z_e 无梯度
commitment_loss = beta * F.mse_loss(z_e, e_c.detach())  # 对 z_e 有梯度
loss = recon_loss + codebook_loss + commitment_loss

方式 B:指数移动平均 EMA(工业界更常用)

EMA 法完全绕过 codebook loss 的梯度。不把 codebook 注册为 nn.Parameter,而是用滑动平均手动更新:

$$N_k^{(t+1)} \leftarrow \gamma \cdot N_k^{(t)} + (1-\gamma) \cdot n_k \qquad \text{(各 code 的使用计数 EMA)}$$ $$m_k^{(t+1)} \leftarrow \gamma \cdot m_k^{(t)} + (1-\gamma) \cdot \sum_{z_e \to k} z_e \qquad \text{(映射到该 code 的 encoder 输出的 EMA 之和)}$$ $$e_k^{(t+1)} \leftarrow \frac{m_k^{(t+1)}}{N_k^{(t+1)}} \qquad \text{(码字 = 均值)}$$

其中 $\gamma$(衰减因子 decay)通常取 0.99,$n_k$ 是当前 batch 中被分配到第 $k$ 个 code 的样本数。

# EMA 法(PyTorch 实现骨架)
# codebook 注册为 buffer(不参与 optimizer),EMA 手动更新
self.register_buffer('embeddings', torch.randn(K, D))
self.register_buffer('ema_cluster_size', torch.zeros(K))
self.register_buffer('ema_dw', torch.zeros(K, D))

# 训练时 forward:
if self.training:
    one_hot = F.one_hot(indices, K).float()          # (B, K)
    self.ema_cluster_size = decay * self.ema_cluster_size + (1-decay) * one_hot.sum(0)
    self.ema_dw = decay * self.ema_dw + (1-decay) * one_hot.T @ z_e
    # 拉普拉斯平滑防止除零
    n = self.ema_cluster_size.sum()
    smooth_size = (self.ema_cluster_size + eps) / (n + K*eps) * n
    self.embeddings = self.ema_dw / smooth_size.unsqueeze(1)

# 损失只剩 commitment loss(codebook 不走 optimizer)
loss = beta * F.mse_loss(z_e, e_c.detach())
EMA 等价性(ICML 2023 证明):EMA 更新实际上等价于用 SGD 优化 codebook loss(当 $\beta=1$),EMA 的 decay 常数 $\gamma$ 对应学习率 $\eta = 1 - \gamma$。EMA 的优势在于与全局 optimizer 解耦,codebook 更新更平滑,不受全局学习率调度影响,能有效防止 codebook 和 encoder 之间"来回互追"导致的不稳定。

3.5 最大难点:Codebook 坍塌(Index Collapse)

Codebook 坍塌是训练 VQ-VAE/RQ-VAE 最臭名昭著的失败模式:训练结束后,只有少数几个 code(甚至个位数)被实际使用,大量 code 永远"休眠"。

根本原因(ICML 2023 深度分析)

commitment loss 本质上是一个非对称散度:只对被选中的 code 有梯度,未被选中的 code 永远得不到更新。一旦某些 code 在训练初期没被选到,它们就会永远"死亡",而 encoder 会越来越倾向于只输出少数"安全"的几个方向。

坍塌的雪崩效应
初始化不好 → 少数 code 离 encoder 输出近
这几个 code 被频繁选中,其余 code 得不到梯度
encoder 向这几个 code 靠拢(commitment loss)
其余 code 越来越远,彻底死亡(坍塌完成)

工业实践中的防坍塌 checklist(综合多篇论文)

手段原理效果注意事项
① K-Means 初始化 训练前先用 K-Means 聚类 encoder 输出,将聚类中心作为 codebook 初值 ⭐⭐⭐⭐ 极有效,从源头保证初始覆盖率 需要先 forward 一轮收集数据;逐层初始化(第 l 层用第 l-1 层的残差做 K-Means)
② Dead Code 重置 周期性检测长期未被使用的 code(如 20 个 iteration 内使用次数 = 0),随机替换为高密度区域的 encoder 输出 ⭐⭐⭐⭐ Eugene Yan 实验验证有效 LRU 策略(least-recently-used)效果最好;触发阈值需调
③ EMA 更新 codebook 码字 = 映射到该 code 的 encoder 输出的滑动平均,更新平滑 ⭐⭐⭐ 有效降低训练抖动 decay=0.99 是常用值;需配合 commitment loss 训 encoder
④ 大 batch size batch 越大,每个 code 在一次 forward 中被选到的概率越高,梯度更稠密 ⭐⭐⭐ Eugene Yan 实验验证 尽量用显存能支持的最大 batch,如 ≥ 1024
⑤ Rotation Trick 替换 STE,减小量化误差导致的梯度偏差,让 encoder 能更准确地学习 ⭐⭐⭐ 对 unique ID 比例提升明显 需要额外计算旋转矩阵,开销略高
⑥ L2 归一化 对 encoder 输出和 codebook 都做 L2 normalization,在单位球面上量化(cosine 距离而非 L2) ⭐⭐⭐ 约束了 encoder 输出的"漂移"范围 可能牺牲幅度信息;不适合所有任务
⑦ 学习率 Warmup 训练初期用小学习率,防止 encoder 输出分布突变导致 codebook 跟不上 ⭐⭐ ICML 2023 推荐,配合 cosine decay cosine warmup + decay 通常效果最好
⑧ Affine 重参数化 给 codebook 加一个共享的仿射变换(mean + scale),让未被选中的 code 也能随着分布漂移而移动 ⭐⭐⭐ ICML 2023 核心贡献 需要调 affine lr scale 超参

3.6 完整训练流程(Step-by-Step)

  • 0
    数据准备:收集所有 item 的 embedding

    用冻结的大模型(如 MLLM、Sentence-BERT)对所有 item 的文本/多模态内容进行编码,得到高维 embedding $\{s_i\}$。此步骤不涉及 RQ-VAE 训练,仅离线跑一次。

  • 1
    Codebook K-Means 初始化(关键!)

    先用一部分数据 forward 一遍 encoder,收集所有 encoder 输出 $z_e$。对第 1 层 codebook 做 K-Means,用聚类中心初始化码字。然后计算残差 $r_1$,对第 2 层 codebook 做 K-Means,以此类推。

    # 逐层 K-Means 初始化
    residual = z_e
    for l, vq_layer in enumerate(rqvae.vq_layers):
        kmeans = KMeans(n_clusters=K).fit(residual.numpy())
        vq_layer.embeddings.data.copy_(torch.tensor(kmeans.cluster_centers_))
        with torch.no_grad():
            e_c = vq_layer.get_nearest(residual)
            residual = residual - e_c  # 残差传给下一层
  • 2
    正常训练循环

    使用 AdamW(lr ≈ 1e-4,配合 cosine LR warmup),在每个 batch 中:

    1. Forward:$s \to z_e \to [c_1,\ldots,c_L] \to z_q \to \hat{s}$
    2. 计算 $L_{\text{recon}} + L_{\text{vq}}$,backward
    3. 若用 EMA:手动更新 codebook(在 optimizer.step 之前或之后)
    4. 检测 dead code:统计本 batch 每个 code 的使用次数,未使用次数超阈值则重置
  • 3
    训练监控(见 §3.8)

    同步监控:recon loss、vq loss、codebook perplexity(信息熵)、unique ID 比例。其中 unique ID 比例是检测坍塌的最直接指标。

  • 4
    推理:冻结 encoder + codebook,生成 SID

    训练收敛后,encoder 和 codebook 冻结。对每个 item 做一次 forward,输出 $[c_1,\ldots,c_L]$ 即为该 item 的 Semantic ID。

3.7 关键超参数调优指南

超参数典型值影响调优建议
$\beta$(commitment cost) 0.25 ~ 1.0 encoder 与 codebook 的对齐强度 0.25 是经典默认值(VQ-VAE 原论文);Eugene Yan 实验发现 0.5 在重建和唯一 ID 之间取得最佳平衡;$\beta=1.0$ 等价于 EMA 更新
codebook 大小 $K$ 256 ~ 1024 词汇量 vs 坍塌风险 每层 256 是广泛验证的鲁棒选择;超大 codebook(>1024)在数据不足时容易坍塌
量化层数 $L$ 2 ~ 4 精度 vs 计算量 3 层为工业常用(DAS、TIGER);层数越多,后面几层的残差越小,收益递减
latent dim $d$ 32 ~ 64 信息瓶颈宽度 压缩率约 8x~32x;过小导致信息丢失,过大量化难度增加
学习率 lr 1e-4 ~ 1e-3 训练稳定性 配合 cosine warmup;过高的 lr 是 codebook 坍塌的首要元凶
batch size ≥ 1024 codebook 更新的统计稳定性 尽量用显存能支持的最大值;小 batch 会导致 EMA 统计不稳定
EMA decay $\gamma$ 0.99 ~ 0.999 codebook 更新的平滑程度 越接近 1.0 更新越慢越稳;过低则 codebook 跟随噪声
dead code 阈值 20 ~ 100 个 iter 何时重置死亡 code LRU 策略(≥20 iter 未使用则重置)是 ICML 2023 推荐的最优策略

3.8 训练监控指标

📉 loss/reconstruction

重建损失,衡量 encoder→量化→decoder 链路的信息保真度。应持续下降并趋于稳定。

📉 loss/vq

codebook loss + commitment loss 之和。编码器输出与 codebook 的距离。正常训练中应下降;若 vq_loss 异常低但 recon_loss 高,可能已经坍塌。

📊 codebook perplexity(困惑度)

$\text{PPL} = 2^{H(p)}$,其中 $H(p)$ 是 code 使用分布的熵。接近 $K$ 表示均匀使用,接近 1 表示完全坍塌。这是最重要的健康指标。

📊 unique ID proportion

当前 batch 中不同 ID 的比例(Eugene Yan 推荐监控),直接衡量"不同 item 能否被区分"。低于 50% 要警惕。

📊 avg residual norm

所有量化层之后剩余残差的均值范数。越低说明 codebook 能更好地逼近 encoder 输出。

🔢 active code count

每隔若干步统计实际使用的 code 数量(≥1 次被选中)。健康状态应接近 $K$;如果只有 10% 被使用则已经严重坍塌。

✅ 实战经验小结(综合多篇论文与工业实践)
  • K-Means 初始化是最有效的单一手段,必做
  • EMA 更新 codebook + dead code 重置是工业落地的标配组合
  • Large batch(≥1024)对 EMA 统计稳定性至关重要
  • β = 0.25 是安全起点,可根据 recon loss 和 perplexity 的平衡微调
  • Rotation Trick 是 STE 的更优替代,追求质量时优先尝试
  • 监控 perplexityunique ID proportion,两者都高才算收敛良好

3.9 训练完成后:线上推理流程

训练收敛后,encoder 和 codebook 全部冻结,decoder 也可以丢弃(推理不需要重建)。推理只做两步:

  • 1
    Encoder forward:$s \to z_e$ 把 item 的 MLLM embedding $s_i$ 送进冻结的 encoder,得到低维 latent $z_e$。
  • 2
    逐层 argmin 查 codebook → 输出 SID
    @torch.no_grad()
    def encode_to_semantic_ids(rqvae, s):
        residual = rqvae.encoder(s)   # z_e
        ids = []
        for vq_layer in rqvae.vq_layers:
            distances = compute_distances(residual, vq_layer.embeddings)
            c = distances.argmin(dim=-1)      # 选最近 code
            ids.append(c)
            residual = residual - vq_layer.embeddings[c]  # 计算残差
        return torch.stack(ids, dim=-1)       # shape: (N, L)
    也可同时输出连续表示 $z_q = \sum_l e_{c_l}$ 作为 dense feature 用于 CTR/精排。
唯一 ID 冲突处理:实际中 $L$ 层 codebook 不能保证每个 item 都有唯一的 $[c_1,\ldots,c_L]$(约有 10% 冲突,Eugene Yan 实验数据)。工业解法:在第 $L+1$ 层追加一个顺序 token(按碰撞发生顺序分配 0/1/2...),确保 item 唯一可区分,但不增加语义层数。
🤝
§4 注入协同信号

4.1 为什么需要注入协同信号?

上面讲的 SID 生产过程,全程只用了内容(图文视频文本)。但推荐系统的核心是行为——用户到底点击了什么、购买了什么,这才是最有价值的信号。

🔴 没有 CF 信号的 SID

  • 只反映"内容相似",不反映"被同一类用户喜欢"
  • 内容相似但用户群完全不同的商品,会被量到同一个 code
  • 下游 CTR 模型拿 SID 做特征,效果受限

No-Aligned SID DAS 论文称之为

🟢 注入 CF 信号后的 SID

  • 同时反映内容相似 + 行为相似
  • 被同一类用户点击的物品,SID 更接近
  • SID 的"分桶"逻辑更符合推荐系统目标

Aligned SID 注入 CF 后得到

🧩 问题举例:为什么纯内容 SID 会出错?

广告 A:空气炸锅(健康烹饪,家庭主妇购买)

广告 B:烤箱(烘焙爱好者购买,价格更高)

广告 C:空气炸锅配件(配件,买空气炸锅的人才会买)

纯内容 SID 的结果:A 和 C 内容语义最像 → SID 最接近。B 内容不像 → SID 最远。

但 CF 信号告诉我们:买 A 的人也经常买 B(都在买厨房电器);买 C 的人只买 C(配件场景不同)。

注入 CF 后理想的 SID:A 和 B 的 SID 应该更近(行为相似),C 的 SID 应该被推远。

接下来介绍三种注入方式:CF First、Alignment First、一阶段联合(DAS)。

4.2 CF First:先有 CF embedding,再监督量化——以 LETTER 为代表

CF First 训练流程
阶段 1
训 CF 模型
CF embedding
$i_{cf}$(冻结)
阶段 2
内容 embedding $s_i$
RQ-VAE 量化
→ $z_i$, SID
+
对齐 loss
$\|z_i - W(i_{cf})\|^2$

详细步骤:

  • 1
    先训练 CF 模型(阶段 1,独立完成) 只用用户 ID、物品 ID、点击/购买行为,训一个双塔召回模型(DSSM / GCN 等),得到每个物品的 CF embedding $i_{cf} \in \mathbb{R}^{d_{cf}}$。训练完后冻结不再更新
  • 2
    再训练 SID 量化器,加入 CF 对齐 loss(阶段 2) 输入内容 embedding $s_i$,经过 encoder + RQ-VAE 得到量化后的连续表示 $z_i$。 在量化训练的损失里加一项: $$\mathcal{L} = \mathcal{L}_{\text{Sem}} + \lambda \cdot \|z_i - W(i_{cf})\|^2$$ 其中 $W$ 是线性投影层(对齐维度),$i_{cf}$ 来自第 1 步的冻结 CF 模型。
💡 "对齐"发生在哪里? 对齐的是 $z_i$(量化后的连续表示)和 $i_{cf}$(冻结 CF embedding)。
不是直接对齐离散 SID 编号——因为整数 index 无法做连续空间的距离约束。

这个 loss 会反传梯度到:
  • Codebook 向量(让码字靠近 CF 语义)
  • Encoder 参数(让 encoder 输出靠近 CF 空间)
最终结果:量化器的"分桶方式"被 CF 信号重新塑形,SID 隐式带上了 CF 结构。
🧩 用例子看 CF First 的效果

CF 模型里,广告 A(空气炸锅)和 B(烤箱)的 $i_{cf}$ 很近(行为相似)。

CF First 训练后,量化器被约束:$z_A$ 靠近 $i_{cf}(A)$,$z_B$ 靠近 $i_{cf}(B)$。

因为 $i_{cf}(A)$ 和 $i_{cf}(B)$ 本来就近 → $z_A$ 和 $z_B$ 也变近 → A 和 B 最终被量到相近的 code。

广告 C 的 $i_{cf}(C)$ 被推远 → 即使 C 内容像 A,最终也量到不同的 code 桶。

⚠️ CF First 的问题
  • 两阶段解耦:CF 模型训完冻结,quantizer 只能"追"老师,两边不联合更新,存在优化目标 gap
  • CF embedding 有偏:CF 模型本身存在流行度偏差(20% 的热门广告占 80% 的交互),如果直接拿来对齐,会把偏差注入 SID codebook,导致冷门物品的 SID 质量差
  • 互信息受限:单向 loss $\|z_i - i_{cf}\|^2$ 不能充分最大化 SID 和 CF 之间的互信息

4.3 Alignment First:先对齐内容 embedding,再量化——以 QARM 为代表

Alignment First 训练流程
阶段 1
训 CF 模型 → 冻结
阶段 2
内容 $s_i$
对齐网络 $g(\cdot)$
$\tilde{s}_i = g(s_i)$
$\tilde{s}_i \approx i_{cf}$
阶段 3
RQ-VAE 量化 $\tilde{s}_i$
→ SID

详细步骤:

  • 1
    预训 CF 模型,冻结(同 CF First)
  • 2
    训练对齐网络 $g(\cdot)$,把内容 embedding 映射到 CF 空间 用一个 MLP 或轻量 Transformer 作为对齐器,目标是让: $$\tilde{s}_i = g(s_i) \approx W(i_{cf})$$ 也就是把原始内容向量 $s_i$ 改造成一个"更像 CF 空间"的向量 $\tilde{s}_i$。
  • 3
    对 $\tilde{s}_i$ 做 RQ-VAE 量化 不再量化原始 $s_i$,而是量化已经对齐过的 $\tilde{s}_i$: $$sid_i = \text{RQ-VAE}(\tilde{s}_i)$$ 输入本身已带 CF 语义,量化出来的 SID 自然也更接近 CF 结构。
🧩 用例子看 Alignment First 的效果

原始内容空间里:A 和 C 很近(都是炸锅类),B 较远(烤箱)。

经过对齐网络 $g(\cdot)$ 之后:$\tilde{s}_A$ 和 $\tilde{s}_B$ 被拉近(CF 行为相似),$\tilde{s}_C$ 被推远。

第 3 步对这组"已重排"的向量做 RQ-VAE,量化结果就自然反映了 CF 结构。

⚠️ Alignment First 的问题
  • 量化会丢信息:即使 $\tilde{s}_i$ 对齐得很好,后续 RQ-VAE 还是会引入量化误差,对齐效果打折
  • 两步仍然割裂:对齐网络和量化器分开训练,优化目标不一致
  • CF embedding 有偏:和 CF First 一样,直接用有偏 CF 会污染 SID

4.4 一阶段联合训练:同时优化量化和对齐——DAS 的做法

DAS 的核心洞察是:CF First 和 Alignment First 都在分两步做,两步之间的优化目标是割裂的。更好的做法是把量化、CF 建模、对齐放进同一个训练循环里联合优化

DAS 一阶段联合训练
UISM
双侧 RQ-VAE
$s_i \to z_i$, SID
ICDM
去偏 CF 模型
$c_u^{int}$, $c_i^{pro}$
MDAM
多视图对比对齐
$z_i \leftrightarrow c_i^{pro}$
联合损失
$\mathcal{L}_{\text{Sem}} + \alpha\mathcal{L}_{\text{CF}} + \beta\mathcal{L}_{\text{Align}}$

DAS 的三个核心创新:

  • 1
    CF 去偏(ICDM):用去偏的 CF 信号做对齐 普通 CF 模型里,热门广告(20% 的广告贡献 80% 的收入)的 embedding 会被反复更新,偏差严重。DAS 用因果图把 CF 表示拆成:
    • 用户真实兴趣 $c_u^{int}$(去掉从众偏差)
    • 广告内容质量 $c_i^{pro}$(去掉流行度偏差)
    只用 $c_u^{int}$ 和 $c_i^{pro}$ 来指导 SID 对齐,避免把偏差注入 codebook。
    ⚠️ 为什么不去偏会有问题? 如果直接拿有偏的 CF embedding 对齐,codebook 里大量码字会被热门广告"霸占",冷门广告的 code 质量变差,codebook 利用率降低(perplexity 下降)。DAS 实验显示,去偏后 codebook 使用率和 perplexity 均显著提升。
  • 2
    多视图对比对齐(MDAM):三种 InfoNCE loss DAS 不是简单一个 $\|z_i - W(i_{cf})\|^2$,而是设计了三种对比对齐任务:
    对齐任务正例含义
    Dual U2I 用户实际点击的 <U, I> 对 $z_u \leftrightarrow c_i^{pro}$(用户 SID ↔ 广告 CF);$c_u^{int} \leftrightarrow z_i$(双向)
    Dual U2U / I2I 同一实体的 SID 表示 vs CF 表示 $z_i \leftrightarrow c_i^{pro}$:让同一广告的 SID 空间和 CF 空间一致
    Co-occurrence 被同一用户点击的两个广告 行为共现 → SID 也应该相近;把行为聚类结构注入 SID 空间

    这三类 InfoNCE loss 从不同角度最大化 SID 和 CF 信号之间的互信息。

  • 3
    双侧量化(Dual Learning):用户和广告 SID 互相约束 DAS 不只对广告做 SID,也对用户做 SID(User Semantic Model, USM)。用户和广告的量化器同步训练、互相对齐,形成"用户 SID ↔ 广告 SID"的双向约束。

    实验验证:双侧 SID(TIGER++)比单侧(TIGER+)在生成式推荐上效果更好。

✅ 一阶段的本质优势 因为量化器(UISM)、CF 模型(ICDM)、对齐机制(MDAM)在同一个 training loop 里用同一个联合 loss 更新,所以:
  • CF 信号不是冻结的"老师",而是和 SID 同步进化
  • 量化器在对齐的同时做量化,不存在"先对齐再量化"的信息丢失
  • 三种多视图对比任务充分最大化互信息,效果比单一 $\ell_2$ loss 更强
📊
§5 三种方案对比总结
维度 CF First(LETTER) Alignment First(QARM) 一阶段联合(DAS)
训练阶段数 2 阶段 3 阶段 1 阶段
对齐发生在哪 量化后连续表示 $z_i$
和冻结 CF embedding
量化前,先对齐内容 embedding $s_i \to \tilde{s}_i$ 量化和对齐同步进行,联合 loss
CF 模型是否更新 ❌ 冻结 ❌ 冻结 ✅ 和 SID 联合更新
是否去偏 ❌ 直接用有偏 CF ❌ 直接用有偏 CF ✅ ICDM 分离兴趣/流行度偏差
对齐方式 $\ell_2$ loss(MSE) $\ell_2$ loss(MSE) InfoNCE 多视图对比(U2I + I2I/U2U + co-occ)
用户侧 SID ❌ 通常没有 ❌ 通常没有 ✅ 双侧量化(用户 + 广告)
核心缺陷 解耦训练,互信息受限;CF 有偏污染 codebook 量化会丢信息;两步仍然割裂;CF 有偏 训练复杂,超参多;工程成本高
代表论文 LETTER(CIKM 2024) QARM(快手 2024) DAS(快手 2025)

一句话总结三种方案

  • CF First = 先有行为老师(CF embedding),再用老师塑形量化器的输出空间
  • Alignment First = 先把内容 embedding 翻译成"行为语言",再对这个翻译好的表示做量化
  • DAS(一阶段) = 量化器、CF 模型、对齐三者同时训练,老师也在更新,互信息最大化

完整 SID 生命周期

从原始数据到下游应用的完整路径
多模态内容
图文视频
LLM 摘要
PLM 编码
$s_i \in \mathbb{R}^{256}$
RQ-VAE 量化
(联合 CF 对齐训练)
SID $[c_1,c_2,c_3]$
+ $z_i$(连续)
SID
Prefix-Ngram
稀疏 ID 特征
CTR/CVR
精排模型
SID
用户历史 SID 序列
Cross SID 特征
召回 / 粗排
SID
作为 token ID
生成式推荐
(TIGER++)