Repository navigation
fix: respect CPU affinity and cgroup CPU quota for default thread counts - #2385
Open
manunicholasjacob wants to merge 2 commits into
Open
manunicholasjacob wants to merge 2 commits into
manunicholasjacob wants to merge 2 commits into
Conversation
The default n_threads and n_threads_batch (in Llama and in the server settings) come from multiprocessing.cpu_count(), which reports every host CPU. Under a cgroup CPU quota (docker run --cpus, Kubernetes limits) or a reduced affinity mask (taskset, --cpuset-cpus) this starts far more threads than the process can run, and llama.cpp's worker threads spend most of each CFS period throttled. Add default_cpu_count(): the sched_getaffinity() count where available, capped at ceil(quota/period) from cgroup v2 cpu.max or cgroup v1 cpu.cfs_quota_us/cpu.cfs_period_us on the process cgroup and its ancestors, falling back to multiprocessing.cpu_count(). Explicit n_threads/n_threads_batch values are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
The default
n_threadsandn_threads_batchcome frommultiprocessing.cpu_count()inLlama.__init__and inserver/settings.py.That count is every CPU on the host, so inside
docker run --cpus=2on a 20-thread machine the defaults are 10 and 20 threads.llama.cpp's worker threads then exhaust the CFS quota early in each period and the container is throttled almost all the time.
The same happens with a reduced affinity mask (
taskset,docker run --cpuset-cpus, Kubernetes static CPU manager).This adds
default_cpu_count()inllama_cpp/_utils.pyand uses it at those call sites:len(os.sched_getaffinity(0))where available, elsemultiprocessing.cpu_count();ceil(quota / period)from cgroup v2cpu.max, or cgroup v1cpu.cfs_quota_us/cpu.cfs_period_us, taking the tightest limit on the process cgroup and its ancestors;Explicit
n_threads/n_threads_batchvalues, and the-1server option, keep working;-1now resolves to the usable count instead of the host count.On an unrestricted host the result is the same as before (20 on the test machine).
Tests
tests/test_utils.py: fake/proc/self/cgroupand cgroup roots for v2 quota, v2max, tightest ancestor, v1 quota, v1-1, missing files; affinity capped by quota (including fractional quotas rounding up), affinity without quota, and nosched_getaffinity.tests/test_llama.py::test_llama_default_threads_use_usable_cpu_count:Llamapicksn // 2andnfrom the helper.make lint(ruff check + format check) passes.python:3.11-slimcontainer with a CPU wheel built frommain(1652066) and the patched files: 22 tests pass. The remaining 18 need Hugging Face model downloads, which this offline run skipped; they fail the same way on unpatchedmain.Measurements
Validation environment: i7-12700H (20 logical CPUs), WSL2 + Docker,
python:3.11-slim, CPU wheel built frommainwithGGML_NATIVE=ON, Qwen2.5-0.5B-Instruct Q4_K_M, 256-token prompt then 64 greedy tokens,n_ctx=1024.Each run is a fresh container. Arms were interleaved, alternating order each rep, with throttling read from the container's
cpu.stataround each phase.Results are the median [min, max] in tokens/s.
docker run --cpus=2(6 reps per arm):n_threads=2, n_threads_batch=2docker run --cpuset-cpus=0-3(5 reps per arm, no quota):Under
--cpus=2the PR's generation default is one thread because the existing// 2heuristic halves the usable count.I left that heuristic alone to keep this change narrow; it is why the explicit 2-thread arm decodes faster in that row.
The machine had other background load during these runs, which accounts for the low minimums.
AI disclosure: this change, its tests and the benchmark harness were written with Claude Code (Anthropic), and the measurements above were run with it on my machine.
Generated with Claude Code