Skip to content

Commit c839702

Browse files
authored
Migrate XME blocking patches into ME-FL overrides (#70)
- Add ME-FL override hooks and registry entries for MTP loss reduction, DeepEP A2A, and async checkpointing - Add KunLunXin plugin implementations for M13, DeepEP BufferV2/sync guards, and M11 filesystem async checkpointing - Stage checkpoint tensors before persistent worker queue serialization to avoid XPU tensor sharing failures - Add default passthrough plugins and route tests for default/KunLunXin override selection Validation: - PYTHONPATH=. pytest tests/unit_tests/dist_checkpointing/test_async_utils_shutdown.py -q - PYTHONPATH=. python -m compileall ... - Real XPU async checkpoint smoke with MG_FL_PREFER=kunlunxin # Description PR Description ## Type of change - [ ] New feature (non-breaking change which adds functionality) - [ ] Infra/Build change (changes to CI/CD workflows or build scripts) - [ ] Code refactoring - [ ] Documentation change - [ ] Bug fix - [ ] Breaking change ## Changes - Content 1 - Content 2 - Content 3 - Content 4 ## Checklist - [ ] I have read and followed the contributing guidelines - [ ] The functionality is complete - [ ] I have commented my code, particularly in coverage report uploading steps - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added/updated tests that prove my feature works - [ ] New and existing unit tests pass locally
1 parent e217416 commit c839702

18 files changed

Lines changed: 620 additions & 10 deletions

File tree

megatron/core/dist_checkpointing/strategies/filesystem_async.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
_results_queue = None
5050

5151
# FlagScale Begin
52+
from megatron.plugin.decorators import overridable
5253
from megatron.plugin.platform import get_platform
5354

5455
cur_platform = get_platform()
@@ -117,6 +118,7 @@ def __init__(
117118
self.results_queue: Optional[mp.Queue] = None
118119
self.separation_hint = separation_hint
119120

121+
@overridable # FlagScale Add
120122
def prepare_write_data(self, plan: SavePlan, planner: SavePlanner) -> None:
121123
"""
122124
First stage of async saving. Copy data to CPU and plan the local saving.
@@ -230,6 +232,7 @@ def get_save_function_and_args(self) -> Tuple[Optional[Callable], Optional[Calla
230232
)
231233

232234
@staticmethod
235+
@overridable # FlagScale Add
233236
def preload_tensors(write_buckets: List[WriteBucket], non_blocking=True) -> List[WriteBucket]:
234237
"""
235238
Preloads tensors in `state_dict` to host memory via CPU memory.
@@ -366,6 +369,7 @@ def check_local_output(local_results_or_exc, local_worker_idx):
366369
logger.debug(f"{w_end}, rank: {rank}, write(sync,threads): {w_end - w_start}")
367370

368371
@staticmethod
372+
@overridable # FlagScale Add
369373
@_disable_gc()
370374
def write_preloaded_data(
371375
transform_list: List[_StorageWriterTransforms],

megatron/core/tensor_parallel/random.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
get_tensor_model_parallel_rank,
2424
)
2525
from megatron.core.utils import is_te_min_version, safely_set_viewless_tensor_data
26+
from megatron.plugin.decorators import overridable # FlagScale Add
2627

2728
# ---------------------------------------------------------------------------
2829
# C++ extension: zero-copy storage sharing for CheckpointWithoutOutput
@@ -220,6 +221,7 @@ def get_data_parallel_rng_tracker_name():
220221
return _DATA_PARALLEL_RNG_TRACKER_NAME
221222

222223

224+
@overridable # FlagScale Add
223225
class CudaRNGStatesTracker:
224226
"""Tracker for the cuda RNG states.
225227

megatron/core/transformer/moe/fused_a2a.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
from megatron.core.utils import internal_api
77

8+
########## FlagScale Begin ##########
9+
from megatron.plugin.decorators import overridable
10+
########## FlagScale End ##########
11+
812
try:
913
from deep_ep import Buffer
1014
from deep_ep.utils import EventHandle, EventOverlap
@@ -30,6 +34,7 @@ def get_hidden_bytes(x: torch.Tensor) -> int:
3034
return x.size(1) * max(x.element_size(), 2)
3135

3236

37+
@overridable # FlagScale Add
3338
def get_buffer(group: torch.distributed.ProcessGroup, hidden_bytes: int):
3439
"""Get or create a buffer for all-to-all communication.
3540
@@ -66,6 +71,7 @@ def get_buffer(group: torch.distributed.ProcessGroup, hidden_bytes: int):
6671
return _buffer
6772

6873

74+
@overridable # FlagScale Add
6975
class FusedDispatch(torch.autograd.Function):
7076
"""Fused dispatch operation for MoE routing combining computation and communication."""
7177

@@ -160,6 +166,7 @@ def backward(
160166
return grad_x, None, grad_token_probs, None, None, None, None
161167

162168

169+
@overridable # FlagScale Add
163170
class FusedCombine(torch.autograd.Function):
164171
"""Fused combine operation for MoE output combining computation and communication."""
165172

@@ -209,6 +216,7 @@ def backward(ctx, grad_output, previous_event=None):
209216

210217
if HAVE_DEEP_EP:
211218

219+
@overridable # FlagScale Add
212220
def fused_dispatch(
213221
x,
214222
token_indices,
@@ -241,6 +249,7 @@ def fused_dispatch(
241249
allocate_on_comm_stream,
242250
)
243251

252+
@overridable # FlagScale Add
244253
def fused_combine(x, group, handle, async_finish=False, allocate_on_comm_stream=False):
245254
"""Perform fused combine operation if deep_ep is available.
246255

megatron/core/transformer/multi_token_prediction.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
)
4040

4141
########## FlagScale Begin ##########
42+
from megatron.plugin.decorators import overridable
4243
from megatron.plugin.platform import get_platform
4344

4445
cur_platform = get_platform()
@@ -376,6 +377,7 @@ def clean_loss_in_tracker():
376377
tracker["reduce_group"] = None
377378
tracker["avg_group"] = None
378379

380+
@overridable # FlagScale Add
379381
def reduce_loss_in_tracker():
380382
"""Collect and reduce the mtp losses across ranks."""
381383
tracker = MTPLossLoggingHelper.tracker

megatron/plugin/decorators.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,33 @@
6060

6161

6262
def _get_preferred_vendor() -> Optional[str]:
63-
"""Get the preferred vendor from MG_FL_PREFER environment variable.
63+
"""Get the preferred override vendor.
64+
65+
``MG_FL_PREFER`` remains the explicit override selector. When it is unset,
66+
follow the selected ME-FL platform so XPU-only patches activate with the
67+
same runtime trigger as XME.
6468
6569
Returns:
66-
The vendor name string (lowercased), or None if not set.
70+
Optional[str]: The vendor name string (lowercased) from MG_FL_PREFER,
71+
or inferred from the selected platform (e.g. 'kunlunxin'), or None.
6772
"""
6873
vendor = os.environ.get("MG_FL_PREFER")
6974
if vendor is not None:
7075
vendor = vendor.strip().lower()
7176
if vendor == "":
7277
return None
73-
return vendor
78+
return vendor
79+
80+
try:
81+
from megatron.plugin.platform import platform_manager
82+
83+
platform = platform_manager.cur_platform
84+
if platform is not None:
85+
return platform.device_name()
86+
except Exception as e:
87+
logger.debug(f"Failed to infer override vendor from platform: {e}")
88+
89+
return None
7490

7591

7692
def register_override_method(method_key: str, implementation: Callable,
@@ -166,9 +182,14 @@ def get_override_method(method_key: str) -> Optional[Callable]:
166182
preferred = _get_preferred_vendor()
167183

168184
# 1. Preferred vendor
169-
if preferred is not None and preferred in vendor_map:
170-
logger.debug(f"Using vendor '{preferred}' for {method_key}")
171-
return vendor_map[preferred]
185+
if preferred is not None:
186+
if preferred in vendor_map:
187+
logger.debug(f"Using vendor '{preferred}' for {method_key}")
188+
return vendor_map[preferred]
189+
if method_key in _lazy_registry and preferred in _lazy_registry[method_key]:
190+
lazy_impl = _resolve_lazy_impl(method_key)
191+
if lazy_impl is not None:
192+
return lazy_impl
172193

173194
# 2. Default vendor
174195
if _DEFAULT_VENDOR in vendor_map:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""KunLunXin vendor plugin implementations."""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""KunLunXin dist checkpointing plugin implementations."""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""KunLunXin dist checkpointing strategy plugins."""
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
"""KunLunXin plugin implementations for filesystem async checkpointing."""
2+
3+
import importlib.util
4+
import inspect
5+
import logging
6+
import os
7+
import warnings
8+
from time import time
9+
from typing import List, Tuple, Union
10+
11+
import torch
12+
from torch.distributed.checkpoint.filesystem import DEFAULT_SUFFIX, _StoragePrefix, _write_item
13+
from torch.distributed.checkpoint.planner import SavePlan, SavePlanner, WriteItemType
14+
from megatron.core.dist_checkpointing.strategies.filesystem_async import (
15+
FileSystemWriterAsync,
16+
WriteBucket,
17+
_process_memory,
18+
_split_by_separation_hint,
19+
_split_by_size_and_type,
20+
get_write_results_queue,
21+
logger,
22+
)
23+
24+
25+
def _bridge_available() -> bool:
26+
"""Check if megatron.bridge is available."""
27+
return importlib.util.find_spec("megatron.bridge") is not None
28+
29+
30+
def prepare_write_data(self, plan: SavePlan, planner: SavePlanner) -> None:
31+
"""Prepare async checkpoint write buckets for KunLunXin bridge path.
32+
33+
Args:
34+
plan: Save plan from PyTorch distributed planner.
35+
planner: Save planner for resolving bytes and tensor data.
36+
"""
37+
if not _bridge_available():
38+
return FileSystemWriterAsync.prepare_write_data.__wrapped__(self, plan, planner)
39+
40+
storage_plan: _StoragePrefix = plan.storage_data
41+
start = time()
42+
logger.debug(f"thread_count: {self.thread_count}, time: {start}")
43+
if self.separation_hint:
44+
assert self.thread_count > 1, "thread_count must be at least 2 if separation_hint is provided"
45+
bins = self.thread_count // 2 if self.separation_hint is not None else self.thread_count
46+
item_buckets = _split_by_size_and_type(bins, plan.items)
47+
logger.debug(f"bucket_prep, time: {time() - start}")
48+
49+
start = time()
50+
file_count = 0
51+
52+
def gen_file(prefix=""):
53+
"""Generate a unique checkpoint file name with optional prefix."""
54+
nonlocal file_count
55+
file_name = f"{prefix}{storage_plan.prefix}{file_count}{DEFAULT_SUFFIX}"
56+
file_count += 1
57+
return file_name
58+
59+
def _clone_or_dequantize_if_needed(ten: torch.Tensor):
60+
"""Detach and dequantize GPU tensors if needed."""
61+
ten = ten.detach()
62+
if ten.device.type != "cpu" and ten.device.type == "cuda" and "dequantize" in type(ten).__dict__:
63+
ten = ten.dequantize()
64+
return ten
65+
66+
self.write_buckets = []
67+
for group_name, group_buckets in _split_by_separation_hint(
68+
item_buckets, self.separation_hint
69+
).items():
70+
for bucket in group_buckets:
71+
bytes_data = [
72+
(item, planner.resolve_data(item))
73+
for item in bucket
74+
if item.type == WriteItemType.BYTE_IO
75+
]
76+
tensor_data = [
77+
(item, _clone_or_dequantize_if_needed(planner.resolve_data(item)))
78+
for item in bucket
79+
if item.type != WriteItemType.BYTE_IO
80+
]
81+
if len(bytes_data) > 0 or len(tensor_data) > 0:
82+
file_name = gen_file(prefix=group_name)
83+
self.write_buckets.append(
84+
(
85+
os.path.join(self.checkpoint_dir, file_name),
86+
file_name,
87+
(bytes_data, tensor_data),
88+
)
89+
)
90+
91+
if len(self.write_buckets) > 0:
92+
assert len(self.write_buckets) <= self.thread_count, (
93+
len(self.write_buckets),
94+
self.thread_count,
95+
)
96+
self.results_queue = get_write_results_queue()
97+
else:
98+
self.results_queue = None
99+
end = time()
100+
logger.debug(f"D2H and push, time: {end - start}")
101+
102+
103+
def preload_tensors_kunlunxin(
104+
write_buckets: List[WriteBucket], non_blocking=True
105+
) -> List[WriteBucket]:
106+
"""Preload tensors with blocking D2H copies for KunLunXin.
107+
108+
Args:
109+
write_buckets: List of write buckets to preload.
110+
non_blocking: Whether to use non-blocking D2H (forced to False).
111+
112+
Returns:
113+
List of write buckets with tensors moved to CPU.
114+
"""
115+
if _bridge_available():
116+
return write_buckets
117+
118+
if non_blocking:
119+
warnings.warn(
120+
"D2H might fail when `non_blocking` is True, force it to be False for KunLunXin."
121+
)
122+
return FileSystemWriterAsync.preload_tensors.__wrapped__(write_buckets, non_blocking=False)
123+
124+
125+
def write_preloaded_data(
126+
transform_list,
127+
local_proc_idx: int,
128+
write_bucket: WriteBucket,
129+
results_queue,
130+
count_queue,
131+
use_fsync: bool,
132+
**kwargs,
133+
) -> Union[Tuple[int, Exception], None]:
134+
"""Write preloaded checkpoint data for KunLunXin bridge path.
135+
136+
Args:
137+
transform_list: Storage writer transforms.
138+
local_proc_idx: Index of the worker performing writing.
139+
write_bucket: Data to write to storage.
140+
results_queue: Queue to return write results (may be None for main worker).
141+
count_queue: Queue to signal task completion (may be None).
142+
use_fsync: If True, call os.fsync at end of saving.
143+
144+
Returns:
145+
Tuple of (proc_idx, results) or (proc_idx, exception), or None.
146+
"""
147+
if not _bridge_available():
148+
return FileSystemWriterAsync.write_preloaded_data.__wrapped__(
149+
transform_list,
150+
local_proc_idx,
151+
write_bucket,
152+
results_queue,
153+
count_queue,
154+
use_fsync,
155+
**kwargs,
156+
)
157+
158+
logger = logging.getLogger(__name__)
159+
logger.debug(f"{local_proc_idx} started")
160+
mem_before = _process_memory()
161+
use_msc = kwargs.get("use_msc", False)
162+
163+
local_results = []
164+
try:
165+
file_name, storage_key, (bytes_data, tensor_data) = write_bucket
166+
extra_kwargs = {}
167+
if "serialization_format" in inspect.signature(_write_item).parameters:
168+
from torch.distributed.checkpoint.filesystem import SerializationFormat
169+
170+
extra_kwargs["serialization_format"] = SerializationFormat.TORCH_SAVE
171+
if use_msc:
172+
import multistorageclient as msc
173+
174+
open_file = msc.open
175+
else:
176+
open_file = open
177+
with open_file(file_name, "wb") as stream:
178+
for write_item, data in bytes_data:
179+
local_results.append(
180+
_write_item(
181+
*transform_list, stream, data, write_item, storage_key, **extra_kwargs
182+
)
183+
)
184+
185+
for write_item, tensor in tensor_data:
186+
assert tensor.is_cpu
187+
if tensor.dtype != torch.bfloat16:
188+
tensor = tensor.bfloat16()
189+
if tensor.untyped_storage().size() != tensor.numel() * tensor.itemsize:
190+
tensor = tensor.clone()
191+
local_results.append(
192+
_write_item(
193+
*transform_list, stream, tensor, write_item, storage_key, **extra_kwargs
194+
)
195+
)
196+
197+
if use_fsync:
198+
if use_msc:
199+
stream.fsync()
200+
else:
201+
os.fsync(stream.fileno())
202+
local_output = (local_proc_idx, local_results)
203+
except Exception as e:
204+
logger.debug(f"{local_proc_idx} failed")
205+
local_output = (local_proc_idx, e)
206+
if results_queue is not None:
207+
results_queue.put(local_output)
208+
if count_queue is not None:
209+
count_queue.get()
210+
count_queue.task_done()
211+
212+
mem_after = _process_memory()
213+
logger.debug(
214+
f"{local_proc_idx} consumed: {mem_after - mem_before},"
215+
f" before: {mem_before}, after: {mem_after}"
216+
)
217+
return local_output
218+

megatron/plugin/kunlunxin/tensor_parallel/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)