“推荐系统 Scaling Up”,这几个字放在一起,懂行的人第一反应往往不是某个模型结构,而是一连串头疼的问题:训练吞吐上不去、Embedding表撑爆显存、线上推理延迟抖动、特征管道追不上流量高峰。我最近系统梳理了一遍这个技术方向,把从数据管道、模型结构、训练框架到线上 serving 的完整链路都过了一遍,踩了不少坑,也整理出一些可以落地的经验,这篇就当是调研笔记分享出来。
先说清楚我理解的 Scale Up 是什么。很多文章喜欢把 Scaling Up 和模型参数量画等号,但实际上推荐系统的规模瓶颈从来不只是模型本身。它更像是一根链条:数据管道能不能喂饱 GPU,特征体系能不能支撑新业务,Embedding 能不能塞进显存,并行策略能不能把利用率拉起来,检索漏斗能不能在延迟预算内找到候选集,每一环都会在业务量翻几倍的时候第一个站出来报警。“推荐系统 Scaling Up”真正要做的事情,是让整条链条一起变粗,而不是只把某一环加宽。
这篇笔记的适用对象,主要是正在从单机实验走向大规模生产的算法工程师和系统工程师,也适合那些刚接手推荐系统、想搞清楚“扩容到底在扩什么”的读者。我会按照数据层、模型层、训练层、检索层这条主线来拆,每一层都会给出我实际验证过的方案选型和参数建议。
1. 先盘点规模化要打通的四层链路
在动手优化之前,先得搞清楚一个核心问题:推荐系统 Scale Up 到底在 Scale 什么?我见过不少团队一开始就把目光锁在模型参数上,结果 GPU 跑起来了,数据管道先崩了,或者线上推理超时率直接拉满。这里我习惯把整个链路切成四层,每一层都有它独立的规模瓶颈。
1.1 数据管道层:规模化的第一道关卡
数据管道对应的是“样本从哪来”。当业务从百万用户涨到千万、亿级,日志量会从每天几 GB 涨到几十 TB,离线链路和实时链路的压力完全不同。
- 离线侧:全量样本的生成、清洗、join、采样是否能在 T+1 的时间窗口内跑完
- 实时侧:用户实时行为、实时特征能否在秒级进入训练样本,而不是分钟级别延迟
这里最容易踩的坑是实时和离线特征不一致。我之前排查过一个 CTR 异常波动,折腾了三天才发现,离线样本的某个时长特征用的是请求完成后的完整时间,在线实时特征只截取了前 200 毫秒。Scale Up 阶段数据量大,特征一致性出问题的排查成本也会被放大很多倍,所以整理数据层方案时我通常会把特征一致性校验放在第一优先级。
1.2 模型结构层:参数规模膨胀的三个方向
模型层的规模诉求通常来自三个方向:特征数量增加、Embedding 维度增加、网络层数或跨网络交互变深。以经典的 DeepFM 类双塔结构为例,特征从几百个涨到几千个,Embedding 表的总尺寸会从几 GB 涨到几十甚至上百 GB,单卡显存根本放不下。到了大规模场景,稀疏参数占比最大,稠密部分反而相对可控。
我的建议是先统计 Embedding 总参数量,再算单卡显存容量,这两个数字一对比,要不要走分布式的答案就清楚了。比如用户 ID 特征是 1 亿个 ID、每个 Embedding 128 维,光这一个特征就是 1 亿乘 128 乘 4 字节,约 51.2 GB,单张 A100 的 80 GB 显存被它占掉一大半,再叠加其他特征就一定会爆。
1.3 训练框架层:并行策略影响利用率上限
训练框架层解决的是“参数多了怎么更快地训练”。常见选项包括 Horovod、PyTorch DDP,以及基于 2D/3D 混合并行的自研框架。推荐系统有个显著特点:稀疏参数巨量,但稠密参数和计算量相比 NLP 大模型来说并不夸张。因此通用大模型那套纯张量并行策略在推荐场景里不一定最优,更实用的做法是:
- 稠密参数用数据并行,每个 GPU 保留完整副本
- 稀疏 Embedding 按特征维度切分或按 ID 范围切分,通过 All-to-All 通信获取远端的 Embedding
这种把 DP 和专家并行混在一起的做法,算力和通信开销的平衡比单一策略好很多。实际压测时,我发现纯数据并行在特征数量超过几千之后,通信耗时占比会急剧上升,加机器并不能线性提速;但切成特征并行后,同样规模的数据,吞吐能提升 1.5 到 2 倍。
1.4 在线推理层:延迟与召回量的矛盾
在线推理层要平衡的是候选集大小、模型复杂度和延迟预算。在需要保证接口 P99 延迟不超过 50 毫秒的线上环境里,全量深度模型跑所有候选是基本不可能的。工业级方案几乎都会做漏斗:召回阶段用轻量模型或向量检索筛出几百到几千个候选,精排阶段再用重模型给这些候选打分。
漏斗设计在 Scale Up 后会被迫做得更细。我见过一个日活过亿的业务,漏斗升级成了五层:触发层、粗排向量层、粗排模型层、精排层、重排层。每多加一层,召回候选量就降一个数量级,延迟预算也一级一级分配,最终精排只需要处理上百个 item。这个思路无论是新搭建还是旧系统扩容,都值得优先参考。
2. 数据管道与特征治理,规模化的地基工程
数据管道看着不像模型结构那么“性感”,但它恰恰是最容易拖垮整体 Scale Up 计划的地方。很多团队扩完模型,训练一跑起来发现数据供给根本跟不上,GPU 利用率掉到 30% 以下,问题就出在地基没打好。
2.1 实时与离线特征一致性:先堵住最大的坑
推荐系统 Scale Up 后,实时和离线特征的不一致会被数倍的样本量放大。常见的不一致来源有三个:特征取值窗口不同、特征加工逻辑不同、特征落地时间不同。
我建议在数据管道层加一道“一致性比对任务”:每天挑出 1% 的实时样本,重放离线日志,逐字段比对特征值。这个任务跑起来之后,至少能揪出几类隐藏问题。实操上,我们当时也遇到过一个经典案例:实时特征里用户活跃度用的是“当前自然日”,离线重算用的是“滚动 24 小时”,导致同一个用户同一时刻两个特征值差了 20% 以上。加了比对之后,这类问题基本能收敛在特征上线阶段就能发现。
另外一个容易忽视的点是特征回填。实时管道允许特征延迟到 5 到 10 秒才落地,但离线管道是等行为全部发生后统一重算的。Scala Up 阶段样本量变大后,这个时间差会造成训练样本特征分布与线上不一致。缓解办法是给实时特征留一个“等待窗口”,窗口内允许修正,窗口外不再变动。这个等待窗口通常设置在 30 秒左右,不会明显增加训练延迟,但能显著提升特征一致率。
2.2 特征工程规模化:从手写转向配置化
特征数量从几百涨到几千之后,代码堆叠式的特征工程就撑不住了。每次上线一个新特征,都要改多处代码、做数据回填、验证离在线一致性,周期长、出错概率高。我在项目里推的是配置化特征平台:用一份 DSL 或 JSON 描述特征来源、加工逻辑、聚合窗口、上线状态,由统一引擎负责生成离线和在线两套执行计划。
一个特征配置化的模板长这样:
{ "feature_name": "user_active_level", "source": "user_profile", "type": "category", "embedding_dim": 16, "transform": "bucketize", "window": "rolling_24h", "online_consistent": true, "version": "2025.01.01" }好处很直接:新特征上线不用写代码,配置完自动映射到在线和离线两套管道;回填逻辑由平台统一控制;特征版本管理也清晰了,出问题可以快速回滚到上一版本。配置化还有一个隐藏收益,就是特征下线变得安全可控,不再出现“某个特征早已无人维护、却还在训练和线上跑着”的僵尸特征。
2.3 样本采样与重放策略的规模化调参
样本量大了之后,训练一个 epoch 的时间会线性拉长,但样本的重要程度并不是均匀的。常见的做法是负样本采样、困难样本加权、热门 item 降采样。Scale Up 阶段要做的是把这些策略变成可配置、可实验的参数,而不是写死在代码里。
一个我在多个项目里验证过比较稳的配置是:曝光但未点击样本按 0.1 到 0.3 的采样率保留,滑动窗口里的实时负样本额外加权,特别热的 item 负样本再降采样一半。这样样本总量能减少 40% 到 60%,AUC 基本不降,训练时间显著缩短。要注意的是采样比例最好不要全局统一,而是按 item 热度分段采样,避免热门 item 对整个模型产生过大影响。
数据回放这块也值得提一下。做增量训练时,回放旧样本的比例不是固定的,我一般会让最近 7 天的样本占 70%,更早的样本逐渐衰减到 30%。这里的关键参数是回放比例的衰减曲线,用指数衰减会比线性衰减更贴近业务新鲜度,对 CTR 波动的抑制也更明显。
3. 模型结构的 Scale Up:不只是堆参数
模型结构层面的规模化,核心矛盾是参数量、计算量、效果三者的三角平衡。推荐系统里参数量最大的几乎总是稀疏 Embedding,这一块的扩容策略直接决定了训练能不能跑起来。
3.1 Embedding 表的设计与压缩
Embedding 总量怎么算,前面给过一个式子:特征取值数量乘以 Embedding 维度乘以 4 字节。实践中我们会针对不同频次的特征区别设计:
- 高频特征:维度可以给到 64 到 128,信息容量足够
- 中频特征:32 到 64 维,兼顾效果和存储
- 低频特征:8 到 16 维,主要起平滑作用
这样做下来,总参数量通常能压缩 50% 左右,效果损失可以控制在可接受范围内。更激进的做法是做 Embedding 去重。用户和 item 的侧特征里常常存在大量近似重复的 Embedding 行,可以用哈希去重或者向量量化把表变小。哈希去重会引入碰撞误差,需要评估对线上指标的影响;向量量化则适合某个特征本身就特别稀疏的场景。
建议在模型结构定型之前跑一轮 Embedding 维度敏感度实验,画一条“维度 vs 离线 AUC”的曲线。我观察到的经验规律是:大部分特征在维度超过 64 之后收益就非常平缓了,只有用户序列和 item 序列这类输入特别复杂的特征才需要用到 128 维以上。按这个规律配维度,比所有特征统一用 128 维能省下近一半存储。
3.2 双塔与多塔结构在扩容时的取舍
双塔结构是推荐系统里最经典的召回结构,它把用户侧和 item 侧分别编码,线上用向量检索完成召回,不需要实时跑复杂的交叉特征。Scale Up 阶段双塔的优势会被放大:item 塔可以全量离线算出 Embedding,索引建好之后,线上只需要计算用户塔一次,然后走 ANN 检索就能拿到候选。
但双塔的交叉信息不足也是老问题。扩容到大规模后,业界通常会用多层漏斗来缓解:第一层双塔粗召回,第二层换成能引入交叉特征的轻量模型粗排,第三层精排再用完整的 DeepFM 或 DCN 类模型。这种衔接方式既保留了双塔的高效检索能力,又弥补了全量跑重模型算不过来的缺陷。
多塔结构一般用在多目标推荐,比如同时优化 CTR 和 CVR。每个塔共享底层的特征表示,上层各自接不同的目标网络。扩容时要注意多塔之间的 loss 权重平衡,如果 CTR 塔的样本量远大于 CVR 塔,CVR 塔很容易被压制。我的经验是给 CVR 塔单独设置一个梯度缩放系数,让它的更新幅度不被淹没,比直接调 loss 权重更灵活。
3.3 引入跨层交互时的计算量控制
随着模型深度增加,跨层交互(如特征交叉、attention 类结构)带来的计算量是超线性的。我之前把 FM 式二阶交叉从所有特征扩展成全量组合,离线迭代速度快了一半,训练时长直接翻了三倍,线上延迟也压不住。后来学乖了,只挑业务上明确有关联性的特征组做交叉,比如“用户年龄段 × item 类目”、“用户城市 × item 区域标签”,效果能保住,计算量却能砍掉 70% 以上。
经验是:在做特征交叉扩展之前,先看这些特征组合是否在线下 A/B 实验中有过正向信号。没有信号支撑的组合交叉,只会在 Scale Up 之后变成训练与推理的负担。
4. 分布式训练与性能优化实录
模型结构定了,接下来就是怎么把框架跑出吞吐。这个环节踩的坑最多,也最依赖实测数据说话。我在项目里验证过几条结论,单独拉出来说。
4.1 并行策略选型:数据并行 + 特征并行的混合方案
纯数据并行在 Embedding 总量能塞进单卡时最简单,一旦 Embedding 表超过单卡显存,就必须走特征并行或者把表切分到多卡。我最终稳定在用的方案是:稠密参数保持数据并行,稀疏 Embedding 按特征维度切分到不同的 worker,通信走 All-to-All。
举一个实际配置例子:8 卡 A100,Embedding 总量 120 GB。按每卡 80 GB 显存算,其实单卡放不下全量 Embedding,所以我切成 8 份,每份 15 GB,分发到 8 张卡上,同时保留约 20 GB 给稠密参数和激活值。训练时每个 batch 需要先通过 All-to-All 把远端 Embedding 拉过来,算完梯度再 All-to-All 把梯度送回去。这里通信量不小,所以在网络层面要确保使用 RDMA 或高带宽 TCP,否则通信会成为新的瓶颈。
组网时我建议优先保证卡间带宽,而不是盲目加卡。同样的集群规模,网卡从 25G 换成 100G,训练吞吐可能提升 30% 甚至更多,这个收益比单纯加两张卡还明显。
4.2 All-Reduce 与梯度同步的实践参数
稠密参数的梯度同步用 All-Reduce,这一步的耗时取决于参数量、卡数和带宽。设稠密参数量为 D 字节,卡数为 N,All-Reduce 的理论通信量约为 2 × (N-1) × D / N,这还没算带宽的损耗。实际调优时,常用梯度压缩来降低通信压力。
梯度压缩的做法是只在卡间传输 top-k 比例的梯度,剩下的本地累积。我测试过 top-5% 和 top-1% 两种配置:top-5% 时 AUC 几乎不掉,训练吞吐提升约 40%;top-1% 时吞吐提升明显但 AUC 掉得也快,已经能感觉到模型不收敛的趋势。所以我的建议是梯度压缩比例控制在 5% 以上,不要为了吞吐牺牲太多效果。
关于同步方式,同步训练收敛更稳,但慢;异步训练快,但容易不稳定,甚至发散。我最终采用了折中的全局异步、局部同步策略:每个节点内部用同步 All-Reduce,节点之间允许 1 到 2 个 step 的延迟。这个方案把吞吐和收敛质量平衡得不错,可以作为参考。
4.3 训练吞吐监控与瓶颈定位方法
训练扩起来之后,一定要建立一个吞吐监控仪表盘,至少盯全局吞吐(样本数/秒)、单卡利用率、通信耗时占比、数据加载耗时这几个指标。排查训练瓶颈时我的顺序固定是:
- 看 GPU 利用率,如果长期低于 70%,先怀疑数据管道
- 看数据加载耗时,确认不是在等样本
- 看通信耗时占比,如果超过 20%,优先考虑梯度压缩或调整并行策略
- 最后才怀疑模型结构里的算子效率
这套排查顺序帮我快速定位过好几个问题。比如有个项目 GPU 利用率只有 50%,我们花了很多时间调试并行参数,最后发现是数据管道的 TFRecord 读取没做多进程预取,CPU 卡在 IO 上。把预取缓冲从默认的 2 调到 8,利用率直接回到 90% 以上。这事给我的教训是:先看数据,再看通信,最后才动模型和框架,顺序反了会浪费大量时间。
5. 在线推理与检索链路的规模化
训练再快,线上服务扛不住也是白搭。推荐系统 Scale Up 的后半场,重点在推理延迟、检索效率和兜底策略三件事上。
5.1 漏斗分层与延迟预算分配
线上延迟预算要逐层分配。我常用的参考分配方式(以总预算 80ms 为例):
- 召回:20ms
- 粗排:10ms
- 精排:40ms
- 重排和组装结果:10ms
这个分配不是拍脑袋定的,而是根据每层的候选量和模型复杂度反推出来的。粗排候选量是千级别,精排候选量是百级别,所以精排反而分到了最多的预算,因为它要跑最重的模型。如果线上日志显示 P99 延迟总是压不住,先看是不是精排超时,而不是盲目压缩召回。
精排的性能优化我推荐两个方向。第一,Batch 推理时把多个候选打包成一个 batch 喂给 GPU,吞吐比逐条推理高很多;第二,把模型算子做算子融合,比如把 BN 和 ReLU 融合成一个算子,或者把 Embedding 查表和多特征 concat 合成一个自定义 op,能显著减少 kernel 启动开销。这两步做完,精排的吞吐通常能提升 30% 到 50%。
5.2 向量检索索引的选型:HNSW 与 IVF-PQ 的对比
召回层的向量检索是延迟敏感项,索引选择和参数一定要结合线上实测。我用过的两类主流索引是 HNSW 和 IVF-PQ,两类适合的场景差异很大:
| 索引类型 | 适合场景 | 优点 | 缺点 |
|---|---|---|---|
| HNSW | 千万级向量、高召回率要求 | 查询快,召回质量高 | 内存开销大,构建耗时长 |
| IVF-PQ | 亿级向量、内存受限 | 内存占用低,构建快 | 召回率受PQ量化损失影响 |
实操配置上,我在千万级 item 场景下用的 HNSW 参数是 M=32、efConstruction=200、efSearch=64,召回率和延迟都表现很稳。如果把候选规模推到亿级,内存压力变大,我会改用 IVF-PQ,nlist 设为 4096 到 8192,m 设为 64,PQ 编码后能大幅压缩内存。
还有一点容易被忽略:索引更新频率。推荐系统的 item 池每天都有新增和淘汰,索引不能一天只重建一次。我的做法是高频索引增量更新,比如每 5 分钟做一次增量,同时每天在低峰期做一次全量重建,保证新鲜度和召回质量都稳定。
5.3 兜底策略与降级方案
线上系统再稳也怕突发流量。Scale Up 之后,降级方案必须提前设计,不能等到线上出问题才临时开会。
推荐的降级层次从轻到重依次是:
- 向量检索超时:直接降级为热门 item 召回,保住响应
- 精排模型超时:降级为粗排打分结果,牺牲一部分精度但保住延迟
- 粗排也超时:用规则 + 热门兜底,保证用户还能刷出内容
关键体验是:降级必须是链路局部动作,不能全链路同时降级。比如只降精排、保留召回和粗排,用户体验不会崩塌;如果召回也降级了,用户看到的内容质量会明显下降,负面影响会大很多。每个降级开关都要有独立的监控报警,平时每周做一次降级演练,确保关键时刻真能切得过去。
6. 规模化演进中值得关注的新方向
前面聊的是当前能直接落地的方案,但 Scale Up 这条路上还有一些更前沿的方向,我调研下来觉得它们会在未来两三年改变推荐系统的架构形态。
6.1 大模型统一建模与稀疏特征融合
大模型进推荐系统不是一个新鲜话题,但这两年逐渐从“讲故事”走到了可落地阶段。这套路线不再把推荐分成召回、粗排、精排三个独立模型,而是尝试用一个统一的模型结构完成多阶段任务,用 prompt 或任务 token 区分不同阶段的目标。这样的优势是:模型参数量可以做大,特征体系全网共享,多阶段之间不再有信息损耗。
落地层面的难点是性价比。推荐系统的大模型和 NLP 不一样,不是纯粹拼参数量,而是要同时处理好稀疏 ID 特征和稠密语义特征。我在调研中比较认可的方向是:底层用大参数量的共享层做特征提取,上层保留轻量的任务塔,对召回、粗排、精排分别输出结果。这个结构和前面说的多塔有点像,但底层共享层的规模大得多,可以学到更通用的用户和 item 表示。
6.2 生成式推荐与序列建模的新约束
生成式推荐是另一个吸引眼球的方向,核心是用序列模型直接预测用户下一个交互的 item,而不是对全量 item 打分。这个思路和 LLM 的 next token prediction 很相似,在内容消费类产品里想象空间很大。
但生成式推荐的约束也很明显:自回归生成需要一个 token 一个 token 的来,线上延迟比一次性打分高很多。目前比较实用的形态是只在重排阶段用生成式模型做序列优化,或者在召回阶段生成少量高质量候选,然后其他阶段仍然用判别式模型。真要把生成式模型推到全链路,就得配合蒸馏和小型化推理引擎,把生成步数压缩到个位数。
6.3 全链路可观测性建设
最后想提一个容易忽略但极其重要的方向:可观测性。Scale Up 到一定程度,系统的复杂度会让故障定位变成一场灾难。特征数据有没有延迟、模型效果有没有衰减、线上延迟有没有劣化,这些问题如果不能用指标回答,扩容就变成盲人摸象。
可观测性建设最核心的三个指标是:数据新鲜度、特征覆盖率、模型在线与离线 AUC 的偏差。这三个指标做到按小时级监控,一旦出现异动很快能定位到是数据管道问题、模型问题还是检索链路问题。我甚至在项目里写过一段内部经验总结:宁可牺牲一部分实验迭代速度,也不允许全链路日志缺失。因为大规模系统的故障成本远比多跑几个实验的收益高。
写在最后的心得
从我自己的实操体验来看,推荐系统 Scaling Up 最难的往往不是某一个环节的技术选型,而是怎么在端到端视角下做取舍。很多人一上来就调模型结构,忽略了数据管道才是训练吞吐的最大瓶颈;也有团队埋头堆卡,结果发现通信开销把加速比吃掉了大半。
我比较推荐的做法是:先做一次全链路压测,把每一层的瓶颈量化出来,哪怕先用小规模数据,也要把链路完整跑通。别急着上大模型大参数,先把数据一致性、索引更新、降级演练这些“看不见”的工程做完,再谈规模和效果的提升,这条路走起来反而稳。
如果这篇调研能帮你在做扩容规划时少踩几个坑,就算值得了。后面有机会,我再把大模型统一建模和生成式推荐的实际落地案例单独拉出来聊。