TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

能否将自制的嵌入服务器替换为 Dynamo 嵌入工作进程?

状态:草案(2026-11-15),实测。 本文中的数字是由作者于 2026-11-15 亲自测量的值。基于 Kubernetes v1.34、Dynamo v1.6.2 的 vllm-runtime:1.6.2 镜像(vLLM 0.31.0)、Qwen3-Embedding-0.6B 模型以及单张 8GB 级别的笔记本 GPU。由于对比对象并非通用产品,而是作者自制的服务器,因此这些数字不能直接照搬到其他服务器上。 编写规则:以实测数据为基础,未能测量的数据标记为“未实测”,无法确认的内容标记为“未确认”。

将嵌入服务器迁移至 Dynamo

目录

1. 为什么是嵌入

第 3 篇和 第 4 篇是通过测试用聊天模型进行测量的。这一次,我们将 Dynamo 应用到了实际使用的服务中。然而,在仔细查看正在使用的服务时,发现它并不是聊天模型。这是一个由自制的 FastAPI 服务器组成的系统,在单张 GPU 上由同一个进程提供用于搜索(RAG)的嵌入、重排序以及语音识别服务。

Dynamo 的代表性功能在这个服务器上毫无用武之地。由于嵌入不生成 Token,因此前缀缓存、KV 路由和分离式服务均不适用。Dynamo 的代码也印证了这一点。嵌入工作进程的帮助文档中写着它会跳过 KV 事件和 KV 路由器的注册(v1.6.2, components/src/dynamo/vllm/backend_args.py).

因此,问题变了。 是否可以移除自制服务器并将其替换为 Dynamo 嵌入工作进程,如果这样做会有什么改变?这就是我们的问题。

2. 架构

Dynamo 的 vLLM 后端通过 --embedding-worker 参数变身为嵌入专用工作进程。在 vLLM 侧,同时传入了 --runner pooling 。

command: ["python3", "-m", "dynamo.vllm"]
args:
  - --model
  - Qwen/Qwen3-Embedding-0.6B
  - --embedding-worker
  - --runner
  - pooling
  - --max-model-len
  - "1024"
  - --gpu-memory-utilization
  - "0.4"

架构由一个前端和一个工作进程组成。模型预先下载了与现有服务器相同的修订版本(Revision),工作进程通过 HF_HUB_OFFLINE=1 启动。 --max-model-len 1024 是与现有服务器的输入限制相匹配的值。

对比步骤如下:

  1. 向现有服务器发送请求,收集向量和延迟数据(共 3 次)。
  2. 关闭现有服务器,在相同的 GPU 上部署 Dynamo 架构。
  3. 向 Dynamo 前端发送相同的请求(共 3 次)。
  4. 关闭 Dynamo 架构并重新启动现有服务器。

请求内容为 40 个文档(包含韩语和英语,平均每个文档 84 个 Token),每 8 个一组进行批处理。这是因为现有服务器的客户端实际上就是按每 8 个一组打包发送的。延迟测量了 30 次顺序请求以及并发 4 下的 40 次请求,并取 3 次重复测量结果的中位数。

3. 向量是相同的

项目 现有服务器 Dynamo 嵌入工作进程
维度 1024 1024
第一个向量的长度 (L2) 1.0 1.0
40个文档的 Token 总数 3,375 3,375
各文档的余弦相似度 最小 0.9996 · 中位数 0.9998 · 最大 0.9999

Token 数量完全相同,向量在小数点后第四位开始出现分歧。由于现有服务器直接使用 transformers 进行推理,而 Dynamo 侧使用 vLLM 进行推理,因此它们的计算顺序和精度处理有所不同。我们并未单独测量这是否是足以改变搜索排名的差异(未实测)。我们不能仅凭这些数字就断定是否可以在不重新构建索引的情况下混用它们。

4. 无法直接无缝替换

两个服务器都开启了 /v1/embeddings 。因为名字相同,看起来似乎只要改一下地址就行,但对于相同的请求,有四个地方给出了不同的响应。

请求 现有服务器 Dynamo 嵌入工作进程
dimensions: 256 200,256维 500
input_type: "query" (查询指令) 200,附加指令后共 28 个 Token 200, 忽略指令,共 9 个 Token
超出限制的长输入 200,截断为 1,024 个 Token 500
encoding_format: "base64" 忽略并返回数组 base64 字符串

dimensions. 工作进程日志中留下了原因。vLLM 认为该模型是不支持 Matryoshka 嵌入的模型,当存在 dimensions 时会拒绝请求。现有客户端在所有请求中都带有 dimensions 发送。即使该值与模型的原始维度相同,也会触发“不应存在”的检查,因此如果不修改客户端,所有请求都会失败。虽然我知道 vLLM 中存在将模型作为 Matryoshka 处理的重写选项,但本文未对此进行测试(未实测)。

查询指令。 这种情况更加危险,因为不会报错。Qwen3-Embedding 是一个设计为在查询前附加指令的模型,现有服务器通过接收 input_type 或 instruction 字段来代为处理该工作。而 Dynamo 侧则会丢弃不认识的字段并直接按原文字面量进行嵌入。两方针对相同查询生成的向量的余弦相似度为 0.82 。如果不带指令进行对比,则是 0.9995。如果仅仅更换地址,所有请求都会返回 200,但搜索质量会悄然下降。指令必须由客户端在文本中自行拼接后发送。

长输入。 现有服务器会在限制处进行截断,而 vLLM 在超出限制时会直接拒绝。很难说哪一方是正确的。不过,如果客户端一直将截断工作交由服务器处理,那么就必须把这部分逻辑接过来。

错误代码。 dimensions 和长输入都属于客户端请求错误的情况,但前端返回了带有 Internal server error 的 500 错误。工作进程日志中留下了 vLLM 的验证错误及其原因。由于许多客户端将 500 视为需要重试的对象,照此下去,客户端会不断重复发送无法修复的错误请求。这是在 v1.6.2 中观察到的行为,最新版本是否已更改尚未确认(未确认)。

5. 延迟与吞吐量

这是在同款 GPU(8GB 级别)上测量的值。它是包含 8 个批处理请求的单个请求的延迟,取 3 次重复测量结果的中位数。双方的错误发生次数均为 0.

顺序 p50 顺序 p99 并发 4 p50 并发 4 p99 并发 4 吞吐量
现有服务器 54.3ms 76.0ms 225.9ms 245.9ms 141.0文档/s
Dynamo 嵌入工作进程 36.9ms 40.5ms 77.4ms 108.9ms 395.8文档/s

顺序延迟为 0.68 倍,并发 4 时的吞吐量为 2.8 倍。每次重复的吞吐量方面,现有服务器为 139.4·141.0·141.2,Dynamo 为 409.1·395.8·351.9 文档/s。

并发 4 下出现这种现象的原因似乎在于现有服务器的架构。现有服务器用一个全局锁将推理过程包裹起来,一次只能处理一个请求。并发 4 的延迟(226ms)约为顺序延迟(54ms)的四倍,这便是痕迹所在。而 vLLM 会将同时到达的请求打包成一个批次(Batch)。不过,由于我们并未通过释放锁或关闭批处理进行单独测量,因此该解释并非经过验证的结论,而是契合数字的推测。

在阅读本表时有三点需要注意。

6. 无法迁移的部分

现有服务器执行三项任务,而本文尝试迁移的只有一项。

功能 Dynamo v1.6.2 的 vLLM 后端 在本文中
嵌入 --embedding-worker 实测
重排序 仅在 SGLang 后端侧的代码中找到了重排序处理器 未实测
语音识别 代码中存在实时转录处理器 未实测

如果只迁移嵌入,服务器数量就会从一个增加到两个。现有服务器将三个模型放在一个进程中运行,共享一张 8GB 的显卡;而如果将嵌入独立出来,该显卡上就会有两个进程,并且还会多出一个前端 Pod。如果只有一个工作进程,那么 Dynamo 前端所做的事情就只是将请求转发给该工作进程而已。

7. 总结

结合本系列前面的文章来看,其结论如下:KV 路由在存在前缀重叠的聊天请求中发挥作用(第 3 篇)。分离式服务需要具备快速的网络和大模型(第 4 篇)。对于像嵌入这样没有生成(Generation)过程的任务,这两者都不适用,剩下的问题在于 API 契约是否匹配(本文)。

参考

登录后即可点赞

评论

还没有评论。

登录后即可发表评论