Skip to content

Improve built-in search relevance for symbol queries - #252

Merged
malfet merged 1 commit into
pytorch_sphinx_theme2from
search-symbol-ranking
Aug 25, 2026
Merged

malfet merged 1 commit into
pytorch_sphinx_theme2from
search-symbol-ranking

Conversation

@malfet

@malfet malfet commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Searching nn.linear on the PyTorch docs returns twelve *.modules.linear module pages before torch.nn.Linear, which lands at rank 29. Reported in pytorch/pytorch#4954.

Cause

Two independent problems in Sphinx's stock scorer:

  1. The Python domain gives every module search priority 0, worth +15 in Scorer.objPrio, while classes and functions get priority 1 for +5. Module pages therefore always outrank the class the user wanted.
  2. performSearch merges three passes into one list and sorts on the raw score without normalising them. The title/index pass is a 0-100 percentage; object and fulltext hits are small additive scores. Any title match outscores any object match.

Fix

searchtools.js declares var Scorer only when it is not already defined, so this replaces the scorer from a script registered ahead of it rather than patching Sphinx internals. It fixes the module priority, compresses the top of the title pass monotonically onto the object scale, and adds a bonus for matching the dotted symbol a page documents — recovered from the generated/<dotted.path> docname, which is the only symbol signal a title-pass result carries since its display text is just the section heading ("Linear").

Results

Ran the real searchtools.js under node against the published 2.13 search index, over 16 representative symbol queries:

baseline tuned
MRR 0.488 0.969
top-1 (of 16) 4 15
top-3 (of 16) 12 16

nn.linear 29 → 1, torch.nn.Linear 29 → 1, optim.adam 13 → 1. Seven prose queries (cuda semantics, autograd mechanics, broadcasting semantics, …) keep their existing top result.

Alternatives rejected

  • Overriding splitQuery to keep dotted identifiers intact made things worse (nn.linear 29 → 161) by starving the fulltext pass.
  • Swapping engines scored below the tuned scorer: flexsearch 0.854 MRR, minisearch 0.740, lunr 0.539. lunr returns nothing at all for dotted queries.

Still to verify

Draft because the ranking is measured against the published index, not a local docs build — so add_js_file emitting before searchtools.js in a real build is still unconfirmed. It must stay a plain, non-deferred script.


Authored with the assistance of an AI coding agent.

Searching `nn.linear` on the PyTorch docs returns twelve `*.modules.linear`
module pages before `torch.nn.Linear`, which lands at rank 29.

Two causes. The Python domain gives every module search priority 0, worth
+15 in Scorer.objPrio, while classes and functions get priority 1 for +5,
so module pages always outrank the class the user wanted. And performSearch
merges three passes into one list sorted on raw score without normalising
them -- the title/index pass is a 0-100 percentage, object and fulltext hits
are small additive scores -- so any title match outscores any object match.

searchtools.js declares `var Scorer` only when it is not already defined, so
this replaces the scorer from a script registered ahead of it instead of
patching Sphinx internals. It fixes the module priority, compresses the top
of the title pass monotonically onto the object scale, and adds a bonus for
matching the dotted symbol a page documents, recovered from the
`generated/<dotted.path>` docname -- the only symbol signal a title-pass
result carries, since its display text is just the section heading.

Two alternatives were measured and rejected: overriding `splitQuery` to keep
dotted identifiers intact made things worse (`nn.linear` 29 -> 161) by
starving the fulltext pass, and swapping engines scored below the tuned
scorer (flexsearch 0.854 MRR, minisearch 0.740, lunr 0.539, vs 0.969 here;
lunr returns nothing at all for dotted queries).

Test Plan:

Ran the real searchtools.js under node against the published 2.13 search
index, over 16 representative symbol queries:

```
node verify.js
```

                        baseline    tuned
    MRR                 0.488       0.969
    top-1 (of 16)       4           15
    top-3 (of 16)       12          16

`nn.linear` 29 -> 1, `torch.nn.Linear` 29 -> 1, `optim.adam` 13 -> 1. Seven
prose queries (`cuda semantics`, `autograd mechanics`, ...) keep their
existing top result. Not yet verified against a local docs build.

Authored with the assistance of an AI coding agent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Aug 24, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for pytorchsphinxtheme ready!

Name Link
🔨 Latest commit a115052
🔍 Latest deploy log https://app.netlify.com/projects/pytorchsphinxtheme/deploys/6a8c494db577db0008ae703a
😎 Deploy Preview https://deploy-preview-252--pytorchsphinxtheme.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@meta-cla meta-cla Bot added the cla signed label Aug 24, 2026
@vadimkantorov

Copy link
Copy Markdown

Hoping it can also dedup results based on symbols

@malfet
malfet marked this pull request as ready for review August 25, 2026 16:12
@malfet

malfet commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

@vadimkantorov it will not, but will follow up with dedupe

@malfet
malfet merged commit fa86962 into pytorch_sphinx_theme2 Aug 25, 2026
8 of 9 checks passed
@malfet malfet mentioned this pull request Aug 27, 2026
malfet added a commit that referenced this pull request Aug 27, 2026
* Bump theme to 0.4.12

Picks up the search relevance fix (#252) and the GitHub Actions Node 20
bumps (#253).

Goes straight from 0.4.10 to 0.4.12. 0.4.11 was published to PyPI on
2026-05-19 from the `bump-theme-0.4.11` branch, which was never merged, so
the branch still read 0.4.10 and there is no 0.4.11 changelog entry. This
bump lands on the branch so the next release does not inherit the same gap.

Test Plan:

```
python3 -c "import pytorch_sphinx_theme2 as t; print(t.__version__)"
grep -rn '0\.4\.12' setup.py pytorch_sphinx_theme2/__init__.py
```

All three version strings (setup.py, __version__, and the dict returned by
setup()) report 0.4.12.

Authored with the assistance of an AI coding agent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Backfill v0.4.11 changelog entry

0.4.11 shipped to PyPI on 2026-05-19 but its bump branch was never merged,
so the changelog jumped 0.4.10 to 0.4.12 while the code it contained (#251)
is on the branch and reaches anyone upgrading from 0.4.10. Recording it as
its own section keeps that accurate for both sets of users, rather than
folding it into v0.4.12 and telling existing 0.4.11 users about changes they
already have.

Authored with the assistance of an AI coding agent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
malfet added a commit that referenced this pull request Sep 2, 2026
Searching `linear` returned eight rows all rendering as "Linear", pointing
at eight different pages, and `nn.linear` returned four rows that were the
same page reached three different ways. This is the "finds duplicates" half
of pytorch/pytorch#4954, which the ranking fix in #252 did not address.

Two causes, one fix. performSearch runs a title pass, an object pass and a
fulltext pass, each emitting its own row for a page, and the built-in dedup
keys on [docname, title, anchor, descr, filename] -- which differs per pass,
so nothing collapses. Separately the rows are labelled with whichever
heading matched, and for autodoc symbol pages that is the bare leaf name, so
sibling pages are visually indistinguishable.

searchtools.js applies Scorer.score to every result before it dedupes, and
passes score() the live result array. Rewriting the fields the dedup keys on
therefore makes Sphinx's own dedup collapse the rows, with no patching of
Search.query. Relabelling to the page's own symbol fixes the display at the
same time: `torch.ao.nn.qat.Linear` instead of a third row saying "Linear".

Only `generated/<dotted.path>` pages are canonicalised. Prose pages are left
alone, since distinct sections of one page are useful deep links rather than
duplicates.

The one fragility is the ordering dependency: score() must run ahead of the
dedup in Search.query. That holds in Sphinx 7.2.6, which docs builds pin. If
a future Sphinx reorders it, rows duplicate again exactly as they do today,
so it degrades to current behaviour rather than breaking.

An alternative considered and rejected: switching to a document-oriented
engine, which cannot produce same-page duplicates because it indexes one
entry per page. MiniSearch over a page-level corpus does return zero
duplicates, but ranks the wanted page 3rd for `adam` and `linear` where this
returns it 1st, so it would trade the #252 ranking win for the dedup win
rather than getting both.

Test Plan:

Ran the real searchtools.js under node against the PyTorch docs preview
index from pytorch/pytorch#195042 (3472 docs, 8918 objects):

```
node rank2.js
node cmp.js
```

Same-page duplicates in the top 10: `nn.linear` 4 -> 0, `conv2d` 2 -> 0,
`adam` 1 -> 0. Every one of eight symbol queries keeps its target page at
rank 1, and `cuda semantics`, `autograd mechanics` and `broadcasting
semantics` keep their existing top result.

Authored with the assistance of an AI coding agent.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
pytorchmergebot pushed a commit to pytorch/pytorch that referenced this pull request Sep 9, 2026
Picks up **both** halves of the docs search fix for #4954, in a single bump.

### Ranking — theme [#252](pytorch/pytorch_sphinx_theme#252), released in 0.4.12

Sphinx's stock scorer ranked module stub pages above the API symbols they document: the Python domain gives modules search priority 0 (worth `+15`) against `+5` for classes and functions, and `performSearch` merges three passes onto one list without normalising their scales. Searching `nn.linear` returned twelve `*.modules.linear` module pages ahead of `torch.nn.Linear`, which sat at rank 29.

### Duplicates — theme [#255](pytorch/pytorch_sphinx_theme#255), released in 0.4.13

Those same three passes each emitted a row for one page, and the built-in dedup keyed on `[docname, title, anchor, descr, filename]` — which differs per pass — so a single symbol page was listed repeatedly. Rows were also labelled with whichever heading matched, so `linear` returned eight indistinguishable entries reading `Linear`.

### Note on the Docker rebuild

This touches `.ci/docker/`, so it forces a docs image rebuild. That's unavoidable rather than accidental — `docs/requirements.txt` is a symlink to `.ci/docker/requirements-docs.txt`, and the image is what the docs job actually installs from. Both theme fixes are taken in one bump so the rebuild happens once rather than twice.

### Test plan

CI builds the docs and publishes a preview. On that preview, searching `nn.linear` should return `torch.nn.Linear` first rather than the quantized module pages, and `linear` should return distinct qualified names rather than repeated `Linear` rows.

Verified before landing against the preview index from an earlier revision of this PR (3472 docs, 8918 objects), running the real `searchtools.js` under node.

Rank of the wanted page:

| query | before | after |
|---|---|---|
| `nn.linear` → `torch.nn.Linear` | 29 | **1** |
| `linear` → `torch.nn.Linear` | 3 | **1** |
| `conv2d` → `torch.nn.Conv2d` | 3 | **1** |
| `adam` → `torch.optim.Adam` | 2 | **1** |
| `softmax` → `torch.nn.Softmax` | 2 | **1** |

Same-page duplicates in the top 10: `nn.linear` 4 → **0**, `conv2d` 2 → **0**, `adam` 1 → **0**. Prose queries (`cuda semantics`, `autograd mechanics`, `broadcasting semantics`) keep their existing top result.

### Not fixed here

`torch.optim.Adam` and `torch.optim.adam.Adam_class` are two separate generated pages documenting the same class (likewise `AdamW`, `Adamax`, `NAdam`, `RAdam`). No search change can merge those — it's an autodoc/autosummary duplication that puts genuine duplicates in the index, and it deserves its own issue.

---

Authored with the assistance of an AI coding agent.
Pull Request resolved: #195042
Approved by: https://github.com/atalman

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants