Skip to content

fix(generate): reject top_k below 1 instead of masking every logit - #2309

Open
Anai-Guo wants to merge 1 commit into
Lightning-AI:mainfrom
Anai-Guo:fix-sample-top-k-below-one
Open

fix(generate): reject top_k below 1 instead of masking every logit#2309
Anai-Guo wants to merge 1 commit into
Lightning-AI:mainfrom
Anai-Guo:fix-sample-top-k-below-one

Conversation

@Anai-Guo

Copy link
Copy Markdown

What

sample() validates top_p but not top_k. A top_k below 1 selects no
candidates at all, and the failure is silent in greedy mode.

The bug

if top_k is not None:
    v, i = torch.topk(logits, min(top_k, logits.size(-1)))
    logits = torch.full_like(logits, float("-inf")).scatter_(-1, i, v)

With top_k=0, torch.topk(..., 0) returns empty tensors, the scatter writes
nothing, and every logit stays at the fill value. The output stops depending on
the model:

>>> logits = torch.tensor([[[0.5, -1.2, 3.1, 0.8, -0.3, 2.7, -0.9, 1.4]]])
>>> sample(logits, temperature=0.0, top_k=0).item()
0          # argmax over an all -inf row; the real argmax is 2
>>> sample(logits, temperature=1.0, top_k=0)
RuntimeError: probability tensor contains either `inf`, `nan` or element < 0
>>> sample(logits, temperature=1.0, top_k=-1)
RuntimeError: selected index k out of range

Greedy decoding is the one that hurts: no warning, no exception, just token 0
emitted at every step. The two sampling-mode errors do stop the run, but they
surface inside torch.multinomial / torch.topk and name neither top_k nor
sample.

top_k arrives here straight from the caller — litgpt generate,
litgpt chat, LLM.generate(), and the LitServe endpoints in
litgpt/deploy/serve.py all forward it unchanged.

The fix

Add the range check next to the one top_p already has:

if top_p < 0.0 or top_p > 1.0:
    raise ValueError(f"top_p must be in [0, 1], got {top_p}")
if top_k is not None and top_k < 1:
    raise ValueError(f"top_k must be >= 1, got {top_k}")

litgpt/generate/speculative_decoding.py has the same sample() with the same
top_p guard and the same unguarded top_k, so it gets the same check. The
silent mode there is worse: with apply_softmax=False the mask fill value is
0 instead of -inf, so the draft distribution handed to the acceptance test
comes out all zeros.

Compatibility

No default in the tree is below 1 — they are None or 50 — so no working
configuration changes. The values that now raise are exactly the ones that
could only produce garbage before.

top_p=0 and temperature=0 keep their documented meaning (greedy); this only
covers top_k, whose domain starts at 1.

Tests

tests/generate/test_main.py

  • test_sample_rejects_top_k_below_onetop_k in (0, -1) × temperature
    in (0.0, 1.0) all raise ValueError
  • test_sample_top_k_one_is_greedy — the smallest accepted value still works
    and pins sampling to the argmax

tests/test_generate_speculatively.py

  • test_sample_rejects_top_k_below_one — same guard on the speculative copy

Verified locally on Windows / Python 3.12 / torch 2.x:

tests/generate/test_main.py          11 passed, 1 xfailed
tests/test_generate_speculatively.py 18 passed

(test_cli in both files fails identically before and after this change — it
shells out to a litgpt entry point that is not installed in my environment.)

ruff check and ruff format --check clean at the pinned v0.15.9 from
.pre-commit-config.yaml.

🤖 Generated with Claude Code

@gitguardian

gitguardian Bot commented Aug 27, 2026

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Signed-off-by: Tai An <antai12232931@outlook.com>
@Anai-Guo
Anai-Guo force-pushed the fix-sample-top-k-below-one branch from 7bfdef0 to ed0ca01 Compare August 31, 2026 07:17
@Anai-Guo

Copy link
Copy Markdown
Author

Sorry about the noise — an earlier push accidentally included two local pip install --target directories (corelib/, rufflib/), which is what blew the diff up to 732 files and what GitGuardian was flagging. I've force-pushed the branch rebuilt on current main with only the intended change: 4 files, +44/-0. Nothing else in the PR has changed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant