Skip to content

Fix multi-rank distributed-launch races and redundant LoRA merge in f… - #155

Open
sagarneeldubey wants to merge 1 commit into
moojink:mainfrom
sagarneeldubey:fix/distributed-launch-races
Open

sagarneeldubey wants to merge 1 commit into
moojink:mainfrom
sagarneeldubey:fix/distributed-launch-races

Conversation

@sagarneeldubey

@sagarneeldubey sagarneeldubey commented Jun 1, 2026

Copy link
Copy Markdown

Summary

vla-scripts/finetune.py has three multi-rank issues that I hit while fine-tuning OpenVLA with torchrun / accelerate launch on more than one GPU on a single node. All three are fixed this PR

  1. snapshot_download() race. All ranks call it concurrently, which produces one HF Hub API hit per rank and races the rank-0-only update_auto_map() rewrite that happens immediately after.

  2. from_pretrained(..., trust_remote_code=True) race. transformers copies the model's custom modules (processing_prismatic.py, modeling_prismatic.py, etc.) into a process-local transformers_modules/<commit>/ cache. With N ranks importing concurrently, one rank can observe a partial file write and raise:

AttributeError: module 'transformers_modules.<commit>.processing_prismatic' has no attribute 'PrismaticProcessor'

This reproduces fairly reliably at ≥4 ranks on a fresh HF cache.

  1. Redundant LoRA merge in save_training_checkpoint(). Every rank calls AutoModelForVision2Seq.from_pretrained(...) + PeftModel.from_pretrained(...) + merge_and_unload() on the 7B base model, but only rank 0 writes the result. The other ranks build merged_vla and immediately drop it on the floor. On 8 ranks that's 8× redundant 7B load + merge per checkpoint.

Changes (single commit, single file)

  • snapshot_download race: wrapped in distributed_state.main_process_first() so rank 0 populates the cache and runs update_auto_map before other ranks read it.
  • from_pretrained race: wrapped both AutoProcessor.from_pretrained and AutoModelForVision2Seq.from_pretrained in the same main_process_first() block so rank 0 finishes the dynamic-modules copy before peers import.
  • Redundant merge: moved from_pretrained + PeftModel.from_pretrained + merge_and_unload() inside the existing if distributed_state.is_main_process: branch in save_training_checkpoint. The preceding dist.barrier() already guarantees the adapter is on disk before this block runs.
  • The trailing dist.barrier() after the merge is intentionally kept outside the is_main_process guard so all ranks still reach it and stay in sync. Putting it inside the guard would deadlock.
  • Added comments at each call site explaining the failure mode so the next reader doesn't accidentally undo the guards.

Single-process / single-rank runs hit the same code paths but with main_process_first() as a no-op, so behavior is unchanged.

Test plan

Smoke-tested on a single AWS p4d.24xlarge (8× A100 40GB) with 8 ranks via torchrun --nproc_per_node=8, fine-tuning openvla/openvla-7b on aloha_scoop_x_into_bowl. The short run exercises:

  • snapshot_download + update_auto_map on a cold HF cache,
  • trust_remote_code dynamic-modules copy across all 8 ranks,
  • at least one checkpoint save, exercising the LoRA merge path.

Verified on the patched version:

  • All 8 ranks reach the training loop without the PrismaticProcessor AttributeError previously seen on main.
  • Training step loss decreases as expected.
  • Checkpoint directory contains the merged model + adapter; only rank 0 logs Saved merged model for Step ....
  • No deadlock at the post-merge dist.barrier(); all ranks proceed to the next training step.

Logs / screenshots

smoke test: 8 ranks past model load
8 ranks

smoke test: checkpoint save
Screenshot 2026-06-01 at 12 30 15 PM

Risk

Low. All changes are process-ordering / guard adjustments in vla-scripts/finetune.py. No change to model architecture, optimizer, scheduler, dataset pipeline, training loop semantics, or saved-checkpoint format. Single-rank users see no behavior change.

Notes for reviewers

  • Happy to split the redundant-merge change into a separate commit if you'd prefer two commits in this PR (one for the races, one for the merge cleanup). They're independent fixes; I bundled them because they're both small and touch only finetune.py.
  • The main_process_first() pattern is already used in accelerate examples for exactly this kind of cache-population race, so I went with that rather than a hand-rolled dist.barrier()-based gate.

…inetune.py

When launching with multiple ranks (torchrun/accelerate), two model-load
call sites race because each rank does the work independently:

1. snapshot_download(): all ranks call it concurrently, producing one HF
   Hub API hit per rank and racing the post-download update_auto_map()
   rewrite that runs only on the main process.

2. from_pretrained() with trust_remote_code=True: transformers copies the
   model's custom modules (e.g. processing_prismatic.py) into a
   process-local dynamic-modules cache. Concurrent ranks race on the same
   destination file and a rank can import a partial write, raising:
     AttributeError: module 'transformers_modules.<commit>.processing_prismatic'
       has no attribute 'PrismaticProcessor'

Both are fixed by wrapping the call sites in
accelerate.PartialState.main_process_first() so rank 0 populates the
shared caches before the other ranks read them. Single-process runs are
unaffected. No behavior change beyond ordering.

Additionally, the LoRA-merge step in save_training_checkpoint() had every
rank call AutoModelForVision2Seq.from_pretrained() + PeftModel.from_pretrained()
+ merge_and_unload() on the 7B base model, even though only rank 0 ever
wrote the merged result to disk. That meant N redundant 7B loads + merges
per checkpoint on every multi-rank job. The adapter is already on disk
before this block (the preceding dist.barrier() guarantees it), so the
load + merge can be moved inside the is_main_process branch. The trailing
dist.barrier() is intentionally kept outside the guard so all ranks reach
it and stay in sync; placing it inside would deadlock.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant