Repository navigation
fix(utils): honor the cgroup v2 CPU limit in cpu_count() - #6347
Conversation
chrikrah
left a comment
There was a problem hiding this comment.
@charan-rathore this fixes #6344. It needs a rebase first, signed off (git commit -s) so the DCO check passes: #5910 landed on master yesterday and adds its own python/kserve/test/test_cpu_count.py, so GitHub shows a conflict.
The #6344 repro under a real cgroup v2 quota, at head 31f8428 and base 5be27cf:
# head 31f8428, from python/kserve
$ systemd-run --user --scope -q -p CPUQuota=200% python -c 'from kserve.utils.utils import cpu_count; print(cpu_count())'
2
# base 5be27cf, from python/kserve
$ systemd-run --user --scope -q -p CPUQuota=200% python -c 'from kserve.utils.utils import cpu_count; print(cpu_count())'
32
$ python -m pytest test/test_cpu_count.py -q
8 passed # head
2 failed, 6 passed # utils.py from base, tests kept: the two v2 tests
$ python -m pytest test/test_model_server.py -q
5 passed # head and base
blocking: the add/add conflict on test_cpu_count.py. Joining both files on top of 7a8b7da gives 15 passed with test_model_server.py.
non-blocking: after that join, test_cpu_count_respects_affinity from #5910 reads the real cpu.max of the test process. Under CPUQuota=100% it fails with assert 1 == 2. Your fake_files fixture in that test would close it.
non-blocking: a quota under one CPU still returns the node count, and CPUQuota=50% gives 32. int(quota / period) is 0 there and cpu_count() skips it, as the v1 branch did.
@Jooho could you take the review once the rebase is pushed?
cpu_count() only read the cgroup v1 quota files, so under a cgroup v2 CPU limit it returned the node CPU count. Read cpu.max for the process cgroup (falling back to the cgroup mount root), treat max as unlimited, and keep the v1 fallback. Signed-off-by: Charan Rathore <[email protected]>
31f8428 to
a52ba5f
Compare
|
Rebased onto master (7a8b7da) and pushed. The add/add on test_cpu_count.py is joined: your two tests plus the eight v1/v2 tests, 10 in the file.
@Jooho ready for review. |
chrikrah
left a comment
There was a problem hiding this comment.
@charan-rathore approving at a52ba5f. The join, the sign-off and the fake_files move are in. Your box has no cgroup controllers, so here is the run that needs one: the joined file with test_cpu_count_respects_affinity back on monkeypatch fails at CPUQuota=100%, and your version passes.
# head a52ba5f, master 7a8b7da, from python/kserve; CPython 3.12, pytest 9.1.1, cgroup2fs, 32 CPUs
$ systemd-run --user --scope -q -p CPUQuota=100% python -m pytest test/test_cpu_count.py -q
10 passed # head
1 failed, 9 passed # head, affinity test back on monkeypatch
FAILED test/test_cpu_count.py::test_cpu_count_respects_affinity - assert 1 == 2
$ systemd-run --user --scope -q -p CPUQuota=200% python -m pytest test/test_cpu_count.py -q
10 passed # head
$ systemd-run --user --scope -q -p CPUQuota=200% python -c 'from kserve.utils.utils import cpu_count; print(cpu_count())'
2 # head
32 # master 7a8b7da
$ python -m pytest test/test_cpu_count.py -q
10 passed # head
2 failed, 8 passed # utils.py from 7a8b7da: the two v2 tests
$ python -m pytest test/test_model_server.py test/test_cpu_count.py -q
15 passed # head
# from the repo root, ruff 0.14.13 (the pre-commit pin)
$ ruff format --config ruff.toml --check python/kserve/kserve/utils/utils.py python/kserve/test/test_cpu_count.py
2 files already formatted
$ ruff check --config ruff.toml python/kserve/kserve/utils/utils.py python/kserve/test/test_cpu_count.py
All checks passed!
# not run: the e2e suites.
The single commit carries Signed-off-by and the DCO check is green.
@spolti, the Actions runs on a52ba5f are waiting for approval. Could you approve them and take a look? It builds on your #5910 merge in the same file.
Jooho
left a comment
There was a problem hiding this comment.
Thanks for your contribution!
I noticed you already mentioned the parent cgroup limitation in the PR description. Since Kubernetes now supports Pod-level CPU limits, I think this is worth addressing in the future.
I don't think it should block this PR, as the existing cgroup v1 implementation has the same limitation. Would it make sense to create a follow-up issue to track this?
/lgtm
|
Thanks for the update and for creating the follow-up issue! Everything looks good to me. /approve |
What this PR does / why we need it:
kserve.utils.utils.cpu_count()only read the cgroup v1 quota files (/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us). On a cgroup v2 host those files do not exist, so the open raised inside a bareexceptand the CPU limit was silently dropped. The function returned the node CPU count instead of the limit, andModelServersizes its default worker pool from it.This change reads the cgroup v2 limit first:
cpu.maxof the process cgroup (path from/proc/self/cgroup, line0::<path>), then/sys/fs/cgroup/cpu.maxas the fallback, which is the process cgroup when it runs in its own cgroup namespace.max <period>means no limit, so the host count and the affinity count stay in effect.int(quota / period)), and a result of zero or less is ignored as before.Which issue(s) this PR fixes:
Fixes #6344
Feature/Issue validation/testing:
python/kserve/test/test_cpu_count.pywith 8 tests, using mocked file reads: v2 quota from the mount root, v2 quota from the process cgroup path,maxas unlimited, a limit above the host count, v1 quota still applied, v1-1ignored, no cgroup files, and CPU affinity still respected.test_cpu_count.pyandtest_model_server.pytogether: 13 passed (Python 3.12).blackis clean on the two changed files.Not run: the rest of the python test suite,
make precommit, and a real cgroup v2 host or cluster. The tests use mocked file contents, so the parsing follows the kernelcpu.maxformat as documented but was not checked against a live host.Special notes for your reviewer:
The cgroup v2 path only checks the process's own cgroup, not its ancestors, which matches how the v1 branch reads a single file. Limits set only on a parent cgroup are not picked up.
Checklist:
Release note: