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,13 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
# Imports for registering ops
from .attention import *
from .linear import *
from .post_norm import *
from .pre_norm import *
from .embedding import *
from .unembed import *
from .moe import *
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .dense_blocked_attention import DSDenseBlockedAttention
@@ -0,0 +1,180 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any, Dict, Optional
import torch
from deepspeed.accelerator import get_accelerator
from ....allocator import empty_from
from ....inference_utils import DtypeEnum
from ....kernels.ragged_ops import (
AtomBuilder,
BlockedFlashAttn,
BlockedRotaryEmbeddings,
BlockedTrainedRotaryEmbeddings,
get_q_block_size,
get_kv_block_size,
LinearBlockedKVCopy,
)
from ....ragged import RaggedBatchWrapper, split_kv
from deepspeed.ops.op_builder import RaggedUtilsBuilder
from ...interfaces import DSSelfAttentionBase, DSSelfAttentionRegistry
from ...configs import DSSelfAttentionConfig, PositionalEmbeddingType, MaskingType
try:
from functools import cached_property
except ImportError:
def cached_property(func):
return property(func)
@DSSelfAttentionRegistry.register_module
class DSDenseBlockedAttention(DSSelfAttentionBase):
"""
Self attention implementation for dense, blocked self attention.
"""
@staticmethod
def name() -> str:
return 'dense_blocked_attention'
@staticmethod
def supports_config(config: DSSelfAttentionConfig) -> bool:
if config.input_dtype != config.output_dtype:
return False
if DtypeEnum(config.input_dtype) not in (DtypeEnum.fp16, DtypeEnum.bf16):
return False
if PositionalEmbeddingType(config.positional_embedding_type) not in [
PositionalEmbeddingType.none, PositionalEmbeddingType.rotate_half
]:
return False
if MaskingType(config.masking_type) != MaskingType.causal:
return False
return True
def __init__(self, config: DSSelfAttentionConfig, implementation_config: Dict[str, Any]) -> None:
"""
Create the Attention DSModule.
Args:
config (DSSelfAttentionConfig): The self attention config for all attention DSModules.
implementation_config (Dict[str, Any]):
There are two (dependent) potential components in the implementtion config.
1. `trained_freqs` - If the embedding weights for RoPE are trained, the implementation
config should contain {'trained_freqs': True}. This will mean the implementation will
expect a `trained_freqs` tensor in the `forward` method and will not synthesize the
values internally.
2. `theta_base` - The base value for synthesized frequencies in the rotary embeddings.
This will only be used if `trained_freqs` is False or not present in the `implementation_config`. If this is not included, the default value of 10000.0 will be used.
"""
super().__init__(config, implementation_config)
embed_type = PositionalEmbeddingType(config.positional_embedding_type)
if embed_type == PositionalEmbeddingType.none:
self._kv_copy = LinearBlockedKVCopy(self._config.head_size, self._config.n_heads_q,
self._config.n_heads_kv, self._config.input_dtype)
elif embed_type == PositionalEmbeddingType.rotate_half:
rotary_config = config.positional_embedding_config
assert rotary_config is not None, "Rotary config must be provided if using rotate_half as Positional Embedding Type."
if rotary_config.use_trained_freqs:
# Theta and rotary dim are effectively embedded into either the values (theta) or the shape (rotary_dim)
# of the trained_freqs tensor.
self._kv_copy = BlockedTrainedRotaryEmbeddings(self._config.head_size, self._config.n_heads_q,
self._config.n_heads_kv, self._config.input_dtype)
else:
theta_base = rotary_config.theta_base
rotary_dim = rotary_config.rotate_dim if rotary_config.rotate_dim is not None else self._config.head_size
self._kv_copy = BlockedRotaryEmbeddings(self._config.head_size, self._config.n_heads_q,
self._config.n_heads_kv, self._config.input_dtype, rotary_dim,
theta_base)
self._softmax_scale = self._config.scale_factor
# TODO(cmikeh2): Attention kernel gets created here.
self._attn_kernel = BlockedFlashAttn(self._config.head_size, self._config.input_dtype)
self._atom_builder = AtomBuilder()
self.model_dim = self._config.head_size * self._config.n_heads_q
self._output = torch.empty((self._config.max_tokens, self._config.head_size * self._config.n_heads_q),
dtype=self._config.output_dtype,
device=get_accelerator().current_device())
# TODO(cmikeh2): Pre-allocate storage buffer for the attention atoms.
self._max_atoms = self._config.max_sequences
self._atoms = torch.empty((self._max_atoms, 8), dtype=torch.int32, device=get_accelerator().current_device())
alloc_func = RaggedUtilsBuilder().load().allocate_fast_host_buffer
self._atoms_shadow = alloc_func(self._atoms)
self._cur_atoms = 0
@cached_property
def kv_block_size(self) -> int:
"""
Return preferred granulatity for blocked KV-cache implementation.
"""
return get_kv_block_size(self._config.head_size)
@cached_property
def q_block_size(self) -> int:
"""
Property to calculate blocking granularity for the query dimension.
This has no impact on the KV-cache structure, but will affect the
number of attention atoms associated with a batch.
"""
return get_q_block_size(self._config.head_size)
def build_atoms(self, ragged_batch: RaggedBatchWrapper) -> None:
"""
Build the atoms for the attention kernel.
Args:
ragged_batch (RaggedBatchWrapper): The input ids and associated ragged batch metadata.
"""
host_atoms, n_atoms = self._atom_builder(self._atoms_shadow, ragged_batch, self.q_block_size,
self.kv_block_size)
self._cur_atoms = n_atoms
self._atoms[:n_atoms].copy_(host_atoms[:n_atoms], non_blocking=True)
def forward(self,
q_k_v: torch.Tensor,
kv_cache: torch.Tensor,
batch: RaggedBatchWrapper,
inv_freqs: Optional[torch.Tensor] = None) -> torch.Tensor:
"""
Forward implementation.
Args:
q_k_v (torch.Tensor): Query/Key/Value projection Tensor of shape
[n_heads, (n_heads_q + 2 * n_heads_kv) * head_size].
kv_cache (torch.Tensor): Blocked persistent cache of shape
[2, batch, block_size, n_heads_kv, head_size].
batch (RaggedBatchWrapper): The input ids and associated ragged batch metadata.
inv_freqs (Optional[torch.Tensor]): The inverse frequencies for the rotary embeddings if they
have been modified from synthesizable values.
"""
if inv_freqs is not None:
self._kv_copy(kv_cache, q_k_v, batch, inv_freqs)
else:
self._kv_copy(kv_cache, q_k_v, batch)
q = q_k_v[:, :self._config.head_size * self._config.n_heads_q]
output = empty_from(self._output, q.shape)
k_cache, v_cache = split_kv(kv_cache)
self._attn_kernel(output, q, k_cache, v_cache, self._atoms[:self._cur_atoms], self._softmax_scale)
return output
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .ragged_embedding import DSRaggedEmbedding
@@ -0,0 +1,77 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any, Dict, Optional
import torch
from deepspeed.accelerator import get_accelerator
from ....allocator import empty_from
from ....inference_utils import DtypeEnum
from ....kernels.ragged_ops import RaggedEmbeddingKernel
from ....ragged import RaggedBatchWrapper
from ...interfaces import DSEmbeddingBase, DSEmbeddingRegistry
from ...configs import DSEmbeddingsConfig
@DSEmbeddingRegistry.register_module
class DSRaggedEmbedding(DSEmbeddingBase):
@staticmethod
def name():
return 'ragged_embedding'
@staticmethod
def supports_config(config: DSEmbeddingsConfig) -> bool:
if DtypeEnum(config.residual_dtype) not in [DtypeEnum.fp16, DtypeEnum.bf16, DtypeEnum.fp32]:
return False
if config.use_token_type:
return False
if config.output_normalization is not None:
return False
try:
_ = RaggedEmbeddingKernel(config.residual_dtype, torch.int32, config.embedding_dim)
except ValueError:
return False
return True
def __init__(self, config: DSEmbeddingsConfig, implementation_config: Dict[str, Any]) -> None:
super().__init__(config, implementation_config)
self.embed_offset = self._config.positional_offset
# TODO(cmikeh2): How do we want to avoid the int32 vs int64 issue?
self._ragged_embed = RaggedEmbeddingKernel(self._config.residual_dtype, torch.int32,
self._config.embedding_dim)
self._output = torch.empty((self._config.max_tokens, self._config.embedding_dim),
dtype=self._config.residual_dtype,
device=get_accelerator().current_device())
@property
def output(self) -> torch.Tensor:
return self._output
def forward(self,
ragged_batch: RaggedBatchWrapper,
word_embeddings: torch.Tensor,
position_embeddings: Optional[torch.Tensor] = None) -> torch.Tensor:
"""
Parameters:
ragged_batch (RaggedBatchWrapper): The input ids and associated ragged batch metadata.
word_embeddings (torch.Tensor): The word embedding table
"""
output = empty_from(self._output, (ragged_batch.tensor_toks, self._config.embedding_dim))
self._ragged_embed(output,
ragged_batch,
word_embeddings,
position_embed_weight=position_embeddings,
position_embed_offset=self.embed_offset)
return output
@@ -0,0 +1,7 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .blas_fp_linear import BlasFPLinear
from .quantized_linear import QuantizedWf6Af16Linear, fp_quantize
@@ -0,0 +1,103 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any, Dict, Optional
import torch
from deepspeed.accelerator import get_accelerator
from ....allocator import empty_from
from ....inference_utils import is_gated
from ....kernels.core_ops import (
BlasLibLinear,
CUDABiasActivation,
CUDAGatedActivation,
)
from ...interfaces import DSLinearBase, DSLinearRegistry
from ...configs import DSLinearConfig
from ....inference_parameter import InferenceParameter
@DSLinearRegistry.register_module
class BlasFPLinear(DSLinearBase):
"""
Linear DSModule based on BLAS library and standalone bias + activation kernel implementation.
"""
@staticmethod
def name():
return 'blas_fp_linear'
@staticmethod
def supports_config(config: DSLinearConfig) -> bool:
if config.input_dtype != config.output_dtype:
return False
if config.input_dtype != torch.float16 and config.input_dtype != torch.bfloat16:
return False
if is_gated(config.activation):
try:
_ = CUDAGatedActivation(config.out_channels, config.output_dtype, config.activation)
except ValueError:
return False
else:
try:
_ = CUDABiasActivation(config.out_channels, config.output_dtype, config.activation)
except ValueError:
return False
return True
def __init__(self, config: DSLinearConfig, implementation_config: Dict[str, Any]) -> None:
super().__init__(config, implementation_config)
self._linear_impl = BlasLibLinear(self._config.input_dtype)
if is_gated(config.activation):
self._is_gated = True
self._act_fn = CUDAGatedActivation(config.out_channels, config.output_dtype, config.activation)
self._double_buffer = torch.empty((config.max_tokens, config.out_channels * 2),
dtype=config.output_dtype,
device=get_accelerator().current_device())
else:
self._is_gated = False
self._act_fn = CUDABiasActivation(config.out_channels, config.output_dtype, config.activation)
self._output = torch.empty((config.max_tokens, config.out_channels),
dtype=config.output_dtype,
device=get_accelerator().current_device())
def transform_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Converts param to same data type as input and output.
Parameters:
param (torch.Tensor): Weight or bias tensor.
"""
param = param.to(self._config.output_dtype)
return InferenceParameter.initialize(param)
def forward(self, hidden_states: torch.Tensor, w: torch.Tensor, b: Optional[torch.Tensor] = None) -> torch.Tensor:
output = empty_from(self._output, (hidden_states.shape[0], self._config.out_channels))
if self._is_gated:
staging_output = empty_from(self._double_buffer, (hidden_states.shape[0], self._config.out_channels * 2))
self._linear_impl(staging_output, hidden_states, w)
self._act_fn(output, staging_output, b)
else:
self._linear_impl(output, hidden_states, w)
self._act_fn(output, b)
return output
@property
def output(self) -> torch.Tensor:
"""
Return the padded, pre-allocated output Tensor.
"""
return self._output
@@ -0,0 +1,205 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any, Dict, Optional
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.ops.op_builder import InferenceCoreBuilder
from ....allocator import empty_from
from ....inference_utils import is_gated
from ....kernels.core_ops import (
CUDAWf6Af16Linear,
CUDABiasActivation,
CUDAGatedActivation,
)
from ...interfaces import DSLinearBase, DSLinearRegistry
from ...configs import DSLinearConfig
from ....inference_parameter import InferenceParameter
def fp_quantize(input: torch.FloatTensor,
num_bits: int = 6,
exp_bits: int = 3,
min_value: torch.FloatTensor = None,
max_value: torch.FloatTensor = None,
group_size: int = -1):
"""
Args:
inputs (`torch.FloatTensor`)
The input which needs to be quantized
num_bits (int, >=4)
Number of bits to use for quantization
exp_bits:
fp exp_bits
min_value/max_vlue (torch.FloatTensor)
Used for static activation quantization
group_size (int) N
The quantization block size, each N numbers has its own scaling
factor and off-site. -1 means use the last dim as the group_size
Returns:
quantized_fake_fp6
The quantized weights, in fp16 format and contains fp6 value.
scales
Quantization scales
"""
try:
from qtorch.quant import float_quantize
except ImportError:
raise ImportError("Please install qtorch to use this function")
assert (min_value is None and max_value is None) or (min_value is not None and max_value is not None)
assert input.dtype == torch.float16
orig_device = input.device
input = input.to(torch.float32).to(get_accelerator().current_device())
if num_bits == 6 and exp_bits == 3: # this is default
q_range = 28
else:
raise NotImplementedError
man_bits = num_bits - exp_bits - 1
input_shape = input.shape
if group_size == -1:
group_size = input_shape[-1]
else:
# Only support per-channel quantization
raise NotImplementedError
num_groups = input.numel() // group_size
input = input.reshape(num_groups, -1)
if min_value is None:
max_input = torch.amax(torch.abs(input), dim=-1).view(num_groups, -1)
else:
max_input = torch.max(min_value.abs(), max_value) # .view(-1)
scales = max_input / q_range # q_range + 1
scales[scales == 0] = 1 # avoid zero scales
scaled_input = input / scales
quantized_fake_fp6 = float_quantize(scaled_input, exp_bits, man_bits, rounding="nearest")
quantized_fake_fp6 = quantized_fake_fp6.reshape(input_shape).contiguous().to(torch.float16).to(orig_device)
scales = scales.to(torch.float16).to(orig_device)
# Now the dequantized value is quantized_fake_fp6 * scales
return quantized_fake_fp6, scales
@DSLinearRegistry.register_module
class QuantizedWf6Af16Linear(DSLinearBase):
"""
Linear DSModule for FP6 weight-only quantization kernel, where weight is FP6
and activation is FP16.
"""
@staticmethod
def name():
return 'quantized_wf6af16_linear'
@staticmethod
def supports_config(config: DSLinearConfig) -> bool:
if config.input_dtype != config.output_dtype:
return False
# As for fp6 data items, they are packed and stored in a set of fp16
# tensors. E.g., 8 fp6 data items are stored in 3 fp16 tensor.
if config.input_dtype != torch.float16:
return False
if is_gated(config.activation):
try:
_ = CUDAGatedActivation(config.out_channels, config.output_dtype, config.activation)
except ValueError:
return False
else:
try:
_ = CUDABiasActivation(config.out_channels, config.output_dtype, config.activation)
except ValueError:
return False
return True
def __init__(self, config: DSLinearConfig, implementation_config: Dict[str, Any]) -> None:
super().__init__(config, implementation_config)
self._linear_impl = CUDAWf6Af16Linear()
if is_gated(config.activation):
# In the FP6 kernel implementation, the MatMul is W * A, where W is
# the weight and A is activation. M is the output channel size.
self.out_channels = self._config.out_channels * 2
self.in_channels = self._config.in_channels
self._is_gated = True
self._act_fn = CUDAGatedActivation(config.out_channels, config.output_dtype, config.activation)
self._double_buffer = torch.empty((config.max_tokens, config.out_channels * 2),
dtype=config.output_dtype,
device=get_accelerator().current_device())
else:
self.out_channels = self._config.out_channels
self.in_channels = self._config.in_channels
self._is_gated = False
self._act_fn = CUDABiasActivation(config.out_channels, config.output_dtype, config.activation)
self._output = torch.empty((config.max_tokens, config.out_channels),
dtype=config.output_dtype,
device=get_accelerator().current_device())
self.inf_module = InferenceCoreBuilder().load()
self.inf_module.create_handle()
self.preprocess_weight = self.inf_module.preprocess_weight
self.quantizer = fp_quantize
def transform_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Converts param to same data type as input and output.
Parameters:
param (torch.Tensor): Weight or bias tensor.
"""
# It expects that the quantization scales are store in the attribute `scales`.
if param.ndim == 1: # bias, do nothing
return InferenceParameter.initialize(param)
quantized_fake_fp6, scales = self.quantizer(param, num_bits=6, exp_bits=3)
# This is for debugging, will delete before release.
assert (quantized_fake_fp6.dtype == torch.float16)
assert quantized_fake_fp6.shape[0] == self.out_channels
assert scales.numel() == self.out_channels
weights_2bit, weights_4bit = self.preprocess_weight(quantized_fake_fp6)
return InferenceParameter.initialize(weights_2bit, weights_4bit=weights_4bit, scales=scales)
def forward(self, hidden_states: torch.Tensor, w: torch.Tensor, b: Optional[torch.Tensor] = None) -> torch.Tensor:
weights_2bit = w
weights_4bit = w.weights_4bit
scales = w.scales
output = empty_from(self._output, (hidden_states.shape[0], self._config.out_channels))
if self._is_gated:
staging_output = empty_from(self._double_buffer, (hidden_states.shape[0], self.out_channels))
self._linear_impl(staging_output, hidden_states, weights_2bit, weights_4bit, scales, self.out_channels,
hidden_states.shape[0], self.in_channels)
self._act_fn(output, staging_output, b)
else:
self._linear_impl(output, hidden_states, weights_2bit, weights_4bit, scales, self.out_channels,
hidden_states.shape[0], self.in_channels)
self._act_fn(output, b)
return output
@property
def output(self) -> torch.Tensor:
"""
Return the padded, pre-allocated output Tensor.
"""
return self._output
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .cutlass_multi_gemm import DSMultiGemmMoE
@@ -0,0 +1,249 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any, Dict, Optional, Tuple
import torch
from deepspeed.accelerator import get_accelerator
from ....allocator import empty_from
from ....inference_utils import ActivationType, is_gated
from ....kernels.core_ops import BlasLibLinear, CUDAGatedActivation
from ....kernels.ragged_ops import (
MoEGather,
MoEScatter,
RaggedTopKGating,
)
from ....ragged import RaggedBatchWrapper
from ...interfaces import DSMoEBase, DSMoERegistry
from ...configs import DSMoEConfig
from ....kernels.cutlass_ops import MoEGEMM
from ....inference_parameter import InferenceParameter
@DSMoERegistry.register_module
class DSMultiGemmMoE(DSMoEBase):
"""
MoE implementation based on the CUTLASS multi-GEMM.
"""
@staticmethod
def name():
return 'cutlass_multi_gemm_moe'
@staticmethod
def supports_config(config: DSMoEConfig) -> bool:
if config.input_dtype != config.output_dtype:
return False
if config.input_dtype != torch.float16 and config.input_dtype != torch.bfloat16:
return False
if config.top_k != 1 and config.top_k != 2 and config.top_k != 4 and config.top_k != 8:
return False
return True
def __init__(self, config: DSMoEConfig, implementation_config: Dict[str, Any]) -> None:
super().__init__(config, implementation_config)
# Convenience variables for frequently accessed items.
self.max_tokens = self._config.max_tokens
self.n_experts = self._config.n_experts
self.n_top_k = self._config.top_k
self.intermediate_dim = self._config.intermediate_features
moe_op_act_fn = ActivationType.IDENTITY if is_gated(self._config.activation) else self._config.activation
self._mlp_1 = MoEGEMM(fp_dtype=implementation_config['weight_dtype'], act_fn=moe_op_act_fn)
self._mlp_2 = MoEGEMM(fp_dtype=implementation_config['weight_dtype'], act_fn=ActivationType.IDENTITY)
if is_gated(self._config.activation):
self._activation = CUDAGatedActivation(self._config.model_dim, self._config.input_dtype,
self._config.activation)
else:
self._activation = None
self._gate_proj = BlasLibLinear(self._config.input_dtype)
self._top_1_gate = RaggedTopKGating(config.input_dtype)
self._moe_scatter = MoEScatter(config.input_dtype, config.model_dim)
self._moe_gather = MoEGather(config.input_dtype, config.model_dim, config.normalize_scores)
self._create_buffers()
def _create_buffers(self):
# Gating buffers
self._logits = torch.empty((self._config.max_tokens, self.n_experts),
dtype=self._config.input_dtype,
device=get_accelerator().current_device())
self._expert_counts = torch.empty((self.n_experts, ),
dtype=torch.int32,
device=get_accelerator().current_device())
self._scores = torch.empty((self._config.max_tokens, self.n_top_k),
dtype=torch.float32,
device=get_accelerator().current_device())
self._assignments = torch.empty((self._config.max_tokens, self.n_top_k),
dtype=torch.int32,
device=get_accelerator().current_device())
self._offsets = torch.empty((self._config.max_tokens, self.n_top_k),
dtype=torch.int32,
device=get_accelerator().current_device())
# Scatter buffers
self._moe_input = torch.empty((self._config.max_tokens * self.n_top_k, self._config.model_dim),
dtype=self._config.input_dtype,
device=get_accelerator().current_device())
self._expert_cumsum = torch.empty((self._config.n_experts, ),
dtype=torch.int64,
device=get_accelerator().current_device())
self._mapped_slots = torch.empty((self._config.max_tokens, self.n_top_k),
dtype=torch.int32,
device=get_accelerator().current_device())
# GEMM Buffers
self._intermediate = torch.empty((self._config.max_tokens * self.n_top_k, self._config.intermediate_features),
dtype=self._config.output_dtype,
device=get_accelerator().current_device())
if self._activation is not None:
self._gated_intermediate = torch.empty(
(self._config.max_tokens * self.n_top_k, self._config.intermediate_features * 2),
dtype=self._config.output_dtype,
device=get_accelerator().current_device())
self._output_unordered = torch.empty((self._config.max_tokens * self.n_top_k, self._config.model_dim),
dtype=self._config.output_dtype,
device=get_accelerator().current_device())
# Gather buffer
self._output = torch.empty((self._config.max_tokens, self._config.model_dim),
dtype=self._config.output_dtype,
device=get_accelerator().current_device())
def transform_gate_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Ensures gate param is going to match the activation data type.
"""
param = param.to(self._config.input_dtype)
return InferenceParameter.initialize(param)
def transform_moe_mlp_1_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Converts param to same data type as input and output.
Parameters:
param (torch.Tensor): Weight or bias tensor.
"""
param = param.to(self._config.input_dtype)
if len(param.shape) == 3:
param = param.permute(0, 2, 1).contiguous()
return InferenceParameter.initialize(param)
def transform_moe_mlp_2_param(self, param: torch.Tensor) -> InferenceParameter:
"""
Converts param to same data type as input and output.
Parameters:
param (torch.Tensor): Weight or bias tensor.
"""
param = param.to(self._config.input_dtype)
if len(param.shape) == 3:
param = param.permute(0, 2, 1).contiguous()
return InferenceParameter.initialize(param)
@property
def output(self) -> torch.Tensor:
return self._output
def _gate(self, hidden_states: torch.Tensor, batch_metadata: RaggedBatchWrapper,
gate_w: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Helper function to isolate the logit for gating. This will take the hidden states
and produce the metadata + tensors for the CUTLASS ragged GEMMs. If the input has
been padded for CG, this will strip the padding for MoE.
Parameters:
hidden_states (torch.Tensor): Hidden states tensor. Expected shape is [n_tokens, model_dim].
batch_metadata (RaggedBatchWrapper): Batch metadata for the hidden states.
gate_w (torch.Tensor): Gate weight tensor. Expected shape is [num_experts, model_dim].
Returns:
Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: The MoE input, the cumsum of the offsets (for the MoE kernels themselves), the scores, and the mapped slots (to recover the original order of the tokens)
"""
# Get views on the buffers for gating
logits = empty_from(self._logits, (hidden_states.shape[0], self._logits.shape[-1]))
scores = empty_from(self._scores, (hidden_states.shape[0], self.n_top_k))
assignments = empty_from(self._assignments, (hidden_states.shape[0], self.n_top_k))
offsets = empty_from(self._offsets, (hidden_states.shape[0], self.n_top_k))
mapped_slots = empty_from(self._mapped_slots, (hidden_states.shape[0], self.n_top_k))
moe_input = empty_from(self._moe_input, (hidden_states.shape[0] * self.n_top_k, self._moe_input.shape[-1]))
self._gate_proj(logits, hidden_states, gate_w)
self._expert_counts.zero_()
self._top_1_gate(self._expert_counts, scores, assignments, offsets, logits, batch_metadata)
self._moe_scatter(moe_input, self._expert_cumsum, mapped_slots, hidden_states, self._expert_counts,
assignments, offsets)
return moe_input, self._expert_cumsum, scores, mapped_slots
def forward(self,
hidden_states: torch.Tensor,
batch_metadata: RaggedBatchWrapper,
gate_w: torch.Tensor,
mlp_1_w: torch.Tensor,
mlp_2_w: torch.Tensor,
mlp_1_b: Optional[torch.Tensor] = None,
mlp_2_b: Optional[torch.Tensor] = None) -> torch.Tensor:
"""
MoE forward pass built on top of CUTLASS multi-GEMM.
Parameters:
hidden_states (torch.Tensor): Hidden states tensor. Expected shape is [batch, seq_len, model_dim].
gate_w (torch.Tensor): Gate weight tensor. Expected shape is [num_experts, model_dim].
"""
moe_input, expert_cumsum, scores, mapped_slots = self._gate(hidden_states, batch_metadata, gate_w)
# Get views on the buffers for GEMM
intermediate = empty_from(self._intermediate,
(hidden_states.shape[0] * self.n_top_k, self._intermediate.shape[-1]))
output_unordered = empty_from(self._output_unordered,
(hidden_states.shape[0] * self.n_top_k, self._output_unordered.shape[-1]))
output = empty_from(self._output, (hidden_states.shape[0], self._output.shape[-1]))
if self._activation is not None:
gated_intermediate = empty_from(
self._gated_intermediate, (hidden_states.shape[0] * self.n_top_k, self._gated_intermediate.shape[-1]))
self._mlp_1(
gated_intermediate,
moe_input,
mlp_1_w,
expert_cumsum,
mlp_1_b,
)
self._activation(intermediate, gated_intermediate)
else:
self._mlp_1(
intermediate,
moe_input,
mlp_1_w,
expert_cumsum,
mlp_1_b,
)
self._mlp_2(
output_unordered,
intermediate,
mlp_2_w,
expert_cumsum,
mlp_2_b,
)
self._moe_gather(output, output_unordered, scores, mapped_slots, self._expert_counts)
return output
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .cuda_post_ln import DSPostLNCUDAModule
@@ -0,0 +1,56 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any, Dict, Tuple
import torch
from deepspeed.accelerator import get_accelerator
from ...interfaces import DSPostNormBase, DSPostNormRegistry
from ...configs import DSNormConfig
from ....kernels.core_ops.cuda_layer_norm.cuda_post_ln import CUDAFPPostLN
from ....allocator import empty_from
from ....inference_parameter import InferenceParameter
@DSPostNormRegistry.register_module
class DSPostLNCUDAModule(DSPostNormBase):
@staticmethod
def name():
return 'cuda_post_ln'
@staticmethod
def supports_config(config: DSNormConfig):
if len(set([config.residual_dtype, config.input_dtype, config.output_dtype])) != 1:
return False
try:
_ = CUDAFPPostLN(config.channels, config.residual_dtype)
except ValueError:
return False
return True
def __init__(self, config: DSNormConfig, implementation_config: Dict[str, Any]):
super().__init__(config, implementation_config)
self._fp_post_ln = CUDAFPPostLN(self._config.channels, self._config.residual_dtype, epsilon=self._config.eps)
self._output = torch.empty((config.max_tokens, config.channels),
dtype=config.output_dtype,
device=get_accelerator().current_device())
def transform_param(self, param: torch.Tensor) -> InferenceParameter:
param = param.to(self._config.input_dtype)
return InferenceParameter.initialize(param)
def forward(self, residual: torch.Tensor, hidden_in: torch.Tensor, gamma: torch.Tensor,
beta: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Since the CUDA FP only supports all data types being the same, we will alias the residual
with our output.
"""
self._residual_output = empty_from(self._output, residual.shape)
self._fp_post_ln(residual, residual, hidden_in, gamma, beta)
return residual, residual
@@ -0,0 +1,7 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .cuda_pre_ln import DSPreLNCUDAModule
from .cuda_pre_rms import DSPreRMSCUDAModule
@@ -0,0 +1,69 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any, Dict, Optional, Tuple
import torch
from deepspeed.accelerator import get_accelerator
from ...interfaces import DSPreNormBase, DSPreNormRegistry
from ...configs import DSNormConfig, NormTypeEnum
from ....kernels.core_ops.cuda_layer_norm.cuda_pre_ln import CUDAFPPreLN
from ....kernels.core_ops.cuda_layer_norm.cuda_ln import CUDAFPLN
from ....allocator import empty_from
from ....inference_parameter import InferenceParameter
@DSPreNormRegistry.register_module
class DSPreLNCUDAModule(DSPreNormBase):
@staticmethod
def name():
return 'cuda_pre_ln'
@staticmethod
def supports_config(config: DSNormConfig):
type = NormTypeEnum(config.type)
if type != NormTypeEnum.LayerNorm:
return False
if len(set([config.residual_dtype, config.input_dtype, config.output_dtype])) != 1:
return False
try:
_ = CUDAFPPreLN(config.channels, config.residual_dtype)
except ValueError:
return False
return True
def __init__(self, config: DSNormConfig, implementation_config: Dict[str, Any]):
super().__init__(config, implementation_config)
self._fp_pre_ln = CUDAFPPreLN(self._config.channels, self._config.residual_dtype, epsilon=self._config.eps)
self._fp_ln = CUDAFPLN(self._config.channels, self._config.residual_dtype, epsilon=self._config.eps)
# Buffers for the hidden output (residual is updated in-place)
self._hidden_output = torch.empty((config.max_tokens, config.channels),
dtype=config.output_dtype,
device=get_accelerator().current_device())
def transform_param(self, param: torch.Tensor) -> InferenceParameter:
param = param.to(self._config.input_dtype)
return InferenceParameter.initialize(param)
def forward(self, residual: torch.Tensor, hidden_in: Optional[torch.Tensor], gamma: torch.Tensor,
beta: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Since the CUDA FP only supports all data types being the same, we will alias the residual
with our output.
If hidden_in is None, that means we do not need to perform the residual add and will
only return the hidden output modified.
"""
hidden_out = empty_from(self._hidden_output, residual.shape)
if hidden_in is None:
self._fp_ln(hidden_out, residual, gamma, beta)
else:
self._fp_pre_ln(residual, hidden_out, residual, hidden_in, gamma, beta)
return residual, hidden_out
@@ -0,0 +1,79 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any, Dict, Optional, Tuple
import torch
from deepspeed.accelerator import get_accelerator
from ...interfaces import DSPreNormBase, DSPreNormRegistry
from ...configs import DSNormConfig, NormTypeEnum
from ....kernels.core_ops import CUDARMSNorm, CUDARMSPreNorm
from ....allocator import empty_from
from ....inference_parameter import InferenceParameter
@DSPreNormRegistry.register_module
class DSPreRMSCUDAModule(DSPreNormBase):
@staticmethod
def name():
return 'cuda_pre_rms'
@staticmethod
def supports_config(config: DSNormConfig):
type = NormTypeEnum(config.type)
if type != NormTypeEnum.RMSNorm:
return False
if len(set([config.residual_dtype, config.input_dtype, config.output_dtype])) != 1:
return False
try:
# Only need to check one since the support matrix for the two rms kernels is the same
_ = CUDARMSPreNorm(config.channels, config.residual_dtype)
except ValueError:
return False
return True
def __init__(self, config: DSNormConfig, implementation_config: Dict[str, Any]):
super().__init__(config, implementation_config)
self._fp_rms = CUDARMSNorm(self._config.channels, self._config.residual_dtype, epsilon=self._config.eps)
self._fp_rms_pre = CUDARMSPreNorm(self._config.channels, self._config.residual_dtype, epsilon=self._config.eps)
# Buffers for both the hidden and residual outputs
self._hidden_output = torch.empty((config.max_tokens, config.channels),
dtype=config.output_dtype,
device=get_accelerator().current_device())
self._residual_output = torch.empty((config.max_tokens, config.channels),
dtype=config.output_dtype,
device=get_accelerator().current_device())
def transform_param(self, param: torch.Tensor) -> InferenceParameter:
param = param.to(self._config.input_dtype)
return InferenceParameter.initialize(param)
def forward(self,
residual: torch.Tensor,
hidden_in: Optional[torch.Tensor],
gamma: torch.Tensor,
beta: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Since the CUDA FP only supports all data types being the same, we will alias the residual
with our output.
If hidden_in is None, that means we do not need to perform the residual add and will
only return the hidden output modified.
"""
assert beta is None, "Beta is not supported for RMSNorm"
hidden_out = empty_from(self._hidden_output, residual.shape)
if hidden_in is None:
self._fp_rms(hidden_out, residual, gamma)
residual_out = residual
else:
residual_out = empty_from(self._residual_output, residual.shape)
self._fp_rms_pre(residual_out, hidden_out, residual, hidden_in, gamma)
return residual_out, hidden_out
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from .ragged_unembed import DSRaggedUnembed
@@ -0,0 +1,120 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Any, Dict, Optional
import torch
from deepspeed.accelerator import get_accelerator
from ....allocator import empty_from
from ....inference_utils import DtypeEnum, ActivationType
from ....kernels.core_ops import CUDAFPLN, BlasLibLinear, CUDARMSNorm, CUDABiasActivation
from ....kernels.ragged_ops import RaggedLogitsGather
from ....ragged import RaggedBatchWrapper
from ...interfaces import DSUnembedBase, DSUnembedRegistry
from ...configs import DSUnembedConfig
@DSUnembedRegistry.register_module
class DSRaggedUnembed(DSUnembedBase):
"""
Ragged unembedding implementation. This implementation will gather only the last token
of each sequence in the ragged inflight batch and calculate the logits only for those rows.
"""
@staticmethod
def name():
return 'ragged_unembed'
@staticmethod
def supports_config(config: DSUnembedConfig):
if DtypeEnum(config.dtype) not in [DtypeEnum.fp16, DtypeEnum.bf16, DtypeEnum.fp32]:
return False
try:
_ = RaggedLogitsGather(config.model_dim, config.dtype)
except ValueError:
return False
if config.norm_type == 'rms_norm':
try:
_ = CUDARMSNorm(config.model_dim, config.dtype)
except ValueError:
return False
elif config.norm_type == 'layer_norm':
try:
_ = CUDAFPLN(config.model_dim, config.dtype)
except ValueError:
return False
return True
def __init__(self, config: DSUnembedConfig, implementation_config: Dict[str, Any]) -> None:
super().__init__(config, implementation_config)
self._logits_gather = RaggedLogitsGather(config.model_dim, self._config.dtype)
if self._config.norm_type == 'layer_norm':
self._norm = CUDAFPLN(self._config.model_dim, self._config.dtype)
elif self._config.norm_type == 'rms_norm':
self._norm = CUDARMSNorm(self._config.model_dim, self._config.dtype)
else:
self._norm = None
self._linear = BlasLibLinear(self._config.dtype)
# Here the activation kernel is being used to apply bias, hence the identity activation type!
self._act_fn = CUDABiasActivation(self._config.vocab_size, self._config.dtype, ActivationType.IDENTITY)
self._intermediate = torch.empty((self._config.max_sequences, self._config.model_dim),
dtype=self._config.dtype,
device=get_accelerator().current_device())
self._output = torch.empty((self._config.max_sequences, self._config.vocab_size),
dtype=self._config.dtype,
device=get_accelerator().current_device())
@property
def output(self) -> torch.Tensor:
return self._output
def forward(self,
hidden_states: torch.Tensor,
vocab_embedding: torch.Tensor,
ragged_metadata: RaggedBatchWrapper,
bias: Optional[torch.Tensor] = None,
gamma: Optional[torch.Tensor] = None,
beta: Optional[torch.Tensor] = None) -> torch.Tensor:
"""
Return final model logits.
Args:
hidden_states (torch.Tensor): The hidden states from the model. This is the output of the
final layer of the model.
vocab_embedding (torch.Tensor): The vocab embedding table.
raged_metadata (RaggedBatchWrapper): The ragged batch metadata.
gamma (Optional[torch.Tensor]): The gamma tensor for normalization.
beta (Optional[torch.Tensor]): The beta tensor for normalization.
"""
cut_down_hidden_states = empty_from(self._intermediate,
(ragged_metadata.current_sequences, self._config.model_dim))
self._logits_gather(cut_down_hidden_states, hidden_states, ragged_metadata)
if self._config.norm_type == 'rms_norm':
if gamma is None:
raise ValueError('RMS Normalization enabled but gamma not provided.')
self._norm(cut_down_hidden_states, cut_down_hidden_states, gamma)
elif self._config.norm_type == 'layer_norm':
if gamma is None or beta is None:
raise ValueError('Normalization enabled but gamma and/or beta not provided.')
self._norm(cut_down_hidden_states, cut_down_hidden_states, gamma, beta)
output = empty_from(self._output, (ragged_metadata.current_sequences, self._config.vocab_size))
self._linear(output, cut_down_hidden_states, vocab_embedding)
if bias is not None:
self._act_fn(output, bias)
return output