
做推荐系统这些年我越来越觉得传统那套“召回→粗排→精排→重排→解释生成”的链路有点笨重。每个环节一个模型每轮迭代都要重新训练、联调、压测线上的问题还经常互相甩锅。最近我们团队把整套流程收拢成了一个端到端生成式推荐框架项目代号叫 OneSugOne Suggestion用一个统一的生成模型同时完成推荐决策和推荐理由生成。效果上核心业务指标涨了不止一个点性能上靠着受限解码和 KV Cache 复用单次请求的推理延迟也压到了日常可用。这项工作近期被人工智能领域的顶级学术会议录用也算是对这个方向的一个阶段总结。这篇内容我不会讲太多论文式的空话重点说清楚 OneSug 到底在解决什么问题、核心架构怎么设计、效果和性能双突破是怎么做到的以及我们在工业落地时踩过的坑。对正在搞推荐系统、尤其是想尝试“生成式推荐”的同行来说应该会有一些能直接搬回去用的东西。1. OneSug 到底在解决什么问题1.1 传统推荐系统的“模块割裂”困局大部分推荐系统都不是单点模型而是一条流水线。以短视频和电商场景为例线上链路一般是召回、粗排、精排、重排最后还可能挂一个文案模块给推荐结果生成理由。听起来很完善但实际维护过这套系统的人都懂它有几个非常磨人的内科病。首先是目标不一致。召回模块追求的是覆盖度希望把用户可能感兴趣的东西尽量捞回来精排模块追求的是点击率或时长重排模块又要兼顾多样性和商业约束解释文案模块则只关心语言通顺不通顺。每个模块都在优化自己的局部目标但放到一起时它们之间没有一个人对整体结果负责。其次是级联误差。召回阶段从千万级候选里筛出几百个如果这一层漏掉了用户真正想看的后面精排再强也救不回来。每到这种时候大家就会在机制上打补丁比如给召回加样本、加特征、加额外通道链路越来越长补丁越来越多。再就是维护成本。一条流水线上的模型少则四五个多则七八个每个模型都要单独准备样本、做特征工程、配置训练任务、上线监控。改一个上游模型的输出格式下游模型和数据管道都得跟着改。团队里经常出现“改完召回之后精排莫名其妙跌点了”的诡异情况最后查半天发现是特征分布变了。用装修来解释可能更直白传统推荐系统就像分别请了水电工、木工和油漆工每个人手艺都不差但没有一个总设计师对最终住进去的体验负责。结果就是客厅灯挺亮墙也刷了但插座位置和家具摆放互相打架。OneSug 想做的事情就是换一种施工方式让一个人从头到尾把整套活干完从理解需求到出方案再到交付一次性产出。1.2 生成式思路的破局点生成式推荐的核心想法是别再把推荐当成“打分排序”而是把它当成“序列生成”。给定用户的历史行为、场景上下文和候选池让模型直接生成一个推荐列表顺带生成一句解释文案。有点像你和某个熟悉你的朋友聊天问“今天有什么好看的”他直接告诉你——“你最近喜欢看户外类的这部露营纪录片的节奏刚好还有一期是讲高原天气的”。这个思路不是拍脑袋想的它有几个很现实的好处。第一任务统一。推荐和解释不再由多个模型分头完成而是一个模型、一次推理、一套梯度信号。用户在页面上看到的物品列表和理由从一个模型里长出来天然一致。第二共享表征。生成模型需要同时理解“用户喜欢什么”“什么东西和什么东西像”“这句话怎么表达才自然”这三个能力在同一个 Transformer 里互相复用比分开训练三个模型的数据利用效率高得多。第三符合内容流场景。短视频和信息流本身就是按顺序展示内容的用户消費的是一个序列而不是孤立的一堆格子。用序列生成的方式来建模内容分发逻辑上更贴近业务本质。当然把整个链路压成一个模型工程上的代价也不小。最大的挑战就是推理性能自回归生成是一个 token 一个 token 往外蹦的候选空间又有千万级弄不好线上延迟直接爆炸。OneSug 的整个设计其实都是围绕“能不能让这种生成在工业环境里跑起来”展开的。1.3 OneSug 的项目定位与实践边界先说清楚OneSug 不是要做一个人畜无害的学术原型。项目立项时目标就很明确做成一套能进线上推荐主链路、替代现有“多模块串行”的生成式框架并且性能和效果都要能打。它的典型应用场景有两类。一类是列表推荐进入首页后基于用户历史行为一次性生成一个推荐页面另一类是交互式推荐用户说“我想看轻松点的”模型能够根据这个请求动态生成新的推荐内容和对应的解释。后者这类场景传统的多模块链路处理起来非常吃力因为用户表达是开放式的很难用一个离散的槽位模板去覆盖。OneSug 的设计目标我总结成三句话一个模型统一预测与生成一次解码输出列表与解释一套参数同时优化效果与性能。项目代号里的 “Sug” 就是 Suggestion 的缩写OneSug 的意思比较直白——“只出一个建议”。2. OneSug 的核心架构一个模型生成推荐2.1 把行为、物品与上下文统一做成 Token做生成式推荐第一个绕不开的问题就是输入和输出怎么表示。推荐系统里最常用的表达方式是 feature embedding也就是把用户、物品、场景都映射成稠密向量。但生成模型不是这么玩的它吃的是离散 token。所以 OneSug 做的第一件事是把推荐系统里的各种信息全部转成 token 序列。用户侧的信息主要来自历史行为。我们把用户最近看过的内容 ID、看过的内容类型、停留时长等级、行为发生的时间间隔都离散化成 token。时间间隔不是原始秒数而是做分桶比如“5分钟以内”“5到15分钟”“1小时以上”每种分桶对应一个 token这种粗粒度表示反而更稳因为秒级数值对生成模型来说没有意义。物品侧的信息也是类似思路。每个内容有自己的 ID、所属类目、标签、作者 ID、甚至封面图的视觉聚类 ID这些都能映射成 token。但这里有一个非常现实的问题候选集的规模是千万量级的不可能给每个内容 ID 都留一个词表位置词表太大不仅存不下训练时还会让 token 分布极度稀疏。OneSug 的处理方式是用“语义索引 分组 token”的思路。具体来说模型不直接在一个千万量级的 ID 词表上做分类而是把物品先映射到一个相对小的语义空间比如几十万量级的簇生成的时候先预测簇再在簇内做细粒度的选择。这就把千万级别的输出空间拆成了两级训练和推理的复杂度都降下来了。上下文信息也一样。时段、星期几、用户所在城市、网络状态、设备型号这些拼进输入序列里作为条件 token。模型在生成时能看到“现在是晚上十点用户用 WiFi 连着历史里刷了三条美食视频”有了这些条件推荐结果会更贴近具体场景。2.2 训练目标先学“预测下一个”再学“解释为什么”OneSug 的训练目标不是单一的而是三个目标一起学。第一个是 next-item prediction也就是给定历史行为序列预测下一个要展示给用户的内容。这是推荐生成模型的基础能力训练样本来自线上真实的曝光和点击数据核心思想是让模型学会“什么内容最有可能成为用户的下一个选择”。第二个是 listwise generation。单点预测只能学会“下一个推什么”但推荐场景要的是一整页而且这些内容之间还要有差异性。所以我们在训练时会把一整段推荐列表作为生成目标比如输入用户行为序列输出的是一个连续 token 段代表五个物品 ID。这样做的好处是模型不仅要考虑“每个物品单独好不好”还得考虑“这五个放一起合不合适”多样性信息被直接编入训练目标。第三个是 explanation generation。也就是让模型同时学会生成一句推荐理由。这个目标的训练样本来源比较麻烦纯靠用户行为数据无法回答“为什么推荐这个”。我们用的是“后验文案匹配”的方式从线上日志里提取用户看完之后真的发生了互动比如点了关注、看完了完整视频的样本再让离线模型为这些匹配生成参考文案之后进入训练管线。三个目标的 loss 直接相加但各自权重不同。这里有个实际教训解释任务的 loss 量级通常比推荐任务的 loss 大很多因为语言模型的交叉熵在短句上很容易收敛。权重如果不做约束模型会“偷懒”把精力都放在把句子说通顺上推荐准确率反而被稀释。我们试过在 loss 上加不确定性加权也试过两阶段训练——先用行为数据预训练推荐能力再拿文案数据精调解释能力。我们的实践结果是分阶段训练的效果更稳定联合训练后期容易震荡需要反复调权重太耗时。2.3 模型本身为什么是 Decoder-Only 自回归确定输入输出形式之后就是选模型骨架。 OneSug 采用的是 decoder-only 的自回归结构类似现在主流语言模型的范式。为什么不选 encoder-decoder原因有两个。一是推荐输出本身既有结构化 token物品 ID又有自然语言 token解释文案这两类 token 用同一个解码器处理起来更顺不需要额外设计 bridge 模块二是工业部署时decoder-only 只有一个模型体加载、请求、缓存都更简单。线上推理每多一个模块延迟和故障点就多一分。模型整体的工作方式可以描述得很直白。输入侧把用户行为、上下文、候选约束拼成一个序列解码侧先生成结构化部分也就是“物品1 token 物品2 token ...”再生成文本部分也就是“推荐理由”。这两段输出之间其实没有严格分隔模型可以从结构化 token 无缝切到文本 token因为训练时我们就在两者之间加了特殊分隔符。细节上OneSug 在解码时做了“结构化约束”这是和普通语言模型最大的区别。普通语言模型每个位置都可以从全词表里选 token而 OneSug 在物品槽位只允许从候选索引里选 token这些候选是先用向量检索从千万级内容里筛出来的几百个“可选项”。这样既约束了解码空间又大幅缩小了搜索范围属于工业可用的关键设计。2.4 一个可供参考的模型配置每个场景的 Vocab、序列长度和模型尺寸都会不一样但我们可以给出一个内部跑通过的基准配置框架供参考。这个配置在千万级候选、单卡训练的条件下是可行的。配置项推荐值备注模型结构Decoder-only Transformer参考主流语言模型的残差结构层数12 层更深层收益不明显训练成本翻倍hidden size768与 12 层搭配较合理注意力头数12多头注意力头维度 64输入最大长度1024行为序列 上下文 前置候选输出最大长度128覆盖最多 10 个推荐槽位和解释文案物品簇数50 万语义聚类后的一级词表beam size5调大效果提升有限延迟线性增长量化方式INT8推理吞吐提升约 1.8 到 2 倍这个配置不是最优解但胜在稳。大模型的收益在推荐场景里并不是线性的尤其当候选被限制以后模型容量堆到一定程度就进入平台期。与其卷参数量不如把数据质量做扎实。3. 效果与性能双突破的关键细节3.1 效果为什么能涨端到端建模消除级联损失先说效果。OneSug 上线后离线评估和在线实验都有明显提升。深入分析之后我觉得能涨的核心原因不是单个模块变强了而是“端到端”这个属性带来了三个结构性优势。第一个优势是训练信号的一致性。传统链路里精排模型只看到召回模块丢过来的候选集合它没法知道召回阶段漏掉什么而 OneSug 在训练时直接把“从全库中选什么”和“选完之后排什么”的目标统一了。模型学到的表示同时服务于召回和排序而不是各学各的。相当于原来两个教练分别训练球员的进攻和防守现在一个教练同时盯全场整体战术自然更连贯。第二个优势是上下文信息在链路里无损传递。传统精排阶段看到的特征已经是经过召回路筛选和加工的“二手特征”很多原始行为信号在被截断或聚合时丢了。OneSug 是全程拿着原始行为序列在做生成信息损耗小得多。尤其在用户兴趣比较冷门的情况下传统召回层很可能直接把这些兴趣过滤掉而生成模型还可以通过长尾 token 之间的组合关系拿出内容。第三个优势是解释和推荐互相反哺。解释文案并不是可有可无的装饰。训练时解释生成任务要求模型理解“为什么推荐这个内容”这种理解会被反向编码进推荐模块的 attention 权重里。我们做了个内部对比把解释生成任务从训练中摘掉推荐准确率会掉一小截说明文案任务确实在帮助模型更深入地理解用户与内容的匹配关系而不是单纯的附属品。3.2 性能优化的主战场一次解码搞定千万级候选效果再好看线上推理跑不动也是白搭。生成式推荐上线前团队里反对声音最大的就是性能——大家普遍觉得自回归解码不可能替代毫秒级的向量检索和排序。OneSug 实际做下来确实需要一轮硬核优化才能把延迟压到可用范围主要打法是围绕“解码”下功夫。第一步是候选约束。这一条最重要。我们不会让模型在千万级词表上做 full softmax而是先通过ANN近似最近邻 简单规则把候选缩小到 200 到 500 个。解码时物品槽位只能从这几个候选 token 里挑相当于从“大海捞针”变成“小筐择菜”。这个操作直接让解码时间降了一个数量级而且没有明显效果损失因为真正值得选的候选大概率已经被向量召回覆盖了。第二步是结构化解码。OneSug 会先生成一个“输出 plan”比如“接下来我要输出 5 个物品和 1 段文案”模型依据这个 plan 严格按槽位填充。配合强制前缀机制每个位置该生成什么类型物品还是文本在解码前就确定了既防止输出格式乱掉也减少了解码分支数量。第三步是 KV Cache 的极致复用。自回归解码对 Cache 的依赖极高。OneSug 在线上接收请求时同一个用户短时间内的多次请求可以复用前缀 Cache同一个 batch 内部做 dynamic padding保持序列长度对齐模型的量化也专门针对 KV Cache 做了内存压缩。这一套组合拳下来单机吞吐能支撑线上流量P99 延迟被控制在可接受范围内。有个数字可参考在一次基准测试里同样的候选规模下不做候选约束的生成式方案单请求延迟约 350ms经过候选约束和结构化解码之后降到 80ms 左右再加上 KV Cache 复用和量化最终稳定在 40ms 附近。这个延迟虽然比传统精排模型的几条毫秒要慢但考虑到一个模型同时干了召回、排序、重排和解释四件事这个成本是划算的。3.3 一份可复现的优化清单把 OneSug 落地过程中的优化点整理成清单方便直接对照自查。优化方向具体做法收益候选空间压缩ANN 召回 200 到 500 个候选解码限定候选池延迟降低 60% 以上结构化解码强制槽位类型生成 plan 先行防止格式错误减少分支KV Cache 复用同用户前缀复用 连续内存缓存吞吐提升明显INT8 量化权重和激活量化压缩显存占用吞吐约翻倍动态 batch padding按批次内最长度对齐避免内存浪费并发能力提升重复惩罚对已生成物品 token 做归一化抑制列表多样性提升每个优化点单独拿出来都不难实现难的是它们之间的配合。候选约束做小了模型效果会跌KV Cache 复用做激进线上流量突增时容易内存暴涨。这些参数的平衡要靠线下压测和线上灰度一起调没有一劳永逸的配置。4. 技术落地中的典型问题与排查实录4.1 候选暴露偏移导致生成结果“头重脚轻”OneSug 上线后的第一版就出现了一个典型问题生成结果严重偏向高热度内容。模型刷出来的推荐列表前几个永远是头部爆款哪怕用户行为已经表现出了很明显的长尾兴趣。排查下来原因还是数据偏差。训练样本来自线上日志而线上日志里的曝光和点击天然偏向头部内容模型从中学到的分布自然也被带偏。这和传统精排模型遇到的问题类似但因为生成模型通过自回归把上下文关系串了起来这种“热偏差”会被 token 之间的相互影响放大。解决办法分两步。训练侧我们对训练样本做了倾向性加权给低热度内容的曝光样本提高采样权重同时构造了部分“全局随机曝光”样本让模型见过被随机打扰的分布在线侧解码时对高温内容 token 做了额外的概率惩罚抬高长尾内容在候选中的相对权重。这两板斧下去列表的长尾覆盖率明显回升。4.2 Beam Search 的“复读机”问题生成式推荐里最容易遇到的反直觉现象是模型会连续推荐非常相似的内容。你让它生成五个视频推荐它可能给出同一个作者拍的五条不同视频或者同类型但内容几乎重复的五条。表面看每个物品单独打分都不低但放到一起用户感知就是“怎么全是这类”体验很差。这股现象完全等同于聊天机器人中的“重复生成”问题。原因在于 beam search 天然擅长找局部最优如果某个类型在条件概率上稍微占优就会被一路放大。我们尝试过两个方案一个是在解码时对已出现在结果集合里的相似 token 做重复惩罚另一个是把多样性的约束直接写进训练目标——之前提到的 listwise 目标就派上用场了它让模型在训练时就要考虑整段列表的整体合理性而不只是每个位置的局部最优。现在线上用的策略是这两者叠加训练时学多样性习惯解码时做重复惩罚硬兜底。这种方式在长短期效果之间找到了平衡。4.3 多任务 Loss 互相打架OneSug 同时训练推荐和解释两个任务实际训练中踩到最狠的坑是推荐准确率没怎么涨解释文案先变得异常模板化。模型学会了生成“这个视频很好看快来看看吧”这种泛泛而谈的废话而这样的文案对用户其实没有信息量。我们花了挺长时间才定位到原因。解释任务收敛太快它的 loss 很快就压到了很低的位置而推荐任务还在慢悠悠地学决策边界。梯度更新中解释任务的梯度占比越来越大把推荐任务的信号给盖住了。加上一开始是直接用加权求和的方式联合训练权重比例又偏向了解释端。后来改成两阶段方案第一阶段只学行为序列上的 next-item 与 listwise 目标让 model 先变成“一个懂内容的人”第二阶段冻结推荐主干的底层参数只对高层和解释模块做文案精调。这样解释任务就不会再干扰推荐能力。如果想在数据量很大的场景下玩联合训练建议用不确定性加权来自动平衡两个 loss而不是手动给固定权重。4.4 线上时延抖动与稳定性问题性能优化做得再好线上还是会出现偶发抖动。我们遇到过两类比较典型的场景。第一类是热门事件爆发时的流量尖峰。短时间涌入大量用户每个人的行为序列都包含同一个热点事件KV Cache 复用率骤降显存开销飙升P99 延迟会突然翻倍。我们的应对办法是给解码加上 token 数量上限并把超长请求降级到“只生成结构化推荐、不生成文案减少整体解码长度”。这种降级策略对用户感知影响很小但能保证系统不被打垮。第二类是长序列用户的解码问题。有的用户历史行为特别长输入序列都超过了 1024 上限。直接截断会造成最近的兴趣信息丢失全量塞进去又让内存和延迟爆炸。我们做了分层处理用一小段高权重的最新行为加上一部分经过摘要模型的长期兴趣摘要共同构成输入序列兼顾实时兴趣和长期偏好。4.5 常见问题速查表问题现象可能原因排查思路与解决办法生成结果总推爆款训练样本偏好头部内容样本加权 随机曝光采样 解码惩罚列表内容重复Beam Search 局部最优解码重复惩罚 listwise 训练目标解释文案模板化多任务 Loss 失衡两阶段训练 / 不确定性加权请求延迟突然飙高长序列解码 Cache 复用率下降截断输入 降级策略新内容生成概率极低模型没见过新 ID冷启动兜底通道 语义索引扩展离线指标好但在线无增益评估口径不一致加在线 A/B 与人工评估联合判断5. 评估这套系统的正确姿势5.1 离线评估推荐和解释要分开看生成式推荐系统的评估比传统推荐要复杂因为输出里面同时有物品序列和文案。如果只用一个指标去衡量很容易自欺欺人。我们的做法是分成三条线。推荐侧用 HRHit Rate、NDCG、GAUC 这类经典指标衡量物品序列和用户真实交互的匹配程度。解释侧用 BLEU、Rouge 这类文本相似度指标但这里有个坑文本指标只能衡量“像不像参考文案”不能反映“用户喜不喜欢”。所以我们还会安排人工评估从流畅性、相关性和个性化三个维度打分。系统侧再看端到端目标比如页面整体互动率、单次会话的时长。这里特别推荐一个内部经常用的伪指标解释与推荐的一致性。我们会让标注员看生成的推荐列表和生成的文案然后判断“文案是否真的在解释这些物品的选择原因”。很多模型单独看推荐和文案都不错合在一起却驴唇不对马嘴这一维度能把这个大问题直接照出来。5.2 在线实验与人工评估怎么配合OneSug 这类改动牵一发动全身不能一上来就直接替换主链路。我们采用的是三档灰度节奏。第一档是小流量影子模式线上正常跑老链路同时把请求复制一份给 OneSug只记录结果不真正展示用来比对两套系统的指标差异。这一阶段的成本低、风险小能快速暴露模型在真实流量下的表现。第二档是低比例 A/B 实验通常 5% 到 10% 的流量切到新系统观察一周核心指标点击率、人均时长、次日留存。重点不是看一两天而是看整个周期内的稳定性。第三档是地理或用户群的分桶灰度逐步放大到 50%最后全量。过程中要有人工评估团队持续盯样例不能完全依赖数据指标因为数据指标会被短期行为干扰比如点击率涨了但用户后续其实更累了这种钝化现象只有人工抽检才能率先感知。5.3 工程验收吞吐、延迟与稳定性除了模型效果工程层面的验收也要前置。我们通常用三组指标卡口延迟P50、P95、P99、吞吐单机 QPS和稳定性成功率、错误率、超时率。相对于那些指标激进的调优方案我们还是倾向于保守迭代——每次改动都限流观察宁可慢一点也不要线上翻车。还有一个容易被忽视的指标是解码长度分布。自回归模型在不同的请求上有不同的输出长度如果某个请求的输出长度意外超长会拖慢整个批次的速度。我们会在线监控输出长度均值与极限值一旦发现异常就主动截断并及时排查。6. 项目做完之后的一点实在体会6.1 第一件事先把链路打通再谈模型变大回头看 OneSug 的整个过程踩过最浪费时间的坑就是一上来就把精力花在了模型结构调优上。真正让效果突飞猛进的节点其实是数据管线和候选索引的调整。建议想做同类方向的团队先用一个不太大的模型把“输入行为序列 → 输出物品 token → 输出解释 token”的链路完整跑通中间哪怕效果差一点都没关系。链路通了后面所有的优化才有验证的载体线路不通模型调得再花哨也无处安放。6.2 第二件事性能优化不只是工程问题也影响效果传统项目里模型效果归算法延迟归工程两个团队各管一摊。但生成式推荐里性能优化手段会直接影响生成质量。候选约束如果太紧模型就看不到足够的候选效果损失肉眼可见KV Cache 复用策略太激进长尾请求的生成质量会下滑。所以做这种系统的团队最好让算法和工程同学坐在一起从第一天就共同为“延迟与效果联合最优”负责不要等到模型冻结了才开始考虑性能。6.3 这个项目还能往哪里走OneSug 目前还只是文本和结构化 token 的生成。我们内部已经在讨论向多模态扩展比如根据视频封面图特征生成 token让推荐内容从“看过哪些”升级到“可能喜欢哪个画面风格”另一个方向是把用户实时反馈比如点赞、滑动、留言在解码过程中就动态并入生成条件做到真正的交互式推荐。OneSug 的框架应该还能承载这些扩展毕竟自回归生成的好处就是条件可以不断往输入里加输出形式也可以越来越丰富。如果让我给同行一句实在建议那就是别被“生成式推荐模型很大、跑不动”的固有印象吓住。先把推荐问题正确地翻译成生成问题再把这套系统的链路跑通性能问题总能靠工程手段一步步磨平。这套路我们在 OneSug 上已经验证过了。