1818from .core import CheckpointingException
1919from .dict_utils import nested_values
2020from .mapping import ShardedStateDict , ShardedTensor , is_main_replica
21- from .utils import _sharded_tensor_shard_id , _ShardId , debug_time
21+ from .utils import _quantized_storage_tensors , _sharded_tensor_shard_id , _ShardId , debug_time
2222
2323# TODO: remove TE references once the TE bug is fixed
2424# Check if Transformer Engine has Float8Tensor class
@@ -764,12 +764,147 @@ def exchange_loaded_tensors_broadcast(
764764 return all_loaded_tensors
765765
766766
767+ _QUANTIZED_STORAGE_PART_ALIGNMENT = 16
768+
769+
770+ def _aligned_nbytes (nbytes : int ) -> int :
771+ return - (- nbytes // _QUANTIZED_STORAGE_PART_ALIGNMENT ) * _QUANTIZED_STORAGE_PART_ALIGNMENT
772+
773+
774+ def _quantized_storage_nbytes (tensor : torch .Tensor ) -> int :
775+ """Size of the packed quantized storage of a TE quantized tensor (see `_pack_...`)."""
776+ return sum (
777+ _aligned_nbytes (part .numel () * part .element_size ())
778+ for part in _quantized_storage_tensors (tensor )
779+ )
780+
781+
782+ def _pack_quantized_storage (tensor : torch .Tensor ) -> torch .Tensor :
783+ """Packs the raw storage of a TE quantized tensor (codes and scales) into one byte buffer.
784+
785+ Exchanging the raw storage instead of the dequantized values keeps the loading rank's
786+ encoding bit for bit on the receivers (re-quantizing dequantized values can pick different
787+ block scales) and halves the exchanged bytes. Parts are 16-byte aligned so that every part
788+ can be viewed in place with its own dtype.
789+ """
790+ buf = torch .empty (_quantized_storage_nbytes (tensor ), dtype = torch .uint8 , device = tensor .device )
791+ offset = 0
792+ for part in _quantized_storage_tensors (tensor ):
793+ nbytes = part .numel () * part .element_size ()
794+ buf [offset : offset + nbytes ].view (part .dtype ).view (part .shape ).copy_ (part )
795+ offset += _aligned_nbytes (nbytes )
796+ return buf
797+
798+
799+ def _unpack_quantized_storage (buf : torch .Tensor , tensor : torch .Tensor ) -> None :
800+ """Inverse of `_pack_quantized_storage`, writing into the storage of `tensor`."""
801+ offset = 0
802+ for part in _quantized_storage_tensors (tensor ):
803+ nbytes = part .numel () * part .element_size ()
804+ part .copy_ (buf [offset : offset + nbytes ].view (part .dtype ).view (part .shape ))
805+ offset += _aligned_nbytes (nbytes )
806+ if offset != buf .numel ():
807+ raise CheckpointingException (
808+ f'Packed quantized storage ({ buf .numel ()} bytes) does not match the destination'
809+ f' ({ offset } bytes)'
810+ )
811+
812+
813+ def exchange_loaded_tensors_broadcast_quantized (
814+ loaded_tensors : Dict [_ShardId , torch .Tensor ],
815+ unloaded_shards : Dict [_ShardId , ShardedTensor ],
816+ shard_distribution : ShardDistribution ,
817+ parallelization_group : Optional [torch .distributed .ProcessGroup ] = None ,
818+ ) -> Dict [_ShardId , torch .Tensor ]:
819+ """`exchange_loaded_tensors_broadcast` for state dicts with quantized destinations.
820+
821+ Used by the streaming dequantize load, which loads quantized destinations in place on the
822+ loading rank. Such tensors are broadcast as their packed raw storage (see
823+ `_pack_quantized_storage`) and unpacked straight into the receivers' destinations, so that
824+ every rank ends up with the loading rank's encoding and no high-precision copy is created.
825+ Other tensors are exchanged as by `exchange_loaded_tensors_broadcast`.
826+
827+ Args: see `exchange_loaded_tensors_broadcast`.
828+ """
829+ from ..fp8_utils import is_float8tensor # Avoid circular import
830+
831+ main_rank_for_shard , _ , shard_to_metadata , all_ranks_for_shard = shard_distribution
832+ local_rank = torch .distributed .get_rank (group = parallelization_group )
833+
834+ # Ranks that do not need a shard still take part in its broadcast and must know the size of
835+ # its packed storage, which only the loading rank knows: gather the sizes once up front.
836+ local_nbytes = {
837+ shard_id : _quantized_storage_nbytes (tensor )
838+ for shard_id , tensor in loaded_tensors .items ()
839+ if is_float8tensor (tensor )
840+ }
841+ all_nbytes = [None ] * torch .distributed .get_world_size (group = parallelization_group )
842+ torch .distributed .all_gather_object (all_nbytes , local_nbytes , group = parallelization_group )
843+ quantized_nbytes = {k : v for rank_nbytes in all_nbytes for k , v in rank_nbytes .items ()}
844+
845+ if not quantized_nbytes :
846+ return exchange_loaded_tensors_broadcast (
847+ loaded_tensors , unloaded_shards , shard_distribution , parallelization_group
848+ )
849+
850+ plain_loaded_tensors = {k : v for k , v in loaded_tensors .items () if k not in quantized_nbytes }
851+ plain_distribution = ShardDistribution (
852+ {k : v for k , v in main_rank_for_shard .items () if k not in quantized_nbytes },
853+ shard_distribution .shards_in_this_group ,
854+ shard_to_metadata ,
855+ all_ranks_for_shard ,
856+ )
857+ all_loaded_tensors = exchange_loaded_tensors_broadcast (
858+ plain_loaded_tensors , unloaded_shards , plain_distribution , parallelization_group
859+ )
860+
861+ for shard_id , rank in main_rank_for_shard .items ():
862+ if shard_id not in quantized_nbytes :
863+ continue
864+ nbytes = quantized_nbytes [shard_id ]
865+ if rank == local_rank :
866+ all_loaded_tensors [shard_id ] = loaded_tensors [shard_id ]
867+ if len (all_ranks_for_shard [shard_id ]) == 1 :
868+ # Only the loading rank needs this tensor, no exchange needed.
869+ continue
870+ if rank == local_rank :
871+ quantized = loaded_tensors [shard_id ]
872+ buf = _pack_quantized_storage (quantized )
873+ else :
874+ quantized = unloaded_shards [shard_id ].data if shard_id in unloaded_shards else None
875+ if quantized is not None and (
876+ not is_float8tensor (quantized ) or _quantized_storage_nbytes (quantized ) != nbytes
877+ ):
878+ raise CheckpointingException (
879+ f'Shard { shard_id } was loaded as a quantized tensor on rank { rank } but its'
880+ f' destination on rank { local_rank } does not have the same quantized storage'
881+ )
882+ buf = torch .empty (nbytes , dtype = torch .uint8 , device = 'cuda' )
883+ global_src_rank = (
884+ rank
885+ if parallelization_group is None
886+ else torch .distributed .get_global_rank (parallelization_group , rank )
887+ )
888+ torch .distributed .broadcast (buf , src = global_src_rank , group = parallelization_group )
889+ if rank != local_rank and quantized is not None :
890+ with torch .no_grad ():
891+ _unpack_quantized_storage (buf , quantized )
892+ # Hand back a view rather than the destination object itself: `load_state_dict`
893+ # copies the loaded tensor into the param, and TE's generic quantized `copy_`
894+ # cannot copy a tensor onto itself (it detaches the source storage first).
895+ all_loaded_tensors [shard_id ] = quantized .view (quantized .shape )
896+ del buf
897+
898+ return all_loaded_tensors
899+
900+
767901def exchange_by_distribution (
768902 loaded_tensors : Dict [_ShardId , torch .Tensor ],
769903 unloaded_shards : Dict [_ShardId , ShardedTensor ],
770904 shard_distribution : ShardDistribution ,
771905 parallelization_group : Optional [torch .distributed .ProcessGroup ] = None ,
772906 exchange_algo = "broadcast" ,
907+ exchange_quantized_storage : bool = False ,
773908) -> Dict [_ShardId , torch .Tensor ]:
774909 """Exchange tensors loaded by different ranks using the specified exchange_algo.
775910
@@ -783,6 +918,10 @@ def exchange_by_distribution(
783918 distribution. Tensors will be exchanged within this group
784919 exchange_algo (str): The algorithm used for performing exchanges.
785920 Defaults to 'broadcast'.
921+ exchange_quantized_storage (bool): with the 'broadcast' algorithm, exchange quantized
922+ tensors as their raw storage and unpack them into the receivers' quantized
923+ destinations (streaming dequantize load), see
924+ `exchange_loaded_tensors_broadcast_quantized`.
786925
787926 Returns:
788927 Dict[_ShardId, torch.Tensor]: dictionary mapping shard ids to tensors
@@ -796,7 +935,11 @@ def exchange_by_distribution(
796935 elif exchange_algo == "gather_rounds" :
797936 exchange_fn = exchange_loaded_tensors_gather_rounds
798937 elif exchange_algo == "broadcast" :
799- exchange_fn = exchange_loaded_tensors_broadcast
938+ exchange_fn = (
939+ exchange_loaded_tensors_broadcast_quantized
940+ if exchange_quantized_storage
941+ else exchange_loaded_tensors_broadcast
942+ )
800943 else :
801944 raise NotImplementedError (f"Unrecognized gather algorithm: { exchange_algo } " )
802945 return exchange_fn (loaded_tensors , unloaded_shards , shard_distribution , parallelization_group )
0 commit comments