Diff Coverage

Diff: origin/master...HEAD, staged and unstaged changes

Source File Diff Coverage (%) Missing Lines
hyper_parallel/core/distributed_checkpoint/api.py 85.7% 415-416,489
hyper_parallel/core/distributed_checkpoint/async_persist.py 100%  
hyper_parallel/core/distributed_checkpoint/filesystem_storage.py 92.4% 472,483,488-489,494,518,608,622
hyper_parallel/core/distributed_checkpoint/metadata.py 100%  
hyper_parallel/core/distributed_checkpoint/offline_transform.py 100%  
hyper_parallel/core/distributed_checkpoint/planner.py 100%  
hyper_parallel/core/distributed_checkpoint/standard_planner.py 97.7% 88
hyper_parallel/core/distributed_checkpoint/util.py 95.7% 661-663,794,844
hyper_parallel/platform/platform.py 50.0% 310-312,314,596,1905
hyper_parallel/platform/torch/platform.py 50.0% 1054-1055,1248-1250,1612
hyper_parallel/core/distributed_checkpoint/api.py
411
412
413
414
415
416
417
418
419
420
        together either.
    """
    if storage_reader is None:
        storage_reader = FileSystemReader(checkpoint_id)
    elif checkpoint_id:
        storage_reader.initialize_reader(checkpoint_id)

    try:
        return storage_reader, storage_reader.load_metadata(), True
    except FileNotFoundError:
485
486
487
488
489
490
491
492
493
    use_collectives = False if no_dist else use_collectives

    # Check the arguments
    if broadcast_groups and not broadcast_replicated_tensors:
        raise ValueError("broadcast_groups is only used when broadcast_replicated_tensors is True.")
    if storage_reader is None and checkpoint_id is None:
        raise ValueError("Either storage_reader or checkpoint_id must be provided")

    # Set up planner
hyper_parallel/core/distributed_checkpoint/filesystem_storage.py
468
469
470
471
472
473
474
475
        else:
            # Scalar entries (rank-0 tensors such as the AdamW ``step``) have no slices to
            # narrow by, and safetensors before 0.4.3 rejects the empty index with
            # "too many indices for tensor of dimension 0" - read the whole tensor instead.
            tensor = tensor_file.get_tensor(tensor_key)
        fetched.append((req, tensor))
    return fetched

479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
        reqs: list[ReadItem],
        storage_data: dict[MetadataIndex, StorageInfo],
) -> list[tuple[ReadItem, Any]]:
    """Narrow the region each item asked for out of a file the ms adapter has read in."""
    fetched: list[tuple[ReadItem, Any]] = []
    for req in reqs:
        storage_info = _get_storage_info(req, storage_data)
        tensor_key = storage_info.tensor_key or req.storage_index.fqn
        if tensor_key not in param_dict:
            raise KeyError(f"Key {tensor_key} not found in checkpoint file")
        fetched.append((req, narrow_tensor_by_index(
            param_dict[tensor_key],
            req.storage_offsets,
            req.lengths,
        )))
    return fetched


def _fetch_tensor_file(
        tensor_file: Any,
514
515
516
517
518
519
520
521
522
        list[tuple[ReadItem, Any]]: Each item with the region it asked for.
    """
    if platform.platform_type == PlatformType.PYTORCH:
        return _fetch_torch_tensor_file(tensor_file, reqs, storage_data)
    return _fetch_ms_tensor_file(tensor_file, reqs, storage_data)


def _apply_fetched(
        fetched: list[tuple[ReadItem, Any]],
604
605
606
607
608
609
610
611
612
            _TORCH_FILES_KEPT,
            lambda path: safe_open(path, framework="pt", device="cpu"),
            lambda reader: reader.__exit__(None, None, None),
        )
    return _OpenFiles(_MS_FILES_KEPT, platform.load_checkpoint, lambda reader: None)


def _broadcast_batch_bytes(requested: int) -> int:
    """
618
619
620
621
622
623
624
625
626
        reach the buffer behind them; there every shard is sent on its own, as before.
    """
    if platform.platform_type == PlatformType.PYTORCH:
        return requested
    return 0


class FileSystemReader(StorageReader):
    """
hyper_parallel/core/distributed_checkpoint/standard_planner.py
84
85
86
87
88
89
90
91
92
        ValueError: If the gathered list holds no plan for this rank.
    """
    own_index = rank if len(all_plans) > 1 else 0
    if not 0 <= own_index < len(all_plans):
        raise ValueError(
            f"Rank {rank} has no plan of its own among the {len(all_plans)} gathered "
            "plans; the gathered list must hold one plan per rank, in rank order."
        )
    return own_index
hyper_parallel/core/distributed_checkpoint/util.py
657
658
659
660
661
662
663
664
665
666
667
    while len(in_flight) >= _MAX_BROADCASTS_IN_FLIGHT:
        _finish_broadcast(in_flight.popleft())
    handle = platform.broadcast_async(buffer, source.src_rank, groups[source.group_ranks])
    if handle is None:
        if after is not None:
            after()
        return
    in_flight.append((handle, after))


def _finish_broadcast(pending: tuple) -> None:
790
791
792
793
794
795
796
797
798
        """Send one batch, and arrange for it to be dealt out once it has landed."""
        buffers = self._batches.pop(key, [])
        self._pending_bytes.pop(key, None)
        if not buffers:
            return

        group_ranks, src_rank = key[:2]
        source = BroadcastSource(group_ranks=group_ranks, src_rank=src_rank)
        self.sent += 1
840
841
842
843
844
845
846
        Any: A tensor view over the shard, writable in place by a collective.
    """
    if isinstance(obj, DTensor):
        if obj.layout is not None and obj.layout.ragged_shard is not None:
            return get_ragged_box_tensor(obj, index).detach()
        return obj.to_local().detach()
    return obj.detach()
hyper_parallel/platform/platform.py
306
307
308
309
310
311
312
313
314
315
316
317
318
        Args:
            dests (list): Tensors to copy into, one per source.
            srcs (list): Tensors to copy from, matching ``dests`` in length and shape.
        """
        for dest, src in zip(dests, srcs):
            if hasattr(dest, "copy_"):
                dest.copy_(src)
            else:
                dest[...] = src

    @staticmethod
    def differentiable_all_reduce(data, op, group):
        """Perform differentiable all-reduce operation.
592
593
594
595
596
597
598
599
600

        Returns:
            A work handle to wait on, or None if the backend completed the call inline.
        """
        raise NotImplementedError("Platform subclasses must implement broadcast_async")

    @staticmethod
    def scatter(output, scatter_list, src=None, group=None, async_op=False, group_src=None):
        """Scatter tensor list from source rank to all ranks in group."""
1901
1902
1903
1904
1905
1906
1907
1908
1909

        Returns:
            The process group of every rank.
        """
        raise NotImplementedError("Platform subclasses must implement get_world_group")

    @staticmethod
    def get_created_group(rank_list: Union[list[int], tuple[int]]):
        """Get an existing process group by rank list.
hyper_parallel/platform/torch/platform.py
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059

        Measured on an Ascend card, five hundred copies of 64 KiB take 11.8 ms one at a
        time and 1.4 ms fused: nearly all of what a small copy costs is starting it.
        """
        if dests:
            torch._foreach_copy_(dests, srcs)  # pylint: disable=protected-access

    @staticmethod
    def parameters_dict(cell: Module):
        return cell.named_parameters()
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
            handle.wait()

    @staticmethod
    def broadcast_async(data, src=None, group=None, group_src=None):
        if group_src is not None:
            src = dist.get_global_rank(group, group_src)
        return dist.broadcast(data, src, group, async_op=True)

    @staticmethod
    def scatter(output, scatter_list, src=None, group=None, async_op=False, group_src=None):
        if group_src is not None:
1608
1609
1610
1611
1612
1613
1614
1615
1616

    @staticmethod
    def get_world_group() -> ProcessGroup:
        """The default process group, which holds every rank."""
        return _get_default_group()

    @staticmethod
    def destroy_process_group(group: Optional[ProcessGroup] = None) -> None:
        """