Skip to content

Commit b5054ef

Browse files
committed
fix(ci3): bind bench memory to its NUMA node to cut variance
Benches pin CPUs (docker --cpuset-cpus / taskset) but not memory. On the dedicated bench box (m6a.32xlarge, 4 NUMA nodes of 16 cores) the MSM/commitment phases of proving are memory-bandwidth-bound, so when their buffers land on a remote node they run ~50% slower while the compute-bound phases (trace gen, sumcheck) stay flat — the bimodal flip seen in bench history (e.g. avm_bulk wire commitments 2.1s vs 3.2s). Bind each bench's memory to its CPUs' NUMA node: --cpuset-mems on the docker path, numactl --membind on the taskset path (new cpu_numa_nodes helper maps a CPU list to node ids). Also disable automatic NUMA balancing during the bench run so the kernel doesn't migrate pages mid-measurement.
1 parent 6deae81 commit b5054ef

4 files changed

Lines changed: 42 additions & 3 deletions

File tree

ci3/bench_engine

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ function isolate_bench_cpus {
2121
sudo sh -c "echo 0 > /sys/devices/system/cpu/cpu$cpu/online" 2>/dev/null || true
2222
done
2323

24+
# Each bench's memory is bound to its CPUs' NUMA node (--cpuset-mems / numactl), so the
25+
# kernel's automatic page migration only adds jitter to the memory-bandwidth-bound
26+
# phases. Turn it off for the run.
27+
sudo sh -c "echo 0 > /proc/sys/kernel/numa_balancing" 2>/dev/null || true
28+
2429
export BENCH_CPU_COUNT=$physical
2530
echo "Benchmark CPU isolation: CPUs 0-$((physical - 1)) ($physical cores, hyperthreads off) for benchmarks."
2631
}
@@ -33,6 +38,7 @@ function unisolate_bench_cpus {
3338
for cpu in $(seq 1 $((total_cpus - 1))); do
3439
sudo sh -c "echo 1 > /sys/devices/system/cpu/cpu$cpu/online" 2>/dev/null || true
3540
done
41+
sudo sh -c "echo 1 > /proc/sys/kernel/numa_balancing" 2>/dev/null || true
3642
echo "All CPUs re-enabled. Online CPUs: $(nproc)"
3743
}
3844

ci3/docker_isolate

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@ if [ -n "${NAME:-}" ]; then
2626
docker rm -f $name &>/dev/null || true
2727
fi
2828

29-
# For pinning to given CPUs.
30-
[ -n "${CPU_LIST:-}" ] && cpuset_arg="--cpuset-cpus=$CPU_LIST"
29+
# For pinning to given CPUs, and binding memory to the same NUMA node(s) so that
30+
# memory-bandwidth-bound work (e.g. the MSM/commitment phases of proving) doesn't stream
31+
# from a remote node — the main source of run-to-run variance on the bench box.
32+
if [ -n "${CPU_LIST:-}" ]; then
33+
cpuset_arg="--cpuset-cpus=$CPU_LIST"
34+
mems=$(cpu_numa_nodes "$CPU_LIST")
35+
[ -n "$mems" ] && cpuset_mems_arg="--cpuset-mems=$mems"
36+
fi
3137

3238
# Env vars to inject into the container.
3339
arg_env_vars=()
@@ -60,6 +66,7 @@ cid=$(docker run -d \
6066
${name_arg:-} \
6167
${network_arg:-} \
6268
${cpuset_arg:-} \
69+
${cpuset_mems_arg:-} \
6370
--cpus=$CPUS \
6471
--memory=$MEM \
6572
--user $(id -u):$(id -g) \

ci3/exec_test

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,15 @@ if [ "${ISOLATE:-0}" -eq 1 ]; then
5252
docker_isolate "timeout -v $TIMEOUT bash -c '$test_cmd'" &> >(add_timestamps)
5353
else
5454
[ "${ONLY_TERM_PARENT:-0}" -eq 1 ] && fg_arg="--foreground"
55-
taskset -c $CPU_LIST timeout ${fg_arg:-} -v $TIMEOUT bash -c "$test_cmd" &> >(add_timestamps)
55+
# Bind memory to the CPU list's NUMA node(s), matching the docker path's --cpuset-mems,
56+
# so memory-bandwidth-bound work doesn't stream from a remote node. Needs numactl; if it
57+
# (or NUMA topology) is absent, fall back to plain taskset.
58+
mems=$(cpu_numa_nodes "$CPU_LIST")
59+
if [ -n "$mems" ] && command -v numactl &>/dev/null; then
60+
numactl --membind=$mems taskset -c $CPU_LIST timeout ${fg_arg:-} -v $TIMEOUT bash -c "$test_cmd" &> >(add_timestamps)
61+
else
62+
taskset -c $CPU_LIST timeout ${fg_arg:-} -v $TIMEOUT bash -c "$test_cmd" &> >(add_timestamps)
63+
fi
5664
fi
5765
code=$?
5866
set -e

ci3/source_stdlib

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,24 @@ function get_num_cpus_max {
4646
echo $jobs
4747
}
4848

49+
# Given a comma-separated CPU list, print the comma-separated set of NUMA node ids those
50+
# CPUs belong to (sorted, unique). Empty if the box exposes no NUMA topology. Used to bind
51+
# a pinned bench's memory to the same node(s) as its cores (--cpuset-mems / numactl), so
52+
# memory-bandwidth-bound work doesn't stream from a remote node.
53+
function cpu_numa_nodes {
54+
local cpu_list="${1:-}"
55+
[ -z "$cpu_list" ] && return
56+
local cpu d nodes=()
57+
for cpu in ${cpu_list//,/ }; do
58+
for d in /sys/devices/system/cpu/cpu$cpu/node*; do
59+
[ -e "$d" ] || continue
60+
nodes+=("${d##*/node}")
61+
done
62+
done
63+
[ "${#nodes[@]}" -eq 0 ] && return
64+
printf '%s\n' "${nodes[@]}" | sort -un | paste -sd,
65+
}
66+
4967
function aws_get_meta_data {
5068
curl -fs -H "X-aws-ec2-metadata-token: $AWS_TOKEN" http://169.254.169.254/latest/meta-data/$1 || true
5169
}

0 commit comments

Comments
 (0)