Skip to content

Commit a99d90c

Browse files
Merge branch 'master' into tohtana/fix-zero2-bf16-shared-grad-8224
2 parents fccfa47 + e70b903 commit a99d90c

64 files changed

Lines changed: 4290 additions & 296 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

accelerator/abstract_accelerator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ def is_bf16_supported(self):
174174
def is_fp16_supported(self):
175175
...
176176

177+
# Not abstract: nearly every accelerator supports fp64, so only those that do not need to override.
178+
def is_fp64_supported(self):
179+
return True
180+
177181
@abc.abstractmethod
178182
def supported_dtypes(self):
179183
...

accelerator/cuda_accelerator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ def prefer_triton_grouped_mm(self):
274274
# torch._grouped_mm only has a fused grouped-GEMM kernel on Hopper (sm90)
275275
# and newer; on sm8x it falls back to a slow per-group loop, so a Triton
276276
# grouped-GEMM kernel is preferred there when Triton is available.
277-
from deepspeed.moe.group_gemm_triton import is_available as triton_grouped_mm_is_available
277+
from deepspeed.ops.triton_ops import is_triton_available as triton_grouped_mm_is_available
278278
# not verified on AMD GPU
279279
if torch.version.hip is not None or not triton_grouped_mm_is_available():
280280
return False

accelerator/mps_accelerator.py

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ class MPS_Accelerator(DeepSpeedAccelerator):
1919

2020
def __init__(self):
2121
self._name = "mps"
22-
self._communication_backend_name = None
22+
# MPS has no native collective backend; gloo is the only torch backend available on macOS.
23+
self._communication_backend_name = "gloo"
2324
self._compile_backend = "inductor"
2425

2526
def is_synchronized_device(self):
26-
return False
27+
# MPS runs everything on a single in-order command queue and exposes no user-visible
28+
# streams, so DeepSpeed never needs to synchronize between streams on this device.
29+
return True
2730

2831
def use_host_timers(self):
2932
# Event timers are not supported on MPS
@@ -90,7 +93,8 @@ def Stream(self):
9093
return None
9194

9295
def stream(self, stream):
93-
return None
96+
from deepspeed.runtime.utils import noop_context
97+
return noop_context()
9498

9599
def current_stream(self, device_index=None):
96100
return None
@@ -100,7 +104,7 @@ def default_stream(self, device_index=None):
100104

101105
@property
102106
def Event(self):
103-
return None
107+
return torch.mps.Event
104108

105109
# Memory management
106110
def empty_cache(self):
@@ -119,41 +123,53 @@ def reset_max_memory_allocated(self, device_index=None):
119123
return
120124

121125
def memory_cached(self, device_index=None):
122-
return
126+
return torch.mps.driver_allocated_memory()
123127

124128
def max_memory_cached(self, device_index=None):
125-
return
129+
return torch.mps.driver_allocated_memory()
126130

127131
def reset_max_memory_cached(self, device_index=None):
128132
return
129133

130134
def memory_stats(self, device_index=None):
131-
return
135+
# torch.mps has no caching-allocator stats; expose what it does report under the CUDA key names.
136+
return {
137+
'allocated_bytes.all.current': torch.mps.current_allocated_memory(),
138+
'reserved_bytes.all.current': torch.mps.driver_allocated_memory(),
139+
}
132140

133141
def reset_peak_memory_stats(self, device_index=None):
134142
return
135143

136144
def memory_reserved(self, device_index=None):
137-
return
145+
return torch.mps.driver_allocated_memory()
138146

139147
def max_memory_reserved(self, device_index=None):
140-
return
148+
return torch.mps.driver_allocated_memory()
141149

142150
def total_memory(self, device_index=None):
143-
return
151+
# Unified memory: the driver-recommended working set is the usable budget for the GPU.
152+
return torch.mps.recommended_max_memory()
144153

145154
def available_memory(self, device_index=None):
146-
return
155+
return self.total_memory() - torch.mps.driver_allocated_memory()
147156

148157
# Data types
149158
def is_bf16_supported(self):
150-
return False
159+
# bf16 on MPS requires macOS 14 (Sonoma) or newer.
160+
return torch.backends.mps.is_macos_or_newer(14, 0)
151161

152162
def is_fp16_supported(self):
163+
return True
164+
165+
def is_fp64_supported(self):
153166
return False
154167

155168
def supported_dtypes(self):
156-
return [torch.float]
169+
supported_dtypes = [torch.float, torch.half]
170+
if self.is_bf16_supported():
171+
supported_dtypes.append(torch.bfloat16)
172+
return supported_dtypes
157173

158174
# Misc
159175
def is_available(self):
@@ -188,31 +204,39 @@ def replay_graph(self, graph):
188204
# Tensor operations
189205
@property
190206
def BFloat16Tensor(self):
191-
return
207+
return torch.BFloat16Tensor
192208

193209
@property
194210
def ByteTensor(self):
195-
return
211+
return torch.ByteTensor
196212

197213
@property
198214
def DoubleTensor(self):
199-
return
215+
return torch.DoubleTensor
200216

201217
@property
202218
def FloatTensor(self):
203-
return
219+
return torch.FloatTensor
204220

205221
@property
206222
def HalfTensor(self):
207-
return
223+
return torch.HalfTensor
208224

209225
@property
210226
def IntTensor(self):
211-
return
227+
return torch.IntTensor
212228

213229
@property
214230
def LongTensor(self):
215-
return
231+
return torch.LongTensor
232+
233+
# Apple Silicon has unified memory, so host tensors are already directly accessible to the GPU
234+
# and there is nothing to pin. torch's pin_memory() also raises for the MPS backend.
235+
def _torch_pin_memory(self, tensor):
236+
return tensor
237+
238+
def _torch_is_pinned(self, tensor):
239+
return tensor.device.type == 'cpu'
216240

217241
def on_accelerator(self, tensor):
218242
device_str = str(tensor.device)
@@ -227,9 +251,9 @@ def op_builder_dir(self):
227251
# if successful this also means we're doing a local install and not JIT compile path
228252
from op_builder import __deepspeed__ # noqa: F401 # type: ignore
229253

230-
return "op_builder"
254+
return "op_builder.mps"
231255
except ImportError:
232-
return "deepspeed.ops.op_builder"
256+
return "deepspeed.ops.op_builder.mps"
233257

234258
# create an instance of op builder, specified by class_name
235259
def create_op_builder(self, op_name):
@@ -240,9 +264,18 @@ def create_op_builder(self, op_name):
240264

241265
# return an op builder class, specified by class_name
242266
def get_op_builder(self, class_name):
243-
from deepspeed.ops.op_builder.cpu import NotImplementedBuilder
267+
try:
268+
# is op_builder from deepspeed or a 3p version? this should only succeed if it's deepspeed
269+
# if successful this also means we're doing a local install and not JIT compile path
270+
from op_builder import __deepspeed__ # noqa: F401 # type: ignore
271+
from op_builder.mps import FusedAdamBuilder, NotImplementedBuilder
272+
except ImportError:
273+
from deepspeed.ops.op_builder.mps import FusedAdamBuilder, NotImplementedBuilder
244274

245-
return NotImplementedBuilder
275+
if class_name == "FusedAdamBuilder":
276+
return FusedAdamBuilder
277+
else:
278+
return NotImplementedBuilder
246279

247280
def build_extension(self):
248281
from torch.utils.cpp_extension import BuildExtension

deepspeed/comm/torch.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,66 @@ def wait(self):
9595
return None
9696

9797

98+
class StagedWork:
99+
"""Completes a staged collective by copying the CPU results back to the original device tensors."""
100+
101+
def __init__(self, work, copy_back):
102+
self.work = work
103+
self.copy_back = copy_back
104+
105+
def wait(self):
106+
if self.work is not None:
107+
self.work.wait()
108+
self.copy_back()
109+
return None
110+
111+
112+
def _needs_cpu_staging(tensor):
113+
# gloo (the only torch backend on macOS) cannot operate on MPS tensors.
114+
return isinstance(tensor, torch.Tensor) and tensor.device.type == 'mps'
115+
116+
117+
def stage_on_cpu(func):
118+
"""Runs a collective on CPU copies of any MPS tensor arguments, then copies the results back.
119+
120+
This is what lets DeepSpeed use the gloo backend on Apple Silicon, where device tensors are
121+
not supported by any torch.distributed backend. Unified memory keeps the copies cheap.
122+
"""
123+
124+
def _stage(arg, pairs):
125+
if _needs_cpu_staging(arg):
126+
cpu_tensor = arg.to('cpu')
127+
pairs.append((arg, cpu_tensor))
128+
return cpu_tensor
129+
if isinstance(arg, list) and any(_needs_cpu_staging(t) for t in arg):
130+
return [_stage(t, pairs) for t in arg]
131+
return arg
132+
133+
signature = inspect.signature(func)
134+
135+
def wrapper(self, *args, **kwargs):
136+
pairs = []
137+
args = [_stage(arg, pairs) for arg in args]
138+
kwargs = {key: _stage(arg, pairs) for key, arg in kwargs.items()}
139+
if not pairs:
140+
return func(self, *args, **kwargs)
141+
142+
work = func(self, *args, **kwargs)
143+
144+
def copy_back():
145+
for device_tensor, cpu_tensor in pairs:
146+
device_tensor.copy_(cpu_tensor)
147+
148+
# async_op is usually forwarded positionally, so resolve it against the real signature.
149+
bound_args = signature.bind(self, *args, **kwargs)
150+
if bound_args.arguments.get('async_op', False):
151+
return StagedWork(work, copy_back)
152+
copy_back()
153+
return work
154+
155+
return wrapper
156+
157+
98158
class TorchBackend(Backend):
99159
"""
100160
A light-weight wrapper class for torch.distributed API.
@@ -179,6 +239,7 @@ def init_process_group(self, backend, timeout, init_method, rank, world_size):
179239
self.using_mpi = torch.distributed.get_backend() == 'mpi'
180240

181241
@disable_compiler_collective
242+
@stage_on_cpu
182243
def all_reduce(self, tensor, op=torch.distributed.ReduceOp.SUM, group=None, async_op=False):
183244
op = self._reduce_op(op)
184245
return torch.distributed.all_reduce(tensor=tensor, op=op, group=group, async_op=async_op)
@@ -195,6 +256,7 @@ def inference_all_reduce(self, tensor, op, group=None):
195256
return torch.ops.deepspeed.inference_all_reduce_(tensor)
196257

197258
@disable_compiler_collective
259+
@stage_on_cpu
198260
def all_reduce_coalesced(self, tensors, op=torch.distributed.ReduceOp.SUM, group=None, async_op=False):
199261
""" proxy func to torch.distributed.all_reduce_coalesced,
200262
which is included in PyTorch 1.13 and above
@@ -206,6 +268,7 @@ def all_reduce_coalesced(self, tensors, op=torch.distributed.ReduceOp.SUM, group
206268
return torch.distributed.all_reduce_coalesced(tensors=tensors, op=op, group=group, async_op=async_op)
207269

208270
@disable_compiler_collective
271+
@stage_on_cpu
209272
def reduce(self, tensor, dst, op=ReduceOp.SUM, group=None, async_op=False):
210273
if DS_COMM_REDUCE_OFF:
211274
if int(os.getenv('RANK', '0')) == 0:
@@ -214,6 +277,7 @@ def reduce(self, tensor, dst, op=ReduceOp.SUM, group=None, async_op=False):
214277
return torch.distributed.reduce(tensor=tensor, dst=dst, op=self._reduce_op(op), group=group, async_op=async_op)
215278

216279
@disable_compiler_collective
280+
@stage_on_cpu
217281
def reduce_scatter(self, output, input_list, op=ReduceOp.SUM, group=None, async_op=False):
218282
if DS_COMM_REDUCE_SCATTER_OFF:
219283
if int(os.getenv('RANK', '0')) == 0:
@@ -227,6 +291,7 @@ def reduce_scatter(self, output, input_list, op=ReduceOp.SUM, group=None, async_
227291
async_op=async_op)
228292

229293
@disable_compiler_collective
294+
@stage_on_cpu
230295
def broadcast(self, tensor, src, group=None, async_op=False):
231296
if DS_COMM_BROADCAST_OFF:
232297
if int(os.getenv('RANK', '0')) == 0:
@@ -240,6 +305,7 @@ def broadcast_object_list(self, object_list, src, group=None, device=None):
240305
return torch.distributed.broadcast_object_list(object_list=object_list, src=src, group=group, device=device)
241306

242307
@disable_compiler_collective
308+
@stage_on_cpu
243309
def all_gather(self, tensor_list, tensor, group=None, async_op=False):
244310
if DS_COMM_ALL_GATHER_OFF:
245311
if int(os.getenv('RANK', '0')) == 0:
@@ -249,6 +315,7 @@ def all_gather(self, tensor_list, tensor, group=None, async_op=False):
249315
return torch.distributed.all_gather(tensor_list=tensor_list, tensor=tensor, group=group, async_op=async_op)
250316

251317
@disable_compiler_collective
318+
@stage_on_cpu
252319
def all_gather_into_tensor(self, output_tensor, input_tensor, group=None, async_op=False):
253320
# Transparent SDMA fast-path on AMD/ROCm: when the mori backend is
254321
# available and the call is on the WORLD process group, route
@@ -266,6 +333,7 @@ def all_gather_into_tensor(self, output_tensor, input_tensor, group=None, async_
266333
async_op=async_op)
267334

268335
@disable_compiler_collective
336+
@stage_on_cpu
269337
def all_gather_base(self, output_tensor, input_tensor, group=None, async_op=False):
270338
if DS_COMM_ALL_GATHER_OFF:
271339
if int(os.getenv('RANK', '0')) == 0:
@@ -284,6 +352,7 @@ def all_gather_base(self, output_tensor, input_tensor, group=None, async_op=Fals
284352
pass
285353

286354
@disable_compiler_collective
355+
@stage_on_cpu
287356
def all_gather_coalesced(self, output_tensors, input_tensors, group=None, async_op=False):
288357
""""""
289358
assert len(output_tensors) == len(input_tensors), ""
@@ -312,6 +381,7 @@ def all_gather_object(self, object_list, obj, group=None):
312381
return torch.distributed.all_gather_object(object_list=object_list, obj=obj, group=group)
313382

314383
@disable_compiler_collective
384+
@stage_on_cpu
315385
def reduce_scatter_tensor(self, output_tensor, input_tensor, op=ReduceOp.SUM, group=None, async_op=False):
316386
if self.has_reduce_scatter_tensor():
317387
return self.reduce_scatter_function(output_tensor,
@@ -326,6 +396,7 @@ def reduce_scatter_tensor(self, output_tensor, input_tensor, op=ReduceOp.SUM, gr
326396
pass
327397

328398
@disable_compiler_collective
399+
@stage_on_cpu
329400
def all_to_all_single(self,
330401
output,
331402
input,
@@ -341,26 +412,32 @@ def all_to_all_single(self,
341412
async_op=async_op)
342413

343414
@disable_compiler_collective
415+
@stage_on_cpu
344416
def all_to_all(self, output_tensor_list, input_tensor_list, group=None, async_op=False):
345417
return torch.distributed.all_to_all(output_tensor_list, input_tensor_list, group=group, async_op=async_op)
346418

347419
@disable_compiler_collective
420+
@stage_on_cpu
348421
def send(self, tensor, dst, group=None, tag=0):
349422
return torch.distributed.send(tensor=tensor, dst=dst, group=group, tag=tag)
350423

351424
@disable_compiler_collective
425+
@stage_on_cpu
352426
def recv(self, tensor, src=None, group=None, tag=0):
353427
return torch.distributed.recv(tensor=tensor, src=src, group=group, tag=tag)
354428

355429
@disable_compiler_collective
430+
@stage_on_cpu
356431
def isend(self, tensor, dst, group=None, tag=0):
357432
return torch.distributed.isend(tensor=tensor, dst=dst, group=group, tag=tag)
358433

359434
@disable_compiler_collective
435+
@stage_on_cpu
360436
def irecv(self, tensor, src=None, group=None, tag=0):
361437
return torch.distributed.irecv(tensor=tensor, src=src, group=group, tag=tag)
362438

363439
@disable_compiler_collective
440+
@stage_on_cpu
364441
def gather(self, tensor, gather_list=None, dst=0, group=None, async_op=False):
365442
return torch.distributed.gather(tensor=tensor,
366443
gather_list=gather_list,
@@ -369,6 +446,7 @@ def gather(self, tensor, gather_list=None, dst=0, group=None, async_op=False):
369446
async_op=async_op)
370447

371448
@disable_compiler_collective
449+
@stage_on_cpu
372450
def scatter(self, tensor, scatter_list=None, src=0, group=None, async_op=False):
373451
return torch.distributed.scatter(tensor=tensor,
374452
scatter_list=scatter_list,

0 commit comments

Comments
 (0)