Skip to content

多模态帖子嵌入器

Grox mm_emb flow 的三种帖子嵌入:v5(截断到 1024 维)、v8.2(带作者画像与转写)、v8-search(含历史回填)

Updated

这一页回答什么

本页说明 Grox 如何把一条帖子(文本、图片、视频帧、语音转写)编码成一个稠密向量。本页还说明当前并行运行的 v5、v8.2、v8-search 三种嵌入器的区别,以及嵌入的写入位置和读取方。

核心结论

  1. 三种嵌入器并行运行:MultimodalPostEmbedderV5、MultimodalPostEmbedderV82、MultimodalPostEmbedderV8Search(含回填子类)分别由 mm_emb_v5、mm_emb_v8_2、mm_emb_v8_search 三组 Plan 调用。旧版的 MultimodalPostEmbedderV2 和“带 Grok 摘要”的嵌入 Plan 已删除。
  2. v5 仍截断到 1024 维:模型为 recsys-v5-embedding。嵌入器截取输出的前 1024 维,再执行 L2 归一化(grox/flows/mm_emb/embedder.py:15、grox/flows/mm_emb/embedder.py:42-52)。
  3. v8 系列把作者画像写入嵌入输入:v8.2 与 v8-search 的输入文本以作者块开头。作者块包含认证颜色、账号标签、NSFW 账号标记、粉丝数档位、注册时间、国家和语言(grox/flows/mm_emb/renderer_v82.py:187-243)。因此,同一段文字由不同账号发出时,得到的嵌入不同。
  4. v8 系列不截断:输出维度取模型原生维度,嵌入器只执行 L2 归一化(grox/flows/mm_emb/embedder_v82.py:28-32)。
  5. 回复单独成流:三组嵌入都有 _for_reply 版本。回复嵌入只写入 Manhattan,不发送到 Kafka。回复所在线程中任一方是私密账号时,Plan 跳过该回复(grox/flows/mm_emb/task_filter.py:10-50)。
  6. 嵌入是召回的输入: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):

  1. 赞数闸门:赞数低于动态阈值时,丢弃该帖子。阈值初始为 20,000,目标通过速率为每秒 5 个(环境变量 MM_EMB_BACKFILL_TARGET_QPS)。
  2. 已存在闸门: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 分流

相关页面

Type to search…

↑↓ navigate↵ selectEsc close