fix(fsdp): set ACCELERATE_USE_FSDP so device_map and cpu_ram_efficient_loading take effect - #9980
Open
cben484 wants to merge 1 commit into
Open
fix(fsdp): set ACCELERATE_USE_FSDP so device_map and cpu_ram_efficient_loading take effect#9980cben484 wants to merge 1 commit into
cben484 wants to merge 1 commit into
Conversation
…t_loading take effect
`_init_fsdp` only exported FSDP_VERSION, never ACCELERATE_USE_FSDP. That variable
is read in two places that both silently misbehave without it:
- swift's own `get_default_device_map()` falls through to 'npu:{rank}'/'cuda:{rank}',
so every rank materializes the full weights before FSDP2 can shard them.
- transformers' `is_fsdp_enabled()` gates the rank0-only load path, so the
`cpu_ram_efficient_loading: true` shipped in swift/config/fsdp2.json has no effect
and all ranks each build a full copy in CPU RAM.
Setting it in `_init_fsdp` keeps the fix scoped to the FSDP branch (the function
early-returns when --fsdp is unset) and runs before TrainingArguments is built.
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.
PR type
PR information
Problem
When training with
--fsdp fsdp2, every rank loads the full model weights onto its own device, causing two silent failures:from_pretrainedOOMs during loading and never reaches the FSDP2 sharding stage.cpu_ram_efficient_loadingsilently has no effect:swift/config/fsdp2.jsonships with"cpu_ram_efficient_loading": true, intended to make rank 0 load the weights while other ranks init on meta device. Because the switch is never wired up, all ranks each materialize a full copy of the weights in CPU RAM — N cards means N × full-model host memory (measured ~136G RSS per process on 16 cards, exhausting host memory and triggering the global OOM killer).Small models (e.g., the 4B model in the official GRPO example) don't OOM, which is why this has gone unnoticed so far.
Root Cause
The
ACCELERATE_USE_FSDPenvironment variable is read in two places:get_default_device_map()(swift/model/utils.py):is_fsdp_enabled()(the gate for the FSDP rank0-only / meta-device init load path)The variable is normally set by accelerate when the
Acceleratoris created, but swift loads the model before the Trainer/Accelerator is created, so the variable doesn't exist yet;_init_fsdp()only setsFSDP_VERSION. As a result, both readers conclude FSDP is disabled:get_default_device_map()returnscuda:{local_rank}/npu:{local_rank}→ every rank loads the full weightscpu_ram_efficient_loadinghas no effectZeRO-3 is unaffected because
is_deepspeed_zero3_enabled()has its own dedicated branch — which is also why this bug only shows up on the FSDP path.Fix
Set
os.environ['ACCELERATE_USE_FSDP'] = 'true'in_init_fsdp():_init_fsdp()returns early when--fsdpis unset, so non-FSDP users are unaffectedget_default_device_map()(no more device_map) and transformers'is_fsdp_enabled()(rank0-only load path)Reproduce
Any model whose weights exceed one card's memory reproduces this (GPU/NPU alike):
NPROC_PER_NODE=8 swift sft \ --model Qwen/Qwen2.5-72B-Instruct \ --fsdp fsdp2 \ --tuner_type lora \ --dataset <any> --max_steps 2Two observations:
model_kwargs: {'device_map': 'cuda:0', ...}(expected:None)Loading weights: 100%full loading, followed by an OOM during loadingSmall models are also affected (no OOM, but wrong behavior): N full-load progress bars + each rank's CPU RSS ≈ full model size.
Scope
--fsdpusers: device_map behavior fixed (no more full loading) +cpu_ram_efficient_loadingpath wired upExperiment results
Measured on Ascend 910B (64G HBM × 8), model
Qwen/Qwen3.5-35B-A3B(MoE, bf16 ≈ 67G),swift rlhfDPO + LoRA + FSDP2:model_kwargs{'device_map': 'npu:0'}{'device_map': None}fully_shard; no OOMNote on the CPU side (transformers 5.12.1):
from_pretraineddoes still have an FSDP non-rank0 branch (modeling_utils.py,_move_missing_keys, gated byis_fsdp_enabled() and not is_local_dist_rank_0()), but it materializes every parameter withtorch.zeros_like(param, device='cpu')— i.e. the full model in host RAM per rank (~136G peak incl. prepare-time copies for a 35B bf16 model), with real weights broadcast from rank0 by accelerate afterwards. It is not a zero-memory load, and it is not keyed oncpu_ram_efficient_loading(that identifier no longer appears inmodeling_utils.py). So even with this PR, per-rank CPU usage stays at ~model-size: 8 cards × ~136G ≈ 1.1T is fine on a 2T host, 16 cards gets tight. A follow-up PR will make non-rank0 ranks load on the meta device (validated on 16× Ascend 910B: host RAM peak 187G total instead of ~2.2T, identical training metrics vs full loading), letting accelerate'scpu_ram_efficient_loadingsync be the only materialization path.Checklist
_init_fsdp()early return)