Skip to content

推荐系统在线推理:延迟预算下的多模型串联

本页速览 推荐系统的在线推理是延迟预算最紧的部署场景之一。本文拆解推荐链路(召回→粗排→精排→重排)中各模型如何部署、特征怎么取、多模型如何串联,以及延迟预算怎么分配。

推荐系统在线推理:延迟预算下的多模型串联

一句话定义:推荐系统的在线推理是"召回→粗排→精排→重排"四个阶段、多个模型在百毫秒延迟预算内级联执行的部署工程——它同时挑战了特征工程、模型服务、级联调度与容量规划四件事。

为什么值得动手:推荐在线链路是全站延迟预算最紧、模型最多、最考验部署架构的场景——一个 100ms 的端到端预算里要塞下特征读取、向量检索、粗排几千条、精排几百条、重排混排;任何一个环节慢 20ms 都会挤爆预算。本文拆解这条链路每个阶段怎么部署、特征怎么在线取、模型怎么串,并给出可落地的延迟预算分配表。推荐的部署决策离不开模型网关与灰度发布的流量编排与性能优化与容量规划的预算分配,本文是它们在一线场景的合体。

一、推荐链路全景与延迟预算

text
用户请求


┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
│  召回     │──▶│  粗排     │──▶│  精排     │──▶│  重排     │──▶ 结果
│ 千万→千    │   │ 千→百     │   │ 百→几十   │   │ 去重/多样性 │
└──────────┘   └──────────┘   └──────────┘   └──────────┘
 向量检索       双塔/小模型     深度排序模型     规则+小模型
阶段候选规模典型模型延迟预算
召回千万 → 1~2 千向量检索(Faiss/ANN)+ 双塔模型15~20ms
粗排千 → 100~300双塔/小 DNN(特征精简版)10~15ms
精排百 → 几十深度 CTR 模型(大特征集)30~50ms
重排几十 → 展示规则 + 小模型(多样性、新鲜度)5~10ms
特征/上下文读取Feature Store / KV15~25ms
合计≤ 100ms

先给结论:预算分配的比例原则是"越靠前的阶段越便宜"——召回阶段候选多但模型轻,精排阶段候选少但模型重,把贵的计算留给最值得精排的几百条。具体数字按业务调,但总预算必须留 20% 的余量给网络抖动和不可预知开销。

二、特征工程在线化

精排模型吃的是特征,特征的一致性是推荐在线的第一事故源。三件事:

  1. 特征分实时/离线两路:离线特征(用户长期画像、物品属性)由批处理推理管道每天灌入 Feature Store;实时特征(最近点击序列、上下文)在线实时计算并写 Redis 类存储,TTL 控制;
  2. 特征一致性训练时用"该请求时间点之前"的特征,在线也必须如此,不能偷看未来。最常见的事故是"训练用 T 时刻特征、在线用 T+1 时刻特征",模型直接崩。一致性治理方法见常见陷阱与反模式的特征泄漏条目;
  3. 特征存储选型:读多写少、毫秒级延迟 → Redis/Faiss 这类 KV;要回放与血缘 → Feature Store(Feast/阿里云 FeatureStore 等)。在线只读、离线批量写,避免在线侧写库引入链路耦合。

三、精排模型在线服务

精排是延迟大头,部署上三个关键动作:

1. embedding 检索与模型输入组装:特征分两半——直接数值特征进模型,稀疏 ID 特征先查 embedding(KV/向量库)再拼成张量。组装要在模型服务内做还是外层做,先给结论:组装放服务内、输入 schema 传业务语义(user_id、item_ids),模型服务把"查表→组装→前向→后处理"封装成一个 predict 类——与 FastAPI 实战的封装原则一致,但对延迟敏感,通常不用 HTTP 而是 gRPC(见服务化与推理 API)。

2. batch 推理:精排一次收到上百条候选,务必一次前向出全部分数,而不是逐条推理。深度 CTR 模型把 item_ids 拼成 batch 维度 [N, feature_dim],一次 forward 拿到 N 个分数。这在性能优化与容量规划里就是"批处理"打法;模型量大、要更高吞吐可上 Triton 的动态批处理。

python
# 精排服务核心逻辑(示意)
import torch

@torch.inference_mode()
def rank(user_vector, item_ids, item_emb_cache):
    # item_emb_cache: 从 KV 查出的 [N, d] embedding 矩阵
    item_emb = torch.from_numpy(item_emb_cache)          # [N, d]
    user = user_vector.unsqueeze(0).expand(len(item_ids), -1)  # [N, d]
    x = torch.cat([user, item_emb, cross_features], dim=1)     # 组装输入
    scores = ctr_model(x).squeeze(-1)                    # 一次前向出 N 个分数
    return scores

3. 模型体积与显存:精排模型大、显存贵,常见的降本手段是量化(FP16/INT8,见量化)和 embedding 表按热程度分层(热 ID 全量驻留、冷 ID 查外存)。

精排接口契约:一次组装、一次前向

精排服务对外的 schema 用业务语义而不是原始张量,模型服务内部负责"查表→组装→前向":

json
// 请求:一条用户 + N 个候选
{
  "request_id": "req_8f21",
  "user": { "user_id": "u_10086", "scene": "home_feed" },
  "items": ["i_101", "i_102", "i_103"],
  "context": { "hour": 21, "device": "android" }
}
// 响应:与请求 items 一一对应的分数
{
  "request_id": "req_8f21",
  "scores": [0.91, 0.82, 0.76]
}

契约里带上 request_id,是为了整条链路对账——召回/粗排/精排各阶段日志都带同一个 id,排查"某条候选为什么没进精排"时直接串起来。这是监控与可观测性里链路追踪在推荐场景的标准用法。

特征组装的延迟账本

精排 40ms 预算里,特征取数是容易被低估的隐形开销。一组参考分解(KV 全部命中、单机 Redis):

动作耗时说明
用户画像批量取(1 次批量 GET)约 0.5~1ms特征存储批量读
N 条候选 item 特征批量取约 1~2msN=300,批量而非逐条
embedding 查表约 2~5ms向量库/内存 KV
输入组装(concat/numpy)约 1ms别用 Python 逐字段拼
模型前向(batch=300)约 10~25ms精排模型主体

先给结论:特征取数通常只占精排 10~20% 预算,但"逐条取"会把它放大 10 倍——任何阶段都做批量读取,不做逐条 N 次往返。这也是推荐链路优化的第一优先级:先修"没批量化"的笨取数,再谈调模型。

四、多模型串联:级联、超时与降级

四个阶段四个服务,串联的核心是超时与降级

风险对策
上游慢,拖垮下游每阶段独立超时(如召回 20ms),超时用上一批缓存结果
召回挂了降级:只推热门池/首页固定池,保可用性
精排慢降级:跳过精排,粗排结果直接进重排
单点打满每阶段独立扩缩 + 限流,见模型网关与灰度发布

实现方式三种,先给结论:业务逻辑复杂(多分支、个性化降级)→ 应用层编排(网关/服务内调用);链路固定简单 → Triton ensemble 或 KServe 推理图。超时设置参考:端到端 100ms,每阶段预留独立超时(召回 20ms、粗排 15ms、精排 40ms、重排 10ms),用 Hystrix/Sentinel 类熔断器保护下游——某阶段连续失败快速熔断,避免雪崩。

五、缓存与热点处理

推荐在线延迟优化的两个常用手段:

  1. 结果缓存:同一用户+同一场景短期内重复请求,直接命中缓存。Key = user_id + scene + model_version注意缓存必须带模型版本号,否则模型更新后仍在返回旧结果——这是模型网关与灰度发布里"缓存污染"问题的同款坑;
  2. 热点请求:头部用户贡献大部分流量。做法:头部用户的推荐结果预热进缓存(上一轮全量算好)、热点 Item 的 embedding 提前驻留内存。热点治理见部署架构模式的"热点与缓存"讨论。

六、架构图与工具选型

text
客户端 ─▶ 推荐网关(路由/限流/灰度/A-B)


       推荐编排服务(级联 + 超时 + 降级)
        │            │            │
        ▼            ▼            ▼
   召回服务       粗排服务      精排服务 ──▶ 重排服务 ──▶ 输出
   (Faiss +      (小DNN)      (深度CTR)      (规则/小模型)
    双塔)
        │            │            │
        ▼            ▼            ▼
   Feature Store(KV/Redis + 离线画像管道) + 向量库(Milvus/Faiss)
组件选型参考一句话理由
特征存储Redis / Feast毫秒读、批量写、TTL
向量检索Faiss / Milvus召回主战场,ANN 检索
模型服务FastAPI(小模型)/ Triton(大模型、多模型)延迟与吞吐按需选
推荐网关Envoy / 自研路由、限流、灰度、A/B
编排自研编排服务 / Triton ensemble级联、超时、降级

延迟预算与容量规划的落地方法见性能优化与容量规划;灰度上线新推荐模型要走模型网关与灰度发布的完整流程(本文的网关就是它的入口);模型版本与特征的对齐关系靠MLOps 部署流水线管起来。

常见坑与排查

现象排查
特征泄漏在线效果比离线差一大截训练/在线特征时间对齐,见第二节
级联超时没设某阶段慢 → 整条链路超时每阶段独立超时 + 熔断
精排逐条推理精排耗时 200ms候选拼 batch 一次前向
缓存不带版本号模型更新后结果不变缓存 key 加 model_version
召回池全量扫描召回超预算用 ANN(HNSW/IVF),别暴力全量
头部用户打满服务热点用户请求拖垮整体热点结果预热缓存
降级策略缺失精排一挂整站推荐白屏预置降级链(跳过精排/热门兜底)

延伸阅读

参考资料