背景
在推荐系统中,多路召回会产生大量候选 item。如果不使用粗排(General Rank),这些候选会直接进入精排模型打分,带来较大的计算开销和延迟。
PAI-Rec 提供了两个过滤器来解决这个问题:
- PriorityAdjustCountFilter:按优先级截取各路召回,保证高优先级召回路的 item 优先入选
- SnakeFilter:按权重蛇形交错分配各路召回,保证各路 item 均匀混合
两者都能在不使用粗排的情况下,将多路召回的数百甚至上千个候选压缩到指定数量(如 200 个),再进入精排。
一、PriorityAdjustCountFilter —— 按优先级截取
核心逻辑
- 将所有候选 item 按
RetrieveId(召回路名称)分组 - 按配置顺序(即优先级)从高到低,依次从每路中取 N 个 item
- 每路内部按 Score 降序排列,优先取高分 item
- 支持两种计数模式:
fix:每路固定取 N 个accumulator:累计取满 N 个后停止
配置示例
{
"FilterConfs": [
{
"Name": "priority_truncate",
"FilterType": "PriorityAdjustCountFilter",
"AdjustCountConfs": [
{
"RecallName": "u2i_recall",
"Type": "fix",
"Count": 80
},
{
"RecallName": "vector_recall",
"Type": "fix",
"Count": 60
},
{
"RecallName": "hot_recall",
"Type": "fix",
"Count": 40
},
{
"RecallName": "new_recall",
"Type": "fix",
"Count": 20
}
]
}
]
}
效果:从 u2i 取 80 个 → 向量召回取 60 个 → 热门取 40 个 → 新品取 20 个,总共 200 个进入精排。
累计模式配置
如果希望"总共不超过 200 个,按优先级依次填充":
{
"FilterConfs": [
{
"Name": "priority_truncate_acc",
"FilterType": "PriorityAdjustCountFilter",
"AdjustCountConfs": [
{
"RecallName": "u2i_recall",
"Type": "accumulator",
"Count": 100
},
{
"RecallName": "vector_recall",
"Type": "accumulator",
"Count": 160
},
{
"RecallName": "hot_recall",
"Type": "accumulator",
"Count": 200
}
]
}
]
}
效果:先从 u2i 取最多 100 个;再从 vector_recall 取,累计到 160 个;最后从 hot_recall 补满到 200 个。如果前面的路数量不够,后面的路会多取。
适用场景
- 各路召回有明确优先级(如个性化召回 > 热门召回)
- 希望保证核心召回路的候选数量不被稀释
- 对各路占比有确定性要求
二、SnakeFilter —— 按权重蛇形交错分配
核心逻辑
- 根据各路的
Weight按比例计算每路应分配的 item 数量 - 按"蛇形"方式轮流从各路取 item:每轮按权重值取对应个数
- 各路内部按 Score 降序排列
- 遇到重复 item(被多路召回的同一物品),支持两种策略:
REFILL_ON_DUPLICATE(默认):跳过重复 item 但不消耗配额,继续取下一个SKIP_ON_DUPLICATE:跳过重复 item 且消耗配额
配置示例
{
"FilterConfs": [
{
"Name": "snake_truncate",
"FilterType": "SnakeFilter",
"RetainNum": 200,
"AdjustCountConfs": [
{
"RecallName": "u2i_recall",
"Weight": 4
},
{
"RecallName": "vector_recall",
"Weight": 3
},
{
"RecallName": "hot_recall",
"Weight": 2
},
{
"RecallName": "new_recall",
"Weight": 1
}
]
}
]
}
效果:总共保留 200 个候选,按 4:3:2:1 的比例分配。蛇形交错取出,最终结果中:
- u2i_recall 约 80 个
- vector_recall 约 60 个
- hot_recall 约 40 个
- new_recall 约 20 个
输出结果的排列顺序是交错的(每轮 u2i 取 4 个 → vector 取 3 个 → hot 取 2 个 → new 取 1 个),有利于后续排序的多样性。
SKIP_ON_DUPLICATE 模式
当一个 item 被多路召回同时命中时,默认模式会让它"不占坑位"(即不消耗当前路的配额),如果你希望严格按权重分配坑位数(即使遇到重复也消耗配额),可以设置:
{
"FilterConfs": [
{
"Name": "snake_truncate_skip",
"FilterType": "SnakeFilter",
"RetainNum": 200,
"SnakeType": "SKIP_ON_DUPLICATE",
"AdjustCountConfs": [
{
"RecallName": "u2i_recall",
"Weight": 4
},
{
"RecallName": "vector_recall",
"Weight": 3
}
]
}
]
}
适用场景
- 各路召回没有明确优先级,希望"雨露均沾"
- 希望最终候选的多样性更好(交错排列)
- 各路召回重叠较多,需要合理去重分配
三、完整配置示例:两者组合使用
在实际业务中,可以组合使用:先用 PriorityAdjustCountFilter 做初步截断(如从 1000 降到 400),再用 SnakeFilter 做精确的权重分配(从 400 降到 200)。
{
"FilterConfs": [
{
"Name": "exposure_filter",
"FilterType": "User2ItemExposureFilter",
"TimeInterval": 300,
"WriteLog": true,
"DaoConf": {
"AdapterType": "featurestore",
"FeatureStoreName": "fs_pairec",
"FeatureStoreViewName": "user_expose"
}
},
{
"Name": "priority_truncate",
"FilterType": "PriorityAdjustCountFilter",
"AdjustCountConfs": [
{
"RecallName": "u2i_recall",
"Type": "accumulator",
"Count": 150
},
{
"RecallName": "vector_recall",
"Type": "accumulator",
"Count": 280
},
{
"RecallName": "hot_recall",
"Type": "accumulator",
"Count": 350
},
{
"RecallName": "new_recall",
"Type": "accumulator",
"Count": 400
}
]
},
{
"Name": "snake_truncate",
"FilterType": "SnakeFilter",
"RetainNum": 200,
"AdjustCountConfs": [
{
"RecallName": "u2i_recall",
"Weight": 4
},
{
"RecallName": "vector_recall",
"Weight": 3
},
{
"RecallName": "hot_recall",
"Weight": 2
},
{
"RecallName": "new_recall",
"Weight": 1
}
]
}
],
"FilterNames": {
"home_feed": ["exposure_filter", "priority_truncate", "snake_truncate"]
}
}
执行流程
多路召回 (1000+ items)
↓
曝光过滤 (去除已曝光)
↓
PriorityAdjustCountFilter (按优先级截取到 400)
↓
SnakeFilter (按权重蛇形分配到 200)
↓
精排模型打分 (200 items)
↓
重排/多样性调控
↓
返回结果
四、关键配置字段说明
PriorityAdjustCountFilter
| 字段 | 类型 | 说明 |
|---|---|---|
FilterType |
string | 固定值 "PriorityAdjustCountFilter" |
AdjustCountConfs |
array | 各路召回的截取规则,顺序即优先级 |
AdjustCountConfs[].RecallName |
string | 召回路名称,对应 RecallConfs 中的 Name |
AdjustCountConfs[].Type |
string | "fix" = 每路固定数量;"accumulator" = 累计到指定数量 |
AdjustCountConfs[].Count |
int | fix 模式下为该路取的数量;accumulator 模式下为累计上限 |
EnsureDiversity |
bool | 是否在截取时保证多样性(需配合 DiversityDaoConf) |
SnakeFilter
| 字段 | 类型 | 说明 |
|---|---|---|
FilterType |
string | 固定值 "SnakeFilter" |
RetainNum |
int | 最终保留的候选总数 |
SnakeType |
string | 重复处理策略:空(默认)= REFILL_ON_DUPLICATE;"SKIP_ON_DUPLICATE" |
AdjustCountConfs |
array | 各路召回的权重配置 |
AdjustCountConfs[].RecallName |
string | 召回路名称 |
AdjustCountConfs[].Weight |
int | 权重值,决定各路分配的比例 |
五、两者的对比与选择
| 维度 | PriorityAdjustCountFilter | SnakeFilter |
|---|---|---|
| 分配策略 | 按优先级顺序截取 | 按权重比例蛇形交错 |
| 输出顺序 | 按路分块(先 A 路全部,再 B 路全部) | 交错排列(A B C A B C ...) |
| 重复 item 处理 | 按 RetrieveId 分组,多路命中的 item 只归属一路 |
支持多路共享同一 item,配额可配 |
| 总数控制 | 通过最后一条 accumulator 的 Count 间接控制 | 通过 RetainNum 直接控制 |
| 多样性 | 可选 EnsureDiversity 配合多样性约束 |
天然交错,多样性较好 |
| 适合场景 | 有明确优先级,核心路必须保量 | 各路平等,追求均匀混合 |
推荐选择
- 只需要简单截断:用
PriorityAdjustCountFilter,配置直观 - 需要均匀混合+精确总数控制:用
SnakeFilter - 两者组合:先用 PriorityAdjustCountFilter 粗截(高优保量),再用 SnakeFilter 精细分配(权重混合)
六、注意事项
- 过滤顺序:在
FilterNames中,截断过滤器应放在曝光过滤、状态过滤之后,确保先去掉无效 item 再截断 - RecallName 匹配:
AdjustCountConfs中的RecallName必须与RecallConfs中召回的Name完全一致 - 未配置的召回路:如果某路召回未在
AdjustCountConfs中配置,其 item 会被直接丢弃(不会进入最终结果) - 候选不足时:如果某路的实际 item 数不够配额,会取尽该路所有 item,不会报错
- 精排数量规划:
RetainNum/ 最终 Count 应根据精排模型的 QPS 和延迟要求来设置,通常 100~300 是合理范围 - 实验参数:两个过滤器都支持通过 AB 实验动态调整参数,可以在实验中验证不同截断策略的效果