TEI 在 arm64 上无解:我们如何用 llama.cpp 在 DGX Spark 上跑 bge-reranker-v2-m3
HuggingFace Text Embeddings Inference(TEI)没有 linux/arm64 镜像,这意味着它在 DGX Spark(GB10,ARM64)上永远起不来。本文记录我们如何改用同一个 llama.cpp 镜像跑 cross-encoder 重排模型,以及路上踩到的两个真实的坑。
目录
问题:TEI 在 GB10 上永远起不来
我们的重排(reranker)方案原计划用 HuggingFace 的 Text Embeddings Inference(TEI),它是 RAG 领域最常见的 cross-encoder 服务框架,接口简单、社区文档也多。但 DGX Spark 的 CPU 是 ARM64(GB10 是 Grace CPU + Blackwell GPU 的统一内存架构),而我们验证发现 TEI 在 1.5–1.8 以及 cpu 系列的全部标签下都没有发布 linux/arm64 镜像——用 docker manifest inspect 逐个标签核实,结果是一致的:镜像清单里只有 amd64[1]。也就是说,不是"配置麻烦",是这条路在这台机器上根本走不通。
方案:复用已经在跑的 llama.cpp 镜像
我们没有另起一套服务栈,而是让同一个 local/llama.cpp:server-cuda 镜像(本来就在跑 LLM)多起一个实例,用它自带的 --reranking 模式服务 cross-encoder 模型[2]。具体做法:
- 下载
BAAI/bge-reranker-v2-m3的 Q8_0 GGUF 量化版本(约 600MB,一次性),来自 gpustack 的 GGUF 转换仓库; - compose 里新增一个
reranker服务,镜像复用 llama.cpp 的 base 配置,命令行加--reranking标志; - 关键的一步:llama.cpp 的
/rerank接口返回的响应形状([{"index", "score"}, ...],按分数降序排列)和 TEI 的响应形状完全一致——这意味着后端原来为 TEI 写的解析器(rag/reranker.py)不需要改一行代码就能直接吃 llama.cpp 的输出,我们用一个探测脚本现场验证了这一点[2]。
这个契约兼容性是这次切换能"零改动上线"的关键——如果响应形状不一致,我们就得同时改基础设施和业务代码,风险会高很多。
坑:默认批大小装不下真实的 RAG 分片
上线后第一批真实流量打进来,reranker 直接对约 1K token 长度的文档分片返回 500 错误。原因是 llama.cpp 的默认物理批大小(ubatch)是 512——重排任务是非因果(non-causal)的,每一对"查询+分片"必须完整塞进一个物理批次,512 装不下 1024 token 的分片加查询词。修复很直接:把 --ctx-size 提到 8192、--batch-size/--ubatch-size 都提到 4096(覆盖 chunk_size 1024 加查询词留足余量),槽位数从默认降到 -np 2,避免过度占用显存[3]。
排查过程中还带出另外两个真实缺陷,都是在这次改造前就存在、但从未被"点亮"过的代码路径:一是 smoke_reranker.py 探测脚本 import 了一个不存在的类名(CrossEncoderReranker,实际类名是 Reranker)——这个脚本从写下的那天起就从没真正跑成功过,直到这次才被发现;二是 rag/reranker.py 里过滤空文本分片时,索引没有跟着重新对齐——doc_texts 过滤掉空文本后,索引映射回的却是过滤前的原始文档列表,存在"分数对错文档"的隐患,代码评审直接标为高优先级问题并在同一批修复里解决[1]。
效果:从全 0 分数到真实的 cross-encoder 排序
在这次改造之前,聊天路径从未真正接入过重排——检索直接拿向量召回的 top-8 作为最终答案上下文,rerank_score 字段要么不存在要么恒为 0。改造后,聊天路径先召回 24 个候选,交给 cross-encoder 精排出 8 个,生产环境日志确认:"Chat-path rerank applied, method: cross-encoder, candidates: 24, returned: 8"[1]。REST 检索接口的抽样验证里,rerank_score 是真实、有区分度的分数(例如同一批候选里的 5.47 / 5.28 / 4.72),不再是心照不宣的占位符[1]。
常见问题
Q: ARM64 上除了 llama.cpp,还有没有别的 TEI 替代方案?
A: 我们只验证过并采用了 llama.cpp 这一条路径——它的优势是可以直接复用我们已经在跑的 LLM 服务镜像,不用新引入一套基础设施。我们没有做过对其他 arm64 原生 reranker 服务框架的穷尽调研,如果你在评估其他方案,docker manifest inspect 逐标签核实 arm64 支持是第一步,别假设"社区常用框架"默认支持 ARM64。
Q: 为什么不干脆放弃重排,只用向量召回?
A: 我们改造前的状态正是"只用向量召回"——rerank_score 恒为 0,候选池直接截断在 8。改造后我们能观察到真实、有区分度的 cross-encoder 分数,说明重排确实在对候选做有意义的重新排序,而不只是走个流程。
Q: --ubatch-size 从 512 提到 4096,会不会拖慢重排本身或占用更多显存?
A: 会占用更多单批次显存,这是我们把并发槽位从默认值降到 -np 2 的直接原因——用更大的批容量换取"能处理真实分片长度",同时控制槽位数不过度挤占显存。我们没有对"提升批大小前后"的重排延迟做过专门的 A/B 测量,所以这里不给具体的延迟数字,只描述配置权衡本身。
关于 HTZL.AI
HTZL.AI 是四川慧途智联科技有限公司旗下的企业级私有 AI 品牌,专注于"数据不出企业、模型不进云端"的私有化部署路径,是 NVIDIA Inception 会员企业。本文所有数据均来自我们在真实 DGX Spark 生产环境上的工程记录。