|
71 | 71 | from thunder.extend import FUEL_LEVEL, FusionExecutor, register_executor |
72 | 72 | from thunder.executors.nvfuserex import nvfuser_version |
73 | 73 |
|
| 74 | + |
| 75 | +DTENSOR_SUPPORTED_VERSION = LooseVersion("0.2.28") |
| 76 | +if nvfuser_version() >= DTENSOR_SUPPORTED_VERSION: |
| 77 | + import nvfuser_direct as nvfd |
| 78 | + from nvfuser_direct import FusionDefinition as DirectFusionDefinition |
| 79 | + |
74 | 80 | # NOTE This impl file is here because nvFuser may not be available, so it's imported conditionally |
75 | 81 | # by nvfuserex.py when nvFuser is available. |
76 | 82 | import nvfuser |
@@ -241,41 +247,34 @@ def get_translator(bsym: BoundSymbol) -> Callable: |
241 | 247 | return _translation_map[bsym.sym.id] |
242 | 248 |
|
243 | 249 |
|
244 | | -class MultiDeviceFusionDefinition(FusionDefinition): |
245 | | - def __init__(self, define_fn: Callable[[FusionDefinition], None], in_dtensors: list[DTensorProxy], max_length: int): |
246 | | - super().__init__(max_length=max_length) |
247 | | - self._in_dtensors = in_dtensors |
248 | | - self._define_fn = define_fn |
| 250 | +def register_dtensor_supported(prim_id: int, fn: Callable, checker_fn: Callable) -> None: |
| 251 | + if nvfuser_version() < DTENSOR_SUPPORTED_VERSION: |
| 252 | + # Only register dtensor ops if supported version is available. |
| 253 | + return |
249 | 254 |
|
250 | | - def definition(self) -> None: |
251 | | - self._define_fn(self) |
| 255 | + register_supported(prim_id, fn, checker_fn) |
252 | 256 |
|
253 | | - def _find_tensor_by_index(self, index: int) -> nvfuser.Tensor: |
254 | | - for t in self.sched.tensors(): |
255 | | - if t.index == index: |
256 | | - return t |
257 | | - return None |
258 | 257 |
|
259 | | - def multidevice_schedule(self) -> None: |
260 | | - for in_tensor_index, in_dtensor in zip(self.inputs(), self._in_dtensors): |
261 | | - in_tensor = self._find_tensor_by_index(in_tensor_index) |
| 258 | +def multidevice_schedule(fd: FusionDefinition, in_dtensors: list[Proxy]) -> None: |
| 259 | + for in_tv, in_dtensor in zip(fd.fusion.inputs(), in_dtensors): |
| 260 | + assert isinstance(in_dtensor, DTensorProxy) |
| 261 | + # Set the device mesh. |
| 262 | + assert in_dtensor.device_mesh.ndim == 1, "nvFuser's Python API only supports 1D meshes." |
| 263 | + mesh = nvfd.multidevice.DeviceMesh(in_dtensor.device_mesh.mesh.tolist()) |
262 | 264 |
|
263 | | - # Set the device mesh. |
264 | | - utils.check(in_dtensor.device_mesh.ndim == 1, lambda: "nvFuser's Python API only supports 1D meshes.") |
265 | | - mesh = nvfuser.DeviceMesh(in_dtensor.device_mesh.mesh.tolist()) |
| 265 | + in_tv.set_device_mesh(mesh) |
266 | 266 |
|
267 | | - self.sched._set_device_mesh(in_tensor, mesh) |
| 267 | + assert len(in_dtensor.placements) == 1, "nvFuser's Python API only supports 1D meshes." |
268 | 268 |
|
269 | | - # Split and parallelize. |
270 | | - utils.check(len(in_dtensor.placements) == 1, lambda: "nvFuser's Python API only supports 1D meshes.") |
271 | | - # When the mesh is multi-dimensional, iterate through the |
272 | | - # placements in descending order of Placement.dim. |
273 | | - placement: Placement = in_dtensor.placements[0] |
274 | | - if placement.is_shard(): |
275 | | - dim = cast(Shard, placement).dim |
276 | | - self.sched.split(in_tensor, dim, mesh.size, False) |
277 | | - self.sched.parallelize(in_tensor, dim, nvfuser.ParallelType.mesh_x) |
278 | | - self.sched.set_allocation_as_loop(in_tensor) |
| 269 | + # Split and parallelize. |
| 270 | + # When the mesh is multi-dimensional, iterate through the |
| 271 | + # placements in descending order of Placement.dim. |
| 272 | + placement: Placement = in_dtensor.placements[0] |
| 273 | + if placement.is_shard(): |
| 274 | + dim = cast(Shard, placement).dim |
| 275 | + in_tv.split(dim, mesh.size, inner_split=False) |
| 276 | + in_tv.axis(dim).parallelize(nvfd.ParallelType.mesh_x) |
| 277 | + in_tv.set_allocation_domain(in_tv.get_loop_domain(), new_contiguity=True) |
279 | 278 |
|
280 | 279 |
|
281 | 280 | def create_fd( |
@@ -376,10 +375,13 @@ def check_dtensor_tracing_and_runtime_metadata(inp): |
376 | 375 | lambda: "nvfuser: Expected runtime and tracing metadata to be the same for DTensor.", |
377 | 376 | ) |
378 | 377 |
|
379 | | - fd = MultiDeviceFusionDefinition(definition, sorted_unique_inputs, max_length=MAX_LENGTH) |
| 378 | + fd = DirectFusionDefinition() |
380 | 379 | # Device may be set in one of the "factory" methods like full, iota, or uniform |
381 | 380 | # NOTE: This should be called before defining because a factory method may look-up at `_selected_device` while being defined. |
382 | 381 | fd._selected_device = None |
| 382 | + with fd: |
| 383 | + definition(fd) |
| 384 | + multidevice_schedule(fd, sorted_unique_inputs) |
383 | 385 | else: |
384 | 386 | # NOTE nvFuser's default max length is 1024 operations at the time of this writing |
385 | 387 | # This arbitrarily increases it to 9999 |
@@ -535,28 +537,10 @@ def __call__(self, *args): |
535 | 537 | if self.store_inputs: |
536 | 538 | self.last_inputs = args |
537 | 539 |
|
538 | | - if hasattr(fd, "multidevice_schedule"): |
| 540 | + if dist.is_available() and any(isinstance(t, torch.distributed.tensor.DTensor) for t in args): |
539 | 541 | with annotate_for_profile(self.name): |
540 | | - in_tensors = [in_dtensor.to_local() for in_dtensor in args] |
541 | | - out_tensors, out_shardings = fd.execute( |
542 | | - in_tensors, |
543 | | - device=fd._selected_device, |
544 | | - save_repro_inputs=self.save_fake_inputs, |
545 | | - _enable_options=self.enable_options, |
546 | | - _disable_options=self.disable_options, |
547 | | - ) |
548 | | - |
549 | | - assert len(out_tensors) == len(out_shardings) |
550 | | - out_dtensors: list[DTensor] = [] |
551 | | - for out_tensor, out_sharding in zip(out_tensors, out_shardings): |
552 | | - mesh = dist.device_mesh.init_device_mesh("cuda", (out_sharding.mesh.size,)) |
553 | | - placements: list[Placement] = [] |
554 | | - for parallel_type in [nvfuser.ParallelType.mesh_x]: |
555 | | - axis: int = out_sharding.axis_sharded_on(parallel_type) |
556 | | - placements.append(Replicate() if axis == -1 else Shard(axis)) |
557 | | - out_dtensors.append(DTensor.from_local(out_tensor, mesh, placements)) |
558 | | - |
559 | | - return out_dtensors |
| 542 | + output = nvfd.execute_with_dtensors(fd, args) |
| 543 | + return output |
560 | 544 | else: |
561 | 545 | with annotate_for_profile(self.name): |
562 | 546 | return fd.execute( |
@@ -1917,7 +1901,7 @@ def mul(a: TensorProxy | Number, b: TensorProxy | Number, *, fd: FusionDefinitio |
1917 | 1901 |
|
1918 | 1902 |
|
1919 | 1903 | register_supported(PrimIDs.MUL, mul, _elementwise_binary_check) |
1920 | | -register_supported(dtensor_mul_prim.id, mul, _elementwise_binary_check) |
| 1904 | +register_dtensor_supported(dtensor_mul_prim.id, mul, _elementwise_binary_check) |
1921 | 1905 |
|
1922 | 1906 |
|
1923 | 1907 | def ne(a: TensorProxy | Number, b: TensorProxy | Number, *, fd: FusionDefinition, lc_to_nv_map: dict) -> Any: |
|
0 commit comments