Skip to content

[WIP]: feat(offload): run model_cpu_offload on non-CUDA accelerators (MUSA) - #90

Open
cennn wants to merge 5 commits into
mainfrom
feat/musa-cpu-offload
Open

cennn wants to merge 5 commits into
mainfrom
feat/musa-cpu-offload

Conversation

@cennn

@cennn cennn commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

🗂️ PR Category

  • ✨ New Feature
  • 🚀 Optimization (performance, memory, etc.)
  • 💥 Breaking Change
  • 🐛 Bug Fix
  • 🛠️ Development / Refactoring
  • 📚 Documentation
  • 🧹 Chore (Dependencies, CI/CD, Configuration, etc.)
  • 🧪 Testing

📝 Description

model_cpu_offload hard-coded CUDA (torch.cuda streams/events, .to("cuda"), .is_cuda, cudaHostRegister, torch.cuda.nvtx), so it could not run on other PyTorch accelerator backends such as Moore Threads MUSA. Offload matters most there: large models that do not fit a card's memory are exactly what we want to run on these devices.

1. Offload runs on the current accelerator, CUDA unchanged.
Streams, events, synchronize, H2D copies and the offload hook's .to(<device>) detection go through torch.get_device_module() / torch.accelerator.current_accelerator() (on CUDA these are torch.cuda / cuda, so CUDA behaviour is identical). Pinning picks musaHostRegister or cudaHostRegister by accelerator type. Two backend quirks are handled: NVTX ranges are skipped when torch is built without NVTX, and fix_graph_device_placement also rewrites orig_to (torch_musa wraps Tensor.to, so Dynamo records call_method orig_to; without this an in-graph .to(device=x.device) stayed on CPU and a Triton kernel received a CPU pointer).

2. Fix: float module attributes broke offload compilation (CUDA too).
Dynamo lifts unspecialized Python floats (a module's eps, scale, ...) into 0-dim CPU tensor inputs and reads them back with .item() where a Python float is required (custom-op float args, Triton launches). Offload moved every tensor input to the device, so the fake copy lost its constant and the piecewise fake run raised DataDependentOutputException. 0-dim CPU tensors now stay on the host (offload fake args, graph device placement, executor input preparation). New test_cpu_offload_float_attribute fails before and passes after.

3. Real-model result on 8× S5000. A 400B MoE video DiT (W8A8), 540p 10 s i2v, 4 steps, gpu_resident_weight_ratio=0.0:

diffusion / request peak device memory / card host memory
no offload 20.42 s 73.8 GB 100 GB
offload, ratio 0.0 20.65 s (+1.1%) 21.7 GB 543 GB

Outputs match the non-offload run by eye:

Small model on S5000 (1 and 2 ranks, BASE / COST_EFFECTIVE / HEURISTIC, ratio 0 / 0.5): weights stay on CPU and are pinned, relative error vs eager 4.2e-3 (bf16); with 2 ranks the pinned per-rank buffer takes 1.24 ms/call vs 4.23 ms unpinned.

Other

  • Caveat for model code: offload traces the first call with CPU stand-ins for its inputs, so any x.device.type == "cuda" / x.is_cuda branch in the traced model takes its CPU path, silently and for good. On the model above this dropped its Triton fused kernels and cost ~80% per step until the model accounted for it.
  • Not touched (off the offload path or already guarded by is_available()): symm_mem/, cuda_graph_mgr.py, profiling/runtime_estimator.py, EVT fusion.

cennn added 5 commits October 16, 2026 16:35
The offload path hard-coded torch.cuda, "cuda", .is_cuda and libcudart, so it failed on MUSA unless
torchada remapped torch.cuda. Use the current accelerator's device module instead; on CUDA this is
torch.cuda, so behaviour there is unchanged.

- utils/device.py: accelerator_module() / accelerator_type().
- offload executor, schedulers, profiler: streams, events, synchronize and H2D copies go through them.
- _api.py: detect .to(<accelerator>) by device type rather than .is_cuda.
- magi_backend.py: fix_graph_device_placement and the offload fake args target the accelerator.
- cuda/cudart.py: pin_memory_in_place falls back to libmusart musaHostRegister when libcudart is absent.
- utils/nvtx.py: skip NVTX ranges when torch is built without NVTX.

8x S5000 (torch 2.9.0, no torchada), toy 4-layer block, ratio 0/0.5, BASE/COST_EFFECTIVE/HEURISTIC:
weights stay on CPU and pinned, rel err vs eager 4.2e-3 (bf16); 2 ranks: giant buffers pinned,
1.24 ms/call vs 4.23 ms unpinned. H200: test_cpu_offload, test_ep_shared_memory, test_fix_to_cpu_in_graph,
test_shm_memory_peak, cache/test_cache_topology_isolation and api_tests pass (140).
# Conflicts:
#	magi_compiler/magi_backend/magi_backend.py
Dynamo lifts unspecialized Python floats (e.g. a module's eps) into 0-dim CPU tensor inputs and reads
them back with .item() where a Python float is required (custom op float args, Triton launches). The
offload path moved every tensor input to the accelerator: the fake copy loses its constant, so the
piecewise fake run raised DataDependentOutputException, and at runtime the scalar was a device tensor.

Leave 0-dim CPU tensors alone in the offload fake args, fix_graph_device_placement and the executor's
input preparation; device kernels accept CPU scalars as before.

test_cpu_offload_float_attribute fails without the fix (DataDependentOutputException) and passes with it
on H200; the H200 offload/cache/api suites still pass (147). Found on MAGI-2 400B, where every RMSNorm
eps and mHC scale hit this once the Triton paths were traced.
torch_musa replaces Tensor.to with a wrapper that calls self.orig_to, so on MUSA Dynamo records
call_method orig_to. fix_graph_device_placement only matched 'to', so an in-graph
.to(device=x.device) traced with the offload's CPU inputs stayed device_put(..., cpu), and a Triton
kernel then received a CPU pointer at runtime.

MAGI-2 400B W8A8 on 8x S5000, gpu_resident_weight_ratio=0.0: compiles and runs, 5.15 s/step vs
5.08 s/step without offload, peak device memory 21.7 GB/card vs 73.8 GB.
pin_memory_in_place tried libcudart first and used cudaHostRegister whenever it loaded, so a MUSA host
that also ships libcudart called the CUDA runtime and raised. Select musaHostRegister or
cudaHostRegister from accelerator_type() instead.

Also rename the offload executor/scheduler/profiler attributes rt -> device_module and
device -> device_type (it holds the type string, not a torch.device).
@cennn cennn added the ci:run Trigger CI integration tests label Oct 16, 2026
@github-actions github-actions Bot removed the ci:run Trigger CI integration tests label Oct 16, 2026
@cennn cennn changed the title feat(offload): run model_cpu_offload on non-CUDA accelerators (MUSA) [WIP]: feat(offload): run model_cpu_offload on non-CUDA accelerators (MUSA) Oct 16, 2026

This branch has not been deployed

No deployments
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