chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:18:33 +08:00
commit 4ececc111a
2017 changed files with 331736 additions and 0 deletions
@@ -0,0 +1,84 @@
# Adding Support for a New Model in DeepSpeed Inference V2
Adding supoprt for a new model in DeepSpeed Inference requires developing three related components:
- Containers: These describe the parameters contained in the model
- Model implementation: How should the model be computed.
- Policy: The map for adding parameters to your containers and creating the model implementation.
In this tutorial, we will assume that you'd like to use a relatively traditionally styled Transformer model and will be able to inherit from `DSTransformerModelBase` and can take advantage of the utilities that provides.
## Defining Your Containers
A container is the bridge between the original model's parameters and how to transform them to serve them for inference. For a model implementation, there are two primary kinds of containers: transformer containers and non-transformer containers. A transformer container consists of the parameters for a single Transformer layer in the model. So this includes your traditional parameters like the projections for the fully connected network, or query-key-value projections. The non-transformer container will contain basically everything else! However, before defining these containers, we need to understand how to define an individual parameter.
In DeepSpeed inference, the original model parameters are populated into the model and mapped as dependencies to a parameter. A `Parameter` has two primary components: its dependencies and its `finalize` method. Let's do an example. In Llama models, the native format is for the `query`, `key`, and `value` projections to be performed independently. However, we can achieve higher throughput by fusing them into a single larger projection. We can define this fusion with a parameter:
```python
from deepspeed.inference.module_implementations.parameter_base import ParameterBase
class UnfusedQKVParameter(ParameterBase):
query: torch.Tensor
key: torch.Tensor
value: torch.Tensor
def finalize(self) -> torch.Tensor:
fused_param = torch.cat([self.query, self.key, self.value], dim=0)
return self.inference_model.transform_qkv_param(fused_param)
```
Let's walk through each part of this implementation. First, parameters should inherit from `ParameterBase`. This will allow it to automatically determine when its dependencies are met and set the appropriate components of a parent `LayerContainer`. The second key component is the type annotations on the class itself. Each type annotation represents a dependency of the parameter. Since the original Llama mode has separate query, key, and value dependencies, our fused parameter will declare dependencies for each. Finally, we have the `finalize` method. This method is automatically called once all dependencies on the layer are met and should return the final parameter.
In this `finalize` method, we are doing two things: the first is the act of fusing the parameters together through the concatenate method. Note that each of the dependencies can be accessed via `self.{name}`. The second is calling `self.inference_model.transform_qkv_param`. A parameter's finalize method always has access to the inference model. In this case we are using that to use a feature provided by `DSTransformerBase`. This method will automatically shard the parameter for tensor parallelism and then pass it to the linear module implementation to perform additional optimizations or shape transformations, like quantization.
Since many patterns are very common in Transformer models, `model_implementations.common_parameters` provides implementations for many of the patterns (all compatible with `DSTransformerBase`) to help accelerate development.
Once all parameters are created, we need to compose them into a layer container. In our simplified Llama model, let's assume there's only QKV and attention output projection matrices. A layer container would appear as the following:
```python
from deepspeed.inference.module_implementations.layer_container_base import LayerContainer
class ExampleContainer(LayerContainer):
qkvw: UnfusedQKVParameter
attn_o: AttentionOutputParameter
PARAM_MAPPING: {
"self_attn.q_proj.weight": "qkvw.query",
"self_attn.k_proj.weight": "qkvw.key",
"self_attn.v_proj.weight": "qkvw.value",
"self_attn.o_proj.weight": "attn_o.params",
}
```
Once again, we have a couple of key components. The first are parameter type annotations. Each annotation corresponds to a parameter that can be used in the model implementation. In the model implementation, I can simply write `container.qkvw` to access my fused and transformed QKV parameter. The second key component is the `PARAM_MAPPING` dictionary. This is our explicit mapping of the names of parameters in the source model to a parameter dependency. This mapping dictionary will be used by the policy to automatically populate dependencies.
Once you have written `LayerContainer`s for both the transformer and non-transformer parameters, it's time to work on the model implementation!
## Building a Model Implementation that Inherits from `DSTransformerBase`
By inheriting from `DSTransformerBase`, most of the implementation work for sharding and transforming parameters will be automatically handled for you. However, there are four key tasks that still need to be completed.
1. Defining the abstract properties based on your model configuration.
2. Configuring embedding and unembedding modules and the forward implementations for them.
3. Configuring the attention configuration and desired KV cache behaviors.
4. Writing the forward implementation for your layer.
## Writing a Policy
The `InferenceV2Policy` is the level of composition. This is the object that will be passed directly to the inference engine and will compose the model implementation and your containers to create an end-to-end solution. There are two main components to be implemented: the first is to create the model that you defined earlier. This is done by implementing the `instantiate_model` method of the policy. In general, this can just be implemented by calling the constructor for your model and passing the engine config, tensor-parallel communication object, and your custom model config.
The second component is to define how the parameters from the checkpoint will map to each container. From the section on `LayerContainer`s above, you may remember that the `LayerContainer` can handle the internal routing of a checkpoint parameter to its dependency. In order to find the correct `LayerContainer` though, we need a second abstraction: the `ContainerMap`.
A `ContainerMap` performs this mapping by categorizing checkpoint prefix strings to the type of container they map to. Typically, the easiest way to do this is through iterating over a model checkpoint's state dict or by iterating over the `named_parameters` of a PyTorch model. There are three types of mappings to define: the transformer mappings, the non-transformer mappings, and the what we'll call the rest. Let's work through an example:
```python
from deepspeed.inference.module_implementations.inference_policy_base import ContainerMap
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
transformer_containers = [MyTransformerContainer(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params("model.layers", transformer_containers)
non_transformer_container = MyNonTransformerContainer(self.model)
```
@@ -0,0 +1,22 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .inference_model_base import DSInferenceModelBase
from .inference_transformer_base import DSTransformerModelBase, DSMoETransformerModelBase
from .inference_policy_base import InferenceV2Policy, ContainerMap
from .sharding import *
# Model Implementations
from .llama_v2 import *
from .opt import *
from .mistral import *
from .mixtral import *
from .falcon import *
from .phi import *
from .phi3 import *
from .qwen import *
from .qwen_v2 import *
from .qwen_v2_moe import *
from .exaone4 import *
@@ -0,0 +1,13 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .attn_output_parameters import *
from .embedding_parameters import *
from .mlp_parameters import *
from .moe_parameters import *
from .norm_parameters import *
from .qkv_parameters import *
from .unembed_parameters import *
from .invfreq_parameters import *
@@ -0,0 +1,29 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from ...model_implementations.parameter_base import ParameterBase
"""
Common Attention Output Parameter Patterns
"""
class AttentionOutputParameter(ParameterBase):
"""
Attention output parameter container.
Note: The differentiation for something like GQA for this matrix is primarily
encompassed in the sharding logic, which is currently expected to be performed by
the model implementation.
"""
params: torch.Tensor
"""
Unsharded attention output parameter of shape [model_dim, model_dim]
"""
def finalize(self) -> torch.Tensor:
return self.inference_model.transform_attn_out_param(self.params)
@@ -0,0 +1,26 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from ...model_implementations.parameter_base import ParameterBase
"""
Embedding containers.
"""
class EmbeddingParameter(ParameterBase):
"""
Embedding container. This should be safe to use for all types of embeddings (i.e. word, position,
and token type).
"""
params: torch.Tensor
"""
Vocabulary parameter of shape [vocab_size, model_dim].
"""
def finalize(self) -> torch.Tensor:
return self.inference_model.transform_embedding_param(self.params)
@@ -0,0 +1,19 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from ...model_implementations.parameter_base import ParameterBase
"""
Common InvFreq Parameter Patterns
"""
class InvFreqParameter(ParameterBase):
params: torch.Tensor
def finalize(self) -> torch.Tensor:
return self.params.to(self.inference_model.activation_dtype.value)
@@ -0,0 +1,99 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from ...model_implementations.parameter_base import ParameterBase
"""
MLP Parameter Containers
"""
class MLP1Parameter(ParameterBase):
"""
First MLP projection weight container. This performs a straight pass-through to the
model implementation for transformation.
"""
params: torch.Tensor
def finalize(self) -> torch.Tensor:
# NOTE(cmikeh2): If we are gated but not in the format specified below, we should trigger a permutation here.
# I am not currently aware of any models that use this format (or how we should even detect it; probably should
# just be a different param entirely, but until then we'll just assume the format is correct).
return self.inference_model.transform_mlp_1_param(self.params)
class GatedMLPParameter(ParameterBase):
"""
Gated MLP projection container.
"""
gate_params: torch.Tensor
"""
Weight parameter for the gating matrix.
"""
up_params: torch.Tensor
"""
For lack of a better name, the non-gating weight parameters.
"""
def finalize(self) -> torch.Tensor:
"""
Our gated format (this is different from InferenceV1!) is to have the gate and activated neurons
interleaved. So if we have 4 output neurons (two effective neurons) with 4 input neurons, the finalized
parameter will look like:
[g0_0, g0_1, g0_2, g0_3]
[a0_0, a0_1, a0_2, a0_3]
[g1_0, g1_1, g1_2, g1_3]
[a1_0, a1_1, a1_2, a1_3]
As a reference, in inference v1, the format is:
[g0_0, g0_1, g0_2, g0_3]
[g1_0, g1_1, g1_2, g1_3]
[a0_0, a0_1, a0_2, a0_3]
[a1_0, a1_1, a1_2, a1_3]
"""
assert self.gate_params.shape[0] == self.up_params.shape[
0], "Gated MLP parameters must have the same number of neurons."
total_neurons = self.gate_params.shape[0] + self.up_params.shape[0]
# flip the order if even with the correct tokenizer we get wrong output
#fused_param = torch.cat([self.up_params, self.gate_params], dim=-1).reshape(total_neurons, -1)
fused_param = torch.cat([self.gate_params, self.up_params], dim=-1).reshape(total_neurons, -1)
return self.inference_model.transform_mlp_1_param(fused_param)
class FusedGatedMLPParameter(ParameterBase):
"""
Gated MLP projection container.
"""
params: torch.Tensor
"""
Weight parameter for the fused gating and non-gating weight parameters.
"""
def finalize(self) -> torch.Tensor:
gate_params = self.params[:self.params.shape[0] // 2]
up_params = self.params[self.params.shape[0] // 2:]
total_neurons = gate_params.shape[0] + up_params.shape[0]
fused_param = torch.cat([gate_params, up_params], dim=-1).reshape(total_neurons, -1)
return self.inference_model.transform_mlp_1_param(fused_param)
class MLP2Parameter(ParameterBase):
"""
Second MLP projection weight container. This performs a straight pass-through to the
model implementation for transformation.
"""
params: torch.Tensor
"""
Full weight parameter.
"""
def finalize(self) -> torch.Tensor:
return self.inference_model.transform_mlp_2_param(self.params)
@@ -0,0 +1,78 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from ...model_implementations.parameter_base import ParameterBase, ParamList
"""
Moe Parameters
These parameters are compatible with any model inheriting from ``DSMoETransformerModelBase``.
"""
class MoEGatingWeightParameter(ParameterBase):
"""
Gating weight matrix.
"""
params: torch.Tensor
"""
Projection matrix from the input activations to the gate logits.
"""
def finalize(self) -> torch.Tensor:
return self.inference_model.transform_moe_gate_param(self.params)
class UnfusedMoEMLP1Parameter(ParameterBase):
"""
This container should be used when the experts are held in separate parameters
and need to be joined into a single group.
"""
experts: ParamList("n_experts") # noqa: F821
def finalize(self) -> torch.Tensor:
stacked_experts = torch.stack([p for p in self.experts], dim=0)
return self.inference_model.transform_moe_mlp_1_param(stacked_experts)
class UnfusedMoEMLP2Parameter(ParameterBase):
"""
This container should be used when the experts are held in separate parameters
and need to be joined into a single group.
"""
experts: ParamList("n_experts") # noqa: F821
def finalize(self) -> torch.Tensor:
stacked_experts = torch.stack([p for p in self.experts], dim=0)
return self.inference_model.transform_moe_mlp_2_param(stacked_experts)
class UnfusedMoEGatedMLPParameter(ParameterBase):
"""
MoE Parameter for a gated activation function in which the gating matrix is not
fused in the same parameter as the non-gating matrix.
This is a stacked version of the ``GatedMLPParameter``. Please see that class for more
documentation on the layout of the parameters.
"""
gating_experts: ParamList("n_experts") # noqa: F821
up_experts: ParamList("n_experts") # noqa: F821
def finalize(self) -> torch.Tensor:
transposed_experts = []
for gate, up in zip(self.gating_experts, self.up_experts):
assert gate.shape[0] == up.shape[0], "Gated MLP parameters must have the same number of neurons."
total_neurons = gate.shape[0] + up.shape[0]
fused_expert = torch.cat([gate, up], dim=-1).reshape(total_neurons, -1)
transposed_experts.append(fused_expert)
stacked_experts = torch.stack(transposed_experts, dim=0)
return self.inference_model.transform_moe_mlp_1_param(stacked_experts)
@@ -0,0 +1,22 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from ...model_implementations.parameter_base import ParameterBase
"""
Common Attention Output Parameter Patterns
"""
class NormParameter(ParameterBase):
"""
Simple normalization container.
"""
params: torch.Tensor
def finalize(self) -> torch.Tensor:
return self.inference_model.transform_norm_param(self.params)
@@ -0,0 +1,115 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from ...model_implementations.parameter_base import ParameterBase
"""
Common QKV Parameter Patterns
"""
class FusedQKVParameter(ParameterBase):
"""
Traditional fused QKV parameters for QKV projection. This is functionally
a direct copy.
src_qkv_w shape: [3 * out_features, in_features]
qkv_w shape: [3 * out_features, in_features]
"""
params: torch.Tensor
def finalize(self) -> torch.Tensor:
return self.inference_model.transform_qkv_param(self.params)
class UnfusedQKVParameter(ParameterBase):
"""
QKV parameter container for unfused QKV projection.
src_param shapes: 3 x [out_features, in_features]
dst_param shape: [3 x out_features, in_features]
"""
q_params: torch.Tensor
k_params: torch.Tensor
v_params: torch.Tensor
def finalize(self):
fused_param = torch.cat([self.q_params, self.k_params, self.v_params], dim=0)
return self.inference_model.transform_qkv_param(fused_param)
def megatron_qkv_reshape(param: torch.Tensor, head_size: int, n_heads: int) -> torch.Tensor:
assert param.shape[0] == 3 * n_heads * head_size
all_heads = torch.chunk(param, chunks=3 * n_heads, dim=0)
q_heads = all_heads[::3]
k_heads = all_heads[1::3]
v_heads = all_heads[2::3]
return torch.cat([q_heads, k_heads, v_heads], dim=0)
class MegatronQKVParameter(ParameterBase):
"""
QKV parameter container for Megatron-style QKV projection. Megatron stores the parameter
as [n_heads, 3, head_size, in_features] whereas our inference system is built around
[3, n_heads, head_size, in_features]. This container handles the conversion.
Note: this container expects the model implementation to implement properties for
`head_size` and `n_heads`.
src_qkv_w shape: [3 * out_features, in_features]
qkv_w shape: [3 * out_features, in_features]
"""
params: torch.Tensor
def finalize(self) -> torch.Tensor:
head_size = self.inference_model.head_size
n_heads = self.inference_model.n_heads
transposed_param = megatron_qkv_reshape(self.params, head_size, n_heads)
return self.inference_model.transform_qkv_param(transposed_param)
def transform_gqa_megatron(src_param: torch.Tensor, head_size: int, n_q_heads: int, n_kv_heads: int) -> torch.Tensor:
assert src_param.shape[0] == (2 * n_kv_heads + n_q_heads) * head_size
head_ratio = n_q_heads // n_kv_heads
# Reshape to get the groups as the leading dimension
groups_leading_view = src_param.reshape(n_kv_heads, 2 + head_ratio, head_size, -1)
q_heads = groups_leading_view[:, :head_ratio, :, :].reshape(-1, groups_leading_view.shape[-1])
k_heads = groups_leading_view[:, head_ratio, :, :].reshape(-1, groups_leading_view.shape[-1])
v_heads = groups_leading_view[:, head_ratio + 1, :, :].reshape(-1, groups_leading_view.shape[-1])
# Squeeze will remove extra dimension for bias
return torch.cat([q_heads, k_heads, v_heads], dim=0).squeeze()
class GQAMegatronQKVParameter(ParameterBase):
"""
QKV parameter for Megatron-style QKV projection with GQA-style QKV projection. In this
storage format each of the groups is stored consecutively, so there will be multiple q_heads,
then one k head, and one v head.
Note: this container expects the model implementation to implement properties for
`head_size`, `n_q_heads`, and `n_kv_heads`.
src_qkv_w shape: [(2 * n_kv_heads + n_q_heads) * head_size, in_features]
qkv_w shape: [(2 * n_kv_heads + n_q_heads) * head_size, in_features]
"""
params: torch.Tensor
def finalize(self) -> torch.Tensor:
head_size = self.inference_model.head_size
n_q_heads = self.inference_model.n_heads_q
n_kv_heads = self.inference_model.n_heads_kv
transposed_param = transform_gqa_megatron(self.params, head_size, n_q_heads, n_kv_heads)
return self.inference_model.transform_qkv_param(transposed_param)
@@ -0,0 +1,26 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from ...model_implementations.parameter_base import ParameterBase
"""
Unembedding containers.
"""
class UnembedParameter(ParameterBase):
"""
Unembedding parameter. This will likely be mapped to the same original weight in the model as the
embedding, but we have a different preferred sharding approach.
"""
params: torch.Tensor
"""
Unembedding parameter of shape [vocab_size, model_dim].
"""
def finalize(self) -> torch.Tensor:
return self.inference_model.transform_unembed_param(self.params)
@@ -0,0 +1,5 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import Exaone4Policy
@@ -0,0 +1,49 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from deepspeed.inference.v2.model_implementations.common_parameters import *
from deepspeed.inference.v2.model_implementations.layer_container_base import LayerContainer
class Exaone4TransformerContainer(LayerContainer):
"""
Transformer layer container for the EXAONE 4.0 model.
"""
qkv_w: UnfusedQKVParameter
attn_out_w: AttentionOutputParameter
mlp_1_w: GatedMLPParameter
mlp_2_w: MLP2Parameter
q_norm_gamma: NormParameter
k_norm_gamma: NormParameter
post_attn_norm_gamma: NormParameter
post_ff_norm_gamma: NormParameter
PARAM_MAPPING = {
"self_attn.q_proj.weight": "qkv_w.q_params",
"self_attn.k_proj.weight": "qkv_w.k_params",
"self_attn.v_proj.weight": "qkv_w.v_params",
"self_attn.o_proj.weight": "attn_out_w.params",
"mlp.gate_proj.weight": "mlp_1_w.gate_params",
"mlp.up_proj.weight": "mlp_1_w.up_params",
"mlp.down_proj.weight": "mlp_2_w.params",
"self_attn.q_norm.weight": "q_norm_gamma.params",
"self_attn.k_norm.weight": "k_norm_gamma.params",
"post_attention_layernorm.weight": "post_attn_norm_gamma.params",
"post_feedforward_layernorm.weight": "post_ff_norm_gamma.params",
}
class Exaone4NonTransformerContainer(LayerContainer):
"""
Non-Transformer layer container for the EXAONE 4.0 model.
"""
word_emb: EmbeddingParameter
word_unembed: UnembedParameter
final_norm: NormParameter
PARAM_MAPPING = {
"model.embed_tokens.weight": "word_emb.params",
"model.norm.weight": "final_norm.params",
"lm_head.weight": "word_unembed.params",
}
@@ -0,0 +1,204 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...inference_utils import ActivationType, DtypeEnum
from ...model_implementations import *
from ...modules.configs import *
from ...modules.interfaces import *
from ...ragged import RaggedBatchWrapper
from ...kernels.core_ops.cuda_rms_norm.rms_norm import CUDARMSNorm
from .container import Exaone4NonTransformerContainer, Exaone4TransformerContainer
class Exaone4InferenceModel(DSTransformerModelBase):
"""
Inference model implementation for ragged batching for EXAONE 4.0 models.
Key differences from Mistral/Llama:
- Post-norm architecture (norm after attn/mlp, not before)
- QK-Norm (RMSNorm on Q and K projections per head)
"""
_non_transformer: Optional[Exaone4NonTransformerContainer]
_transformer: Optional[Iterable[Exaone4TransformerContainer]]
@property
def max_sequence_length(self) -> int:
return self._config.max_position_embeddings
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return getattr(self._config, "head_dim", self.model_dim // self.n_heads)
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return self._config.intermediate_size
@property
def n_heads_kv(self) -> int:
return self._config.num_key_value_heads
@property
def activation_dtype(self) -> DtypeEnum:
if self._config.torch_dtype == torch.float16:
return DtypeEnum.fp16
elif self._config.torch_dtype == torch.bfloat16:
return DtypeEnum.bf16
else:
raise NotImplementedError("Only fp16 and bf16 are supported")
@property
def mlp_activation_fn(self) -> ActivationType:
activation = self._config.hidden_act.lower()
if activation == "silu":
return ActivationType.SiGLU
elif activation == "gelu":
return ActivationType.GEGLU
elif activation == "relu":
return ActivationType.ReGLU
else:
raise NotImplementedError(f"Activation {activation} not supported")
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.RMSNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.rotate_half
@property
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
rope_theta = getattr(self._config, "rope_theta", 1000000.0)
return RotateHalfConfig(theta_base=rope_theta)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._qk_norm = CUDARMSNorm(
channels=self.head_size,
fp_dtype=torch.float16 if self.activation_dtype == DtypeEnum.fp16 else torch.bfloat16,
epsilon=getattr(self._config, "rms_norm_eps", 1e-5),
)
def _apply_qk_norm(self, hidden_states: torch.Tensor, q_norm_gamma: torch.Tensor,
k_norm_gamma: torch.Tensor) -> torch.Tensor:
"""
Apply RMSNorm to Q and K projections independently per head.
hidden_states shape: [tokens, (n_q + n_kv + n_kv) * head_size]
"""
tokens = hidden_states.shape[0]
local_n_heads = self.n_heads_q_local
local_n_heads_kv = self.n_heads_kv_local
q_len = local_n_heads * self.head_size
kv_len = local_n_heads_kv * self.head_size
q = hidden_states[:, :q_len].contiguous()
k = hidden_states[:, q_len:q_len + kv_len].contiguous()
v = hidden_states[:, q_len + kv_len:]
# Reshape to [tokens * n_heads, head_size] for per-head RMSNorm
q = q.view(-1, self.head_size)
self._qk_norm(q, q, q_norm_gamma)
q = q.view(tokens, q_len)
k = k.view(-1, self.head_size)
self._qk_norm(k, k, k_norm_gamma)
k = k.view(tokens, kv_len)
hidden_states[:, :q_len] = q
hidden_states[:, q_len:q_len + kv_len] = k
return hidden_states
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
embed = self.embed(ragged_batch, self._non_transformer.word_emb)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
"""
EXAONE 4.0 uses post-norm architecture:
hidden = attn(hidden)
hidden = post_attn_norm(hidden)
residual = residual + hidden
hidden = mlp(residual)
hidden = post_ff_norm(hidden)
residual = residual + hidden
"""
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
# Attention block
hidden_states = self.qkv(hidden_states, cur_params.qkv_w, b=None)
hidden_states = self._apply_qk_norm(hidden_states, cur_params.q_norm_gamma, cur_params.k_norm_gamma)
hidden_states = self.attn(hidden_states, kv_cache, ragged_batch_info)
hidden_states = self.attn_out(hidden_states, cur_params.attn_out_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
# Post-attn norm + residual add
_, hidden_states = self.norm(hidden_states, None, cur_params.post_attn_norm_gamma, beta=None)
residual.add_(hidden_states)
# MLP block
hidden_states = self.mlp_1(residual, cur_params.mlp_1_w, b=None)
hidden_states = self.mlp_2(hidden_states, cur_params.mlp_2_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
# Post-ff norm + residual add
_, hidden_states = self.norm(hidden_states, None, cur_params.post_ff_norm_gamma, beta=None)
residual.add_(hidden_states)
return residual, residual
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed,
ragged_batch_info,
gamma=self._non_transformer.final_norm)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer(layer_idx, residual, residual, wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,27 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .container import Exaone4NonTransformerContainer, Exaone4TransformerContainer
from .model import Exaone4InferenceModel
class Exaone4Policy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> Exaone4InferenceModel:
return Exaone4InferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
transformer_containers = [Exaone4TransformerContainer(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['model.layers'], transformer_containers)
map.set_non_transformer_params(Exaone4NonTransformerContainer(self.model))
map.set_unmapped_params([])
return map
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import FalconPolicy
@@ -0,0 +1,129 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Create a container object to save model-specific tensors using the policy file above.
from ..common_parameters import *
from ..layer_container_base import LayerContainer
'''
# HF Falcon 7b model looks like this:
FalconForCausalLM(
(transformer): FalconModel(
(word_embeddings): Embedding(65024, 4544)
(h): ModuleList(
(0-31): 32 x FalconDecoderLayer(
(self_attention): FalconAttention(
(maybe_rotary): FalconRotaryEmbedding()
(query_key_value): FalconLinear(in_features=4544, out_features=4672, bias=False)
(dense): FalconLinear(in_features=4544, out_features=4544, bias=False)
(attention_dropout): Dropout(p=0.0, inplace=False)
)
(mlp): FalconMLP(
(dense_h_to_4h): FalconLinear(in_features=4544, out_features=18176, bias=False)
(act): GELU(approximate='none')
(dense_4h_to_h): FalconLinear(in_features=18176, out_features=4544, bias=False)
)
(input_layernorm): LayerNorm((4544,), eps=1e-05, elementwise_affine=True)
)
)
(ln_f): LayerNorm((4544,), eps=1e-05, elementwise_affine=True)
)
(lm_head): Linear(in_features=4544, out_features=65024, bias=False)
)
'''
class FalconTransformerContainer(LayerContainer):
"""
Transformer layer container for the Falcon model.
"""
qkv_w: FusedQKVParameter
attn_out_w: AttentionOutputParameter
mlp_1_w: MLP1Parameter
mlp_2_w: MLP2Parameter
ln_attn_gamma: NormParameter
ln_attn_beta: NormParameter
PARAM_MAPPING = {
"self_attention.query_key_value.weight": "qkv_w.params",
"self_attention.dense.weight": "attn_out_w.params",
"mlp.dense_h_to_4h.weight": "mlp_1_w.params",
"mlp.dense_4h_to_h.weight": "mlp_2_w.params",
"input_layernorm.weight": "ln_attn_gamma.params",
"input_layernorm.bias": "ln_attn_beta.params",
}
class FalconNonTransformerContainer(LayerContainer):
"""
Non-Transformer layer container for the Falcon model.
"""
word_emb: EmbeddingParameter
word_unembed: UnembedParameter
final_norm_gamma: NormParameter
final_norm_beta: NormParameter
PARAM_MAPPING = {
"transformer.word_embeddings.weight": "word_emb.params",
"transformer.ln_f.weight": "final_norm_gamma.params",
"transformer.ln_f.bias": "final_norm_beta.params",
"lm_head.weight": "word_unembed.params",
}
'''
# HF Falcon 40b model looks like this:
FalconForCausalLM(
(transformer): FalconModel(
(word_embeddings): Embedding(65024, 8192)
(h): ModuleList(
(0-59): 60 x FalconDecoderLayer(
(self_attention): FalconAttention(
(maybe_rotary): FalconRotaryEmbedding()
(query_key_value): FalconLinear(in_features=8192, out_features=9216, bias=False)
(dense): FalconLinear(in_features=8192, out_features=8192, bias=False)
(attention_dropout): Dropout(p=0.0, inplace=False)
)
(mlp): FalconMLP(
(dense_h_to_4h): FalconLinear(in_features=8192, out_features=32768, bias=False)
(act): GELU(approximate='none')
(dense_4h_to_h): FalconLinear(in_features=32768, out_features=8192, bias=False)
)
(ln_attn): LayerNorm((8192,), eps=1e-05, elementwise_affine=True)
(ln_mlp): LayerNorm((8192,), eps=1e-05, elementwise_affine=True)
)
)
(ln_f): LayerNorm((8192,), eps=1e-05, elementwise_affine=True)
)
(lm_head): Linear(in_features=8192, out_features=65024, bias=False)
)
'''
class FalconNewArchTransformerContainer(LayerContainer):
"""
Transformer layer container for the Falcon model.
"""
qkv_w: GQAMegatronQKVParameter
attn_out_w: AttentionOutputParameter
mlp_1_w: MLP1Parameter
mlp_2_w: MLP2Parameter
ln_attn_gamma: NormParameter
ln_attn_beta: NormParameter
ln_mlp_gamma: NormParameter
ln_mlp_beta: NormParameter
PARAM_MAPPING = {
"self_attention.query_key_value.weight": "qkv_w.params",
"self_attention.dense.weight": "attn_out_w.params",
"mlp.dense_h_to_4h.weight": "mlp_1_w.params",
"mlp.dense_4h_to_h.weight": "mlp_2_w.params",
"ln_attn.weight": "ln_attn_gamma.params",
"ln_attn.bias": "ln_attn_beta.params",
"ln_mlp.weight": "ln_mlp_gamma.params",
"ln_mlp.bias": "ln_mlp_beta.params",
}
@@ -0,0 +1,213 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...inference_utils import ActivationType, DtypeEnum
from .. import *
from ...modules.configs import *
from ...modules.interfaces import *
from ...ragged import RaggedBatchWrapper
from .container import FalconNonTransformerContainer, FalconTransformerContainer
class FalconInferenceModel(DSTransformerModelBase):
"""
Inference model implementation for ragged batching for Llama-2 models.
"""
_non_transformer: Optional[FalconNonTransformerContainer]
"""
Embed + unembed container. Specializing the type annotation.
"""
_transformer: Optional[Iterable[FalconTransformerContainer]]
"""
Per-layer transformer container. Specializing the type annotation.
"""
"""
Properties inherited from `DSInferenceModelBase`
"""
@property
def max_sequence_length(self) -> int:
return self._config.max_seq_length
"""
Properties inherited from `DSTransformerModelBase`
"""
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return self.model_dim // self.n_heads
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return 4 * self._config.hidden_size
@property
def n_heads_kv(self) -> int:
return self._config.num_kv_heads if (self._config.new_decoder_architecture
or not self._config.multi_query) else 1
@property
def activation_dtype(self) -> DtypeEnum:
if self._config.torch_dtype == torch.float16:
return DtypeEnum.fp16
elif self._config.torch_dtype == torch.bfloat16:
return DtypeEnum.bf16
else:
raise NotImplementedError("Only fp16 and bf16 are supported")
@property
def mlp_activation_fn(self) -> ActivationType:
return ActivationType.GELU
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.LayerNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.rotate_half
@property
def positional_embedding_config(self) -> RotateHalfConfig:
"""
The positional embedding configuration for the model.
"""
return RotateHalfConfig()
"""
Forward implementations
"""
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs the embedding lookup prior to running the transformer of the model.
Arguments:
ragged_batch (RaggedBatchWrapper): The batch to embed.
Returns:
torch.Tensor: The embedded batch.
"""
embed = self.embed(ragged_batch, self._non_transformer.word_emb)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer_layer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Executes one (slightly offset) layer of the transformer. This implementation does a peak-ahead
optimization to fuse the layer norm of the next layer into the current layer.
Arguments:
layer_idx (int): The index of the layer to execute.
residual (torch.Tensor): The residual tensor from the previous layer.
hidden_states (torch.Tensor): The hidden states from the previous layer. This is the
hidden states after pre normalization.
ragged_batch_info (RaggedBatchWrapper): The batch metadata.
"""
assert self.config.parallel_attn, "Only parallel attention implementation is supported"
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
attn_ln_out = hidden_states
attn_hidden_state = self.qkv(attn_ln_out, cur_params.qkv_w, b=None)
attn_hidden_state = self.attn(attn_hidden_state, kv_cache, ragged_batch_info)
attention_output = self.attn_out(attn_hidden_state, cur_params.attn_out_w, b=None)
if self.config.new_decoder_architecture:
residual, mlp_ln_out = self.norm(residual,
None,
gamma=cur_params.ln_mlp_gamma,
beta=cur_params.ln_mlp_beta)
else:
mlp_ln_out = hidden_states
mlp_hidden_state = self.mlp_1(mlp_ln_out, cur_params.mlp_1_w, b=None)
mlp_output = self.mlp_2(mlp_hidden_state, cur_params.mlp_2_w, b=None)
mlp_output.add_(attention_output)
if self.tp_size > 1:
dist.all_reduce(mlp_output, group=self._base_mp_group)
if layer_idx != self.num_layers - 1:
next_params = self._transformer[layer_idx + 1]
residual, mlp_output = self.norm(residual,
mlp_output,
next_params.ln_attn_gamma,
beta=next_params.ln_attn_beta)
else:
# On last layer, we just need to perform the residual add. Adding into the residual
# here is safe.
residual.add_(mlp_output)
return residual, mlp_output
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs unembedding of the hidden states to logits. This will only sample the final
token of each sequence.
"""
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed,
ragged_batch_info,
gamma=self._non_transformer.final_norm_gamma,
beta=self._non_transformer.final_norm_beta)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
residual, hidden_states = self.norm(residual,
None,
gamma=self._transformer[0].ln_attn_gamma,
beta=self._transformer[0].ln_attn_beta)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer_layer(layer_idx, residual, hidden_states,
wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,33 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .container import FalconNonTransformerContainer, FalconTransformerContainer
from .container import FalconNewArchTransformerContainer
from .model import FalconInferenceModel
class FalconPolicy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> FalconInferenceModel:
return FalconInferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
trans_container_cls = FalconNewArchTransformerContainer if self._model_config.new_decoder_architecture else FalconTransformerContainer
transformer_containers = [trans_container_cls(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['transformer.h'], transformer_containers)
map.set_non_transformer_params(FalconNonTransformerContainer(self.model))
map.set_unmapped_params(
[f'model.layers.{i}.self_attn.rotary_emb.inv_freq' for i in range(self.model.num_layers)])
return map
@@ -0,0 +1,282 @@
# 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)
@@ -0,0 +1,272 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from abc import ABC, abstractmethod
from typing import Iterable, Optional, Tuple, Type
import torch
import deepspeed.comm as dist
from ..ragged import DSStateManager, RaggedBatchWrapper
from ..ragged.manager_configs import KVCacheConfig
from ..ragged import DSSequenceDescriptor
from ..model_implementations.layer_container_base import LayerContainer
from ..config_v2 import RaggedInferenceEngineConfig
from .flat_model_helpers import ModelMetadata
try:
from functools import cached_property
except ImportError:
def cached_property(func):
return property(func)
"""
This abstract class defines the interfaces that a model implementation should implement
in order to include anything that may be called by the engine. Most models should be able
to inherit from `DSInferenceTransformerModelBase` to reduce implementation work so it is recommended
to begin there.
"""
"""
Placeholder for typing the model config, which can vary based on model implementation/
"""
DSModelImplementationConfig = Type['DSModelImplementationConfig']
"""
Placeholder for typing the distributed comm object.
TODO(cmikeh2): Replace when we have a more defined API for the inference communication system.
"""
MPType = Type["MPType"]
class DSInferenceModelBase(torch.nn.Module, ABC):
"""
Implementation of a model for inference composable with ragged batching.
"""
_config: DSModelImplementationConfig
"""
Model-specific configuration. No abstraction surrounds this yet.
"""
_engine_config: RaggedInferenceEngineConfig
"""
Engine configuration.
"""
_base_mp_group: MPType
"""
Base communication group for Tensor-parallel inference.
"""
_non_transformer: Optional[LayerContainer]
"""
Abstract container for storing both embedding (pre-transformer) and unembedding (post-transformer)
parameters. This attribute should be None at model instantiation until the Policy sets
the model parameters. These parameters are grouped together since many model implementations
will tie the embedding and unembedding parameters together.
"""
_transformer: Optional[Iterable[LayerContainer]]
"""
List of abstract containers (1 per layer) for storing transformer (transformer)
parameters. This attribute should be None at model instantiation until the Policy
sets the model parameters.
"""
state_manager: Optional[DSStateManager]
"""
Since the state manager is lazy initialized, by the engine, it is not guaranteed to be present
until full initialization.
"""
def __init__(self, config: DSModelImplementationConfig, engine_config: RaggedInferenceEngineConfig,
base_mp_group: MPType) -> None:
"""
Minimal initialization of the model.
Arguments:
config (DSModelImplementationConfig): Model-specific configuration. No assumptions
should be made about this config that are not closely tied to the specific
model implementation.
engine_config (RaggedInferenceEngineConfig): Engine configuration.
base_mp_group (MPType): Base communication group for Tensor-parallel inference.
"""
super().__init__()
self._config = config
self._engine_config = engine_config
self._base_mp_group = base_mp_group
# Set to None until the Policy sets the model parameters
self._non_transformer = None
self._transformer = None
self._flattened_param_buffer = None
self._flattened_param_metadata = None
@property
def config(self) -> DSModelImplementationConfig:
"""
The model config.
"""
return self._config
def set_parameters(self, transformer: Iterable[LayerContainer], non_transformer: LayerContainer,
flattened_param_buffer: torch.Tensor, flattened_param_metadata: ModelMetadata):
"""
Set the model parameters for the embedding, transformer, and unembedding containers.
"""
self._transformer = transformer
self._non_transformer = non_transformer
self._flattened_param_buffer = flattened_param_buffer
self._flattened_param_metadata = flattened_param_metadata
def set_state_manager(self, state_manager: DSStateManager):
"""
Sets the state manager attribute. This is called by the inference engine after
the model is fully initialized.
"""
self.state_manager = state_manager
@cached_property
def tp_rank(self) -> int:
"""
The rank of the current process.
# TODO(cmikeh2): Kind of a hack right now, but this is too verbose to use at
the frequency we need.
"""
return dist.get_rank(group=self._base_mp_group)
@cached_property
def tp_size(self) -> int:
"""
The total number of processes.
# TODO(cmikeh2): Kind of a hack right now, but this is too verbose to use at
the frequency we need.
"""
return dist.get_world_size(group=self._base_mp_group)
@property
def model_config(self):
"""
The model config.
"""
return self._config
@property
def engine_config(self):
"""
The engine config.
"""
return self._engine_config
@property
def flattened_params(self) -> Optional[torch.Tensor]:
"""
The flattened parameter buffer.
"""
return self._flattened_param_buffer
@property
def flattened_param_metadata(self) -> Optional[ModelMetadata]:
"""
The flattened parameter metadata.
"""
return self._flattened_param_metadata
@abstractmethod
def get_kv_requirements(self, sequence: DSSequenceDescriptor, max_new_tokens: int,
max_new_blocks: Tuple[int, ...]) -> Tuple[int, torch.Tensor]:
"""
Given a sequence and the number of new tokens in the sequence, determine the
number of new KV blocks needed to support the sequence. This method is
used to help the engine provide schedulability APIs and can be used as a helper
for ``maybe_allocate_kv``.
Args:
sequence (DSSequenceDescriptor): The sequence for which to allocate KV-storage.
max_new_tokens (int): Maximum number of tokens to hypothetically schedule.
max_new_blocks (int): Maximum number of blocks to hypothetically allocate.
Returns:
Tuple[int, torch.Tensor]: The tuple of number of tokens scheduled and number
of blocks allocated (per KV cache). In general, only one of these numbers will
match the corresponding input argument, but this is not guaranteed.
"""
raise NotImplementedError()
@abstractmethod
def get_remaining_block_capacity(self, sequence: DSSequenceDescriptor) -> int:
raise NotImplementedError()
@abstractmethod
def maybe_allocate_kv(self, sequence: DSSequenceDescriptor, n_new_tokens: int) -> None:
"""
Given a sequence and the number of new tokens in the sequence, determine
whether or not additional KV-storage is needed and allocate it if so.
Args:
sequence (DSSequenceDescriptor): The sequence for which to allocate KV-storage.
n_new_tokens (int): The number of new tokens in the sequence.
"""
raise NotImplementedError()
@abstractmethod
def kv_cache_config(self) -> Tuple[KVCacheConfig, ...]:
"""
Return the KV-cache configuration for this model. This should be a tuple of one or more
KVCacheConfig objects (one for each distinct cache group).
"""
raise NotImplementedError()
@property
@abstractmethod
def max_sequence_length(self) -> int:
"""
The maximum sequence length supported by the model.
"""
...
def maybe_free_kv(self, sequence: DSSequenceDescriptor) -> None:
"""
After completing a forward pass, determine whether or not there are any KV blocks
that maybe freed since they are no longer in use.
Consider the following example:
We have a block size of 4 and a local window size of 8. At the beginning of the forward
pass there 10 tokens had been seen and the new forward has a size of 4. This would lend
itself to the following cache structure prior to the forward:
[[0, 1, 2*, 3*] [4*, 5*, 6*, 7*] [8*, 9*, x, x] [x x x x]]
Where x's denote empty cache locations and * denote values that are needed for attention
of the next open slot. After the forward, the cache would look like the following:
[[0, 1, 2, 3] [4, 5, 6*, 7*] [8*, 9*, 10*, 11*] [12* 13* x x]]
In this case, the first block is no longer needed since it is not needed for any future
local attention windows. This function would be responsible for freeing that block.
Default behavior assumes no local patterns that require freeing and in general should
be sufficient.
"""
pass
@abstractmethod
def prepare_batch(self, wrapped_batch: RaggedBatchWrapper) -> None:
"""
This will be called before each forward with the intent of building forward-specific metadata
about a batch. The intent here is to build data structures like attention atoms without necessarily
needing to implement graphable kernels to do so.
Abstract so as to force model implementations to opt out of doing anything here explicitly.
"""
raise NotImplementedError()
def forward(wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
"""
Complete a forward pass of the model. This interface should be graphable, so it
should not rely on the ability to use python control flow.
"""
raise NotImplementedError()
@@ -0,0 +1,220 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import json
from abc import ABC, ABCMeta, abstractmethod
from typing import Any, Iterable, List, Optional, Union
import torch
from ..config_v2 import RaggedInferenceEngineConfig
from ..checkpoint import CheckpointEngineBase
from ..logging import inference_logger
from .layer_container_base import LayerContainer
from .inference_model_base import DSInferenceModelBase
from .flat_model_helpers import (
flatten_inference_model,
make_param_filename,
make_metadata_filename,
ModelMetadata,
restore_inference_model,
)
POLICIES = {}
class ContainerMap:
def __init__(self) -> None:
self._prefix_map = {}
self._transformer_params = None
self._non_transformer_params = None
@property
def transformer_params(self) -> Iterable[LayerContainer]:
return self._transformer_params
@property
def non_transformer_params(self) -> LayerContainer:
return self._non_transformer_params
def set_transformer_params(self, prefixes: Union[str, Iterable[str]], containers: List[LayerContainer]) -> None:
if not isinstance(containers, list):
raise ValueError(
f"The transformer containers should be a list, of one container per layer, but got {type(containers)} instead."
)
self._transformer_prefixes = prefixes if isinstance(prefixes, list) else [prefixes]
self._transformer_params = containers
def set_non_transformer_params(self, container: LayerContainer) -> None:
self._non_transformer_params = container
def set_unmapped_params(self, prefixes: Union[str, Iterable[str]]) -> None:
self._unmapped_prefixes = prefixes
def map_param(self, name, parameter) -> None:
for unmapped_prefix in self._unmapped_prefixes:
if name.startswith(unmapped_prefix):
inference_logger().debug(f"Ignoring: {name} for {unmapped_prefix}")
return
for transformer_prefix in self._transformer_prefixes:
if name.startswith(transformer_prefix):
popped_name = name[len(transformer_prefix) + 1:]
layer_idx = popped_name.split(".")[0]
assert layer_idx.isdigit(
), f"expected name to start w. list index but got {layer_idx} instead, name={name}"
layer_idx = int(layer_idx)
inference_logger().debug(
f"Setting: {'.'.join(popped_name.split('.')[1:])} for layer-idx={layer_idx} to {parameter.shape}")
self._transformer_params[layer_idx].set_dependency(".".join(popped_name.split(".")[1:]), parameter)
return
try:
inference_logger().debug(f"Setting: {name} to {parameter.shape}")
self._non_transformer_params.set_dependency(name, parameter)
except ValueError:
# Catch the ValueError here from the non_transformer_params because we are knowingly
# calling it with something that may not match. This should allow us to raise a slightly more
# informative error message.
raise ValueError(f"Cannot find container for {name}, please double check the Containers/ContainerMap")
def validate(self) -> None:
if not self._non_transformer_params.is_initialized:
raise RuntimeError("Non-transformer parameters not fully initialized after checkpoint load.")
for layer_idx, container in enumerate(self._transformer_params):
if not container.is_initialized:
raise RuntimeError(
f"Transformer container at index {layer_idx} not fully initialized after checkpoint load.")
class PolicyMeta(ABCMeta):
def __new__(cls, name, bases, dct):
new_obj = super().__new__(cls, name, bases, dct)
if name != "InferenceV2Policy":
POLICIES[name] = new_obj
return new_obj
class InferenceV2Policy(ABC, metaclass=PolicyMeta):
"""
The InferenceV2Policy is the base class for all inference policies. An inference policy
is responsible for instantiating the inference model and mapping the parameters from the
checkpoint engine to the model itself.
"""
def __init__(
self,
model_config: Any,
checkpoint_engine: Optional[CheckpointEngineBase] = None,
inf_checkpoint_path: Optional[str] = None,
) -> None:
"""
Create the Policy with sufficient context to build the model. There are two supported
model creation mechanisms.
The first is the generalized ``checkpoint_engine`` which
will iterate over the parameters of the model and provide them to the policy. These in
turn will be sharded/transformed by the model implementation.
The second is used to re-create a previously serialized DeepSpeed inference model. These
checkpoints should not be used across different model backend configurations.
TODO(cmikeh2): Enforce this in code
"""
if checkpoint_engine is None and inf_checkpoint_path is None:
raise ValueError("Either checkpoint_engine or ds_checkpoint_path must be provided.")
if checkpoint_engine is not None and inf_checkpoint_path is not None:
raise ValueError("Only one of checkpoint_engine or ds_checkpoint_path can be provided.")
self._checkpoint_engine = checkpoint_engine
self._inf_checkpoint_path = inf_checkpoint_path
self._model_config = model_config
def build_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> DSInferenceModelBase:
"""
Completely instantiate the inference model. This will both create the ops needed to run the
model, as well as load the model parameters via the checkpoint engine. For more context
on each of these components please see ``instantiate_model`` and ``populate_model_parameters``.
Arguments:
engine_config: The config that has been used to instantiate the engine. This is used
to communicate to the model implementation the limits on batches (sequences/tokens)
and bound the size of intermediate buffers.
mp_group: Object to enable communication between tensor parallel ranks.
Returns:
DSInferenceModelBase: An implementation of the inference model abstraction that will be
run by the engine.
"""
self.model = self.instantiate_model(engine_config, mp_group)
self.populate_model_parameters()
return self.model
@abstractmethod
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig) -> DSInferenceModelBase:
"""
Instantiate the inference model. Depending on the engine/model config, this could be where
different model implementations could be selected.
Arguments:
engine_config: The config that has been used to instantiate the engine. This is used
to communicate to the model implementation the limits on batches (sequences/tokens)
and bound the size of intermediate buffers.
Returns:
DSInferenceModelBase: An implementation of the inference model abstraction that will be
run by the engine.
"""
...
@abstractmethod
def build_container_map(self) -> ContainerMap:
"""
Build a dictionary representing the structure of the string prefixes leading
to the parameters to be mapped to the container.
Returns:
ContainerMap: An instantiated mapping describing how checkpoint prefixes map
to ``LayerContainer`` instances.
"""
raise NotImplementedError()
def populate_model_parameters(self) -> None:
"""
This model will iterate over the parameters (as provided by the checkpoint engine) and
use the container map built by ``build_container_map`` to populate the model
"""
container_map = self.build_container_map()
if self._checkpoint_engine is not None:
for name, parameter in self._checkpoint_engine.parameters():
container_map.map_param(name, parameter)
buffer, metadata = flatten_inference_model(container_map.transformer_params,
container_map.non_transformer_params, self.__class__.__name__)
else:
buffer_path = make_param_filename(self._inf_checkpoint_path, self.model.tp_rank, self.model.tp_size)
metadata_path = make_metadata_filename(self._inf_checkpoint_path, self.model.tp_rank, self.model.tp_size)
buffer = torch.load(buffer_path, weights_only=False)
metadata = json.load(open(metadata_path, "r"))
metadata = ModelMetadata.parse_raw(metadata)
restore_inference_model(buffer, metadata, container_map.transformer_params,
container_map.non_transformer_params)
container_map.validate()
self.model.set_parameters(transformer=container_map.transformer_params,
non_transformer=container_map.non_transformer_params,
flattened_param_buffer=buffer,
flattened_param_metadata=metadata)
@@ -0,0 +1,617 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from abc import abstractmethod
from typing import Optional
import torch
from deepspeed.accelerator import get_accelerator
from ..config_v2 import RaggedInferenceEngineConfig
from ..inference_utils import ActivationType, ceil_div, is_gated
from ..model_implementations import *
from ..model_implementations.sharding import *
from ..modules.configs import (
DSEmbeddingsConfig,
DSLinearConfig,
DSMoEConfig,
DSNormConfig,
DSSelfAttentionConfig,
DSUnembedConfig,
NormTypeEnum,
PositionalEmbeddingType,
RotateHalfConfig,
)
from ..modules import heuristics
from ..ragged import (
DSSequenceDescriptor,
KVCacheConfig,
RaggedBatchWrapper,
)
from .inference_model_base import (
DSInferenceModelBase,
DSModelImplementationConfig,
MPType,
)
from ..inference_parameter import InferenceParameter
try:
from functools import cached_property
except ImportError:
def cached_property(func):
return property(func)
class DSTransformerModelBase(DSInferenceModelBase):
"""
Dimensioning properties
"""
@property
@abstractmethod
def num_layers(self) -> int:
"""
Number of the layers in the model
"""
...
@property
@abstractmethod
def model_dim(self) -> int:
"""
Size of embedding projection and residuals.
"""
...
@property
@abstractmethod
def vocab_size(self) -> int:
"""
Size of the vocabulary (including padding).
"""
...
@property
@abstractmethod
def head_size(self) -> int:
"""
Size of each attention head.
"""
...
@property
@abstractmethod
def n_heads(self) -> int:
"""
The number of query heads on the model. This should not take into account
any dimension reductions from model sharding.
"""
...
@property
def n_heads_q(self) -> int:
"""
Alias to n_heads.
"""
return self.n_heads
@property
def n_heads_kv(self) -> int:
"""
The number of key and value heads on the model. For GQA or MQA, overload this attribute.
Otherwise it adopts MHA formulations and uses n_heads. This should not take into account
any dimension reductions from model sharding.
"""
return self.n_heads
@property
@abstractmethod
def intermediate_dim(self) -> int:
"""
The size of the (unsharded) intermediate projection dim. For a gated activation function
this is the size of the input to the second MLP layer. This should not take into account
any dimension reductions from model sharding.
"""
...
@property
@abstractmethod
def positional_embedding_type(self) -> PositionalEmbeddingType:
"""
The type of positional embedding used by the model.
"""
...
"""
Architectural properties
"""
@property
@abstractmethod
def activation_dtype(self) -> torch.dtype:
"""
The activation dtype of the model.
"""
...
@property
@abstractmethod
def mlp_activation_fn(self) -> ActivationType:
"""
The activation function used in the MLP.
"""
...
@property
@abstractmethod
def norm_type(self) -> NormTypeEnum:
"""
The type of normalization used in the model.
"""
...
@property
@abstractmethod
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
"""
The positional embedding configuration for the model.
"""
...
"""
Derived helpers
"""
@cached_property
def n_heads_q_local(self) -> int:
"""
Number of local heads post sharding.
"""
return get_local_heads(self.tp_rank, self.tp_size, self.n_heads_q, self.n_heads_kv)[0]
@cached_property
def n_heads_kv_local(self) -> int:
"""
Number of local heads post sharding.
"""
return get_local_heads(self.tp_rank, self.tp_size, self.n_heads_q, self.n_heads_kv)[1]
@property
def gated_mlp(self) -> bool:
"""
Return a boolean to determine whether the model uses a gated activation function.
"""
return is_gated(self.mlp_activation_fn)
"""
Method implementations
"""
def __init__(self, config: DSModelImplementationConfig, engine_config: RaggedInferenceEngineConfig,
base_mp_group: MPType) -> None:
"""
Base implementation for initialization. By default, this will initialize
the traditional components of a transformer model:
- Embedding
- QKV projection
- Self attention
- Attention output projection
- Feed forward network
- Normalization
- Unembedding
Arguments:
config (DSModelImplementationConfig): Model-specific configuration. No assumptions
should be made about this config that are not closely tied to the specific
model implementation.
engine_config (RaggedInferenceEngineConfig): Engine configuration.
base_mp_group (MPType): Base communication group for Tensor-parallel inference.
"""
super().__init__(config, engine_config, base_mp_group)
self.make_norm_layer()
self.make_qkv_layer()
self.make_attn_layer()
self.make_attn_out_layer()
self.make_mlp_1_layer()
self.make_mlp_2_layer()
self.make_embedding_layer()
self.make_unembedding_layer()
self._kv_cache_config = None
######### Embedding #########
def make_embedding_layer(self) -> None:
"""
Performs setup and creates embedding DSModule. This will set the `self.embed` attribute.
"""
embed_config = DSEmbeddingsConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
residual_dtype=self.activation_dtype,
embedding_dim=self.model_dim,
)
self.embed = heuristics.instantiate_embed(embed_config, self._engine_config)
def transform_embedding_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Performs embedding sharding along the channels dimension.
"""
# Until we can do non-contiguous all-gather, we won't shard the embedding parameters.
param = param.to(self.activation_dtype.value)
return InferenceParameter.initialize(param)
######### Unembedding #########
def make_unembedding_layer(self) -> None:
"""
Performs setup and creates an unembedding layer. This implementation assumes
normalization prior to the LM head projection. If this does not match the model's
implementation, override this method. This will set the ``self.unembed`` attribute.
"""
unembed_dim = sharded_unembed_dim(self.vocab_size, self.tp_rank, self.tp_size)
unembed_config = DSUnembedConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
max_sequences=self._engine_config.state_manager.max_ragged_sequence_count,
dtype=self.activation_dtype,
model_dim=self.model_dim,
vocab_size=unembed_dim,
norm_type=self.norm_type,
)
self.unembed = heuristics.instantiate_unembed(unembed_config, self._engine_config)
if self.tp_size > 1:
self._comm_logits = torch.empty(self.tp_size,
self._engine_config.state_manager.max_ragged_sequence_count,
unembed_dim,
device=get_accelerator().current_device(),
dtype=self.activation_dtype.value)
self._return_logits = torch.empty(self._engine_config.state_manager.max_ragged_sequence_count,
self.vocab_size,
device=get_accelerator().current_device(),
dtype=self.activation_dtype.value)
def transform_unembed_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Performs sharding along the vocab dimension.
"""
param = shard_unembed_param(param, self.tp_rank, self.tp_size).to(self.activation_dtype.value)
return InferenceParameter.initialize(param)
######### QKV #########
def make_qkv_layer(self) -> None:
"""
Instantiates the linear projection layer for the QKV linear layer. This sets the
`self.qkv` attribute.
"""
out_features = qkv_out_features(self.model_dim, self.tp_rank, self.tp_size, self.head_size, self.n_heads_q,
self.n_heads_kv)
linear_config = DSLinearConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
in_channels=self.model_dim,
out_channels=out_features,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
)
self.qkv = heuristics.instantiate_linear(linear_config, self._engine_config)
def transform_qkv_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Passes a QKV parameter to the underlying implementation for any necessary
transformations.
Args:
param (torch.Tensor): The parameter to transform. This may be either a bias or weight and should have
the shape (out_neurons, in_neurons)
"""
param = shard_qkv_param(param, self.tp_rank, self.tp_size, self.head_size, self.n_heads_q, self.n_heads_kv)
return self.qkv.transform_param(param)
######### Attention #########
def make_attn_layer(self) -> None:
"""
Builds the attention layer for the model. This sets the `self.attn` attribute.
"""
softmax_scale = 1.0 / (self.head_size**0.5)
attn_config = DSSelfAttentionConfig(max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
n_heads_q=self.n_heads_q_local,
n_heads_kv=self.n_heads_kv_local,
head_size=self.head_size,
max_sequences=self._engine_config.state_manager.max_ragged_sequence_count,
scale_factor=softmax_scale,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
positional_embedding_type=self.positional_embedding_type,
positional_embedding_config=self.positional_embedding_config)
self.attn = heuristics.instantiate_attention(attn_config, self._engine_config)
def get_kv_requirements(self, sequence: DSSequenceDescriptor, max_new_tokens: int,
max_new_blocks: int) -> Tuple[int, int]:
"""
See ``DSInferenceModelBase.get_kv_requirements`` for documentation.
This method assumes an autoregressive dense attention pattern. Override this method
if this does not match the model's attention pattern.
"""
total_tokens = sequence.seen_tokens + max_new_tokens
req_blocks = ceil_div(total_tokens, self.attn.kv_block_size)
block_lim = req_blocks - sequence.cur_allocated_blocks
if block_lim <= max_new_blocks:
return max_new_tokens, block_lim
token_capacity = (max_new_blocks +
sequence.cur_allocated_blocks) * self.attn.kv_block_size - sequence.seen_tokens
return token_capacity, max_new_blocks
def get_remaining_block_capacity(self, sequence: DSSequenceDescriptor) -> int:
return sequence.seen_tokens % self.attn.kv_block_size
def maybe_allocate_kv(self, sequence: DSSequenceDescriptor, n_new_tokens: int) -> None:
"""
See ``DSInferenceModelBase.maybe_allocate_kv`` for documentation.
This method assumes an autoregressive dense attention pattern. Override this method
if this does not match the model's attention pattern.
"""
free_block = self.state_manager.free_blocks[0]
_, n_needed_blocks = self.get_kv_requirements(sequence, n_new_tokens, free_block)
if n_needed_blocks > 0:
new_blocks = self.state_manager.allocate_blocks(n_needed_blocks)
sequence.extend_kv_cache(new_blocks)
def kv_cache_config(self) -> Tuple[KVCacheConfig, ...]:
"""
See ``DSInferenceModelBase.kv_cache_config`` for documentation.
This method assumes an autoregressive dense attention pattern. Override this method
if this does not match the model's attention pattern.
"""
if self._kv_cache_config is None:
cache_shape = (self.num_layers, self.n_heads_kv_local, self.head_size)
max_blocks = ceil_div(self.max_sequence_length, self.attn.kv_block_size)
self._kv_cache_config = KVCacheConfig(block_size=self.attn.kv_block_size,
cache_shape=cache_shape,
cache_dtype=self.activation_dtype,
max_blocks_per_allocation_group=max_blocks)
return (self._kv_cache_config, )
def prepare_batch(self, wrapped_batch: RaggedBatchWrapper) -> None:
"""
See ``DSInferenceModelBase.prepare_batch`` for documentation.
This method assumes an autoregressive dense attention pattern. Override this method
if this does not match the model's attention pattern.
"""
self.attn.build_atoms(wrapped_batch)
######### Attention output #########
def make_attn_out_layer(self) -> None:
"""
Instantiates the linear projection layer for the attention output linear layer. This sets the
`self.attn_out` attribute.
"""
in_features = attn_out_in_features(self.model_dim, self.tp_rank, self.tp_size, self.head_size, self.n_heads_q,
self.n_heads_kv)
linear_config = DSLinearConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
in_channels=in_features,
out_channels=self.model_dim,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
)
self.attn_out = heuristics.instantiate_linear(linear_config, self._engine_config)
def transform_attn_out_param(self, param: torch.Tensor) -> Optional[InferenceParameter]:
"""
Shards an attention output projection parameter and passes it to the underlying
implementation for any necessary transformations. This will return `None` for bias parameters
if they are not on TP rank 0.
Args:
param (torch.Tensor): The parameter to transform. This may be either a bias or weight and should have
the shape (out_neurons, in_neurons).
"""
param = shard_attn_out_param(param, self.tp_rank, self.tp_size, self.head_size, self.n_heads_q,
self.n_heads_kv)
if param is not None:
param = self.attn_out.transform_param(param)
return param
######### MLP #########
def make_mlp_1_layer(self) -> None:
"""
Instantiates the linear projection layer for the first MLP in the feedforward network.
This sets the `self.mlp_1` attribute.
"""
shard_size = sharded_intermediate_dim(self.intermediate_dim, self.tp_size, self.tp_rank)
linear_config = DSLinearConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
in_channels=self.model_dim,
out_channels=shard_size,
activation=self.mlp_activation_fn,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
)
self.mlp_1 = heuristics.instantiate_linear(linear_config, self._engine_config)
def transform_mlp_1_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Shards the first MLP parameter and passes it to the underlying implementation
for any necessary transformations.
Args:
param (torch.Tensor): The parameter to transform. This may be either a bias or weight and should have
the shape (out_neurons, in_neurons).
"""
param = shard_mlp_1_param(param, self.tp_rank, self.tp_size, gated=self.gated_mlp)
return self.mlp_1.transform_param(param)
def make_mlp_2_layer(self) -> None:
"""
Instantiates the linear projection layer for the second MLP in the feedforward network.
This sets the `self.mlp_2` attribute.
"""
shard_size = sharded_intermediate_dim(self.intermediate_dim, self.tp_size, self.tp_rank)
linear_config = DSLinearConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
in_channels=shard_size,
out_channels=self.model_dim,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
)
self.mlp_2 = heuristics.instantiate_linear(linear_config, self._engine_config)
def transform_mlp_2_param(self, param: torch.Tensor) -> Optional[InferenceParameter]:
"""
Shards the second MLP parameter and passes it to the underlying implementation
for any necessary transformations. This will return `None` for bias parameters
if they are not on TP rank 0.
Args:
param (torch.Tensor): The parameter to transform. This may be either a bias or weight and should have
the shape (out_neurons, in_neurons).
"""
param = shard_mlp_2_param(param, self.tp_rank, self.tp_size)
if param is not None:
param = self.mlp_2.transform_param(param)
return param
######### Norm #########
def make_norm_layer(self) -> None:
"""
Instantiates the normalization layer for the model. This sets the `self.norm` attribute.
TODO(cmikeh2): In the future we'll distinguish between the different norm objects,
but for now we'll just use the same one for all of them.
"""
norm_config = DSNormConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
type=self.norm_type,
channels=self.model_dim,
residual_dtype=self.activation_dtype,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
)
self.norm = heuristics.instantiate_pre_norm(norm_config, self._engine_config)
def transform_norm_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Passes a normalization parameter to the underlying implementation for any
necessary transformations.
TODO(cmikeh2): In the future we'll distinguish between the different norm objects,
but for now we'll just use the same one for all of them.
Args:
param (torch.Tensor): The parameter to transform. This may be either a bias or weight and should have
shape (model_dim,)
"""
return self.norm.transform_param(param)
class DSMoETransformerModelBase(DSTransformerModelBase):
@property
def n_experts(self) -> int:
"""
Return the number of experts in the model.
"""
raise NotImplementedError("Attempted to access an unimplemented number of experts")
@property
def n_top_k(self) -> int:
"""
Number of experts per token.
"""
raise NotImplementedError("Attempted to access an unimplemented number of experts per token")
@property
def normalize_expert_scores(self) -> bool:
"""
Whether to normalize expert scores. If true, sum(expert_scores) = 1.
"""
raise NotImplementedError("Attempted to access an unimplemented normalization flag")
def make_moe_layer(self) -> None:
"""
Instantiates the MoE layer for the model. This sets the `self.moe` attribute.
"""
sharded_dim = sharded_intermediate_dim(self.intermediate_dim, self.tp_size, self.tp_rank)
moe_config = DSMoEConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
model_dim=self.model_dim,
intermediate_features=sharded_dim,
activation=self.mlp_activation_fn,
n_experts=self.n_experts,
top_k=self.n_top_k,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
normalize_scores=self.normalize_expert_scores,
)
self.moe = heuristics.instantiate_moe(moe_config, self._engine_config)
def transform_moe_gate_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Passes a MoE gate parameter to the underlying implementation for any necessary transformations.
TODO(cmikeh2): This will need to be updated/overridden for expert parallelism.
"""
return self.moe.transform_gate_param(param)
def transform_moe_mlp_1_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Shards the first MoE param and passes it to the underlying implementation. Since it's possible for an architecture
to have both MoE and non-MoE layers, this can't be overloaded on the MLP1 transform. Furthermore, since both
the MoE DSModule owns both MLP1 and MLP2, under certain sharding conditions it's not possible for the model implementation
to infer from the shape whether to perform a different transformation based on MLP1 or MLP2. This (and the below)
separations are intended to solve both these issues.
Args:
param (torch.Tensor): The parameter to transform. This should have shape (n_experts, out_neurons, in_neurons).
"""
param = shard_mlp_1_param(param, self.tp_rank, self.tp_size, gated=self.gated_mlp, is_moe=True)
return self.moe.transform_moe_mlp_1_param(param)
def transform_moe_mlp_2_param(self, param: torch.Tensor) -> Optional[torch.Tensor]:
"""
Shards the second MoE param and passes it to the underlying implementation. See the above for context on why this API
exists.
This will return `None` for expert bias params not on TP rank 0. NOTE(cmikeh2): Does it make sense to round-robin assign?
My intuition is that this will make debugging much more difficult for minimal memory reduction.
Args:
param (torch.Tensor): The parameter to transform. This should have shape (n_experts, out_neurons, in_neurons).
"""
param = shard_mlp_2_param(param, self.tp_rank, self.tp_size, is_moe=True)
if param is not None:
param = self.moe.transform_moe_mlp_2_param(param)
return param
@@ -0,0 +1,356 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import re
from typing import Type
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.compat import get_annotations_from_namespace, get_annotations
from .parameter_base import ParameterBase, ParametrizedList
from ..inference_parameter import InferenceParameter
# Currently have dependency loops for the type hints.
InferenceModel = Type["InferenceModel"]
LayerContainer = Type["LayerContainer"] # noqa: F811
MAPPING_KEY = "PARAM_MAPPING"
PLIST_HELPERS = "_ds_plist_strip_vals"
def make_finalization_callback(all_names: str):
"""
Helper method for building the finalization callback for a LayerContainer. This
is not client code and should not be used or called directly.
"""
def finalization_callback(self, param: ParameterBase, finalized_param: torch.Tensor) -> None:
"""
Callback for when a parameter is finalized.
"""
self._finalized_params += 1
for name in all_names:
if getattr(self, name) is param:
setattr(self, name, finalized_param)
return finalization_callback
class LayerMetaclass(type):
"""
MetaClass for the LayerContainer base class. This class will parse the annotations
of the class that correspond to `ParameterBase` and create None initializers for each
as well as a finalization callback that for when each `ParameterBase` is finalized
and should be replaced with a Tensor.
"""
def __new__(cls, clsname, bases, attrs):
annotations = get_annotations_from_namespace(attrs)
for base in bases:
# We'll pick up all annotations on any base classes. This will allow us to
# to use inheritance to share common parameter groups in base classes.
annotations.update(get_annotations(base))
if hasattr(base, MAPPING_KEY):
if MAPPING_KEY not in attrs:
# This is likely a fail state. If a parent has MAPPING KEY but the child does
# not, then we're guaranteed only a subset of the parameters will be mapped.
attrs[MAPPING_KEY] = {}
attrs[MAPPING_KEY].update(getattr(base, MAPPING_KEY))
all_names = [name for name, annotation in annotations.items() if issubclass(annotation, ParameterBase)]
if MAPPING_KEY in attrs:
# If we have a mapping key at all, then we will enter the validation mode for building
# helpers for mapping and ensuring we have complete mapping.
# First we'll build a flat list of every dependency for this layer.
all_deps = set()
for name in all_names:
parameter_deps = [
name for name, annotation in get_annotations(annotations[name]).items()
if issubclass(annotation, (torch.Tensor, ParametrizedList))
]
all_deps.update([f"{name}.{dep}" for dep in parameter_deps])
# Create static helper for doing the string processing only once.
attrs[PLIST_HELPERS] = []
# Iterate over all the mappings
for src_name, target_or_targets in attrs[MAPPING_KEY].items():
if isinstance(target_or_targets, str):
target_or_targets = [target_or_targets]
actual_targets = []
for target_name in target_or_targets:
base_dependency, dependency_attr = target_name.split(".")
# Check for invalid mappings
if base_dependency not in all_names:
raise ValueError(
"Target parameter \"{}\" not found in this layer. Valid targets are {}".format(
base_dependency, all_names))
if dependency_attr not in get_annotations(annotations[base_dependency]):
# This check is not universal (see below) if a single dependency is being
# mapped to by a single row.
raise ValueError(
"Target dependency \"{}\" not found on parameter \"{}\". Valid targets are {}".format(
dependency_attr, base_dependency,
get_annotations(annotations[base_dependency]).keys()))
if target_name not in all_deps:
raise ValueError(
"Target dependency \"{}\" was targeted with multiple mapping rules.".format(target_name))
# If we've made it this far, the dependency definitely exists.
actual_targets.append(get_annotations(annotations[base_dependency])[dependency_attr])
all_deps.remove(target_name)
are_plists = [issubclass(target, ParametrizedList) for target in actual_targets]
if all(are_plists):
# We can do direct sets on everything but ParametrizedLists, so we'll only explicitly
# handle these here.
# TODO(cmikeh2): SPLIT, error if more than 1
glob_count = src_name.count("*")
if glob_count > 1:
raise ValueError(
"ParametrizedList index inference can only work with a single glob: {}".format(src_name))
elif glob_count == 0:
raise ValueError(
"Must have wildcard (*) in source name for ParametrizedList mapping: {}".format(src_name))
wildcard_idx = src_name.find("*")
prefix = src_name[:wildcard_idx]
suffix = src_name[wildcard_idx + 1:]
attrs[PLIST_HELPERS].append((prefix, suffix, target_or_targets))
elif any(are_plists):
raise ValueError("Cannot mix ParametrizedLists and Tensors in a single mapping rule.")
if len(all_deps) > 0:
raise ValueError(
"A parameter mapping was provided for {}, but the following dependencies were not mapped: {}".
format(clsname, all_deps))
attrs["finalization_callback"] = make_finalization_callback(all_names)
new_obj = super().__new__(cls, clsname, bases, attrs)
setattr(new_obj, "_n_params", len(all_names))
setattr(new_obj, "_annotation_attrs", all_names)
return new_obj
def __call__(cls, *args, **kwargs):
instance = cls.__new__(cls, *args, **kwargs)
instance.__init__(*args, **kwargs)
for name, annotation in get_annotations(instance).items():
if issubclass(annotation, ParameterBase):
# TODO(cmikeh2): Do we want to make this a property
# It might also make sense to do this in the base class __init__
# but since it is tied with the changes made in __new__ it feels
# to me like it should be here.
setattr(instance, name, annotation(instance.inference_model, instance))
return instance
class LayerContainer(metaclass=LayerMetaclass): # noqa: F811
"""
Abstract base class for containing model parameters.
This is primarily a guidance abstraction since we do not put any restrictions
on how the parameters are stored.
To use this class, annotate the class with `ParameterBase` subclasses and give them
names. As a checkpoint is loaded into this container, the `ParameterBase` instances
will be replaced with realized Tensors as soon as each of their dependencies are met.
To enable automatic mapping, add a static attribute `PARAM_MAPPING` to the class
definition. This should be a dictionary mapping from a source string to one or
more dependencies.
```python
class MyLayer(LayerContainer):
PARAM_MAPPING = {
"path.to.param.dependency", "container_param_1.dependency",
"path.to.param2.dependency", "container_param_2.dependency",
"path.to.param3.*.dependency", "container_param_3.list_dependency"
}
...
```
"""
def __init__(self, model: InferenceModel) -> None:
"""
Initialization of the LayerContainer. This method does not need to be overridden
for any children classes.
Args:
model (InferenceModel): Inference model that will be used to shard and transform
parameters correctly, as well as provide specific information about the model
for `ParameterizedList`s that may be part of one of the member `ParameterBase`s.
"""
self.inference_model = model
self._finalized_params = 0
def _initialization_checker(self, check_device: bool = True) -> bool:
"""
Returns whether or not all parameters have been initialized and transformed by
the model. Once this returns True, all the `ParameterBase` instances will be
torch.Tensors.
"""
if self._finalized_params != self.n_params:
return False
for name in self._annotation_attrs:
tensor = getattr(self, name)
if tensor is None:
continue
elif not isinstance(tensor, InferenceParameter):
raise ValueError("Layer should be finalized, but {} ({}) is neither InferenceParameter or None".format(
name, type(tensor)))
elif check_device and tensor.device != torch.device(get_accelerator().current_device()):
raise RuntimeError("Layer should be finalized, but {} is not on device {}".format(
name,
get_accelerator().current_device()))
return True
@property
def is_populated(self) -> bool:
"""
Returns whether or not all parameters have been populated by the checkpoint engine, but
does not validat the parameters are on the correct device.
"""
return self._initialization_checker(check_device=False)
@property
def is_initialized(self) -> bool:
"""
Returns whether or not all parameters have been initialized and transformed by
the model and are located on the appropriate device. Once this returns True, all
the `ParameterBase` instances ``InferenceParameter``s or explicitly set to ``None``.
"""
return self._initialization_checker()
@property
def n_params(self) -> int:
"""
The number of parameters this container holds. This is a read-only value
that is set by the metaclass.
"""
return self._n_params
@property
def annotation_attrs(self) -> list:
return self._annotation_attrs
@property
def mapping_params(self) -> dict:
return getattr(self.__class__, MAPPING_KEY, {})
@property
def plist_helpers(self) -> list:
return getattr(self.__class__, PLIST_HELPERS, [])
def direct_injection(self, name: str, tensor: InferenceParameter) -> None:
if name not in self._annotation_attrs:
raise ValueError(f"Cannot directly inject {name}, not a valid parameter.")
setattr(self, name, tensor)
self._finalized_params += 1
def set_dependency(self, dep_name: str, dep_value: torch.Tensor) -> None:
"""
Set dependency can be used for managing dependencies when a mapping is provided
in the class definition for the layer. The dep_name here should have any prefix
for transformer layers removed (such as model.layers.*.attn.qkv.weight -> attn.qkv.weight).
Args:
dep_name (str): The name of the dependency to set.
dep_value (torch.Tensor): The value to set the dependency to.
"""
def get_dep_name_target(dep_name: str) -> str:
"""
Helper method for getting the target name for a dependency from the
mapping params. Tries to match exact string first, then looks for
wildcards and attempts regex matching. Will return empty string if
no match found.
"""
if dep_name in self.mapping_params:
# If we have an exact match, it's a direct mapping and we can
# immediately set the value.
return self.mapping_params[dep_name]
matched_targets = []
for key, target in self.mapping_params.items():
regex_key = key.replace("*", ".*")
if re.match(regex_key, dep_name):
matched_targets.append(target)
if len(matched_targets) > 1:
raise ValueError(f"Multiple targets matched for dependency {dep_name}: {matched_targets}")
if matched_targets:
return matched_targets[0]
return ""
if dep_name in self.mapping_params:
# If we have an exact match, it's a direct mapping and we can immediately set
# the value.
target = self.mapping_params[dep_name]
# Convert single targets to a list for consistency
if isinstance(target, str):
target = [target]
for target_name in target:
# Double setting doesn't set the attribute correctly, so we do a getattr then setattr
target_param_name, target_dependency_name = target_name.split(".")
target_param = getattr(self, target_param_name)
setattr(target_param, target_dependency_name, dep_value)
return
# Otherwise we need to map to one of the parameter lists.
for prefix, suffix, dests in self.plist_helpers:
if dep_name.startswith(prefix) and dep_name.endswith(suffix):
# We have a match, so we can set the value.
target_idx = int(dep_name[len(prefix):-len(suffix)])
# Convert single targets to a list for consistency
if isinstance(dests, str):
dests = [dests]
for dest in dests:
target_param_name, target_dependency_name = dest.split(".")
target_param = getattr(self, target_param_name)
target_dependency = getattr(target_param, target_dependency_name)
target_dependency[target_idx] = dep_value
return
# TODO: Refactor this with the help of cmikeh2
# We should be able to combine this with the wildcard matching above.
target = get_dep_name_target(dep_name)
if target:
# Convert single targets to a list for consistency
if isinstance(target, str):
target = [target]
for target_name in target:
# Double setting doesn't set the attribute correctly, so we do a getattr then setattr
target_param_name, target_dependency_name = target_name.split(".")
target_param = getattr(self, target_param_name)
setattr(target_param, target_dependency_name, dep_value)
return
raise ValueError(
"Could not find a mapping for dependency \"{}\". Check that it is included in the ``MAPPING_PARAMS``. See docstring for more on ``MAPPING_PARAMS``"
.format(dep_name))
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import Llama2Policy
@@ -0,0 +1,78 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Create a container object to save model-specific tensors using the policy file above.
from ..common_parameters import *
from ..layer_container_base import LayerContainer
'''
# HF Llama model looks like this:
LlamaForCausalLM(
(model): LlamaModel(
(embed_tokens): Embedding(32000, 4096, padding_idx=0)
(layers): ModuleList(
(0-31): 32 x LlamaDecoderLayer(
(self_attn): LlamaAttention(
(q_proj): Linear(in_features=4096, out_features=4096, bias=False)
(k_proj): Linear(in_features=4096, out_features=4096, bias=False)
(v_proj): Linear(in_features=4096, out_features=4096, bias=False)
(o_proj): Linear(in_features=4096, out_features=4096, bias=False)
(rotary_emb): LlamaRotaryEmbedding()
)
(mlp): LlamaMLP(
(gate_proj): Linear(in_features=4096, out_features=11008, bias=False)
(up_proj): Linear(in_features=4096, out_features=11008, bias=False)
(down_proj): Linear(in_features=11008, out_features=4096, bias=False)
(act_fn): SiLUActivation()
)
(input_layernorm): LlamaRMSNorm()
(post_attention_layernorm): LlamaRMSNorm()
)
)
(norm): LlamaRMSNorm()
)
(lm_head): Linear(in_features=4096, out_features=32000, bias=False)
)
'''
class Llama2TransformerContainer(LayerContainer):
"""
Transformer layer container for the Llama-2 model.
"""
qkv_w: UnfusedQKVParameter
attn_out_w: AttentionOutputParameter
mlp_1_w: GatedMLPParameter
mlp_2_w: MLP2Parameter
attn_norm_gamma: NormParameter
mlp_norm_gamma: NormParameter
PARAM_MAPPING = {
"self_attn.q_proj.weight": "qkv_w.q_params",
"self_attn.k_proj.weight": "qkv_w.k_params",
"self_attn.v_proj.weight": "qkv_w.v_params",
"self_attn.o_proj.weight": "attn_out_w.params",
"mlp.gate_proj.weight": "mlp_1_w.gate_params",
"mlp.up_proj.weight": "mlp_1_w.up_params",
"mlp.down_proj.weight": "mlp_2_w.params",
"input_layernorm.weight": "attn_norm_gamma.params",
"post_attention_layernorm.weight": "mlp_norm_gamma.params",
}
class Llama2NonTransformerContainer(LayerContainer):
"""
Non-Transformer layer container for the Llama-2 model.
"""
word_emb: EmbeddingParameter
word_unembed: UnembedParameter
final_norm: NormParameter
PARAM_MAPPING = {
"model.embed_tokens.weight": "word_emb.params",
"model.norm.weight": "final_norm.params",
"lm_head.weight": "word_unembed.params",
}
@@ -0,0 +1,209 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...inference_utils import ActivationType, DtypeEnum
from .. import *
from ...modules.configs import *
from ...modules.interfaces import *
from ...ragged import RaggedBatchWrapper
from .container import Llama2NonTransformerContainer, Llama2TransformerContainer
class Llama2InferenceModel(DSTransformerModelBase):
"""
Inference model implementation for ragged batching for Llama-2 models.
"""
_non_transformer: Optional[Llama2NonTransformerContainer]
"""
Embed + unembed container. Specializing the type annotation.
"""
_transformer: Optional[Iterable[Llama2TransformerContainer]]
"""
Per-layer transformer container. Specializing the type annotation.
"""
"""
Properties ineherited from `DSInferenceModelBase`
"""
@property
def max_sequence_length(self) -> int:
return self._config.max_seq_length
"""
Properties ineherited from `DSTransformerModelBase`
"""
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return self.model_dim // self.n_heads
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return self._config.intermediate_size
@property
def n_heads_kv(self) -> int:
return self._config.num_key_value_heads
@property
def activation_dtype(self) -> DtypeEnum:
if self._config.torch_dtype == torch.float16:
return DtypeEnum.fp16
elif self._config.torch_dtype == torch.bfloat16:
return DtypeEnum.bf16
else:
raise NotImplementedError("Only fp16 and bf16 are supported")
@property
def mlp_activation_fn(self) -> ActivationType:
activation = self._config.hidden_act.lower()
# llama model family is special and is always gated so force gated versions of relu, gelu, silu
if activation == "gelu":
return ActivationType.GEGLU
elif activation == "relu":
return ActivationType.ReGLU
elif activation == "gegelu":
return ActivationType.GEGLU
elif activation == "silu":
return ActivationType.SiGLU
else:
raise NotImplementedError(f"Activation {activation} not supported")
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.RMSNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.rotate_half
@property
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
return RotateHalfConfig(theta_base=self._config.rope_theta)
"""
Forward implementations
"""
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs the embedding lookup prior to running the transformer of the model.
Arguments:
ragged_batch (RaggedBatchWrapper): The batch to embed.
Returns:
torch.Tensor: The embedded batch.
"""
embed = self.embed(ragged_batch, self._non_transformer.word_emb)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer_layer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Executes one (slightly offset) layer of the transformer. This implementation does a peak-ahead
optimization to fuse the layer norm of the next layer into the current layer.
Arguments:
layer_idx (int): The index of the layer to execute.
residual (torch.Tensor): The residual tensor from the previous layer.
hidden_states (torch.Tensor): The hidden states from the previous layer. This is the
hidden states after pre normalization.
ragged_batch_info (RaggedBatchWrapper): The batch metadata.
"""
# TODO(cmikeh2): Distribute ragged_batch_info to all modules
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
hidden_states = self.qkv(hidden_states, cur_params.qkv_w, b=None)
hidden_states = self.attn(hidden_states, kv_cache, ragged_batch_info)
hidden_states = self.attn_out(hidden_states, cur_params.attn_out_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
residual, hidden_states = self.norm(residual, hidden_states, cur_params.mlp_norm_gamma, beta=None)
# Should be configurable in the future
hidden_states = self.mlp_1(hidden_states, cur_params.mlp_1_w, b=None)
hidden_states = self.mlp_2(hidden_states, cur_params.mlp_2_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
if layer_idx != self.num_layers - 1:
next_params = self._transformer[layer_idx + 1]
residual, hidden_states = self.norm(residual, hidden_states, next_params.attn_norm_gamma, beta=None)
else:
# On last layer, we just need to perform the residual add. Adding into the residual
# here is safe.
residual.add_(hidden_states)
return residual, hidden_states
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs unembedding of the hidden states to logits. This will only sample the final
token of each sequence.
"""
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed,
ragged_batch_info,
gamma=self._non_transformer.final_norm)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
residual, hidden_states = self.norm(residual, None, self._transformer[0].attn_norm_gamma, beta=None)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer_layer(layer_idx, residual, hidden_states,
wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,31 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .container import Llama2NonTransformerContainer, Llama2TransformerContainer
from .model import Llama2InferenceModel
class Llama2Policy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> Llama2InferenceModel:
return Llama2InferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
transformer_containers = [Llama2TransformerContainer(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['model.layers'], transformer_containers)
map.set_non_transformer_params(Llama2NonTransformerContainer(self.model))
map.set_unmapped_params(
[f'model.layers.{i}.self_attn.rotary_emb.inv_freq' for i in range(self.model.num_layers)])
return map
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import MistralPolicy
@@ -0,0 +1,77 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Create a container object to save model-specific tensors using the policy file above.
from deepspeed.inference.v2.model_implementations.common_parameters import *
from deepspeed.inference.v2.model_implementations.layer_container_base import LayerContainer
'''
# HF Mistral model (mistralai/Mistral-7B-v0.1) looks like this:
MistralForCausalLM(
(model): MistralModel(
(embed_tokens): Embedding(32000, 4096)
(layers): ModuleList(
(0-31): 32 x MistralDecoderLayer(
(self_attn): MistralAttention(
(q_proj): Linear(in_features=4096, out_features=4096, bias=False)
(k_proj): Linear(in_features=4096, out_features=1024, bias=False)
(v_proj): Linear(in_features=4096, out_features=1024, bias=False)
(o_proj): Linear(in_features=4096, out_features=4096, bias=False)
(rotary_emb): MistralRotaryEmbedding()
)
(mlp): MistralMLP(
(gate_proj): Linear(in_features=4096, out_features=14336, bias=False)
(up_proj): Linear(in_features=4096, out_features=14336, bias=False)
(down_proj): Linear(in_features=14336, out_features=4096, bias=False)
(act_fn): SiLUActivation()
)
(input_layernorm): MistralRMSNorm()
(post_attention_layernorm): MistralRMSNorm()
)
)
(norm): MistralRMSNorm()
)
(lm_head): Linear(in_features=4096, out_features=32000, bias=False)
)
'''
class MistralTransformerContainer(LayerContainer):
"""
Transformer layer container for the Mistral model.
"""
qkv_w: UnfusedQKVParameter
attn_out_w: AttentionOutputParameter
mlp_1_w: GatedMLPParameter
mlp_2_w: MLP2Parameter
attn_norm_gamma: NormParameter
mlp_norm_gamma: NormParameter
PARAM_MAPPING = {
"self_attn.q_proj.weight": "qkv_w.q_params",
"self_attn.k_proj.weight": "qkv_w.k_params",
"self_attn.v_proj.weight": "qkv_w.v_params",
"self_attn.o_proj.weight": "attn_out_w.params",
"mlp.gate_proj.weight": "mlp_1_w.gate_params",
"mlp.up_proj.weight": "mlp_1_w.up_params",
"mlp.down_proj.weight": "mlp_2_w.params",
"input_layernorm.weight": "attn_norm_gamma.params",
"post_attention_layernorm.weight": "mlp_norm_gamma.params",
}
class MistralNonTransformerContainer(LayerContainer):
"""
Non-Transformer layer container for the Mistral model.
"""
word_emb: EmbeddingParameter
word_unembed: UnembedParameter
final_norm: NormParameter
PARAM_MAPPING = {
"model.embed_tokens.weight": "word_emb.params",
"model.norm.weight": "final_norm.params",
"lm_head.weight": "word_unembed.params",
}
@@ -0,0 +1,207 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...inference_utils import ActivationType, DtypeEnum
from ...model_implementations import *
from ...modules.configs import *
from ...modules.interfaces import *
from ...ragged import RaggedBatchWrapper
from .container import MistralNonTransformerContainer, MistralTransformerContainer
class MistralInferenceModel(DSTransformerModelBase):
"""
Inference model implementation for ragged batching for Mistral models.
"""
_non_transformer: Optional[MistralNonTransformerContainer]
"""
Embed + unembed container. Specializing the type annotation.
"""
_transformer: Optional[Iterable[MistralTransformerContainer]]
"""
Per-layer transformer container. Specializing the type annotation.
"""
"""
Properties ineherited from `DSInferenceModelBase`
"""
@property
def max_sequence_length(self) -> int:
return self._config.max_seq_length
"""
Properties ineherited from `DSTransformerModelBase`
"""
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return self.model_dim // self.n_heads
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return self._config.intermediate_size
@property
def n_heads_kv(self) -> int:
return self._config.num_key_value_heads
@property
def activation_dtype(self) -> DtypeEnum:
if self._config.torch_dtype == torch.float16:
return DtypeEnum.fp16
elif self._config.torch_dtype == torch.bfloat16:
return DtypeEnum.bf16
else:
raise NotImplementedError("Only fp16 and bf16 are supported")
@property
def mlp_activation_fn(self) -> ActivationType:
activation = self._config.hidden_act.lower()
if activation == "gelu":
return ActivationType.GEGLU
elif activation == "relu":
return ActivationType.ReGLU
elif activation == "gegelu":
return ActivationType.GEGLU
elif activation == "silu":
return ActivationType.SiGLU
else:
raise NotImplementedError(f"Activation {activation} not supported")
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.RMSNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.rotate_half
@property
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
return RotateHalfConfig(theta_base=self._config.rope_theta)
"""
Forward implementations
"""
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs the embedding lookup prior to running the transformer of the model.
Arguments:
ragged_batch (RaggedBatchWrapper): The batch to embed.
Returns:
torch.Tensor: The embedded batch.
"""
embed = self.embed(ragged_batch, self._non_transformer.word_emb)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Executes one (slightly offset) layer of the transformer. This implementation does a peak-ahead
optimization to fuse the layer norm of the next layer into the current layer.
Arguments:
layer_idx (int): The index of the layer to execute.
residual (torch.Tensor): The residual tensor from the previous layer.
hidden_states (torch.Tensor): The hidden states from the previous layer. This is the
hidden states after pre normalization.
ragged_batch_info (RaggedBatchWrapper): The batch metadata.
"""
# TODO(cmikeh2): Distribute ragged_batch_info to all modules
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
hidden_states = self.qkv(hidden_states, cur_params.qkv_w, b=None)
hidden_states = self.attn(hidden_states, kv_cache, ragged_batch_info)
hidden_states = self.attn_out(hidden_states, cur_params.attn_out_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
residual, hidden_states = self.norm(residual, hidden_states, cur_params.mlp_norm_gamma, beta=None)
# Should be configurable in the future
hidden_states = self.mlp_1(hidden_states, cur_params.mlp_1_w, b=None)
hidden_states = self.mlp_2(hidden_states, cur_params.mlp_2_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
if layer_idx != self.num_layers - 1:
next_params = self._transformer[layer_idx + 1]
residual, hidden_states = self.norm(residual, hidden_states, next_params.attn_norm_gamma, beta=None)
else:
# On last layer, we just need to perform the residual add. Adding into the residual
# here is safe.
residual.add_(hidden_states)
return residual, hidden_states
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs unembedding of the hidden states to logits. This will only sample the final
token of each sequence.
"""
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed,
ragged_batch_info,
gamma=self._non_transformer.final_norm)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
residual, hidden_states = self.norm(residual, None, self._transformer[0].attn_norm_gamma, beta=None)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer(layer_idx, residual, hidden_states, wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,30 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .container import MistralNonTransformerContainer, MistralTransformerContainer
from .model import MistralInferenceModel
class MistralPolicy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> MistralInferenceModel:
return MistralInferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
transformer_containers = [MistralTransformerContainer(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['model.layers'], transformer_containers)
map.set_non_transformer_params(MistralNonTransformerContainer(self.model))
map.set_unmapped_params([])
return map
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import MixtralPolicy
@@ -0,0 +1,46 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Create a container object to save model-specific tensors using the policy file above.
from deepspeed.inference.v2.model_implementations.common_parameters import *
from deepspeed.inference.v2.model_implementations.layer_container_base import LayerContainer
class MixtralTransformerContainer(LayerContainer):
qkv_w: UnfusedQKVParameter
attn_out_w: AttentionOutputParameter
moe_gate: MoEGatingWeightParameter
moe_mlp_1: UnfusedMoEGatedMLPParameter
moe_mlp_2: UnfusedMoEMLP2Parameter
attn_norm_gamma: NormParameter
mlp_norm_gamma: NormParameter
PARAM_MAPPING = {
"input_layernorm.weight": "attn_norm_gamma.params",
"post_attention_layernorm.weight": "mlp_norm_gamma.params",
"self_attn.q_proj.weight": "qkv_w.q_params",
"self_attn.k_proj.weight": "qkv_w.k_params",
"self_attn.v_proj.weight": "qkv_w.v_params",
"self_attn.o_proj.weight": "attn_out_w.params",
"block_sparse_moe.gate.weight": "moe_gate.params",
"block_sparse_moe.experts.*.w1.weight": "moe_mlp_1.gating_experts",
"block_sparse_moe.experts.*.w3.weight": "moe_mlp_1.up_experts",
"block_sparse_moe.experts.*.w2.weight": "moe_mlp_2.experts",
}
class MixtralNonTransformerContainer(LayerContainer):
word_emb: EmbeddingParameter
word_unembed: UnembedParameter
final_norm: NormParameter
PARAM_MAPPING = {
"model.embed_tokens.weight": "word_emb.params",
"lm_head.weight": "word_unembed.params",
"model.norm.weight": "final_norm.params",
}
@@ -0,0 +1,261 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...config_v2 import RaggedInferenceEngineConfig
from ...inference_utils import ActivationType, DtypeEnum
from ...model_implementations import *
from ...modules.configs import *
from ...modules.interfaces import *
from ...ragged import RaggedBatchWrapper
from ..inference_model_base import (
DSModelImplementationConfig,
MPType,
)
from .container import MixtralNonTransformerContainer, MixtralTransformerContainer
class MixtralInferenceModel(DSMoETransformerModelBase):
"""
Inference model implementation for Mixtral models.
"""
_non_transformer: Optional[MixtralNonTransformerContainer]
"""
Embed + unembed container. Specializing the type annotation.
"""
_transformer: Optional[Iterable[MixtralTransformerContainer]]
"""
Per-layer transformer container. Specializing the type annotation.
"""
"""
Properties ineherited from `DSInferenceModelBase`
"""
@property
def max_sequence_length(self) -> int:
return self._config.max_position_embeddings
"""
Properties ineherited from `DSTransformerModelBase`
"""
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return self.model_dim // self.n_heads
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return self._config.intermediate_size
@property
def n_heads_kv(self) -> int:
return self._config.num_key_value_heads
@property
def activation_dtype(self) -> DtypeEnum:
if self._config.torch_dtype == torch.float16:
return DtypeEnum.fp16
elif self._config.torch_dtype == torch.bfloat16:
return DtypeEnum.bf16
else:
raise NotImplementedError("Only fp16 and bf16 are supported")
@property
def mlp_activation_fn(self) -> ActivationType:
activation = self._config.hidden_act.lower()
if activation == "gelu":
return ActivationType.GEGLU
elif activation == "relu":
return ActivationType.ReGLU
elif activation == "gegelu":
return ActivationType.GEGLU
elif activation == "silu":
return ActivationType.SiGLU
else:
raise NotImplementedError(f"Activation {activation} not supported")
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.RMSNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.rotate_half
@property
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
"""
The positional embedding configuration for the model.
"""
return RotateHalfConfig(theta_base=self._config.rope_theta)
"""
Inherited from `DSMoETransformerModelBase`
"""
@property
def n_experts(self) -> int:
return self._config.num_local_experts
@property
def n_top_k(self) -> int:
return self._config.num_experts_per_tok
@property
def normalize_expert_scores(self) -> bool:
return True
"""
Model implementation
"""
def __init__(self, config: DSModelImplementationConfig, engine_config: RaggedInferenceEngineConfig,
base_mp_group: MPType) -> None:
"""
Base implementation for initialization. By default, this will initialize
the traditional components of a transformer model:
- Embedding
- QKV projection
- Self attention
- Attention output projection
- Feed forward network
- Normalization
- Unembedding
Arguments:
config (DSModelImplementationConfig): Model-specific configuration. No assumptions
should be made about this config that are not closely tied to the specific
model implementation.
engine_config (RaggedInferenceEngineConfig): Engine configuration.
base_mp_group (MPType): Base communication group for Tensor-parallel inference.
"""
super().__init__(config, engine_config, base_mp_group)
self.make_norm_layer()
self.make_qkv_layer()
self.make_attn_layer()
self.make_attn_out_layer()
self.make_moe_layer()
self.make_embedding_layer()
self.make_unembedding_layer()
self._kv_cache_config = None
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs the embedding lookup prior to running the transformer of the model.
Arguments:
ragged_batch (RaggedBatchWrapper): The batch to embed.
Returns:
torch.Tensor: The embedded batch.
"""
embed = self.embed(ragged_batch, self._non_transformer.word_emb)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Executes one (slightly offset) layer of the transformer. This implementation does a peak-ahead
optimization to fuse the layer norm of the next layer into the current layer.
Arguments:
layer_idx (int): The index of the layer to execute.
residual (torch.Tensor): The residual tensor from the previous layer.
hidden_states (torch.Tensor): The hidden states from the previous layer. This is the
hidden states after pre normalization.
ragged_batch_info (RaggedBatchWrapper): The batch metadata.
"""
# TODO(cmikeh2): Distribute ragged_batch_info to all modules
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
hidden_states = self.qkv(hidden_states, cur_params.qkv_w)
hidden_states = self.attn(hidden_states, kv_cache, ragged_batch_info)
hidden_states = self.attn_out(hidden_states, cur_params.attn_out_w)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
residual, hidden_states = self.norm(residual, hidden_states, cur_params.mlp_norm_gamma)
hidden_states = self.moe(hidden_states, ragged_batch_info, cur_params.moe_gate, cur_params.moe_mlp_1,
cur_params.moe_mlp_2)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
if layer_idx != self.num_layers - 1:
next_params = self._transformer[layer_idx + 1]
residual, hidden_states = self.norm(residual, hidden_states, next_params.attn_norm_gamma)
else:
# On last layer, we just need to perform the residual add. Adding into the residual
# here is safe.
residual.add_(hidden_states)
return residual, hidden_states
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs unembedding of the hidden states to logits. This will only sample the final
token of each sequence.
"""
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed,
ragged_batch_info,
gamma=self._non_transformer.final_norm)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
residual, hidden_states = self.norm(residual, None, self._transformer[0].attn_norm_gamma, beta=None)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer(layer_idx, residual, hidden_states, wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,31 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .container import MixtralTransformerContainer, MixtralNonTransformerContainer
from .model import MixtralInferenceModel
class MixtralPolicy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> MixtralInferenceModel:
return MixtralInferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
transformer_containers = [MixtralTransformerContainer(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['model.layers'], transformer_containers)
map.set_non_transformer_params(MixtralNonTransformerContainer(self.model))
map.set_unmapped_params([])
return map
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import OPTPolicy
@@ -0,0 +1,94 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Create a container object to save model-specific tensors using the policy file above.
from ..common_parameters import *
from ..layer_container_base import LayerContainer
'''
# HF OPT model looks like this:
OPTForCausalLM(
(model): OPTModel(
(decoder): OPTDecoder(
(embed_tokens): Embedding(50272, 768, padding_idx=1)
(embed_positions): OPTLearnedPositionalEmbedding(2050, 768)
(final_layer_norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(layers): ModuleList(
(0-11): 12 x OPTDecoderLayer(
(self_attn): OPTAttention(
(k_proj): Linear(in_features=768, out_features=768, bias=True)
(v_proj): Linear(in_features=768, out_features=768, bias=True)
(q_proj): Linear(in_features=768, out_features=768, bias=True)
(out_proj): Linear(in_features=768, out_features=768, bias=True)
)
(activation_fn): ReLU()
(self_attn_layer_norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
(fc1): Linear(in_features=768, out_features=3072, bias=True)
(fc2): Linear(in_features=3072, out_features=768, bias=True)
(final_layer_norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)
)
)
)
)
(lm_head): Linear(in_features=768, out_features=50272, bias=False)
)
'''
class OPTTransformerContainer(LayerContainer):
"""
Transformer layer container for the OPT model.
"""
qkv_w: UnfusedQKVParameter
qkv_b: UnfusedQKVParameter
attn_out_w: AttentionOutputParameter
attn_out_b: AttentionOutputParameter
mlp_1_w: MLP1Parameter
mlp_1_b: MLP1Parameter
mlp_2_w: MLP2Parameter
mlp_2_b: MLP2Parameter
attn_norm_beta: NormParameter
attn_norm_gamma: NormParameter
mlp_norm_beta: NormParameter
mlp_norm_gamma: NormParameter
PARAM_MAPPING = {
"self_attn.q_proj.weight": "qkv_w.q_params",
"self_attn.q_proj.bias": "qkv_b.q_params",
"self_attn.k_proj.weight": "qkv_w.k_params",
"self_attn.k_proj.bias": "qkv_b.k_params",
"self_attn.v_proj.weight": "qkv_w.v_params",
"self_attn.v_proj.bias": "qkv_b.v_params",
"self_attn.out_proj.weight": "attn_out_w.params",
"self_attn.out_proj.bias": "attn_out_b.params",
"fc1.weight": "mlp_1_w.params",
"fc1.bias": "mlp_1_b.params",
"fc2.weight": "mlp_2_w.params",
"fc2.bias": "mlp_2_b.params",
"self_attn_layer_norm.weight": "attn_norm_gamma.params",
"self_attn_layer_norm.bias": "attn_norm_beta.params",
"final_layer_norm.weight": "mlp_norm_gamma.params",
"final_layer_norm.bias": "mlp_norm_beta.params",
}
class OPTNonTransformerContainer(LayerContainer):
"""
Non-Transformer layer container for the OPT model.
"""
word_emb: EmbeddingParameter
word_emb_pos: EmbeddingParameter
word_unembed: UnembedParameter
final_norm_w: NormParameter
final_norm_b: NormParameter
PARAM_MAPPING = {
"*decoder.embed_tokens.weight": ["word_emb.params", "word_unembed.params"],
"*decoder.embed_positions.weight": "word_emb_pos.params",
"*decoder.final_layer_norm.weight": "final_norm_w.params",
"*decoder.final_layer_norm.bias": "final_norm_b.params",
}
@@ -0,0 +1,197 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...inference_utils import ActivationType, DtypeEnum
from ...model_implementations import *
from ...modules.configs import *
from ...ragged import RaggedBatchWrapper
from .container import OPTNonTransformerContainer, OPTTransformerContainer
from ...modules.heuristics import instantiate_embed
class OPTInferenceModel(DSTransformerModelBase):
"""
Inference model implementation for ragged batching for OPT models.
"""
_non_transformer: Optional[OPTNonTransformerContainer]
"""
Embed + unembed container. Specializing the type annotation.
"""
_transformer: Optional[Iterable[OPTTransformerContainer]]
"""
Per-layer transformer container. Specializing the type annotation.
"""
"""
Properties ineherited from `DSInferenceModelBase`
"""
@property
def max_sequence_length(self) -> int:
return self._config.max_seq_length
"""
Properties ineherited from `DSTransformerModelBase`
"""
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return self.model_dim // self.n_heads
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return self._config.ffn_dim
@property
def activation_dtype(self) -> DtypeEnum:
if self._config.torch_dtype == torch.float16:
return DtypeEnum.fp16
elif self._config.torch_dtype == torch.bfloat16:
return DtypeEnum.bf16
else:
raise NotImplementedError("Only fp16 and bf16 are supported")
@property
def mlp_activation_fn(self) -> ActivationType:
return ActivationType.RELU
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.LayerNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.none
@property
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
return None
"""
Overrides of ``DSTransformerModelBase`` methods
"""
def make_embedding_layer(self) -> None:
"""
Performs setup and creates embedding DSModule. Since OPT includes trained
positional embeddings, we will override the base model implementation.
"""
embed_config = DSEmbeddingsConfig(max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
residual_dtype=self.activation_dtype,
embedding_dim=self.model_dim,
positional_embedding=True,
positional_offset=2)
self.embed = instantiate_embed(embed_config, self._engine_config)
"""
Forward implementations
"""
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
embed = self.embed(ragged_batch, self._non_transformer.word_emb, self._non_transformer.word_emb_pos)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer_layer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
# TODO(cmikeh2): Distribute ragged_batch_info to all modules
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
hidden_states = self.qkv(hidden_states, cur_params.qkv_w, b=cur_params.qkv_b)
hidden_states = self.attn(hidden_states, kv_cache, ragged_batch_info)
hidden_states = self.attn_out(hidden_states, cur_params.attn_out_w, b=cur_params.attn_out_b)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
residual, hidden_states = self.norm(residual,
hidden_states,
cur_params.mlp_norm_gamma,
beta=cur_params.mlp_norm_beta)
# Should be configurable in the future
hidden_states = self.mlp_1(hidden_states, cur_params.mlp_1_w, b=cur_params.mlp_1_b)
hidden_states = self.mlp_2(hidden_states, cur_params.mlp_2_w, b=cur_params.mlp_2_b)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
if layer_idx != self.num_layers - 1:
next_params = self._transformer[layer_idx + 1]
residual, hidden_states = self.norm(residual,
hidden_states,
next_params.attn_norm_gamma,
beta=next_params.attn_norm_beta)
else:
# On last layer, we just need to perform the residual add. Adding into the residual
# here is safe.
residual.add_(hidden_states)
return residual, hidden_states
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed,
ragged_batch_info,
gamma=self._non_transformer.final_norm_w,
beta=self._non_transformer.final_norm_b)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
residual, hidden_states = self.norm(residual,
None,
self._transformer[0].attn_norm_gamma,
beta=self._transformer[0].attn_norm_beta)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer_layer(layer_idx, residual, hidden_states,
wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,30 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .container import OPTNonTransformerContainer, OPTTransformerContainer
from .model import OPTInferenceModel
class OPTPolicy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> OPTInferenceModel:
return OPTInferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
transformer_containers = [OPTTransformerContainer(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['model.decoder.layers', 'decoder.layers'], transformer_containers)
map.set_non_transformer_params(OPTNonTransformerContainer(self.model))
map.set_unmapped_params(['lm_head.weight'])
return map
@@ -0,0 +1,258 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import weakref
from abc import abstractmethod
from typing import Type
import torch
from deepspeed.compat import get_annotations_from_namespace, get_annotations
# Currently have dependency loops for the type hints.
InferenceModel = Type["InferenceModel"]
LayerContainer = Type["LayerContainer"]
MAPPING_KEY = "PARAM_MAPPING"
def make_param_getter(clsname, param):
"""
Normal getter implementation for a property.
"""
def param_getter(self):
return getattr(self, f"__{clsname}__{param}")
return param_getter
def make_param_setter(clsname, param):
"""
Setter implementation that will call complete component to potentially
finalize the parameter.
"""
def param_setter(self, value):
setattr(self, f"__{clsname}__{param}", value)
self.dtype = value.dtype
self.complete_component()
return param_setter
def make_readonly_setter():
"""
Setter implementation that will raise an error if called.
"""
def paramlist_setter(self, value):
raise ValueError("Cannot set a ParametrizedList directly.")
return paramlist_setter
class ParameterMetaclass(type):
"""
MetaClass for the ParameterBase base class. This class will parse the `src_params`
attribute and create properties for each of the dependencies. A dependency can either
be represented as a string, which is interpreted as a named Tensor, or a `ParametrizedList`
subclass.
"""
def __new__(cls, clsname, bases, attrs):
annotations = get_annotations_from_namespace(attrs)
dependencies = {
name: annotation
for name, annotation in annotations.items() if issubclass(annotation, (torch.Tensor, ParametrizedList))
}
n_dependencies = len(dependencies)
# Create properties for each of our dependencies
for d_name, d_type in dependencies.items():
if issubclass(d_type, ParametrizedList):
assert hasattr(
d_type, "count_attr"
), "ParametrizedList must have a count_attr attribute to access on the inference module."
attrs[d_name] = property(make_param_getter(clsname, d_name), make_readonly_setter())
else: # torch.Tensor
attrs[d_name] = property(make_param_getter(clsname, d_name), make_param_setter(clsname, d_name))
new_cls = super().__new__(cls, clsname, bases, attrs)
new_cls.n_dependencies = n_dependencies
return new_cls
def __call__(cls, *args, **kwargs):
new_obj = super().__call__(*args, **kwargs)
new_obj.__init__(*args, **kwargs)
setattr(new_obj, "dest_param", None)
# Initialize our dependences to None/empty `ParametrizedList`s
for name, annotation in get_annotations(new_obj).items():
if issubclass(annotation, ParametrizedList):
#TODO(jeff): update assert with this, model implementation attribute does not align or missing wrt the ParametrizedList attributes
assert hasattr(
new_obj.inference_model, annotation.count_attr
), f"new_obj={new_obj.__class__.__name__}, name={name}, annotation.count_attr={annotation.count_attr}"
param_list = annotation(new_obj, getattr(new_obj.inference_model, annotation.count_attr))
setattr(new_obj, f"__{new_obj.__class__.__name__}__{name}", param_list)
else: # torch.Tensor
setattr(new_obj, f"__{new_obj.__class__.__name__}__{name}", None)
return new_obj
class ParameterBase(metaclass=ParameterMetaclass):
"""
A ParameterBase allows us to consolidate tracking the dependencies of loading a parameter from
a checkpoint into a single object. This class should not be used directly, but rather subclassed
and the `src_params` attribute set to a list of strings and/or `ParametrizedList`s.
"""
# inference_model: InferenceModel
"""
Inference model that will provide context on how to shard and transform the parameter.
"""
#completed_components: int
"""
How many of the layer dependencies have been met. This is used to determine when the parameter
is ready to be finalized. A ParametrizedList counts as a single dependency for the purposes
of this counter.
"""
def __init__(self, model: InferenceModel, parent_container: LayerContainer) -> None:
"""
Direct constructor. This should not be called from client code.
Args:
model (InferenceModel): Inference model that will be used to shard and transform the
parameter in `finalize`.
parent_container (LayerContainer): The parent container that this parameter is a member
of. We will build a weakref to this container to call the finalization callback.
"""
self.inference_model = model
self.completed_components = 0
self.parent_container = weakref.ref(parent_container)
@abstractmethod
def finalize(self) -> torch.Tensor:
"""
Finalize the parameter after all of its source parameters have been set. This method
will be automatically called when all inputs have been set. It should return the Tensor
with all transformations performed on it.
"""
pass
def complete_component(self) -> None:
"""
Mark a component as completed. This should be called by the relevant setter of a direct
property or a ParametrizedList. This method will automatically call `finalize` when all
dependencies have been met and then call the finalization callback on the parent container.
Once the finalization callback has been called, the parameter will be replaced with the
`dst_param` attribute on the parent container, and this instance will be destroyed.
"""
self.completed_components += 1
if self.completed_components != self.n_dependencies:
return
finalized_param = self.finalize()
self.parent_container().finalization_callback(self, finalized_param)
class ParametrizedList:
"""
A ParametrizedList is a list of parameters that are dependencies
of a `ParameterBase` but may vary in length depending on the model
configuration (rather than architecture). For example, a MoE layer
may have different number of experts depending on the size of the model.
This class is used to manage these lists and provide integer indexing
of a single component rather than accessing names directly. For example,
it tends to be more natural to access the 8th expert with `experts[8]`
rather than a name like `expert_8`, especially as an attribute.
To inherit from this class, set static variables `name` and `count_attr`.
```python
class MyParametrizedList(ParametrizedList):
count_attr: str = "my_list_count"
```
In the above example, `my_list_count` should be an accessible attribute
of the inference model (i.e. via `self.inference_model.my_list_count`).
NOTE: There are some APIs in which this type cannot be used as if it is
just a list of Tensors. For example, `torch.cat(param_list)` will not work.
However, you can make it compatible with a tuple wrapper:
`torch.cat(tuple(param_list))`
"""
n_params: int
"""
Number of params this list contains.
"""
param: ParameterBase
"""
WeakRef to the owning parameter.
"""
def __init__(self, param: ParameterBase, n_params: int) -> None:
"""
Constructor. Should not be called from client code.
Args:
param (ParameterBase): The owning parameter.
n_params (int): The number of parameters this list contains. This should be
"""
self.n_params = n_params
self.set_params = 0
self.param = weakref.ref(param)
self._params = [None] * n_params
def __getitem__(self, index):
return self._params[index]
def __setitem__(self, index, value):
if self._params[index] is not None:
raise ValueError("Cannot set a parameter twice.")
self._params[index] = value
self.set_params += 1
if self.set_params != self.n_params:
return
self.param().complete_component()
def __iter__(self):
return iter(self._params)
def ParamList(attr: str):
"""
Helper to create a subclass of ParametrizedList with the desired `count_attr`.
In this manner, we can annotate the type of a Parameter dependency with the
following:
```python
class CustomParameter(ParameterBase):
dependency_list: ParamList("dependencies_count_name")
```
where "dependencies_count_name" is the name of the attribute on the inference model.
"""
class ParametrizedListInstance(ParametrizedList):
count_attr: str = attr
return ParametrizedListInstance
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import PhiPolicy
@@ -0,0 +1,91 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Create a container object to save model-specific tensors using the policy file above.
from ..common_parameters import *
from ..layer_container_base import LayerContainer
'''
# HF Phi-2 model looks like this:
PhiForCausalLM(
(model): PhiModel(
(embed_tokens): Embedding(51200, 2560)
(embed_dropout): Dropout(p=0.0, inplace=False)
(layers): ModuleList(
(0-31): 32 x PhiDecoderLayer(
(self_attn): PhiAttention(
(q_proj): Linear(in_features=2560, out_features=2560, bias=True)
(k_proj): Linear(in_features=2560, out_features=2560, bias=True)
(v_proj): Linear(in_features=2560, out_features=2560, bias=True)
(dense): Linear(in_features=2560, out_features=2560, bias=True)
(rotary_emb): PhiRotaryEmbedding()
)
(mlp): PhiMLP(
(activation_fn): NewGELUActivation()
(fc1): Linear(in_features=2560, out_features=10240, bias=True)
(fc2): Linear(in_features=10240, out_features=2560, bias=True)
)
(input_layernorm): LayerNorm((2560,), eps=1e-05, elementwise_affine=True)
(resid_dropout): Dropout(p=0.1, inplace=False)
)
)
(final_layernorm): LayerNorm((2560,), eps=1e-05, elementwise_affine=True)
)
(lm_head): Linear(in_features=2560, out_features=51200, bias=True)
)
'''
class PhiTransformerContainer(LayerContainer):
"""
Transformer layer container for the Phi model.
"""
qkv_w: UnfusedQKVParameter
qkv_b: UnfusedQKVParameter
attn_out_w: AttentionOutputParameter
attn_out_b: AttentionOutputParameter
mlp_1_w: MLP1Parameter
mlp_1_b: MLP1Parameter
mlp_2_w: MLP2Parameter
mlp_2_b: MLP2Parameter
ln_gamma: NormParameter
ln_beta: NormParameter
PARAM_MAPPING = {
"self_attn.q_proj.weight": "qkv_w.q_params",
"self_attn.k_proj.weight": "qkv_w.k_params",
"self_attn.v_proj.weight": "qkv_w.v_params",
"self_attn.q_proj.bias": "qkv_b.q_params",
"self_attn.k_proj.bias": "qkv_b.k_params",
"self_attn.v_proj.bias": "qkv_b.v_params",
"self_attn.dense.weight": "attn_out_w.params",
"self_attn.dense.bias": "attn_out_b.params",
"mlp.fc1.weight": "mlp_1_w.params",
"mlp.fc1.bias": "mlp_1_b.params",
"mlp.fc2.weight": "mlp_2_w.params",
"mlp.fc2.bias": "mlp_2_b.params",
"input_layernorm.weight": "ln_gamma.params",
"input_layernorm.bias": "ln_beta.params",
}
class PhiNonTransformerContainer(LayerContainer):
"""
Non-Transformer layer container for the Phi model.
"""
word_emb: EmbeddingParameter
word_unembed_w: UnembedParameter
word_unembed_b: UnembedParameter
final_norm_gamma: NormParameter
final_norm_beta: NormParameter
PARAM_MAPPING = {
"model.embed_tokens.weight": "word_emb.params",
"model.final_layernorm.weight": "final_norm_gamma.params",
"model.final_layernorm.bias": "final_norm_beta.params",
"lm_head.weight": "word_unembed_w.params",
"lm_head.bias": "word_unembed_b.params",
}
@@ -0,0 +1,199 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...inference_utils import ActivationType, DtypeEnum
from .. import *
from ...modules.configs import *
from ...modules.interfaces import *
from ...ragged import RaggedBatchWrapper
from .containers import PhiNonTransformerContainer, PhiTransformerContainer
class PhiInferenceModel(DSTransformerModelBase):
"""
Inference model implementation for ragged batching for Llama-2 models.
"""
_non_transformer: Optional[PhiNonTransformerContainer]
"""
Embed + unembed container. Specializing the type annotation.
"""
_transformer: Optional[Iterable[PhiTransformerContainer]]
"""
Per-layer transformer container. Specializing the type annotation.
"""
"""
Properties inherited from `DSInferenceModelBase`
"""
@property
def max_sequence_length(self) -> int:
return self._config.max_seq_length
"""
Properties inherited from `DSTransformerModelBase`
"""
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return self.model_dim // self.n_heads
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return self._config.intermediate_size
@property
def n_heads_kv(self) -> int:
return self._config.num_key_value_heads
@property
def activation_dtype(self) -> DtypeEnum:
if self._config.torch_dtype == torch.float16:
return DtypeEnum.fp16
elif self._config.torch_dtype == torch.bfloat16:
return DtypeEnum.bf16
else:
raise NotImplementedError("Only fp16 and bf16 are supported")
@property
def mlp_activation_fn(self) -> ActivationType:
return ActivationType.GELU
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.LayerNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.rotate_half
@property
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
rotary_dim = int(self._config.partial_rotary_factor * self.head_size)
return RotateHalfConfig(rotate_dim=rotary_dim, theta_base=self._config.rope_theta)
"""
Forward implementations
"""
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs the embedding lookup prior to running the transformer of the model.
Arguments:
ragged_batch (RaggedBatchWrapper): The batch to embed.
Returns:
torch.Tensor: The embedded batch.
"""
embed = self.embed(ragged_batch, self._non_transformer.word_emb)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer_layer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Executes one (slightly offset) layer of the transformer. This implementation does a peak-ahead
optimization to fuse the layer norm of the next layer into the current layer.
Arguments:
layer_idx (int): The index of the layer to execute.
residual (torch.Tensor): The residual tensor from the previous layer.
hidden_states (torch.Tensor): The hidden states from the previous layer. This is the
hidden states after pre normalization.
ragged_batch_info (RaggedBatchWrapper): The batch metadata.
"""
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
attn_ln_out = hidden_states
attn_hidden_state = self.qkv(attn_ln_out, cur_params.qkv_w, b=cur_params.qkv_b)
attn_hidden_state = self.attn(attn_hidden_state, kv_cache, ragged_batch_info)
attention_output = self.attn_out(attn_hidden_state, cur_params.attn_out_w, b=cur_params.attn_out_b)
mlp_ln_out = hidden_states
mlp_hidden_state = self.mlp_1(mlp_ln_out, cur_params.mlp_1_w, b=cur_params.mlp_1_b)
mlp_output = self.mlp_2(mlp_hidden_state, cur_params.mlp_2_w, b=cur_params.mlp_2_b)
mlp_output.add_(attention_output)
if self.tp_size > 1:
dist.all_reduce(mlp_output, group=self._base_mp_group)
if layer_idx != self.num_layers - 1:
next_params = self._transformer[layer_idx + 1]
residual, mlp_output = self.norm(residual, mlp_output, next_params.ln_gamma, beta=next_params.ln_beta)
else:
# On last layer, we just need to perform the residual add. Adding into the residual
# here is safe.
residual.add_(mlp_output)
return residual, mlp_output
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs unembedding of the hidden states to logits. This will only sample the final
token of each sequence.
"""
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed_w,
ragged_batch_info,
bias=self._non_transformer.word_unembed_b,
gamma=self._non_transformer.final_norm_gamma,
beta=self._non_transformer.final_norm_beta)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
residual, hidden_states = self.norm(residual,
None,
gamma=self._transformer[0].ln_gamma,
beta=self._transformer[0].ln_beta)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer_layer(layer_idx, residual, hidden_states,
wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,32 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .containers import PhiNonTransformerContainer, PhiTransformerContainer
from .model import PhiInferenceModel
class PhiPolicy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> PhiInferenceModel:
return PhiInferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
trans_container_cls = PhiTransformerContainer
transformer_containers = [trans_container_cls(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['model.layers'], transformer_containers)
map.set_non_transformer_params(PhiNonTransformerContainer(self.model))
map.set_unmapped_params(
[f'model.layers.{i}.self_attn.rotary_emb.inv_freq' for i in range(self.model.num_layers)])
return map
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import Phi3Policy
@@ -0,0 +1,75 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Create a container object to save model-specific tensors using the policy file above.
from ..common_parameters import *
from ..layer_container_base import LayerContainer
'''
# HF Phi-3 model looks like this:
Phi3ForCausalLM(
(model): Phi3Model(
(embed_tokens): Embedding(32064, 3072)
(embed_dropout): Dropout(p=0.0, inplace=False)
(layers): ModuleList(
(0-31): 32 x Phi3DecoderLayer(
(self_attn): Phi3Attention(
(o_proj): Linear(in_features=3072, out_features=3072, bias=False)
(qkv_proj): Linear(in_features=3072, out_features=9216, bias=False)
(rotary_emb): Phi3RotaryEmbedding()
)
(mlp): PhiMLP(
(gate_up_proj): Linear(in_features=3072, out_features=16384, bias=False)
(down_proj): Linear(in_features=16384, out_features=3072, bias=False)
(activation_fn): SiLU()
)
(input_layernorm): Phi3RMSNorm((3072,), eps=1e-05)
(resid_attn_dropout): Dropout(p=0.0)
(resid_mlp_dropout): Dropout(p=0.0)
(post_attention_layernorm): Phi3RMSNorm((3072,), eps=1e-05)
)
)
(final_layernorm): Phi3RMSNorm((3072,), eps=1e-05)
)
(lm_head): Linear(in_features=3072, out_features=32064, bias=False)
)
'''
class Phi3TransformerContainer(LayerContainer):
"""
Transformer layer container for the Phi model.
"""
qkv_w: FusedQKVParameter
attn_out_w: AttentionOutputParameter
mlp_1_w: FusedGatedMLPParameter
mlp_2_w: MLP2Parameter
attn_norm_gamma: NormParameter
mlp_norm_gamma: NormParameter
PARAM_MAPPING = {
"self_attn.qkv_proj.weight": "qkv_w.params",
"self_attn.o_proj.weight": "attn_out_w.params",
"mlp.gate_up_proj.weight": "mlp_1_w.params",
"mlp.down_proj.weight": "mlp_2_w.params",
"input_layernorm.weight": "attn_norm_gamma.params",
"post_attention_layernorm.weight": "mlp_norm_gamma.params",
}
class Phi3NonTransformerContainer(LayerContainer):
"""
Non-Transformer layer container for the Phi model.
"""
word_emb: EmbeddingParameter
word_unembed_w: UnembedParameter
final_norm_gamma: NormParameter
PARAM_MAPPING = {
"model.embed_tokens.weight": "word_emb.params",
"model.norm.weight": "final_norm_gamma.params",
"lm_head.weight": "word_unembed_w.params",
}
@@ -0,0 +1,204 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...inference_utils import ActivationType, DtypeEnum
from .. import *
from ...modules.configs import *
from ...modules.interfaces import *
from ...ragged import RaggedBatchWrapper
from .containers import Phi3NonTransformerContainer, Phi3TransformerContainer
class Phi3InferenceModel(DSTransformerModelBase):
"""
Inference model implementation for ragged batching for Llama-2 models.
"""
_non_transformer: Optional[Phi3NonTransformerContainer]
"""
Embed + unembed container. Specializing the type annotation.
"""
_transformer: Optional[Iterable[Phi3TransformerContainer]]
"""
Per-layer transformer container. Specializing the type annotation.
"""
"""
Properties inherited from `DSInferenceModelBase`
"""
@property
def max_sequence_length(self) -> int:
return self._config.max_seq_length
"""
Properties inherited from `DSTransformerModelBase`
"""
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return self.model_dim // self.n_heads
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return self._config.intermediate_size
@property
def n_heads_kv(self) -> int:
return self._config.num_key_value_heads
@property
def activation_dtype(self) -> DtypeEnum:
if self._config.torch_dtype == torch.float16:
return DtypeEnum.fp16
elif self._config.torch_dtype == torch.bfloat16:
return DtypeEnum.bf16
else:
raise NotImplementedError("Only fp16 and bf16 are supported")
@property
def mlp_activation_fn(self) -> ActivationType:
activation = self._config.hidden_act.lower()
if activation == "gelu":
return ActivationType.GEGLU
elif activation == "relu":
return ActivationType.ReGLU
elif activation == "gegelu":
return ActivationType.GEGLU
elif activation == "silu":
return ActivationType.SiGLU
else:
raise NotImplementedError(f"Activation {activation} not supported")
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.RMSNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.rotate_half
@property
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
return RotateHalfConfig(theta_base=self._config.rope_theta)
"""
Forward implementations
"""
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs the embedding lookup prior to running the transformer of the model.
Arguments:
ragged_batch (RaggedBatchWrapper): The batch to embed.
Returns:
torch.Tensor: The embedded batch.
"""
embed = self.embed(ragged_batch, self._non_transformer.word_emb)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer_layer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Executes one (slightly offset) layer of the transformer. This implementation does a peak-ahead
optimization to fuse the layer norm of the next layer into the current layer.
Arguments:
layer_idx (int): The index of the layer to execute.
residual (torch.Tensor): The residual tensor from the previous layer.
hidden_states (torch.Tensor): The hidden states from the previous layer. This is the
hidden states after pre normalization.
ragged_batch_info (RaggedBatchWrapper): The batch metadata.
"""
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
hidden_states = self.qkv(hidden_states, cur_params.qkv_w, b=None)
hidden_states = self.attn(hidden_states, kv_cache, ragged_batch_info)
hidden_states = self.attn_out(hidden_states, cur_params.attn_out_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
residual, hidden_states = self.norm(residual, hidden_states, cur_params.mlp_norm_gamma, beta=None)
hidden_states = self.mlp_1(hidden_states, cur_params.mlp_1_w, b=None)
hidden_states = self.mlp_2(hidden_states, cur_params.mlp_2_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
if layer_idx != self.num_layers - 1:
next_params = self._transformer[layer_idx + 1]
residual, hidden_states = self.norm(residual, hidden_states, next_params.attn_norm_gamma, beta=None)
else:
# On last layer, we just need to perform the residual add. Adding into the residual
# here is safe.
residual.add_(hidden_states)
return residual, hidden_states
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs unembedding of the hidden states to logits. This will only sample the final
token of each sequence.
"""
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed_w,
ragged_batch_info,
gamma=self._non_transformer.final_norm_gamma)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
residual, hidden_states = self.norm(residual, None, gamma=self._transformer[0].attn_norm_gamma, beta=None)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer_layer(layer_idx, residual, hidden_states,
wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,30 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .containers import Phi3NonTransformerContainer, Phi3TransformerContainer
from .model import Phi3InferenceModel
class Phi3Policy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> Phi3InferenceModel:
return Phi3InferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
transformer_containers = [Phi3TransformerContainer(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['model.layers'], transformer_containers)
map.set_non_transformer_params(Phi3NonTransformerContainer(self.model))
map.set_unmapped_params([])
return map
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import QwenPolicy
@@ -0,0 +1,77 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Create a container object to save model-specific tensors using the policy file above.
from ..common_parameters import *
from ..layer_container_base import LayerContainer
'''
# HF Qwen model looks like this:
QWenLMHeadModel(
(transformer): QWenModel(
(wte): Embedding(151936, 4096)
(drop): Dropout(p=0.0, inplace=False)
(rotary_emb): RotaryEmbedding()
(h): ModuleList(
(0-31): 32 x QWenBlock(
(ln_1): RMSNorm()
(attn): QWenAttention(
(c_attn): Linear(in_features=4096, out_features=12288, bias=True)
(c_proj): Linear(in_features=4096, out_features=4096, bias=False)
(attn_dropout): Dropout(p=0.0, inplace=False)
)
(ln_2): RMSNorm()
(mlp): QWenMLP(
(w1): Linear(in_features=4096, out_features=11008, bias=False)
(w2): Linear(in_features=4096, out_features=11008, bias=False)
(c_proj): Linear(in_features=11008, out_features=4096, bias=False)
)
)
)
(ln_f): RMSNorm()
)
(lm_head): Linear(in_features=4096, out_features=151936, bias=False)
)
'''
class QwenTransformerContainer(LayerContainer):
"""
Transformer layer container for the Qwen model.
"""
qkv_w: FusedQKVParameter
qkv_b: FusedQKVParameter
attn_out_w: AttentionOutputParameter
mlp_1_w: GatedMLPParameter
mlp_2_w: MLP2Parameter
attn_norm_gamma: NormParameter
mlp_norm_gamma: NormParameter
PARAM_MAPPING = {
"attn.c_attn.weight": "qkv_w.params",
"attn.c_attn.bias": "qkv_b.params",
"attn.c_proj.weight": "attn_out_w.params",
"mlp.w1.weight": "mlp_1_w.up_params",
"mlp.w2.weight": "mlp_1_w.gate_params",
"mlp.c_proj.weight": "mlp_2_w.params",
"ln_1.weight": "attn_norm_gamma.params",
"ln_2.weight": "mlp_norm_gamma.params",
}
class QwenNonTransformerContainer(LayerContainer):
"""
Non-Transformer layer container for the Qwen model.
"""
word_emb: EmbeddingParameter
word_unembed: UnembedParameter
final_norm: NormParameter
PARAM_MAPPING = {
"transformer.wte.weight": "word_emb.params",
"transformer.ln_f.weight": "final_norm.params",
"lm_head.weight": "word_unembed.params",
}
@@ -0,0 +1,223 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...inference_utils import ActivationType, DtypeEnum
from .. import *
from ...modules.configs import *
from ...modules.interfaces import *
from ...modules import heuristics
from ...ragged import RaggedBatchWrapper
from .container import QwenNonTransformerContainer, QwenTransformerContainer
class QwenInferenceModel(DSTransformerModelBase):
"""
Inference model implementation for ragged batching for Llama-2 models.
"""
_non_transformer: Optional[QwenNonTransformerContainer]
"""
Embed + unembed container. Specializing the type annotation.
"""
_transformer: Optional[Iterable[QwenTransformerContainer]]
"""
Per-layer transformer container. Specializing the type annotation.
"""
"""
Properties ineherited from `DSInferenceModelBase`
"""
@property
def max_sequence_length(self) -> int:
return self._config.max_seq_length
"""
Properties ineherited from `DSTransformerModelBase`
"""
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return self.model_dim // self.n_heads
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return self._config.intermediate_size // 2
@property
def n_heads_kv(self) -> int:
return self._config.hidden_size // self._config.kv_channels
@property
def activation_dtype(self) -> DtypeEnum:
autoset_precision = self._config.bf16 + self._config.fp16 == 0
if autoset_precision:
return DtypeEnum.fp16
if self._config.fp16:
return DtypeEnum.fp16
elif self._config.bf16:
# TODO(ZonePG): bf16 inference results may be different from huggingface bf16,
# because in rms_norm, Qwen still use float() instead of bf16
return DtypeEnum.bf16
else:
raise NotImplementedError("Only fp16 and bf16 are supported")
@property
def mlp_activation_fn(self) -> ActivationType:
return ActivationType.SiGLU
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.RMSNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.rotate_half
@property
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
return RotateHalfConfig(theta_base=self._config.rotary_emb_base)
def make_norm_layer(self) -> None:
"""
Instantiates the normalization layer for the model. This sets the `self.norm` attribute.
TODO(cmikeh2): In the future we'll distinguish between the different norm objects,
but for now we'll just use the same one for all of them.
"""
norm_config = DSNormConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
type=self.norm_type,
channels=self.model_dim,
residual_dtype=self.activation_dtype,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
eps=self._config.layer_norm_epsilon,
)
self.norm = heuristics.instantiate_pre_norm(norm_config, self._engine_config)
"""
Forward implementations
"""
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs the embedding lookup prior to running the transformer of the model.
Arguments:
ragged_batch (RaggedBatchWrapper): The batch to embed.
Returns:
torch.Tensor: The embedded batch.
"""
embed = self.embed(ragged_batch, self._non_transformer.word_emb)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer_layer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Executes one (slightly offset) layer of the transformer. This implementation does a peak-ahead
optimization to fuse the layer norm of the next layer into the current layer.
Arguments:
layer_idx (int): The index of the layer to execute.
residual (torch.Tensor): The residual tensor from the previous layer.
hidden_states (torch.Tensor): The hidden states from the previous layer. This is the
hidden states after pre normalization.
ragged_batch_info (RaggedBatchWrapper): The batch metadata.
"""
# TODO(cmikeh2): Distribute ragged_batch_info to all modules
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
hidden_states = self.qkv(hidden_states, cur_params.qkv_w, b=cur_params.qkv_b)
hidden_states = self.attn(hidden_states, kv_cache, ragged_batch_info)
hidden_states = self.attn_out(hidden_states, cur_params.attn_out_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
residual, hidden_states = self.norm(residual, hidden_states, cur_params.mlp_norm_gamma, beta=None)
# Should be configurable in the future
hidden_states = self.mlp_1(hidden_states, cur_params.mlp_1_w, b=None)
hidden_states = self.mlp_2(hidden_states, cur_params.mlp_2_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
if layer_idx != self.num_layers - 1:
next_params = self._transformer[layer_idx + 1]
residual, hidden_states = self.norm(residual, hidden_states, next_params.attn_norm_gamma, beta=None)
else:
# On last layer, we just need to perform the residual add. Adding into the residual
# here is safe.
residual.add_(hidden_states)
return residual, hidden_states
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs unembedding of the hidden states to logits. This will only sample the final
token of each sequence.
"""
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed,
ragged_batch_info,
gamma=self._non_transformer.final_norm)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
residual, hidden_states = self.norm(residual, None, self._transformer[0].attn_norm_gamma, beta=None)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer_layer(layer_idx, residual, hidden_states,
wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,30 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .container import QwenNonTransformerContainer, QwenTransformerContainer
from .model import QwenInferenceModel
class QwenPolicy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> QwenInferenceModel:
return QwenInferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
transformer_containers = [QwenTransformerContainer(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['transformer.h'], transformer_containers)
map.set_non_transformer_params(QwenNonTransformerContainer(self.model))
map.set_unmapped_params(['transformer.rotary_emb.inv_freq'])
return map
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import Qwen2Policy
@@ -0,0 +1,82 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Create a container object to save model-specific tensors using the policy file above.
from ..common_parameters import *
from ..layer_container_base import LayerContainer
'''
# HF Qwen2 model looks like this:
Qwen2ForCausalLM(
(model): Qwen2Model(
(embed_tokens): Embedding(151936, 1024)
(layers): ModuleList(
(0-23): 24 x Qwen2DecoderLayer(
(self_attn): Qwen2SdpaAttention(
(q_proj): Linear(in_features=1024, out_features=1024, bias=True)
(k_proj): Linear(in_features=1024, out_features=1024, bias=True)
(v_proj): Linear(in_features=1024, out_features=1024, bias=True)
(o_proj): Linear(in_features=1024, out_features=1024, bias=False)
(rotary_emb): Qwen2RotaryEmbedding()
)
(mlp): Qwen2MLP(
(gate_proj): Linear(in_features=1024, out_features=2816, bias=False)
(up_proj): Linear(in_features=1024, out_features=2816, bias=False)
(down_proj): Linear(in_features=2816, out_features=1024, bias=False)
(act_fn): SiLU()
)
(input_layernorm): Qwen2RMSNorm()
(post_attention_layernorm): Qwen2RMSNorm()
)
)
(norm): Qwen2RMSNorm()
)
(lm_head): Linear(in_features=1024, out_features=151936, bias=False)
)
'''
class Qwen2TransformerContainer(LayerContainer):
"""
Transformer layer container for the Qwen2 model.
"""
qkv_w: UnfusedQKVParameter
qkv_b: UnfusedQKVParameter
attn_out_w: AttentionOutputParameter
mlp_1_w: GatedMLPParameter
mlp_2_w: MLP2Parameter
attn_norm_gamma: NormParameter
mlp_norm_gamma: NormParameter
PARAM_MAPPING = {
"self_attn.q_proj.weight": "qkv_w.q_params",
"self_attn.k_proj.weight": "qkv_w.k_params",
"self_attn.v_proj.weight": "qkv_w.v_params",
"self_attn.q_proj.bias": "qkv_b.q_params",
"self_attn.k_proj.bias": "qkv_b.k_params",
"self_attn.v_proj.bias": "qkv_b.v_params",
"self_attn.o_proj.weight": "attn_out_w.params",
"mlp.gate_proj.weight": "mlp_1_w.gate_params",
"mlp.up_proj.weight": "mlp_1_w.up_params",
"mlp.down_proj.weight": "mlp_2_w.params",
"input_layernorm.weight": "attn_norm_gamma.params",
"post_attention_layernorm.weight": "mlp_norm_gamma.params",
}
class Qwen2NonTransformerContainer(LayerContainer):
"""
Non-Transformer layer container for the Qwen2 model.
"""
word_emb: EmbeddingParameter
word_unembed: UnembedParameter
final_norm: NormParameter
PARAM_MAPPING = {
"model.embed_tokens.weight": "word_emb.params",
"model.norm.weight": "final_norm.params",
"lm_head.weight": "word_unembed.params",
}
@@ -0,0 +1,221 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...inference_utils import ActivationType, DtypeEnum
from .. import *
from ...modules.configs import *
from ...modules.interfaces import *
from ...modules import heuristics
from ...ragged import RaggedBatchWrapper
from .container import Qwen2NonTransformerContainer, Qwen2TransformerContainer
class Qwen2InferenceModel(DSTransformerModelBase):
"""
Inference model implementation for ragged batching for Llama-2 models.
"""
_non_transformer: Optional[Qwen2NonTransformerContainer]
"""
Embed + unembed container. Specializing the type annotation.
"""
_transformer: Optional[Iterable[Qwen2TransformerContainer]]
"""
Per-layer transformer container. Specializing the type annotation.
"""
"""
Properties ineherited from `DSInferenceModelBase`
"""
@property
def max_sequence_length(self) -> int:
return self._config.max_seq_length
"""
Properties ineherited from `DSTransformerModelBase`
"""
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return self.model_dim // self.n_heads
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return self._config.intermediate_size
@property
def n_heads_kv(self) -> int:
return self._config.num_key_value_heads
@property
def activation_dtype(self) -> DtypeEnum:
# TODO(ZonePG): bf16 inference results may be different from huggingface bf16,
# because in rms_norm, Qwen still use float() instead of bf16
# if self._config.torch_dtype == torch.float16:
# return DtypeEnum.fp16
# elif self._config.torch_dtype == torch.bfloat16:
# return DtypeEnum.bf16
# else:
# raise NotImplementedError("Only fp16 and bf16 are supported")
return DtypeEnum.fp16
@property
def mlp_activation_fn(self) -> ActivationType:
return ActivationType.SiGLU
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.RMSNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.rotate_half
@property
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
return RotateHalfConfig(theta_base=self._config.rope_theta)
def make_norm_layer(self) -> None:
"""
Instantiates the normalization layer for the model. This sets the `self.norm` attribute.
TODO(cmikeh2): In the future we'll distinguish between the different norm objects,
but for now we'll just use the same one for all of them.
"""
norm_config = DSNormConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
type=self.norm_type,
channels=self.model_dim,
residual_dtype=self.activation_dtype,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
eps=self._config.rms_norm_eps,
)
self.norm = heuristics.instantiate_pre_norm(norm_config, self._engine_config)
"""
Forward implementations
"""
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs the embedding lookup prior to running the transformer of the model.
Arguments:
ragged_batch (RaggedBatchWrapper): The batch to embed.
Returns:
torch.Tensor: The embedded batch.
"""
embed = self.embed(ragged_batch, self._non_transformer.word_emb)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer_layer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Executes one (slightly offset) layer of the transformer. This implementation does a peak-ahead
optimization to fuse the layer norm of the next layer into the current layer.
Arguments:
layer_idx (int): The index of the layer to execute.
residual (torch.Tensor): The residual tensor from the previous layer.
hidden_states (torch.Tensor): The hidden states from the previous layer. This is the
hidden states after pre normalization.
ragged_batch_info (RaggedBatchWrapper): The batch metadata.
"""
# TODO(cmikeh2): Distribute ragged_batch_info to all modules
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
hidden_states = self.qkv(hidden_states, cur_params.qkv_w, b=cur_params.qkv_b)
hidden_states = self.attn(hidden_states, kv_cache, ragged_batch_info)
hidden_states = self.attn_out(hidden_states, cur_params.attn_out_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
residual, hidden_states = self.norm(residual, hidden_states, cur_params.mlp_norm_gamma, beta=None)
# Should be configurable in the future
hidden_states = self.mlp_1(hidden_states, cur_params.mlp_1_w, b=None)
hidden_states = self.mlp_2(hidden_states, cur_params.mlp_2_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
if layer_idx != self.num_layers - 1:
next_params = self._transformer[layer_idx + 1]
residual, hidden_states = self.norm(residual, hidden_states, next_params.attn_norm_gamma, beta=None)
else:
# On last layer, we just need to perform the residual add. Adding into the residual
# here is safe.
residual.add_(hidden_states)
return residual, hidden_states
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs unembedding of the hidden states to logits. This will only sample the final
token of each sequence.
"""
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed,
ragged_batch_info,
gamma=self._non_transformer.final_norm)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
residual, hidden_states = self.norm(residual, None, self._transformer[0].attn_norm_gamma, beta=None)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer_layer(layer_idx, residual, hidden_states,
wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,31 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .container import Qwen2NonTransformerContainer, Qwen2TransformerContainer
from .model import Qwen2InferenceModel
class Qwen2Policy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> Qwen2InferenceModel:
return Qwen2InferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
transformer_containers = [Qwen2TransformerContainer(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['model.layers'], transformer_containers)
map.set_non_transformer_params(Qwen2NonTransformerContainer(self.model))
map.set_unmapped_params(
[f'model.layers.{i}.self_attn.rotary_emb.inv_freq' for i in range(self.model.num_layers)])
return map
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .policy import Qwen2MoePolicy
@@ -0,0 +1,103 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Create a container object to save model-specific tensors using the policy file above.
from ..common_parameters import *
from ..layer_container_base import LayerContainer
'''
# HF Qwen2-57B-A14B model looks like this:
Qwen2MoeForCausalLM(
(model): Qwen2MoeModel(
(embed_tokens): Embedding(151936, 3584)
(layers): ModuleList(
(0-27): 28 x Qwen2MoeDecoderLayer(
(self_attn): Qwen2MoeSdpaAttention(
(q_proj): Linear(in_features=3584, out_features=3584, bias=True)
(k_proj): Linear(in_features=3584, out_features=512, bias=True)
(v_proj): Linear(in_features=3584, out_features=512, bias=True)
(o_proj): Linear(in_features=3584, out_features=3584, bias=False)
(rotary_emb): Qwen2MoeRotaryEmbedding()
)
(mlp): Qwen2MoeSparseMoeBlock(
(gate): Linear(in_features=3584, out_features=64, bias=False)
(experts): ModuleList(
(0-63): 64 x Qwen2MoeMLP(
(gate_proj): Linear(in_features=3584, out_features=2560, bias=False)
(up_proj): Linear(in_features=3584, out_features=2560, bias=False)
(down_proj): Linear(in_features=2560, out_features=3584, bias=False)
(act_fn): SiLU()
)
)
(shared_expert): Qwen2MoeMLP(
(gate_proj): Linear(in_features=3584, out_features=20480, bias=False)
(up_proj): Linear(in_features=3584, out_features=20480, bias=False)
(down_proj): Linear(in_features=20480, out_features=3584, bias=False)
(act_fn): SiLU()
)
(shared_expert_gate): Linear(in_features=3584, out_features=1, bias=False)
)
(input_layernorm): Qwen2MoeRMSNorm((3584,), eps=1e-06)
(post_attention_layernorm): Qwen2MoeRMSNorm((3584,), eps=1e-06)
)
)
(norm): Qwen2MoeRMSNorm((3584,), eps=1e-06)
)
(lm_head): Linear(in_features=3584, out_features=151936, bias=False)
)
'''
class Qwen2MoeTransformerContainer(LayerContainer):
"""
Transformer layer container for the Qwen2Moe model.
"""
qkv_w: UnfusedQKVParameter
qkv_b: UnfusedQKVParameter
attn_out_w: AttentionOutputParameter
moe_gate: MoEGatingWeightParameter
moe_mlp_1: UnfusedMoEGatedMLPParameter
moe_mlp_2: UnfusedMoEMLP2Parameter
shared_moe_mlp_1: GatedMLPParameter
shared_moe_mlp_2: MLP2Parameter
shared_moe_gate: MoEGatingWeightParameter
attn_norm_gamma: NormParameter
mlp_norm_gamma: NormParameter
PARAM_MAPPING = {
"self_attn.q_proj.weight": "qkv_w.q_params",
"self_attn.k_proj.weight": "qkv_w.k_params",
"self_attn.v_proj.weight": "qkv_w.v_params",
"self_attn.q_proj.bias": "qkv_b.q_params",
"self_attn.k_proj.bias": "qkv_b.k_params",
"self_attn.v_proj.bias": "qkv_b.v_params",
"self_attn.o_proj.weight": "attn_out_w.params",
"mlp.gate.weight": "moe_gate.params",
"mlp.experts.*.gate_proj.weight": "moe_mlp_1.gating_experts",
"mlp.experts.*.up_proj.weight": "moe_mlp_1.up_experts",
"mlp.experts.*.down_proj.weight": "moe_mlp_2.experts",
"mlp.shared_expert.gate_proj.weight": "shared_moe_mlp_1.gate_params",
"mlp.shared_expert.up_proj.weight": "shared_moe_mlp_1.up_params",
"mlp.shared_expert.down_proj.weight": "shared_moe_mlp_2.params",
"mlp.shared_expert_gate.weight": "shared_moe_gate.params",
"input_layernorm.weight": "attn_norm_gamma.params",
"post_attention_layernorm.weight": "mlp_norm_gamma.params",
}
class Qwen2MoeNonTransformerContainer(LayerContainer):
"""
Non-Transformer layer container for the Qwen2Moe model.
"""
word_emb: EmbeddingParameter
word_unembed: UnembedParameter
final_norm: NormParameter
PARAM_MAPPING = {
"model.embed_tokens.weight": "word_emb.params",
"model.norm.weight": "final_norm.params",
"lm_head.weight": "word_unembed.params",
}
@@ -0,0 +1,359 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional, Tuple
import torch
import deepspeed.comm as dist
from ...allocator import empty_from
from ...config_v2 import RaggedInferenceEngineConfig
from ...inference_utils import ActivationType, DtypeEnum
from ...model_implementations import *
from ...modules.configs import *
from ...modules.interfaces import *
from ...modules import heuristics
from ...ragged import RaggedBatchWrapper
from ..inference_model_base import (
DSModelImplementationConfig,
MPType,
)
from .container import Qwen2MoeNonTransformerContainer, Qwen2MoeTransformerContainer
class Qwen2MoeInferenceModel(DSMoETransformerModelBase):
"""
Inference model implementation for Qwen2MoE models.
"""
_non_transformer: Optional[Qwen2MoeNonTransformerContainer]
"""
Embed + unembed container. Specializing the type annotation.
"""
_transformer: Optional[Iterable[Qwen2MoeTransformerContainer]]
"""
Per-layer transformer container. Specializing the type annotation.
"""
"""
Properties ineherited from `DSInferenceModelBase`
"""
@property
def max_sequence_length(self) -> int:
return self._config.max_position_embeddings
"""
Properties ineherited from `DSTransformerModelBase`
"""
@property
def num_layers(self) -> int:
return self._config.num_hidden_layers
@property
def model_dim(self) -> int:
return self._config.hidden_size
@property
def vocab_size(self) -> int:
return self._config.vocab_size
@property
def head_size(self) -> int:
return self.model_dim // self.n_heads
@property
def n_heads(self) -> int:
return self._config.num_attention_heads
@property
def intermediate_dim(self) -> int:
return self._config.shared_expert_intermediate_size
@property
def n_heads_kv(self) -> int:
return self._config.num_key_value_heads
@property
def activation_dtype(self) -> DtypeEnum:
# TODO(ZonePG): bf16 inference results may be different from huggingface bf16,
# because in rms_norm, Qwen still use float() instead of bf16
# if self._config.torch_dtype == torch.float16:
# return DtypeEnum.fp16
# elif self._config.torch_dtype == torch.bfloat16:
# return DtypeEnum.bf16
# else:
# raise NotImplementedError("Only fp16 and bf16 are supported")
return DtypeEnum.fp16
@property
def mlp_activation_fn(self) -> ActivationType:
return ActivationType.SiGLU
@property
def norm_type(self) -> NormTypeEnum:
return NormTypeEnum.RMSNorm
@property
def positional_embedding_type(self) -> PositionalEmbeddingType:
return PositionalEmbeddingType.rotate_half
@property
def positional_embedding_config(self) -> Optional[RotateHalfConfig]:
return RotateHalfConfig(theta_base=self._config.rope_theta)
"""
Inherited from `DSMoETransformerModelBase`
"""
@property
def n_experts(self) -> int:
return self._config.num_experts
@property
def n_top_k(self) -> int:
return self._config.num_experts_per_tok
@property
def normalize_expert_scores(self) -> bool:
return self._config.norm_topk_prob
def make_moe_layer(self) -> None:
"""
Instantiates the MoE layer for the model. This sets the `self.moe` attribute.
"""
sharded_dim = sharded_intermediate_dim(self.intermediate_dim // self.n_top_k, self.tp_size, self.tp_rank)
moe_config = DSMoEConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
model_dim=self.model_dim,
intermediate_features=sharded_dim,
activation=self.mlp_activation_fn,
n_experts=self.n_experts,
top_k=self.n_top_k,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
normalize_scores=self.normalize_expert_scores,
)
self.moe = heuristics.instantiate_moe(moe_config, self._engine_config)
######### MLP 1 #########
def make_shared_expert_mlp_1_layer(self) -> None:
"""
Instantiates the linear projection layer for the first MLP in the feedforward network.
This sets the `self.mlp_1` attribute.
"""
shard_size = sharded_intermediate_dim(self.intermediate_dim, self.tp_size, self.tp_rank)
linear_config = DSLinearConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
in_channels=self.model_dim,
out_channels=shard_size,
activation=self.mlp_activation_fn,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
)
self.shared_expert_mlp_1 = heuristics.instantiate_linear(linear_config, self._engine_config)
######### MLP 2 #########
def make_shared_expert_mlp_2_layer(self) -> None:
"""
Instantiates the linear projection layer for the second MLP in the feedforward network.
This sets the `self.mlp_2` attribute.
"""
shard_size = sharded_intermediate_dim(self.intermediate_dim, self.tp_size, self.tp_rank)
linear_config = DSLinearConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
in_channels=shard_size,
out_channels=self.model_dim,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
)
self.shared_expert_mlp_2 = heuristics.instantiate_linear(linear_config, self._engine_config)
######### MLP 2 #########
def make_shared_expert_gate_layer(self) -> None:
"""
Instantiates the linear projection layer for the second MLP in the feedforward network.
This sets the `self.mlp_2` attribute.
"""
shard_size = sharded_intermediate_dim(self.model_dim, self.tp_size, self.tp_rank)
linear_config = DSLinearConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
in_channels=shard_size,
out_channels=8,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
)
self.shared_expert_gate = heuristics.instantiate_linear(linear_config, self._engine_config)
def make_norm_layer(self) -> None:
"""
Instantiates the normalization layer for the model. This sets the `self.norm` attribute.
TODO(cmikeh2): In the future we'll distinguish between the different norm objects,
but for now we'll just use the same one for all of them.
"""
norm_config = DSNormConfig(
max_tokens=self._engine_config.state_manager.max_ragged_batch_size,
type=self.norm_type,
channels=self.model_dim,
residual_dtype=self.activation_dtype,
input_dtype=self.activation_dtype,
output_dtype=self.activation_dtype,
eps=self._config.rms_norm_eps,
)
self.norm = heuristics.instantiate_pre_norm(norm_config, self._engine_config)
"""
Model implementation
"""
def __init__(self, config: DSModelImplementationConfig, engine_config: RaggedInferenceEngineConfig,
base_mp_group: MPType) -> None:
"""
Base implementation for initialization. By default, this will initialize
the traditional components of a transformer model:
- Embedding
- QKV projection
- Self attention
- Attention output projection
- Feed forward network
- Normalization
- Unembedding
Arguments:
config (DSModelImplementationConfig): Model-specific configuration. No assumptions
should be made about this config that are not closely tied to the specific
model implementation.
engine_config (RaggedInferenceEngineConfig): Engine configuration.
base_mp_group (MPType): Base communication group for Tensor-parallel inference.
"""
super().__init__(config, engine_config, base_mp_group)
self.make_norm_layer()
self.make_qkv_layer()
self.make_attn_layer()
self.make_attn_out_layer()
self.make_moe_layer()
self.make_shared_expert_mlp_1_layer()
self.make_shared_expert_mlp_2_layer()
self.make_shared_expert_gate_layer()
self.make_embedding_layer()
self.make_unembedding_layer()
self._kv_cache_config = None
"""
Forward implementations
"""
def _forward_embed(self, ragged_batch: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs the embedding lookup prior to running the transformer of the model.
Arguments:
ragged_batch (RaggedBatchWrapper): The batch to embed.
Returns:
torch.Tensor: The embedded batch.
"""
embed = self.embed(ragged_batch, self._non_transformer.word_emb)
if embed.shape[-1] != self.model_dim:
raise ValueError(f"Embedding output shape {embed.shape} does not match model_dim {self.model_dim}")
return embed
def _forward_transformer(self, layer_idx: int, residual: torch.Tensor, hidden_states: torch.Tensor,
ragged_batch_info: RaggedBatchWrapper) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Executes one (slightly offset) layer of the transformer. This implementation does a peak-ahead
optimization to fuse the layer norm of the next layer into the current layer.
Arguments:
layer_idx (int): The index of the layer to execute.
residual (torch.Tensor): The residual tensor from the previous layer.
hidden_states (torch.Tensor): The hidden states from the previous layer. This is the
hidden states after pre normalization.
ragged_batch_info (RaggedBatchWrapper): The batch metadata.
"""
# TODO(cmikeh2): Distribute ragged_batch_info to all modules
cur_params = self._transformer[layer_idx]
kv_cache = self.state_manager.get_cache(layer_idx)
hidden_states = self.qkv(hidden_states, cur_params.qkv_w, b=cur_params.qkv_b)
hidden_states = self.attn(hidden_states, kv_cache, ragged_batch_info)
hidden_states = self.attn_out(hidden_states, cur_params.attn_out_w, b=None)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
residual, hidden_states = self.norm(residual, hidden_states, cur_params.mlp_norm_gamma, beta=None)
shared_expert_output = self.shared_expert_mlp_1(hidden_states, cur_params.shared_moe_mlp_1, b=None)
shared_expert_output = self.shared_expert_mlp_2(shared_expert_output, cur_params.shared_moe_mlp_2, b=None)
shared_expert_gate_output = self.shared_expert_gate(hidden_states, cur_params.shared_moe_gate, b=None)[..., :1]
# shared_expert_gate_output shape[-1] is 1
shared_expert_output.mul_(torch.sigmoid(shared_expert_gate_output))
hidden_states = self.moe(hidden_states, ragged_batch_info, cur_params.moe_gate, cur_params.moe_mlp_1,
cur_params.moe_mlp_2)
hidden_states.add_(shared_expert_output)
if self.tp_size > 1:
dist.all_reduce(hidden_states, group=self._base_mp_group)
if layer_idx != self.num_layers - 1:
next_params = self._transformer[layer_idx + 1]
residual, hidden_states = self.norm(residual, hidden_states, next_params.attn_norm_gamma, beta=None)
else:
# On last layer, we just need to perform the residual add. Adding into the residual
# here is safe.
residual.add_(hidden_states)
return residual, hidden_states
def _forward_unembed(self, hidden_states: torch.Tensor, ragged_batch_info: RaggedBatchWrapper) -> torch.Tensor:
"""
Performs unembedding of the hidden states to logits. This will only sample the final
token of each sequence.
"""
logits = self.unembed(hidden_states,
self._non_transformer.word_unembed,
ragged_batch_info,
gamma=self._non_transformer.final_norm)
if self.tp_size > 1:
comm_buffer = empty_from(self._comm_logits, (self.tp_size, logits.shape[0], logits.shape[1]))
full_logits = empty_from(self._return_logits, (logits.shape[0], self.vocab_size))
dist.all_gather_into_tensor(comm_buffer, logits, group=self._base_mp_group)
full_logits.copy_(comm_buffer.permute(1, 0, 2).reshape(logits.shape[0], self.vocab_size))
return full_logits
else:
return logits
def forward(self, wrapped_batch: RaggedBatchWrapper) -> torch.Tensor:
residual = self._forward_embed(wrapped_batch)
residual, hidden_states = self.norm(residual, None, self._transformer[0].attn_norm_gamma, beta=None)
for layer_idx in range(self.num_layers):
residual, hidden_states = self._forward_transformer(layer_idx, residual, hidden_states, wrapped_batch)
return self._forward_unembed(residual, wrapped_batch)
@@ -0,0 +1,30 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any
from ...config_v2 import RaggedInferenceEngineConfig
from ..inference_policy_base import ContainerMap, InferenceV2Policy
from .container import Qwen2MoeNonTransformerContainer, Qwen2MoeTransformerContainer
from .model import Qwen2MoeInferenceModel
class Qwen2MoePolicy(InferenceV2Policy):
def instantiate_model(self, engine_config: RaggedInferenceEngineConfig, mp_group: Any) -> Qwen2MoeInferenceModel:
return Qwen2MoeInferenceModel(config=self._model_config, engine_config=engine_config, base_mp_group=mp_group)
def build_container_map(self) -> ContainerMap:
map = ContainerMap()
transformer_containers = [Qwen2MoeTransformerContainer(self.model) for _ in range(self.model.num_layers)]
map.set_transformer_params(['model.layers'], transformer_containers)
map.set_non_transformer_params(Qwen2MoeNonTransformerContainer(self.model))
map.set_unmapped_params([])
return map
@@ -0,0 +1,12 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .attn import *
from .attn_out import *
from .embedding import *
from .mlp import *
from .qkv import *
from .types import *
from .unembed import *
@@ -0,0 +1,63 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional, Tuple
def get_local_heads(shard_rank: int,
num_shards: int,
n_heads_q: int,
n_heads_kv: Optional[int] = None) -> Tuple[int, int]:
"""
Helper to determine the number of local heads of a given shard.
Args:
shard_rank (int): The rank of the shard.
num_shards (int): The total number of shards that attention is distributed over.
n_heads_q (int): The number of query heads.
n_heads_kv (int): The number of key/value heads. If not passed, it is assumed that
the number of query and key/value heads are the same.
"""
if n_heads_q < num_shards:
raise ValueError("There must be at least as many attention heads as there are shards.")
if n_heads_kv is None or n_heads_kv == n_heads_q:
# MHA attention
base_heads = n_heads_q // num_shards
extra_heads = n_heads_q % num_shards
if shard_rank < extra_heads:
return (base_heads + 1), (base_heads + 1)
else:
return base_heads, base_heads
else:
# GQA attention
if n_heads_q % n_heads_kv != 0:
raise ValueError("Must be an even ratio between query and key/value heads.")
if n_heads_kv < num_shards and num_shards % n_heads_kv != 0:
raise ValueError(
"If splitting a group across multiple shards, we must be able to distribute the groups evenly.")
if n_heads_kv >= num_shards and n_heads_kv % num_shards != 0:
raise ValueError("If parallelizing groups, must be able to evenly distribute them.")
q_ratio = n_heads_q // n_heads_kv
if n_heads_kv >= num_shards:
local_kv_heads = n_heads_kv // num_shards
local_q_heads = local_kv_heads * q_ratio
return local_q_heads, local_kv_heads
else:
group_sharding_size = num_shards // n_heads_kv
group_rank_idx = shard_rank % group_sharding_size
base_heads = q_ratio // group_sharding_size
extra_heads = q_ratio % group_sharding_size
if group_rank_idx < extra_heads:
return (base_heads + 1), 1
else:
return base_heads, 1
@@ -0,0 +1,111 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional
import torch
from .types import ShardingType
from .utils import shard_param, get_shard_endpoints
def shard_attn_out_param(param: torch.Tensor,
shard_rank: int,
num_shards: int,
head_size: int,
n_heads_q: Optional[int] = None,
n_heads_kv: Optional[int] = None) -> Optional[torch.Tensor]:
"""
Utility method for sharding an attention output parameter.
"""
if len(param.shape) == 1:
# We will do the bias addition on the 0th rank only rather than scale the parameter and
# implicitly reconstruct this in the distributed reduce.
return param if shard_rank == 0 else None
assert n_heads_kv is None or (n_heads_q is not None
and n_heads_kv is not None), "n_heads_kv should not be passed without n_heads_q"
mha_sharding = n_heads_kv is None or n_heads_q == n_heads_kv
if mha_sharding:
return shard_param(param, ShardingType.INNER_DIMENSION, shard_rank, num_shards, granularity=head_size)
else:
assert param.shape[0] == head_size * n_heads_q, "GQA param shape is not correct"
# 32 KV heads, 16 shards for example
even_kv_sharding = n_heads_kv % num_shards == 0
# 8 KV heads, 16 shards for example
even_kv_distribution = num_shards % n_heads_kv == 0
assert even_kv_sharding or even_kv_distribution, "No partitioning algorithm for this yet."
if even_kv_sharding:
# Same as original sharding scenario
return shard_param(param, ShardingType.INNER_DIMENSION, shard_rank, num_shards, granularity=head_size)
else:
# We will first do a sharding on the KV and Q to map to the one KV shard per group of Q.
q_sharding_degree = num_shards // n_heads_kv
kv_head = shard_rank // q_sharding_degree
q_sharding_rank = shard_rank % q_sharding_degree
q_factor = n_heads_q // n_heads_kv
q_chunk = param[..., q_factor * kv_head * head_size:q_factor * (kv_head + 1) * head_size]
return shard_param(q_chunk,
ShardingType.INNER_DIMENSION,
q_sharding_rank,
q_sharding_degree,
granularity=head_size)
def attn_out_in_features(out_features: int,
shard_rank: int,
num_shards: int,
head_size: int,
n_heads_q: Optional[int] = None,
n_heads_kv: Optional[int] = None) -> int:
"""
Helper to calculate the expected output projection dimension of a QKV projection matrix.
Args:
in_features (int): The model dimension.
shard_rank (int): Which rank to return the corresponding size for.
num_shards (int): The total number of shards the parameter is distributed across.
head_size (int): The size of each attention head.
n_heads_q (int): The number of query heads on the model. This only needs to be passed if the number
of query and key/value heads are different. If passed without n_heads_kv, default
MHA partitioning will be used.
n_heads_kv (int): The number of key and value heads on the model. This only needs to be passed
if the number of query and key/value heads are different. This argument cannot be passed without
also passing n_heads_q (we want to explicitly opt into GQA sharding).
"""
assert n_heads_kv is None or (n_heads_q is not None
and n_heads_kv is not None), "n_heads_kv should not be passed without n_heads_q"
mha_sharding = n_heads_kv is None or n_heads_q == n_heads_kv
if mha_sharding:
endpoints = get_shard_endpoints(out_features, shard_rank, num_shards, granularity=head_size)
return endpoints[1] - endpoints[0]
else:
if n_heads_kv >= num_shards:
assert n_heads_kv % num_shards == 0, "No partitioning algorithm for this yet."
n_local_groups = n_heads_kv // num_shards
group_size = n_heads_q // n_heads_kv
return n_local_groups * head_size * group_size
else:
assert num_shards % n_heads_kv == 0, "No partitioning algorithm for this yet."
q_split_degree = num_shards // n_heads_kv
q_split_rank = shard_rank % q_split_degree
split_granularity = (n_heads_q // n_heads_kv) * head_size
q_endpoints = get_shard_endpoints(split_granularity, q_split_rank, q_split_degree, granularity=head_size)
return q_endpoints[1] - q_endpoints[0]
@@ -0,0 +1,34 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from .types import ShardingType
from .utils import shard_param, get_shard_endpoints
def shard_embedding_param(param: torch.Tensor, shard_rank: int, num_shards: int) -> torch.Tensor:
"""
Utility method for sharding an embedding parameter.
Args:
param (torch.Tensor): The parameter to shard. Should be of shape [vocab_size, model_dim]
shard_rank (int): Which shard of the partitioned tensor to return.
num_shards (int): The total number of shards the parameter is distributed across.
"""
return shard_param(param, ShardingType.INNER_DIMENSION, shard_rank, num_shards)
def sharded_embedding_dim(embedding_size: int, shard_rank: int, num_shards: int) -> int:
"""
Utility method for getting the size of the embedding dimension of a sharded embedding.
Args:
embedding_size (int): The size of the embedding.
shard_rank (int): Which shard of the partitioned tensor to return.
num_shards (int): The total number of shards the parameter is distributed across.
"""
start_idx, end_idx = get_shard_endpoints(embedding_size, shard_rank, num_shards)
return end_idx - start_idx
@@ -0,0 +1,75 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional
import torch
from .types import ShardingType, DEFAULT_SHARD_GRANULARITY
from .utils import shard_param, get_shard_endpoints
def shard_mlp_1_param(param: torch.Tensor,
shard_rank: int,
num_shards: int,
gated: bool = False,
is_moe: bool = False) -> torch.Tensor:
"""
Utility method for sharding an MLP 1 parameter. Both biases and weights are supported, as well
as for fused weights for MoE.
Args:
param (torch.Tensor): The parameter to shard.
shard_rank (int): Which shard of the partitioned tensor to return.
num_shards (int): The total number of shards the parameter is distributed across.
gated (bool): Whether or not the parameter is from a gated MLP.
"""
bias_dims = 2 if is_moe else 1
if gated:
return shard_param(param,
ShardingType.OUTER_DIMENSION,
shard_rank,
num_shards,
granularity=DEFAULT_SHARD_GRANULARITY * 2,
bias_dims=bias_dims)
else:
return shard_param(param, ShardingType.OUTER_DIMENSION, shard_rank, num_shards, bias_dims=bias_dims)
def shard_mlp_2_param(param: torch.Tensor,
shard_rank: int,
num_shards: int,
is_moe: bool = False) -> Optional[torch.Tensor]:
"""
Utility method for sharding an MLP 2 parameter.
Args:
param (torch.Tensor): The parameter to shard.
shard_rank (int): Which shard of the partitioned tensor to return.
num_shards (int): The total number of shards the parameter is distributed across.
is_moe (bool): Whether or not the parameter is from a MoE model.
"""
bias_dim_size = 2 if is_moe else 1
if len(param.shape) == bias_dim_size:
# We will do the bias addition on the 0th rank only rather than scale the parameter and
# implicitly reconstruct this in the distributed reduce.
return param if shard_rank == 0 else None
return shard_param(param, ShardingType.INNER_DIMENSION, shard_rank, num_shards)
def sharded_intermediate_dim(intermediate_size: int, num_shards: int, shard_rank: int) -> int:
"""
Utility method for getting the size of the intermediate dimension of a sharded MLP.
Args:
intermediate_size (int): The size of the intermediate dimension.
num_shards (int): The total number of shards the parameter is distributed across.
shard_rank (int): Which shard of the partitioned tensor to return.
"""
endpoints = get_shard_endpoints(intermediate_size, shard_rank, num_shards)
return endpoints[1] - endpoints[0]
@@ -0,0 +1,167 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional
import torch
from .types import ShardingType
from .utils import shard_param, get_shard_endpoints
def shard_qkv_param(param: torch.Tensor,
shard_rank: int,
num_shards: int,
head_size: int,
n_heads_q: Optional[int] = None,
n_heads_kv: Optional[int] = None) -> Optional[torch.Tensor]:
"""
Utility method for sharding a QKV parameter. Both biases and weights are supported. It is assumed
that the layout of the parameter is such that all Q heads, all K heads, and all V heads
are contiguous with respect to each other.
Args:
param (torch.Tensor): The parameter to shard.
shard_rank (int): Which shard of the partitioned tensor to return.
num_shards (int): The total number of shards the parameter is distributed across.
head_size (int): The size of each head.
n_heads_q (int): The number of query heads. This only needs to be passed if the number
of query and key/value heads are different. If passed without n_heads_kv, default
MHA partitioning will be used.
n_heads_kv (int): The number of key/value heads. This only needs to be passed if the number
of query and key/value heads are different. This argument should not be passed without
n_heads_q (we want to explicitly opt into GQA sharding).
"""
if n_heads_kv is not None and n_heads_q is None:
raise ValueError("n_heads_kv should not be passed without n_heads_q")
if param is None:
raise ValueError("param should not be None")
if n_heads_q is None:
# Guaranteed to be in MHA
if param.shape[0] // 3 % head_size != 0:
raise ValueError("MHA param shape is not correct")
n_heads_q = param.shape[0] // head_size // 3
mha_sharding = True
elif n_heads_kv is None:
mha_sharding = True
else:
mha_sharding = n_heads_q == n_heads_kv
if n_heads_q < num_shards:
raise ValueError("There must be at least as many query heads as there are shards.")
if mha_sharding:
return shard_param(param,
ShardingType.OUTER_DIMENSION,
shard_rank,
num_shards,
num_concatenated_matrices=3,
granularity=head_size)
else:
if n_heads_q % n_heads_kv != 0:
raise ValueError("Must be an even ratio between query and key/value heads.")
if param.shape[0] != head_size * (n_heads_q + 2 * n_heads_kv):
raise ValueError("GQA param shape is not correct")
# 32 KV heads, 16 shards for example
if n_heads_kv >= num_shards and n_heads_kv % num_shards != 0:
raise ValueError("Currently do not support uneven partitioning of KV heads for GQA.")
# 8 KV heads, 16 shards for example
if n_heads_kv < num_shards and num_shards % n_heads_kv != 0:
raise ValueError("Currently do not support distributing KV heads across different numbers of shards.")
else:
even_kv_sharding = n_heads_kv >= num_shards
q_param = param[:head_size * n_heads_q]
kv_param = param[head_size * n_heads_q:]
if even_kv_sharding:
# This is equivalent to the original sharding algorithm since n_heads_q = C * n_heads_kv.
# If n_heads_kv % num_shards == 0, then n_heads_q % num_shards == 0.
q_param = shard_param(q_param, ShardingType.OUTER_DIMENSION, shard_rank, num_shards, granularity=head_size)
kv_param = shard_param(kv_param,
ShardingType.OUTER_DIMENSION,
shard_rank,
num_shards,
num_concatenated_matrices=2,
granularity=head_size)
return torch.cat([q_param, kv_param], dim=0)
else:
# We will first do a sharding on the KV and Q to map to the one KV shard per group of Q.
q_sharding_degree = num_shards // n_heads_kv
kv_head = shard_rank // q_sharding_degree
k_param = kv_param[kv_head * head_size:(kv_head + 1) * head_size]
v_param = kv_param[(n_heads_kv + kv_head) * head_size:(n_heads_kv + kv_head + 1) * head_size]
q_sharding_rank = shard_rank % q_sharding_degree
q_factor = n_heads_q // n_heads_kv
q_chunk = q_param[q_factor * kv_head * head_size:q_factor * (kv_head + 1) * head_size]
q_param = shard_param(q_chunk,
ShardingType.OUTER_DIMENSION,
q_sharding_rank,
q_sharding_degree,
granularity=head_size)
return torch.cat([q_param, k_param, v_param], dim=0)
def qkv_out_features(in_features: int,
shard_rank: int,
num_shards: int,
head_size: int,
n_heads_q: Optional[int] = None,
n_heads_kv: Optional[int] = None) -> int:
"""
Helper to calculate the expected output projection dimension of a QKV projection matrix.
Args:
in_features (int): The model dimension.
shard_rank (int): Which rank to return the corresponding size for.
num_shards (int): The total number of shards the parameter is distributed across.
head_size (int): The size of each head.
n_heads_q (int): The number of query heads. This only needs to be passed if the number
of query and key/value heads are different. If passed without n_heads_kv, default
MHA partitioning will be used.
n_heads_kv (int): The number of key/value heads. This only needs to be passed if the number
of query and key/value heads are different. This argument cannot be passed without also
passing n_heads_q (we want to explicitly opt into GQA sharding).
"""
if n_heads_kv is not None and n_heads_q is None:
raise ValueError("n_heads_kv should not be passed without n_heads_q")
mha_sharding = n_heads_kv is None or n_heads_q == n_heads_kv
if n_heads_q is not None and in_features != head_size * n_heads_q:
raise ValueError("in_features is not consistent with n_heads_q and head_size")
if mha_sharding:
endpoints = get_shard_endpoints(in_features, shard_rank, num_shards, granularity=head_size)
return (endpoints[1] - endpoints[0]) * 3
else:
if n_heads_kv >= num_shards:
if n_heads_kv % num_shards != 0:
raise ValueError("The KV heads must be evenly distributed across the shards.")
n_local_groups = n_heads_kv // num_shards
group_size = n_heads_q // n_heads_kv
return n_local_groups * head_size * (2 + group_size)
else:
if num_shards % n_heads_kv != 0:
raise ValueError("A shared KV head must always partition across the same number of shards.")
q_split_degree = num_shards // n_heads_kv
q_split_rank = shard_rank % q_split_degree
split_granularity = (n_heads_q // n_heads_kv) * head_size
q_endpoints = get_shard_endpoints(split_granularity, q_split_rank, q_split_degree, granularity=head_size)
return (q_endpoints[1] - q_endpoints[0]) + 2 * head_size
@@ -0,0 +1,18 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from enum import Enum
DEFAULT_SHARD_GRANULARITY = 32
class ShardingType(Enum):
# Inner dimension sharding corresponds to splitting the Tensor along the K-dimension
# of a matrix multiplication. This would be used for attention_output or MLP2.
INNER_DIMENSION = 1
# Outer dimension sharding corresponds to splitting the Tensor along the N-dimension
# of a matrix multiplication. This would be used for the QKV and MLP1 projections.
OUTER_DIMENSION = 0
@@ -0,0 +1,41 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from .types import ShardingType
from .utils import shard_param, get_shard_endpoints
def shard_unembed_param(param: torch.Tensor, shard_rank: int, num_shards: int) -> torch.Tensor:
"""
Utility method for sharding an unembed parameter. We shard unembeddings on the vocab dimension
with the expectation of an all-gather to produce the full results.
TODO(cmikeh2): Really ideal would be if MII could have access to the comm and we would do
an A2A and sharded sampling.
Args:
param (torch.Tensor): The parameter to shard. Should be of shape [vocab_size, model_dim]
shard_rank (int): Which shard of the partitioned tensor to return.
num_shards (int): The total number of shards the parameter is distributed across.
Returns:
torch.Tensor: The sharded parameter of shape [sharded_vocab_size, model_dim]
"""
return shard_param(param, ShardingType.OUTER_DIMENSION, shard_rank, num_shards, granularity=1)
def sharded_unembed_dim(vocab_size: int, shard_rank: int, num_shards: int) -> int:
"""
Utility method for determining the sharded vocab size of a sharded unembed parameter.
Args:
vocab_size (int): The size of the vocabulary.
shard_rank (int): Which shard of the partitioned tensor to return.
num_shards (int): The total number of shards the parameter is distributed across.
"""
start_idx, end_idx = get_shard_endpoints(vocab_size, shard_rank, num_shards, granularity=1)
return end_idx - start_idx
@@ -0,0 +1,104 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional, Tuple
import torch
from .types import ShardingType, DEFAULT_SHARD_GRANULARITY
def get_shard_endpoints(dim_size: int,
shard_rank: int,
num_shards: int,
granularity: int = DEFAULT_SHARD_GRANULARITY) -> Tuple[int, int]:
"""
Given a dimension to shard with size dim_size, return the start and end indices of the slice
that belong to the given rank.
The typical use of this is as an internal helper function, so see if there is a higher level
API that better suits the application.
Args:
dim_size (int): The size of the dimension to shard.
shard_rank (int): The rank of the shard to return.
num_shards (int): Total number of shards the dimension will be distributed across.
granularity (int): The minimum alignment of the shard endpoints. This is used to support
non-even head counts as well as align dimensions to cleaner GEMM boundaries.
"""
assert dim_size % granularity == 0, "Dimension size must be divisible by granularity"
total_chunks = dim_size // granularity
base_chunks_per_rank = total_chunks // num_shards
remainder_chunks = total_chunks % num_shards
start_chunk_id = shard_rank * base_chunks_per_rank + min(shard_rank, remainder_chunks)
end_chunk_id = start_chunk_id + base_chunks_per_rank + (1 if shard_rank < remainder_chunks else 0)
return start_chunk_id * granularity, end_chunk_id * granularity
def shard_param(param: Optional[torch.Tensor],
shard_mode: ShardingType,
shard_rank: int,
num_shards: int,
num_concatenated_matrices: int = 1,
granularity: int = 32,
bias_dims: int = 1) -> torch.Tensor:
"""
Utility for sharding a parameter. This will return the slice of the parameter that should
exist on the given shard_rank given the sharding configuration. The workflow here is
to find the minimum bounded Tensor to shard, get the slicing endpoints, and then concatenate
as needed.
The typical use of this is as an internal helper function, so see if there is a higher level
API that better suits the application.
Args:
param (torch.Tensor): The parameter to shard.
shard_mode (ShardingType): The type of sharding to apply. See ShardingType for more context.
shard_rank (int): The rank of the shard to return.
num_shards (int): Total number of shards the parameter will be distrbuted across.
num_concatenated_matrices (int): The number of matrices that have been concatenated together in the original
parameter. An example of this is a fused QKV projection matrix, where the `num_concatenated_matrices`
argument would be 3.
granularity (int): The minimum alignment of the shard endpoints. For attention projection matrices, this
should be set to the head size to support non-even sharding.
bias_dims (int): The number of dimensions that are considered bias dimensions. This is used to support
sharding of MoE and non-MoE biases on the same codepath.
"""
assert shard_rank < num_shards, "Shard rank must be less than num_shards"
# Easier to hide this inside of the sharding logic than to add checks in every model
# implementation.
if param is None:
return None
if num_shards == 1:
# Trivial case of no sharding.
return param
if shard_mode == ShardingType.OUTER_DIMENSION:
def get_matrices(dim_idx: int) -> torch.Tensor:
dim_size = param.size(dim_idx) // num_concatenated_matrices
start_channel_id, end_channel_id = get_shard_endpoints(dim_size, shard_rank, num_shards, granularity)
return torch.chunk(param, num_concatenated_matrices, dim=dim_idx), start_channel_id, end_channel_id
if param.ndim == bias_dims:
# Special case for bias parameters.
matrices, start_channel_id, end_channel_id = get_matrices(dim_idx=-1)
return torch.cat([mat[..., start_channel_id:end_channel_id] for mat in matrices], dim=-1)
else:
# General case for weight parameters. This assumes MoE parameters are stored in the format of
# [num_experts, out_features, in_features]
matrices, start_channel_id, end_channel_id = get_matrices(dim_idx=-2)
return torch.cat([mat[..., start_channel_id:end_channel_id, :] for mat in matrices], dim=-2)
elif shard_mode == ShardingType.INNER_DIMENSION:
dim_size = param.size(-1) // num_concatenated_matrices
start_channel_id, end_channel_id = get_shard_endpoints(dim_size, shard_rank, num_shards, granularity)
matrices = torch.chunk(param, num_concatenated_matrices, dim=-1)
return torch.cat([mat[..., start_channel_id:end_channel_id] for mat in matrices], dim=-1)