{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/huggingface-co/2026-08-21-how-hugging-face-inference-endpoints-jobs-and-buckets-power/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "3 months ago, we started a [revival](https://www.reddit.com/r/MachineLearning/comments/1tgmwqr/reviving_paperswithcode_by_hugging_face_p/) of [Papers with Code](https://paperswithcode.co) (see also the [announcement tweet](https://x.com/NielsRogge/status/2056366395605078252)). Its goal is to make open AI research accessible and digestible, so that people can easily find the artifacts related to a paper, find state-of-the-art (SOTA) across the various domains of AI, share interesting research and build on top of each other's work. In other words, its goal is to power the wave of research that leads to the next [Transformer](https://paperswithcode.co/paper/1706.03762).\n\nOf course, making AI research accessible requires a powerful search engine, so that humans and agents can quickly find relevant and related work, either through the website or the `pwc search` [CLI command](https://github.com/huggingface/pwc-cli), which agents can use via the [Skill](https://github.com/huggingface/pwc-cli/blob/main/standalone_cli/SKILL.md).\n\nIt's important to note that searching for research is not quite the same as searching for regular text. A useful paper search engine should find an exact title or arXiv identifier, but it should also understand a query such as “small language models for code generation” even when those words do not appear together in a paper. It needs to recognize that “the original BERT paper” is a navigational request, tolerate an incomplete title or typos, and still respond quickly when a model service is cold or temporarily unavailable.\n\n![Papers with Code search results for the query 'DINO'](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/search-results.png)\\\nSearch results on Papers with Code for the query DINO.\n\nFor [Papers with Code](https://paperswithcode.co), we built this as a **hybrid search** system. This is also based on our prior experience at [ML6](http://ml6.eu/), where we developed [RAG](https://paperswithcode.co/paper/2005.11401)-based systems for clients. It turned out that hybrid search typically outperforms keyword- and vector-based search systems, as it combines the best of both worlds (see also [this blog](https://techcommunity.microsoft.com/blog/azure-ai-foundry-blog/azure-ai-search-outperforming-vector-search-with-hybrid-retrieval-and-reranking/3929167) for more info). Keyword search finds exact mentions, whereas vector search finds more fuzzy, semantically similar terms. Note that [rerankers](https://www.pinecone.io/learn/series/rag/rerankers/) (also called cross-encoders) can further improve the results, although they also come with additional overhead and latency.\n\n![Chart showing hybrid retrieval outperforming vector-only and keyword search](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/hybrid-search.png)\\\nHybrid retrieval outperforms keyword- and vector-only search. Figure from Microsoft, [Azure AI Search: Outperforming vector search with hybrid retrieval and reranking](https://techcommunity.microsoft.com/blog/azure-ai-foundry-blog/azure-ai-search-outperforming-vector-search-with-hybrid-retrieval-and-reranking/3929167) (2023).\n\nPapers with Code relies on a PostgreSQL database, hence its full-text search capabilities provide a fast lexical baseline. For dense embeddings, [pgvector](https://github.com/pgvector/pgvector) is used to add semantic recall, and the [reciprocal rank fusion (RRF)](https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion) algorithm combines the two. Three Hugging Face services are used for the dense embeddings:\n\n- [Hugging Face Jobs](https://huggingface.co/docs/hub/jobs) gives us burstable GPU compute for embedding the paper corpus.\n- [Hugging Face Storage Buckets](https://huggingface.co/docs/hub/storage-buckets) provides the durable handoff between our database, experiments, and Jobs.\n- [Hugging Face Inference Endpoints](https://huggingface.co/docs/inference-endpoints/index) serves low-latency embeddings for live queries and incremental updates.\n\nToday, the system maintains embeddings for more than 110,000 current papers sourced from [arXiv](https://arxiv.org/) and [Daily Papers](https://hf.co/papers). This post explains the architecture, the design decisions behind it, and the lessons we learned while taking it to production.\n\n## TL;DR\n\nWe deliberately split search into an offline corpus build and an online search service:\n\n![Architecture diagram of the offline corpus build and online hybrid search pipeline](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/architecture.png)\\\nArchitecture of the offline corpus build and online hybrid search pipeline.\n\nThe expensive, throughput-oriented work runs as Jobs. Durable artifacts live in a Bucket. Only the small query-embedding step sits on the request path, behind a protected Inference Endpoint, to power the online search. If that endpoint is cold, busy, or unhealthy, search immediately falls back to full-text retrieval. This separation makes the system both powerful and fast.\n\n## Start with a strict embedding contract\n\nEmbedding pipelines often fail in subtle ways: a model revision changes, query and document prompts are mixed up, vectors are truncated differently, or an updated abstract no longer matches its stored vector.\n\nWe avoid this by treating the embedding format as a versioned API. Every paper is encoded as:\n\n```text\nnormalized title + \"\\n\\n\" + normalized abstract\n```\n\nFor each vector generation, we record:\n\n- the model repository and exact revision;\n- the output dimension;\n- the input-format version;\n- whether the input is a query or a document;\n- the normalization method;\n- a content hash for the source title and abstract.\n\nOur production generation uses [`Qwen/Qwen3-Embedding-0.6B`](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B), pinned to an exact revision, with 256-dimensional L2-normalized vectors. We selected the model with help from the [MTEB leaderboard](https://huggingface.co/spaces/mteb/leaderboard), the go-to benchmark for comparing embedding models. Note that newer embedding models like Qwen3 allow for 2 new features:\n\n- one can specify a **dynamic embedding size**, which allows to trade-off quality with speed/storage costs. Qwen models call this \"MRL\" which is short for [Matryoshka Representation Learning](https://paperswithcode.co/paper/2205.13147). You can learn all about it [here](https://huggingface.co/blog/matryoshka). We chose an embedding size of 256 to make the search fast.\n- one can provide an **instruction prompt**. Qwen embedding models support a `document` prompt (which we use to embed the papers) and live searches use their `query` prompt (to embed the user query).\n\nThis contract follows an embedding from export, through GPU inference, into PostgreSQL, and finally into online retrieval.\n\n## Jobs turn a database snapshot into a vector corpus\n\nFull-corpus embedding is a classic batch workload. It needs a GPU for a relatively short period, benefits from high throughput, and should not consume resources between runs. [Hugging Face Jobs](https://huggingface.co/docs/huggingface_hub/guides/jobs) fits that shape well: a Job is defined by a command, a [hardware flavor](https://huggingface.co/docs/hub/main/en/jobs-pricing#pricing), and optionally a Docker image, and can run [uv](https://docs.astral.sh/uv/) scripts with their dependencies declared inline.\n\nOur corpus build starts by exporting the latest version of every paper from a repeatable-read PostgreSQL snapshot. The exporter streams rows rather than loading the catalog into memory, writes bounded JSONL shards, and creates a manifest containing row counts and SHA-256 checksums.\n\nWe sync that immutable run directory to a private [Storage Bucket](https://huggingface.co/docs/hub/storage-buckets) and mount the Bucket directly (using [hf-mount](https://github.com/huggingface/hf-mount)) into an `l4x1` Job (an NVIDIA L4 GPU, which has 24GB of VRAM). From the worker's perspective it is simply a filesystem:\n\n```bash\nhf jobs uv run \\\n  --flavor l4x1 \\\n  --timeout 6h \\\n  --volume hf://buckets/OWNER/pwc-paper-embeddings:/bucket \\\n  embed_papers_job.py \\\n  --input /bucket/runs/RUN_ID/input \\\n  --output /bucket/runs/RUN_ID/output \\\n  --model Qwen/Qwen3-Embedding-0.6B \\\n  --revision MODEL_REVISION \\\n  --dimensions 256 \\\n  --allow-matryoshka\n```\n\nThe worker:\n\n1. verifies the input manifest and every shard checksum;\n2. loads the pinned model revision;\n3. sorts texts by length to reduce padding;\n4. calls `encode_document` in batches (as noted in the [model card](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B));\n5. reduces the batch size automatically if the GPU runs out of memory;\n6. truncates the [Matryoshka representation](https://huggingface.co/blog/matryoshka) to 256 dimensions and normalizes it;\n7. writes float16 Parquet shards atomically; and\n8. records throughput, package versions, hardware, peak VRAM, row counts, and output checksums.\n\nEach completed shard has its own marker, so a restarted Job can skip verified work. This is useful for a large corpus: retrying should just resume work rather than overwriting existing embeddings.\n\nIn our 5,000-paper pilot, the Qwen Job encoded about 75 papers per second at 1024 dimensions on an L4 GPU. The same pass could be deterministically materialized at 512 and 256 dimensions, so we could compare the storage and retrieval trade-offs without paying for more inference.\n\n## Buckets are the connective tissue\n\n[Storage Buckets](https://huggingface.co/docs/hub/storage-buckets) are mutable, S3-like object storage on the Hub, optimized for AI workloads. They can be accessed through `hf://buckets/...` paths and [mounted](https://github.com/huggingface/hf-mount) read-write in Jobs without building a separate storage integration.\n\nFor us, the Bucket is more than a place to put vectors. It is the boundary between three systems with different lifecycles:\n\n- the production database exports source records;\n- ephemeral Jobs consume those records and produce vectors;\n- the importer validates the results before touching the search index.\n\nWe organize artifacts under immutable run prefixes:\n\n```text\nruns/<run-id>/\n├── input/\n│   ├── manifest.json\n│   └── papers-*.jsonl\n└── output/\n    ├── manifest.json\n    ├── embeddings-*.parquet\n    └── embeddings-*.complete.json\n```\n\nBuckets themselves are intentionally mutable, so immutability is an application-level rule: a run ID is never overwritten, and every artifact is covered by a manifest and checksum.\n\nThis gives us several useful properties:\n\n- **Reproducibility:** we can trace a database generation back to an exact corpus snapshot, model revision, and set of artifacts.\n- **Safe retries:** Jobs can resume from completed shards in the same run prefix.\n- **Cheap experiments:** several models or dimensions can reuse one verified input snapshot.\n- **Controlled rollout:** importing a generation does not activate it. We first validate coverage and build its index.\n- **Simple rollback:** the previous generation and its artifacts remain available until the new one is proven stable.\n\nOnly after the importer rechecks schemas, checksums, dimensions, normalization, unique paper IDs, and current content hashes do we load the vectors into PostgreSQL. We then build a separate [HNSW](https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world) index for the new generation and atomically mark it active only when every eligible current paper is covered (HNSW is the graph-based algorithm that enables fast vector search).\n\n## Inference Endpoints put semantic search on the request path\n\nBatch embeddings solve the document side of retrieval. A user query still needs to be embedded at request time using the same model contract.\n\nWe deploy the pinned model as an authenticated [Inference Endpoint](https://huggingface.co/docs/inference-endpoints/index) backed by [Text Embeddings Inference (TEI)](https://github.com/huggingface/text-embeddings-inference). The endpoint accepts the query text and returns a normalized 256-dimensional vector using the model's `query` prompt. Note that one could also leverage [vLLM](https://github.com/vllm-project/vllm) or [SGLang](https://github.com/sgl-project/sglang) here.\n\n![Hugging Face Inference Endpoint overview for the Papers with Code query embedding model](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/inference-endpoint.png)\\\nHugging Face Inference Endpoint for the Papers with Code query embedding model.\n\nThe API then performs a cosine-distance search over the active pgvector generation:\n\n```sql\nSELECT paper_id,\n       embedding <=> CAST(:query_vector AS halfvec(256)) AS distance\nFROM paper_embeddings\nWHERE generation_id = :active_generation\nORDER BY embedding <=> CAST(:query_vector AS halfvec(256))\nLIMIT 50;\n```\n\nThe HNSW index keeps this lookup fast. On our 5,000-paper pilot, the 256-dimensional Qwen index achieved 0.9955 Recall@20 against exact search, with 1.31 ms p50 and 2.21 ms p95 HNSW lookup latency. Its table and index used about 27% of the storage of the 1024-dimensional version while retaining essentially the same ANN recall in that test.\n\nThe Endpoint is configured with a maximum of one replica and can [scale to zero](https://huggingface.co/docs/inference-endpoints/guides/autoscaling) when idle. That is a useful cost lever, as this means you're not paying when there's no usage. However, this also means cold starts must be part of the application design rather than treated as an exceptional event, as it takes some time for the endpoint to spin up and serve traffic.\n\nOur query client therefore has deliberately strict behavior:\n\n- a one-second production timeout;\n- a non-blocking concurrency limit;\n- response dimension, finiteness, and norm validation;\n- a short cache keyed by the query and embedding generation;\n- a circuit breaker after repeated failures; and\n- no raw query text in logs, only a normalized fingerprint.\n\nIf the endpoint is scaling up, times out, returns a malformed vector, or has no concurrency available, we skip the semantic branch immediately. Users still receive lexical results instead of waiting for an unreliable dependency.\n\nInference Endpoints works really reliably, and includes a nice dashboard so you can quickly see key analytics.\n\n![Hugging Face Inference Endpoint analytics dashboard showing request volume, errors, latency, and replica state](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/endpoint-analytics.png)\\\nInference Endpoint analytics showing request volume, errors, latency, and replica state.\n\n## Hybrid retrieval is stronger than either branch alone\n\nFor every query, the lexical branch retrieves up to 50 candidates using weighted PostgreSQL full-text search. The semantic branch retrieves up to 50 candidates from pgvector.\n\nWe combine their ranks using weighted reciprocal rank fusion (RRF):\n\nscore(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)}\n\nRRF is simple and robust, because it combines ranks rather than scores from two systems with different scales. Basically, if a paper is ranked high both by the lexical branch and the semantic branch, it has a higher chance of being ranked high by the hybrid search. We currently use equal branch weights and (k=60) (k is the \"rank constant\", a hyperparameter of the RRF algorithm).\n\nDense retrieval improves recall for conceptual queries. Full-text retrieval remains excellent for exact terminology, identifiers, and rare names. We also preserve deterministic identity behavior on top of the fused ranking:\n\n- exact titles and arXiv IDs stay at the top;\n- the [method taxonomy](https://paperswithcode.co/methods) recognizes navigational searches such as “the original BERT paper”;\n- incomplete titles and bounded spelling mistakes use conservative trigram candidates; and\n- ambiguous fuzzy matches abstain rather than forcing a bad result.\n\nNote: hybrid search isn't always the best option, it is recommended to start with keyword search as a cheap and fast baseline, and only adding semantic and/or hybrid search when it turns out those give a reasonable boost in retrieval quality. One could further improve the search by adding a reranker after keyword/semantic/hybrid retrieval, using a model like [Qwen3-Reranker](https://huggingface.co/collections/Qwen/qwen3-reranker).\n\n## One Endpoint, two update paths\n\nThe large initial corpus is embedded with Jobs, but Papers with Code changes continuously. New papers arrive, abstracts are corrected, and new arXiv versions become current.\n\nLaunching a GPU Job for a handful of changed rows would add unnecessary startup and orchestration overhead. Instead, an hourly incremental process selects missing or content-changed papers and sends a bounded delta to the same TEI Endpoint, this time with the `document` prompt.\n\nEach run processes at most 500 papers in batches of 16. Before an embedding is written, the source row is locked and its content hash is checked again. If a paper changed during inference, that vector is discarded and picked up by the next run.\n\nThis gives us a useful division of labor:\n\n- **Jobs** handle full rebuilds, new model generations, and large backfills.\n- **Inference Endpoints** handle interactive query embeddings and small incremental document updates.\n- **Buckets** preserve the large-build artifacts and make those builds resumable and auditable.\n\nThe hourly path keeps the active index close to the live catalog without turning an online endpoint into an unbounded batch processor.\n\n## Related papers become almost free online\n\nThe same document embeddings also power related-paper recommendations on each paper page.\n\n![Related papers feature](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/related_papers.png)\\\nRelated papers for [SenseNova-U1](https://paperswithcode.co/paper/2605.12500).\n\nBecause the source paper already has a stored vector, related-paper retrieval requires no model call at request time. It is a single nearest-neighbor query over the active generation. If a vector is temporarily missing, the application can use a previous arXiv version or fill results from the existing task- and citation-based fallback. We fetch citation data through the [Semantic Scholar API](https://www.semanticscholar.org/product/api) and also built [`s2-cli`](https://github.com/huggingface/s2-cli), a command-line interface for querying its citation graph. The latter is used by the agent at [https://paperswithcode.co/chat](https://paperswithcode.co/chat).\n\n## What we learned\n\n### 1. Separate throughput work from latency-sensitive work\n\nCorpus embedding and query embedding use the same model, but they are different infrastructure problems. Jobs optimize for throughput and bounded cost; Inference Endpoints optimize for availability and request latency.\n\n### 2. Make storage the explicit contract between compute and production\n\nBuckets provide an explicit handoff between compute and production. Checksummed artifacts create a reviewable boundary before data enters the production index.\n\n### 3. Pin more than the model name\n\nThe revision, dimension, prompt, normalization, and input formatter all affect retrieval. Store them together and validate them everywhere.\n\n### 4. Design for cold starts\n\nScale-to-zero is valuable when traffic is intermittent, but only if the product has a fast fallback. Hybrid search gave us that fallback naturally: lexical search is always useful on its own.\n\n### 5. Smaller vectors can be a systems feature\n\nMatryoshka embeddings let us evaluate quality, memory, index size, and latency as one trade-off. In our pilot, 256 dimensions preserved ANN recall while materially shrinking storage compared with 1024 dimensions.\n\n### 6. Activation should be boring\n\nNew generations are imported beside the current one, indexed independently, checked for complete and current coverage, and then activated atomically. Rollback is a configuration change, not an emergency recomputation.\n\nFeel free to try out the search at [https://paperswithcode.co](https://paperswithcode.co) or the chat interface at [https://paperswithcode.co/chat](https://paperswithcode.co/chat), and let us know any feedback!\n",
  "authors": [],
  "captured_at": "2026-10-19T01:26:53.998049939+00:00",
  "category": "ai",
  "content_html": "<p>3 months ago, we started a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.reddit.com/r/MachineLearning/comments/1tgmwqr/reviving_paperswithcode_by_hugging_face_p/\">revival</a> of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paperswithcode.co\">Papers with Code</a> (see also the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/NielsRogge/status/2056366395605078252\">announcement tweet</a>). Its goal is to make open AI research accessible and digestible, so that people can easily find the artifacts related to a paper, find state-of-the-art (SOTA) across the various domains of AI, share interesting research and build on top of each other's work. In other words, its goal is to power the wave of research that leads to the next <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paperswithcode.co/paper/1706.03762\">Transformer</a>.</p>\n<p>Of course, making AI research accessible requires a powerful search engine, so that humans and agents can quickly find relevant and related work, either through the website or the <code>pwc search</code> <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/pwc-cli\">CLI command</a>, which agents can use via the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/pwc-cli/blob/main/standalone_cli/SKILL.md\">Skill</a>.</p>\n<p>It's important to note that searching for research is not quite the same as searching for regular text. A useful paper search engine should find an exact title or arXiv identifier, but it should also understand a query such as “small language models for code generation” even when those words do not appear together in a paper. It needs to recognize that “the original BERT paper” is a navigational request, tolerate an incomplete title or typos, and still respond quickly when a model service is cold or temporarily unavailable.</p>\n<figure class=\"article-figure\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/search-results.png\" alt=\"Papers with Code search results for the query &#39;DINO&#39;\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture><figcaption>Search results on Papers with Code for the query DINO.</figcaption></figure>\n<p>For <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paperswithcode.co\">Papers with Code</a>, we built this as a <strong>hybrid search</strong> system. This is also based on our prior experience at <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"http://ml6.eu/\">ML6</a>, where we developed <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paperswithcode.co/paper/2005.11401\">RAG</a>-based systems for clients. It turned out that hybrid search typically outperforms keyword- and vector-based search systems, as it combines the best of both worlds (see also <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://techcommunity.microsoft.com/blog/azure-ai-foundry-blog/azure-ai-search-outperforming-vector-search-with-hybrid-retrieval-and-reranking/3929167\">this blog</a> for more info). Keyword search finds exact mentions, whereas vector search finds more fuzzy, semantically similar terms. Note that <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.pinecone.io/learn/series/rag/rerankers/\">rerankers</a> (also called cross-encoders) can further improve the results, although they also come with additional overhead and latency.</p>\n<figure class=\"article-figure\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/hybrid-search.png\" alt=\"Chart showing hybrid retrieval outperforming vector-only and keyword search\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture><figcaption>Hybrid retrieval outperforms keyword- and vector-only search. Figure from Microsoft, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://techcommunity.microsoft.com/blog/azure-ai-foundry-blog/azure-ai-search-outperforming-vector-search-with-hybrid-retrieval-and-reranking/3929167\">Azure AI Search: Outperforming vector search with hybrid retrieval and reranking</a> (2023).</figcaption></figure>\n<p>Papers with Code relies on a PostgreSQL database, hence its full-text search capabilities provide a fast lexical baseline. For dense embeddings, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/pgvector/pgvector\">pgvector</a> is used to add semantic recall, and the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion\">reciprocal rank fusion (RRF)</a> algorithm combines the two. Three Hugging Face services are used for the dense embeddings:</p>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/hub/jobs\">Hugging Face Jobs</a> gives us burstable GPU compute for embedding the paper corpus.</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/hub/storage-buckets\">Hugging Face Storage Buckets</a> provides the durable handoff between our database, experiments, and Jobs.</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/inference-endpoints/index\">Hugging Face Inference Endpoints</a> serves low-latency embeddings for live queries and incremental updates.</li>\n</ul>\n<p>Today, the system maintains embeddings for more than 110,000 current papers sourced from <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://arxiv.org/\">arXiv</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://hf.co/papers\">Daily Papers</a>. This post explains the architecture, the design decisions behind it, and the lessons we learned while taking it to production.</p>\n<h2>TL;DR</h2>\n<p>We deliberately split search into an offline corpus build and an online search service:</p>\n<figure class=\"article-figure\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/architecture.png\" alt=\"Architecture diagram of the offline corpus build and online hybrid search pipeline\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture><figcaption>Architecture of the offline corpus build and online hybrid search pipeline.</figcaption></figure>\n<p>The expensive, throughput-oriented work runs as Jobs. Durable artifacts live in a Bucket. Only the small query-embedding step sits on the request path, behind a protected Inference Endpoint, to power the online search. If that endpoint is cold, busy, or unhealthy, search immediately falls back to full-text retrieval. This separation makes the system both powerful and fast.</p>\n<h2>Start with a strict embedding contract</h2>\n<p>Embedding pipelines often fail in subtle ways: a model revision changes, query and document prompts are mixed up, vectors are truncated differently, or an updated abstract no longer matches its stored vector.</p>\n<p>We avoid this by treating the embedding format as a versioned API. Every paper is encoded as:</p>\n<pre><code class=\"language-text\"><span class=\"code-snippet\">normalized title + &quot;\\n\\n&quot; + normalized abstract\n</span></code></pre>\n<p>For each vector generation, we record:</p>\n<ul>\n<li>the model repository and exact revision;</li>\n<li>the output dimension;</li>\n<li>the input-format version;</li>\n<li>whether the input is a query or a document;</li>\n<li>the normalization method;</li>\n<li>a content hash for the source title and abstract.</li>\n</ul>\n<p>Our production generation uses <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/Qwen/Qwen3-Embedding-0.6B\"><code>Qwen/Qwen3-Embedding-0.6B</code></a>, pinned to an exact revision, with 256-dimensional L2-normalized vectors. We selected the model with help from the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/spaces/mteb/leaderboard\">MTEB leaderboard</a>, the go-to benchmark for comparing embedding models. Note that newer embedding models like Qwen3 allow for 2 new features:</p>\n<ul>\n<li>one can specify a <strong>dynamic embedding size</strong>, which allows to trade-off quality with speed/storage costs. Qwen models call this &quot;MRL&quot; which is short for <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paperswithcode.co/paper/2205.13147\">Matryoshka Representation Learning</a>. You can learn all about it <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/blog/matryoshka\">here</a>. We chose an embedding size of 256 to make the search fast.</li>\n<li>one can provide an <strong>instruction prompt</strong>. Qwen embedding models support a <code>document</code> prompt (which we use to embed the papers) and live searches use their <code>query</code> prompt (to embed the user query).</li>\n</ul>\n<p>This contract follows an embedding from export, through GPU inference, into PostgreSQL, and finally into online retrieval.</p>\n<h2>Jobs turn a database snapshot into a vector corpus</h2>\n<p>Full-corpus embedding is a classic batch workload. It needs a GPU for a relatively short period, benefits from high throughput, and should not consume resources between runs. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/huggingface_hub/guides/jobs\">Hugging Face Jobs</a> fits that shape well: a Job is defined by a command, a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/hub/main/en/jobs-pricing#pricing\">hardware flavor</a>, and optionally a Docker image, and can run <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.astral.sh/uv/\">uv</a> scripts with their dependencies declared inline.</p>\n<p>Our corpus build starts by exporting the latest version of every paper from a repeatable-read PostgreSQL snapshot. The exporter streams rows rather than loading the catalog into memory, writes bounded JSONL shards, and creates a manifest containing row counts and SHA-256 checksums.</p>\n<p>We sync that immutable run directory to a private <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/hub/storage-buckets\">Storage Bucket</a> and mount the Bucket directly (using <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/hf-mount\">hf-mount</a>) into an <code>l4x1</code> Job (an NVIDIA L4 GPU, which has 24GB of VRAM). From the worker's perspective it is simply a filesystem:</p>\n<pre><code class=\"language-bash\"><span class=\"code-snippet\" data-language=\"Shell\"><span class=\"syntax-source syntax-shell syntax-bash\"><span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-variable syntax-function syntax-shell\">hf</span></span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-shell\"> jobs uv run <span class=\"syntax-punctuation syntax-separator syntax-continuation syntax-line syntax-shell\">\\\n</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\">  --</span>flavor</span> l4x1 <span class=\"syntax-punctuation syntax-separator syntax-continuation syntax-line syntax-shell\">\\\n</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\">  --</span>timeout</span> 6h <span class=\"syntax-punctuation syntax-separator syntax-continuation syntax-line syntax-shell\">\\\n</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\">  --</span>volume</span> hf://buckets/OWNER/pwc-paper-embeddings:/bucket <span class=\"syntax-punctuation syntax-separator syntax-continuation syntax-line syntax-shell\">\\\n</span>  embed_papers_job.py <span class=\"syntax-punctuation syntax-separator syntax-continuation syntax-line syntax-shell\">\\\n</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\">  --</span>input</span> /bucket/runs/RUN_ID/input <span class=\"syntax-punctuation syntax-separator syntax-continuation syntax-line syntax-shell\">\\\n</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\">  --</span>output</span> /bucket/runs/RUN_ID/output <span class=\"syntax-punctuation syntax-separator syntax-continuation syntax-line syntax-shell\">\\\n</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\">  --</span>model</span> Qwen/Qwen3-Embedding-0.6B <span class=\"syntax-punctuation syntax-separator syntax-continuation syntax-line syntax-shell\">\\\n</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\">  --</span>revision</span> MODEL_REVISION <span class=\"syntax-punctuation syntax-separator syntax-continuation syntax-line syntax-shell\">\\\n</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\">  --</span>dimensions</span> 256 <span class=\"syntax-punctuation syntax-separator syntax-continuation syntax-line syntax-shell\">\\\n</span><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\">  --</span>allow-matryoshka</span></span>\n</span></span></code></pre>\n<p>The worker:</p>\n<ol>\n<li>verifies the input manifest and every shard checksum;</li>\n<li>loads the pinned model revision;</li>\n<li>sorts texts by length to reduce padding;</li>\n<li>calls <code>encode_document</code> in batches (as noted in the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/Qwen/Qwen3-Embedding-0.6B\">model card</a>);</li>\n<li>reduces the batch size automatically if the GPU runs out of memory;</li>\n<li>truncates the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/blog/matryoshka\">Matryoshka representation</a> to 256 dimensions and normalizes it;</li>\n<li>writes float16 Parquet shards atomically; and</li>\n<li>records throughput, package versions, hardware, peak VRAM, row counts, and output checksums.</li>\n</ol>\n<p>Each completed shard has its own marker, so a restarted Job can skip verified work. This is useful for a large corpus: retrying should just resume work rather than overwriting existing embeddings.</p>\n<p>In our 5,000-paper pilot, the Qwen Job encoded about 75 papers per second at 1024 dimensions on an L4 GPU. The same pass could be deterministically materialized at 512 and 256 dimensions, so we could compare the storage and retrieval trade-offs without paying for more inference.</p>\n<h2>Buckets are the connective tissue</h2>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/hub/storage-buckets\">Storage Buckets</a> are mutable, S3-like object storage on the Hub, optimized for AI workloads. They can be accessed through <code>hf://buckets/...</code> paths and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/hf-mount\">mounted</a> read-write in Jobs without building a separate storage integration.</p>\n<p>For us, the Bucket is more than a place to put vectors. It is the boundary between three systems with different lifecycles:</p>\n<ul>\n<li>the production database exports source records;</li>\n<li>ephemeral Jobs consume those records and produce vectors;</li>\n<li>the importer validates the results before touching the search index.</li>\n</ul>\n<p>We organize artifacts under immutable run prefixes:</p>\n<pre><code class=\"language-text\"><span class=\"code-snippet\">runs/&lt;run-id&gt;/\n├── input/\n│   ├── manifest.json\n│   └── papers-*.jsonl\n└── output/\n    ├── manifest.json\n    ├── embeddings-*.parquet\n    └── embeddings-*.complete.json\n</span></code></pre>\n<p>Buckets themselves are intentionally mutable, so immutability is an application-level rule: a run ID is never overwritten, and every artifact is covered by a manifest and checksum.</p>\n<p>This gives us several useful properties:</p>\n<ul>\n<li><strong>Reproducibility:</strong> we can trace a database generation back to an exact corpus snapshot, model revision, and set of artifacts.</li>\n<li><strong>Safe retries:</strong> Jobs can resume from completed shards in the same run prefix.</li>\n<li><strong>Cheap experiments:</strong> several models or dimensions can reuse one verified input snapshot.</li>\n<li><strong>Controlled rollout:</strong> importing a generation does not activate it. We first validate coverage and build its index.</li>\n<li><strong>Simple rollback:</strong> the previous generation and its artifacts remain available until the new one is proven stable.</li>\n</ul>\n<p>Only after the importer rechecks schemas, checksums, dimensions, normalization, unique paper IDs, and current content hashes do we load the vectors into PostgreSQL. We then build a separate <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world\">HNSW</a> index for the new generation and atomically mark it active only when every eligible current paper is covered (HNSW is the graph-based algorithm that enables fast vector search).</p>\n<h2>Inference Endpoints put semantic search on the request path</h2>\n<p>Batch embeddings solve the document side of retrieval. A user query still needs to be embedded at request time using the same model contract.</p>\n<p>We deploy the pinned model as an authenticated <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/inference-endpoints/index\">Inference Endpoint</a> backed by <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/text-embeddings-inference\">Text Embeddings Inference (TEI)</a>. The endpoint accepts the query text and returns a normalized 256-dimensional vector using the model's <code>query</code> prompt. Note that one could also leverage <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/vllm-project/vllm\">vLLM</a> or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/sgl-project/sglang\">SGLang</a> here.</p>\n<figure class=\"article-figure\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/inference-endpoint.png\" alt=\"Hugging Face Inference Endpoint overview for the Papers with Code query embedding model\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture><figcaption>Hugging Face Inference Endpoint for the Papers with Code query embedding model.</figcaption></figure>\n<p>The API then performs a cosine-distance search over the active pgvector generation:</p>\n<pre><code class=\"language-sql\"><span class=\"code-snippet\" data-language=\"SQL\"><span class=\"syntax-source syntax-sql\"><span class=\"syntax-keyword syntax-other syntax-DML syntax-sql\">SELECT</span> paper_id,\n       embedding <span class=\"syntax-keyword syntax-operator syntax-comparison syntax-sql\">&lt;=</span><span class=\"syntax-keyword syntax-operator syntax-comparison syntax-sql\">&gt;</span> CAST(:query_vector <span class=\"syntax-keyword syntax-other syntax-alias syntax-sql\">AS</span> halfvec(<span class=\"syntax-constant syntax-numeric syntax-sql\">256</span>)) <span class=\"syntax-keyword syntax-other syntax-alias syntax-sql\">AS</span> distance\n<span class=\"syntax-keyword syntax-other syntax-DML syntax-sql\">FROM</span> paper_embeddings\n<span class=\"syntax-keyword syntax-other syntax-DML syntax-sql\">WHERE</span> generation_id <span class=\"syntax-keyword syntax-operator syntax-comparison syntax-sql\">=</span> :active_generation\n<span class=\"syntax-keyword syntax-other syntax-DML syntax-sql\">ORDER BY</span> embedding <span class=\"syntax-keyword syntax-operator syntax-comparison syntax-sql\">&lt;=</span><span class=\"syntax-keyword syntax-operator syntax-comparison syntax-sql\">&gt;</span> CAST(:query_vector <span class=\"syntax-keyword syntax-other syntax-alias syntax-sql\">AS</span> halfvec(<span class=\"syntax-constant syntax-numeric syntax-sql\">256</span>))\n<span class=\"syntax-keyword syntax-other syntax-DML syntax-sql\">LIMIT</span> <span class=\"syntax-constant syntax-numeric syntax-sql\">50</span>;\n</span></span></code></pre>\n<p>The HNSW index keeps this lookup fast. On our 5,000-paper pilot, the 256-dimensional Qwen index achieved 0.9955 Recall@20 against exact search, with 1.31 ms p50 and 2.21 ms p95 HNSW lookup latency. Its table and index used about 27% of the storage of the 1024-dimensional version while retaining essentially the same ANN recall in that test.</p>\n<p>The Endpoint is configured with a maximum of one replica and can <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/inference-endpoints/guides/autoscaling\">scale to zero</a> when idle. That is a useful cost lever, as this means you're not paying when there's no usage. However, this also means cold starts must be part of the application design rather than treated as an exceptional event, as it takes some time for the endpoint to spin up and serve traffic.</p>\n<p>Our query client therefore has deliberately strict behavior:</p>\n<ul>\n<li>a one-second production timeout;</li>\n<li>a non-blocking concurrency limit;</li>\n<li>response dimension, finiteness, and norm validation;</li>\n<li>a short cache keyed by the query and embedding generation;</li>\n<li>a circuit breaker after repeated failures; and</li>\n<li>no raw query text in logs, only a normalized fingerprint.</li>\n</ul>\n<p>If the endpoint is scaling up, times out, returns a malformed vector, or has no concurrency available, we skip the semantic branch immediately. Users still receive lexical results instead of waiting for an unreliable dependency.</p>\n<p>Inference Endpoints works really reliably, and includes a nice dashboard so you can quickly see key analytics.</p>\n<figure class=\"article-figure\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/endpoint-analytics.png\" alt=\"Hugging Face Inference Endpoint analytics dashboard showing request volume, errors, latency, and replica state\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture><figcaption>Inference Endpoint analytics showing request volume, errors, latency, and replica state.</figcaption></figure>\n<h2>Hybrid retrieval is stronger than either branch alone</h2>\n<p>For every query, the lexical branch retrieves up to 50 candidates using weighted PostgreSQL full-text search. The semantic branch retrieves up to 50 candidates from pgvector.</p>\n<p>We combine their ranks using weighted reciprocal rank fusion (RRF):</p>\n<p>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)}</p>\n<p>RRF is simple and robust, because it combines ranks rather than scores from two systems with different scales. Basically, if a paper is ranked high both by the lexical branch and the semantic branch, it has a higher chance of being ranked high by the hybrid search. We currently use equal branch weights and (k=60) (k is the &quot;rank constant&quot;, a hyperparameter of the RRF algorithm).</p>\n<p>Dense retrieval improves recall for conceptual queries. Full-text retrieval remains excellent for exact terminology, identifiers, and rare names. We also preserve deterministic identity behavior on top of the fused ranking:</p>\n<ul>\n<li>exact titles and arXiv IDs stay at the top;</li>\n<li>the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paperswithcode.co/methods\">method taxonomy</a> recognizes navigational searches such as “the original BERT paper”;</li>\n<li>incomplete titles and bounded spelling mistakes use conservative trigram candidates; and</li>\n<li>ambiguous fuzzy matches abstain rather than forcing a bad result.</li>\n</ul>\n<p>Note: hybrid search isn't always the best option, it is recommended to start with keyword search as a cheap and fast baseline, and only adding semantic and/or hybrid search when it turns out those give a reasonable boost in retrieval quality. One could further improve the search by adding a reranker after keyword/semantic/hybrid retrieval, using a model like <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/collections/Qwen/qwen3-reranker\">Qwen3-Reranker</a>.</p>\n<h2>One Endpoint, two update paths</h2>\n<p>The large initial corpus is embedded with Jobs, but Papers with Code changes continuously. New papers arrive, abstracts are corrected, and new arXiv versions become current.</p>\n<p>Launching a GPU Job for a handful of changed rows would add unnecessary startup and orchestration overhead. Instead, an hourly incremental process selects missing or content-changed papers and sends a bounded delta to the same TEI Endpoint, this time with the <code>document</code> prompt.</p>\n<p>Each run processes at most 500 papers in batches of 16. Before an embedding is written, the source row is locked and its content hash is checked again. If a paper changed during inference, that vector is discarded and picked up by the next run.</p>\n<p>This gives us a useful division of labor:</p>\n<ul>\n<li><strong>Jobs</strong> handle full rebuilds, new model generations, and large backfills.</li>\n<li><strong>Inference Endpoints</strong> handle interactive query embeddings and small incremental document updates.</li>\n<li><strong>Buckets</strong> preserve the large-build artifacts and make those builds resumable and auditable.</li>\n</ul>\n<p>The hourly path keeps the active index close to the live catalog without turning an online endpoint into an unbounded batch processor.</p>\n<h2>Related papers become almost free online</h2>\n<p>The same document embeddings also power related-paper recommendations on each paper page.</p>\n<figure class=\"article-figure\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/related_papers.png\" alt=\"Related papers feature\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture><figcaption>Related papers for <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paperswithcode.co/paper/2605.12500\">SenseNova-U1</a>.</figcaption></figure>\n<p>Because the source paper already has a stored vector, related-paper retrieval requires no model call at request time. It is a single nearest-neighbor query over the active generation. If a vector is temporarily missing, the application can use a previous arXiv version or fill results from the existing task- and citation-based fallback. We fetch citation data through the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.semanticscholar.org/product/api\">Semantic Scholar API</a> and also built <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/s2-cli\"><code>s2-cli</code></a>, a command-line interface for querying its citation graph. The latter is used by the agent at <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paperswithcode.co/chat\">https://paperswithcode.co/chat</a>.</p>\n<h2>What we learned</h2>\n<h3>1. Separate throughput work from latency-sensitive work</h3>\n<p>Corpus embedding and query embedding use the same model, but they are different infrastructure problems. Jobs optimize for throughput and bounded cost; Inference Endpoints optimize for availability and request latency.</p>\n<h3>2. Make storage the explicit contract between compute and production</h3>\n<p>Buckets provide an explicit handoff between compute and production. Checksummed artifacts create a reviewable boundary before data enters the production index.</p>\n<h3>3. Pin more than the model name</h3>\n<p>The revision, dimension, prompt, normalization, and input formatter all affect retrieval. Store them together and validate them everywhere.</p>\n<h3>4. Design for cold starts</h3>\n<p>Scale-to-zero is valuable when traffic is intermittent, but only if the product has a fast fallback. Hybrid search gave us that fallback naturally: lexical search is always useful on its own.</p>\n<h3>5. Smaller vectors can be a systems feature</h3>\n<p>Matryoshka embeddings let us evaluate quality, memory, index size, and latency as one trade-off. In our pilot, 256 dimensions preserved ANN recall while materially shrinking storage compared with 1024 dimensions.</p>\n<h3>6. Activation should be boring</h3>\n<p>New generations are imported beside the current one, indexed independently, checked for complete and current coverage, and then activated atomically. Rollback is a configuration change, not an emergency recomputation.</p>\n<p>Feel free to try out the search at <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paperswithcode.co\">https://paperswithcode.co</a> or the chat interface at <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://paperswithcode.co/chat\">https://paperswithcode.co/chat</a>, and let us know any feedback!</p>\n",
  "content_markdown": "3 months ago, we started a [revival](https://www.reddit.com/r/MachineLearning/comments/1tgmwqr/reviving_paperswithcode_by_hugging_face_p/) of [Papers with Code](https://paperswithcode.co) (see also the [announcement tweet](https://x.com/NielsRogge/status/2056366395605078252)). Its goal is to make open AI research accessible and digestible, so that people can easily find the artifacts related to a paper, find state-of-the-art (SOTA) across the various domains of AI, share interesting research and build on top of each other's work. In other words, its goal is to power the wave of research that leads to the next [Transformer](https://paperswithcode.co/paper/1706.03762).\n\nOf course, making AI research accessible requires a powerful search engine, so that humans and agents can quickly find relevant and related work, either through the website or the `pwc search` [CLI command](https://github.com/huggingface/pwc-cli), which agents can use via the [Skill](https://github.com/huggingface/pwc-cli/blob/main/standalone_cli/SKILL.md).\n\nIt's important to note that searching for research is not quite the same as searching for regular text. A useful paper search engine should find an exact title or arXiv identifier, but it should also understand a query such as “small language models for code generation” even when those words do not appear together in a paper. It needs to recognize that “the original BERT paper” is a navigational request, tolerate an incomplete title or typos, and still respond quickly when a model service is cold or temporarily unavailable.\n\n![Papers with Code search results for the query 'DINO'](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/search-results.png)\\\nSearch results on Papers with Code for the query DINO.\n\nFor [Papers with Code](https://paperswithcode.co), we built this as a **hybrid search** system. This is also based on our prior experience at [ML6](http://ml6.eu/), where we developed [RAG](https://paperswithcode.co/paper/2005.11401)-based systems for clients. It turned out that hybrid search typically outperforms keyword- and vector-based search systems, as it combines the best of both worlds (see also [this blog](https://techcommunity.microsoft.com/blog/azure-ai-foundry-blog/azure-ai-search-outperforming-vector-search-with-hybrid-retrieval-and-reranking/3929167) for more info). Keyword search finds exact mentions, whereas vector search finds more fuzzy, semantically similar terms. Note that [rerankers](https://www.pinecone.io/learn/series/rag/rerankers/) (also called cross-encoders) can further improve the results, although they also come with additional overhead and latency.\n\n![Chart showing hybrid retrieval outperforming vector-only and keyword search](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/hybrid-search.png)\\\nHybrid retrieval outperforms keyword- and vector-only search. Figure from Microsoft, [Azure AI Search: Outperforming vector search with hybrid retrieval and reranking](https://techcommunity.microsoft.com/blog/azure-ai-foundry-blog/azure-ai-search-outperforming-vector-search-with-hybrid-retrieval-and-reranking/3929167) (2023).\n\nPapers with Code relies on a PostgreSQL database, hence its full-text search capabilities provide a fast lexical baseline. For dense embeddings, [pgvector](https://github.com/pgvector/pgvector) is used to add semantic recall, and the [reciprocal rank fusion (RRF)](https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion) algorithm combines the two. Three Hugging Face services are used for the dense embeddings:\n\n- [Hugging Face Jobs](https://huggingface.co/docs/hub/jobs) gives us burstable GPU compute for embedding the paper corpus.\n- [Hugging Face Storage Buckets](https://huggingface.co/docs/hub/storage-buckets) provides the durable handoff between our database, experiments, and Jobs.\n- [Hugging Face Inference Endpoints](https://huggingface.co/docs/inference-endpoints/index) serves low-latency embeddings for live queries and incremental updates.\n\nToday, the system maintains embeddings for more than 110,000 current papers sourced from [arXiv](https://arxiv.org/) and [Daily Papers](https://hf.co/papers). This post explains the architecture, the design decisions behind it, and the lessons we learned while taking it to production.\n\n## TL;DR\n\nWe deliberately split search into an offline corpus build and an online search service:\n\n![Architecture diagram of the offline corpus build and online hybrid search pipeline](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/architecture.png)\\\nArchitecture of the offline corpus build and online hybrid search pipeline.\n\nThe expensive, throughput-oriented work runs as Jobs. Durable artifacts live in a Bucket. Only the small query-embedding step sits on the request path, behind a protected Inference Endpoint, to power the online search. If that endpoint is cold, busy, or unhealthy, search immediately falls back to full-text retrieval. This separation makes the system both powerful and fast.\n\n## Start with a strict embedding contract\n\nEmbedding pipelines often fail in subtle ways: a model revision changes, query and document prompts are mixed up, vectors are truncated differently, or an updated abstract no longer matches its stored vector.\n\nWe avoid this by treating the embedding format as a versioned API. Every paper is encoded as:\n\n```text\nnormalized title + \"\\n\\n\" + normalized abstract\n```\n\nFor each vector generation, we record:\n\n- the model repository and exact revision;\n- the output dimension;\n- the input-format version;\n- whether the input is a query or a document;\n- the normalization method;\n- a content hash for the source title and abstract.\n\nOur production generation uses [`Qwen/Qwen3-Embedding-0.6B`](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B), pinned to an exact revision, with 256-dimensional L2-normalized vectors. We selected the model with help from the [MTEB leaderboard](https://huggingface.co/spaces/mteb/leaderboard), the go-to benchmark for comparing embedding models. Note that newer embedding models like Qwen3 allow for 2 new features:\n\n- one can specify a **dynamic embedding size**, which allows to trade-off quality with speed/storage costs. Qwen models call this \"MRL\" which is short for [Matryoshka Representation Learning](https://paperswithcode.co/paper/2205.13147). You can learn all about it [here](https://huggingface.co/blog/matryoshka). We chose an embedding size of 256 to make the search fast.\n- one can provide an **instruction prompt**. Qwen embedding models support a `document` prompt (which we use to embed the papers) and live searches use their `query` prompt (to embed the user query).\n\nThis contract follows an embedding from export, through GPU inference, into PostgreSQL, and finally into online retrieval.\n\n## Jobs turn a database snapshot into a vector corpus\n\nFull-corpus embedding is a classic batch workload. It needs a GPU for a relatively short period, benefits from high throughput, and should not consume resources between runs. [Hugging Face Jobs](https://huggingface.co/docs/huggingface_hub/guides/jobs) fits that shape well: a Job is defined by a command, a [hardware flavor](https://huggingface.co/docs/hub/main/en/jobs-pricing#pricing), and optionally a Docker image, and can run [uv](https://docs.astral.sh/uv/) scripts with their dependencies declared inline.\n\nOur corpus build starts by exporting the latest version of every paper from a repeatable-read PostgreSQL snapshot. The exporter streams rows rather than loading the catalog into memory, writes bounded JSONL shards, and creates a manifest containing row counts and SHA-256 checksums.\n\nWe sync that immutable run directory to a private [Storage Bucket](https://huggingface.co/docs/hub/storage-buckets) and mount the Bucket directly (using [hf-mount](https://github.com/huggingface/hf-mount)) into an `l4x1` Job (an NVIDIA L4 GPU, which has 24GB of VRAM). From the worker's perspective it is simply a filesystem:\n\n```bash\nhf jobs uv run \\\n  --flavor l4x1 \\\n  --timeout 6h \\\n  --volume hf://buckets/OWNER/pwc-paper-embeddings:/bucket \\\n  embed_papers_job.py \\\n  --input /bucket/runs/RUN_ID/input \\\n  --output /bucket/runs/RUN_ID/output \\\n  --model Qwen/Qwen3-Embedding-0.6B \\\n  --revision MODEL_REVISION \\\n  --dimensions 256 \\\n  --allow-matryoshka\n```\n\nThe worker:\n\n1. verifies the input manifest and every shard checksum;\n2. loads the pinned model revision;\n3. sorts texts by length to reduce padding;\n4. calls `encode_document` in batches (as noted in the [model card](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B));\n5. reduces the batch size automatically if the GPU runs out of memory;\n6. truncates the [Matryoshka representation](https://huggingface.co/blog/matryoshka) to 256 dimensions and normalizes it;\n7. writes float16 Parquet shards atomically; and\n8. records throughput, package versions, hardware, peak VRAM, row counts, and output checksums.\n\nEach completed shard has its own marker, so a restarted Job can skip verified work. This is useful for a large corpus: retrying should just resume work rather than overwriting existing embeddings.\n\nIn our 5,000-paper pilot, the Qwen Job encoded about 75 papers per second at 1024 dimensions on an L4 GPU. The same pass could be deterministically materialized at 512 and 256 dimensions, so we could compare the storage and retrieval trade-offs without paying for more inference.\n\n## Buckets are the connective tissue\n\n[Storage Buckets](https://huggingface.co/docs/hub/storage-buckets) are mutable, S3-like object storage on the Hub, optimized for AI workloads. They can be accessed through `hf://buckets/...` paths and [mounted](https://github.com/huggingface/hf-mount) read-write in Jobs without building a separate storage integration.\n\nFor us, the Bucket is more than a place to put vectors. It is the boundary between three systems with different lifecycles:\n\n- the production database exports source records;\n- ephemeral Jobs consume those records and produce vectors;\n- the importer validates the results before touching the search index.\n\nWe organize artifacts under immutable run prefixes:\n\n```text\nruns/<run-id>/\n├── input/\n│   ├── manifest.json\n│   └── papers-*.jsonl\n└── output/\n    ├── manifest.json\n    ├── embeddings-*.parquet\n    └── embeddings-*.complete.json\n```\n\nBuckets themselves are intentionally mutable, so immutability is an application-level rule: a run ID is never overwritten, and every artifact is covered by a manifest and checksum.\n\nThis gives us several useful properties:\n\n- **Reproducibility:** we can trace a database generation back to an exact corpus snapshot, model revision, and set of artifacts.\n- **Safe retries:** Jobs can resume from completed shards in the same run prefix.\n- **Cheap experiments:** several models or dimensions can reuse one verified input snapshot.\n- **Controlled rollout:** importing a generation does not activate it. We first validate coverage and build its index.\n- **Simple rollback:** the previous generation and its artifacts remain available until the new one is proven stable.\n\nOnly after the importer rechecks schemas, checksums, dimensions, normalization, unique paper IDs, and current content hashes do we load the vectors into PostgreSQL. We then build a separate [HNSW](https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world) index for the new generation and atomically mark it active only when every eligible current paper is covered (HNSW is the graph-based algorithm that enables fast vector search).\n\n## Inference Endpoints put semantic search on the request path\n\nBatch embeddings solve the document side of retrieval. A user query still needs to be embedded at request time using the same model contract.\n\nWe deploy the pinned model as an authenticated [Inference Endpoint](https://huggingface.co/docs/inference-endpoints/index) backed by [Text Embeddings Inference (TEI)](https://github.com/huggingface/text-embeddings-inference). The endpoint accepts the query text and returns a normalized 256-dimensional vector using the model's `query` prompt. Note that one could also leverage [vLLM](https://github.com/vllm-project/vllm) or [SGLang](https://github.com/sgl-project/sglang) here.\n\n![Hugging Face Inference Endpoint overview for the Papers with Code query embedding model](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/inference-endpoint.png)\\\nHugging Face Inference Endpoint for the Papers with Code query embedding model.\n\nThe API then performs a cosine-distance search over the active pgvector generation:\n\n```sql\nSELECT paper_id,\n       embedding <=> CAST(:query_vector AS halfvec(256)) AS distance\nFROM paper_embeddings\nWHERE generation_id = :active_generation\nORDER BY embedding <=> CAST(:query_vector AS halfvec(256))\nLIMIT 50;\n```\n\nThe HNSW index keeps this lookup fast. On our 5,000-paper pilot, the 256-dimensional Qwen index achieved 0.9955 Recall@20 against exact search, with 1.31 ms p50 and 2.21 ms p95 HNSW lookup latency. Its table and index used about 27% of the storage of the 1024-dimensional version while retaining essentially the same ANN recall in that test.\n\nThe Endpoint is configured with a maximum of one replica and can [scale to zero](https://huggingface.co/docs/inference-endpoints/guides/autoscaling) when idle. That is a useful cost lever, as this means you're not paying when there's no usage. However, this also means cold starts must be part of the application design rather than treated as an exceptional event, as it takes some time for the endpoint to spin up and serve traffic.\n\nOur query client therefore has deliberately strict behavior:\n\n- a one-second production timeout;\n- a non-blocking concurrency limit;\n- response dimension, finiteness, and norm validation;\n- a short cache keyed by the query and embedding generation;\n- a circuit breaker after repeated failures; and\n- no raw query text in logs, only a normalized fingerprint.\n\nIf the endpoint is scaling up, times out, returns a malformed vector, or has no concurrency available, we skip the semantic branch immediately. Users still receive lexical results instead of waiting for an unreliable dependency.\n\nInference Endpoints works really reliably, and includes a nice dashboard so you can quickly see key analytics.\n\n![Hugging Face Inference Endpoint analytics dashboard showing request volume, errors, latency, and replica state](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/endpoint-analytics.png)\\\nInference Endpoint analytics showing request volume, errors, latency, and replica state.\n\n## Hybrid retrieval is stronger than either branch alone\n\nFor every query, the lexical branch retrieves up to 50 candidates using weighted PostgreSQL full-text search. The semantic branch retrieves up to 50 candidates from pgvector.\n\nWe combine their ranks using weighted reciprocal rank fusion (RRF):\n\nscore(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)}\n\nRRF is simple and robust, because it combines ranks rather than scores from two systems with different scales. Basically, if a paper is ranked high both by the lexical branch and the semantic branch, it has a higher chance of being ranked high by the hybrid search. We currently use equal branch weights and (k=60) (k is the \"rank constant\", a hyperparameter of the RRF algorithm).\n\nDense retrieval improves recall for conceptual queries. Full-text retrieval remains excellent for exact terminology, identifiers, and rare names. We also preserve deterministic identity behavior on top of the fused ranking:\n\n- exact titles and arXiv IDs stay at the top;\n- the [method taxonomy](https://paperswithcode.co/methods) recognizes navigational searches such as “the original BERT paper”;\n- incomplete titles and bounded spelling mistakes use conservative trigram candidates; and\n- ambiguous fuzzy matches abstain rather than forcing a bad result.\n\nNote: hybrid search isn't always the best option, it is recommended to start with keyword search as a cheap and fast baseline, and only adding semantic and/or hybrid search when it turns out those give a reasonable boost in retrieval quality. One could further improve the search by adding a reranker after keyword/semantic/hybrid retrieval, using a model like [Qwen3-Reranker](https://huggingface.co/collections/Qwen/qwen3-reranker).\n\n## One Endpoint, two update paths\n\nThe large initial corpus is embedded with Jobs, but Papers with Code changes continuously. New papers arrive, abstracts are corrected, and new arXiv versions become current.\n\nLaunching a GPU Job for a handful of changed rows would add unnecessary startup and orchestration overhead. Instead, an hourly incremental process selects missing or content-changed papers and sends a bounded delta to the same TEI Endpoint, this time with the `document` prompt.\n\nEach run processes at most 500 papers in batches of 16. Before an embedding is written, the source row is locked and its content hash is checked again. If a paper changed during inference, that vector is discarded and picked up by the next run.\n\nThis gives us a useful division of labor:\n\n- **Jobs** handle full rebuilds, new model generations, and large backfills.\n- **Inference Endpoints** handle interactive query embeddings and small incremental document updates.\n- **Buckets** preserve the large-build artifacts and make those builds resumable and auditable.\n\nThe hourly path keeps the active index close to the live catalog without turning an online endpoint into an unbounded batch processor.\n\n## Related papers become almost free online\n\nThe same document embeddings also power related-paper recommendations on each paper page.\n\n![Related papers feature](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/pwc-search/related_papers.png)\\\nRelated papers for [SenseNova-U1](https://paperswithcode.co/paper/2605.12500).\n\nBecause the source paper already has a stored vector, related-paper retrieval requires no model call at request time. It is a single nearest-neighbor query over the active generation. If a vector is temporarily missing, the application can use a previous arXiv version or fill results from the existing task- and citation-based fallback. We fetch citation data through the [Semantic Scholar API](https://www.semanticscholar.org/product/api) and also built [`s2-cli`](https://github.com/huggingface/s2-cli), a command-line interface for querying its citation graph. The latter is used by the agent at [https://paperswithcode.co/chat](https://paperswithcode.co/chat).\n\n## What we learned\n\n### 1. Separate throughput work from latency-sensitive work\n\nCorpus embedding and query embedding use the same model, but they are different infrastructure problems. Jobs optimize for throughput and bounded cost; Inference Endpoints optimize for availability and request latency.\n\n### 2. Make storage the explicit contract between compute and production\n\nBuckets provide an explicit handoff between compute and production. Checksummed artifacts create a reviewable boundary before data enters the production index.\n\n### 3. Pin more than the model name\n\nThe revision, dimension, prompt, normalization, and input formatter all affect retrieval. Store them together and validate them everywhere.\n\n### 4. Design for cold starts\n\nScale-to-zero is valuable when traffic is intermittent, but only if the product has a fast fallback. Hybrid search gave us that fallback naturally: lexical search is always useful on its own.\n\n### 5. Smaller vectors can be a systems feature\n\nMatryoshka embeddings let us evaluate quality, memory, index size, and latency as one trade-off. In our pilot, 256 dimensions preserved ANN recall while materially shrinking storage compared with 1024 dimensions.\n\n### 6. Activation should be boring\n\nNew generations are imported beside the current one, indexed independently, checked for complete and current coverage, and then activated atomically. Rollback is a configuration change, not an emergency recomputation.\n\nFeel free to try out the search at [https://paperswithcode.co](https://paperswithcode.co) or the chat interface at [https://paperswithcode.co/chat](https://paperswithcode.co/chat), and let us know any feedback!\n",
  "dateCreated": "2026-10-19T01:26:53.998049939+00:00",
  "date_created": "2026-08-21T00:00:00+00:00",
  "date_modified": "2026-08-21T00:00:00+00:00",
  "date_published": "2026-08-21T00:00:00+00:00",
  "description": "3 months ago, we started a revival of Papers with Code (see also the announcement tweet). Its goal is to make open AI research accessible and digestible, so that people can easily find the artifacts related to a paper, find…",
  "external_url": "https://huggingface.co/blog/pwc-search",
  "git": {
    "commit": "ebb0941c5c06a2ab2377342d607535262a3e969e",
    "path": "items/huggingface-co/2026/08/2026-08-21-how-hugging-face-inference-endpoints-jobs-and-buckets-power.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/ebb0941c5c06a2ab2377342d607535262a3e969e/items/huggingface-co/2026/08/2026-08-21-how-hugging-face-inference-endpoints-jobs-and-buckets-power.md"
  },
  "id": "urn:aggr:item:076454a9d7146e657c8e8a0dcfff12300d5df433",
  "isBasedOn": {
    "@id": "https://huggingface.co/blog/pwc-search"
  },
  "mainEntity": {
    "@id": "https://huggingface.co/blog/pwc-search",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/huggingface-co/2026-08-21-how-hugging-face-inference-endpoints-jobs-and-buckets-power/#webpage"
    },
    "dateModified": "2026-08-21T00:00:00+00:00",
    "datePublished": "2026-08-21T00:00:00+00:00",
    "headline": "How Hugging Face Inference Endpoints, Jobs, and Buckets Power Search on Papers with Code",
    "image": {
      "@type": "ImageObject",
      "caption": null,
      "height": 90,
      "url": "https://huggingface.co/blog/assets/pwc-search/thumbnail.png",
      "width": 160
    },
    "timeRequired": "PT12M",
    "url": "https://huggingface.co/blog/pwc-search",
    "wordCount": 2587
  },
  "name": "Archived snapshot: How Hugging Face Inference Endpoints, Jobs, and Buckets Power Search on Papers with Code",
  "preview": {
    "alt": null,
    "color": null,
    "height": 90,
    "placeholder": {
      "data_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
      "hash": ""
    },
    "url": "https://huggingface.co/blog/assets/pwc-search/thumbnail.png",
    "width": 160
  },
  "reading_minutes": 12,
  "replicated_at": null,
  "source": "huggingface-co",
  "source_name": "Hugging Face - Blog",
  "summary": null,
  "tags": [],
  "temporalCoverage": "2026-10-19T01:26:53.998049939+00:00",
  "title": "How Hugging Face Inference Endpoints, Jobs, and Buckets Power Search on Papers with Code",
  "url": "https://aggr.aymericbeaumet.com/items/huggingface-co/2026-08-21-how-hugging-face-inference-endpoints-jobs-and-buckets-power/",
  "word_count": 2587
}