docs(thread-affinity): state the CPU placement axis, and what would close it - #133
Open
Helldez wants to merge 1 commit into
Open
docs(thread-affinity): state the CPU placement axis, and what would close it#133Helldez wants to merge 1 commit into
Helldez wants to merge 1 commit into
Conversation
Helldez
force-pushed
the
exp/thread-affinity
branch
from
August 17, 2026 15:17
d1ee017 to
1578d8f
Compare
Helldez
marked this pull request as ready for review
August 17, 2026 15:18
…lose it The engine picks a thread count and leaves placement to the kernel. Decode is compute-bound, so this is the one open axis that acts on compute rather than on flash, and it has been carried as a one-line "cheapest untested lever" for weeks without anyone writing down what would actually be tested. Three mechanisms hide behind the single word. Barrier skew and migration deflating the governor's per-core utilisation both argue for taking the fast cores; the reader as a co-tenant argues for leaving one free, since compute threads that spin on every core push the cost into stall rather than compute. They are distinguishable only if the verdict metric is the decode breakdown instead of tok/s, which is the main thing this note is meant to prevent getting wrong. It also records why the result cannot ship as a mask: cgroup cpusets, vendor userspace moving threads, topology that varies by part, and per-cluster caps that move during a run. The shippable unit is a policy resolved from sched_getaffinity, cpu_capacity and related_cpus. Prior art is surveyed rather than assumed. llama.cpp already exposes the flags through the public threadpool API, so the first two cells need no engine change and either can close the axis outright. ncnn and MNN answer the portability question the same way. None of them streams its weights, which is why the co-tenant hypothesis is ours to measure. Nothing is implemented. Pinning the read workers is separately already closed at under 2%, and the note keeps the two results from being quoted for each other.
Helldez
force-pushed
the
exp/thread-affinity
branch
from
August 17, 2026 15:31
1578d8f to
bdd938c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Documentation only. No code, no flags, nothing implemented.
The engine picks a thread count and leaves placement to the kernel. Decode on the
reference device is compute-bound, so CPU placement is the one open axis that acts on
compute rather than on flash, and it has been carried for weeks as a one-line "cheapest
untested lever" without anyone writing down what would actually be tested. This PR writes
it down, so the experiment is not re-derived a third time.
What the note establishes
Three mechanisms hide behind one word, and they disagree about the intervention.
Barrier skew (a node costs what its slowest thread costs) and migration deflating the
governor's per-core utilisation both argue for taking the fast cores. The reader as a
co-tenant argues for the opposite: compute threads that spin on every core deschedule the
O_DIRECT workers, and that cost lands in
stall, not incompute. They aredistinguishable only if the verdict metric is the decode breakdown rather than tok/s,
which is the mistake the note mainly exists to prevent.
The result cannot ship as a core mask. cgroup cpusets that the vendor controls and
that change with foreground state, vendor userspace that moves threads, topology that
varies by part (4+4, 1+3+4, 2+6, three clusters, parts with no low-power cluster), and
per-cluster frequency caps that move during a run, so a mask computed at startup from
"which cores are fastest" can describe a machine that no longer exists ninety seconds in.
What is discoverable at runtime is
sched_getaffinityas the granted universe,cpu_capacity, andrelated_cpus, which makes the shippable unit a policy rather than amask, and makes the frequency-invariant policy safer than the frequency-derived one.
Prior art is surveyed rather than assumed. The pinned llama.cpp already exposes
--cpu-mask,--cpu-strictand--pollthrough the public threadpool API, andupstream's own Snapdragon bench script excludes the low-power cores. ncnn has shipped a
three-value policy enum across the Android fleet for years. MNN-AECS selects cores
adaptively at runtime. All of them assume resident weights, so the co-tenant hypothesis
is the part that is ours to measure.
The first two cells need no engine change and either one can close the axis.
Per-thread user times from
/procduring a real decode settle whether skew exists at all;an upstream
llama-benchsweep settles whether this SoC has an exploitable placementeffect. Only then is there anything to build. The confounds are listed with them: the
frequency regime differs between adb and in-app and decays under load, so cells must be
long and the cap sampled during them, and concentrating threads on one cluster throttles
sooner, which can hand back a short cell's gain.
Pinning the read workers is a separate, already closed question (under 2%, measured with
bmoe-iobench). The note records it explicitly so the two results are never quoted foreach other.
Files
docs/thread-affinity.md, new.docs/roadmap.md, a section for the axis pointing at it.docs/README.md, index row.CHANGELOG.md,[Unreleased]documentation entry.The branch previously held a single empty placeholder commit from July with no PR; it has
been replaced by this one on current
main.What this changes about the July proposal
This PR replaces the original "pin the compute threads to the big cores" proposal that opened
it. Three parts of that framing did not survive being written out properly:
three, and the co-tenant one wants the opposite mask.
--cpu-mask <hex>, which cannot be portable on Android. The surfacebecomes a runtime-resolved policy, and there are two threadpools to configure, not one,
since prefill and decode are opposite regimes.
number is claimed here. The two zero-code cells come first, and either can close the axis.
The
-tknee is kept, and promoted: it may be the whole result, and it is the one form of theanswer with none of the portability hazards.