Skip to content

docs(sampling): explain why per-row filter params are keyed by row_idx - #5346

Merged
kahyunnam merged 5 commits into
flashinfer-ai:mainfrom
0z5a:work/fi-a2-sampling-quality
Nov 5, 2026
Merged

kahyunnam merged 5 commits into
flashinfer-ai:mainfrom
0z5a:work/fi-a2-sampling-quality

Conversation

@0z5a

@0z5a 0z5a commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds a comment to TopKSamplingFromProbKernel and MinPSamplingFromProbKernel in include/flashinfer/sampling.cuh. It explains why the per-row top_k / min_p values are read at row_idx and not at bx: the per-row tensor has one entry per probability row. Indexing by bx can select the wrong row's threshold when indices reorders or reuses rows, and can read past the tensor when the output batch exceeds the number of probability rows.

No code change. The fixes themselves landed in #5340 and #5348, and the fuzzer this PR originally carried was superseded by #5341. After merging main into this branch, the diff is only this comment, so the title and description have been cut down to match.

Related: #3605.

Summary by CodeRabbit

  • Documentation
    • Clarified how per-row sampling parameters are indexed. No user-visible behavior changed.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Walkthrough

Comments in top-k and min-p sampling document that per-row parameters use row_idx, not bx, when selecting parameters for probability rows. No executable behavior changed.

Changes: Sampling index documentation

Layer / File(s) Summary
Per-row parameter indexing
include/flashinfer/sampling.cuh
Comments clarify that top-k and min-p sampling index per-row parameters by row_idx.

Estimated code review effort: 1 (Trivial) | ~2 minutes

Change: Other

Possibly related PRs

  • flashinfer-ai/flashinfer#5617: Adds a split-row sampling path that documents the same row-indexing convention for per-request parameters.
  • flashinfer-ai/flashinfer#5324: Changes indexing of per-request sampling thresholds in related kernels, addressing the same parameter-indexing contract.
Merge Risk: Minimal · up to 681a5

The change documents per-row sampling parameter indexing without changing behavior. No merge-blocking issue is established.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title names the change: a docs-only comment in sampling.cuh that explains why per-row filter parameters are keyed by row_idx. It matches the single file change (+2 lines, comment only) and the PR …
Description check Passed The description covers the purpose of the change, the reasoning (per-row tensor indexed by row_idx, not bx, with the risk of wrong-row selection and out-of-bounds reads), and the context of related PR…
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the change: a docs-only comment in sampling.cuh that explains why per-row filter parameters are keyed by row_idx. It matches the single file change (+2 lines, comment only) and the PR objectives. It is specific and follows the conventional commit style, so it passes.

Full details: Description check

Explanation

The description covers the purpose of the change, the reasoning (per-row tensor indexed by row_idx, not bx, with the risk of wrong-row selection and out-of-bounds reads), and the context of related PRs (#5340, #5348, #5341, #3605). It states that no code changed. It does not follow the repository template: the Checklist (pre-commit and test items), the Tests section, the Experimental Track section, and the Related Issues section in the template format are missing or not filled in. The author's comments say pre-commit passed, but the description does not record the checklist. The description is mostly complete on the substance, so it passes, but the template sections should be added or marked for completeness.

Resolution

Optional: Add the template sections to the description. Under Related Issues, link #3605 in the template format. Under Pull Request Checklist, mark the pre-commit items. Under Tests, state that no tests apply because the change is comment-only, or list the test targets. Mark the Experimental Track section as not applicable.

Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 0 files. (1 skipped: 1 unsupported.)

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 32f7ae40-5429-4f04-8fda-974c7eedf885
Commits

Reviewing files that changed from the base of the PR and between 1a01648 and 681a54f.

Files selected for processing: 1
  • include/flashinfer/sampling.cuh
Files skipped from review as they are similar to previous changes: 1
  • include/flashinfer/sampling.cuh

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

@flashinfer-bot flashinfer-bot added the op: misc norm, activation, sampling, RoPE, quantization, etc. label Sep 19, 2026
@0z5a

0z5a commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

The change is complete on this head and pre-commit is green (including the mypy/ruff hooks). The only red check left is Report unauthorized PR status: the GPU test lane is gated for fork PRs, so it needs a maintainer to authorize the workflow (or apply the appropriate label) before PR Test can run. Ready for review.

@0z5a
0z5a force-pushed the work/fi-a2-sampling-quality branch from f714ec9 to cdf28a8 Compare September 24, 2026 01:13
## Scope

Related to flashinfer-ai#3605 — this PR adds the Sampling side of the quality-fuzzing plan (contract layer, independent CPU oracle, deterministic layer, distribution layer, failure ledger). It does not redo the Norm/RoPE work or the Attention fuzzer, and speculative sampling is out of scope for this first version.

In scope: `sampling_from_probs`, `sampling_from_logits`, the individual top-k / top-p / min-p filters, the combined orderings (`top_k_first` vs `joint`), plus two production defects found while building the oracle.

Out of scope: any public API change, `chain_speculative_sampling`, CUDA-graph/compile paths.

## Implementation

Two production fixes, both about per-row filter parameters when `indices` is used:

- `TopKSamplingFromProbKernel` read `top_k_arr[bx]` and `MinPSamplingFromProbKernel` read `min_p_arr[bx]`, while the launcher validates that tensor against `probs.size(0)` (the unique-row count). Whenever `indices` reuses rows (`batch_size > unique_batch_size`) that is an out-of-bounds device read *and* the wrong row's parameter. Both now key on `row_idx`, like the top-p and joint kernels already did.
- The Python shims always materialised the per-row `top_k` tensor as `int32`, while the C++ side reads that buffer as the output `IdType`. With int64 `indices` the int32 buffer was read as int64: garbage `k` (filtering silently disabled) plus an OOB read. The buffer is now materialised in the output dtype.

The test suite is layered the way the issue plan describes:

- **Contract first**, per API, with the source line each fact was read from: retention is value-based (top-k keeps every token equal to the k-th largest value, not k tokens), top-p is strict at the boundary while min-p is inclusive at `max(p) * min_p`, `top_k_first` is *not* `joint`, `valid` differs per API (all-zero rows return `(0, False)` everywhere except `min_p_sampling_from_probs`, whose predicate accepts zeros), and the documented RNG offset advance is `ceil(increment / 4) * 4`.
- **Independent CPU oracle** that implements softmax and the filters itself and never calls the code under test; `top_k_first` and `joint` get separate references.
- **Deterministic layer**: one-hot / unique-candidate inputs must return the only legal token; the same fixed `seed`+`offset` must reproduce the documented behaviour.
- **Distribution layer** with a pre-declared budget: `K = 44` comparisons, `alpha = 0.01`, `N = 200000` trials per case, acceptance `|p_hat - p| <= sqrt(log(2K/alpha) / (2N))` (half width 0.004765). The plan was calibrated against pure reference sampling before being used on the device: 0/30 false positives, power 0.833 at 1.0x half width, 1.000 at 2.0x and at an injected 0.02 bias, 1.000 against a permuted row mapping.
- **Failure ledger**: seed/offset, input rule, filter order, thresholds, indices, reference support, observed frequencies and versions, so a failure is reproducible rather than seed-rolled away.

## Validation

FlashInfer base: `dc04f50c9aa3eabcdaa5feb0934edb3d85e9529a`
Hardware and software: NVIDIA L20 (SM89), driver 595.91.07, CUDA 12.8, torch 2.13.0+cu130, Python 3.10

```
python -m pytest -q tests/sampling/test_sampling_contract_cpu.py
# 12 passed, 0 failed
```

GPU layer status (first live run on the L20, single GPU):

```
CUDA_VISIBLE_DEVICES=<n> FLASHINFER_SAMPLING_FUZZ_SHARD=0/4 \
  python -m pytest -q tests/sampling/test_sampling_quality_fuzz.py
# 25 failed, 3 skipped
```

Two failure classes, both stated plainly:

- the harness itself indexes a CUDA tensor with a CPU index tensor in `singular.repeat(case.calls)[rows]` — a test-side device-placement defect, being fixed;
- the first `illegal memory access` occurs in `struct/keying/top_k_int64`, i.e. exactly the int64-indices keying scenario the production fix above addresses, after which the CUDA context poisons the remaining cases. Whether the kernel still reads out of bounds on some path or the harness feeds an invalid input is **not yet attributed**; the next step is a single-case run with `CUDA_LAUNCH_BLOCKING=1` against both the patched and the unpatched tree.

This PR is opened as a draft for that reason: the CPU contract/oracle/statistics layer is green and reviewable, the GPU layer is not yet green.

## Results and limitations

Correctness: CPU layer 12/12; the two production fixes have regression cases that fail on the base tree.
Performance: not evaluated; the suite adds no hot-path work.
Memory: not evaluated.
Limitations: GPU layer not green yet (above); per-test timeout / `--forked` isolation and the multi-GPU shard launch are not wired here.

Signed-off-by: 0z5a <[email protected]>
@YangXu1990uiuc

Copy link
Copy Markdown
Collaborator

@0z5a thanks for chasing these. Both fixes in this draft have now landed through the parallel sampling slice:

The int64 struct/keying/top_k_int64 IMA you attributed on 09-20 is the #5347 dtype bug, and #5348 fixes it. The sampling fuzz suite (#5341) passes 455/455 on A100, H100 and B200 with it. The diff here is therefore fully superseded, and the conflict with main is also from those merges. I suggest closing this PR so the sampling slice has one owner (@JulianZJN, per #3605). If your contract table (per-API source-line citations, valid semantics, the RNG offset advance) has anything #5341 lacks, a small test-only PR on top of #5341 would be welcome. Your RMSNorm slice (#4994) is approved and will merge once CI is green.

Retain the upstream dtype and row-index fixes, which already include
the original implementation, and preserve the per-row parameter notes.

Signed-off-by: 0z5a <[email protected]>
@YangXu1990uiuc

Copy link
Copy Markdown
Collaborator

@0z5a after your 10-21 merge of main, this PR's diff is the 6-line comment in sampling.cuh explaining why per-row parameters are keyed by row_idx. The title and description still describe the original fuzzer and the two fixes, and the squash commit message would carry them.

The comment is accurate and worth keeping. If you retitle the PR to something like docs(sampling): explain why per-row filter params are keyed by row_idx and cut the description to that one change, I'll approve and merge it. If you'd rather not, please close it.

@0z5a 0z5a changed the title [Test] Add contract and distribution quality fuzzing for sampling docs(sampling): explain why per-row filter params are keyed by row_idx Oct 26, 2026
@0z5a

0z5a commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

@YangXu1990uiuc Done: retitled to docs(sampling): explain why per-row filter params are keyed by row_idx, and cut the description down to the comment. The diff is unchanged (head 1a01648e).

@YangXu1990uiuc YangXu1990uiuc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @0z5a. Approving the comment-only change; I've added run-ci and will merge once the check passes.

@kahyunnam kahyunnam left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving pending CI — the comment documents the right rule for the per-row top_k / min_p read (keyed by the probability row, as in the top-p and joint kernels), and the PR changes no code; thanks for cutting it down to this.
One non-blocking wording suggestion is inline.
The public H100 lane on this head was cancelled rather than failed, so I will start a CI run on this head before merge.

Comment thread include/flashinfer/sampling.cuh Outdated
const uint32_t row_idx = indices == nullptr ? bx : indices[bx];
// Per-row parameters are keyed by the probability row they describe, like the top-p and
// joint kernels. Indexing by bx would read past a length-`unique_batch_size` tensor whenever
// `indices` reuses rows, i.e. whenever batch_size > unique_batch_size.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small precision point: reusing rows is not the same as batch_size > unique_batch_size (e.g. indices=[0,0,2] with 3 rows reuses row 0 but bx stays in bounds and just picks the wrong row's threshold), and only the latter overflows. Could you collapse this to one accurate line here and at :1137, e.g. // One entry per probs row: index by row_idx, not bx (#5340).?

@kahyunnam
kahyunnam enabled auto-merge (squash) November 3, 2026 17:26
@kahyunnam

Copy link
Copy Markdown
Member

@flashinfer-bot run tests/test_env.py

auto-merge was automatically disabled November 3, 2026 17:55

Head branch was pushed to by a user without write access

@kahyunnam
kahyunnam enabled auto-merge (squash) November 4, 2026 16:06
@kahyunnam

Copy link
Copy Markdown
Member

@flashinfer-bot run tests/test_env.py

@kahyunnam

Copy link
Copy Markdown
Member

@flashinfer-bot run tests/cli/test_cli_cmds.py

@kahyunnam
kahyunnam merged commit 3d8944f into flashinfer-ai:main Nov 5, 2026
29 of 38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

op: misc norm, activation, sampling, RoPE, quantization, etc. run-ci

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants