这一页回答什么
本页说明 Grox 如何把一条帖子(文本、图片、视频帧、语音转写)编码成一个稠密向量。本页还说明当前并行运行的 v5、v8.2、v8-search 三种嵌入器的区别,以及嵌入的写入位置和读取方。
核心结论
- 三种嵌入器并行运行:
MultimodalPostEmbedderV5、MultimodalPostEmbedderV82、MultimodalPostEmbedderV8Search(含回填子类)分别由mm_emb_v5、mm_emb_v8_2、mm_emb_v8_search三组 Plan 调用。旧版的MultimodalPostEmbedderV2和“带 Grok 摘要”的嵌入 Plan 已删除。 - v5 仍截断到 1024 维:模型为
recsys-v5-embedding。嵌入器截取输出的前 1024 维,再执行 L2 归一化(grox/flows/mm_emb/embedder.py:15、grox/flows/mm_emb/embedder.py:42-52)。 - v8 系列把作者画像写入嵌入输入:v8.2 与 v8-search 的输入文本以作者块开头。作者块包含认证颜色、账号标签、NSFW 账号标记、粉丝数档位、注册时间、国家和语言(
grox/flows/mm_emb/renderer_v82.py:187-243)。因此,同一段文字由不同账号发出时,得到的嵌入不同。 - v8 系列不截断:输出维度取模型原生维度,嵌入器只执行 L2 归一化(
grox/flows/mm_emb/embedder_v82.py:28-32)。 - 回复单独成流:三组嵌入都有
_for_reply版本。回复嵌入只写入 Manhattan,不发送到 Kafka。回复所在线程中任一方是私密账号时,Plan 跳过该回复(grox/flows/mm_emb/task_filter.py:10-50)。 - 嵌入是召回的输入:Phoenix 的候选塔用帖子多模态嵌入生成语义 ID(
phoenix/README.md:110-114)。Phoenix 训练数据支持的嵌入版本包括v5、v8,均为 1024 维(phoenix/xrex/data/recsys/recsys_batch.py:138-146)。源码未直接写明这些版本名与 Grox 产出的对应关系。
嵌入 Plan 的链路
三组 Plan 结构相同。下面以 PlanPostEmbeddingV82 为例(grox/flows/mm_emb/plan_post_embedding_v82.py:15-32):
flowchart LR
RL["60 秒去重"] --> MH["媒体水合<br/>下载图片与视频帧"]
MH --> ASR["语音转写<br/>TaskASRTranscription"]
ASR --> EMB["嵌入<br/>v5 / v8.2 / v8-search"]
EMB --> W["写入 Manhattan<br/>原帖再发 Kafka"]
回复版 Plan 在去重之后多一个 TaskPostEmbeddingForReplyFilter(grox/flows/mm_emb/plan_post_embedding_v82_for_reply.py:26-35)。mm_emb_v5 还多一个写入 Task,它把每条原帖和回复的 v5 嵌入都发送到 grox-multimodal-embedding-v5-all 主题(grox/flows/mm_emb/plan_post_embedding_v5.py:19-37、grox/flows/mm_emb/constants.py:18)。
V5:截断到 1024 维
MultimodalPostEmbedderV5(grox/flows/mm_emb/embedder.py:18-116)的行为如下:
- 模型键为
recsys-v5-embedding,经XaiEmbeddingClientHttp调用。配置未写超时时,默认 60 秒。 - 系统提示词为:“Represent this X post for retrieving relevant X posts by content, topic, sentiment, or interest groups”(
grox/flows/mm_emb/embedder.py:14)。 - 渲染:
V5EmbedPostRenderer.render_for_embedding产出带占位符的文本和图片列表(grox/flows/mm_emb/renderer.py:16-46)。占位符<|vision_start|><|image_pad|><|vision_end|>插在第 N 张图在文本中应出现的位置。编码时,模型把第 N 张图填回该位置,从而保留图文顺序(grox/flows/mm_emb/renderer.py:7)。引用帖以Quoted post:接在后面。 - 转写:
TaskMultimodalPostEmbeddingV5收集帖子中所有视频的 ASR 转写,用换行拼接,再以\nTranscript: ...追加到文本末尾(grox/flows/mm_emb/task_multimodal_post_embedding.py:23-35、grox/flows/mm_emb/embedder.py:72-73)。 - 编码:调用
encode_with_embedded_pads_async(text_with_pads, images)(grox/flows/mm_emb/embedder.py:84-86)。 - 截断:取前 1024 维并执行 L2 归一化。不足 1024 维时,只执行归一化(
grox/flows/mm_emb/embedder.py:42-52)。 - 文本和图片都为空时,嵌入器返回空嵌入,Task 以
TaskStopExecution跳过写入(grox/flows/mm_emb/task_multimodal_post_embedding.py:42-47)。
结果在状态中的键名为 v5_1(grox/flows/mm_emb/task_multimodal_post_embedding.py:48)。
V8.2:带作者画像
MultimodalPostEmbedderV82(grox/flows/mm_emb/embedder_v82.py:19-114)与 v5 的区别如下:
| 维度 | v5 | v8.2 |
|---|---|---|
| 模型键 | recsys-v5-embedding |
recsys-v82-mm-embedding |
| 调用接口 | encode_with_embedded_pads_async |
embed_openai_async(OpenAI 协议) |
| 指令 | 系统提示词 | 文本首行 INSTRUCTION_PREFIX:“Represent this X post (formerly Twitter) for recommendation: capture its topic, intent, key entities, and likely engagement.” |
| 作者信息 | 名字和 handle | 作者块:认证颜色、账号标签、私密标记、NSFW 账号标记、简介(≤ 500 字符)、粉丝数与关注数档位、订阅等级、注册年月、国家、语言、关联企业 |
| 引用帖作者 | 名字和 handle | 同样拉取引用帖作者的 core user 资料 |
| 媒体 | 每帖按预算取图 | 原帖与引用帖合计最多 4 张,每个视频最多 4 帧 |
| 转写 | 追加在文本末尾 | Transcript: 块,截断到 2000 字符 |
| 输出 | 截断到 1024 维 + 归一化 | 原生维度 + 归一化 |
出处:grox/flows/mm_emb/renderer_v82.py:12-243、grox/flows/mm_emb/embedder_v82.py:16-91。
作者块中的认证颜色规则为:verifiedType 为 Government 时记 Grey,为 Business 时记 Gold,蓝标记 Blue,后面附订阅档位(grox/flows/mm_emb/renderer_v82.py:246-266)。NSFW 标记来自 core user 的 nsfwUser 或 nsfwAdmin(grox/flows/mm_emb/renderer_v82.py:277-284)。core user 资料拉取失败时,作者块退化为帖子自带的字段,嵌入照常计算(grox/flows/mm_emb/embedder_v82.py:34-43)。
V8-search:更多媒体与历史回填
MultimodalPostEmbedderV8Search(grox/flows/mm_emb/embedder_v8_search.py:23-110)沿用 v8.2 的作者块和指令。它与 v8.2 的差别在媒体与转写:
- 原帖与引用帖合计最多 16 张图片或帧。嵌入器按出现顺序分配预算,超出预算的视频减少帧数(
grox/flows/mm_emb/renderer_v8_search.py:34、grox/flows/mm_emb/renderer_v8_search.py:381-404)。 - 有多个视频时,每个视频的转写紧跟该视频的帧。2000 字符预算在各视频间平分(
grox/flows/mm_emb/renderer_v8_search.py:194-208)。 - 嵌入器不拉取引用帖作者的 core user 资料。
- 写入时,模型版本名为
v8_search_1(grox/flows/mm_emb/constants.py:26)。
历史回填。grox-multimodal-embedding-backfill 主题中的每条消息包含若干个 8 字节帖子 ID,生成器每 100 个 ID 打成一批(grox/flows/mm_emb/generators.py:65-95、grox/flows/mm_emb/batched_fav_hydrator.py:14)。TaskBackfillBatchFanout 去重并批量拉取赞数,再为每个帖子单独执行 mm_emb_v8_search_backfill Plan(grox/flows/mm_emb/task_backfill_fanout.py:37-85)。该 Plan 在嵌入前设有两道闸门(grox/flows/mm_emb/task_backfill_gate.py:16-60):
- 赞数闸门:赞数低于动态阈值时,丢弃该帖子。阈值初始为 20,000,目标通过速率为每秒 5 个(环境变量
MM_EMB_BACKFILL_TARGET_QPS)。 - 已存在闸门:Manhattan 中已有
v8_search_1嵌入时,丢弃该帖子。
AdaptiveMinFav 调整动态阈值:它每 30 秒计算一次实际通过速率。偏离目标超过 10% 时,它按 阈值 × (1 + 0.3 × 相对误差) 调整阈值,上限 20,000,下限 0。启动后的前 60 秒内,它不调整阈值(grox/flows/mm_emb/adaptive_min_fav.py:6-63)。结果是:系统先回填最热的历史帖子,处理能力有富余时再逐步降低门槛。回填使用单独的模型键 recsys-v8-search-backfill-embedding(grox/flows/mm_emb/embedder_v8_search.py:112-113)。
写入
TaskWriteMMEmbeddingSinkBase 的所有子类都遵循以下规则(grox/flows/mm_emb/task_write_mm_embedding_sink.py:26-250):
- 只在 mm-emb 生产环境执行(
DisableTaskForNonMmEmbProd)。 - 自带 3 次重试,间隔为 1 秒和 2 秒。
- 写入 Manhattan:键为帖子 ID 加模型版本(
v5_1、v8_2、v8_search_1)。 - 原帖额外发送到 Kafka:
grox-multimodal-embedding-v5、grox-multimodal-embedding-v8-2、grox-multimodal-embedding-v8-search(grox/flows/mm_emb/constants.py:17-20)。回复只写入 Manhattan(grox/flows/mm_emb/task_write_mm_embedding_sink.py:77-88)。
设计决策
| 决策 | 理由 |
|---|---|
| 并行产出三种嵌入 | 不同下游(推荐召回、搜索)可各自选择版本;新版本上线不影响旧消费方 |
| v5 截断到 1024 维 | 控制存储与检索成本;归一化后点积等于余弦相似度,适合召回 |
| v8 把作者画像写入输入 | 让嵌入同时编码“谁发的”;对推荐而言,作者身份本身是强信号 |
| 回复只写入 Manhattan | 回复量远大于原帖;回复嵌入不进入实时 Kafka 消费方,只供按 ID 查询 |
| 回复遇到私密账号时跳过 | 不为私密线程产出可被检索的内容表示 |
| 回填使用动态赞数阈值 | 在固定吞吐下优先处理最热的历史帖子 |
FAQ
Q:同一条帖子会被嵌入几次?
A:原帖进入 grox-multimodal-embedding-requests-with-summary 主题后,v5、v8.2、v8-search 三个生成器各消费一次,得到三份嵌入(grox/flows/mm_emb/generators.py:47-150)。恢复主题和回填主题会补算缺失的嵌入。
Q:v8 嵌入包含作者的粉丝数,这会不会让大号的帖子更容易被召回? A:源码只说明粉丝数档位进入了嵌入输入文本,没有说明召回如何使用这部分信息。“放大大号优势”属于推测,需要线上实验才能确认。
源码锚点
grox/flows/mm_emb/embedder.py:14-116—— v5 嵌入器与 1024 维截断grox/flows/mm_emb/renderer.py:7-46—— v5 渲染与图片占位符grox/flows/mm_emb/embedder_v82.py:16-114—— v8.2 嵌入器grox/flows/mm_emb/renderer_v82.py:12-284—— v8.2 指令与作者块grox/flows/mm_emb/embedder_v8_search.py:23-113—— v8-search 与回填子类grox/flows/mm_emb/renderer_v8_search.py:157-404—— v8-search 渲染与帧预算grox/flows/mm_emb/task_backfill_gate.py:16-60—— 回填闸门grox/flows/mm_emb/adaptive_min_fav.py:6-63—— 动态赞数阈值grox/flows/mm_emb/task_write_mm_embedding_sink.py:26-250—— 写入与 Kafka 分流
相关页面
- Grox 服务架构 —— 嵌入 Plan 的执行框架
- Grox 内容分类器 —— Grox 的另一类产出:内容分类
- 图片与视频模型 —— 另一套媒体嵌入:CLIP 媒体嵌入
- Phoenix 召回:双塔检索架构 —— 用多模态嵌入生成语义 ID 的召回模型
- VM Ranker —— 读取多模态嵌入的另一个排序组件
- 哈希嵌入 —— Phoenix 模型侧的 ID 哈希嵌入