283 lines
9.5 KiB
Python
283 lines
9.5 KiB
Python
# Copyright (c) Microsoft Corporation.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# DeepSpeed Team
|
|
|
|
from typing import Dict, Iterable, Tuple, Optional
|
|
from os import path
|
|
|
|
import torch
|
|
|
|
from deepspeed.accelerator import get_accelerator
|
|
from deepspeed.ops.op_builder import RaggedUtilsBuilder
|
|
from deepspeed.runtime.config_utils import DeepSpeedConfigModel
|
|
from .layer_container_base import LayerContainer
|
|
from ..inference_parameter import InferenceParameter, STR_TO_DTYPE
|
|
from ..inference_utils import elem_size
|
|
|
|
|
|
def pad_to_aligned_offset(offset: int, alignment: int = 256) -> int:
|
|
"""
|
|
Pad the provided offset to a well-aligned value.
|
|
"""
|
|
return ((offset + alignment - 1) // alignment) * alignment
|
|
|
|
|
|
class TensorMetadata(DeepSpeedConfigModel):
|
|
"""
|
|
A class to represent a tensor specification.
|
|
"""
|
|
dtype: Optional[str] = None
|
|
shape: Optional[Tuple[int, ...]] = None
|
|
strides: Optional[Tuple[int, ...]] = None
|
|
offset: int
|
|
|
|
|
|
class ParameterMetadata(DeepSpeedConfigModel):
|
|
"""
|
|
A class to represent a parameter specification.
|
|
"""
|
|
core_param: Optional[TensorMetadata] = None
|
|
aux_params: Dict[str, TensorMetadata] = {}
|
|
|
|
|
|
class LayerMetadata(DeepSpeedConfigModel):
|
|
"""
|
|
A class to represent a layer specification.
|
|
"""
|
|
params: Dict[str, ParameterMetadata] = {}
|
|
|
|
|
|
class ModelMetadata(DeepSpeedConfigModel):
|
|
"""
|
|
A class to represent a model specification.
|
|
"""
|
|
policy: str = ""
|
|
layers: Dict[str, LayerMetadata] = {}
|
|
|
|
|
|
def make_param_filename(base: str, rank: int, n_ranks: int) -> str:
|
|
"""
|
|
Make a filename for a parameter file.
|
|
|
|
Arguments:
|
|
rank: Rank of the file.
|
|
n_ranks: Total number of ranks.
|
|
|
|
Returns:
|
|
str: Filename.
|
|
"""
|
|
return path.join(base, f"params_rank_{rank}_of_{n_ranks}.pt")
|
|
|
|
|
|
def make_metadata_filename(base: str, rank: int, n_ranks: int) -> str:
|
|
"""
|
|
Make a filename for a metadata file.
|
|
|
|
Arguments:
|
|
rank: Rank of the file.
|
|
n_ranks: Total number of ranks.
|
|
|
|
Returns:
|
|
str: Filename.
|
|
"""
|
|
return path.join(base, f"metadata_rank_{rank}_of_{n_ranks}.json")
|
|
|
|
|
|
def make_model_config_filename(base: str) -> str:
|
|
"""
|
|
Make a filename for a model config file.
|
|
|
|
Arguments:
|
|
base: Base directory.
|
|
|
|
Returns:
|
|
str: Filename.
|
|
"""
|
|
return path.join(base, "ds_model_config.json")
|
|
|
|
|
|
def flatten_inference_model(
|
|
transformer_containers: Iterable[LayerContainer],
|
|
non_transformer_container: LayerContainer,
|
|
policy_name: str,
|
|
) -> Tuple[torch.Tensor, ModelMetadata]:
|
|
"""
|
|
Flatten the underlying parameters into
|
|
|
|
Arguments:
|
|
transformer_containers: Iterable of layer containers corresponding to the transformer
|
|
parameters.
|
|
non_transformer_container: Layer container corresponding to the non-transformer parameters.
|
|
policy_name: The name of the policy class (typically accessed with `type(policy).__name__`).
|
|
|
|
Returns:
|
|
Iterable[Any]: Flattened list of parameters.
|
|
"""
|
|
alloc_fn = RaggedUtilsBuilder().load().allocate_view_on
|
|
|
|
total_size = 0
|
|
metadata = ModelMetadata(policy=policy_name)
|
|
|
|
def process_layer(layer_container: LayerContainer, l_name: str, cur_offset: int) -> int:
|
|
"""
|
|
Iterate over the parameters of a single container and collect metadata for the final
|
|
flattened buffer.
|
|
|
|
Arguments:
|
|
layer_container: The layer container to process.
|
|
l_name: The name of the layer container to key the metadata.
|
|
cur_offset: The current offset into the flattened buffer.
|
|
|
|
Captured Variables:
|
|
metadata: The metadata object to populate.
|
|
|
|
Returns:
|
|
int: The updated offset into the flattened buffer.
|
|
"""
|
|
try:
|
|
_ = layer_container.is_populated
|
|
except ValueError as e:
|
|
raise ValueError(f"Layer container {l_name} is not populated.") from e
|
|
|
|
layer_metadata = LayerMetadata()
|
|
|
|
for p_name in layer_container.annotation_attrs:
|
|
param = getattr(layer_container, p_name)
|
|
param_metadata = ParameterMetadata()
|
|
|
|
if param is None:
|
|
param_metadata.core_param = TensorMetadata(offset=-1)
|
|
layer_metadata.params[p_name] = param_metadata
|
|
continue
|
|
|
|
param_metadata.core_param = TensorMetadata(dtype=str(param.dtype),
|
|
shape=param.shape,
|
|
strides=param.stride(),
|
|
offset=cur_offset)
|
|
|
|
cur_offset += pad_to_aligned_offset(elem_size(param.dtype) * param.numel())
|
|
|
|
for t_name, tensor in param.aux_attrs.items():
|
|
param_metadata.aux_params[t_name] = TensorMetadata(dtype=str(tensor.dtype),
|
|
shape=tensor.shape,
|
|
strides=tensor.stride(),
|
|
offset=cur_offset)
|
|
|
|
cur_offset += pad_to_aligned_offset(elem_size(tensor.dtype) * tensor.numel())
|
|
|
|
layer_metadata.params[p_name] = param_metadata
|
|
|
|
metadata.layers[l_name] = layer_metadata
|
|
return cur_offset
|
|
|
|
for i, layer in enumerate(transformer_containers):
|
|
l_name = f"transformer_layer_{i}"
|
|
total_size = process_layer(layer, l_name, total_size)
|
|
|
|
l_name = "non_transformer"
|
|
total_size = process_layer(non_transformer_container, l_name, total_size)
|
|
|
|
buffer = torch.empty(total_size, dtype=torch.uint8, device=get_accelerator().current_device())
|
|
|
|
def copy_layer(layer_container: LayerContainer, l_name: str) -> None:
|
|
"""
|
|
Local method for copying from the layer container to the flattened buffer.
|
|
|
|
Arguments:
|
|
layer_container: The layer container to copy from.
|
|
l_name: The name of the layer container to key the metadata.
|
|
|
|
Captured Variables:
|
|
buffer: The flattened buffer to copy into.
|
|
metadata: The metadata object to populate.
|
|
"""
|
|
l_metadata = metadata.layers[l_name]
|
|
for p_name in layer_container.annotation_attrs:
|
|
p_metadata = l_metadata.params[p_name]
|
|
param = getattr(layer_container, p_name)
|
|
|
|
if param is None:
|
|
continue
|
|
|
|
core_param = alloc_fn(param, buffer, p_metadata.core_param.offset)
|
|
core_param.copy_(param)
|
|
|
|
aux_params = {}
|
|
|
|
for t_name, tensor in param.aux_attrs.items():
|
|
t_view = alloc_fn(tensor, buffer, p_metadata.aux_params[t_name].offset)
|
|
aux_params[t_name] = t_view
|
|
t_view.copy_(tensor)
|
|
|
|
setattr(layer_container, p_name, InferenceParameter.initialize(core_param, **aux_params))
|
|
|
|
for i, layer in enumerate(transformer_containers):
|
|
l_name = f"transformer_layer_{i}"
|
|
copy_layer(layer, l_name)
|
|
|
|
l_name = "non_transformer"
|
|
copy_layer(non_transformer_container, l_name)
|
|
|
|
return buffer, metadata
|
|
|
|
|
|
def restore_inference_model(buffer: torch.Tensor, metadata: ModelMetadata,
|
|
transformer_containers: Iterable[LayerContainer],
|
|
non_transformer_container: LayerContainer) -> None:
|
|
"""
|
|
Restore the model from the buffer and metadata.
|
|
|
|
Arguments:
|
|
buffer: Buffer containing the model parameters.
|
|
metadata: Metadata for the model.
|
|
transformer_containers: Iterable of transformer layer containers.
|
|
non_transformer_container: Non-transformer layer container.
|
|
"""
|
|
alloc_fn = RaggedUtilsBuilder().load().allocate_view_like
|
|
|
|
def restore_layer(layer_container: LayerContainer, l_name: str) -> None:
|
|
"""
|
|
Local method for restoring a layer container from a flattened buffer. This
|
|
only constructs views for the parameters onto the buffer. No data movement
|
|
is performed.
|
|
|
|
Arguments:
|
|
layer_container: The layer container to restore.
|
|
l_name: The name of the layer container to key the metadata.
|
|
|
|
Captured Variables:
|
|
buffer: The flattened buffer to reconstruct views on top of.
|
|
metadata: The metadata object describing the each parameter in the model.
|
|
"""
|
|
l_metadata = metadata.layers[l_name]
|
|
|
|
for p_name in layer_container.annotation_attrs:
|
|
p_metadata = l_metadata.params[p_name]
|
|
|
|
if p_metadata.core_param.offset == -1:
|
|
layer_container.direct_injection(p_name, None)
|
|
continue
|
|
|
|
dummy_tensor = torch.empty([], dtype=STR_TO_DTYPE[p_metadata.core_param.dtype])
|
|
core_param = alloc_fn(p_metadata.core_param.shape, p_metadata.core_param.strides, dummy_tensor, buffer,
|
|
p_metadata.core_param.offset)
|
|
|
|
aux_params = {}
|
|
|
|
for t_name, t_metadata in p_metadata.aux_params.items():
|
|
dummy_tensor = torch.empty([], dtype=STR_TO_DTYPE[t_metadata.dtype])
|
|
t_view = alloc_fn(t_metadata.shape, t_metadata.strides, dummy_tensor, buffer, t_metadata.offset)
|
|
|
|
aux_params[t_name] = t_view
|
|
|
|
restored_param = InferenceParameter.initialize(core_param, **aux_params)
|
|
layer_container.direct_injection(p_name, restored_param)
|
|
|
|
for i, layer in enumerate(transformer_containers):
|
|
l_name = f"transformer_layer_{i}"
|
|
restore_layer(layer, l_name)
|
|
|
|
l_name = "non_transformer"
|
|
restore_layer(non_transformer_container, l_name)
|