---
title: "VM Ranker 重排服务"
description: "vm-ranker 服务按特性开关解析权重计算价值模型得分，并利用帖子多模态向量执行行列式点过程（DPP）多样性筛选"
---

> Documentation Index
> Fetch the complete documentation index at: https://x-algorithm.vergego.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# VM Ranker 重排服务

## 这一页回答什么

本页说明 home-mixer 的 `VMRanker` 打分器调用的 `vm-ranker` gRPC 重排服务。内容包括该服务的输入参数、计算流程、基于帖子多模态向量的行列式点过程（Determinantal Point Processes，简称 DPP）多样性筛选机制、配置参数，以及调用失败时的多级降级策略。

加权求和、作者多样性衰减和站外降权的具体公式详见 [打分与排序](https://x-algorithm.vergego.ai/concepts/scoring-and-ranking/)。本页侧重说明各项算法在服务内部的调用机制与执行逻辑。

## 核心结论

1. **vm-ranker 是独立的 gRPC 服务**：服务仅暴露 `rank` 与 `get_carousel_model` 两个 RPC 接口（`vm-ranker/ranker_service.rs:70-173`）。home-mixer 在每次 For You 推荐请求中均调用一次 `rank` 接口。
2. **服务先计算价值模型得分，再执行 DPP 筛选**：价值模型综合分由加权求和、作者探索奖励、新作者扶持以及作者多样性衰减与站外降权构成。所有算法逻辑均调用共享 crate `xai-value-model`（`vm-ranker/scoring/value_model.rs:159-219`）。
3. **权重参数在服务内部按用户上下文动态解析**：服务定期从配置中心同步特性开关（feature switch）。服务根据请求中携带的用户上下文（国家、语言、账号年龄等）为当前用户解析适用的参数组合（`vm-ranker/ranking_config.rs:55-61`）。
4. **DPP 仅负责候选子集筛选，不修改入选帖子的得分与相对排序**：算法从得分最高的前 `max_selected_rank` 条候选帖子中，依据“高得分且彼此低相似度”准则贪心挑选最多 `top_k` 条帖子。入选候选保留原始得分，未入选候选得分置为 0（`vm-ranker/scoring/dpp_model.rs:196-206`；`vm-ranker/dpp.rs:155-169`）。
5. **相似度基于帖子多模态向量的余弦相似度计算**：向量数据来源于 Phoenix 的 `xai-recsys-mm-server` 客户端，采用 1024 维 f16 格式。针对转发帖子，服务查询原帖标识符。当缓存缺失向量时，服务生成随机单位向量替代（`vm-ranker/scoring/dpp_model.rs:43-51`、`80-84`）。
6. **DPP 在开源配置中的默认参数**：`theta = 0.65`、`max_selected_rank = 150`（`vm-ranker/params.rs:253-259`）；`top_k = 50` 取自服务启动参数（`vm-ranker/args.rs:18-19`）。话题请求默认跳过 DPP 筛选（`params.rs:247-252`）。
7. **系统提供三级异常降级保障**：服务并发满载时直接拒绝请求；缺少配置数据时透传 home-mixer 的输入得分；home-mixer 远程调用失败时采用本地加权分与新作者扶持结果，此时跳过多样性衰减、站外降权和 DPP 筛选。

## 在系统中的位置

```mermaid
flowchart LR
    subgraph HM["home-mixer"]
        VMS["VMRanker 打分器<br/>本地加权分 + 新作者扶持"]
    end
    subgraph VR["vm-ranker 服务"]
        RS["VMRankerServiceImpl<br/>信号量限流"]
        CFG["RankingConfig<br/>按观看者解析参数"]
        VAL["value_model::compute<br/>xai-value-model"]
        DPP["dpp_model + dpp<br/>贪心 DPP 筛选"]
        EMB[("EmbeddingStore<br/>帖子多模态 embedding")]
    end
    GIT[("配置仓库<br/>feature switch 文件")] -->|"git 同步 30 秒"| CFG
    VMS -->|"RankRequest"| RS
    RS --> VAL
    CFG --> VAL
    VAL --> DPP
    EMB --> DPP
    DPP -->|"RankResponse"| VMS
```

调用方 `VMRanker` 的请求组装和降级逻辑详见 [打分与排序](https://x-algorithm.vergego.ai/concepts/scoring-and-ranking/) 的“VMRanker 打分器与兜底”小节。

## 调用方式

### 请求内容

home-mixer 的 `RequestShape::build()` 负责组装 `RankRequest`（`home-mixer/scorers/vm_ranker_request.rs:27-76`）：

| 字段 | 内容 |
|------|------|
| `viewer_id` / `viewer` | 观看者 id 与上下文（国家、语言、客户端、角色、`now_ms`、feature switch 覆盖等），供服务解析参数 |
| `candidates[]` | 每个候选的 `tweet_id`、`author_id`、`retweeted_tweet_id`、本地分 `score`、`in_network`、`is_reply`、`is_retweet`、`is_mutual_follow_author`、`cold_start_lift_to_rank`、视频时长、全部 Phoenix 预测、缓存的 `weighted_score`、`semantic_ids` |
| `value_model_id` | 固定为 `"dpp"`（`vm_ranker_request.rs:8`） |
| `compute_value_model` | 固定为 `true`，让服务端重算价值模型分 |
| `topic_request` | 是否话题请求，影响站外系数和 DPP 开关 |
| `include_top_heads` | Under the Hood 请求时为真，服务返回每条帖子贡献最大的头 |
| `experiment_payload` | 可选的实验载荷：用户行为序列（去偏用）、内容特征、话题历史等 |

home-mixer 请求默认不设置 `dpp_params` 与 `seed_tweet_id`，两项字段在服务中采用默认空值。

### 客户端与超时

home-mixer 设定客户端超时时间为 400 毫秒（`home-mixer/clients/vm_ranker_client.rs:21`）。目标集群由 `VMRankerClusterId` 配置，默认值为 `"Experiment1"`（`home-mixer/params/param.rs:560-565`）。xDS 寻址与 DNS 寻址通道的切换机制见调用方页面说明。

### 服务入口

方法 `VMRankerServiceImpl::rank()`（`vm-ranker/ranker_service.rs:72-149`）按以下三步执行：

1. **并发限流**：服务通过 `try_acquire` 获取信号量许可。许可耗尽时立即返回 `RESOURCE_EXHAUSTED` 错误，不进入排队（`ranker_service.rs:85-96`）。最大并发许可数 `max_concurrent_requests` 默认值为 256（`vm-ranker/args.rs:12-13`）。
2. **执行打分**：组装包含 DPP 上下文、去偏模型与配置信息的 `RankContext`，随后调用 `scoring::rank()`（`ranker_service.rs:111-129`）。
3. **返回结果**：向调用方返回各候选帖子的 `score`、`weighted_score` 以及可选的 `top_heads` 贡献列表。

服务支持 gzip 与 zstd 格式压缩，单条 gRPC 消息大小上限为 20 MB（`ranker_service.rs:59-67`）。

## 参数来源与同步

当配置项 `config_sync_enabled` 为真时，服务在启动阶段从配置仓库拉取特性开关文件，随后每隔 `config_sync_interval_secs`（默认 30 秒）自动同步一次（`vm-ranker/main.rs:69-98`；`vm-ranker/args.rs:48-61`）。同步流程通过 `git fetch` 与 `git reset --hard` 执行（`vm-ranker/config_sync.rs:99-125`）。

每次接收到推荐请求时，`RankingConfig::resolve()` 利用用户上下文构建受众标识符（recipient），匹配实验规则并叠加请求自带的 `fs_overrides` 覆盖参数（`vm-ranker/ranking_config.rs:55-61`）。受众信息包含根据用户标识符 snowflake 时间戳推导出的账号年龄（天数、分钟数）与回流时间等特征字段（`ranking_config.rs:70-103`）。

文件 `vm-ranker/params.rs` 中定义的数值为代码默认值，仅在配置文件缺失对应键时生效。该文件与 `home-mixer/params/param.rs` 使用相同的配置键名，但部分代码默认值存在差异，具体对照见 [打分与排序](https://x-algorithm.vergego.ai/concepts/scoring-and-ranking/) 的权重表。

## rank() 的执行流程

```mermaid
flowchart TD
    A["RankRequest"] --> B["resolve_params<br/>按观看者解析参数"]
    B --> C{"DPP 上下文存在<br/>且 dpp_enabled?"}
    C -- 否 --> D["value_model::compute"]
    D --> E["返回价值模型分"]
    C -- 是 --> F["spawn_blocking"]
    F --> G["value_model::compute"]
    G --> H["dpp_model::rank_with_memo<br/>贪心选子集"]
    H --> I["入选保留原分<br/>未入选置 0"]
```

上述流程对应 `vm-ranker/scoring/mod.rs:40-94`。DPP 筛选属于纯 CPU 密集型核矩阵运算，因此服务将其派发至 `spawn_blocking` 专用线程池执行。

函数 `dpp_enabled()` 判定是否启用 DPP（`scoring/mod.rs:96-98`）：当未解析到配置参数（例如未开启配置同步）时默认判定为开启；解析到参数时，要求 `DppEnabled` 为真，且必须是非话题请求或配置项 `TopicDppEnabled` 为真。参数解析成功后，配置中的 `theta` 与 `max_selected_rank` 会覆盖启动参数，启动参数设定的 `top_k` 保持不变（`scoring/mod.rs:55-62`）。

### 价值模型分

方法 `value_model::compute()`（`vm-ranker/scoring/value_model.rs:48-70`）按以下逻辑计算：

1. 当请求字段 `compute_value_model` 为假且参数 `ComputeValueModel` 同样为假时，服务直接透传请求自带的 `score`。
2. 当未成功解析到配置参数时，服务记录 `fallback` 监控指标并同样透传输入得分。
3. 其余情况调用 `compute_or_fallback()` 计算得分（`value_model.rs:159-219`）：
   - `weights_from_params()` 根据参数构建权重，并通过 `WeightPerturbationSigma` 执行针对用户的扰动（`value_model.rs:72-121`）；
   - `scoring_context()` 选取适用的站外降权系数（`value_model.rs:123-147`）；
   - 根据作者实验分组注入作者探索奖励（`value_model.rs:243-262`）；
   - 调用 `xai_value_model::compute_value_scores()` 计算加权分与最终得分；
   - 当启用去偏逻辑时，服务通过用户去偏模型校准各行为概率并重新计算得分（`value_model.rs:196-218`）。

当所有候选帖子均包含缓存加权分且调用方未请求 top heads 时，服务直接返回首轮结果，跳过去偏计算（`value_model.rs:180-194`）。

### 用户去偏（默认关闭）

配置项 `UserDebiasEnabled` 默认值为 `false`（`vm-ranker/params.rs:260-265`）。开启该功能后，服务加载针对用户行为序列训练的轻量模型，估计当前用户在各项行为上的个人基线 logit，比对全局基线并将 Phoenix 输出概率向全局均值修正（`vm-ranker/scoring/debias.rs:125-152`、`221-233`）。校准强度由参数 `UserDebiasAlpha`（默认值为 0.0）与截断阈值 `UserDebiasClipLogit`（默认值为 2.0）控制（`params.rs:266-277`）。当模型权重文件超过 `UserDebiasMaxArtifactAgeSecs`（3600 秒）未更新时，服务跳过去偏步骤（`params.rs:296-301`）。去偏模型的权重存储路径与训练代码未包含在开源仓库中。

## DPP 多样性筛选

### 输入数据

函数 `build_dpp_inputs()`（`vm-ranker/scoring/dpp_model.rs:54-100`）按以下步骤准备数据：

1. 按照价值模型得分降序排列候选列表，截取前 `max_selected_rank` 条候选（`dpp_model.rs:78`）。
2. 查询各帖子的向量表示：转发帖子使用被转发的原帖标识符，其余帖子使用自身标识符（`dpp_model.rs:80-84`）。
3. 向量查询缺失时，生成随机单位向量并记录 `embedding_missing` 标记（`dpp_model.rs:43-51`、`23-37`）。

向量数据由 `EmbeddingStore` 提供，底层调用 `xai_recsys_mm_server` 的 `MmEmbeddingsClient`（`vm-ranker/embedding_store.rs:19-22`）。客户端在宿主机共享内存中维护按帖子标识符索引的 f16 向量缓存。服务启动时从对象存储拉取快照，并在内存中保留最近 2 天的向量条目（`phoenix/crates/serving/xai-recsys-mm-server/src/mm_embedding_client.rs:24-30`）。在向量快照预加载完成前，服务保持未就绪状态（`vm-ranker/main.rs:143-151`）。向量的生成流程详见 [多模态帖子嵌入器](https://x-algorithm.vergego.ai/concepts/multimodal-embedders/)。

### 核矩阵构建

函数 `dpp::rescore()`（`vm-ranker/dpp.rs:35-221`）构建 L 核矩阵：

```
s_max  = 池内最高分
q_i    = exp(α · s_i / s_max),   α = θ / (2·(1 − θ))
L_ii   = q_i²
L_ij   = q_i · q_j · cos(e_i, e_j)
```

上述公式实现于 `dpp.rs:92-133`。参数 θ 被限制在 `[0, 1)` 区间内。θ 越大，α 越大，分数差异在子集选择中的权重越高；θ 越小，算法越倾向于挑选语义正交的内容。在开源默认配置下 θ = 0.65，对应 α ≈ 0.929，此时得分最高的候选帖子的质量项 `q` 约为零分候选帖子的 2.53 倍。

### 贪心求解

函数 `greedy_dpp()`（`vm-ranker/dpp.rs:223-314`）基于增量 Cholesky 分解执行贪心最大后验概率（MAP）推断：

1. 优先选取对角线元素 `L_ii` 最大的候选帖子（即质量分最高项；若请求包含种子帖子，则固定优先纳入种子）。
2. 在每轮迭代中，为剩余候选计算加入当前集合后的行列式增益（条件方差），选取增益最大的候选帖子。
3. 当增益低于 `1e-6`（表明候选与已选集合高度线性相关）或选满 `top_k` 条候选时终止迭代（`dpp.rs:288-292`）。

候选帖子与已选帖子的向量相似度越高，其条件方差越小，入选概率随之降低。该算法实现了兼顾高得分与内容差异性的目标。

### 输出结果

筛选出的候选集合按原始价值模型得分重新排序（`dpp.rs:155-169`）。函数 `rank_with_memo()` 保持入选帖子的原始分数值，未入选帖子的得分全部置为 0（`dpp_model.rs:196-206`）。DPP 筛选不重排入选帖子的相对顺序，主要将语义高相似度的低分候选排挤出展示队列。分数为 0 的候选帖子将在下游选择阶段被排至队列末尾。

种子帖子（`seed_tweet_id`）仅用于压制与其语义相似的候选，本身不进入输出列表，亦不占用 `top_k` 筛选配额（`dpp_model.rs:102-125`）。home-mixer 发起的 For You 推荐请求不传递种子帖子。

### 参数配置

| 参数 | 开源默认值 | 来源 |
|------|------:|------|
| `DppEnabled` | `true` | `vm-ranker/params.rs:241-246` |
| `TopicDppEnabled` | `false` | `vm-ranker/params.rs:247-252` |
| `DppTheta` | 0.65 | `vm-ranker/params.rs:253` |
| `DppMaxSelectedRank` | 150 | `vm-ranker/params.rs:254-259` |
| `--dpp-enabled`（启动参数） | `false` | `vm-ranker/args.rs:15-16` |
| `--dpp-top-k` | 50 | `vm-ranker/args.rs:18-19` |
| `--dpp-theta` | 0.5 | `vm-ranker/args.rs:21-22` |
| `--dpp-max-selected-rank` | 100 | `vm-ranker/args.rs:24-25` |
| `--embedding-dim` | 1024 | `vm-ranker/args.rs:27-28` |

DPP 生效要求同时满足两个前提条件：启动参数 `--dpp-enabled` 处于开启状态（否则服务不初始化 DPP 上下文，`main.rs:31-52`），且请求级参数 `DppEnabled` 为真。开源仓库未包含生产环境的部署启动参数。当特性开关解析成功时，`theta` 与 `max_selected_rank` 采用配置值（0.65 与 150），`top_k` 维持启动参数配置（50）。

## 失败与兜底

| 失败点 | 结果 | 位置 |
|------|------|------|
| 服务并发已满 | 返回 `RESOURCE_EXHAUSTED`；home-mixer 用本地分 | `vm-ranker/ranker_service.rs:85-96` |
| 未启用配置同步或未带观看者上下文 | 价值模型直通，返回 home-mixer 带来的本地分 | `vm-ranker/scoring/value_model.rs:159-165`、`vm-ranker/scoring/mod.rs:126-134` |
| 帖子 embedding 缺失 | 用随机单位向量参与 DPP，近似视为与其他帖不相似 | `vm-ranker/scoring/dpp_model.rs:43-51` |
| DPP 的阻塞任务异常 | `rank` 返回错误；home-mixer 用本地分 | `vm-ranker/scoring/mod.rs:65-79` |
| home-mixer 调用失败或超时 | 用本地加权分 + 新作者扶持；无多样性、站外降权、DPP | `home-mixer/scorers/vm_ranker.rs:107-113` |
| 响应缺少某个候选 | 该候选保留本地分 | `home-mixer/scorers/vm_ranker.rs:122-139` |

本地得分未包含作者多样性衰减和站外降权调整。因此在服务降级期间，推荐流中同一作者的帖子密度相对上升，站外帖子的排序位次相对靠前。该现象依据源码逻辑推导得出，开源仓库未提供生产环境的实测数据。

## 视频轮播模型

RPC 接口 `get_carousel_model` 根据名称返回 JSON 格式的模型配置文件。文件从 `--carousel-model-dir` 目录读取，服务每隔 60 秒自动重新加载（`vm-ranker/ranker_service.rs:151-172`；`vm-ranker/carousel_model.rs:10-40`；`vm-ranker/args.rs:45-46`）。未配置该目录时服务返回 `UNAVAILABLE` 状态码。开源仓库未包含模型具体内容与调用端细节的详细说明。

## 设计决策

| 决策 | 理由 |
|------|------|
| 价值模型分在服务端按观看者重算 | 实验参数按用户解析，可在不改 home-mixer 的情况下调权重 |
| DPP 只筛选、不改分 | 保留价值模型对入选帖的排序，DPP 只负责把相似内容挤出前列 |
| 用多模态 embedding 余弦衡量相似 | 能识别文字不同但内容相近的帖子（同一张图、同一段视频），作者多样性做不到这一点 |
| 缺 embedding 时用随机向量 | 不让缺数据的帖子整体出局；随机向量与其他向量近似正交 |
| 话题请求默认不做 DPP | 话题页本来就要同类内容，按相似度去重会伤害相关性 |
| `try_acquire` 立即拒绝 | 调用方有本地兜底，排队等待只会拉长尾延迟 |
| embedding 预加载完才就绪 | 冷启动时缺 embedding 会让 DPP 退化成随机筛选 |

## 源码锚点

- `vm-ranker/main.rs:31-151` —— 启动：DPP 上下文、去偏、配置同步、就绪门控
- `vm-ranker/args.rs:12-64` —— 启动参数默认值
- `vm-ranker/params.rs:235-301` —— 价值模型、DPP、去偏参数
- `vm-ranker/ranker_service.rs:72-149` —— `rank` RPC 入口与限流
- `vm-ranker/ranking_config.rs:55-103` —— 按观看者解析参数
- `vm-ranker/scoring/mod.rs:40-134` —— `rank()` 流程与 DPP 开关
- `vm-ranker/scoring/value_model.rs:48-219` —— 价值模型计算与直通
- `vm-ranker/scoring/dpp_model.rs:43-206` —— DPP 输入、embedding 查询与输出
- `vm-ranker/dpp.rs:35-314` —— 核矩阵与贪心 DPP
- `home-mixer/scorers/vm_ranker_request.rs:27-76` —— 调用方请求组装

## 相关页面

- [打分与排序](https://x-algorithm.vergego.ai/concepts/scoring-and-ranking/) —— 权重表、加权求和、多样性与站外降权公式、调用方兜底
- [多模态帖子嵌入器](https://x-algorithm.vergego.ai/concepts/multimodal-embedders/) —— DPP 所用帖子 embedding 的来源
- [候选选择与成型](https://x-algorithm.vergego.ai/concepts/candidate-selection/) —— vm-ranker 之后的选择阶段
- [Phoenix 排序模型](https://x-algorithm.vergego.ai/concepts/phoenix-ranking/) —— vm-ranker 输入里各行为预测的来源
- [home-mixer 编排层](https://x-algorithm.vergego.ai/concepts/home-mixer-orchestration/) —— `VMRanker` 打分器所在的流水线

Source: https://x-algorithm.vergego.ai/concepts/vm-ranker/index.mdx
