You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The worker pool sizes itself from sysconf(_SC_NPROCESSORS_ONLN), which reports
the host's online CPUs. It ignores both mechanisms an operator uses to
restrict a process: CPU affinity (taskset, docker --cpuset-cpus, systemd AllowedCPUs) and CFS quota (docker --cpus, Kubernetes CPU limits). A
container given one CPU still builds a pool sized for the whole machine.
ray_physical_core_count() is affected the same way — it counts unique
(package, core) pairs from sysfs, which is also a host-wide view.
Measurement
Pinning the process and counting the OS threads it actually creates, on a
4-physical / 8-logical host (/proc/<pid>/task, 2M-row group-by in a loop):
affinity
CPUs the process may use
OS threads created
unrestricted
8
4
taskset -c 0,1
2
4
taskset -c 0
1
4
The pool is identical in all three cases. Pinned to a single CPU it still starts
four threads, which then time-slice one core: context switches and scheduler
latency with no parallelism available to recover them.
For contrast, nproc gets this right because it uses sched_getaffinity:
Two reasons this is worth more than it was a week ago.
Pool width just became deliberate policy. #606 / perf(pool): size the auto pool by physical cores, not logical CPUs #609 changed the default
from logical CPUs to physical cores precisely because oversizing the pool
costs cycles for no throughput. Being wrong about the denominator undoes
that reasoning in exactly the deployments where it matters most.
It is invisible. An operator who sets --cpus=2 has every reason to
believe they constrained the process. Nothing in the banner or .sys.info
contradicts them: .sys.info's cores reports the host count too, so the
diagnostic agrees with the bug.
The failure mode is worst under CFS quota rather than cpuset: with --cpus=1.5 the threads are not pinned, they are throttled as a group, so N
runnable threads share 1.5 CPUs of budget and each one's tail latency grows with
N. That is the shape a latency-sensitive service in Kubernetes would hit, and it
is silent.
What a fix needs to read
Three sources, taking the minimum of whichever are present:
Affinity — sched_getaffinity(0, ...) + CPU_COUNT. Covers taskset, --cpuset-cpus, systemd AllowedCPUs. This alone fixes the measured case
above.
cgroup v2 quota — /sys/fs/cgroup/cpu.max, "$QUOTA $PERIOD" or "max $PERIOD"; effective CPUs = ceil(QUOTA / PERIOD). This host is cgroup2fs.
All three are Linux-specific and should fall back to the current behaviour
elsewhere, in the same shape as ray_physical_core_count's existing fallback.
The clamp belongs at the auto-size path in pool.c rather than inside ray_thread_count, since that function is also used to report the hardware fact
and some callers legitimately want the host view — deciding which callers want
which is part of the work.
Worth pairing with a way to see the answer: the REPL banner and .sys.info
should be able to show the pool width against the CPUs the process may actually
use, so the next person can tell these apart without /proc/<pid>/task.
Not urgent
No correctness impact, -c N is an effective workaround for anyone who knows to
use it, and the oversizing is bounded by the host's CPU count rather than
unbounded. Filing it because it is silent, because containerized deployment is
the normal case for a service, and because it makes the #606 default less
effective exactly where that default was aimed.
Related: #606 (physical-core default), #609 (the PR), #599 (the report that
started this).
Summary
The worker pool sizes itself from
sysconf(_SC_NPROCESSORS_ONLN), which reportsthe host's online CPUs. It ignores both mechanisms an operator uses to
restrict a process: CPU affinity (
taskset,docker --cpuset-cpus, systemdAllowedCPUs) and CFS quota (docker --cpus, Kubernetes CPU limits). Acontainer given one CPU still builds a pool sized for the whole machine.
src/core/platform.c:ray_physical_core_count()is affected the same way — it counts unique(package, core) pairs from sysfs, which is also a host-wide view.
Measurement
Pinning the process and counting the OS threads it actually creates, on a
4-physical / 8-logical host (
/proc/<pid>/task, 2M-row group-by in a loop):taskset -c 0,1taskset -c 0The pool is identical in all three cases. Pinned to a single CPU it still starts
four threads, which then time-slice one core: context switches and scheduler
latency with no parallelism available to recover them.
For contrast,
nprocgets this right because it usessched_getaffinity:Nothing in
src/callssched_getaffinitytoday.Why it matters now
Two reasons this is worth more than it was a week ago.
from logical CPUs to physical cores precisely because oversizing the pool
costs cycles for no throughput. Being wrong about the denominator undoes
that reasoning in exactly the deployments where it matters most.
--cpus=2has every reason tobelieve they constrained the process. Nothing in the banner or
.sys.infocontradicts them:
.sys.info'scoresreports the host count too, so thediagnostic agrees with the bug.
The failure mode is worst under CFS quota rather than cpuset: with
--cpus=1.5the threads are not pinned, they are throttled as a group, so Nrunnable threads share 1.5 CPUs of budget and each one's tail latency grows with
N. That is the shape a latency-sensitive service in Kubernetes would hit, and it
is silent.
What a fix needs to read
Three sources, taking the minimum of whichever are present:
sched_getaffinity(0, ...)+CPU_COUNT. Coverstaskset,--cpuset-cpus, systemdAllowedCPUs. This alone fixes the measured caseabove.
/sys/fs/cgroup/cpu.max,"$QUOTA $PERIOD"or"max $PERIOD"; effective CPUs = ceil(QUOTA / PERIOD). This host iscgroup2fs.cpu.cfs_quota_us/cpu.cfs_period_us, quota-1meaning unlimited.
All three are Linux-specific and should fall back to the current behaviour
elsewhere, in the same shape as
ray_physical_core_count's existing fallback.The clamp belongs at the auto-size path in
pool.crather than insideray_thread_count, since that function is also used to report the hardware factand some callers legitimately want the host view — deciding which callers want
which is part of the work.
Worth pairing with a way to see the answer: the REPL banner and
.sys.infoshould be able to show the pool width against the CPUs the process may actually
use, so the next person can tell these apart without
/proc/<pid>/task.Not urgent
No correctness impact,
-c Nis an effective workaround for anyone who knows touse it, and the oversizing is bounded by the host's CPU count rather than
unbounded. Filing it because it is silent, because containerized deployment is
the normal case for a service, and because it makes the #606 default less
effective exactly where that default was aimed.
Related: #606 (physical-core default), #609 (the PR), #599 (the report that
started this).