Meta生成式推荐:重塑万亿级推荐系统
Meta生成式推荐(Generative Recommenders, GRs)的核心设计 不仅是一个新模型,更是一次对传统推荐系统范式的根本性重塑。
GitHub - meta-recsys/generative-recommenders: Repository hosting code for "Actions Speak Louder than Words: Trillion-Parameter Sequential Transducers for Generative Recommendations" (https://arxiv.org/abs/2402.17152).
核心理念:统一与序列化
Meta GRs 的基石是两大理念:
- 统一特征空间:将推荐系统中所有异构的特征(用户行为、物品ID、属性等)都转化为一种统一的格式——时间序列上的事件(Event)。这相当于为推荐系统创造了一种“通用语言”。
- 序列直推(Sequential Transduction):将召回和排序任务统一重塑为序列生成任务。模型接收一个历史事件序列,然后预测下一个或多个事件。
一、 输入重塑:将一切视为序列事件
- 稀疏特征(用户行为、物品ID等):作为主时间序列的核心。每个用户交互(点击、购买、点赞、点踩)都按时间顺序作为一个事件插入序列。格式为:
[Item_ID, Action_Type, Timestamp]
。 - 缓慢变化特征(用户属性等):作为辅助事件,在其发生变化的时间点插入序列(如
[Event_User_Attribute, Gender_Female]
)。 - 稠密特征(CTR等统计特征):被主动舍弃。论文认为,一个强大的序列模型可以从用户的行为历史中自行推断出这些统计信息,无需人工注入,从而极大简化系统复杂度。
二、 任务重塑:召回与排序的统一视角
召回任务:被定义为掩码语言模型任务。模型学习预测序列中下一个可能出现的物品Token。通过掩码机制忽略负反馈和非物品事件,只对正向物品进行预测,然后用预测结果检索全库。
排序任务:通过巧妙的序列改造实现目标感知。将序列格式构造为 [Item1, Action1, Item2, Action2, ...]
。
- 在 Action 位置进行掩码,让模型预测用户行为(如点击/购买的概率),此概率直接作为排序分数。
- 这种结构使得目标Item(在Item位置)能通过自注意力机制与整个用户历史进行早期、充分的特征交叉,完美模拟了传统排序模型的功能。
召回怎么处理大量ID
1. 输入 (Input) 和输出 (Output) 的定义
假设我们有一个固定的用户行为序列(已经按照之前讨论的方式统一化了):
[token_A, token_B, token_C, token_D, token_E]
模型的任务就是标准的语言模型任务:根据前面的 tokens 预测下一个 token。
- 输入 (Input
X
):[token_A, token_B, token_C, token_D]
- 期望的输出/监督信号 (Target
Y
):token_E
关键点: 这个 (X, Y)
样本对是固定不变的。它来自用户真实的历史行为日志。token_E
就是用户真实交互过的下一个物品(正例)。
2. 训练过程与负采样的角色
现在,我们要训练模型,让它输出的概率分布里 token_E
的概率尽可能高。
- 模型计算:将输入
X
送入 HSTU Encoder,模型会为词表中的每一个 token 计算一个分数(logit),形成一个巨大的分数向量S
(维度为十亿级)。 - 计算损失函数的挑战:我们需要基于分数向量
S
计算损失(如交叉熵损失),鼓励token_E
的分数高,其他所有 token 的分数低。直接计算涉及十亿维的 Softmax,不可能。 - 负采样登场:负采样是一种损失函数的近似计算方法,而不是改变数据本身。
- 我们从全量词表中随机抽取一小部分(比如 1000 个)负样本 tokens(
token_N1
,token_N2
, ...,token_N1000
)。这些 tokens 是用户在此刻没有交互过的物品。 - 现在,我们不再计算十亿维的 Softmax,而是只计算一个 “迷你版”的 Softmax,它只包含 1 个正样本 (
token_E
) 和 1000 个负样本。 - 损失函数就变成了:让模型为
token_E
打高分,为token_N1
到token_N1000
打低分。
- 我们从全量词表中随机抽取一小部分(比如 1000 个)负样本 tokens(
可能采用的方法:
Sampled Softmax / Contrastive Learning:模型最后一层只有一个神经元(或少量神经元),它的任务不是预测“是哪一个”,而是判断“输入对”的相似度。
召回任务的核心是学习一个用户表征和一个物品表征,使得正样本用户-物品对的表征在向量空间中是相近的。这本质上是一个对比学习(Contrastive Learning) 任务。
模型结构:编码器(Encoder)而非分类器(Classifier)
- HSTU模型的作用:它是一个编码器(Encoder)。它的任务不是直接输出一个概率分布,而是接收一个序列,输出一个固定大小的向量(例如512维)。这个向量就是序列的“含义总结”。
- Item的表示:每个Item也有一个对应的嵌入向量(Embedding),存储在一个巨大的嵌入表中。这个向量就是该Item的“含义总结”。
对于一条训练数据 (用户序列, 下一个物品)
:
- 生成用户向量:将
用户序列
输入HSTU编码器,得到一个用户向量u
(假设是512维)。 - 获取正样本向量:从嵌入表中查找
下一个物品
对应的物品向量v+
(也是512维)。 - 采样负样本向量:从全量物品库中随机采样N个(例如1000个)物品,并从嵌入表中取出它们对应的向量
v1-, v2-, ..., vN-
。 - 计算相似度:计算用户向量
u
与正样本向量v+
的相似度(如点积s+ = u · v+
),同时计算与所有负样本向量的相似度(s1- = u · v1-, ..., sN- = u · vN-
)。
现在,我们得到了一个包含 1个正分和 N个负分 的分数集合[s+, s1-, s2-, ..., sN-]
。 - 计算损失(Sampled Softmax Loss):
- 对这个分数集合应用Softmax,目标是让
s+
的概率尽可能接近1。 - 损失函数就是标准的交叉熵损失:
L = -log( exp(s+) / (exp(s+) + Σ exp(si-)) )
- 对这个分数集合应用Softmax,目标是让
这个过程的关键在于:模型(HSTU编码器和物品嵌入表)的参数更新,是为了让 u
和 v+
越来越相似,同时让 u
和所有 vi-
越来越不相似。
训练完成后:
- 用户向量:用HSTU编码器对用户当前序列进行编码,得到向量
u
。 - 物品向量:所有物品的嵌入向量
v_i
已经预存在嵌入表中。 - 召回:使用近似最近邻(ANN)搜索,从所有
v_i
中找出与u
最相似的Top-K个。
3. 掩码 (Mask) 的作用
掩码 M
是用来处理序列中哪些位置需要被预测的。
在我们的序列 [token_A, token_B, token_C, token_D, token_E]
中,可能 token_B
是一个用户属性(如性别变化),token_D
是一个负反馈行为(点踩)。我们不希望模型去学习预测这些 token。
- 我们会将
M
在token_B
和token_D
的位置设置为 0。 - 在计算损失时,跳过这些位置。模型仍然能看到这些 token 作为上下文,但不需要预测它们。
- 只有
M=1
的位置(如token_E
,一个正向交互)才会参与上述的“考试”,才会进行负采样损失计算。
总结
- 输入输出是固定的:基于用户真实、连续的历史行为构建
(X, Y)
样本对。 - 负采样是损失计算技巧:它是一种高效的数学近似,用于解决十亿级分类问题的 Softmax 计算瓶颈。它不会改变输入数据或学习目标。
- 掩码控制学习目标:决定序列中的哪些位置需要模型付出“学习成本”去预测。
- 最终目的:通过这种方式,模型学会的不是“避免预测某些负样本”,而是从正样本和无限多种负样本的组合中,学习用户行为序列的内在模式和偏好,从而得到一个强大的序列编码器(Encoder)。这个Encoder产出的向量最终用于向量检索式的召回。
如何得到一个代表用户的向量,并用它来检索?
答案在于对模型输出的巧妙利用和整个系统的流水线设计。这个过程可以分为训练和推理两个截然不同的阶段。
阶段一:训练 - 学会强大的序列编码能力
在这个阶段,目标不是直接得到用户向量,而是训练一个强大的序列编码器(Encoder)。
- 输入:用户的完整行为序列
S
(例如[token_1, token_2, ..., token_N]
)。 - 任务:进行Next-Token Prediction。具体来说,对于序列中的每个位置
t
,模型需要根据前面的所有 tokens (token_1
到token_{t-1}
) 来预测当前被掩码的 tokentoken_t
。 - 输出:对于序列中的每一个位置,模型都会产生一个对应的隐藏状态向量
h_t
。您可以把它理解为模型在处理到第t
个token时,对之前所有上下文信息的总结和编码。
这个训练过程的真正产物,是一个已经学会理解和编码用户行为序列的HSTU模型权重。 它现在是一个强大的“序列理解器”。
阶段二:推理/召回 - 生成用户向量并进行检索
在推理阶段,我们使用训练好的模型来为当前用户生成一个表征向量,并用它进行检索。
1. 如何生成用户向量?
这是最关键的一步。有几种常见策略来选择哪个隐藏状态 h_t
作为用户向量 u
:
- 最后位置输出(Most Common):将用户最新的完整序列输入模型,然后直接取最后一个Token位置对应的隐藏状态
h_last
作为用户向量u
。- 为什么? 最后一个隐藏状态理论上汇聚了之前所有序列的信息,最能代表用户的当前状态和最新兴趣。
- 特殊Token输出:在序列的开头或结尾添加一个特殊的
[CLS]
Token(类似于BERT)。训练时,这个Token的任务就是汇聚整个序列的信息。最终就用这个[CLS]
Token对应的输出作为用户向量u
。 - pooling:对序列中所有Token的隐藏状态进行平均池化(Mean Pooling)或最大池化(Max Pooling),将结果作为用户向量
u
。
Meta的论文很可能采用第一种策略,即使用最后位置的输出,因为这最符合自回归模型的直觉。
2. 如何检索Item?Item的向量从哪里来?
召回的本质是衡量用户向量 u
和所有候选Item向量 v_i
的相似度。那么Item的向量从哪里来呢?
- 来源:Item向量来自同一个HSTU模型的Embedding查找表(Embedding Lookup Table)。这个巨大的表存储了所有Item ID对应的向量(即它们的初始Embedding)。
- 过程:
- 有一个包含十亿Item的列表
I = [item_1, item_2, ..., item_1B]
。 - 通过Embedding表,可以查到每个Item的向量:
V = [v_1, v_2, ..., v_1B]
。 - 计算用户向量
u
与所有Item向量v_i
的相似度(通常用内积或余弦相似度)。 - 返回相似度最高的K个Item作为召回结果。
- 有一个包含十亿Item的列表
然而,对十亿向量进行暴力计算是不可能的。 所以实际工程中会使用近似最近邻(ANN)搜索库(如Faiss、HNSW)。这些库可以预先对Item向量 V
建立索引,然后在毫秒级时间内快速找到与 u
最相似的Top-K个向量。
总结:完整的召回流水线
让我们串联起整个流程,以一个新用户为例:
- 准备序列:用户产生了新行为,系统将其行为更新到他的历史序列
S
中。 - 生成用户向量:将序列
S
输入已训练好的HSTU模型。- 模型为序列中的每个位置输出隐藏状态。
- 取最后一个位置的隐藏状态
h_last
作为当前用户的表征向量u
。
- 检索:将用户向量
u
提交给ANN检索系统。- 检索系统在预建好索引的全体Item向量库
V
中进行快速搜索。 - 返回与
u
最相似的Top-K个Item作为召回结果。
- 检索系统在预建好索引的全体Item向量库
因此,生成式训练和向量化检索是流程中的两个不同环节:
- 训练环节:使用生成式目标(Next-Token Prediction)来训练模型,使其获得强大的序列编码能力。
- 推理环节:利用训练好的模型作为特征提取器,产出用户向量和Item向量,再通过高效的向量检索技术得到结果。
这种设计巧妙地规避了在推理时进行生成式预测的计算灾难,同时享受了生成式预训练带来的模型强大表征能力的红利。
三、 核心架构:HSTU(为推荐而生的Transformer)
Hierarchical Sequential Transduction Unit (HSTU)
HSTU不是一个标准的Transformer,而是一个深度定制的高效Encoder,其核心优化在于:
-
Pointwise Aggregation注意力机制:
- 弃用Softmax:为了解决Softmax归一化会抹杀用户偏好强度信息的问题。(例如点击100次 vs 点击10次,当其它都是0次,softmax之后 点击概率都是1,两者没有差别)
- 直接聚合:使用原始注意力分数进行加权求和,完美保留“序”和“量”的信息,这对多目标预估(如点击率、观看时长)至关重要。
- 使用Layer Norm:为了解决弃用Softmax后的数值不稳定问题。
LN(V) = γ * (V - μ) / σ + β。保留更多量的信息。
可学习的参数γ
和β
随后会重新缩放和偏移这个分布,以适应下一层网络的需要。
-
长期兴趣建模:
- 在底层额外计算一个向量
G
,用于压缩和表征用户的长期、全局行为偏好,为Attention计算提供强大的先验信息。
- 在底层额外计算一个向量
-
显式特征交叉:
- 在结构设计中加入了元素级乘法,使底层原始特征 与 高层聚合特征 进行显式交叉,确保信息深度融合。
-
极致的工程优化:
- 减少线性层、融合算子Fusion(减少访问存储)、采用Rowwise AdamW优化器等,极大减少了内存占用(尤其是激活内存)。
- 随机长度(Stochastic Length):在训练时随机截断长序列,用极小的精度损失换取巨大的计算效率提升。
1. 减少线性层:从6个到2个
这里对比的是HSTU和标准Transformer Block的结构。
-
标准Transformer Block 通常包含:
- 一个多头自注意力层(MHSA)
- 一个前馈网络层(FFN)
如果我们拆开看里面的线性层(Linear或Projection层):
- MHSA:包含Q、K、V三个投影层(3个Linear),有时输出还有一个投影层(1个Linear),所以MHSA部分通常是 3或4个Linear。
- FFN:通常由两个Linear层组成(例如上投影和下投影,即
Linear1 -> Activation -> Linear2
),这是 2个Linear。 - 总计:一个标准Transformer Block大约有 5到6个Linear层。
-
HSTU Block 的结构(根据公式):
- Pointwise Projection:
U^l = f_{in}(X^l)
-> 1个Linear - Spatial Aggregation:
A^l = f_{agg}(U^l)
-> 这是注意力计算,本身不包含额外的Linear参数。 - Pointwise Transformation:
X^{l+1} = f_{out}(U^l \odot f_{mid}(A^l))
-> 这里f_{mid}
和f_{out}
从描述看是1个Linear层(f_{mid}
)和一个可选的变换。
关键点:HSTU用
U^l \odot f_{mid}(A^l)
(元素乘法)这个操作,替代了FFN的两个Linear层。元素乘法的计算成本和内存开销远低于一个Linear层。
总计:HSTU一个Block大约只有 1~2个Linear层。 - Pointwise Projection:
减少收益
- 减少参数量:线性层的参数是
(d_{in} * d_{out})
。减少线性层数量直接减少了 billions 级别的参数量。 - 减少激活内存(Activation Memory):这是最大的收益。在训练进行反向传播时,需要保存每一层的输入(激活值)来计算梯度。更少的层意味着需要缓存的中间激活结果更少,极大地降低了GPU内存的压力。
2. 融合算子(Kernel Fusion)
问题:深度学习框架(如PyTorch)默认会将操作分解为多个小的、独立的CUDA内核(Kernel)来执行。例如,一个 Linear -> Bias -> ReLU
会依次调用三个独立的内核。
代价:每个独立的内核调用都需要:
- 从GPU全局内存(HBM)中读取输入数据。
- 进行计算。
- 将结果写回HBM。
如果下一个内核需要上一个内核的结果,它又必须从HBM中读出来。这个过程被称为内存访问瓶颈。大量的时间花在了数据的读写上,而不是实际的计算上。
解决方案:融合算子
- 做法:HSTU将多个连续的小操作(例如:
Linear + Bias + SiLU
)手工编写成一个单一的、复杂的CUDA内核。 - 好处:
- 极大减少内存读写:中间结果直接在GPU的高速寄存器(Register)或共享内存(Shared Memory)中传递,避免了与低速HBM的频繁交互。
- 提升速度:减少了内核启动的开销和内存等待时间。
- 降低内存占用:因为中间结果不用写回HBM保存了。
举例:没有融合时,需要为A、B、C在HBM中分配空间。融合后,只需要分配输入和输出的空间,中间结果在芯片内部处理掉了。
// 未融合 (多个Kernel)
Input -> Kernel1(Linear) -> A (in HBM) -> Kernel2(Bias) -> B (in HBM) -> Kernel3(SiLU) -> Output// 融合后 (单个Kernel)
Input -> Custom_Fused_Kernel(Linear+Bias+SiLU) -> Output
3. 随机长度(Stochastic Length)
问题:注意力机制的计算复杂度是 O(序列长度^2)
。用户序列长度分布不均,少数用户有超长序列(例如10k个token),这些超长序列会主导整个训练过程的时间。
解决方案:
- 做法:在训练时,对每个批次(Batch)中的序列,随机截断到一个更短的长度
L
(例如2k)。L
是一个超参数。 - 如何随机:不是简单粗暴地截取最后L个token。论文中的策略是:以概率
p
截取一个随机起始点的连续L个token,以概率(1-p)
保留完整的序列。这确保了数据的多样性,避免总是丢失早期历史。
为什么能极大提升效率?
- 计算量从
O(10000^2) = 100M
降到O(2000^2) = 4M
,计算量变为原来的 1/25。 - 由于计算复杂度是平方关系,处理少数极长序列所节省的时间,远远超过了处理大量短序列所增加的时间。这是一种用极小的精度损失(因为模型依然能看到足够长的序列)换取巨大效率提升的策略。
总结
这些优化手段共同作用,目标高度一致:
优化手段 | 主要对抗的问题 | 达成的效果 |
---|---|---|
减少线性层 | 参数爆炸 & 激活内存爆炸 | 直接减少模型参数量和最耗内存的激活值数量。 |
融合算子 | 内存访问瓶颈 | 减少HBM读写次数,提升计算效率,间接降低内存需求。 |
随机长度 | 计算复杂度平方爆炸 | 直接降低计算量,避免极长序列拖慢整个训练。 |
正是通过这些在算法层和工程层每一个环节的极致压榨,Meta才成功地将原本不可能实现的万亿参数推荐模型训练变成了现实。
一些问题
减少线性层必然会付出代价,否则Transformer最初就不会那样设计。这个比较是合理的,但需要理解其背后的设计哲学差异:Transformer追求的是通用性和表现力,而HSTU追求的是在特定任务上的极致效率。
1. Transformer为什么需要FFN?(6线性层设计)
标准Transformer(Encoder/Decoder)的设计目标是成为一个通用的序列建模工具,用于处理语言、代码等各种任务。它的强大功能来源于两个核心子层的分工:
-
多头自注意力层(MHSA):
- 作用:信息交互。负责捕捉序列中所有元素两两之间的关系(无论距离多远),是一种“全局混音台”。
- 局限:注意力机制本质上是线性变换和加权求和的组合,其表示能力有限。它擅长重组信息,但不擅长进行复杂的非线性变换。
-
前馈网络层(FFN):
- 作用:非线性变换和特征升华。这是模型的“肌肉”,负责对注意力层聚合好的信息进行深度加工,将其映射到更高维、更抽象的特征空间。
- 为什么需要两个线性层?:单层线性变换无法实现复杂的非线性函数。FFN的经典结构
Linear -> Activation (e.g., ReLU) -> Linear
是一个万能近似器,可以拟合非常复杂的函数。它将输入先投影到高维空间(d_model -> d_ff
,例如2048维),进行非线性激活,再投影回原空间(d_ff -> d_model
)。这个“bottleneck”结构提供了强大的非线性能力。
结论:Transformer用注意力层负责“关系”,用FFN层负责“转化”。FFN的两个线性层是其强大表征能力的关键,缺一不可。没有FFN,Transformer会退化为一个几乎纯线性的模型,能力大打折扣。
2. HSTU为什么敢减少线性层?(2线性层设计)
HSTU的设计哲学完全不同:它不是一个通用模型,而是一个为推荐系统高度特化的模型。它的优化是在理解了推荐任务特性后做出的权衡(Trade-off)。
HSTU的假设和代价:
-
假设:推荐任务的非线性需求可能更低(或者说,可以用别的方式弥补)。
- 推荐系统的输入虽然庞大,但模式可能相比自然语言更简单、更结构化。特征的交互和转换也许不需要自然语言中那么复杂的非线性变换。
- HSTU用 Element-wise乘法(Gating机制)
U^l \odot f_{mid}(A^l)
来部分替代FFN的非线性功能。这个操作计算量极小,但能实现一种条件选择(“门控”)的效果,让模型决定保留或放大哪些信息。
-
代价:毫无疑问,HSTU的理论表示能力(Representation Capacity)是低于标准Transformer的。
- 它牺牲了模型的部分“智力潜能”,即处理极其复杂和非线性模式的能力。
- 这个代价换来的就是之前提到的极致的内存和计算效率,使得训练万亿参数模型成为可能。
-
另一个视角:分工转移。
- 在HSTU中,强大的非线性变换责任可能部分地从FFN转移到了Attention机制本身。它那套独特的、不用Softmax的Pointwise Aggregation注意力,其计算过程可能本身就蕴含了更强的非线性变换能力。
- 同时,推荐系统的复杂性很大程度上被庞大的Embedding表所承担,主干的Transformer层可以适当简化。
3. 与Transformer变种的比较
与最原始的Transformer比较确实有点“过时”,因为像LLaMA、GPT等采用的Transformer++已经做了很多优化(如SwiGLU激活函数,它甚至需要3个线性层)。
- 更合适的比较对象:HSTU更应该与这些现代Transformer变种进行比较,而不是与2017年的原始模型比较。论文中也提到了他们比较了“Transformer++”(应该就是指这类现代变体)。
- 比较的公平性:即便如此,比较仍然是公平的,因为HSTU的对比基线是拥有完整FFN的现代Transformer。实验结果表明,HSTU在推荐这个特定任务上,效果更好或持平,但速度更快、内存更省。
- 核心结论:这个结果恰恰证明了HSTU设计的成功——它通过领域特化(Domain Specialization),找到了推荐系统任务的“甜蜜点”。在这个点上,它用更少的参数和计算量,达到了与通用模型相当甚至更好的效果。这说明对于推荐系统,通用模型的设计可能是过度参数化(Over-parameterized) 的,存在优化空间。
总结
标准Transformer | HSTU | |
---|---|---|
目标 | 通用性,处理语言、代码等各种复杂模式。 | 特化性,极致优化推荐系统任务。 |
FFN作用 | 提供核心的非线性表示能力,不可或缺。 | 其功能被简化,部分由注意力机制和门控操作承担。 |
设计哲学 | “强能力”优先,保证模型的理论上限。 | “效率”优先,在保证效果的前提下,疯狂优化效率。 |
代价 | 计算和内存成本高昂。 | 理论表示能力有上限,可能不适用于更复杂的任务。 |
最终,HSTU的设计告诉我们:没有最好的架构,只有最合适的架构。 通过深刻理解业务场景的独特性,可以大胆地对通用架构进行“手术”,用性能换效率,从而在资源受限的现实世界中实现突破。
四、 性能突破:训练与推理的 Scaling
- 生成式训练:彻底抛弃“曝光-反馈”训练样本,只使用用户实际发生的行为数据,从源头极大缩短了序列长度
T
。 - M-FALCON推理算法:
- 痛点:排序阶段需对上万候选item打分,计算量无法承受。
- 解决方案:通过折叠候选集和KV缓存复用,将计算复杂度从
O(m * n^2)
降为O(n^2 + m * n)
。 - 原理:将多个候选item捆绑为微批次,只计算一次用户历史的KV并缓存,然后让所有候选item共享该结果进行交叉注意力计算。实现了计算成本相对于候选集数量
m
的线性增长,这是线上部署的关键。
最终成果:Scaling Law 与业务效果
通过上述设计,Meta实现了:
- 规模:成功训练并部署了万亿参数(1.5T) 的推荐模型,计算量达到LLaMA-2/GPT-3的级别。
- 效果:首次在推荐系统中观测到类似于LLM的Scaling Law,即模型性能随参数和算力增加而持续提升。
- 业务收益:在线上A/B测试中,排序任务提升12.4%,召回任务提升6.2%,整体提升可达18.6%。
总结
Meta的生成式推荐是一场范式革命。它通过“一切皆序列”的统一思想,借用LLM的架构和Scaling理念,并深度融合推荐系统的先验知识(DIN、MoE等),最终通过一系列恐怖的工程优化(HSTU, M-FALCON),解决了阻碍推荐系统发展的三大核心挑战:特征异构、动态词表、计算瓶颈,为下一代推荐系统的发展指明了方向。