@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / c45f681932
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

[Bugfix][Core] Fall back when numactl --membind is blocked in constrained containers (#45438)

Signed-off-by: Ting Sun <suntcrick@gmail.com> Co-authored-by: Shengqi Chen <harry-chen@outlook.com>

Ting SUN committed Jun 17, 2026 at 00:49 UTC c45f681932838dda6365e1a4aa1f7092aca97bf9
2 files changed +82
tests/utils_/test_numa_utils.py
+46
@@ -464,3 +464,49 @@ def test_parallel_config_validates_numa_bind_nodes():
464 def test_parallel_config_rejects_invalid_numa_bind_cpus(cpuset):
465 with pytest.raises(ValueError, match="numa_bind_cpus"):
466 ParallelConfig(numa_bind_cpus=[cpuset])
467 +
468 +
469 +def _fake_numactl_run(rejected_args):
470 + """Fake ``numactl`` that fails when any of ``rejected_args`` is present."""
471 +
472 + def run(cmd, *args, **kwargs):
473 + arg_str = " ".join(cmd[1:-1])
474 + ok = not any(bad in arg_str for bad in rejected_args)
475 + return SimpleNamespace(returncode=0 if ok else 1)
476 +
477 + return run
478 +
479 +
480 +def test_configure_subprocess_numa_fallback(monkeypatch):
481 + import multiprocessing
482 +
483 + monkeypatch.setattr("shutil.which", lambda name: "/usr/bin/numactl")
484 + monkeypatch.setattr(numa_utils.envs, "VLLM_WORKER_MULTIPROC_METHOD", "spawn")
485 + node_config = _make_config(numa_bind=True, numa_bind_nodes=[0])
486 +
487 + monkeypatch.setattr(numa_utils.subprocess, "run", _fake_numactl_run([]))
488 + with numa_utils.configure_subprocess(node_config, local_rank=0):
489 + assert os.environ[numa_utils._NUMACTL_ARGS_ENV] == "--cpunodebind=0 --membind=0"
490 +
491 + membind_fails = _fake_numactl_run(["--membind="])
492 + monkeypatch.setattr(numa_utils.subprocess, "run", membind_fails)
493 + with numa_utils.configure_subprocess(node_config, local_rank=0):
494 + assert os.environ[numa_utils._NUMACTL_ARGS_ENV] == "--cpunodebind=0"
495 +
496 + cpu_config = _make_config(
497 + numa_bind=True,
498 + numa_bind_nodes=[0],
499 + numa_bind_cpus=["0-3"],
500 + )
501 + with numa_utils.configure_subprocess(cpu_config, local_rank=0):
502 + assert os.environ[numa_utils._NUMACTL_ARGS_ENV] == "--physcpubind=0-3"
503 +
504 + before = multiprocessing.spawn.get_executable()
505 + monkeypatch.setattr(
506 + numa_utils.subprocess,
507 + "run",
508 + _fake_numactl_run(["--cpunodebind=", "--membind="]),
509 + )
510 + with numa_utils.configure_subprocess(node_config, local_rank=0):
511 + assert multiprocessing.spawn.get_executable() == before
512 + assert numa_utils._NUMACTL_ARGS_ENV not in os.environ
vllm/utils/numa_utils.py
+36
@@ -473,6 +473,37 @@ def log_current_affinity_state(label: str) -> None:
473 _log_numactl_show(label)
474
475
476 +def _probe_numactl_args(numactl_args: str) -> bool:
477 + """Whether ``numactl <args> true`` succeeds in this (parent) environment."""
478 + try:
479 + result = subprocess.run(
480 + ["numactl", *numactl_args.split(), "true"],
481 + capture_output=True,
482 + timeout=10,
483 + )
484 + except (OSError, subprocess.SubprocessError):
485 + return False
486 + return result.returncode == 0
487 +
488 +
489 +def _resolve_numactl_args(numactl_args: str) -> str:
490 + """Drop ``--membind`` if the container rejects it, keeping CPU binding."""
491 + cpu_only = " ".join(
492 + t for t in numactl_args.split() if not t.startswith("--membind=")
493 + )
494 + for candidate in (numactl_args, cpu_only, ""):
495 + if _probe_numactl_args(candidate):
496 + if candidate != numactl_args:
497 + logger.warning(
498 + "numactl args %r rejected; falling back to %r. Add "
499 + "--cap-add SYS_NICE for full NUMA binding.",
500 + numactl_args,
501 + candidate or "no binding",
502 + )
503 + return candidate
504 + return ""
505 +
506 +
507 @contextmanager
508 def configure_subprocess(
509 vllm_config: "VllmConfig",
@@ -500,6 +531,11 @@ def configure_subprocess(
531 )
532
533 executable, debug_str = _get_numactl_executable()
534 + numactl_args = _resolve_numactl_args(numactl_args)
535 + if not numactl_args:
536 + # No NUMA binding possible here; launch without the wrapper.
537 + yield
538 + return
539 python_executable = os.fsdecode(multiprocessing.spawn.get_executable())
540 with (
541 _set_numa_wrapper_env(numactl_args, python_executable),