AREX Feed Article
Perplexity 发布自研嵌入服务研究:自测 p99 延迟约为 vLLM 五分之一
北京时间 9 月 5 日凌晨 5 时 17 分(UTC 时间 9 月 4 日 21 时 17 分),宣布“今天发表了研究”,介绍其“这些模型背后的 SoTA(业界最优水平)服务基础设施”是如何构建的,并附上研究文章 。按文章介绍,这套系统分三层:Rust 写的 HTTP 网关 Ivy、Rust 写的 gRPC 推理服务器 Tulip,以及 Python 写的模型引擎 ROSE(Runtime-Optimized Serving Engine 的缩写)。
这批数字出自 Perplexity 的自测口径:在 BGE-M3 模型、128 tokens 的在线嵌入负载、单张 H200 上,p50 延迟约为对照引擎 vLLM 的三分之一,p99 约为五分之一。时给出同一口径。上述数字与后文全部系统描述,均出自 Perplexity 官方推文、研究文章与公司高管转发,属公司自述。
每条回答都先经过嵌入与排序模型
官宣推文的开篇写道:“Perplexity 的每一条回答,都始于嵌入与排序模型挑出与查询最相关的结果。”Perplexity 在博文中称,从 Search、Computer 到 API Platform,快速准确的检索贯穿全部产品线。
查询与文档会被嵌入到同一向量空间,再按最近向量完成检索。文章把检索所基于的索引描述为 exabyte 级(exabyte-scale search index),嵌入模型是自研的 pplx-embed,训练与服务都由自己完成。
由此产生两类负载。批量嵌入(batch embedding)在建库、扩库、重建索引时把大量文档向量化,以吞吐优先;向量检索之后的大批量打分,则在吞吐与延迟之间取平衡。在线嵌入(online embedding)在每次查询时只嵌入一条短查询,以延迟优先。
Perplexity 的解法是复用。嵌入通常是小 Transformer 模型,博文把批量嵌入类比为 LLM 推理中计算密集的 prefill(预填充)阶段,把在线嵌入类比为访存密集的 decode(解码)阶段,于是直接复用为 LLM 写好的两类内核。
按文中的说法,只用很少的额外工程就换来了大批次推理吞吐,同时保住了在线负载的低延迟。
请求怎么穿过 Ivy、Tulip 和 ROSE
嵌入推理通过标准化 API 暴露,内部与对外 API Platform 走同一套接口。一条请求在内部要过三个服务。
Ivy 是 Rust 写的 HTTP 网关,承担 CPU 侧工作:JSON 解析、分词(tokenization)、输入模板化、批次拆分,再把请求翻译成自定义 gRPC 协议发给下游。
生产环境的请求体大小不一,Ivy 会把大批次请求切成块、在副本间负载均衡,以改善利用率、抹平延迟。文中还称,自研的 unigram(一元文法)分词器已在 Ivy 全量上线,相对现成分词器大幅改善延迟;调分词与输入格式参数,也不必再动更重的推理实例。
Tulip 是 Rust 写的 gRPC 推理服务器,基于 tokio 与 tonic 实现。它接收推理请求、维护请求池,按先来先服务(FCFS)把序列凑成批次发给 GPU。
调度逻辑刻意保持简单,背后是一个观察:对服务负载下的小型嵌入模型,dense 层的线性成本大于 attention 的平方成本,延迟基本由 token 数决定,与序列数关系不大。
一旦批次大到能喂饱 GPU(约 512 tokens,针对十亿参数以下的模型),再往批次里塞序列也不会提升效率。
ROSE 是模型引擎,主要用 Python 实现,提供多种模型的 kernel、层与定义,负责前向传播与面向嵌入的 CUDA graph(把整轮内核启动预录成一次调用的机制)管理。
它与 Tulip 通过 step() 函数桥接:step() 接收一个批次,返回对 GPU 上计算结果的引用。
一次 CUDA 调用启动整轮前向,省掉 Python 启动开销
博文给出的判断是:Transformer 与 Hopper/Blackwell 架构都已成熟,各家推理引擎在 GPU 侧的嵌入实现已趋近最优,机会在把模型端到端暴露给客户端的运行时与外围代码里。
小批次下,CPU 侧的内核启动开销可以盖过 GPU 计算。Tulip 的应对是为每个嵌入模型构建整模型 CUDA graph:把一次前向所需全部内核的启动元数据预先录制成一张图,之后用一次 CUDA driver 调用启动,省掉反复执行 Python 与 PyTorch 启动代码的成本。
文中测算,对小型嵌入模型,要到数千 token、几十条序列的批次规模,GPU 执行成本才会超过 CPU 启动成本。
CUDA graph 要按配置分别捕获,嵌入场景就是序列数与 token 数的每种组合各录一张。token 数被填充(pad)成 64 或 256 的倍数,典型模型仍会产生数千张图,捕获全部配置可能耗时数分钟。
Tulip 的应对是边服务边惰性捕获:某配置第一次命中先跑一次 eager(即时执行)预热,第二次命中才触发捕获与回放,此后全部走回放,把数分钟的预热摊到数小时的运行里;代价是启动期的 p99 延迟会受影响。
文中还称,部分 attention 实现依赖动态的 host 侧输入配置内核启动,无法整图捕获,团队已向上游相关内核提交改动,以便在自己的推理引擎里启用整模型捕获。
第二个技巧是 LazyTensor。它跟踪一块页锁定(page-locked)主机内存与一次 cudaMemcpyAsync 拷贝,用事件标记 GPU 结果何时可读。
step() 不再等 CUDA graph 跑完,而是立刻返回一个 LazyTensor。
CPU 因此可以在 GPU 执行当前批次时,提前备好并下发下一批。CUDA graph 压掉启动开销,LazyTensor 把 CPU 从同步等待里解放出来,两层叠加换来低延迟与高吞吐。
嵌入复用 LLM 内核,只把 attention 换成 ragged
ROSE 原本为 LLM 服务而写,这次被改造为同时执行嵌入模型。差异集中在 attention 层。
按博文的说法,pplx-embed 的 serving 与 Qwen3.5 LLM 的 decode 走同一批内核,嵌入与 LLM 的 dense 层完全一致,因为 token 向量彼此独立处理。
差异的处理方式是给内核增加 ragged 输入支持。LLM 服务需要 paged(分页)prefill 与 decode 布局,嵌入服务则完全不实例化 KV cache,改用支持 ragged 格式(不规则长度、免 padding)的 attention kernel。
转换与校准例程同样与 LLM 共享。一个从 LLM 微调出来的嵌入模型,由此可以直接进入原型、评估与生产推理。
内核选择仍要逐案决定。ROSE 集成了 FlashInfer 2、FlashInfer 3 与 FlashAttention 4 三套 attention 后端。
博文称总体上 FlashAttention 4 更快,但在超长序列上,FlashInfer 3 对 Qwen 系模型反而胜出;选哪套要看模型的注意力头数与维度。
对照 vLLM v0.22.0:warmup、BF16 与四组负载
对比对象是 vLLM v0.22.0,BF16 精度,用真实模型权重与来自评估数据集的输入做推理。正式计时前先做 warmup(预热),校验两边输出结果的余弦相似度差异在 0.1% 以内。
博文把测试分成四组:
- 低延迟嵌入:预分词、batch size 1、完全顺序请求,序列长 128/512/4096
- 低延迟打分:预分词、批次 5/25/50,序列长 512
- 高吞吐嵌入:batch size 100、四个并发进程,序列长 512/1024/4096
- 高并发嵌入:1/2/4/8/16 路并发,序列长 512,并计入 Ivy 的分词以及 Ivy 到 Tulip 的网络开销
以低延迟组为例:BGE-M3 在 128 tokens 时,Tulip 的 p50 为 1.53 ms、p99 为 1.67 ms,vLLM 基线分别为 4.60 ms 与 7.96 ms。
自家模型 pplx-embed-1-0.6b 在同条件下为 p50 1.88 ms、p99 2.26 ms,基线为 4.94 ms 与 7.94 ms。
博文的收束口径是:Ivy、Tulip、ROSE 的组合让嵌入服务延迟更低、吞吐更高,检索更准、成本更低。文章还写道,vLLM、SGLang、TokenSpeed 等开源推理引擎正在把 Rust 与 C++ 引入技术栈,而 Perplexity 自称过去两年投入 Rust,在性能与可维护性上回报明显。
,把这篇称为“Perplexity 如何规模化服务搜索结果的深潜”,涵盖为排序做嵌入、GPU 模型推理、请求批处理、推理服务器运行与延迟/吞吐权衡。
Yarats 的引用转发则把主题概括为:为自家 SoTA 的 pplx-embed 模型服务嵌入与重排器(reranker)。他在转发末尾写道,对这类问题感兴趣的人可以直接私信他,或向 Perplexity 投递简历。
在文末的 Future Work 一节,Perplexity 表示会继续改进每一层,以同时压低 CPU 与 GPU 两侧的延迟;随着生态对 free-threaded Python 的支持增长,Python 与 Rust 的互操作开销还有望进一步下降。