Do llama.cpp's truncation samplers honor the sorted-flag contract, or can a stale flag make top_p, min_p,
and top_k truncate the mathematically wrong set of tokens?
llama_token_data_array carries a boolean sorted flag; the public header warns "do not assume the data is
sorted - always check this flag". The truncation samplers have a fast path: when sorted == true they skip
re-sorting and read a prefix of the array in its current order (top_p and min_p also trust data[0] as the
maximum). A sampler's survivor SET is, by definition, a function of the logit multiset and the parameter only, so
it must be invariant to input order. This repo tests, exhaustively over the real shipped code, whether that
invariant holds when the flag is stale, and whether a stale flag is reachable through the public sampler API.
Three of the four truncation samplers are unsound under a stale sorted = true flag, and the stale state is
reachable through a documented public-API sampler chain.
top_p,min_p, andtop_kare order-dependent when handedsorted = trueon an array that is not actually sorted. Exhaustively enumerating all n! permutations of a fixed logit multiset (linked against the reallibllama, calling the shippedllama_sampler_apply) yields many distinct survivor sets for the same multiset: up to 11 distinct sets fortop_p, 8 formin_p, 10 fortop_k. A correct truncation sampler cannot return different token sets for the same multiset, so this is a proof of unsoundness, not a measurement. Every order-dependent set differs from the correct set.typicalis sound. It re-sorts fully by its own key and resetssorted = false, so its output is permutation-invariant (exactly one set across all permutations).- The bug is a broken invariant, not bad luck. A fast path that trusts
sortedis only safe if every sampler that reorders logits resets the flag.penalties,dry, andtypicalall reset it;logit_biasdoes not. It adds a caller-supplied bias to arbitrary tokens (an arbitrary reordering) and leavessorteduntouched.grammarandxtcalso reorder without resetting it.logit_biasis the cleanest injector. - Reachable through the public API. The chain
top_k(4) -> logit_bias(+6 @ id3) -> top_p(0.80), built withllama_sampler_chain_addand run throughllama_sampler_apply, keeps the survivor set{0,1,2,3}where the correct set is{3}.top_ksetssorted = true;logit_biasmoves a token to the front of the true order while the flag staystrue(the harness observessorted == trueafter thelogit_biasstep);top_pthen reads the stale prefix. The correct answer keeps only the biased token (about 94 percent of the mass); the buggy path keeps it plus three low-probability tokens, diluting the distribution a downstream selector samples from.
When a stale sorted = true reaches top_p, min_p, or top_k, the truncation set is wrong: tokens that should
be cut survive, and the renormalized distribution the next stage samples from is not the one the parameters
specify. In the reachable chain the effect is large (keep 4 where 1 is correct).
Scope, stated plainly: the stock common sampler chain in common/sampling.cpp prepends logit_bias and
grammar before any sorter, so the default llama-cli and llama-server chains are not affected. The bug is
reachable for any consumer that constructs a chain through the public llama_sampler_chain_add API with a
reordering sampler placed after a sorter, which is possible for servers, language bindings, and custom sampler
stacks that order samplers freely (stated as a capability of the public API, not a cited downstream instance). This is
a latent-until-ordered soundness bug in a production component, in the spirit of a contract that the fast path
assumes but the codebase does not fully enforce. reach.md is the full static audit of which samplers keep the
invariant.
- Real subject. The harness links the shipped
libllama(Homebrew build 9760, commit6ee0f65793) and calls the publicllama_sampler_init_*/llama_sampler_apply/llama_sampler_chain_*symbols. Harness-eval is production-eval; nothing is transcribed. - The proof is order-variance itself. Distinct survivor sets for one multiset is unsoundness by definition, needing
no external oracle. The independent clean-room reference (
ref.py) then confirms the honest-flag output is the mathematically correct set (0 mismatches over 8 cases), so the stale-flag sets are genuinely wrong. - Negative control. With the honest flag (
sorted = false), every sampler returns exactly one set across all n! permutations, proving the samplers are order-invariant when the flag is honest, so the variance is caused by the stale flag, not the harness. - Positive control. A deliberately stale (reverse-sorted) array fed to
top_pwithsorted = truereturns a set that differs from the honest set, proving the detector fires. - Boundary-tie-free inputs. Multisets use distinct logits with truncation thresholds landing strictly between probability levels, so every reported difference is a genuine wrong token, never a tie-rule disagreement.
./run.sh # builds harness vs libllama, runs the audit + controls + reachability, cross-checks the reference
Needs the Homebrew llama.cpp headers/lib at /opt/homebrew. Fully CPU, exact, deterministic; no timing, no
probability sampling.
harness.cpp— linkslibllama; exhaustive permutation audit under forcedsorted, negative/positive controls, and the public-API reachability chain (manual and realllama_sampler_chain).ref.py— clean-room order-invariant set-functions; cross-oracle against llama.cpp's honest path.reach.md— static audit of everysortedwrite: which samplers keep the invariant, which break it.PREREG.md— the pre-registration.results/— machine-checked outputs.
MIT.