Skip to content

fix(sampling): treat top_k above the vocabulary as no top-k in the C++ chain #2247

Closed

Description

@inureyes

Problem / Background

The stock sampling chain's top_k_filter (src/lib/mlxcel-core/cpp/mlx_cxx_bridge.cpp:6082-6097, origin/main 1b4e3657) calls mlx::core::argpartition(neg_x, top_k - 1, -1) without comparing top_k to the vocabulary size. Found while reviewing PR #2218. This is from reading the code; it has not been reproduced yet.

Current Behavior

  • argpartition throws std::invalid_argument("[argpartition] Received invalid kth ...") when kth >= shape(axis) (src/lib/mlx-cpp/patches-rocm/mlx/ops.cpp:2873-2878, same check upstream). So top_k > vocab throws while the graph is being built. top_k == vocab is valid and keeps every token.
  • fused_sample, fused_sample_xtc, fused_sample_categorical and fused_sample_probs are not Result bridge functions (src/lib/mlxcel-core/src/lib.rs:2503-2509, :2536, :2557), so the exception reaches cxx's noexcept boundary and calls std::terminate: the process aborts.
  • The default GPU path reaches it. A sampled request with only top-k active (temperature > 0, top_k > vocab, top_p 1.0) skips Gumbel (top_k > 0). sampling_rejection_routes (:5858-5871) returns false when top-p is inactive, so fused_sample_impl (:6644-6762) falls through to fused_sample_argpartition_chain. XTC-active configs, fused_sample_categorical, MLXCEL_SAMPLING_REJECTION=0, the CPU device and backends without a rejection port take the chain unconditionally. With top-p active, top-k at or above vocab counts as inactive (:5869) and routes to the kernel, which is why this rarely shows.
  • Rust does not validate first. The server passes the request's top_k through unchanged (src/server/request_options.rs:293), and fused_sample_dispatch (src/lib/mlxcel-core/src/sampling.rs:560-584) forwards config.top_k. The Rust-side filters already guard (sampling.rs:1235-1238, :1280-1284, :1557-1561 apply top_k_filter only when top_k < vocab). The C++ chain is the only unguarded call site.

Proposed Solution

In fused_sample_filter_logits, define use_top_k as top_k > 0 && top_k < x.shape(-1). That is the same "top-k at or above vocab is inactive" rule sampling_rejection_routes uses, and llama.cpp treats top_k >= n_vocab as no filtering. Leave the Rust reference top_k_filter (sampling.rs:2518) unchanged, since its callers guard, but add a one-line doc comment that k must be below the vocabulary. Request validation is not needed: an out-of-range top_k is a valid "no top-k" request.

Acceptance Criteria

Verification

cargo test -p mlxcel-core sampling -- --test-threads=1
make verify

Related: PR #2218, #901 (rejection routing), #1379 (chain order).

Activity

  1. added
    type:bugBug fixes, error corrections, or issue resolutions
    area:inferenceGeneration, sampling, decoding (incl. speculative, DRY)
    and removed on Oct 8, 2026
  2. inureyes commented on Oct 8, 2026

    @inureyes
    MemberAuthor

    Reproduced on gfx1151 (ROCm, origin/main 1b4e365, mlxcel-server -m models/mlx/Qwen3-0.6B-4bit). One request aborts the whole server process (exit status 134, SIGABRT), dropping every in-flight request:

    POST /v1/chat/completions {"model":"Qwen3-0.6B-4bit","messages":[...],"max_tokens":8,"temperature":0.7,"top_k":1000000,"top_p":1.0}
    -> curl: empty reply (exit 52); server log:
    terminate called after throwing an instance of 'std::invalid_argument'
      what():  [argpartition] Received invalid kth 999999 along axis -1 for array with shape: (1,151936)
    
    Path top_k 1000000 Result
    server, top_p: 1.0 default GPU path, chain abort (134)
    server, top_p: 0.9, MLXCEL_SAMPLING_REJECTION=0 chain abort (134)
    server, top_p: 0.9 or top_p omitted (model default 0.95) rejection kernel HTTP 200
    mlxcel generate --temp 0.7 --top-k 1000000 chain abort (134)
    mlxcel generate ... --top-k 1000000 --top-p 0.9 rejection kernel OK

    Raising to priority:high: any client can take the server down with one field.

  3. added a commit that references this issue on Oct 12, 2026
    d078601
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:inferenceGeneration, sampling, decoding (incl. speculative, DRY)priority:highHigh prioritystatus:doneCompletedtype:bugBug fixes, error corrections, or issue resolutions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions