Skip to content

Atlas search: SPLADE investigation — third retrieval signal, eval harness, query-adaptive weighting #5741

Closed

Description

@dezren39

Atlas search: SPLADE investigation — third retrieval signal, eval harness, and query-adaptive signal weighting

Promoted from a paragraph in #5733 to its own issue at Drew's direction (2026-11-04: "file SPLADE so the idea isn't lost"). Context: hybrid design = dense cosine scan + FTS/BM25 fused (RRF), Jaccard at ingest/promotion (dedup) and rescore (#5733), scan-over-shards on TursoDB (#5669 restore). This issue owns the SPLADE question end to end.

Drew's framing (2026-11-04) — the constraints that shape this

  • Ingest time is not a problem if results improve. The expensive-at-ingest objection is retired.
  • The bar is query time "sameish": SPLADE's query-side cost is one small-model encoding of the query plus an inverted-index lookup (BM25-class speed). If total query latency stays in the current band, the investigation is worth it.
  • Open questions this issue must answer with measurements, not opinion: run BM25 and SPLADE both? Does the full stack (dense + BM25 + SPLADE, Jaccard at both ends) actually score higher on our corpora?

Does SPLADE fit SQLite natively? (Yes — structurally.)

SPLADE retrieval is inverted-index retrieval: the model emits a sparse, vocabulary-sized vector per text (~30k dims, ~100 nonzero, with learned term weights and expanded terms not literally present). Scoring = Σ over shared terms of w_query(t) · w_doc(t) — a postings lookup and a sum.

  • Implementation A (portable): a postings table (term_id, chunk_id, weight) in the same shard DB as the chunks. Pure SQL join + aggregate; works in libSQL/TursoDB and even D1. This is the same shape FTS5 already maintains internally — SPLADE is, in effect, an FTS index whose term weights come from a model instead of term statistics.
  • Implementation B (native, later): TursoDB's sparse-vector support (its experimental sparse-IVF index is aimed at exactly this type). Not required for v1; A is the boring, shard-friendly route.

Test plan — the eval harness comes first

No signal earns a permanent place on intuition. Build the harness, then ablate:

  1. Held-out eval set from real query shapes against our corpora: code questions, documentation questions, pasted errors, concept questions — with judged relevant chunks. (Reuse the parity/eval methodology from the 2026-11-04 spike.)
  2. Ablation matrix over signal combinations: {dense} / {BM25} / {SPLADE} / {dense+BM25} (today's design) / {dense+SPLADE} / {dense+BM25+SPLADE} / each ± Jaccard rescore. Metrics: recall@10, MRR, per query class.
  3. Latency budget: p50/p95 per-shard and fan-out totals for each combination; the SPLADE configs must stay in the current query-time band (Drew's "sameish" bar). The query encoder's latency counts — measure it where queries will actually be served.
  4. Decision rule: a signal stays only if removing it measurably hurts the fused result. Correlated signals (BM25 and SPLADE are both lexical) may not both earn their keep — let the ablation say.

Query-adaptive weighting (Drew, 2026-11-04)

Fixed fusion weights assume every query is alike. They're not:

  • Explicit hint wins: a caller-supplied target (code vs documentation vs all) sets the weighting profile directly.
  • Rules second: cheap shape detectors — identifiers / paths / extensions / stack-trace shape → code or error profile (lexical signals up); plain prose question → documentation profile (dense up).
  • Classifier last: when no hint and no rule fires, classify the query — the Jev classifier already in the façade stack is the natural home (a query-class task beside its intent work); a small LM is acceptable given the cost lands on ambiguous queries only.
  • Hard rule: weights are floored, never zeroed. A misclassified query degrades gracefully to slightly-wrong weights, never to a missing signal.
  • Log (query class, weights used) per query so the eval can score per-class quality and tune the profiles from evidence.

Expected profiles (to be validated, not assumed): code → BM25/SPLADE high, dense medium; documentation → dense high, lexical medium; error paste → exact/Jaccard features high, dense low.

Query encoding + the cache tie-in

Success bar

Adopt SPLADE (and any weighting profile) only on measured improvement over the shipped dense+BM25 hybrid on our eval set — recall@10 / MRR up by a margin that justifies a permanent query-time inference dependency, at query latency within the current band. Otherwise the finding "SPLADE doesn't earn it here" is itself the deliverable, recorded with its numbers.

Activity

  1. developing-today commented on Nov 5, 2026

    @developing-today
    Contributor
  2. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    Two follow-on design issues filed from Drew's 2026-11-04 direction, both extending this investigation:

  3. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    Agent note — research step: SPLADE eval-harness specification (bounded, 2026-11-05)

    Part of the ID/Atlas constant-progress pass (Drew, 2026-11-05). This issue already contains the design (postings-table Implementation A) and Drew's constraints (ingest cost retired as an objection; bar = query time "sameish"). What it did not yet have is a concrete, pre-registered eval protocol. This note supplies one from public sources, so the eventual build session executes measurements instead of re-deciding methodology.

    Reference numbers (author-reported, for priors only)

    • naver/splade-v3: MS MARCO MRR@10 40.2, BEIR-13 avg nDCG@10 51.7 (109M params). naver/splade-v3-distilbert: 38.7 / 50.0. naver/splade-v3-lexical: 40.0 / 49.1. Source: Sentence Transformers sparse-encoder pretrained-models table (author-reported scores), https://www.sbert.net/docs/sparse_encoder/pretrained_models.html ; SPLADE-v3 paper: Lassance et al. 2024, arXiv:2403.06789. Original SPLADE: Formal et al., SIGIR 2021, arXiv:2107.05720.
    • BM25's BEIR average is ~0.44 nDCG@10 — so the prior is that SPLADE-v3 clears our FTS/BM25 leg comfortably on generic benchmarks. That justifies building the harness; it does not justify adoption. Domain transfer to our corpora (code, docs, session logs) is exactly what the harness must answer, and public tables omit sparsity/FLOPS, so index cost must be measured on our chunks, not assumed.

    The harness (concrete protocol)

    1. Eval set. Held-out queries from real shapes (code questions, documentation questions, ops/log questions — the classes this issue names). Build qrels by pooling top-k from all candidate signals (dense, FTS5/BM25, SPLADE) and judging the pool; freeze the set + qrels before any tuning. Split dev/test: tune fusion on dev, report on test once.
    2. Ablations. (a) BM25 only · (b) dense only · (c) SPLADE only · (d) dense+BM25 (current production shape, RRF) · (e) dense+SPLADE · (f) dense+BM25+SPLADE. For (f) try both RRF and a convex combination of normalized scores — Bruch et al. report convex combination can beat RRF on BEIR/MS MARCO, with a single α to tune (on dev only).
    3. Metrics. Primary: nDCG@10 (BEIR's primary metric convention). Candidate quality: Recall@100 and Recall@1000 (MS MARCO first-stage convention reports Recall@1000 alongside MRR@10). Also MRR@10. Store per-query top-100 runs so metrics can be recomputed without re-running retrieval (BEIR tooling pattern).
    4. Efficiency, specific to Implementation A. Postings rows per chunk (≈ SPLADE nonzeros/doc, measured with the pinned encoder — reference path: SparseEncoder("naver/splade-v3") from sentence-transformers, model rev pinned), postings-table size vs the FTS5 index at shard scale (~107k chunks), and query latency p50/p95 of the SQL join+aggregate vs the FTS5 baseline. Drew's bar: total query latency stays in the current band.
    5. Pre-registered decision gates. Adopt SPLADE as the third signal only if (f) beats (d) by ≥ +2 nDCG@10 points on the held-out test split and p95 query latency stays within the current band. If SPLADE-only ≈ BM25-only but fusion gains, keep it fusion-only and say so. If no configuration clears the gate, close this issue with the measurements as the evidence — a negative result is a result.

    What remains

    Implementation of the harness itself (eval-set construction from real query shapes is the long pole), then the ablation run. Filed as the next step for a build lane; this note is the methodology it should execute unchanged unless the measurements contradict it.

  4. added
    queue/queuedHas a queued task in the Supabase agent queue
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 5, 2026
  5. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  6. added
    work/assignedQueue worker lifecycle state
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 5, 2026
  7. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  8. 27 remaining items

  9. developing-today commented on Nov 5, 2026

    @developing-today
    Contributor

    🤖 Agent note · picked up

  10. developing-today commented on Nov 5, 2026

    @developing-today
    Contributor

    🤖 Agent note · parked

  11. added
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 5, 2026
  12. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Nov 5, 2026
  13. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — task 10582 (small_fix, #5741) DONE by lane code-u: PR #6262 merged (squash, merge commit c26e058) and #5741 auto-closed. Resumed at stage pr_opened; both Codex P2 findings on PR #6262 were fixed in commit 6af6ef0 before merge (PerQuery now preserves top-1000 + judged grades so report metrics recompute from the report alone, with a new test proving it; qrel grades above MAX_GRADE=30 are now rejected at validation instead of overflowing the 2^grade gain to NaN) and replied to on-thread. Laptop verification: cargo test -p atlas-core — 29 lib + 5 parity tests pass (eval:: 12 incl. 2 new); cargo fmt clean. Stale parked labels removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

status:donetask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions