← 文章 / AI技术
HuggingFace博客 2小时前 · 2026-08-30 02:37:46 · 0 阅读

Hugging Face 如何用 Inference Endpoints、Jobs 与 Buckets 驱动 Papers with Code 搜索

3 个月前,我们启动了 Papers with Code 的复兴计划(另见官宣推文)。它的目标是让开放的 AI 研究触手可及、易于消化:让人们能轻松找到与一篇论文相关的各类产物,纵览 AI 各领域的最先进(SOTA)成果,分享有趣的研究并在彼此的工作之上继续构建。换句话说,它的目标是助推那股终将孕育出下一个 Transformer 的研究浪潮。

当然,让 AI 研究触手可及需要一个强大的搜索引擎,让人和智能体都能通过网站或 pwc search CLI 命令快速找到相关联的工作,智能体还可以借助Skill 来使用它。

值得注意的是,检索学术论文与检索普通文本并不相同。一个好用的论文搜索引擎不仅要能精确匹配标题或 arXiv 编号,还应能理解"适用于代码生成的小语言模型"这类查询——即使这些词从未在一篇论文中同时出现。它需要识别出"最早的 BERT 论文"是一个导航式请求,能容忍不完整的标题或拼写错误,并且在模型服务冷启动或暂时不可用时依然快速响应。

Papers with Code search results for the query 'DINO'
Papers with Code 上针对查询 DINO 的搜索结果。

Papers with Code 项目中,我们把它做成了一个混合检索系统。这也源于我们在 ML6 的既往经验——我们曾为客户开发基于 RAG 的系统。实践证明,混合检索通常优于纯关键词检索和纯向量检索,因为它兼取两者之长(更多信息可参见这篇博客)。关键词检索能找到精确的提法,向量检索则能找到更模糊、语义相近的表述。需要说明的是,重排序器(reranker,又称 cross-encoder)可以进一步改善结果,但也会带来额外的开销和延迟。

Chart showing hybrid retrieval outperforming vector-only and keyword search
混合检索优于纯关键词检索和纯向量检索。图片来自 Microsoft 的 Azure AI Search: Outperforming vector search with hybrid retrieval and reranking(2023)。

Papers with Code 基于 PostgreSQL 数据库,其全文检索能力提供了快速的词法基线。在稠密向量一侧,我们用 pgvector 增加语义召回,再用 倒数排序融合(RRF)算法把两者结合起来。稠密向量嵌入用到了三项 Hugging Face 服务:

如今,该系统维护着来自 arXivDaily Papers 的超过 11 万篇现有论文的嵌入。本文将讲解这套架构、其背后的设计决策,以及我们把它推向生产过程中的经验教训。

摘要

我们刻意把检索拆分为离线语料构建和在线检索服务两部分:

Architecture diagram of the offline corpus build and online hybrid search pipeline
离线语料构建与在线混合检索管线的架构。

昂贵、面向吞吐量的工作交给 Jobs 运行。持久化产物存放在 Bucket 中。只有轻量的查询嵌入环节留在请求路径上,由一个受保护的 Inference Endpoint 承担,为在线检索提供动力。一旦该端点处于冷启动、繁忙或不健康状态,检索就立即回退到全文检索。这种分离让系统既强大又快速。

从严格的嵌入契约开始

嵌入管线的失效方式往往很隐蔽:模型版本(revision)变了、查询与文档的 prompt 混用了、向量被以不同方式截断,或者更新后的摘要与已存储的向量不再匹配。

我们的规避办法是把嵌入格式当作带版本的 API 来对待。每篇论文都被编码为:

normalized title + "\n\n" + normalized abstract

每次生成向量时,我们都会记录:

  • 模型仓库及确切的 revision;
  • 输出维度;
  • 输入格式版本;
  • 输入是查询还是文档;
  • 归一化方法;
  • 源标题与摘要的内容哈希。

我们的生产生成使用 Qwen/Qwen3-Embedding-0.6B,锁定到确切的 revision,输出 256 维 L2 归一化向量。我们是借助 MTEB 排行榜——比较嵌入模型的首选基准——选出这个模型的。值得注意的是,Qwen3 这类较新的嵌入模型还带来两个新特性:

  • 可以指定动态嵌入维度,在质量与速度/存储成本之间做权衡。Qwen 模型称之为 "MRL",即 Matryoshka Representation Learning(套娃表示学习)的缩写,详情可见这里。我们选择了 256 维的嵌入以提升检索速度。
  • 可以提供指令 prompt。Qwen 嵌入模型支持 document prompt(我们用它嵌入论文)和 query prompt(在线检索用它嵌入用户查询)。

这份契约让一个嵌入从导出、经 GPU 推理、写入 PostgreSQL、直至进入在线检索的全程都得到遵循。

用 Jobs 把数据库快照变成向量语料

全语料嵌入是典型的批处理工作负载:它需要在一段较短时间内占用 GPU,受益于高吞吐,且不应在两次运行之间消耗资源。Hugging Face Jobs 非常契合这种形态:一个 Job 由一条命令、一个硬件规格以及可选的 Docker 镜像定义,还能运行依赖内联声明的 uv 脚本。

我们的语料构建首先从 PostgreSQL 可重复读快照中导出每篇论文的最新版本。导出器以流式方式处理数据行而非把整个目录载入内存,写出大小受控的 JSONL 分片,并生成包含行数和 SHA-256 校验和的清单文件。

我们把这次运行的不可变目录同步到一个私有 Storage Bucket,再把 Bucket 直接挂载到一个 l4x1 Job(NVIDIA L4 GPU,24GB 显存)中。从 worker 的视角看,它就是一个普通的文件系统:

hf jobs uv run \
  --flavor l4x1 \
  --timeout 6h \
  --volume hf://buckets/OWNER/pwc-paper-embeddings:/bucket \
  embed_papers_job.py \
  --input /bucket/runs/RUN_ID/input \
  --output /bucket/runs/RUN_ID/output \
  --model Qwen/Qwen3-Embedding-0.6B \
  --revision MODEL_REVISION \
  --dimensions 256 \
  --allow-matryoshka

worker 的处理流程:

  1. 校验输入清单和每个分片的校验和;
  2. 加载锁定的模型 revision;
  3. 按文本长度排序以减少 padding;
  4. 分批调用 encode_document(见模型卡说明);
  5. GPU 显存不足时自动减小批大小;
  6. Matryoshka 表示截断到 256 维并做归一化;
  7. 以原子方式写出 float16 Parquet 分片;并
  8. 记录吞吐量、包版本、硬件、显存峰值、行数和输出校验和。

每个完成的分片都有自己的标记文件,因此重启的 Job 可以跳过已验证的工作。这对大型语料很有用:重试应当是续跑,而不是覆盖已有的嵌入。

在 5,000 篇论文的试点中,Qwen Job 在 L4 GPU 上以 1024 维做到了每秒编码约 75 篇论文。同一轮计算还能以确定性的方式在 512 维和 256 维上落地,于是我们无需为额外推理付费就能比较存储与检索的权衡。

Buckets 是连接组织

Storage Buckets 是 Hub 上可变的、类 S3 的对象存储,专为 AI 工作负载优化。它们可以通过 hf://buckets/... 路径访问,并可在 Jobs 中读写挂载,无需另建存储集成。

对我们来说,Bucket 不只是存放向量的地方。它是三个生命周期各异的系统之间的边界:

  • 生产数据库导出源记录;
  • 临时性的 Jobs 消费这些记录并产出向量;
  • 导入器在触碰检索索引之前先验证结果。

我们用不可变的运行前缀来组织产物:

runs/<run-id>/
├── input/
│   ├── manifest.json
│   └── papers-*.jsonl
└── output/
    ├── manifest.json
    ├── embeddings-*.parquet
    └── embeddings-*.complete.json

Bucket 本身刻意设计为可变存储,因此不可变性是一条应用层规则:运行 ID 绝不被覆盖,每个产物都有清单和校验和覆盖。

这带来几项有用的性质:

  • 可复现性:我们可以把一次数据库生成回溯到确切的语料快照、模型 revision 和一组产物。
  • 安全的重试:Jobs 可以在同一运行前缀下从已完成分片处恢复。
  • 低成本的实验:多个模型或维度可以复用同一份已验证的输入快照。
  • 受控的上线:导入一个生成并不会激活它。我们先验证覆盖率并构建索引。
  • 简单的回滚:在新一代被证明稳定之前,上一代及其产物保持可用。

只有当导入器复核了模式、校验和、维度、归一化、唯一的论文 ID 和当前内容哈希之后,我们才把向量加载进 PostgreSQL。随后为新一代单独构建 HNSW 索引,且只有当每一篇合资格的现有论文都被覆盖时,才原子性地将其标记为生效(HNSW 是一种支持快速向量检索的基于图的算法)。

Inference Endpoints 把语义检索放上请求路径

批量嵌入解决了检索的文档一侧。用户查询仍需在请求时用同一模型契约进行嵌入。

我们把锁定的模型部署为经过身份验证的 Inference Endpoint,后端是 Text Embeddings Inference(TEI)。该端点接收查询文本,使用模型的 query prompt 返回归一化的 256 维向量。值得一提的是,这里也可以用 vLLMSGLang

Papers with Code 查询嵌入模型的 Hugging Face Inference Endpoint。

API 随后在生效的 pgvector 生成上执行余弦距离检索:

SELECT paper_id,
       embedding <=> CAST(:query_vector AS halfvec(256)) AS distance
FROM paper_embeddings
WHERE generation_id = :active_generation
ORDER BY embedding <=> CAST(:query_vector AS halfvec(256))
LIMIT 50;

HNSW 索引保证了检索的速度。在 5,000 篇论文的试点中,256 维 Qwen 索引相对精确检索达到 0.9955 的 Recall@20,HNSW 查找延迟 p50 为 1.31 毫秒、p95 为 2.21 毫秒。其表和索引约占 1024 维版本存储量的 27%,而在该测试中 ANN 召回率基本持平。

该 Endpoint 配置为最多一个副本,空闲时可缩容到零。这是一个有用的成本杠杆——没有用量就不用付费。但这也意味着冷启动必须成为应用设计的一部分,而不是被视为异常事件,因为端点启动并开始承接流量需要一些时间。

因此我们的查询客户端有意设计了严格的行为:

  • 生产环境 1 秒超时;
  • 非阻塞的并发上限;
  • 对响应的维度、有限性和范数做校验;
  • 以查询和嵌入生成为键的短时缓存;
  • 反复失败后触发熔断器;以及
  • 日志中不落原始查询文本,只记录归一化指纹。

如果端点正在扩容、超时、返回了格式错误的向量,或没有可用并发,我们就立即跳过语义分支。用户仍然能收到词法检索结果,而不是苦等一个不可靠的依赖。

Inference Endpoints 的可靠性非常出色,还带有实用的仪表盘,可以快速查看关键分析数据。

Hugging Face Inference Endpoint analytics dashboard showing request volume, errors, latency, and replica state
Inference Endpoint 分析面板展示请求量、错误、延迟和副本状态。

混合检索强于任何单一分支

对每个查询,词法分支用加权的 PostgreSQL 全文检索最多取回 50 个候选,语义分支从 pgvector 取回最多 50 个候选。

我们用加权倒数排序融合(RRF)合并两者的排名:

score(d)=∑r ∈ {lexical, semantic}wrk+rankr(d) \text{score}(d) = \sum_{r \,\in\, \{\text{lexical},\, \text{semantic}\}} \frac{w_r}{k + \text{rank}_r(d)} score(d)=r∈{lexical,semantic}∑​k+rankr​(d)wr​​

RRF 简单而稳健,因为它融合的是排名,而非来自两套分数量纲不同系统的分数。基本思路是:如果一篇论文在词法分支和语义分支都排名靠前,它在混合检索中排名靠前的几率也更大。我们目前对两个分支使用相等权重,并取 (k=60)(k 是"排名常数",RRF 算法的超参数)。

稠密检索提升了概念型查询的召回率。全文检索对精确术语、标识符和罕见名称依然表现出色。在融合排序之上,我们还保留了确定性的身份匹配行为:

  • 精确标题和 arXiv 编号始终置顶;
  • 方法分类体系能识别"最早的 BERT 论文"这类导航式搜索;
  • 不完整的标题和有限范围内的拼写错误使用保守的 trigram 候选;以及
  • 含糊的模糊匹配宁可不给出结果,也不强行返回一个坏答案。

注意:混合检索并非总是最优解。建议先从关键词检索这个廉价快速的基线起步,只有当语义检索和/或混合检索确实能带来可观的检索质量提升时再把它们加上。还可以在关键词/语义/混合检索之后接一个重排序器来进一步改进,例如 Qwen3-Reranker 这样的模型。

一个 Endpoint,两条更新路径

大规模初始语料用 Jobs 嵌入,但 Papers with Code 在持续变化:新论文不断到来,摘要会被更正,新的 arXiv 版本会成为现行版本。

为寥寥几行变更启动一个 GPU Job 会带来不必要的启动和编排开销。因此,一个每小时运行的增量进程会选出缺失或内容有变的论文,把有界增量发送到同一个 TEI Endpoint——这次使用 document prompt。

每轮最多处理 500 篇论文,批大小为 16。写入嵌入之前,会先锁定源数据行并再次核对内容哈希。如果某篇论文在推理期间发生了变化,该向量即被丢弃,留待下一轮处理。

这形成了有益的分工:

  • Jobs 负责全量重建、新模型世代和大规模回填。
  • Inference Endpoints 负责交互式查询嵌入和小规模增量文档更新。
  • Buckets 保存大规模构建的产物,让构建可续跑、可审计。

这条每小时运行的路径让生效索引紧贴实时目录,又不会把在线端点变成无节制的批处理器。

相关论文在线上几乎零成本

同样的文档向量还支撑着每个论文页面上的"相关论文"推荐。

Related papers feature
SenseNova-U1 的相关论文。

由于源论文已有存储的向量,相关论文的获取在请求时完全无需调用模型——只是对当前生效世代的一次最近邻查询。如果某个向量暂时缺失,应用可以回退到之前的 arXiv 版本,或用现有的基于任务和引用的兜底逻辑填充结果。我们通过 Semantic Scholar API 获取引用数据,还构建了 s2-cli——一个查询其引用图谱的命令行工具。https://paperswithcode.co/chat 上的智能体就在使用它。

我们的心得

  1. 把吞吐型工作与延迟敏感型工作分开

语料嵌入和查询嵌入用的是同一个模型,但它们是两个不同的基础设施问题。Jobs 优化吞吐量和有界成本;Inference Endpoints 优化可用性和请求延迟。

  1. 让存储成为计算与生产之间的显式契约

Buckets 在计算与生产之间提供了显式的交接。带校验和的产物在数据进入生产索引之前筑起一道可审查的边界。

  1. 要固定的不只是模型名

revision、维度、prompt、归一化和输入格式化器都会影响检索效果。把它们一起存储,并在每个环节加以校验。

  1. 为冷启动而设计

当流量时断时续时,缩容到零很有价值,但前提是产品要有快速回退方案。混合检索天然给了我们这个回退:词法检索本身永远有用。

  1. 更小的向量可以是一种系统特性

Matryoshka 嵌入让我们把质量、内存、索引大小和延迟当作一个整体权衡来评估。在试点中,256 维在保持 ANN 召回率的同时,存储相比 1024 维大幅缩减。

  1. 激活应当是平淡无奇的

新一代数据在现行世代旁边导入,独立构建索引,经过完整且最新的覆盖检查后原子性激活。回滚只是一次配置变更,而不是一场紧急重算。

欢迎在 https://paperswithcode.co 试用搜索,或在 https://paperswithcode.co/chat 体验聊天界面,并随时向我们反馈!

原始来源: HuggingFace博客

评论 (0)