Repository navigation
perf(trainer): NUMA-bind ranks for optimizer-state and activation offload - #4426
Open
faresobeid wants to merge 3 commits into
Open
faresobeid wants to merge 3 commits into
faresobeid wants to merge 3 commits into
Conversation
…load Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
faresobeid
marked this pull request as ready for review
November 8, 2026 15:52
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8a3d4e2. Configure here.
…L index bind_process_to_gpu_numa_node looked the GPU up in NVML by its CUDA index, which NVML reads as a physical index: with CUDA_VISIBLE_DEVICES remapping (colocated RL), ranks bound to another GPU's socket. It now looks the GPU up by its UUID. 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.

Ranks are NUMA-bound only with
model.offload = "full". With the defaultmodel.offload = "optimizer"(and with activation offloading) they are not, so each rank's pinned buffers land on whichever socket its thread happened to run on when it first touched them. On an 8xH200 node (2 sockets, GPUs 0-3 on node 0, 4-7 on node 1), one run put three of eight ranks' optimizer state on the remote socket, and a run with activation offloading put all eight there. Copies to and from the remote socket are slower, and the slow ranks hold back the step for every rank.Change
setup_cpu_offload(config.model)replacessetup_full_cpu_optimizer_offload(). It binds the rank to its GPU's NUMA node (the existingbind_process_to_gpu_numa_node) whenevermodel.offload != "none"or activation offloading is on. It sets the CPU optimizer thread count only for full offload, as before.bind_process_to_gpu_numa_nodenow finds the rank's GPU in NVML by UUID (torch.cuda.get_device_properties(device).uuid) instead of by its CUDA index. NVML numbers every GPU on the node, while CUDA numbers only the visible ones, so underCUDA_VISIBLE_DEVICESremapping (colocated RL) the old lookup bound ranks to another GPU's socket.Copy bandwidth (pinned host memory, 8xH200, all 8 GPUs at once, GiB/s per GPU)
Trainer (Qwen3-30B-A3B, 1 node 8xH200, FSDP 8 / EP 8, 16k seq, full AC, compile, fake data, mean of steps 6-20)
offload = "optimizer", no activation offloadoffload = "optimizer"+ activation offloading)offload = "optimizer", no activation offload)Peak GPU memory and host RSS are unchanged (63.2 GiB, 41-45 GiB per rank). With the binding, each rank's offloaded state sits on its GPU's socket (checked in
/proc/self/numa_maps); without it, the socket was effectively random per rank. Loss and grad norm per step stay within the run-to-run noise of main; the change only moves where the host memory lives.🤖 Generated with Claude Code
Note
Low Risk
Startup-only affinity and thread tuning for training; no change to model math, checkpoints, or APIs beyond when binding runs.
Overview
NUMA binding for all host offload paths, not only
offload = "full".setup_cpu_offload(config.model)replacessetup_full_cpu_optimizer_offload()and is always invoked from both trainer entrypoints; it binds the process to the GPU’s NUMA node whenmodel.offload != "none"or activation offloading is enabled, and still configures CPU optimizer threads only for full offload.GPU lookup for binding now goes through NVML by GPU UUID instead of CUDA device index, so ranks stay pinned to the correct socket when
CUDA_VISIBLE_DEVICESremaps GPUs (e.g. colocated RL).Log output for a successful bind includes the PCI sysfs id alongside the CUDA device id.
Reviewed by Cursor Bugbot for commit 16dc340. Bugbot is set up for automated code reviews on this repo. Configure here.