chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
from .v2 import RaggedInferenceEngineConfig, DeepSpeedTPConfig
|
||||
from .v2.engine_v2 import InferenceEngineV2
|
||||
from .v2 import build_hf_engine, build_engine_from_ds_checkpoint
|
||||
@@ -0,0 +1,323 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
import deepspeed
|
||||
from pydantic import Field, field_validator
|
||||
from deepspeed.runtime.config_utils import DeepSpeedConfigModel
|
||||
from deepspeed.runtime.zero.config import DeepSpeedZeroConfig
|
||||
from typing import Dict, Union, Optional
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class DtypeEnum(Enum):
|
||||
fp16 = (torch.float16, "torch.float16", "fp16", "float16", "half")
|
||||
fp32 = (torch.float32, "torch.float32", "fp32", "float32", "float")
|
||||
bf16 = (torch.bfloat16, "torch.bfloat16", "bf16", "bfloat16", "bfloat")
|
||||
int8 = (torch.int8, "torch.int8", "int8")
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, value: str):
|
||||
for dtype in cls:
|
||||
if value in dtype.value:
|
||||
return dtype
|
||||
raise ValueError(f"'{value}' is not a valid DtypeEnum")
|
||||
|
||||
|
||||
class MoETypeEnum(str, Enum):
|
||||
residual = "residual"
|
||||
standard = "standard"
|
||||
|
||||
|
||||
class DeepSpeedTPConfig(DeepSpeedConfigModel):
|
||||
""" Configure tensor parallelism settings """
|
||||
|
||||
enabled: bool = True
|
||||
""" Turn tensor parallelism on/off. """
|
||||
|
||||
tp_size: int = 1
|
||||
""" Number of devices to split the model across using tensor parallelism. """
|
||||
|
||||
tp_grain_size: int = 64
|
||||
"Desired MLP/lm_head tp size granularity. DNN library favors tensor size in granularity of power of 2, we pick 64 as a default size."
|
||||
|
||||
mpu: object = None
|
||||
"""
|
||||
A model parallelism unit object that implements
|
||||
``get_{model,data}_parallel_{rank,group,world_size}()``.
|
||||
"""
|
||||
|
||||
tp_group: object = None
|
||||
|
||||
|
||||
class DeepSpeedMoEConfig(DeepSpeedConfigModel):
|
||||
""" Sets parameters for MoE """
|
||||
|
||||
enabled: bool = True
|
||||
ep_size: int = 1
|
||||
"""
|
||||
The expert-parallelism size which is used for partitioning the experts
|
||||
across the GPUs in the expert-parallel group.
|
||||
"""
|
||||
|
||||
moe_experts: list = Field([1], alias="num_experts")
|
||||
""" The global number of experts used in an MoE layer. """
|
||||
|
||||
type: MoETypeEnum = MoETypeEnum.standard
|
||||
"""
|
||||
Specify the type of MoE layer. We have two types of MoE layer: 'Standard'
|
||||
and 'Residual'.
|
||||
"""
|
||||
|
||||
ep_mp_group: object = None
|
||||
ep_group: object = Field(None, alias="expert_group")
|
||||
|
||||
|
||||
class QuantTypeEnum(str, Enum):
|
||||
asym = "asymmetric"
|
||||
sym = "symmetric"
|
||||
|
||||
|
||||
class BaseQuantConfig(DeepSpeedConfigModel):
|
||||
enabled: bool = True
|
||||
num_bits: int = 8
|
||||
q_type: QuantTypeEnum = QuantTypeEnum.sym
|
||||
q_groups: int = 1
|
||||
|
||||
|
||||
class WeightQuantConfig(BaseQuantConfig):
|
||||
enabled: bool = True
|
||||
quantized_initialization: Dict = {}
|
||||
post_init_quant: Dict = {}
|
||||
|
||||
|
||||
class ActivationQuantConfig(BaseQuantConfig):
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class QKVQuantConfig(DeepSpeedConfigModel):
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class QuantizationConfig(DeepSpeedConfigModel):
|
||||
enabled: bool = True
|
||||
activation: ActivationQuantConfig = ActivationQuantConfig()
|
||||
weight: WeightQuantConfig = WeightQuantConfig()
|
||||
qkv: QKVQuantConfig = QKVQuantConfig()
|
||||
|
||||
|
||||
# todo: brainstorm on how to do ckpt loading for DS inference
|
||||
class InferenceCheckpointConfig(DeepSpeedConfigModel):
|
||||
checkpoint_dir: Optional[str] = None
|
||||
save_mp_checkpoint_path: Optional[str] = None
|
||||
base_dir: Optional[str] = None
|
||||
|
||||
|
||||
class DeepSpeedInferenceConfig(DeepSpeedConfigModel):
|
||||
""" Sets parameters for DeepSpeed Inference Engine. """
|
||||
|
||||
replace_with_kernel_inject: bool = Field(False, alias="kernel_inject")
|
||||
"""
|
||||
Set to true to inject inference kernels for models such as, Bert, GPT2,
|
||||
GPT-Neo and GPT-J. Otherwise, the injection_dict provides the names of two
|
||||
linear layers as a tuple:
|
||||
`(attention_output projection, transformer output projection)`
|
||||
"""
|
||||
|
||||
dtype: torch.dtype = torch.float16
|
||||
"""
|
||||
Desired model data type, will convert model to this type.
|
||||
Supported target types: `torch.half`, `torch.int8`, `torch.float`
|
||||
"""
|
||||
|
||||
tensor_parallel: DeepSpeedTPConfig = Field({}, alias="tp")
|
||||
"""
|
||||
Configuration for tensor parallelism used to split the model across several
|
||||
GPUs. Expects a dictionary containing values for :any:`DeepSpeedTPConfig`.
|
||||
"""
|
||||
|
||||
enable_cuda_graph: bool = False
|
||||
"""
|
||||
Use this flag for capturing the CUDA-Graph of the inference ops, so that it
|
||||
can run faster using the graph replay method.
|
||||
"""
|
||||
|
||||
use_triton: bool = False
|
||||
"""
|
||||
Use this flag to use triton kernels for inference ops.
|
||||
"""
|
||||
|
||||
triton_autotune: bool = False
|
||||
"""
|
||||
Use this flag to enable triton autotuning.
|
||||
Turning it on is better for performance but increase the 1st runtime for
|
||||
autotuning.
|
||||
"""
|
||||
|
||||
zero: DeepSpeedZeroConfig = {}
|
||||
"""
|
||||
ZeRO configuration to use with the Inference Engine. Expects a dictionary
|
||||
containing values for :any:`DeepSpeedZeroConfig`.
|
||||
"""
|
||||
|
||||
triangular_masking: bool = Field(True, alias="tm")
|
||||
"""
|
||||
Controls the type of masking for attention scores in transformer layer.
|
||||
Note that the masking is application specific.
|
||||
"""
|
||||
|
||||
moe: Union[bool, DeepSpeedMoEConfig] = {}
|
||||
"""
|
||||
Specify if the type of Transformer is MoE. Expects a dictionary containing
|
||||
values for :any:`DeepSpeedMoEConfig`.
|
||||
"""
|
||||
|
||||
keep_module_on_host: bool = False
|
||||
"""
|
||||
When loading checkpoints to model parameters, they are moved to the device. In very large models
|
||||
this might fill the device and cause OOM. Setting this flag to true, will keep checkpoints on
|
||||
host and not move them directly to the device (giving an option to quantize checkpoint data before
|
||||
moving it to the device for example).
|
||||
Set only for models with injection policies and auto TP.
|
||||
"""
|
||||
|
||||
quant: QuantizationConfig = {}
|
||||
"""
|
||||
NOTE: only works for int8 dtype.
|
||||
Quantization settings used for quantizing your model using the MoQ. The
|
||||
setting can be one element or a tuple. If one value is passed in, we
|
||||
consider it as the number of groups used in quantization. A tuple is passed
|
||||
in if we want to mention that there is extra-grouping for the MLP part of a
|
||||
Transformer layer (e.g. (True, 8) shows we quantize the model using 8
|
||||
groups for all the network except the MLP part that we use 8 extra
|
||||
grouping). Expects a dictionary containing values for
|
||||
:any:`QuantizationConfig`.
|
||||
"""
|
||||
|
||||
#todo: refactor the following 3 into the new checkpoint_config
|
||||
checkpoint: Optional[Union[str, Dict]] = None
|
||||
"""
|
||||
Path to deepspeed compatible checkpoint or path to JSON with load policy.
|
||||
"""
|
||||
|
||||
base_dir: str = ""
|
||||
"""
|
||||
This shows the root directory under which all the checkpoint files exists.
|
||||
This can be passed through the json config too.
|
||||
"""
|
||||
|
||||
set_empty_params: bool = False
|
||||
"""
|
||||
specifying whether the inference-module is created with empty or real Tensor
|
||||
"""
|
||||
|
||||
save_mp_checkpoint_path: Optional[str] = None
|
||||
"""
|
||||
The path for which we want to save the loaded model with a checkpoint. This
|
||||
feature is used for adjusting the parallelism degree to help alleviate the
|
||||
model loading overhead. It does not save any new checkpoint if no path is
|
||||
passed.
|
||||
"""
|
||||
|
||||
checkpoint_config: InferenceCheckpointConfig = Field({}, alias="ckpt_config")
|
||||
"""
|
||||
TODO: Add docs. Expects a dictionary containing values for
|
||||
:any:`InferenceCheckpointConfig`.
|
||||
"""
|
||||
|
||||
return_tuple: bool = True
|
||||
"""
|
||||
Specify whether or not the transformer layers need to return a tuple or a
|
||||
Tensor.
|
||||
"""
|
||||
|
||||
training_mp_size: int = 1
|
||||
"""
|
||||
If loading a checkpoint this is the mp size that it was trained with, it
|
||||
may be different than what the mp size that you want to use during
|
||||
inference.
|
||||
"""
|
||||
|
||||
replace_method: str = Field(
|
||||
"auto",
|
||||
json_schema_extra={
|
||||
"deprecated": True,
|
||||
"deprecated_msg": "This parameter is no longer needed, please remove from your call to DeepSpeed-inference"
|
||||
})
|
||||
|
||||
injection_policy: Optional[Dict] = Field(None, alias="injection_dict")
|
||||
"""
|
||||
Dictionary mapping a client nn.Module to its corresponding injection
|
||||
policy. e.g., `{BertLayer : deepspeed.inference.HFBertLayerPolicy}`
|
||||
"""
|
||||
|
||||
injection_policy_tuple: Optional[tuple] = None
|
||||
""" TODO: Add docs """
|
||||
|
||||
config: Optional[Dict] = Field(None, alias="args") # todo: really no need for this field if we can refactor
|
||||
|
||||
max_out_tokens: int = Field(1024, alias="max_tokens")
|
||||
"""
|
||||
This argument shows the maximum number of tokens inference-engine can work
|
||||
with, including the input and output tokens. Please consider increasing it
|
||||
to the required token-length required for your use-case.
|
||||
"""
|
||||
|
||||
min_out_tokens: int = Field(1, alias="min_tokens")
|
||||
"""
|
||||
This argument communicates to the runtime the minimum number of tokens you
|
||||
expect you will need to generate. This will cause the runtime to error
|
||||
if it unable to provide this and provide context on the memory pressure
|
||||
rather than seg-faulting or providing corrupted output.
|
||||
"""
|
||||
|
||||
transposed_mode: bool = Field(False, alias="transposed_mode")
|
||||
|
||||
mp_size: int = Field(1, json_schema_extra={"deprecated": True, "new_param": "tensor_parallel.tp_size"})
|
||||
"""
|
||||
Desired model parallel size, default is 1 meaning no model parallelism.
|
||||
Deprecated, please use the ``tensor_parallel` config to control model
|
||||
parallelism.
|
||||
"""
|
||||
mpu: object = Field(None, json_schema_extra={"deprecated": True, "new_param": "tensor_parallel.mpu"})
|
||||
ep_size: int = Field(1, json_schema_extra={"deprecated": True, "new_param": "moe.ep_size"})
|
||||
ep_group: object = Field(None,
|
||||
alias="expert_group",
|
||||
json_schema_extra={
|
||||
"deprecated": True,
|
||||
"new_param": "moe.ep_group"
|
||||
})
|
||||
ep_mp_group: object = Field(None,
|
||||
alias="expert_mp_group",
|
||||
json_schema_extra={
|
||||
"deprecated": True,
|
||||
"new_param": "moe.ep_mp_group"
|
||||
})
|
||||
moe_experts: list = Field([1], json_schema_extra={"deprecated": True, "new_param": "moe.moe_experts"})
|
||||
moe_type: MoETypeEnum = Field(MoETypeEnum.standard,
|
||||
json_schema_extra={
|
||||
"deprecated": True,
|
||||
"new_param": "moe.type"
|
||||
})
|
||||
|
||||
@field_validator("dtype", mode="before")
|
||||
def validate_dtype(cls, field_value, values):
|
||||
if isinstance(field_value, str):
|
||||
return DtypeEnum.from_str(field_value).value[0]
|
||||
if isinstance(field_value, torch.dtype):
|
||||
return field_value
|
||||
raise TypeError(f"Invalid type for dtype: {type(field_value)}")
|
||||
|
||||
@field_validator("moe")
|
||||
def moe_backward_compat(cls, field_value, values):
|
||||
if isinstance(field_value, bool):
|
||||
return DeepSpeedMoEConfig(moe=field_value)
|
||||
return field_value
|
||||
|
||||
@field_validator("use_triton")
|
||||
def has_triton(cls, field_value, values):
|
||||
if field_value and not deepspeed.HAS_TRITON:
|
||||
raise ValueError('Triton needs to be installed to use deepspeed with triton kernels')
|
||||
return field_value
|
||||
Executable
+628
@@ -0,0 +1,628 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
import time
|
||||
import os
|
||||
import deepspeed
|
||||
from deepspeed import comm as dist
|
||||
from deepspeed.utils.logging import log_dist
|
||||
|
||||
from torch.nn.modules import Module
|
||||
from packaging import version as pkg_version
|
||||
from deepspeed.runtime.checkpoint_engine.torch_checkpoint_engine import TorchCheckpointEngine
|
||||
from deepspeed.utils.timer import SynchronizedWallClockTimer
|
||||
from deepspeed.runtime.compiler import is_compile_supported
|
||||
from ..runtime.state_dict_factory import SDLoaderFactory
|
||||
from ..runtime.weight_quantizer import WeightQuantization
|
||||
from ..module_inject import replace_transformer_layer, generic_injection
|
||||
from ..comm.comm import init_distributed
|
||||
from ..pipe import PipelineModule
|
||||
from ..moe.utils import has_moe_layers
|
||||
from ..module_inject import LinearAllreduce, LinearLayer, Normalize, ReplaceWithTensorSlicing
|
||||
from deepspeed.accelerator import get_accelerator
|
||||
from ..module_inject.policy import TransformerPolicy
|
||||
from ..module_inject.auto_tp import AutoTP
|
||||
|
||||
from ..module_inject.replace_policy import generic_policies
|
||||
from ..module_inject.auto_tp_model_utils import build_bloom_alibi_tensor, build_mpt_atten_bias_tensor, build_mpt_alibi_tensor, get_alibi_mask
|
||||
from ..ops.transformer.inference.ds_attention import DeepSpeedSelfAttention
|
||||
from ..model_implementations.transformers.ds_transformer import DeepSpeedTransformerInference
|
||||
|
||||
DS_INFERENCE_ENABLED = False
|
||||
from torch import nn
|
||||
|
||||
INFERENCE_MODEL_TIMER = "model-forward-inference"
|
||||
|
||||
|
||||
class InferenceEngine(Module):
|
||||
inference_mp_group = None
|
||||
inference_ep_group = None
|
||||
expert_mp_group = None
|
||||
|
||||
def __init__(self, model, config):
|
||||
"""
|
||||
Args:
|
||||
model: torch.nn.Module
|
||||
config: DeepSpeedInferenceConfig
|
||||
"""
|
||||
global DS_INFERENCE_ENABLED
|
||||
DS_INFERENCE_ENABLED = True
|
||||
|
||||
super().__init__()
|
||||
if DeepSpeedTransformerInference.workspace is not None:
|
||||
self.destroy()
|
||||
|
||||
self.module = model
|
||||
self._config = config
|
||||
|
||||
self._get_model_config_generate(config) # keep for weird backward compatibility
|
||||
|
||||
# patch model generate with ours if model uses it
|
||||
if hasattr(self.module, "generate"):
|
||||
self.generate = self._generate
|
||||
|
||||
if hasattr(self.module, "config"):
|
||||
TransformerPolicy.hf_model_config = self.module.config
|
||||
|
||||
if config.dtype not in get_accelerator().supported_dtypes():
|
||||
raise ValueError(
|
||||
f"Data type {config.dtype} is not supported by {get_accelerator().device_name()} accelerator")
|
||||
|
||||
# todo: keep this self.injection_dict because we don't use to change config.injection_policy API
|
||||
# todo: this will get changed when Molly's PR on auto injection dict is merged
|
||||
self.injection_dict = config.injection_policy
|
||||
|
||||
# todo: refactor the mp_group and mp_size related in the next refactor
|
||||
self.mp_group = config.tensor_parallel.tp_group
|
||||
self.mpu = config.tensor_parallel.mpu
|
||||
|
||||
self.quantize_merge_count = 1
|
||||
self.quantization_scales = None
|
||||
|
||||
# these are not needed in the config as we are creating them ourselves in the inference engine
|
||||
self.ep_group = None # config.moe.ep_group
|
||||
self.expert_mp_group = None # config.moe.ep_mp_group
|
||||
|
||||
self.cuda_graph_created = False
|
||||
self.checkpoint_engine = TorchCheckpointEngine()
|
||||
quantization_setting = None
|
||||
self._init_quantization_setting(
|
||||
quantization_setting) # todo: update with the new quant config for weight quant
|
||||
self.model_profile_enabled = False
|
||||
self._model_times = []
|
||||
|
||||
if not self.injection_dict and config.replace_with_kernel_inject:
|
||||
# This is a hack to remove the prepare_mask function on HF side for BLOOM architecture
|
||||
self.remove_mask_prepare_for_bloom()
|
||||
|
||||
if self.injection_dict or not config.replace_with_kernel_inject:
|
||||
# This is a hack to redefine the alibi func due to TP
|
||||
if config.tensor_parallel.tp_size > 1:
|
||||
self.build_alibi_tensor()
|
||||
self.build_attn_bias()
|
||||
|
||||
if get_accelerator().device_name() == 'cuda' and config.enable_cuda_graph:
|
||||
assert pkg_version.parse(torch.__version__) >= pkg_version.parse("1.10"), \
|
||||
"If you want to use cuda graph, please upgrade torch to at least v1.10"
|
||||
|
||||
# convert model to intended dtype
|
||||
if config.dtype:
|
||||
self._convert_to_dtype(config)
|
||||
|
||||
if self.mpu:
|
||||
config.tensor_parallel.tp_size = dist.get_world_size(group=self.mpu.get_model_parallel_group())
|
||||
self.mp_group = self.mpu.get_model_parallel_group()
|
||||
elif config.tensor_parallel.tp_size > 1:
|
||||
self._create_model_parallel_group(config)
|
||||
config.tensor_parallel.tp_group = self.mp_group
|
||||
|
||||
if isinstance(self.module, torch.nn.Module):
|
||||
moe, _ = has_moe_layers(self.module)
|
||||
else:
|
||||
moe = False
|
||||
|
||||
if moe and dist.get_world_size() > 1:
|
||||
self._create_ep_parallel_group(config.moe.ep_size)
|
||||
|
||||
# We only support three modes: 1) user specified policy for tensor-parallelism, 2) kernel injection (replace_with_kernel_inject), and 3) automatic tensor parallelism if tp_size > 1.
|
||||
if self.injection_dict:
|
||||
# 1. User specified Tensor Parallelism
|
||||
assert not config.replace_with_kernel_inject, "Cannot use both user specified injection policy and kernel injection"
|
||||
for client_module, injection_policy in self.injection_dict.items():
|
||||
|
||||
assert issubclass(client_module,
|
||||
torch.nn.Module), f"{client_module} is not a subclass of torch.nn.Module"
|
||||
|
||||
# construct the tuple and pass that instead of a string or dict.
|
||||
if isinstance(injection_policy, str):
|
||||
config.injection_policy_tuple = (injection_policy, )
|
||||
else:
|
||||
config.injection_policy_tuple = injection_policy
|
||||
|
||||
layer_names = [name for name, _ in self.module.named_modules()]
|
||||
for policy in config.injection_policy_tuple:
|
||||
if not any(name.endswith(policy) for name in layer_names):
|
||||
raise ValueError(f"Injection policy layer'{policy}' not valid.")
|
||||
|
||||
self._apply_injection_policy(config, client_module)
|
||||
else:
|
||||
if config.replace_with_kernel_inject:
|
||||
# 2. DeepSpeed Kernel Injection
|
||||
self._apply_injection_policy(config)
|
||||
elif config.tensor_parallel.tp_size > 1:
|
||||
# 3. Automatic Tensor Parallelism
|
||||
parser_dict = AutoTP.tp_parser(model)
|
||||
print("AutoTP: ", parser_dict)
|
||||
for client_module, injection_policy in parser_dict:
|
||||
if isinstance(injection_policy, str):
|
||||
config.injection_policy_tuple = (injection_policy, )
|
||||
else:
|
||||
config.injection_policy_tuple = injection_policy
|
||||
self._apply_injection_policy(config, client_module)
|
||||
|
||||
device = get_accelerator().current_device_name()
|
||||
# NOTE: This check assumes a Hugging Face hierarchy for the device type i.e. module.device.type
|
||||
is_meta_device = hasattr(self.module, "device") and self.module.device.type == 'meta'
|
||||
if is_meta_device:
|
||||
self.module.to_empty(device=device)
|
||||
elif not config.keep_module_on_host:
|
||||
self.module.to(device)
|
||||
|
||||
if config.tensor_parallel.tp_size > 1:
|
||||
_rng_state = get_accelerator().get_rng_state().to(get_accelerator().current_device_name())
|
||||
dist.broadcast(_rng_state, 0)
|
||||
get_accelerator().set_rng_state(_rng_state.cpu())
|
||||
|
||||
if config.tensor_parallel.tp_size > 1:
|
||||
assert not config.enable_cuda_graph, "Cuda graph is not supported for model parallelism"
|
||||
|
||||
# Check if local CUDA graphs can be created in replacement modules
|
||||
self.local_cuda_graph = self._local_cuda_graph_used(self.module)
|
||||
self._is_compiled = False
|
||||
|
||||
def destroy(self):
|
||||
DeepSpeedTransformerInference.layer_id = 0
|
||||
DeepSpeedSelfAttention.num_layers = 0
|
||||
if DeepSpeedTransformerInference.workspace.is_allocated():
|
||||
DeepSpeedTransformerInference.workspace.release_workspace()
|
||||
DeepSpeedTransformerInference.workspace = None
|
||||
|
||||
def profile_model_time(self, use_cuda_events=True):
|
||||
if not self.model_profile_enabled and not self._config.enable_cuda_graph:
|
||||
self.module.register_forward_pre_hook(self._pre_forward_hook)
|
||||
self.module.register_forward_hook(self._post_forward_hook)
|
||||
self.model_profile_enabled = True
|
||||
self.use_cuda_events = use_cuda_events
|
||||
if self.use_cuda_events:
|
||||
self.timers = SynchronizedWallClockTimer()
|
||||
|
||||
# todo: remove this once all the config dicts are centralized from top level pydantic config
|
||||
def _get_model_config_generate(self, config):
|
||||
# this is being passed to replace_transformer_layer(config=self.user_model_config_dict)
|
||||
self.config = getattr(self.module, 'config', None) if config.config is None else config.config
|
||||
|
||||
def remove_mask_prepare_for_bloom(self):
|
||||
if hasattr(self.module, 'transformer'):
|
||||
if hasattr(self.module.transformer, '_prepare_attn_mask'):
|
||||
self.module.transformer._prepare_attn_mask = lambda attention_mask, *args, **kwargs: attention_mask
|
||||
|
||||
def build_alibi_tensor(self):
|
||||
if hasattr(self.module, 'transformer'):
|
||||
if hasattr(self.module.transformer, 'build_alibi_tensor'):
|
||||
self.module.transformer.build_alibi_tensor = build_bloom_alibi_tensor
|
||||
if hasattr(self.module.transformer, 'build_mpt_alibi_tensor'):
|
||||
self.module.transformer.build_mpt_alibi_tensor_orig = self.module.transformer.build_mpt_alibi_tensor
|
||||
self.module.transformer.__class__.build_mpt_alibi_tensor = build_mpt_alibi_tensor
|
||||
if hasattr(self.module, 'model'):
|
||||
if hasattr(self.module.model, 'get_alibi_mask'):
|
||||
self.module.model.get_alibi_mask_orig = self.module.model.get_alibi_mask
|
||||
self.module.model.__class__.get_alibi_mask = get_alibi_mask
|
||||
|
||||
def build_attn_bias(self):
|
||||
if hasattr(self.module, 'transformer'):
|
||||
if hasattr(self.module.transformer, '_attn_bias'):
|
||||
self.module.transformer._attn_bias_orig = self.module.transformer._attn_bias
|
||||
self.module.transformer.__class__._attn_bias = build_mpt_atten_bias_tensor
|
||||
|
||||
def _pre_forward_hook(self, module, *inputs, **kwargs):
|
||||
if self.use_cuda_events:
|
||||
self.timers(INFERENCE_MODEL_TIMER).start()
|
||||
else:
|
||||
get_accelerator().synchronize()
|
||||
self._start = time.time()
|
||||
|
||||
def _post_forward_hook(self, module, input, output):
|
||||
if self.use_cuda_events:
|
||||
self.timers(INFERENCE_MODEL_TIMER).stop()
|
||||
elapsed_time = self.timers(INFERENCE_MODEL_TIMER).elapsed(reset=True)
|
||||
else:
|
||||
get_accelerator().synchronize()
|
||||
self._end = time.time()
|
||||
elapsed_time = (self._end - self._start) * 1e3 # convert seconds to ms
|
||||
self._model_times.append(elapsed_time)
|
||||
|
||||
def _create_model_parallel_group(self, config):
|
||||
# Call the init process
|
||||
if InferenceEngine.inference_mp_group is None:
|
||||
init_distributed()
|
||||
local_rank = int(os.getenv('LOCAL_RANK', '0'))
|
||||
get_accelerator().set_device(local_rank)
|
||||
|
||||
ranks = [i for i in range(config.tensor_parallel.tp_size)]
|
||||
self.mp_group = dist.new_group(ranks)
|
||||
InferenceEngine.inference_mp_group = self.mp_group
|
||||
else:
|
||||
self.mp_group = InferenceEngine.inference_mp_group
|
||||
|
||||
def _create_ep_parallel_group(self, moe_experts):
|
||||
# Call the init process
|
||||
self.ep_group = {}
|
||||
self.expert_mp_group = {}
|
||||
moe_experts = moe_experts if type(moe_experts) is list else [moe_experts]
|
||||
for e in moe_experts:
|
||||
self.ep_group.update({e: None})
|
||||
self.expert_mp_group.update({e: None})
|
||||
for moe_ep_size in self.ep_group.keys():
|
||||
num_ep_groups = dist.get_world_size() // moe_ep_size
|
||||
if num_ep_groups == 0:
|
||||
raise ValueError(f"Invalid ep_size={moe_ep_size} for world_size={dist.get_world_size()}")
|
||||
for i in range(num_ep_groups):
|
||||
ep_cnt = i * moe_ep_size
|
||||
size = dist.get_world_size() if moe_ep_size > dist.get_world_size() else moe_ep_size
|
||||
ranks = list(range(ep_cnt, ep_cnt + size))
|
||||
_ep_group = dist.new_group(ranks)
|
||||
if dist.get_rank() in ranks:
|
||||
self.ep_group.update({moe_ep_size: _ep_group})
|
||||
|
||||
if dist.get_world_size() > moe_ep_size:
|
||||
num_expert_mp_groups = dist.get_world_size() // num_ep_groups
|
||||
expert_mp_size = dist.get_world_size() // moe_ep_size
|
||||
for i in range(num_expert_mp_groups):
|
||||
expert_mp_comm_ranks = [i + nr * moe_ep_size for nr in range(expert_mp_size)]
|
||||
_expert_mp_group = dist.new_group(expert_mp_comm_ranks)
|
||||
if dist.get_rank() in expert_mp_comm_ranks:
|
||||
self.expert_mp_group.update({moe_ep_size: _expert_mp_group})
|
||||
|
||||
def _init_quantization_setting(self, quantization_setting):
|
||||
self.quantize_bits = 8
|
||||
self.mlp_extra_grouping = False
|
||||
self.quantize_groups = 1
|
||||
if type(quantization_setting) is tuple:
|
||||
self.mlp_extra_grouping, \
|
||||
self.quantize_groups = quantization_setting
|
||||
elif quantization_setting is not None:
|
||||
self.quantize_groups = quantization_setting
|
||||
log_dist(
|
||||
f"quantize_bits = {self.quantize_bits} "
|
||||
f"mlp_extra_grouping = {self.mlp_extra_grouping}, "
|
||||
f"quantize_groups = {self.quantize_groups}", [0])
|
||||
|
||||
def load_model_with_checkpoint(self, r_module):
|
||||
self.mp_replace = ReplaceWithTensorSlicing(
|
||||
mp_group=self.mp_group, mp_size=self._config.tensor_parallel.tp_size) #, out_dim=0, in_dim=1)
|
||||
error_msgs = []
|
||||
|
||||
def load(module, state_dict, prefix):
|
||||
args = (state_dict, prefix, {}, True, [], [], error_msgs)
|
||||
if hasattr(module, 'weight'):
|
||||
if module.weight.data.is_meta:
|
||||
# meta tensor cannot be casted or copied to, so we need to replace it with a normal tensor here
|
||||
module.weight = torch.nn.parameter.Parameter(data=torch.empty_like(module.weight.data,
|
||||
device="cpu"),
|
||||
requires_grad=module.weight.data.requires_grad)
|
||||
if 'query_key_value' in prefix:
|
||||
module.weight = self.mp_replace.strided_copy(module.weight.data,
|
||||
state_dict[prefix + 'weight'],
|
||||
num_splits=3)
|
||||
else:
|
||||
module.weight = self.mp_replace.copy(module.weight.data, state_dict[prefix + 'weight'])
|
||||
else:
|
||||
if module.norm.weight.data.is_meta:
|
||||
# meta tensor cannot be casted or copied to, so we need to replace it with a normal tensor here
|
||||
module.norm.weight = torch.nn.parameter.Parameter(
|
||||
data=torch.empty_like(module.norm.weight.data, device="cpu"),
|
||||
requires_grad=module.norm.weight.data.requires_grad)
|
||||
module.norm.weight = self.mp_replace.copy(module.norm.weight.data, state_dict[prefix + 'weight'])
|
||||
if prefix + 'bias' in self.key_list:
|
||||
if hasattr(module, 'norm'):
|
||||
if module.norm.bias.data.is_meta:
|
||||
# meta tensor cannot be casted or copied to, so we need to replace it with a normal tensor here
|
||||
module.norm.bias = torch.nn.parameter.Parameter(
|
||||
data=torch.empty_like(module.norm.bias.data, device="cpu"),
|
||||
requires_grad=module.norm.bias.data.requires_grad)
|
||||
module.norm.bias = self.mp_replace.copy(module.norm.bias, state_dict[prefix + 'bias'])
|
||||
else:
|
||||
if module.bias.data.is_meta:
|
||||
# meta tensor cannot be casted or copied to, so we need to replace it with a normal tensor here
|
||||
module.bias = torch.nn.parameter.Parameter(data=torch.empty_like(module.bias.data,
|
||||
device="cpu"),
|
||||
requires_grad=module.bias.data.requires_grad)
|
||||
data = state_dict[prefix + 'bias']
|
||||
data = data.to(get_accelerator().current_device_name())
|
||||
module.bias = self.mp_replace.copy(module.bias, data)
|
||||
|
||||
layer_policies = {
|
||||
nn.Linear: load,
|
||||
nn.Embedding: load,
|
||||
nn.LayerNorm: load,
|
||||
LinearLayer: load,
|
||||
LinearAllreduce: load
|
||||
}
|
||||
|
||||
def load_module_recursive(module, prefix='', level=0):
|
||||
for name, child in module.named_children():
|
||||
if child.__class__ in layer_policies:
|
||||
checking_key = prefix + name + '.'
|
||||
if not any(checking_key in item for item in self.key_list):
|
||||
continue
|
||||
if len(list(child.parameters())) > 0 and list(child.parameters())[0].numel() == 0:
|
||||
if len(child.weight.ds_shape) == 1:
|
||||
child = Normalize(dim=child.weight.ds_shape[-1], dtype=child.weight.dtype, eps=child.eps)
|
||||
setattr(module, name, child)
|
||||
load(child, self.sd, prefix + name + '.')
|
||||
else:
|
||||
load_module_recursive(child, prefix if level == 0 else prefix + name + '.', level + 1)
|
||||
|
||||
load_module_recursive(r_module)
|
||||
|
||||
embedding_weight = None
|
||||
|
||||
for n, p in r_module.named_parameters():
|
||||
if "word_embeddings." in n or "embed_tokens." in n or "wte." in n:
|
||||
embedding_weight = p
|
||||
if embedding_weight is not None and hasattr(r_module, "lm_head") and hasattr(
|
||||
r_module.lm_head, "weight") and r_module.lm_head.weight.is_meta:
|
||||
r_module.lm_head.weight = embedding_weight
|
||||
|
||||
def _apply_injection_policy(self, config, client_module=None):
|
||||
# client_module is only passed when using the injection_dict method.
|
||||
checkpoint_dir = config.checkpoint
|
||||
checkpoint = SDLoaderFactory.get_sd_loader_json(checkpoint_dir,
|
||||
self.checkpoint_engine) if checkpoint_dir is not None else None
|
||||
|
||||
generic_injection(self.module, dtype=config.dtype, enable_cuda_graph=config.enable_cuda_graph)
|
||||
|
||||
if isinstance(self.module, torch.nn.Module):
|
||||
# config is our DeepSpeedInferenceConfig and self.config is the HF model config
|
||||
replace_transformer_layer(client_module, self.module, checkpoint, config, self.config)
|
||||
|
||||
def _get_all_ckpt_names(self, checkpoints_path, tag):
|
||||
ckpt_file_pattern = self._get_ckpt_name(checkpoints_path, tag, mp_placeholder="*")
|
||||
import glob
|
||||
|
||||
ckpt_files = glob.glob(ckpt_file_pattern)
|
||||
ckpt_files.sort()
|
||||
return ckpt_files
|
||||
|
||||
def _get_ckpt_name(self, checkpoints_path, tag, mp_placeholder=None):
|
||||
if mp_placeholder is not None:
|
||||
mp_rank_str = mp_placeholder
|
||||
else:
|
||||
mp_rank = 0 if self.mpu is None else self.mpu.get_model_parallel_rank()
|
||||
mp_rank_str = "{:02d}".format(mp_rank)
|
||||
|
||||
ckpt_name = os.path.join(
|
||||
checkpoints_path,
|
||||
"mp_rank_" + mp_rank_str + "_model_states.pt",
|
||||
)
|
||||
return ckpt_name
|
||||
|
||||
def _load_checkpoint(self, load_dir, load_module_strict=True, tag=None):
|
||||
is_pipe_parallel = isinstance(self.module, PipelineModule)
|
||||
if is_pipe_parallel:
|
||||
raise RuntimeError('pipeline parallelism is currently not supported in inference.')
|
||||
if not isinstance(load_dir, dict) and os.path.isdir(load_dir):
|
||||
if tag is None:
|
||||
latest_path = os.path.join(load_dir, "latest")
|
||||
if os.path.isfile(latest_path):
|
||||
with open(latest_path, "r") as fd:
|
||||
tag = fd.read().strip()
|
||||
|
||||
ckpt_list = self._get_all_ckpt_names(load_dir, tag)
|
||||
sd_loader = SDLoaderFactory.get_sd_loader(ckpt_list, self.checkpoint_engine)
|
||||
else:
|
||||
sd_loader = SDLoaderFactory.get_sd_loader_json(load_dir, self.checkpoint_engine)
|
||||
|
||||
checkpoint = sd_loader['checkpoints']
|
||||
|
||||
if type(checkpoint) is list:
|
||||
self.sd = torch.load(checkpoint[0], map_location='cpu', weights_only=False)
|
||||
self.key_list = list(self.sd.keys())
|
||||
|
||||
self.load_model_with_checkpoint(self.module)
|
||||
|
||||
for i in range(1, len(checkpoint)):
|
||||
if not dist.is_initialized() or dist.get_rank() == 0:
|
||||
print(f"loading checkpoint ({i})")
|
||||
self.sd = torch.load(checkpoint[i], map_location=get_accelerator().device_name(), weights_only=False)
|
||||
self.key_list = list(self.sd.keys())
|
||||
self.load_model_with_checkpoint(self.module)
|
||||
else:
|
||||
mp_rank = 0 if self.mpu is None else self.mpu.get_model_parallel_rank()
|
||||
|
||||
load_path, checkpoint, quantize_config = sd_loader.load(self._config.tensor_parallel.tp_size,
|
||||
mp_rank,
|
||||
is_pipe_parallel=is_pipe_parallel,
|
||||
quantize=(self._config.dtype is torch.int8),
|
||||
quantize_groups=self.quantize_groups,
|
||||
mlp_extra_grouping=self.mlp_extra_grouping)
|
||||
|
||||
self.quantization_scales, self.quantize_merge_count = quantize_config
|
||||
|
||||
moe, _ = has_moe_layers(self.module)
|
||||
if moe:
|
||||
from deepspeed.runtime.engine import DeepSpeedEngine
|
||||
old_moe_load = False
|
||||
if not isinstance(checkpoint['num_experts'], list):
|
||||
old_moe_load = True
|
||||
DeepSpeedEngine.load_moe_state_dict(load_dir,
|
||||
tag,
|
||||
state_dict=checkpoint[self._choose_module_key(checkpoint)],
|
||||
old_moe_load=old_moe_load,
|
||||
model=self.module,
|
||||
mpu=self.mpu,
|
||||
checkpoint_engine=self.checkpoint_engine,
|
||||
autoep_layers=None)
|
||||
|
||||
self.module.load_state_dict(state_dict=checkpoint[self._choose_module_key(checkpoint)],
|
||||
strict=load_module_strict)
|
||||
|
||||
def _choose_module_key(self, sd):
|
||||
assert not ('module' in sd
|
||||
and 'model' in sd), "checkpoint has both 'model' and 'module' keys, not sure how to proceed"
|
||||
assert 'module' in sd or 'model' in sd, "checkpoint contains neither 'model' or 'module' keys, not sure how to proceed"
|
||||
if 'module' in sd:
|
||||
return 'module'
|
||||
elif 'model' in sd:
|
||||
return 'model'
|
||||
|
||||
def _convert_to_dtype(self, config):
|
||||
if not isinstance(self.module, torch.nn.Module):
|
||||
return
|
||||
|
||||
if False: #config.dtype is torch.int8 and self.quantization_scales is None:
|
||||
quantizer = WeightQuantization(mlp_extra_grouping=self.mlp_extra_grouping)
|
||||
model, self.quantization_scales = quantizer.model_quantize(self.module, self.injection_dict,
|
||||
self.quantize_bits, self.quantize_groups)
|
||||
elif config.dtype == torch.half:
|
||||
self.module.half()
|
||||
elif config.dtype == torch.bfloat16:
|
||||
self.module.bfloat16()
|
||||
elif config.dtype == torch.float:
|
||||
self.module.float()
|
||||
|
||||
def _create_cuda_graph(self, *inputs, **kwargs):
|
||||
# warmup to create the workspace and cublas handle
|
||||
cuda_stream = get_accelerator().Stream()
|
||||
cuda_stream.wait_stream(get_accelerator().current_stream())
|
||||
with get_accelerator().stream(cuda_stream):
|
||||
for i in range(3):
|
||||
ret = self.module(*inputs, **kwargs)
|
||||
get_accelerator().current_stream().wait_stream(cuda_stream)
|
||||
|
||||
# create cuda_graph and assign static_inputs and static_outputs
|
||||
self._cuda_graphs = get_accelerator().create_graph()
|
||||
self.static_inputs = inputs
|
||||
self.static_kwargs = kwargs
|
||||
|
||||
with get_accelerator().capture_to_graph(self._cuda_graphs):
|
||||
self.static_output = self.module(*self.static_inputs, **self.static_kwargs)
|
||||
|
||||
self.cuda_graph_created = True
|
||||
|
||||
def _graph_replay(self, *inputs, **kwargs):
|
||||
for i in range(len(inputs)):
|
||||
if torch.is_tensor(inputs[i]):
|
||||
self.static_inputs[i].copy_(inputs[i])
|
||||
for k in kwargs:
|
||||
if torch.is_tensor(kwargs[k]):
|
||||
self.static_kwargs[k].copy_(kwargs[k])
|
||||
get_accelerator().replay_graph(self._cuda_graphs)
|
||||
return self.static_output
|
||||
|
||||
def model_times(self):
|
||||
assert self.model_profile_enabled, "model profiling is not enabled"
|
||||
model_times = self._model_times
|
||||
if self._config.enable_cuda_graph and len(self._model_times) == 0:
|
||||
raise ValueError("Model times are empty and cuda graph is enabled. If "
|
||||
"this is a GPT-style model this combo is not supported. If this is a "
|
||||
"BERT-style model this is a bug, please report it. "
|
||||
f"Model type is: {type(self.module)}")
|
||||
self._model_times = []
|
||||
return model_times
|
||||
|
||||
def _module_match(self, module):
|
||||
for policy in generic_policies:
|
||||
policy = policy()
|
||||
if policy.match_replaced(module):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _local_cuda_graph_used(self, module):
|
||||
if isinstance(module, torch.nn.Module):
|
||||
return False
|
||||
else:
|
||||
sub_module_cuda_graph = False
|
||||
for name in module.__dict__.keys():
|
||||
sub_module = getattr(module, name)
|
||||
|
||||
if self._module_match(sub_module) and hasattr(sub_module, "enable_cuda_graph"):
|
||||
sub_module_cuda_graph = True
|
||||
|
||||
return sub_module_cuda_graph
|
||||
|
||||
def forward(self, *inputs, **kwargs):
|
||||
"""Execute forward propagation
|
||||
|
||||
Arguments:
|
||||
*inputs: Variable length input list
|
||||
**kwargs: variable length keyword arguments
|
||||
"""
|
||||
start = None
|
||||
if self.model_profile_enabled and get_accelerator().device_name() == 'cuda' and self._config.enable_cuda_graph:
|
||||
get_accelerator().synchronize()
|
||||
start = time.time()
|
||||
|
||||
if get_accelerator().device_name() == 'cuda' and self._config.enable_cuda_graph and not self.local_cuda_graph:
|
||||
if self.cuda_graph_created:
|
||||
outputs = self._graph_replay(*inputs, **kwargs)
|
||||
else:
|
||||
self._create_cuda_graph(*inputs, **kwargs)
|
||||
outputs = self._graph_replay(*inputs, **kwargs)
|
||||
|
||||
else:
|
||||
outputs = self.module(*inputs, **kwargs)
|
||||
|
||||
if self.model_profile_enabled and self._config.enable_cuda_graph:
|
||||
get_accelerator().synchronize()
|
||||
duration = (time.time() - start) * 1e3 # convert seconds to ms
|
||||
self._model_times.append(duration)
|
||||
|
||||
return outputs
|
||||
|
||||
def _generate(self, *inputs, **kwargs):
|
||||
# Reset KV-cache at the beginning of generate
|
||||
if hasattr(self.module, 'reset_cache'):
|
||||
self.module.reset_cache()
|
||||
num_beams = 1
|
||||
if "generation_config" in kwargs:
|
||||
gen_config = kwargs["generation_config"]
|
||||
num_beams = getattr(gen_config, "num_beams", 1)
|
||||
if "num_beams" in kwargs:
|
||||
num_beams = kwargs["num_beams"]
|
||||
|
||||
if num_beams > 1:
|
||||
raise NotImplementedError("DeepSpeed does not support `num_beams` > 1, if this is important to you please "
|
||||
"add your request to: https://github.com/deepspeedai/DeepSpeed/issues/2506")
|
||||
|
||||
if ("input_ids" in kwargs) and (kwargs["input_ids"].dim() == 2):
|
||||
for input_tensor in kwargs["input_ids"]:
|
||||
tensor_length = input_tensor.shape[-1]
|
||||
if tensor_length > self._config.max_out_tokens:
|
||||
raise RuntimeError(
|
||||
f"Input with size {tensor_length} exceeds maximum length of {self._config.max_out_tokens}. Please increase max_tokens in the DeepSpeed Inference Config."
|
||||
)
|
||||
|
||||
return self.module.generate(*inputs, **kwargs)
|
||||
|
||||
def compile(self, backend=get_accelerator().get_compile_backend(), compile_kwargs={}) -> None:
|
||||
"""
|
||||
Compile the module using the specified backend and kwargs.
|
||||
"""
|
||||
if not is_compile_supported():
|
||||
raise RuntimeError("compile is not supported in your version of PyTorch.")
|
||||
|
||||
if self._is_compiled:
|
||||
return
|
||||
|
||||
# Avoid graph breaks
|
||||
deepspeed.utils.nvtx.enable_nvtx = False
|
||||
self.module.compile(backend=backend, **compile_kwargs)
|
||||
self._is_compiled = True
|
||||
|
||||
@property
|
||||
def is_compiled(self) -> bool:
|
||||
return self._is_compiled
|
||||
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
@@ -0,0 +1,114 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from torch import nn
|
||||
from torch import Tensor
|
||||
from torch.nn import functional as F
|
||||
from .utils import Quantizer, DeQuantizer, concat_to_compat_param
|
||||
from typing import Tuple, Callable, Dict
|
||||
from deepspeed.runtime.zero import register_external_parameter
|
||||
|
||||
quantized_weight_registry = {}
|
||||
is_zero3_enabled = False
|
||||
|
||||
|
||||
# deal with weight sharing
|
||||
def get_quantized_weight_wrapper(model, pre_quant_weight: nn.Parameter, quantize_weight_fn: Callable) -> nn.Parameter:
|
||||
if id(pre_quant_weight) in quantized_weight_registry:
|
||||
compat_tensor = quantized_weight_registry[id(pre_quant_weight)]
|
||||
if is_zero3_enabled:
|
||||
register_external_parameter(model, compat_tensor)
|
||||
|
||||
return quantized_weight_registry[id(pre_quant_weight)]
|
||||
else:
|
||||
quantized_weights, quant_scale, quant_min = quantize_weight_fn()
|
||||
quantized_weight_registry[id(pre_quant_weight)] = concat_to_compat_param(quantized_weights, quant_scale,
|
||||
quant_min)
|
||||
return quantized_weight_registry[id(pre_quant_weight)]
|
||||
|
||||
|
||||
def get_quantize_weight_fn(quantizer: Quantizer, pre_quant_weight: nn.Parameter) -> Callable:
|
||||
|
||||
def func() -> Tuple[nn.Parameter, Tensor, Tensor]:
|
||||
quantized_weights, quant_scale, quant_min = quantizer.quantize(pre_quant_weight.data)
|
||||
# A temporary hack as zero Zero3 assume all model weights has the same type. in all_gather_coalesced.get_only_unique_item
|
||||
quantized_weights = quantized_weights.view(pre_quant_weight.dtype)
|
||||
quant_scale = quant_scale.type(pre_quant_weight.dtype)
|
||||
quant_min = quant_min.type(pre_quant_weight.dtype)
|
||||
return quantized_weights, quant_scale, quant_min
|
||||
|
||||
return func
|
||||
|
||||
|
||||
class QuantizedLinear(nn.Linear):
|
||||
|
||||
def __init__(self, config: Dict, pre_quant_layer: nn.Linear) -> None:
|
||||
super(QuantizedLinear, self).__init__(in_features=pre_quant_layer.in_features,
|
||||
out_features=pre_quant_layer.out_features,
|
||||
bias=pre_quant_layer.bias is not None,
|
||||
device=pre_quant_layer.weight.device,
|
||||
dtype=pre_quant_layer.weight.dtype)
|
||||
self.config = config
|
||||
|
||||
self.quantizer = Quantizer(config=config)
|
||||
self.bias = pre_quant_layer.bias
|
||||
self.weight = get_quantized_weight_wrapper(self, pre_quant_layer.weight,
|
||||
get_quantize_weight_fn(self.quantizer, pre_quant_layer.weight))
|
||||
|
||||
self.weight.dequantizer = DeQuantizer(config, pre_quant_layer.weight.dtype)
|
||||
|
||||
def forward(self, input: Tensor) -> Tensor:
|
||||
quantized_weight, quant_scale, quant_min = self.weight.deconcat(self.weight)
|
||||
temp_dequantized_weight = self.weight.dequantizer.dequantize(quantized_weight.view(torch.uint8), quant_scale,
|
||||
quant_min)
|
||||
|
||||
# !!! Do not use torch.functional.linear(input, temp_dequantized_weight, self.bias) here as in zero3 torch.functional.linear is
|
||||
# replaced by LinearFunctionForZeroStage3. Which assume weight is non-temporary.
|
||||
# If weight is temp buffer there will be memory leak.
|
||||
return torch._C._nn.linear(input, temp_dequantized_weight, self.bias)
|
||||
|
||||
|
||||
class QuantizedEmbedding(nn.Embedding):
|
||||
|
||||
def __init__(self, config: Dict, pre_quant_layer: nn.Embedding) -> None:
|
||||
super(QuantizedEmbedding, self).__init__(num_embeddings=pre_quant_layer.num_embeddings,
|
||||
embedding_dim=pre_quant_layer.embedding_dim,
|
||||
padding_idx=pre_quant_layer.padding_idx,
|
||||
max_norm=pre_quant_layer.max_norm,
|
||||
norm_type=pre_quant_layer.norm_type,
|
||||
scale_grad_by_freq=pre_quant_layer.scale_grad_by_freq,
|
||||
sparse=pre_quant_layer.sparse,
|
||||
_weight=pre_quant_layer.weight,
|
||||
device=pre_quant_layer.weight.device,
|
||||
dtype=pre_quant_layer.weight.dtype)
|
||||
|
||||
assert pre_quant_layer.max_norm is None, 'Not supported'
|
||||
assert pre_quant_layer.norm_type == 2, 'Not supported'
|
||||
assert pre_quant_layer.scale_grad_by_freq == False, 'Not supported'
|
||||
assert pre_quant_layer.sparse == False, 'Not supported'
|
||||
|
||||
self.config = config
|
||||
quantizer = Quantizer(config=config)
|
||||
|
||||
self.weight = get_quantized_weight_wrapper(self, pre_quant_layer.weight,
|
||||
get_quantize_weight_fn(quantizer, pre_quant_layer.weight))
|
||||
|
||||
self.weight.dequantizer = DeQuantizer(config, pre_quant_layer.weight.dtype)
|
||||
|
||||
def forward(self, input: Tensor) -> Tensor:
|
||||
quantized_weight, quant_scale, quant_min = self.weight.deconcat(self.weight)
|
||||
temp_dequantized_weight = self.weight.dequantizer.dequantize(quantized_weight.view(torch.uint8), quant_scale,
|
||||
quant_min)
|
||||
|
||||
return F.embedding(input, temp_dequantized_weight, self.padding_idx, self.max_norm, self.norm_type,
|
||||
self.scale_grad_by_freq, self.sparse)
|
||||
|
||||
|
||||
QUANTIZATION_LAYER_MAPPINGS = {
|
||||
nn.Linear: QuantizedLinear,
|
||||
nn.Embedding: QuantizedEmbedding,
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
from typing import Dict
|
||||
import gc
|
||||
from deepspeed.inference.quantization import layers
|
||||
from .layers import QUANTIZATION_LAYER_MAPPINGS
|
||||
from .utils import get_AsyncPartitionedParameterSwapper, recursive_setattr
|
||||
from deepspeed.utils.logging import logger
|
||||
from collections import deque
|
||||
from transformers.utils.generic import ContextManagers
|
||||
from .quantization_context import QuantizationContext
|
||||
import contextlib
|
||||
|
||||
|
||||
def _init_group_wise_weight_quantization(model: nn.Module, ds_config: Dict) -> nn.Module:
|
||||
"""[Experimental] Apply group-wise weight quantization to model. Replace layers module according to config_list
|
||||
|
||||
Args:
|
||||
model (nn.Module): A nn.Module
|
||||
ds_config (Dict, optional): The ds_config dictionary. use None for non-deepspeed managed model.
|
||||
|
||||
Returns:
|
||||
nn.Module: Quantized nn.Module
|
||||
"""
|
||||
|
||||
# global quantized_weight_registry
|
||||
|
||||
matched_module_list_by_key = {}
|
||||
matched_module_count = 0
|
||||
|
||||
assert 'weight_quantization' in ds_config, 'Please provide quantization config in ds_config'
|
||||
quantization_config = ds_config['weight_quantization']['post_init_quant']
|
||||
|
||||
# Return nvme swapper if exists, else return None.
|
||||
# For nvme offloading we must use the same swapper here as model initialized.
|
||||
nvme_swapper = get_AsyncPartitionedParameterSwapper(model)
|
||||
is_zero3_enabled = 'zero_optimization' in ds_config and \
|
||||
'stage' in ds_config['zero_optimization'] and \
|
||||
ds_config['zero_optimization']['stage'] == 3
|
||||
is_offloading_enabled = 'zero_optimization' in ds_config and \
|
||||
'offload_param' in ds_config['zero_optimization']
|
||||
|
||||
layers.is_zero3_enabled = is_zero3_enabled
|
||||
|
||||
context_mgr = ContextManagers([QuantizationContext(config_dict_or_path=ds_config, param_swapper=nvme_swapper)]) \
|
||||
if is_zero3_enabled else contextlib.suppress()
|
||||
with context_mgr:
|
||||
module_list = list(
|
||||
filter(lambda named_module: type(named_module[1]) in QUANTIZATION_LAYER_MAPPINGS, model.named_modules()))
|
||||
|
||||
# Quantize small weight first then large.
|
||||
if not is_offloading_enabled:
|
||||
module_list.sort(key=lambda named_module: named_module[1].weight.ds_tensor.numel()
|
||||
if is_zero3_enabled else named_module[1].weight.numel())
|
||||
module_list = deque(module_list)
|
||||
|
||||
while len(module_list) > 0:
|
||||
# Use popleft to timely release module's memory of replaced module after each loop iteration
|
||||
module_name, module = module_list.popleft()
|
||||
|
||||
matched_key = None
|
||||
matched_quantization_config = None
|
||||
|
||||
for key, config in quantization_config.items():
|
||||
if key in module_name:
|
||||
assert matched_key is None, f'{module_name} matched multiple quantization key word {matched_key} and {key}'
|
||||
matched_key = key
|
||||
matched_quantization_config = config
|
||||
|
||||
if matched_key is None:
|
||||
continue
|
||||
|
||||
if is_zero3_enabled:
|
||||
module.weight.all_gather()
|
||||
|
||||
assert module.weight.dtype == torch.float16, 'Model weight is expected in half.'
|
||||
|
||||
new_module = QUANTIZATION_LAYER_MAPPINGS[type(module)](matched_quantization_config, module)
|
||||
|
||||
if is_zero3_enabled:
|
||||
module.weight.partition()
|
||||
|
||||
recursive_setattr(model, module_name, new_module)
|
||||
|
||||
if matched_key not in matched_module_list_by_key:
|
||||
matched_module_list_by_key[matched_key] = []
|
||||
matched_module_list_by_key[matched_key].append(module_name)
|
||||
matched_module_count += 1
|
||||
|
||||
# Timely recycle memory to prevent OOM on large models
|
||||
gc.collect()
|
||||
|
||||
# Clear registry after model construction.
|
||||
layers.quantized_weight_registry.clear()
|
||||
|
||||
logger.info(
|
||||
f'Group-wise weight quantization summary: convert {matched_module_count} node(s) to quantized implementation')
|
||||
summary_str = '\n'
|
||||
|
||||
for key, module_list in matched_module_list_by_key.items():
|
||||
summary_str += f'Key: {key}, matched modules:\n'
|
||||
for module_name in module_list:
|
||||
summary_str += f'\t{module_name}\n'
|
||||
logger.info(summary_str)
|
||||
|
||||
return model
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from deepspeed.runtime.zero import partition_parameters
|
||||
from deepspeed.runtime.swap_tensor.partitioned_param_swapper import AsyncPartitionedParameterSwapper
|
||||
|
||||
|
||||
class QuantizationContext(partition_parameters.Init):
|
||||
|
||||
def __init__(self, config_dict_or_path, param_swapper: AsyncPartitionedParameterSwapper = None) -> None:
|
||||
super().__init__(config_dict_or_path=config_dict_or_path, param_swapper=param_swapper)
|
||||
@@ -0,0 +1,288 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
import deepspeed
|
||||
from torch import Tensor
|
||||
from typing import Tuple
|
||||
import torch.nn as nn
|
||||
from typing import Dict, Callable, Union
|
||||
from deepspeed.accelerator import get_accelerator
|
||||
import functools
|
||||
|
||||
device = get_accelerator().device_name() if get_accelerator().is_available() else 'cpu'
|
||||
|
||||
quantizer_module = None
|
||||
|
||||
|
||||
def get_quantizer_module():
|
||||
global quantizer_module
|
||||
if quantizer_module is None:
|
||||
quantizer_module = deepspeed.ops.op_builder.QuantizerBuilder().load()
|
||||
return quantizer_module
|
||||
|
||||
|
||||
def tensor_clamp(tensor: Tensor, min, max) -> Tensor:
|
||||
if tensor.device.type == 'cpu' and tensor.dtype == torch.float16:
|
||||
# CPU does not support FP16 clamp
|
||||
return tensor.to(dtype=torch.float32).clamp_(min, max).to(dtype=torch.float16)
|
||||
else:
|
||||
return tensor.clamp_(min, max)
|
||||
|
||||
|
||||
def tensor_round(tensor: Tensor) -> Tensor:
|
||||
if tensor.device.type == 'cpu' and tensor.dtype == torch.float16:
|
||||
# CPU does not support FP16 round
|
||||
return tensor.to(dtype=torch.float32).round_().to(dtype=torch.float16)
|
||||
else:
|
||||
return tensor.round_()
|
||||
|
||||
|
||||
class Quantizer:
|
||||
|
||||
def __init__(self, config: Dict) -> None:
|
||||
self.config = config
|
||||
assert self.config['num_bits'] == 4 or self.config[
|
||||
'num_bits'] == 8, 'Only INT4 and INT8 quantization is supported.'
|
||||
assert self.config['symmetric'] == False, 'Only asymmetric quantization is supported at this moment.'
|
||||
|
||||
def quantize(self, tensor: Tensor) -> Tuple[Tensor, Tensor, Tensor]:
|
||||
assert tensor.shape[self.config['group_dim']] % self.config['group_size'] == 0 \
|
||||
, f'Tensor shape: {tensor.shape} quantization config {self.config}'
|
||||
|
||||
tensor = torch.clone(tensor)
|
||||
|
||||
shape = tensor.shape
|
||||
num_groups = shape[self.config['group_dim']] // self.config['group_size']
|
||||
new_shape = (shape[:self.config['group_dim']] + (num_groups, self.config['group_size']) +
|
||||
shape[self.config['group_dim'] + 1:])
|
||||
tensor = tensor.view(new_shape)
|
||||
|
||||
quantized_tensor, scale, min_value = self._quantize_int8(tensor)
|
||||
quantized_tensor = quantized_tensor.view(shape)
|
||||
|
||||
if self.config['num_bits'] == 4:
|
||||
return self._compress_uint8_to_uint4(quantized_tensor), scale, min_value
|
||||
if self.config['num_bits'] == 8:
|
||||
return quantized_tensor, scale, min_value
|
||||
|
||||
assert False, 'Unsupported quantization bits {}'.format(self.config['num_bits'])
|
||||
|
||||
def _quantize_int8(self, tensor: Tensor) -> Tuple[Tensor, Tensor, Tensor]:
|
||||
q_range = 2**self.config['num_bits'] - 1
|
||||
min_value = tensor.amin(dim=self.config['group_dim'] + 1, keepdim=True)
|
||||
max_value = tensor.amax(dim=self.config['group_dim'] + 1, keepdim=True)
|
||||
|
||||
scale = q_range / (max_value - min_value)
|
||||
|
||||
tensor = tensor.sub_(min_value).mul_(scale)
|
||||
tensor = tensor_round(tensor_clamp(tensor, 0, q_range)).to(torch.uint8)
|
||||
return tensor, scale, min_value
|
||||
|
||||
def _compress_uint8_to_uint4(self, tensor: Tensor) -> Tensor:
|
||||
assert tensor.shape[-1] % 2 == 0
|
||||
|
||||
new_data_shape = list(tensor.shape)
|
||||
new_data_shape[-1] = new_data_shape[-1] // 2
|
||||
|
||||
data = torch.empty(new_data_shape, dtype=torch.uint8, device=tensor.device)
|
||||
data = torch.bitwise_or(tensor[..., 0::2].bitwise_left_shift(4), tensor[..., 1::2])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class DeQuantizer:
|
||||
|
||||
def __init__(self, config: Dict, dtype: torch.dtype) -> None:
|
||||
self.config = config
|
||||
self.dtype = dtype
|
||||
assert self.config['num_bits'] == 4 or self.config[
|
||||
'num_bits'] == 8, 'Only INT4 and INT8 quantization is supported.'
|
||||
assert self.config['symmetric'] == False, 'Only asymmetric quantization is supported at this moment.'
|
||||
|
||||
def dequantize(self, tensor: Tensor, quant_scale: Tensor, quant_min: Tensor) -> Tensor:
|
||||
# Use customized CUDA quantization kernel if possible.
|
||||
if self.config['group_size'] % 8 == 0 and \
|
||||
(self.config['num_bits'] == 4 or self.config['num_bits'] == 8) and \
|
||||
self.config['group_dim'] == len(tensor.shape) - 1 and \
|
||||
self.dtype == torch.float16 and device == get_accelerator().device_name():
|
||||
|
||||
last_dimension_size = self.config['group_size']
|
||||
if self.config['num_bits'] == 4:
|
||||
last_dimension_size = last_dimension_size // 2
|
||||
quantized_tensor = get_quantizer_module().dequantize_int4_to_half_experimental(
|
||||
tensor.reshape(-1, last_dimension_size), quant_scale, quant_min,
|
||||
tensor.numel() // last_dimension_size, self.config['group_size'])
|
||||
shape = list(tensor.shape)
|
||||
shape[-1] = shape[-1] * 2
|
||||
elif self.config['num_bits'] == 8:
|
||||
# last_dimension_size = last_dimension_size // 2
|
||||
quantized_tensor = get_quantizer_module().dequantize_int8_to_half_experimental(
|
||||
tensor.reshape(-1, last_dimension_size), quant_scale, quant_min,
|
||||
tensor.numel() // last_dimension_size, self.config['group_size'])
|
||||
shape = list(tensor.shape)
|
||||
|
||||
return quantized_tensor.reshape(shape)
|
||||
|
||||
if self.config['num_bits'] == 4:
|
||||
tensor = self._decompress_uint4_to_uint8(tensor)
|
||||
elif self.config['num_bits'] != 8:
|
||||
assert False, 'Unsupported quantization bits {}'.format(self.config['num_bits'])
|
||||
|
||||
shape = tensor.shape
|
||||
num_groups = shape[self.config['group_dim']] // self.config['group_size']
|
||||
new_shape = (shape[:self.config['group_dim']] + (num_groups, self.config['group_size']) +
|
||||
shape[self.config['group_dim'] + 1:])
|
||||
tensor = tensor.view(new_shape)
|
||||
|
||||
dequantized_tensor = self._dequantize_int8(tensor, quant_scale, quant_min).view(shape)
|
||||
return dequantized_tensor
|
||||
|
||||
def _dequantize_int8(self, tensor: Tensor, quant_scale: Tensor, quant_min: Tensor) -> Tensor:
|
||||
assert tensor.dtype == torch.uint8
|
||||
data = torch.zeros_like(tensor, dtype=self.dtype, device=tensor.device)
|
||||
data = data.copy_(tensor)
|
||||
data = data.div_(quant_scale).add_(quant_min)
|
||||
|
||||
return data
|
||||
|
||||
def _decompress_uint4_to_uint8(self, tensor: Tensor) -> Tensor:
|
||||
new_data_shape = list(tensor.shape)
|
||||
new_data_shape[-1] = new_data_shape[-1] * 2
|
||||
data = torch.empty(new_data_shape, dtype=torch.uint8, device=tensor.device)
|
||||
data[..., 0::2] = tensor.bitwise_right_shift(4)
|
||||
data[..., 1::2] = tensor.bitwise_and(0xF)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_AsyncPartitionedParameterSwapper(model: nn.Module):
|
||||
for param_name, param in model.named_parameters():
|
||||
if hasattr(param, 'nvme_swapper') and param.nvme_swapper is not None:
|
||||
return param.nvme_swapper
|
||||
return None
|
||||
|
||||
|
||||
def recursive_setattr(model, module_name, module):
|
||||
"""
|
||||
Recursively set the attribute of a module.
|
||||
Args:
|
||||
model (`torch.nn.Module`)
|
||||
The model to set the attribute in.
|
||||
module_name (`str`)
|
||||
The name of the module to set the attribute in.
|
||||
module (`torch.nn.Module`)
|
||||
The module to set the attribute to.
|
||||
"""
|
||||
split_list = module_name.split('.')
|
||||
output = model
|
||||
for name in split_list[:-1]:
|
||||
output = getattr(output, name)
|
||||
output.__setattr__(split_list[-1], module)
|
||||
|
||||
|
||||
def concat_to_compat_param(quantized_weight: Tensor,
|
||||
quant_scale: Tensor,
|
||||
quant_min: Tensor,
|
||||
return_param: bool = True) -> Union[nn.Parameter, Tensor]:
|
||||
shape_wieght = quantized_weight.shape
|
||||
shape_scale = quant_scale.shape
|
||||
shape_min = quant_min.shape
|
||||
|
||||
quantized_weight = torch.flatten(quantized_weight)
|
||||
quant_scale = torch.flatten(quant_scale)
|
||||
quant_min = torch.flatten(quant_min)
|
||||
|
||||
def deconcat_individual_tensors(shape_wieght: torch.Size, shape_scale: torch.Size,
|
||||
shape_min: torch.Size) -> Callable:
|
||||
|
||||
def fn(compat_tensor: nn.Parameter) -> Tuple[Tensor, Tensor, Tensor]:
|
||||
weight = torch.narrow(compat_tensor, 0, 0, shape_wieght.numel()).view(shape_wieght)
|
||||
scale = torch.narrow(compat_tensor, 0, shape_wieght.numel(), shape_scale.numel()).view(shape_scale)
|
||||
min_val = torch.narrow(compat_tensor, 0,
|
||||
shape_wieght.numel() + shape_scale.numel(), shape_min.numel()).view(shape_min)
|
||||
|
||||
return weight, scale, min_val
|
||||
|
||||
return fn
|
||||
|
||||
compat_tensor = torch.concat([quantized_weight, quant_scale, quant_min])
|
||||
if return_param:
|
||||
compat_tensor = nn.Parameter(compat_tensor, requires_grad=False)
|
||||
compat_tensor.deconcat = deconcat_individual_tensors(shape_wieght, shape_scale, shape_min)
|
||||
|
||||
return compat_tensor
|
||||
|
||||
|
||||
def _quantize_param(param: nn.Parameter, quant_config: Dict):
|
||||
assert not hasattr(param, 'weight_quantized'), 'Parameter has already been quantized.'
|
||||
quantizer = Quantizer(quant_config)
|
||||
dequantizer = DeQuantizer(quant_config, param.dtype)
|
||||
|
||||
quantized_weight, quant_scale, quant_min = quantizer.quantize(param.data)
|
||||
|
||||
quantized_weight = quantized_weight.view(param.dtype)
|
||||
quant_scale = quant_scale.view(param.dtype)
|
||||
quant_min = quant_min.view(param.dtype)
|
||||
|
||||
quantized_compat_tensor = concat_to_compat_param(quantized_weight, quant_scale, quant_min)
|
||||
param.data = quantized_compat_tensor
|
||||
param.deconcat = quantized_compat_tensor.deconcat
|
||||
|
||||
param.quantizer = quantizer
|
||||
param.dequantizer = dequantizer
|
||||
setattr(param, 'weight_quantized', True)
|
||||
|
||||
|
||||
def wrap_quantized_functional(f):
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrapper(input: Tensor, weight: nn.Parameter, *args, **kwargs) -> Tensor:
|
||||
if hasattr(weight, 'weight_quantized') and getattr(weight, 'weight_quantized'):
|
||||
quantized_weight, quant_scale, quant_min = weight.deconcat(weight)
|
||||
temp_dequantized_weight = weight.dequantizer.dequantize(quantized_weight.view(torch.uint8), quant_scale,
|
||||
quant_min)
|
||||
return f(input, temp_dequantized_weight, *args, **kwargs)
|
||||
else:
|
||||
return f(input, weight, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def wrap_load_from_state_dict(f):
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrapper(model, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs):
|
||||
replaced_old_value = None
|
||||
key = None
|
||||
# We may have nested wrappers if we launch multiple initialization context.
|
||||
# Use state_dict_quantized flag to quantize state_dict only once
|
||||
if hasattr(model.weight, 'weight_quantized') and getattr(
|
||||
model.weight, 'weight_quantized') and not hasattr(model.weight, 'state_dict_quantized'):
|
||||
setattr(model.weight, 'state_dict_quantized', True)
|
||||
key = prefix + 'weight'
|
||||
if key in state_dict:
|
||||
quantized_weight, quant_scale, quant_min = model.weight.quantizer.quantize(state_dict[key])
|
||||
quantized_weight = quantized_weight.view(model.weight.dtype)
|
||||
quant_scale = quant_scale.view(model.weight.dtype)
|
||||
quant_min = quant_min.view(model.weight.dtype)
|
||||
|
||||
replaced_old_value = state_dict[key]
|
||||
|
||||
state_dict[key] = concat_to_compat_param(quantized_weight, quant_scale, quant_min)
|
||||
|
||||
f(model, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs)
|
||||
|
||||
if replaced_old_value is not None:
|
||||
state_dict[key] = replaced_old_value
|
||||
delattr(model.weight, 'state_dict_quantized')
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
WEIGHT_QUANTIZATION_LAYERS = (
|
||||
nn.Linear,
|
||||
nn.Embedding,
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
from .config_v2 import RaggedInferenceEngineConfig, DeepSpeedTPConfig
|
||||
from .engine_v2 import InferenceEngineV2
|
||||
from .engine_factory import build_hf_engine, build_engine_from_ds_checkpoint
|
||||
@@ -0,0 +1,42 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from functools import reduce
|
||||
from typing import Iterable
|
||||
from collections import defaultdict
|
||||
import torch
|
||||
|
||||
from deepspeed.accelerator import get_accelerator
|
||||
|
||||
|
||||
class Allocator:
|
||||
cache = defaultdict(dict)
|
||||
|
||||
def empty_from(tensor: torch.Tensor, shape: Iterable[int]) -> torch.Tensor:
|
||||
try:
|
||||
return Allocator.cache[tensor][shape]
|
||||
except KeyError:
|
||||
shape_size = reduce(lambda x, y: x * y, shape)
|
||||
if shape_size == 0:
|
||||
raise ValueError("Cannot create empty tensor with size 0")
|
||||
Allocator.cache[tensor][shape] = tensor.flatten()[:shape_size].view(shape)
|
||||
return Allocator.cache[tensor][shape]
|
||||
|
||||
|
||||
empty_from = Allocator.empty_from
|
||||
|
||||
|
||||
def on_device(method) -> torch.Tensor:
|
||||
"""
|
||||
Wraps a method to ensure the returned tensor is on the current device.
|
||||
"""
|
||||
|
||||
def wrapped(self, *args, **kwargs):
|
||||
tensor = method(self, *args, **kwargs)
|
||||
if isinstance(tensor, torch.Tensor):
|
||||
return tensor.to(get_accelerator().current_device())
|
||||
return tensor
|
||||
|
||||
return wrapped
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .base_engine import CheckpointEngineBase
|
||||
from .in_memory_engine import InMemoryModelEngine
|
||||
from .huggingface_engine import HuggingFaceCheckpointEngine
|
||||
@@ -0,0 +1,41 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Iterable, Tuple
|
||||
|
||||
import torch
|
||||
|
||||
#from .huggingface_engine import HuggingFaceCheckpointEngine
|
||||
|
||||
MEGATRON = 'megatron'
|
||||
HUGGINGFACE = 'huggingface'
|
||||
|
||||
|
||||
class CheckpointEngineBase(ABC):
|
||||
"""
|
||||
Abstract interface for checkpoint engines to implement.
|
||||
|
||||
There is no ``__init__`` method here by design, since the creation of the checkpoint
|
||||
engine will happen outside the policy/engine code. The tradeoff being made here is
|
||||
that we will write different frontends for different checkpoint engines, but these
|
||||
frontends can be tailored to the specific checkpoint engine/model source needs.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def parameters(self) -> Iterable[Tuple[str, torch.Tensor]]:
|
||||
"""
|
||||
This method should create a generator of tuples of the form (name, parameter) for
|
||||
all parameters in the model. The name should be the fully qualified name of the
|
||||
parameter, and the parameter should be a torch.Tensor.
|
||||
|
||||
The expected use of a checkpoint engine is the following:
|
||||
```python
|
||||
for name, parameter in checkpoint_engine.parameters():
|
||||
container_map.map_param(name, parameter)
|
||||
```
|
||||
For a concrete use example, see ``InferenceV2Policy``.
|
||||
"""
|
||||
...
|
||||
@@ -0,0 +1,130 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
from .base_engine import CheckpointEngineBase
|
||||
from typing import Iterable, Tuple
|
||||
from functools import partial
|
||||
|
||||
from ..logging import inference_logger
|
||||
|
||||
|
||||
class HuggingFaceCheckpointEngine(CheckpointEngineBase):
|
||||
|
||||
def __init__(self, model_name_or_path: str, auth_token: str = None, **hf_kwargs) -> None:
|
||||
super().__init__()
|
||||
from transformers import AutoConfig, GenerationConfig
|
||||
|
||||
self.model_name_or_path = model_name_or_path
|
||||
self.auth_token = auth_token
|
||||
self.model_config = AutoConfig.from_pretrained(self.model_name_or_path, **hf_kwargs)
|
||||
# Define this property here so we can use it in the model implementation
|
||||
if not hasattr(self.model_config, "max_seq_length"):
|
||||
if hasattr(self.model_config, "max_position_embeddings"):
|
||||
self.model_config.max_seq_length = self.model_config.max_position_embeddings
|
||||
else:
|
||||
generation_config = GenerationConfig.from_pretrained(self.model_name_or_path)
|
||||
self.model_config.max_seq_length = generation_config.max_length
|
||||
self._local_checkpoint_dir = None
|
||||
self._all_ckpt_paths = self._fetch_checkpoint_files()
|
||||
|
||||
def _fetch_checkpoint_files(self):
|
||||
"""
|
||||
Fetch the checkpoint files from the HuggingFace Hub.
|
||||
"""
|
||||
# TODO(jeff): for models like llama-2 the user will have to provide an auth `token`,
|
||||
# currently coming from the ckpt engine init but maybe a catch all kwargs for other
|
||||
# snapshot download parameters would be more flexible.
|
||||
|
||||
from huggingface_hub import snapshot_download, list_repo_tree
|
||||
|
||||
def model_has_safetensors(model_name_or_path: str) -> bool:
|
||||
if os.path.isdir(model_name_or_path):
|
||||
file_list = os.listdir(model_name_or_path)
|
||||
else:
|
||||
file_list = [rf.path for rf in list_repo_tree(model_name_or_path)]
|
||||
for f in file_list:
|
||||
if f.endswith(".safetensors"):
|
||||
return True
|
||||
return False
|
||||
|
||||
if os.path.isdir(self.model_name_or_path):
|
||||
self._local_checkpoint_dir = self.model_name_or_path
|
||||
else:
|
||||
# We need to download the checkpoint files from HF
|
||||
if model_has_safetensors(self.model_name_or_path):
|
||||
# Prioritize downloading safetensors if they are available
|
||||
allow_patterns = ["*.safetensors", "*.json"]
|
||||
else:
|
||||
# Fallback to bin files when safetensors are not present
|
||||
allow_patterns = ["*.bin", "*.json", "*.pt"]
|
||||
self._local_checkpoint_dir = snapshot_download(self.model_name_or_path,
|
||||
allow_patterns=allow_patterns,
|
||||
revision=None,
|
||||
token=self.auth_token)
|
||||
|
||||
assert os.path.isdir(
|
||||
self._local_checkpoint_dir
|
||||
), f"Checkpoint dir {self._local_checkpoint_dir} is not a directory, cannot load checkpoint."
|
||||
|
||||
# Set the appropriate file names based on whether we have safetensors or not
|
||||
if model_has_safetensors(self._local_checkpoint_dir):
|
||||
from safetensors.torch import load_file
|
||||
model_param_json_fname = "model.safetensors.index.json"
|
||||
model_file_fname = "model.safetensors"
|
||||
self._checkpoint_load_fn = load_file
|
||||
else:
|
||||
model_param_json_fname = "pytorch_model.bin.index.json"
|
||||
model_file_fname = "pytorch_model.bin"
|
||||
self._checkpoint_load_fn = partial(torch.load, map_location="cpu", weights_only=False)
|
||||
|
||||
model_param_json = os.path.join(self._local_checkpoint_dir, model_param_json_fname)
|
||||
|
||||
if not os.path.isfile(model_param_json):
|
||||
# We don't need any json as all such HF models will have pytorch_model.bin
|
||||
all_checkpoint_files = [os.path.join(self._local_checkpoint_dir, model_file_fname)]
|
||||
else:
|
||||
param_map = json.load(open(model_param_json, "r"))
|
||||
|
||||
# weight_map -> { "lm_head.weight": "pytorch_model-00002-of-00002.bin", ... }
|
||||
weight_map = param_map["weight_map"]
|
||||
|
||||
# unique set of all checkpoint files
|
||||
all_checkpoint_files = set(weight_map.values())
|
||||
|
||||
# get absolute path of all unique checkpoint files
|
||||
all_checkpoint_files = [os.path.join(self._local_checkpoint_dir, f) for f in all_checkpoint_files]
|
||||
|
||||
return all_checkpoint_files
|
||||
|
||||
def parameters(self) -> Iterable[Tuple[str, torch.Tensor]]:
|
||||
"""
|
||||
Generator of model parameters (satisfies the CheckpointEngineBase interface).
|
||||
"""
|
||||
for checkpoint in self._all_ckpt_paths:
|
||||
inference_logger().info(f"Loading checkpoint: {checkpoint}")
|
||||
checkpoint_sd = self._checkpoint_load_fn(checkpoint)
|
||||
|
||||
# If the model has tied embeddings, we need to make sure the lm_head weights are tied to the embeddings weights
|
||||
if hasattr(self.model_config, "tie_word_embeddings") and self.model_config.tie_word_embeddings:
|
||||
if self.model_config.model_type == "qwen2":
|
||||
checkpoint_sd["lm_head.weight"] = checkpoint_sd["model.embed_tokens.weight"]
|
||||
|
||||
param_keys = list(checkpoint_sd.keys())
|
||||
for param_name in param_keys:
|
||||
param = checkpoint_sd[param_name]
|
||||
yield param_name, param
|
||||
|
||||
del checkpoint_sd
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# To test, add your auth_token here and run `python huggingface_engine.py`
|
||||
engine = HuggingFaceCheckpointEngine(model_name_or_path="meta-llama/Llama-2-7b-hf",
|
||||
auth_token="hf_xxxxxxxxxxxxxxxxx")
|
||||
for name, param in engine.parameters():
|
||||
print(name, param.shape)
|
||||
@@ -0,0 +1,40 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from typing import Iterable, Tuple
|
||||
import torch
|
||||
|
||||
from .base_engine import CheckpointEngineBase
|
||||
|
||||
|
||||
class InMemoryModelEngine(CheckpointEngineBase):
|
||||
"""
|
||||
This "checkpoint" engine uses the existing interface to enable loading parameters into an
|
||||
inference model from a model already instantiated in memory. In general, this is not the
|
||||
recommended way to use the inference engine, and should only be used when absolutely necessary.
|
||||
|
||||
The primary limitation of this approach is that the model must be fully instantiated in memory.
|
||||
In a tensor parallel scenario, this means that the model is either replicated many times in host
|
||||
memory. Currently, it is also recommended to only use this approach for models held in host memory.
|
||||
|
||||
In order to free the memory held by this copy of the model, we delete the model in the first call
|
||||
to `parameters`, so it is not safe to make this call twice.
|
||||
"""
|
||||
|
||||
def __init__(self, model: torch.nn.Module) -> None:
|
||||
"""
|
||||
Create virtual checkpoint engine for the provided module.
|
||||
|
||||
Args:
|
||||
model (torch.nn.Module): Model to load parameters from.
|
||||
"""
|
||||
super().__init__()
|
||||
self.model = model
|
||||
|
||||
def parameters(self) -> Iterable[Tuple[str, torch.Tensor]]:
|
||||
for name, parameter in self.model.named_parameters():
|
||||
yield name, parameter
|
||||
|
||||
del self.model
|
||||
@@ -0,0 +1,44 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from pydantic import Field
|
||||
from typing import Optional
|
||||
|
||||
from deepspeed.runtime.config_utils import DeepSpeedConfigModel
|
||||
from .ragged import DSStateManagerConfig
|
||||
|
||||
|
||||
class DeepSpeedTPConfig(DeepSpeedConfigModel):
|
||||
""" Configure tensor parallelism settings """
|
||||
|
||||
tp_size: int = 1
|
||||
""" Number of devices to split the model across using tensor parallelism. """
|
||||
|
||||
|
||||
class QuantizationConfig(DeepSpeedConfigModel):
|
||||
""" Configure tensor parallelism settings """
|
||||
|
||||
quantization_mode: Optional[str] = None
|
||||
""" The quantization mode in string format. The supported modes are as follows:
|
||||
- 'wf6af16', weight-only quantization with FP6 weight and FP16 activation.
|
||||
"""
|
||||
# TODO: may reuse the constants in deepspeed/compression/constants.py
|
||||
|
||||
|
||||
class RaggedInferenceEngineConfig(DeepSpeedConfigModel):
|
||||
""" Sets parameters for DeepSpeed Inference Engine. """
|
||||
|
||||
tensor_parallel: DeepSpeedTPConfig = Field({}, alias="tp")
|
||||
"""
|
||||
Configuration for tensor parallelism used to split the model across several
|
||||
GPUs. Expects a dictionary containing values for :any:`DeepSpeedTPConfig`.
|
||||
"""
|
||||
|
||||
state_manager: DSStateManagerConfig = Field({}, alias="manager")
|
||||
"""
|
||||
Configuration for managing persistent state
|
||||
"""
|
||||
|
||||
quantization: QuantizationConfig = {}
|
||||
@@ -0,0 +1,141 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
from packaging import version
|
||||
|
||||
from .engine_v2 import InferenceEngineV2
|
||||
from .config_v2 import RaggedInferenceEngineConfig
|
||||
from .checkpoint import HuggingFaceCheckpointEngine
|
||||
from .logging import inference_logger
|
||||
from .model_implementations import (
|
||||
Exaone4Policy,
|
||||
OPTPolicy,
|
||||
Llama2Policy,
|
||||
MistralPolicy,
|
||||
MixtralPolicy,
|
||||
FalconPolicy,
|
||||
PhiPolicy,
|
||||
Phi3Policy,
|
||||
QwenPolicy,
|
||||
Qwen2Policy,
|
||||
Qwen2MoePolicy,
|
||||
)
|
||||
from .model_implementations.inference_policy_base import POLICIES, InferenceV2Policy
|
||||
from .model_implementations.flat_model_helpers import make_metadata_filename, ModelMetadata
|
||||
|
||||
|
||||
def build_engine_from_ds_checkpoint(path: str,
|
||||
engine_config: RaggedInferenceEngineConfig,
|
||||
debug_level: int = logging.INFO) -> InferenceEngineV2:
|
||||
"""
|
||||
Creates an engine from a checkpoint saved by ``InferenceEngineV2``.
|
||||
|
||||
Arguments:
|
||||
path: Path to the checkpoint. This does not need to point to any files in particular,
|
||||
just the directory containing the checkpoint.
|
||||
engine_config: Engine configuration. See ``RaggedInferenceEngineConfig`` for details.
|
||||
debug_level: Logging level to use. Unless you are actively seeing issues, the recommended
|
||||
value is ``logging.INFO``.
|
||||
|
||||
Returns:
|
||||
Fully initialized inference engine ready to serve queries.
|
||||
"""
|
||||
|
||||
inference_logger(level=debug_level)
|
||||
# Load metadata, for grabbing the policy name we'll have all ranks just check for
|
||||
# rank 0.
|
||||
metadata_filename = make_metadata_filename(path, 0, engine_config.tensor_parallel.tp_size)
|
||||
metadata = json.load(open(metadata_filename, "r"))
|
||||
metadata = ModelMetadata.parse_raw(metadata)
|
||||
|
||||
# Get the policy
|
||||
try:
|
||||
policy_cls: InferenceV2Policy = POLICIES[metadata.policy]
|
||||
except KeyError:
|
||||
raise ValueError(f"Unknown policy {metadata.policy} for model {path}")
|
||||
|
||||
# Load the model config
|
||||
model_config = pickle.load(open(os.path.join(path, "ds_model_config.pkl"), "rb"))
|
||||
policy = policy_cls(model_config, inf_checkpoint_path=path)
|
||||
|
||||
return InferenceEngineV2(policy, engine_config)
|
||||
|
||||
|
||||
def build_hf_engine(path: str,
|
||||
engine_config: RaggedInferenceEngineConfig,
|
||||
debug_level: int = logging.INFO) -> InferenceEngineV2:
|
||||
"""
|
||||
Build an InferenceV2 engine for HuggingFace models. This can accept both a HuggingFace
|
||||
model name or a path to an Inference-V2 checkpoint.
|
||||
|
||||
Arguments:
|
||||
path: Path to the checkpoint. This does not need to point to any files in particular,
|
||||
just the directory containing the checkpoint.
|
||||
engine_config: Engine configuration. See ``RaggedInferenceEngineConfig`` for details.
|
||||
debug_level: Logging level to use. Unless you are actively seeing issues, the recommended
|
||||
value is ``logging.INFO``.
|
||||
|
||||
Returns:
|
||||
Fully initialized inference engine ready to serve queries.
|
||||
"""
|
||||
|
||||
if os.path.exists(os.path.join(path, "ds_model_config.pkl")):
|
||||
return build_engine_from_ds_checkpoint(path, engine_config, debug_level=debug_level)
|
||||
else:
|
||||
# Set up logging
|
||||
inference_logger(level=debug_level)
|
||||
# get HF checkpoint engine
|
||||
checkpoint_engine = HuggingFaceCheckpointEngine(path)
|
||||
|
||||
# get model config from HF AutoConfig
|
||||
model_config = checkpoint_engine.model_config
|
||||
|
||||
# get the policy
|
||||
# TODO: generalize this to other models
|
||||
if model_config.model_type == "opt":
|
||||
if not model_config.do_layer_norm_before:
|
||||
raise ValueError(
|
||||
"Detected OPT-350m model. This model is not currently supported. If this is not the 350m model, please open an issue: https://github.com/deepspeedai/DeepSpeed-MII/issues"
|
||||
)
|
||||
policy = OPTPolicy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
elif model_config.model_type == "llama":
|
||||
policy = Llama2Policy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
elif model_config.model_type == "mistral":
|
||||
# Ensure we're using the correct version of transformers for mistral
|
||||
import transformers
|
||||
assert version.parse(transformers.__version__) >= version.parse("4.34.0"), \
|
||||
f"Mistral requires transformers >= 4.34.0, you have version {transformers.__version__}"
|
||||
policy = MistralPolicy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
elif model_config.model_type == "mixtral":
|
||||
# Ensure we're using the correct version of transformers for mistral
|
||||
import transformers
|
||||
assert version.parse(transformers.__version__) >= version.parse("4.36.1"), \
|
||||
f"Mistral requires transformers >= 4.36.1, you have version {transformers.__version__}"
|
||||
policy = MixtralPolicy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
elif model_config.model_type == "falcon":
|
||||
policy = FalconPolicy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
elif model_config.model_type == "phi":
|
||||
policy = PhiPolicy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
elif model_config.model_type == "phi3":
|
||||
policy = Phi3Policy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
elif model_config.model_type == "qwen":
|
||||
policy = QwenPolicy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
elif model_config.model_type == "qwen2":
|
||||
policy = Qwen2Policy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
elif model_config.model_type == "qwen2_moe":
|
||||
policy = Qwen2MoePolicy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
elif model_config.model_type == "exaone4":
|
||||
import transformers
|
||||
assert version.parse(transformers.__version__) >= version.parse("4.54.0"), \
|
||||
f"EXAONE 4.0 requires transformers >= 4.54.0, you have version {transformers.__version__}"
|
||||
policy = Exaone4Policy(model_config, checkpoint_engine=checkpoint_engine)
|
||||
else:
|
||||
raise ValueError(f"Unsupported model type {model_config.model_type}")
|
||||
|
||||
return InferenceEngineV2(policy, engine_config)
|
||||
@@ -0,0 +1,268 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import os
|
||||
import json
|
||||
import pickle
|
||||
from typing import Iterable, Tuple
|
||||
|
||||
import torch
|
||||
|
||||
import deepspeed.comm as dist
|
||||
|
||||
from deepspeed.accelerator import get_accelerator
|
||||
from deepspeed.comm.comm import init_distributed
|
||||
|
||||
from .model_implementations import InferenceV2Policy
|
||||
from .logging import inference_logger
|
||||
from .ragged import DSStateManager, RaggedBatchWrapper, PlaceholderSequenceDescriptor
|
||||
from .scheduling_utils import SchedulingError, SchedulingResult
|
||||
from .model_implementations.flat_model_helpers import make_param_filename, make_metadata_filename
|
||||
from .model_implementations.inference_model_base import DSInferenceModelBase
|
||||
|
||||
from .config_v2 import RaggedInferenceEngineConfig
|
||||
|
||||
INFERENCE_MODEL_TIMER = "model-forward-inference"
|
||||
|
||||
|
||||
class InferenceEngineV2:
|
||||
|
||||
_config: RaggedInferenceEngineConfig
|
||||
"""
|
||||
Configuration of the inference engine.
|
||||
"""
|
||||
|
||||
_model: DSInferenceModelBase
|
||||
"""
|
||||
Inference model supporting ragged inference.
|
||||
"""
|
||||
|
||||
_state_manager: DSStateManager
|
||||
"""
|
||||
Persistent state manager for sequences and KV-cache.
|
||||
"""
|
||||
|
||||
@property
|
||||
def free_blocks(self) -> torch.Tensor:
|
||||
"""
|
||||
Number of free KV blocks. This is a tensor of shape [n_kv_cache_groups] where each
|
||||
element is the number of free blocks in the corresponding KV cache group.
|
||||
"""
|
||||
return self._state_manager.free_blocks
|
||||
|
||||
@property
|
||||
def n_kv_cache_groups(self) -> int:
|
||||
"""
|
||||
Number of KV cache groups.
|
||||
"""
|
||||
return self._state_manager.n_kv_cache_groups
|
||||
|
||||
def model(self) -> DSInferenceModelBase:
|
||||
"""
|
||||
The model implementation.
|
||||
"""
|
||||
return self._model
|
||||
|
||||
def __init__(self, policy: InferenceV2Policy, engine_config: RaggedInferenceEngineConfig) -> None:
|
||||
"""
|
||||
Create the Inference V2 engine.
|
||||
|
||||
Arguments:
|
||||
policy (InferenceV2Policy): Policy for the model implementation. This policy object
|
||||
will be used to build the model and load the checkpoint associated with it.
|
||||
engine_config (RaggedInferenceEngineConfig): Configuration for the inference engine.
|
||||
"""
|
||||
self._config = engine_config
|
||||
self._policy = policy
|
||||
self._base_mp_group = self._initialize_tp_group()
|
||||
|
||||
# Build model from policy
|
||||
inference_logger().info("Building model...")
|
||||
self._model = self._policy.build_model(self._config, self._base_mp_group)
|
||||
inference_logger().info("Model built.")
|
||||
|
||||
# Create state manager
|
||||
self._batch = RaggedBatchWrapper(self._config.state_manager)
|
||||
self._state_manager = DSStateManager(self._config.state_manager,
|
||||
self._model.kv_cache_config(),
|
||||
base_mp_group=self._base_mp_group)
|
||||
self._model.set_state_manager(self._state_manager)
|
||||
|
||||
def _initialize_tp_group(self):
|
||||
"""
|
||||
Implementation of our TP group initialization.
|
||||
"""
|
||||
init_distributed()
|
||||
local_rank = int(os.getenv("LOCAL_RANK", 0))
|
||||
get_accelerator().set_device(local_rank)
|
||||
|
||||
if local_rank >= self._config.tensor_parallel.tp_size:
|
||||
raise RuntimeError("Local rank is greater than TP size, ensure that the TP config is correct.")
|
||||
|
||||
ranks = list(range(self._config.tensor_parallel.tp_size))
|
||||
return dist.new_group(ranks=ranks)
|
||||
|
||||
def put(self,
|
||||
batch_uids: Iterable[int],
|
||||
batch_tokens: Iterable[torch.Tensor],
|
||||
do_checks: bool = True) -> torch.Tensor:
|
||||
"""
|
||||
Put a ragged batch onto the inference engine. This will perform one forward and return
|
||||
a Tensor of the shape [len(batch_uids), *output_shape]. Logits for the non-final tokens
|
||||
are not calculated.
|
||||
|
||||
Arguments:
|
||||
batch_uids: Iterable of uids for the batch on the host
|
||||
batch_tokens: Iterable of token tensors for the batch on the host
|
||||
do_checks: Check schedulability when it is set to True. You can skip this check for better performance when it has already been completed.
|
||||
"""
|
||||
|
||||
if do_checks:
|
||||
token_lens = [len(tokens) for tokens in batch_tokens]
|
||||
schedule_check = self.can_schedule(batch_uids, token_lens)
|
||||
if schedule_check != SchedulingResult.Success:
|
||||
raise SchedulingError(schedule_check)
|
||||
|
||||
self._batch.clear()
|
||||
for uid, tokens in zip(batch_uids, batch_tokens):
|
||||
|
||||
host_seq_desc = self._state_manager.get_or_create_sequence(uid)
|
||||
self._model.maybe_allocate_kv(host_seq_desc, tokens.numel())
|
||||
host_seq_desc.pre_forward(tokens.numel())
|
||||
|
||||
# We can disable checks since we already validated schedulability.
|
||||
self._batch.insert_sequence(host_seq_desc, tokens, do_checks=do_checks)
|
||||
|
||||
# Send all metadata to the device
|
||||
self._batch.finalize()
|
||||
|
||||
# Prep all data structures for the actual forward (in anticipation of CG in the future)
|
||||
# and also to amortize some of the costs in a more straightforward way.
|
||||
self._model.prepare_batch(self._batch)
|
||||
|
||||
# Model implementation will pick up in the forward.
|
||||
logits = self._model.forward(self._batch)
|
||||
|
||||
# We return one set of logits per sequence in the batch (saves cost on unembedding)
|
||||
assert logits.shape[0] == self._batch.current_sequences
|
||||
|
||||
for uid in batch_uids:
|
||||
host_seq_desc = self._state_manager.get_sequence(uid)
|
||||
host_seq_desc.post_forward() # Updates sequence metadata.
|
||||
self._model.maybe_free_kv(host_seq_desc)
|
||||
|
||||
return logits
|
||||
|
||||
def query(self, uid: int, max_request_tokens: int, max_request_blocks) -> Tuple[int, torch.Tensor]:
|
||||
"""
|
||||
Determine the number of tokens and KV blocks to reserve for a given request. Given a UID
|
||||
(this UID may not be recognized by the model yet), this will return the number of tokens
|
||||
and blocks to reserve for the request.
|
||||
|
||||
Arguments:
|
||||
uid (int): The UID of the sequence (as tracked by the scheduling entity). If
|
||||
this is a new sequence (with a UID unknown to the inference engine), then
|
||||
an empty placeholder is created to pass to the occupancy logic.
|
||||
n_tokens (int): The number of tokens to hypothetically send.
|
||||
|
||||
Returns:
|
||||
Tuple[int, Optional[int]]: Tuple of free kv blocks and the number of blocks
|
||||
required to schedule the sequence.
|
||||
"""
|
||||
seq_desc = self._state_manager.get_sequence(uid)
|
||||
if seq_desc is None:
|
||||
if (self._state_manager.n_tracked_sequences == self._config.state_manager.max_tracked_sequences):
|
||||
return (0, 0)
|
||||
seq_desc = PlaceholderSequenceDescriptor()
|
||||
|
||||
req_tokens, req_blocks = self._model.get_kv_requirements(seq_desc, max_request_tokens, max_request_blocks)
|
||||
|
||||
return (req_tokens, req_blocks)
|
||||
|
||||
def can_schedule(self, uids: Iterable[int], lengths: Iterable[int]) -> SchedulingResult:
|
||||
"""
|
||||
Dry run a batch to determine if it can be scheduled. Placeholder sequences will be
|
||||
created for any UIDs that are unknown to the inference engine.
|
||||
|
||||
Arguments:
|
||||
uids (Iterable[int]): Iterable of UIDs for the batch
|
||||
lengths (Iterable[int]): Iterable of lengths for each sequence of the batch. This lengths
|
||||
corresponds to the number of tokens to send in the hypothetical forward; history
|
||||
tokens will be determined via UID lookup and future tokens are disregarded.
|
||||
|
||||
Returns:
|
||||
bool: True if the batch can be scheduled, False otherwise.
|
||||
"""
|
||||
|
||||
cur_seqs = self._state_manager.n_tracked_sequences
|
||||
free_blocks = self._state_manager.free_blocks
|
||||
req_blocks = 0
|
||||
batch_len = 0
|
||||
|
||||
if len(uids) > self._config.state_manager.max_ragged_sequence_count:
|
||||
# Can only compose a batch from a limited number of sequences
|
||||
return SchedulingResult.BatchSequenceLimitExceeded
|
||||
|
||||
for uid, length in zip(uids, lengths):
|
||||
seq_desc = self._state_manager.get_sequence(uid)
|
||||
if seq_desc is None:
|
||||
cur_seqs += 1
|
||||
seq_desc = PlaceholderSequenceDescriptor()
|
||||
|
||||
sched_len, sched_blocks = self._model.get_kv_requirements(seq_desc, length, free_blocks)
|
||||
|
||||
if sched_len != length:
|
||||
# We ran out of KV cache
|
||||
return SchedulingResult.KVCacheLimitExceeded
|
||||
|
||||
batch_len += length
|
||||
free_blocks -= sched_blocks
|
||||
|
||||
if cur_seqs > self._config.state_manager.max_tracked_sequences:
|
||||
# Would run out of tracking metadata
|
||||
return SchedulingResult.EngineSequenceLimitExceeded
|
||||
|
||||
if batch_len > self._config.state_manager.max_ragged_batch_size:
|
||||
# Would exceed the maximum batch size
|
||||
return SchedulingResult.BatchTokenLimitExceeded
|
||||
|
||||
return SchedulingResult.Success
|
||||
|
||||
def get_remaining_block_capacity(self, uid: int) -> int:
|
||||
"""
|
||||
Get the remaining capacity of the last block already allocated.
|
||||
"""
|
||||
seq_desc = self._state_manager.get_sequence(uid)
|
||||
if seq_desc is None:
|
||||
return 0
|
||||
return self._model.get_remaining_block_capacity(seq_desc)
|
||||
|
||||
def flush(self, uid: int) -> None:
|
||||
"""
|
||||
Remove all state associated with a sequence from the inference engine.
|
||||
|
||||
Arguments:
|
||||
uid (int): The UID of the sequence to flush.
|
||||
"""
|
||||
self._state_manager.flush_sequence(uid)
|
||||
|
||||
def serialize(self, save_path: str) -> None:
|
||||
"""
|
||||
Serialize the model to a file.
|
||||
|
||||
Arguments:
|
||||
path (str): Path to the file to serialize to.
|
||||
"""
|
||||
param_file_name = make_param_filename(save_path, self._model.tp_rank, self._model.tp_size)
|
||||
metadata_file_name = make_metadata_filename(save_path, self._model.tp_rank, self._model.tp_size)
|
||||
|
||||
# Save the flattened parameters
|
||||
|
||||
torch.save(self._model.flattened_params, param_file_name)
|
||||
|
||||
json.dump(self._model.flattened_param_metadata.json(), open(metadata_file_name, "w"))
|
||||
|
||||
if self._model.tp_rank == 0:
|
||||
pickle.dump(self._model._config, open(os.path.join(save_path, "ds_model_config.pkl"), "wb"))
|
||||
@@ -0,0 +1,89 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from typing import Dict
|
||||
|
||||
import torch
|
||||
|
||||
CORE_PARAM = "_ds_core_param_key"
|
||||
|
||||
STR_TO_DTYPE = {
|
||||
"torch.float32": torch.float32,
|
||||
"torch.float64": torch.float64,
|
||||
"torch.float16": torch.float16,
|
||||
"torch.bfloat16": torch.bfloat16,
|
||||
"torch.int64": torch.int64,
|
||||
"torch.int32": torch.int32,
|
||||
"torch.int16": torch.int16,
|
||||
"torch.int8": torch.int8,
|
||||
"torch.uint8": torch.uint8,
|
||||
"torch.bool": torch.bool,
|
||||
}
|
||||
|
||||
|
||||
class InferenceParameter(torch.Tensor):
|
||||
"""
|
||||
An extension of the torch.Tensor class to support our inference focused features. One important
|
||||
thing to note here is that an InferenceParam can be used a torch.Tensor, but outputs of
|
||||
torch.Tensor operations will not be InferenceParams.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def __new__(cls, tensor, *args, **kwargs):
|
||||
new_tensor = super().__new__(cls, tensor, *args, **kwargs)
|
||||
if hasattr(tensor, "_aux_attrs"):
|
||||
setattr(new_tensor, "_aux_attrs", tensor.aux_attrs)
|
||||
return new_tensor
|
||||
|
||||
def to(self, *args, **kwargs):
|
||||
new_tensor = super().to(*args, **kwargs)
|
||||
if hasattr(self, "_aux_attrs"):
|
||||
setattr(new_tensor, "_aux_attrs", self.aux_attrs)
|
||||
try:
|
||||
_ = torch.device(args[0])
|
||||
for name, attr in new_tensor.aux_attrs.items():
|
||||
new_attr = attr.to(*args, **kwargs)
|
||||
setattr(new_tensor, name, new_attr)
|
||||
new_tensor.aux_attrs[name] = new_attr
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return new_tensor
|
||||
|
||||
@classmethod
|
||||
def initialize(cls, core_param: torch.Tensor, **kwargs) -> 'InferenceParameter':
|
||||
"""
|
||||
Create the inference parameter.
|
||||
"""
|
||||
param = InferenceParameter(core_param)
|
||||
setattr(param, "_aux_attrs", kwargs)
|
||||
|
||||
for attr_name, attr in kwargs.items():
|
||||
if hasattr(param, attr_name):
|
||||
raise ValueError(f"Attribute {attr_name} already exists on param.")
|
||||
|
||||
if not isinstance(attr, torch.Tensor):
|
||||
raise ValueError(f"Attribute {attr_name} must be a tensor.")
|
||||
|
||||
setattr(param, attr_name, attr)
|
||||
|
||||
return param
|
||||
|
||||
@classmethod
|
||||
def initialize_raw(self, **kwargs) -> 'InferenceParameter':
|
||||
"""
|
||||
All kwargs must be torch.Tensors and must include the core parameter.
|
||||
"""
|
||||
if CORE_PARAM not in kwargs:
|
||||
raise ValueError(f"Must provide core parameter, with key {CORE_PARAM}.")
|
||||
|
||||
return InferenceParameter.initialize(kwargs[CORE_PARAM], **kwargs)
|
||||
|
||||
@property
|
||||
def aux_attrs(self) -> Dict[str, torch.Tensor]:
|
||||
"""
|
||||
Dictionary of auxiliary attributes.
|
||||
"""
|
||||
return self._aux_attrs
|
||||
@@ -0,0 +1,105 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from typing import Dict
|
||||
|
||||
import torch
|
||||
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
|
||||
class NormTypeEnum(Enum):
|
||||
LayerNorm: str = "layer_norm"
|
||||
RMSNorm: str = "rms_norm"
|
||||
|
||||
|
||||
class DtypeEnum(Enum):
|
||||
# The torch dtype must always be the first value (so we return torch.dtype)
|
||||
fp16 = torch.float16, "torch.float16", "fp16", "float16", "half"
|
||||
fp32 = torch.float32, "torch.float32", "fp32", "float32", "float"
|
||||
bf16 = torch.bfloat16, "torch.bfloat16", "bf16", "bfloat16", "bfloat"
|
||||
int8 = torch.int8, "torch.int8", "int8"
|
||||
|
||||
# Copied from https://stackoverflow.com/a/43210118
|
||||
# Allows us to use multiple values for each Enum index and returns first
|
||||
# listed value when Enum is called
|
||||
def __new__(cls, *values):
|
||||
obj = object.__new__(cls)
|
||||
# first value is canonical value
|
||||
obj._value_ = values[0]
|
||||
for other_value in values[1:]:
|
||||
cls._value2member_map_[other_value] = obj
|
||||
obj._all_values = values
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s.%s: %s>" % (
|
||||
self.__class__.__name__,
|
||||
self._name_,
|
||||
", ".join([repr(v) for v in self._all_values]),
|
||||
)
|
||||
|
||||
|
||||
ELEM_SIZES: Dict[torch.dtype, int] = {
|
||||
torch.float16: 2,
|
||||
torch.bfloat16: 2,
|
||||
torch.float32: 4,
|
||||
torch.float64: 8,
|
||||
torch.int8: 1,
|
||||
torch.uint8: 1,
|
||||
torch.int16: 2,
|
||||
torch.int32: 4,
|
||||
torch.int64: 8,
|
||||
torch.bool: 1,
|
||||
}
|
||||
|
||||
|
||||
class ActivationType(IntEnum):
|
||||
"""
|
||||
Types of activations supported by DS-Inference
|
||||
"""
|
||||
|
||||
GELU = 0
|
||||
|
||||
RELU = 1
|
||||
|
||||
SILU = 2
|
||||
|
||||
GEGLU = 3
|
||||
|
||||
ReGLU = 4
|
||||
|
||||
SiGLU = 5
|
||||
|
||||
IDENTITY = 6
|
||||
|
||||
InvalidType = -1
|
||||
|
||||
|
||||
def is_gated(act_fn: ActivationType) -> bool:
|
||||
"""
|
||||
Return True if the given activation function is gated.
|
||||
"""
|
||||
if not isinstance(act_fn, ActivationType):
|
||||
act_fn = ActivationType(act_fn)
|
||||
|
||||
return act_fn in [ActivationType.GEGLU, ActivationType.ReGLU, ActivationType.SiGLU]
|
||||
|
||||
|
||||
def elem_size(dtype: torch.dtype) -> int:
|
||||
"""
|
||||
Return size in bytes of the given dtype.
|
||||
"""
|
||||
try:
|
||||
return ELEM_SIZES[dtype]
|
||||
except KeyError:
|
||||
raise ValueError("Unknown dtype size for {}".format(dtype))
|
||||
|
||||
|
||||
def ceil_div(a: int, b: int) -> int:
|
||||
"""
|
||||
Return ceil(a / b).
|
||||
"""
|
||||
return -(-a // b)
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .ds_kernel import DSKernelBase
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .bias_activations import *
|
||||
from .blas_kernels import *
|
||||
from .cuda_layer_norm import *
|
||||
from .cuda_rms_norm import *
|
||||
from .gated_activations import *
|
||||
from .cuda_linear import *
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .bias_activation import *
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "bias_activation.h"
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include "ds_kernel_utils.h"
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
#define DTYPE_SWITCH(DTYPE, ...) \
|
||||
[&] { \
|
||||
if (DTYPE == torch::kFloat16) { \
|
||||
using scalar_t = __half; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kBFloat16) { \
|
||||
using scalar_t = __nv_bfloat16; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
TORCH_CHECK(false, "Unsupported dtype for BiasActivation"); \
|
||||
} \
|
||||
}()
|
||||
#else
|
||||
#define DTYPE_SWITCH(DTYPE, ...) \
|
||||
[&] { \
|
||||
if (DTYPE == torch::kFloat16) { \
|
||||
using scalar_t = __half; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
TORCH_CHECK(false, "Unsupported dtype for BiasActivation"); \
|
||||
} \
|
||||
}()
|
||||
#endif
|
||||
|
||||
/*
|
||||
In-place bias and activation fusion kernel.
|
||||
*/
|
||||
void bias_activation(torch::Tensor& activation,
|
||||
c10::optional<torch::Tensor>& bias,
|
||||
const int32_t act_type)
|
||||
{
|
||||
const ActivationType atype = static_cast<ActivationType>(act_type);
|
||||
const int32_t rows = activation.size(0);
|
||||
const int32_t cols = activation.size(1);
|
||||
|
||||
TORCH_CHECK(atype == ActivationType::GELU || atype == ActivationType::RELU ||
|
||||
atype == ActivationType::SILU || atype == ActivationType::IDENTITY,
|
||||
"Unsupported activation type for BiasActivation");
|
||||
TORCH_CHECK(activation.dim() == 2, "BiasActivation only supports 2D activation tensors");
|
||||
|
||||
DTYPE_SWITCH(activation.scalar_type(), [&] {
|
||||
scalar_t* activation_ptr = reinterpret_cast<scalar_t*>(activation.data_ptr());
|
||||
|
||||
const scalar_t* bias_ptr;
|
||||
if (bias.has_value()) {
|
||||
TORCH_CHECK(activation.scalar_type() == bias.value().scalar_type(),
|
||||
"BiasActivation activation and bias must have same dtype");
|
||||
bias_ptr = reinterpret_cast<const scalar_t*>(bias.value().data_ptr());
|
||||
} else {
|
||||
bias_ptr = nullptr;
|
||||
}
|
||||
|
||||
if (atype == ActivationType::IDENTITY && bias_ptr == nullptr) { return; }
|
||||
|
||||
launch_bias_activation<scalar_t>(
|
||||
activation_ptr, bias_ptr, rows, cols, atype, c10::cuda::getCurrentCUDAStream());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <torch/extension.h>
|
||||
#include "activation_type.h"
|
||||
|
||||
template <typename T>
|
||||
void launch_bias_activation(T* activation,
|
||||
const T* bias,
|
||||
const int32_t n_rows,
|
||||
const int32_t n_cols,
|
||||
const ActivationType activation_type,
|
||||
cudaStream_t stream);
|
||||
|
||||
void bias_activation(torch::Tensor& activation,
|
||||
c10::optional<torch::Tensor>& bias,
|
||||
const int32_t activation_type);
|
||||
@@ -0,0 +1,62 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
from ....inference_utils import ActivationType, DtypeEnum
|
||||
from deepspeed.ops.op_builder import InferenceCoreBuilder
|
||||
from ... import DSKernelBase
|
||||
|
||||
|
||||
class CUDABiasActivation(DSKernelBase):
|
||||
"""
|
||||
CUDA implementation of bias activation kernel. This kernel should be deprecated once
|
||||
we are fusing the bias activation into the linear kernel in all scenarios.
|
||||
"""
|
||||
|
||||
supported_dtypes = [DtypeEnum.fp16, DtypeEnum.bf16]
|
||||
supported_act_fns = [ActivationType.IDENTITY, ActivationType.GELU, ActivationType.RELU, ActivationType.SILU]
|
||||
|
||||
def __init__(self, channels: int, dtype: DtypeEnum, act_fn: ActivationType) -> None:
|
||||
"""
|
||||
Compile and validate for the fused bias-activation kernel.
|
||||
|
||||
Parameters:
|
||||
channels (int): Number of channels to expect in the activation.
|
||||
dtype (torch.dtype): Data type for the input/output. Supported values
|
||||
are DtypeEnum.fp16 and DtypeEnum.bf16.
|
||||
act_fn (ActivationType): Activation function to use. Only IDENTITY, GELU, RELU, and SILU are supported.
|
||||
"""
|
||||
|
||||
if channels % 8 != 0:
|
||||
raise ValueError("channels must be divisible by 8")
|
||||
|
||||
if DtypeEnum(dtype) not in CUDABiasActivation.supported_dtypes:
|
||||
raise ValueError("Unsupported data type: {}, supported_dtypes are {}".format(
|
||||
dtype, CUDABiasActivation.supported_dtypes))
|
||||
|
||||
act_fn = ActivationType(act_fn)
|
||||
if act_fn not in CUDABiasActivation.supported_act_fns:
|
||||
raise ValueError("Unsupported activation function: {}, supported_act_fns are {}".format(
|
||||
act_fn, CUDABiasActivation.supported_act_fns))
|
||||
|
||||
inf_module = InferenceCoreBuilder().load()
|
||||
self.kernel = inf_module.bias_activation
|
||||
self.act_fn = act_fn
|
||||
|
||||
def __call__(self, activation: torch.Tensor, bias: Optional[torch.Tensor] = None) -> torch.Tensor:
|
||||
"""
|
||||
Add an optional bias and perform the non-linear activation function.
|
||||
|
||||
Parameters:
|
||||
activation (torch.Tensor): Input tensor of shape [tokens, channels]
|
||||
bias (torch.Tensor): Optional bias tensor of shape [channels]
|
||||
|
||||
Returns:
|
||||
activation that has been updated in-place
|
||||
"""
|
||||
self.kernel(activation, bias, self.act_fn.value)
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include <cassert>
|
||||
#include "activation_type.h"
|
||||
#include "conversion_utils.h"
|
||||
#include "ds_kernel_utils.h"
|
||||
#include "memory_access_utils.h"
|
||||
|
||||
// Default activation function will error out
|
||||
template <ActivationType ActType>
|
||||
DS_D_INLINE float act_fn(float val);
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float act_fn<ActivationType::IDENTITY>(float val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float act_fn<ActivationType::RELU>(float val)
|
||||
{
|
||||
return val > 0.0f ? val : 0.0f;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float act_fn<ActivationType::GELU>(float val)
|
||||
{
|
||||
constexpr float sqrt_param = 0.79788456080286535587989211986876f;
|
||||
constexpr float mul_param = 0.044715f;
|
||||
return val * 0.5f * (1.0f + tanhf(sqrt_param * (val + mul_param * val * val * val)));
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float act_fn<ActivationType::SILU>(float val)
|
||||
{
|
||||
return val / (1.0f + expf(-val));
|
||||
}
|
||||
|
||||
namespace bias_act {
|
||||
|
||||
constexpr int access_size = 16;
|
||||
constexpr int threads = 512;
|
||||
constexpr int unroll = 4;
|
||||
|
||||
} // namespace bias_act
|
||||
|
||||
template <typename T, ActivationType ActType>
|
||||
__global__ void bias_activation_kernel(T* activation,
|
||||
const T* bias,
|
||||
const int32_t rows,
|
||||
const int32_t cols)
|
||||
{
|
||||
constexpr int vector_T = bias_act::access_size / sizeof(T);
|
||||
|
||||
const int32_t thread_offset = threadIdx.x * vector_T;
|
||||
const int32_t block_offset = blockIdx.x * vector_T * bias_act::unroll * bias_act::threads;
|
||||
const int32_t base_offset = block_offset + thread_offset;
|
||||
|
||||
const int32_t thread_stride = bias_act::threads * vector_T;
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < bias_act::unroll; i++) {
|
||||
const int32_t iter_offset = base_offset + i * thread_stride;
|
||||
|
||||
const int32_t row = iter_offset / cols;
|
||||
|
||||
T buffer[vector_T];
|
||||
T bias_buffer[vector_T];
|
||||
|
||||
if (row < rows) {
|
||||
const int32_t col = iter_offset % cols;
|
||||
|
||||
mem_access::load_global<bias_act::access_size>(buffer, activation + iter_offset);
|
||||
mem_access::load_global<bias_act::access_size>(
|
||||
bias_buffer, bias + col, bias != nullptr);
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < vector_T; j++) {
|
||||
float val =
|
||||
conversion::to<float>(buffer[j]) + conversion::to<float>(bias_buffer[j]);
|
||||
buffer[j] = conversion::to<T>(act_fn<ActType>(val));
|
||||
}
|
||||
|
||||
mem_access::store_global<bias_act::access_size>(activation + iter_offset, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define ACT_TYPE_SWITCH(ACT_TYPE, ...) \
|
||||
if (ACT_TYPE == ActivationType::IDENTITY) { \
|
||||
constexpr ActivationType act_fn_t = ActivationType::IDENTITY; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (ACT_TYPE == ActivationType::RELU) { \
|
||||
constexpr ActivationType act_fn_t = ActivationType::RELU; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (ACT_TYPE == ActivationType::GELU) { \
|
||||
constexpr ActivationType act_fn_t = ActivationType::GELU; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (ACT_TYPE == ActivationType::SILU) { \
|
||||
constexpr ActivationType act_fn_t = ActivationType::SILU; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
assert(false); \
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void launch_bias_activation(T* activation,
|
||||
const T* bias,
|
||||
const int32_t n_rows,
|
||||
const int32_t n_cols,
|
||||
const ActivationType activation_type,
|
||||
cudaStream_t stream)
|
||||
{
|
||||
constexpr int32_t elems_per_block =
|
||||
bias_act::threads * bias_act::unroll * bias_act::access_size / sizeof(T);
|
||||
const int32_t total_elems = n_rows * n_cols;
|
||||
|
||||
const int32_t blocks = (total_elems + elems_per_block - 1) / elems_per_block;
|
||||
|
||||
const dim3 grid(blocks);
|
||||
const dim3 block(bias_act::threads);
|
||||
|
||||
ACT_TYPE_SWITCH(activation_type, [&] {
|
||||
bias_activation_kernel<T, act_fn_t>
|
||||
<<<grid, block, 0, stream>>>(activation, bias, n_rows, n_cols);
|
||||
});
|
||||
}
|
||||
|
||||
#define INSTANTIATE_FOR_T(T) \
|
||||
template void launch_bias_activation<T>( \
|
||||
T*, const T*, const int32_t, const int32_t, const ActivationType, cudaStream_t);
|
||||
|
||||
INSTANTIATE_FOR_T(__half);
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
INSTANTIATE_FOR_T(__nv_bfloat16);
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .blas_linear import *
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <torch/extension.h>
|
||||
#include <cstdio>
|
||||
#include "blas_utils.h"
|
||||
|
||||
#define DISPATCH_BLAS_MATMUL(T_TYPE, C_TYPE) \
|
||||
if (output.options().dtype() == torch::T_TYPE) { \
|
||||
blas_gemm_ex(output.data_ptr(), \
|
||||
(const void*)weights.data_ptr(), \
|
||||
(const void*)hidden_states.data_ptr(), \
|
||||
m, \
|
||||
n, \
|
||||
k, \
|
||||
lda, \
|
||||
ldb, \
|
||||
ldc, \
|
||||
trans_a, \
|
||||
trans_b, \
|
||||
&alpha, \
|
||||
&beta, \
|
||||
C_TYPE); \
|
||||
}
|
||||
|
||||
void blas_linear(at::Tensor& output, at::Tensor& hidden_states, at::Tensor& weights)
|
||||
{
|
||||
/*
|
||||
Expected shape: output([total_tokens_across_dims], out_neurons)
|
||||
hidden_states([total_tokens_across_dims], in_neurons)
|
||||
weights(out_neurons, in_neurons)
|
||||
|
||||
We are going to assume contiguous for the above shapes.
|
||||
|
||||
The shapes are going to get messed with a little internally to handle column-major
|
||||
GEMMs.
|
||||
*/
|
||||
|
||||
// Number of tokens is N (since the GEMM output is column-major but our Tensor
|
||||
// is row-major, we need to transpose the shapes)
|
||||
const int n = output.numel() / output.size(-1);
|
||||
const int k = weights.size(1);
|
||||
const int m = weights.size(0);
|
||||
|
||||
// A strides
|
||||
const bool trans_a = weights.stride(1) == 1;
|
||||
const int lda = (trans_a) ? weights.stride(0) : weights.stride(1);
|
||||
|
||||
// B strides
|
||||
const bool trans_b = hidden_states.stride(-1) != 1;
|
||||
const int ldb = (trans_b) ? hidden_states.stride(-1) : hidden_states.stride(-2);
|
||||
|
||||
// C strides
|
||||
const int ldc = output.stride(-2);
|
||||
|
||||
const float alpha = 1.0f;
|
||||
const float beta = 0.0f;
|
||||
|
||||
TORCH_CHECK(output.scalar_type() == hidden_states.scalar_type(),
|
||||
"Output and hidden states must have the same scalar type");
|
||||
TORCH_CHECK(output.scalar_type() == weights.scalar_type(),
|
||||
"Output and weights must have the same scalar type");
|
||||
|
||||
// Dispatch the datatypes
|
||||
DISPATCH_BLAS_MATMUL(kFloat, BlasType::FP32);
|
||||
DISPATCH_BLAS_MATMUL(kHalf, BlasType::FP16);
|
||||
#ifdef BF16_AVAILABLE
|
||||
DISPATCH_BLAS_MATMUL(kBFloat16, BlasType::BF16);
|
||||
#endif
|
||||
}
|
||||
|
||||
#define DISPATCH_4D_BLAS(T_TYPE, C_TYPE) \
|
||||
if (C.options().dtype() == torch::T_TYPE) { \
|
||||
blas_strided_batched_gemm(C.data_ptr(), \
|
||||
(const void*)A.data_ptr(), \
|
||||
(const void*)B.data_ptr(), \
|
||||
m, \
|
||||
n, \
|
||||
k, \
|
||||
lda, \
|
||||
ldb, \
|
||||
ldc, \
|
||||
trans_a, \
|
||||
trans_b, \
|
||||
&alpha, \
|
||||
&beta, \
|
||||
stride_a, \
|
||||
stride_b, \
|
||||
stride_c, \
|
||||
batch, \
|
||||
C_TYPE); \
|
||||
}
|
||||
|
||||
void blas_4d_matmul(at::Tensor& C, at::Tensor& B, at::Tensor& A)
|
||||
{
|
||||
/*
|
||||
C shape: (batch_size, N, M)
|
||||
A shape: (batch_size, N, K)
|
||||
B shape: (batch_size, K, M)
|
||||
*/
|
||||
|
||||
const int n = C.size(-2);
|
||||
const int k = C.size(-1);
|
||||
const int m = B.size(-1);
|
||||
|
||||
// A strides
|
||||
const bool trans_a = A.stride(-1) == 1;
|
||||
const int lda = (trans_a) ? A.stride(-2) : A.stride(-1);
|
||||
const int stride_a = A.stride(-3);
|
||||
|
||||
// B strides
|
||||
const bool trans_b = B.stride(-1) != 1;
|
||||
const int ldb = (trans_b) ? B.stride(-1) : B.stride(-2);
|
||||
const int stride_b = B.stride(-3);
|
||||
|
||||
// C strides
|
||||
const int ldc = C.stride(-2);
|
||||
const int stride_c = C.stride(-3);
|
||||
|
||||
const float alpha = 1.0f;
|
||||
const float beta = 0.0f;
|
||||
|
||||
const int batch = C.numel() / (n * m);
|
||||
|
||||
// Dispatch the datatypes
|
||||
DISPATCH_4D_BLAS(kFloat, BlasType::FP32);
|
||||
DISPATCH_4D_BLAS(kHalf, BlasType::FP16);
|
||||
#ifdef BF16_AVAILABLE
|
||||
DISPATCH_4D_BLAS(kBFloat16, BlasType::BF16);
|
||||
#endif
|
||||
}
|
||||
|
||||
void create_handle() { BlasContext::getInstance().get_handle(); }
|
||||
@@ -0,0 +1,55 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from ....inference_utils import DtypeEnum
|
||||
from deepspeed.ops.op_builder import InferenceCoreBuilder
|
||||
from ... import DSKernelBase
|
||||
|
||||
|
||||
class BlasLibLinear(DSKernelBase):
|
||||
"""
|
||||
Wrapper around the BLAS matmul kernel for FP16/BF16/FP32 for CUDA/RoCM.
|
||||
|
||||
Performs z = x @ y
|
||||
"""
|
||||
|
||||
supported_dtypes = [DtypeEnum.fp16, DtypeEnum.bf16, DtypeEnum.fp32]
|
||||
|
||||
def __init__(self, fp_dtype: DtypeEnum):
|
||||
"""
|
||||
Parameters:
|
||||
fp_dtype (torch.dtype): Data type for the input/output. Supported values
|
||||
are torch.float16, torch.bfloat16, and torch.float32.
|
||||
"""
|
||||
fp_dtype = DtypeEnum(fp_dtype)
|
||||
if fp_dtype not in BlasLibLinear.supported_dtypes:
|
||||
raise ValueError("Unsupported data type: {}, supported_dtypes are {}".format(
|
||||
fp_dtype, BlasLibLinear.supported_dtypes))
|
||||
|
||||
self.inf_module = InferenceCoreBuilder().load()
|
||||
self.inf_module.create_handle()
|
||||
self.kernel = self.inf_module.blas_linear
|
||||
|
||||
def __call__(self, output: torch.Tensor, hidden_states: torch.Tensor, weights: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Matmul kernel as implemented by platform BLAS library. The input must be 2D or larger. If
|
||||
n-dimensional, the leading dimensions are folded into each other:
|
||||
2D: m = x.size(0)
|
||||
3D: m = x.size(0) * x.size(1)
|
||||
4D: m = x.size(0) * x.size(1) * x.size(2) (etc...)
|
||||
All inputs should be contiguous.
|
||||
|
||||
Parameters:
|
||||
output (torch.Tensor): Output tensor. Shape is of [*, out_features]
|
||||
hidden_states (torch.Tensor): Input tensor. Shape is of [*, in_features]
|
||||
weights (torch.Tensor): Input tensor. Shape is of [out_features, in_features]
|
||||
|
||||
Returns:
|
||||
z (torch.Tensor): Output tensor. Shape is of [m, n]
|
||||
"""
|
||||
self.kernel(output, hidden_states, weights)
|
||||
return output
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <cublas_v2.h>
|
||||
#include <cuda.h>
|
||||
#ifdef BF16_AVAILABLE
|
||||
#include <cuda_bf16.h>
|
||||
#endif
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_runtime.h>
|
||||
#ifndef __HIP_PLATFORM_AMD__
|
||||
#include <mma.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
class BlasContext {
|
||||
/*
|
||||
Slim wrapper for managing the lifetime of the platform's BLAS handle. This should
|
||||
be hipified for ROCm.
|
||||
*/
|
||||
public:
|
||||
BlasContext()
|
||||
{
|
||||
if (cublasCreate(&_handle) != CUBLAS_STATUS_SUCCESS) {
|
||||
auto message = std::string("Fail to create cublas handle.");
|
||||
std::cerr << message << std::endl;
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
#ifndef __HIP_PLATFORM_AMD__
|
||||
cublasSetMathMode(_handle, CUBLAS_TENSOR_OP_MATH);
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual ~BlasContext() { cublasDestroy(_handle); }
|
||||
|
||||
static BlasContext& getInstance()
|
||||
{
|
||||
// Should always access the singleton through this function.
|
||||
static BlasContext _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
cublasHandle_t get_handle() const { return _handle; }
|
||||
|
||||
private:
|
||||
cublasHandle_t _handle;
|
||||
};
|
||||
|
||||
enum class BlasType { FP32, FP16, BF16 };
|
||||
|
||||
// TODO HIP: Remove backward compatibility for torch<=2.0 in future
|
||||
#if defined(__HIP_PLATFORM_AMD__) && \
|
||||
((TORCH_VERSION_MAJOR < 2) || (TORCH_VERSION_MAJOR == 2 && TORCH_VERSION_MINOR == 0))
|
||||
rocblas_operation get_trans_op(bool do_trans)
|
||||
{
|
||||
return (do_trans) ? rocblas_operation_transpose : rocblas_operation_none;
|
||||
}
|
||||
|
||||
rocblas_datatype get_datatype(BlasType type)
|
||||
{
|
||||
switch (type) {
|
||||
case BlasType::FP32: return rocblas_datatype_f32_r;
|
||||
case BlasType::FP16: return rocblas_datatype_f16_r;
|
||||
case BlasType::BF16: return rocblas_datatype_bf16_r;
|
||||
default: throw std::runtime_error("Unsupported BlasType");
|
||||
}
|
||||
}
|
||||
#else
|
||||
cublasOperation_t get_trans_op(bool do_trans) { return (do_trans) ? CUBLAS_OP_T : CUBLAS_OP_N; }
|
||||
|
||||
cublasDataType_t get_datatype(BlasType type)
|
||||
{
|
||||
switch (type) {
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
case BlasType::FP32: return HIPBLAS_R_32F;
|
||||
case BlasType::FP16: return HIPBLAS_R_16F;
|
||||
case BlasType::BF16: return HIPBLAS_R_16B;
|
||||
#else
|
||||
case BlasType::FP32: return CUDA_R_32F;
|
||||
case BlasType::FP16: return CUDA_R_16F;
|
||||
case BlasType::BF16: return CUDA_R_16BF;
|
||||
#endif
|
||||
default: throw std::runtime_error("Unsupported BlasType");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int blas_gemm_ex(void* C,
|
||||
const void* A,
|
||||
const void* B,
|
||||
int m,
|
||||
int n,
|
||||
int k,
|
||||
int lda,
|
||||
int ldb,
|
||||
int ldc,
|
||||
bool transa,
|
||||
bool transb,
|
||||
const float* alpha,
|
||||
const float* beta,
|
||||
BlasType type)
|
||||
{
|
||||
#if defined(__HIP_PLATFORM_AMD__) && \
|
||||
((TORCH_VERSION_MAJOR < 2) || (TORCH_VERSION_MAJOR == 2 && TORCH_VERSION_MINOR == 0))
|
||||
rocblas_operation_t transa_op = get_trans_op(transa);
|
||||
rocblas_operation_t transb_op = get_trans_op(transb);
|
||||
|
||||
rocblas_datatype_t abc_type = get_datatype(type);
|
||||
|
||||
rocblas_status status = rocblas_gemm_ex(BlasContext::getInstance().get_handle(),
|
||||
transa_op,
|
||||
transb_op,
|
||||
m,
|
||||
n,
|
||||
k,
|
||||
(const void*)alpha,
|
||||
A,
|
||||
abc_type,
|
||||
lda,
|
||||
B,
|
||||
abc_type,
|
||||
ldb,
|
||||
(const void*)beta,
|
||||
C,
|
||||
abc_type,
|
||||
ldc,
|
||||
C,
|
||||
abc_type,
|
||||
ldc,
|
||||
rocblas_datatype_f32_r,
|
||||
rocblas_gemm_algo_standard,
|
||||
0,
|
||||
0);
|
||||
#else
|
||||
cublasOperation_t transa_op = get_trans_op(transa);
|
||||
cublasOperation_t transb_op = get_trans_op(transb);
|
||||
|
||||
cublasDataType_t abc_type = get_datatype(type);
|
||||
cublasStatus_t status = cublasGemmEx(BlasContext::getInstance().get_handle(),
|
||||
transa_op,
|
||||
transb_op,
|
||||
m,
|
||||
n,
|
||||
k,
|
||||
(const void*)alpha,
|
||||
A,
|
||||
abc_type,
|
||||
lda,
|
||||
B,
|
||||
abc_type,
|
||||
ldb,
|
||||
(const void*)beta,
|
||||
C,
|
||||
abc_type,
|
||||
ldc,
|
||||
#if defined(__HIP_PLATFORM_AMD__) && defined(HIPBLAS_V2)
|
||||
HIPBLAS_COMPUTE_32F,
|
||||
#elif defined(__HIP_PLATFORM_AMD__)
|
||||
HIPBLAS_R_32F,
|
||||
#else
|
||||
CUDA_R_32F,
|
||||
#endif
|
||||
CUBLAS_GEMM_DEFAULT_TENSOR_OP);
|
||||
#endif
|
||||
|
||||
#if defined(__HIP_PLATFORM_AMD__) && \
|
||||
((TORCH_VERSION_MAJOR < 2) || (TORCH_VERSION_MAJOR == 2 && TORCH_VERSION_MINOR == 0))
|
||||
if (status != rocblas_status_success) {
|
||||
#else
|
||||
if (status != CUBLAS_STATUS_SUCCESS) {
|
||||
#endif
|
||||
fprintf(stderr,
|
||||
"!!!! kernel execution error. (m: %d, n: %d, k: %d, error: %d) \n",
|
||||
m,
|
||||
n,
|
||||
k,
|
||||
(int)status);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int blas_strided_batched_gemm(void* C,
|
||||
const void* A,
|
||||
const void* B,
|
||||
int m,
|
||||
int n,
|
||||
int k,
|
||||
int lda,
|
||||
int ldb,
|
||||
int ldc,
|
||||
bool transa,
|
||||
bool transb,
|
||||
const float* alpha,
|
||||
const float* beta,
|
||||
int stride_A,
|
||||
int stride_B,
|
||||
int stride_C,
|
||||
int batch,
|
||||
BlasType type)
|
||||
{
|
||||
#if defined(__HIP_PLATFORM_AMD__) && \
|
||||
((TORCH_VERSION_MAJOR < 2) || (TORCH_VERSION_MAJOR == 2 && TORCH_VERSION_MINOR == 0))
|
||||
rocblas_operation_t transa_op = get_trans_op(transa);
|
||||
rocblas_operation_t transb_op = get_trans_op(transb);
|
||||
|
||||
rocblas_datatype_t abc_type = get_datatype(type);
|
||||
|
||||
rocblas_status status =
|
||||
rocblas_gemm_strided_batched_ex(BlasContext::getInstance()::get_handle(),
|
||||
transa_op,
|
||||
transb_op,
|
||||
m,
|
||||
n,
|
||||
k,
|
||||
(const void*)alpha,
|
||||
A,
|
||||
abc_type,
|
||||
lda,
|
||||
stride_A,
|
||||
B,
|
||||
abc_type,
|
||||
ldb,
|
||||
stride_B,
|
||||
(const void*)beta,
|
||||
C,
|
||||
abc_type,
|
||||
ldc,
|
||||
stride_C,
|
||||
C,
|
||||
abc_type,
|
||||
ldc,
|
||||
stride_C,
|
||||
batch,
|
||||
rocblas_datatype_f32_r,
|
||||
rocblas_gemm_algo_standard,
|
||||
0,
|
||||
0);
|
||||
#else
|
||||
cublasOperation_t transa_op = get_trans_op(transa);
|
||||
cublasOperation_t transb_op = get_trans_op(transb);
|
||||
|
||||
cublasDataType_t abc_type = get_datatype(type);
|
||||
|
||||
cublasStatus_t status = cublasGemmStridedBatchedEx(BlasContext::getInstance().get_handle(),
|
||||
transa_op,
|
||||
transb_op,
|
||||
m,
|
||||
n,
|
||||
k,
|
||||
(const void*)alpha,
|
||||
A,
|
||||
abc_type,
|
||||
lda,
|
||||
stride_A,
|
||||
B,
|
||||
abc_type,
|
||||
ldb,
|
||||
stride_B,
|
||||
(const void*)beta,
|
||||
C,
|
||||
abc_type,
|
||||
ldc,
|
||||
stride_C,
|
||||
batch,
|
||||
#if defined(__HIP_PLATFORM_AMD__) && defined(HIPBLAS_V2)
|
||||
HIPBLAS_COMPUTE_32F,
|
||||
#elif defined(__HIP_PLATFORM_AMD__)
|
||||
HIPBLAS_R_32F,
|
||||
#else
|
||||
CUDA_R_32F,
|
||||
#endif
|
||||
CUBLAS_GEMM_DEFAULT_TENSOR_OP);
|
||||
#endif
|
||||
|
||||
#if defined(__HIP_PLATFORM_AMD__) && \
|
||||
((TORCH_VERSION_MAJOR < 2) || (TORCH_VERSION_MAJOR == 2 && TORCH_VERSION_MINOR == 0))
|
||||
if (status != rocblas_status_success) {
|
||||
#else
|
||||
if (status != CUBLAS_STATUS_SUCCESS) {
|
||||
#endif
|
||||
fprintf(stderr,
|
||||
"!!!! kernel execution error. (batch: %d, m: %d, n: %d, k: %d, error: %d) \n",
|
||||
batch,
|
||||
m,
|
||||
n,
|
||||
k,
|
||||
(int)status);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <torch/extension.h>
|
||||
|
||||
#include "bias_activation.h"
|
||||
#include "blas.h"
|
||||
#include "gated_activation_kernels.h"
|
||||
#include "layer_norm.h"
|
||||
#include "linear_kernels.h"
|
||||
#include "rms_norm.h"
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m)
|
||||
{
|
||||
// bias_activation.h
|
||||
m.def("bias_activation", &bias_activation, "DeepSpeed bias activation in CUDA");
|
||||
|
||||
// layer_norm.h
|
||||
m.def("layer_norm", &ds_layer_norm, "DeepSpeed layer norm in CUDA");
|
||||
m.def("pre_layer_norm", &ds_pre_layer_norm, "DeepSpeed pre layer norm in CUDA");
|
||||
m.def("post_layer_norm", &ds_post_layer_norm, "DeepSpeed pre layer norm in CUDA");
|
||||
|
||||
// blas.h
|
||||
m.def("blas_linear", &blas_linear, "Linear implemented by vendor BLAS");
|
||||
m.def("blas_4d_matmul", &blas_4d_matmul, "4D matmul implemented by vendor BLAS");
|
||||
m.def("create_handle", &create_handle, "Create a handle for vendor BLAS");
|
||||
|
||||
// gated_activation_kernels.h
|
||||
m.def("gated_activation", &ds_gated_activation, "DeepSpeed gated activation in CUDA");
|
||||
|
||||
// rms_norm.h
|
||||
m.def("rms_norm", &rms_norm, "DeepSpeed rms norm in CUDA");
|
||||
m.def("rms_pre_norm", &rms_pre_norm, "DeepSpeed rms pre norm in CUDA");
|
||||
|
||||
// linear_kernels.h
|
||||
m.def("cuda_wf6af16_linear", &cuda_wf6af16_linear, "DeepSpeed Wf6Af16 linear in CUDA");
|
||||
m.def(
|
||||
"preprocess_weight", &preprocess_weight, "preprocess the FP16 weight to be 2bit and 4 bit");
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .cuda_ln import *
|
||||
from .cuda_post_ln import *
|
||||
from .cuda_pre_ln import *
|
||||
@@ -0,0 +1,37 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from ... import DSKernelBase
|
||||
from ....inference_utils import elem_size
|
||||
from deepspeed.ops.op_builder import InferenceCoreBuilder
|
||||
|
||||
|
||||
class CUDAFPLNBase(DSKernelBase):
|
||||
"""
|
||||
Base class for CUDA LN kernels. They all same the same validation logic,
|
||||
so we can share it here.
|
||||
"""
|
||||
|
||||
supported_dtypes = [torch.float16, torch.bfloat16, torch.float32]
|
||||
|
||||
def __init__(self, channels: int, fp_dtype: torch.dtype, epsilon: float = 1e-5):
|
||||
"""
|
||||
Parameters:
|
||||
channels (int): Number of channels in the input tensor. Must be divisible to align
|
||||
to 16 bytes.
|
||||
fp_dtype (torch.dtype): Data type for the input/output/gamma. Supported values
|
||||
are torch.float16, torch.bfloat16, and torch.float32.
|
||||
"""
|
||||
if fp_dtype not in CUDAFPLNBase.supported_dtypes:
|
||||
raise ValueError("Unsupported data type: {}, supported_dtypes are {}".format(
|
||||
fp_dtype, CUDAFPLNBase.supported_dtypes))
|
||||
|
||||
if elem_size(fp_dtype) * channels % 16 != 0:
|
||||
raise ValueError("channels must be divisible by 16 bytes")
|
||||
|
||||
self.inf_module = InferenceCoreBuilder().load()
|
||||
self.epsilon = epsilon
|
||||
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from .cuda_fp_ln_base import CUDAFPLNBase
|
||||
|
||||
|
||||
class CUDAFPLN(CUDAFPLNBase):
|
||||
"""
|
||||
Floating point layer norm kernel for CUDA/RoCM.
|
||||
|
||||
Performs: z = ln(x)
|
||||
"""
|
||||
|
||||
def __call__(self, output_z: torch.Tensor, input_x: torch.Tensor, gamma: torch.Tensor,
|
||||
beta: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
output_z may alias input_x directly. All Tensors should have the same shape.
|
||||
|
||||
Parameters:
|
||||
output_z (torch.Tensor): Output tensor.
|
||||
input_x (torch.Tensor): Input tensor.
|
||||
gamma (torch.Tensor): Gamma tensor.
|
||||
beta (torch.Tensor): Beta tensor.
|
||||
"""
|
||||
self.inf_module.layer_norm(output_z, input_x, gamma, beta, self.epsilon)
|
||||
return output_z
|
||||
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from .cuda_fp_ln_base import CUDAFPLNBase
|
||||
|
||||
|
||||
class CUDAFPPostLN(CUDAFPLNBase):
|
||||
"""
|
||||
Floating point post-LayerNorm kernel for CUDA/RoCM.
|
||||
|
||||
Performs: z = ln(x + y)
|
||||
"""
|
||||
|
||||
def __call__(self, output_z: torch.Tensor, input_x: torch.Tensor, input_y: torch.Tensor, gamma: torch.Tensor,
|
||||
beta: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Either input_x or input_y can alias output_z.
|
||||
|
||||
Parameters:
|
||||
output_z (torch.Tensor): Output tensor.
|
||||
input_x (torch.Tensor): Input tensor.
|
||||
input_y (torch.Tensor): Input tensor.
|
||||
gamma (torch.Tensor): Gamma tensor.
|
||||
beta (torch.Tensor): Beta tensor.
|
||||
|
||||
Returns:
|
||||
output (torch.Tensor): Output tensor.
|
||||
"""
|
||||
self.inf_module.post_layer_norm(output_z, input_x, input_y, gamma, beta, self.epsilon)
|
||||
return output_z
|
||||
@@ -0,0 +1,39 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
import torch
|
||||
|
||||
from .cuda_fp_ln_base import CUDAFPLNBase
|
||||
|
||||
|
||||
class CUDAFPPreLN(CUDAFPLNBase):
|
||||
"""
|
||||
Floating point pre-LayerNorm kernel for CUDA/RoCM.
|
||||
|
||||
Performs: z_res = x_res + y_hid
|
||||
z_hid = ln(z_hid)
|
||||
"""
|
||||
|
||||
def __call__(self, z_res: torch.Tensor, z_hid: torch.Tensor, x_res: torch.Tensor, y_hid: torch.Tensor,
|
||||
gamma: torch.Tensor, beta: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
z_res can alias x_res. All non-parameter input/output tensors
|
||||
must have the same shape. z_hid can alias y_hid.
|
||||
|
||||
Parameters:
|
||||
z_res (torch.Tensor): Output residual.
|
||||
z_hid (torch.Tensor): Output hidden states.
|
||||
x_res (torch.Tensor): Input residual.
|
||||
y_hid (torch.Tensor): Input hidden states.
|
||||
gamma (torch.Tensor): Gamma tensor.
|
||||
beta (torch.Tensor): Beta tensor.
|
||||
|
||||
Returns:
|
||||
output (torch.Tensor): Output tensor.
|
||||
"""
|
||||
self.inf_module.pre_layer_norm(z_res, z_hid, x_res, y_hid, gamma, beta, self.epsilon)
|
||||
return z_res, z_hid
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "layer_norm.h"
|
||||
|
||||
#define DISPATCH_LAYER_NORM(T_TYPE, C_TYPE) \
|
||||
if (input.options().dtype() == torch::T_TYPE) { \
|
||||
launch_fused_ln((C_TYPE*)output.data_ptr(), \
|
||||
(const C_TYPE*)input.data_ptr(), \
|
||||
(const C_TYPE*)gamma.data_ptr(), \
|
||||
(const C_TYPE*)beta.data_ptr(), \
|
||||
epsilon, \
|
||||
rows, \
|
||||
elems_per_row, \
|
||||
at::cuda::getCurrentCUDAStream()); \
|
||||
}
|
||||
|
||||
void ds_layer_norm(at::Tensor& output,
|
||||
at::Tensor& input,
|
||||
at::Tensor& gamma,
|
||||
at::Tensor& beta,
|
||||
float epsilon)
|
||||
{
|
||||
bool ragged_input = input.dim() == 2;
|
||||
|
||||
const int rows = ragged_input ? input.size(0) : input.size(0) * input.size(1);
|
||||
const int elems_per_row = ragged_input ? input.size(1) : input.size(2);
|
||||
|
||||
DISPATCH_LAYER_NORM(kFloat, float);
|
||||
DISPATCH_LAYER_NORM(kHalf, __half);
|
||||
#ifdef BF16_AVAILABLE
|
||||
DISPATCH_LAYER_NORM(kBFloat16, __nv_bfloat16);
|
||||
#endif
|
||||
}
|
||||
|
||||
#define DISPATCH_LAYER_NORM_RESIDUAL(T_TYPE, C_TYPE) \
|
||||
if (input.options().dtype() == torch::T_TYPE) { \
|
||||
launch_fused_post_ln((C_TYPE*)output.data_ptr(), \
|
||||
(const C_TYPE*)input.data_ptr(), \
|
||||
(const C_TYPE*)residual.data_ptr(), \
|
||||
(const C_TYPE*)gamma.data_ptr(), \
|
||||
(const C_TYPE*)beta.data_ptr(), \
|
||||
epsilon, \
|
||||
rows, \
|
||||
elems_per_row, \
|
||||
at::cuda::getCurrentCUDAStream()); \
|
||||
}
|
||||
|
||||
void ds_post_layer_norm(at::Tensor& output,
|
||||
at::Tensor& input,
|
||||
at::Tensor& residual,
|
||||
at::Tensor& gamma,
|
||||
at::Tensor& beta,
|
||||
float epsilon)
|
||||
{
|
||||
bool ragged_input = input.dim() == 2;
|
||||
|
||||
const int rows = ragged_input ? input.size(0) : input.size(0) * input.size(1);
|
||||
const int elems_per_row = ragged_input ? input.size(1) : input.size(2);
|
||||
|
||||
DISPATCH_LAYER_NORM_RESIDUAL(kFloat, float);
|
||||
DISPATCH_LAYER_NORM_RESIDUAL(kHalf, __half);
|
||||
#ifdef BF16_AVAILABLE
|
||||
DISPATCH_LAYER_NORM_RESIDUAL(kBFloat16, __nv_bfloat16);
|
||||
#endif
|
||||
}
|
||||
|
||||
#define DISPATCH_PRE_LAYER_NORM_RESIDUAL(T_TYPE, C_TYPE) \
|
||||
if (input.options().dtype() == torch::T_TYPE) { \
|
||||
launch_fused_pre_ln((C_TYPE*)norm_output.data_ptr(), \
|
||||
(C_TYPE*)res_output.data_ptr(), \
|
||||
(const C_TYPE*)input.data_ptr(), \
|
||||
(const C_TYPE*)residual.data_ptr(), \
|
||||
(const C_TYPE*)gamma.data_ptr(), \
|
||||
(const C_TYPE*)beta.data_ptr(), \
|
||||
epsilon, \
|
||||
rows, \
|
||||
elems_per_row, \
|
||||
at::cuda::getCurrentCUDAStream()); \
|
||||
}
|
||||
|
||||
void ds_pre_layer_norm(at::Tensor& res_output,
|
||||
at::Tensor& norm_output,
|
||||
at::Tensor& input,
|
||||
at::Tensor& residual,
|
||||
at::Tensor& gamma,
|
||||
at::Tensor& beta,
|
||||
float epsilon)
|
||||
{
|
||||
bool ragged_input = input.dim() == 2;
|
||||
|
||||
const int rows = ragged_input ? input.size(0) : input.size(0) * input.size(1);
|
||||
const int elems_per_row = ragged_input ? input.size(1) : input.size(2);
|
||||
|
||||
DISPATCH_PRE_LAYER_NORM_RESIDUAL(kFloat, float);
|
||||
DISPATCH_PRE_LAYER_NORM_RESIDUAL(kHalf, __half);
|
||||
#ifdef BF16_AVAILABLE
|
||||
DISPATCH_PRE_LAYER_NORM_RESIDUAL(kBFloat16, __nv_bfloat16);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <torch/extension.h>
|
||||
#include "ds_kernel_utils.h"
|
||||
|
||||
/*
|
||||
Kernel launch methods for layer norm variants.
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
void launch_fused_ln(T* output,
|
||||
const T* vals,
|
||||
const T* gamma,
|
||||
const T* beta,
|
||||
float epsilon,
|
||||
int rows,
|
||||
int elems_per_row,
|
||||
cudaStream_t stream);
|
||||
|
||||
template <typename T>
|
||||
void launch_fused_post_ln(T* output,
|
||||
const T* vals,
|
||||
const T* residual,
|
||||
const T* gamma,
|
||||
const T* beta,
|
||||
float epsilon,
|
||||
int rows,
|
||||
int elems_per_row,
|
||||
cudaStream_t stream);
|
||||
template <typename T>
|
||||
void launch_fused_pre_ln(T* norm_output,
|
||||
T* res_output,
|
||||
const T* vals,
|
||||
const T* residual,
|
||||
const T* gamma,
|
||||
const T* beta,
|
||||
float epsilon,
|
||||
int rows,
|
||||
int elems_per_row,
|
||||
cudaStream_t stream);
|
||||
|
||||
void ds_layer_norm(at::Tensor& output,
|
||||
at::Tensor& input,
|
||||
at::Tensor& gamma,
|
||||
at::Tensor& beta,
|
||||
float epsilon);
|
||||
|
||||
void ds_post_layer_norm(at::Tensor& output,
|
||||
at::Tensor& input,
|
||||
at::Tensor& residual,
|
||||
at::Tensor& gamma,
|
||||
at::Tensor& beta,
|
||||
float epsilon);
|
||||
|
||||
void ds_pre_layer_norm(at::Tensor& res_output,
|
||||
at::Tensor& norm_output,
|
||||
at::Tensor& input,
|
||||
at::Tensor& residual,
|
||||
at::Tensor& gamma,
|
||||
at::Tensor& beta,
|
||||
float epsilon);
|
||||
@@ -0,0 +1,489 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "conversion_utils.h"
|
||||
#include "ds_kernel_utils.h"
|
||||
#include "memory_access_utils.h"
|
||||
#include "reduction_utils.h"
|
||||
|
||||
namespace cg = cooperative_groups;
|
||||
using rop = reduce::ROpType;
|
||||
|
||||
namespace ln {
|
||||
constexpr int granularity = 16;
|
||||
} // namespace ln
|
||||
|
||||
/*
|
||||
Regular layer norm implementation. Assumes elems_per_row % 8
|
||||
is equal to 0.
|
||||
|
||||
Args:
|
||||
output: buffer for output data
|
||||
vals: buffer for input data
|
||||
gamma: gain for normalization
|
||||
beta: bias for normalization
|
||||
epsilon: numeric stability
|
||||
elems_per_row: number of elements each block will normalize
|
||||
*/
|
||||
template <typename T, int unRoll, int threadsPerGroup, int maxThreads>
|
||||
__global__ void fused_ln(T* output,
|
||||
const T* vals,
|
||||
const T* gamma,
|
||||
const T* beta,
|
||||
float epsilon,
|
||||
int elems_per_row)
|
||||
{
|
||||
constexpr int T_per_load = ln::granularity / sizeof(T);
|
||||
|
||||
cg::thread_block tb = cg::this_thread_block();
|
||||
cg::thread_block_tile<hw_warp_size> warp = cg::tiled_partition<hw_warp_size>(tb);
|
||||
|
||||
// X-dimension of the block
|
||||
const int block_offset = (tb.group_index().x * (maxThreads / threadsPerGroup) * elems_per_row) +
|
||||
(tb.thread_index().y * elems_per_row);
|
||||
const int thread_offset = tb.thread_index().x * T_per_load;
|
||||
const int base_offset = block_offset + thread_offset;
|
||||
const int stride = blockDim.x * T_per_load;
|
||||
|
||||
float sum = reduce::init<rop::Add, float>();
|
||||
|
||||
const T* input_base = vals + base_offset;
|
||||
|
||||
T local_buffer[unRoll * T_per_load];
|
||||
|
||||
#pragma unRoll
|
||||
for (int i = 0; i < unRoll; i++) {
|
||||
T* iteration_buffer = local_buffer + i * T_per_load;
|
||||
|
||||
mem_access::load_global<ln::granularity>(
|
||||
iteration_buffer, input_base + i * stride, thread_offset + i * stride < elems_per_row);
|
||||
|
||||
#pragma unRoll
|
||||
for (int j = 0; j < T_per_load; j++) {
|
||||
float vals_up_cast = conversion::to<float>(iteration_buffer[j]);
|
||||
sum = reduce::element<rop::Add>(sum, vals_up_cast);
|
||||
}
|
||||
}
|
||||
|
||||
reduce::partitioned_block<rop::Add, threadsPerGroup>(tb, warp, sum);
|
||||
const float mean = sum / elems_per_row;
|
||||
|
||||
float mean_diff = reduce::init<rop::Add, float>();
|
||||
|
||||
#pragma unRoll
|
||||
for (int i = 0; i < unRoll; i++) {
|
||||
#pragma unRoll
|
||||
for (int j = 0; j < T_per_load; j++) {
|
||||
// Using a 0 value here skews the variance, have to if-guard
|
||||
if (thread_offset + i * stride < elems_per_row) {
|
||||
float diff = (conversion::to<float>(local_buffer[i * T_per_load + j]) - mean);
|
||||
mean_diff = reduce::element<rop::Add>(mean_diff, diff * diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reduce::partitioned_block<rop::Add, threadsPerGroup>(tb, warp, mean_diff);
|
||||
const float variance = mean_diff / elems_per_row;
|
||||
const float denom = __frsqrt_rn(variance + epsilon);
|
||||
|
||||
T* block_output = output + block_offset;
|
||||
|
||||
#pragma unRoll
|
||||
for (int i = 0; i < unRoll; i++) {
|
||||
T* iteration_buffer = local_buffer + i * T_per_load;
|
||||
const int iter_idx = i * stride + thread_offset;
|
||||
const bool do_loads = iter_idx < elems_per_row;
|
||||
|
||||
T gamma_local[T_per_load], beta_local[T_per_load];
|
||||
|
||||
mem_access::load_global<ln::granularity>(gamma_local, gamma + iter_idx, do_loads);
|
||||
mem_access::load_global<ln::granularity>(beta_local, beta + iter_idx, do_loads);
|
||||
|
||||
#pragma unRoll
|
||||
for (int j = 0; j < T_per_load; j++) {
|
||||
float val = conversion::to<float>(iteration_buffer[j]);
|
||||
val = (val - mean) * denom;
|
||||
val =
|
||||
val * conversion::to<float>(gamma_local[j]) + conversion::to<float>(beta_local[j]);
|
||||
iteration_buffer[j] = conversion::to<T>(val);
|
||||
}
|
||||
|
||||
if (do_loads) {
|
||||
mem_access::store_global<ln::granularity>(block_output + iter_idx, iteration_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define LAUNCH_FUSED_LN(unRollFactor, threadsPerGroup, maxThreads) \
|
||||
fused_ln<T, unRollFactor, threadsPerGroup, maxThreads> \
|
||||
<<<grid, block, 0, stream>>>(output, vals, gamma, beta, epsilon, elems_per_row);
|
||||
|
||||
template <typename T>
|
||||
void launch_fused_ln(T* output,
|
||||
const T* vals,
|
||||
const T* gamma,
|
||||
const T* beta,
|
||||
float epsilon,
|
||||
int rows,
|
||||
int elems_per_row,
|
||||
cudaStream_t stream)
|
||||
{
|
||||
// 8 for __half, 4 for float
|
||||
constexpr int T_per_load = ln::granularity / sizeof(T);
|
||||
|
||||
constexpr int maxThreads = 256;
|
||||
|
||||
// For Flaoat, unRoll 4, for __half, unRoll 2
|
||||
constexpr int internal_unRoll = sizeof(T) == 4 ? 4 : 2;
|
||||
|
||||
const bool is_subblock_schedule = (elems_per_row <= 128) ? true : false;
|
||||
const int h_per_step = is_subblock_schedule ? T_per_load : T_per_load * internal_unRoll;
|
||||
|
||||
// Scheduling concern: may be slightly faster for some inputs to assign multiple stages of
|
||||
// warp-sized blocks rather than stepping up to 64/96 threads
|
||||
const int one_step_threads = next_pow2((elems_per_row + h_per_step - 1) / h_per_step);
|
||||
const int threadsPerGroup = (one_step_threads < maxThreads) ? one_step_threads : maxThreads;
|
||||
|
||||
const int groups_per_block_max =
|
||||
is_subblock_schedule ? (maxThreads + threadsPerGroup - 1) / threadsPerGroup : 1;
|
||||
const int groups_per_block = (rows < groups_per_block_max) ? rows : groups_per_block_max;
|
||||
const int groups_launch = (groups_per_block + rows - 1) / groups_per_block;
|
||||
|
||||
dim3 block(threadsPerGroup, groups_per_block);
|
||||
dim3 grid(groups_launch);
|
||||
|
||||
const int elems_per_step = threadsPerGroup * h_per_step;
|
||||
const int external_unRoll = (elems_per_row + elems_per_step - 1) / elems_per_step;
|
||||
|
||||
if (is_subblock_schedule) {
|
||||
// <=128
|
||||
if (threadsPerGroup == 1) {
|
||||
LAUNCH_FUSED_LN(1, 1, maxThreads);
|
||||
} else if (threadsPerGroup == 2) {
|
||||
LAUNCH_FUSED_LN(1, 2, maxThreads);
|
||||
} else if (threadsPerGroup == 4) {
|
||||
LAUNCH_FUSED_LN(1, 4, maxThreads);
|
||||
} else if (threadsPerGroup == 8) {
|
||||
LAUNCH_FUSED_LN(1, 8, maxThreads);
|
||||
} else if (threadsPerGroup == 16) {
|
||||
LAUNCH_FUSED_LN(1, 16, maxThreads);
|
||||
}
|
||||
} else if (external_unRoll == 1) {
|
||||
// 129 - 4096 elems
|
||||
// (this can launch with 1-7 warps as well)
|
||||
LAUNCH_FUSED_LN(1 * internal_unRoll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 2) {
|
||||
// 4097 - 8192 elems
|
||||
LAUNCH_FUSED_LN(2 * internal_unRoll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 3) {
|
||||
// 8193 - 12288 elems
|
||||
LAUNCH_FUSED_LN(3 * internal_unRoll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 4) {
|
||||
// 12289 - 16384 elems
|
||||
LAUNCH_FUSED_LN(4 * internal_unRoll, maxThreads, maxThreads);
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE_FUSED_LN(T) \
|
||||
template void launch_fused_ln(T*, const T*, const T*, const T*, float, int, int, cudaStream_t);
|
||||
|
||||
INSTANTIATE_FUSED_LN(__half);
|
||||
#ifdef BF16_AVAILABLE
|
||||
INSTANTIATE_FUSED_LN(__nv_bfloat16);
|
||||
#endif
|
||||
INSTANTIATE_FUSED_LN(float);
|
||||
|
||||
/*
|
||||
Fused resiual + bias + layer norm implementation. Assumes elems_per_row % 8
|
||||
is equal to 0.
|
||||
|
||||
TODO(cmikeh2): Goal is to deprecate this implementation. The bias + residual
|
||||
need to be fused into compute-bound producer operations.
|
||||
|
||||
Args:
|
||||
output: buffer for output data
|
||||
res_output: output of residual addition
|
||||
vals: buffer for input data
|
||||
residual: residual data
|
||||
bias: bias of of input data
|
||||
gamma: gain for normalization
|
||||
beta: bias for normalization
|
||||
epsilon: numeric stability
|
||||
elems_per_row: number of elements each block will normalize
|
||||
Template arg:
|
||||
StoreResidual: controls whether the residual calculation is stored
|
||||
or not. When set to false, the input `res_output` is unused.
|
||||
*/
|
||||
template <typename T, int unRoll, int threadsPerGroup, int maxThreads, bool preLnResidual>
|
||||
__global__ void fused_residual_ln(T* output,
|
||||
T* res_output,
|
||||
const T* vals,
|
||||
const T* residual,
|
||||
const T* gamma,
|
||||
const T* beta,
|
||||
float epsilon,
|
||||
int elems_per_row)
|
||||
{
|
||||
constexpr int T_per_load = ln::granularity / sizeof(T);
|
||||
|
||||
cg::thread_block tb = cg::this_thread_block();
|
||||
cg::thread_block_tile<hw_warp_size> warp = cg::tiled_partition<hw_warp_size>(tb);
|
||||
|
||||
// X-dimension of the block
|
||||
const int block_offset = (tb.group_index().x * (maxThreads / threadsPerGroup) * elems_per_row) +
|
||||
(tb.thread_index().y * elems_per_row);
|
||||
const int thread_offset = tb.thread_index().x * T_per_load;
|
||||
const int base_offset = block_offset + thread_offset;
|
||||
const int stride = tb.size() * T_per_load;
|
||||
|
||||
float sum = reduce::init<rop::Add, float>();
|
||||
|
||||
const T* input_base = vals + base_offset;
|
||||
const T* residual_base = residual + base_offset;
|
||||
|
||||
T local_buffer[unRoll * T_per_load];
|
||||
|
||||
// Unlike a vanilla layernorm, since we're fusing the two adds as well
|
||||
// an inner unRoll seems to be less valuable. If anything, a double unRoll
|
||||
// makes the most sense if we find we are having performance issues.
|
||||
#pragma unRoll
|
||||
for (int i = 0; i < unRoll; i++) {
|
||||
T* iteration_buffer = local_buffer + i * T_per_load;
|
||||
T residual_buffer[T_per_load];
|
||||
|
||||
mem_access::load_global<ln::granularity>(
|
||||
iteration_buffer, input_base + i * stride, thread_offset + i * stride < elems_per_row);
|
||||
mem_access::load_global<ln::granularity>(residual_buffer,
|
||||
residual_base + i * stride,
|
||||
thread_offset + i * stride < elems_per_row);
|
||||
|
||||
#pragma unRoll
|
||||
for (int j = 0; j < T_per_load; j++) {
|
||||
float vals_up_cast = conversion::to<float>(iteration_buffer[j]);
|
||||
float res_up_cast = conversion::to<float>(residual_buffer[j]);
|
||||
vals_up_cast += res_up_cast;
|
||||
sum = reduce::element<rop::Add>(sum, vals_up_cast);
|
||||
iteration_buffer[j] = conversion::to<T>(vals_up_cast);
|
||||
}
|
||||
|
||||
if (preLnResidual && (thread_offset + i * stride < elems_per_row)) {
|
||||
mem_access::store_global<ln::granularity>(res_output + base_offset + i * stride,
|
||||
iteration_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
reduce::partitioned_block<rop::Add, threadsPerGroup>(tb, warp, sum);
|
||||
const float mean = sum / elems_per_row;
|
||||
|
||||
float mean_diff = reduce::init<rop::Add, float>();
|
||||
#pragma unRoll
|
||||
for (int i = 0; i < unRoll; i++) {
|
||||
#pragma unRoll
|
||||
for (int j = 0; j < T_per_load; j++) {
|
||||
// Using a 0 value here skews the variance, have to if-guard
|
||||
if (thread_offset + i * stride < elems_per_row) {
|
||||
float diff = (conversion::to<float>(local_buffer[i * T_per_load + j]) - mean);
|
||||
mean_diff = reduce::element<rop::Add>(mean_diff, diff * diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reduce::partitioned_block<rop::Add, threadsPerGroup>(tb, warp, mean_diff);
|
||||
const float variance = mean_diff / elems_per_row;
|
||||
const float denom = __frsqrt_rn(variance + epsilon);
|
||||
|
||||
T* block_output = output + block_offset;
|
||||
|
||||
#pragma unRoll
|
||||
for (int i = 0; i < unRoll; i++) {
|
||||
T* iteration_buffer = local_buffer + i * T_per_load;
|
||||
const int iter_idx = i * stride + thread_offset;
|
||||
const bool do_loads = iter_idx < elems_per_row;
|
||||
|
||||
T gamma_local[T_per_load], beta_local[T_per_load];
|
||||
|
||||
mem_access::load_global<ln::granularity>(gamma_local, gamma + iter_idx, do_loads);
|
||||
mem_access::load_global<ln::granularity>(beta_local, beta + iter_idx, do_loads);
|
||||
|
||||
#pragma unRoll
|
||||
for (int j = 0; j < T_per_load; j++) {
|
||||
float val = conversion::to<float>(iteration_buffer[j]);
|
||||
val = (val - mean) * denom;
|
||||
val =
|
||||
val * conversion::to<float>(gamma_local[j]) + conversion::to<float>(beta_local[j]);
|
||||
iteration_buffer[j] = conversion::to<T>(val);
|
||||
}
|
||||
|
||||
if (do_loads) {
|
||||
mem_access::store_global<ln::granularity>(block_output + iter_idx, iteration_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(cmikeh2): There's a bunch of redundancy here that needs to be removed/simplified.
|
||||
#define LAUNCH_FUSED_RES_LN(unRollFactor, threadsPerGroup, maxThreads) \
|
||||
fused_residual_ln<T, unRollFactor, threadsPerGroup, maxThreads, false> \
|
||||
<<<grid, block, 0, stream>>>( \
|
||||
output, nullptr, vals, residual, gamma, beta, epsilon, elems_per_row);
|
||||
|
||||
template <typename T>
|
||||
void launch_fused_post_ln(T* output,
|
||||
const T* vals,
|
||||
const T* residual,
|
||||
const T* gamma,
|
||||
const T* beta,
|
||||
float epsilon,
|
||||
int rows,
|
||||
int elems_per_row,
|
||||
cudaStream_t stream)
|
||||
{
|
||||
// 8 for __half, 4 for float
|
||||
constexpr int T_per_load = ln::granularity / sizeof(T);
|
||||
|
||||
constexpr int maxThreads = 256;
|
||||
|
||||
// For Flaoat, unRoll 4, for __half, unRoll 2
|
||||
constexpr int internal_unRoll = sizeof(T) == 4 ? 4 : 2;
|
||||
|
||||
const bool is_subblock_schedule = (elems_per_row <= 128) ? true : false;
|
||||
const int h_per_step = is_subblock_schedule ? T_per_load : T_per_load * internal_unRoll;
|
||||
|
||||
// Scheduling concern: may be slightly faster for some inputs to assign multiple stages of
|
||||
// warp-sized blocks rather than stepping up to 64/96 threads
|
||||
const int one_step_threads = next_pow2((elems_per_row + h_per_step - 1) / h_per_step);
|
||||
const int threadsPerGroup = (one_step_threads < maxThreads) ? one_step_threads : maxThreads;
|
||||
|
||||
const int groups_per_block_max =
|
||||
is_subblock_schedule ? (maxThreads + threadsPerGroup - 1) / threadsPerGroup : 1;
|
||||
const int groups_per_block = (rows < groups_per_block_max) ? rows : groups_per_block_max;
|
||||
const int groups_launch = (groups_per_block + rows - 1) / groups_per_block;
|
||||
|
||||
dim3 block(threadsPerGroup, groups_per_block);
|
||||
dim3 grid(groups_launch);
|
||||
|
||||
const int elems_per_step = threadsPerGroup * h_per_step;
|
||||
const int external_unRoll = (elems_per_row + elems_per_step - 1) / elems_per_step;
|
||||
|
||||
if (is_subblock_schedule) {
|
||||
// <=128
|
||||
if (threadsPerGroup == 1) {
|
||||
LAUNCH_FUSED_RES_LN(1, 1, maxThreads);
|
||||
} else if (threadsPerGroup == 2) {
|
||||
LAUNCH_FUSED_RES_LN(1, 2, maxThreads);
|
||||
} else if (threadsPerGroup == 4) {
|
||||
LAUNCH_FUSED_RES_LN(1, 4, maxThreads);
|
||||
} else if (threadsPerGroup == 8) {
|
||||
LAUNCH_FUSED_RES_LN(1, 8, maxThreads);
|
||||
} else if (threadsPerGroup == 16) {
|
||||
LAUNCH_FUSED_RES_LN(1, 16, maxThreads);
|
||||
}
|
||||
} else if (external_unRoll == 1) {
|
||||
// 129 - 4096 elems
|
||||
// (this can launch with 1-7 warps as well)
|
||||
LAUNCH_FUSED_RES_LN(1 * internal_unRoll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 2) {
|
||||
// 4097 - 8192 elems
|
||||
LAUNCH_FUSED_RES_LN(2 * internal_unRoll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 3) {
|
||||
// 8193 - 12288 elems
|
||||
LAUNCH_FUSED_RES_LN(3 * internal_unRoll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 4) {
|
||||
// 12289 - 16384 elems
|
||||
LAUNCH_FUSED_RES_LN(4 * internal_unRoll, maxThreads, maxThreads);
|
||||
}
|
||||
}
|
||||
|
||||
#define LAUNCH_FUSED_RES_LN_STORE_PRE_LN_RES(unRollFactor, threadsPerGroup, maxThreads) \
|
||||
fused_residual_ln<T, unRollFactor, threadsPerGroup, maxThreads, true> \
|
||||
<<<grid, block, 0, stream>>>( \
|
||||
norm_output, res_output, vals, residual, gamma, beta, epsilon, elems_per_row);
|
||||
|
||||
template <typename T>
|
||||
void launch_fused_pre_ln(T* norm_output,
|
||||
T* res_output,
|
||||
const T* vals,
|
||||
const T* residual,
|
||||
const T* gamma,
|
||||
const T* beta,
|
||||
float epsilon,
|
||||
int rows,
|
||||
int elems_per_row,
|
||||
cudaStream_t stream)
|
||||
{
|
||||
// 8 for __half, 4 for float
|
||||
constexpr int T_per_load = ln::granularity / sizeof(T);
|
||||
|
||||
constexpr int maxThreads = 256;
|
||||
|
||||
// For Flaoat, unRoll 4, for __half, unRoll 2
|
||||
constexpr int internal_unRoll = sizeof(T) == 4 ? 4 : 2;
|
||||
|
||||
const bool is_subblock_schedule = (elems_per_row <= 128) ? true : false;
|
||||
const int h_per_step = is_subblock_schedule ? T_per_load : T_per_load * internal_unRoll;
|
||||
|
||||
// Scheduling concern: may be slightly faster for some inputs to assign multiple stages of
|
||||
// warp-sized blocks rather than stepping up to 64/96 threads
|
||||
const int one_step_threads = next_pow2((elems_per_row + h_per_step - 1) / h_per_step);
|
||||
const int threadsPerGroup = (one_step_threads < maxThreads) ? one_step_threads : maxThreads;
|
||||
|
||||
const int groups_per_block_max =
|
||||
is_subblock_schedule ? (maxThreads + threadsPerGroup - 1) / threadsPerGroup : 1;
|
||||
const int groups_per_block = (rows < groups_per_block_max) ? rows : groups_per_block_max;
|
||||
const int groups_launch = (groups_per_block + rows - 1) / groups_per_block;
|
||||
|
||||
dim3 block(threadsPerGroup, groups_per_block);
|
||||
dim3 grid(groups_launch);
|
||||
|
||||
const int elems_per_step = threadsPerGroup * h_per_step;
|
||||
const int external_unRoll = (elems_per_row + elems_per_step - 1) / elems_per_step;
|
||||
|
||||
if (is_subblock_schedule) {
|
||||
// <=128
|
||||
if (threadsPerGroup == 1) {
|
||||
LAUNCH_FUSED_RES_LN_STORE_PRE_LN_RES(1, 1, maxThreads);
|
||||
} else if (threadsPerGroup == 2) {
|
||||
LAUNCH_FUSED_RES_LN_STORE_PRE_LN_RES(1, 2, maxThreads);
|
||||
} else if (threadsPerGroup == 4) {
|
||||
LAUNCH_FUSED_RES_LN_STORE_PRE_LN_RES(1, 4, maxThreads);
|
||||
} else if (threadsPerGroup == 8) {
|
||||
LAUNCH_FUSED_RES_LN_STORE_PRE_LN_RES(1, 8, maxThreads);
|
||||
} else if (threadsPerGroup == 16) {
|
||||
LAUNCH_FUSED_RES_LN_STORE_PRE_LN_RES(1, 16, maxThreads);
|
||||
}
|
||||
} else if (external_unRoll == 1) {
|
||||
// 129 - 4096 elems
|
||||
// (this can launch with 1-7 warps as well)
|
||||
LAUNCH_FUSED_RES_LN_STORE_PRE_LN_RES(1 * internal_unRoll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 2) {
|
||||
// 4097 - 8192 elems
|
||||
LAUNCH_FUSED_RES_LN_STORE_PRE_LN_RES(2 * internal_unRoll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 3) {
|
||||
// 8193 - 12288 elems
|
||||
LAUNCH_FUSED_RES_LN_STORE_PRE_LN_RES(3 * internal_unRoll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 4) {
|
||||
// 12289 - 16384 elems
|
||||
LAUNCH_FUSED_RES_LN_STORE_PRE_LN_RES(4 * internal_unRoll, maxThreads, maxThreads);
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE_RES_LN(T) \
|
||||
template void launch_fused_post_ln<T>( \
|
||||
T*, const T*, const T*, const T*, const T*, float, int, int, cudaStream_t);
|
||||
|
||||
#define INSTANTIATE_PRE_LN_RES(T) \
|
||||
template void launch_fused_pre_ln<T>( \
|
||||
T*, T*, const T*, const T*, const T*, const T*, float, int, int, cudaStream_t);
|
||||
|
||||
INSTANTIATE_RES_LN(__half);
|
||||
INSTANTIATE_RES_LN(float);
|
||||
#ifdef BF16_AVAILABLE
|
||||
INSTANTIATE_RES_LN(__nv_bfloat16);
|
||||
#endif
|
||||
|
||||
INSTANTIATE_PRE_LN_RES(__half);
|
||||
INSTANTIATE_PRE_LN_RES(float);
|
||||
#ifdef BF16_AVAILABLE
|
||||
INSTANTIATE_PRE_LN_RES(__nv_bfloat16);
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .cuda_linear import *
|
||||
@@ -0,0 +1,207 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from ....inference_utils import DtypeEnum
|
||||
from ....logging import inference_logger
|
||||
from deepspeed.ops.op_builder import InferenceCoreBuilder
|
||||
from ... import DSKernelBase
|
||||
|
||||
|
||||
class CUDAWf6Af16Linear(DSKernelBase):
|
||||
"""
|
||||
Wrapper around the CUDA kernel of Wf6Af16 quantized linear.
|
||||
|
||||
Performs z = x @ y
|
||||
"""
|
||||
supported_dtypes = [DtypeEnum.fp16]
|
||||
|
||||
def __init__(self):
|
||||
self.inf_module = InferenceCoreBuilder().load()
|
||||
self.inf_module.create_handle()
|
||||
self.kernel = self.inf_module.cuda_wf6af16_linear
|
||||
# The split_k_map is profiled on A100-80G GPU for some common shapes.
|
||||
# It is an array of dictionaries, where the array index is the tokens chunk id.
|
||||
# The dictionary is the mapping from the output channel to the split-K size.
|
||||
self.split_k_map = [
|
||||
{ # tokens: [1, 64]
|
||||
3072: 18,
|
||||
4096: 13,
|
||||
5120: 10,
|
||||
6144: 9,
|
||||
8192: 6,
|
||||
10240: 5,
|
||||
14336: 7,
|
||||
28672: 7,
|
||||
57344: 7
|
||||
},
|
||||
{ # tokens: [65:128]
|
||||
3072: 9,
|
||||
4096: 6,
|
||||
5120: 5,
|
||||
6144: 9,
|
||||
8192: 3,
|
||||
10240: 5,
|
||||
14336: 7,
|
||||
28672: 7,
|
||||
57344: 6
|
||||
},
|
||||
{ # tokens: [129:192]
|
||||
3072: 6,
|
||||
4096: 4,
|
||||
5120: 7,
|
||||
6144: 3,
|
||||
8192: 2,
|
||||
10240: 5,
|
||||
14336: 5,
|
||||
28672: 5,
|
||||
57344: 4
|
||||
},
|
||||
{ # tokens: [193:256]
|
||||
3072: 9,
|
||||
4096: 3,
|
||||
5120: 5,
|
||||
6144: 2,
|
||||
8192: 5,
|
||||
10240: 4,
|
||||
14336: 8,
|
||||
28672: 6,
|
||||
57344: 4
|
||||
},
|
||||
{ # tokens: [257:320]
|
||||
3072: 7,
|
||||
4096: 5,
|
||||
5120: 2,
|
||||
6144: 5,
|
||||
8192: 4,
|
||||
10240: 1,
|
||||
14336: 3,
|
||||
28672: 3,
|
||||
57344: 4
|
||||
},
|
||||
{ # tokens: [321:384]
|
||||
3072: 3,
|
||||
4096: 2,
|
||||
5120: 5,
|
||||
6144: 3,
|
||||
8192: 1,
|
||||
10240: 8,
|
||||
14336: 3,
|
||||
28672: 4,
|
||||
57344: 3
|
||||
},
|
||||
{ # tokens: [385:448]
|
||||
3072: 5,
|
||||
4096: 7,
|
||||
5120: 3,
|
||||
6144: 5,
|
||||
8192: 7,
|
||||
10240: 3,
|
||||
14336: 1,
|
||||
28672: 1,
|
||||
57344: 3
|
||||
},
|
||||
{ # tokens: [449:512]
|
||||
3072: 2,
|
||||
4096: 5,
|
||||
5120: 4,
|
||||
6144: 1,
|
||||
8192: 5,
|
||||
10240: 2,
|
||||
14336: 6,
|
||||
28672: 4,
|
||||
57344: 1
|
||||
},
|
||||
{ # tokens: [513:576]
|
||||
3072: 2,
|
||||
4096: 3,
|
||||
5120: 1,
|
||||
6144: 1,
|
||||
8192: 3,
|
||||
10240: 3,
|
||||
14336: 3,
|
||||
28672: 1,
|
||||
57344: 1
|
||||
},
|
||||
{ # tokens: [577:640]
|
||||
3072: 5,
|
||||
4096: 4,
|
||||
5120: 1,
|
||||
6144: 4,
|
||||
8192: 2,
|
||||
10240: 1,
|
||||
14336: 1,
|
||||
28672: 1,
|
||||
57344: 1
|
||||
},
|
||||
{ # tokens: [641:704]
|
||||
3072: 3,
|
||||
4096: 1,
|
||||
5120: 2,
|
||||
6144: 2,
|
||||
8192: 1,
|
||||
10240: 2,
|
||||
14336: 1,
|
||||
28672: 1,
|
||||
57344: 1
|
||||
},
|
||||
{ # tokens: [705:768]
|
||||
3072: 3,
|
||||
4096: 1,
|
||||
5120: 3,
|
||||
6144: 2,
|
||||
8192: 1,
|
||||
10240: 1,
|
||||
14336: 1,
|
||||
28672: 1,
|
||||
57344: 1
|
||||
}
|
||||
]
|
||||
|
||||
def __call__(self, output: torch.Tensor, hidden_states: torch.Tensor, weights_2bit: torch.Tensor,
|
||||
weights_4bit: torch.Tensor, scale: torch.Tensor, out_channels, tokens, in_channels) -> torch.Tensor:
|
||||
"""
|
||||
Matmul kernel of FP6 weight-only quantized linear. All inputs should be contiguous.
|
||||
It does not support batched-matmul.
|
||||
|
||||
Parameters:
|
||||
output (torch.Tensor): Output tensor. Shape is of [token_number, out_features]
|
||||
hidden_states (torch.Tensor): Input tensor. Shape is of [token_number, in_features]
|
||||
weights_2bit (torch.Tensor): Input tensor of the 2-bit slice. Shape is of [out_features*2/8, in_features]
|
||||
weights_4bit (torch.Tensor): Input tensor of the 4-bit slice. Shape is of [out_features*4/8, in_features]
|
||||
scale (torch.Tensor): Input tensor. Shape is of [out_features], since the scale is per output channel
|
||||
out_channels (int): The number of output channels
|
||||
tokens (int): The number of tokens
|
||||
in_channels (int): The number of input channels
|
||||
"""
|
||||
|
||||
if out_channels % 256 != 0 or in_channels % 64 != 0:
|
||||
raise ValueError("The out and in channel should be multiple of 256 and 64 respectively.")
|
||||
|
||||
# TODO: add a more general heuristic to determine the split-K.
|
||||
split_k = -1 # not initialized
|
||||
if tokens <= 768:
|
||||
# Try to find the split-K from the pre-profiled map.
|
||||
tokens_chunk_id = (tokens - 1) // 64
|
||||
split_k = self.split_k_map[tokens_chunk_id].get(out_channels, -1)
|
||||
if split_k == -1:
|
||||
split_k = 1
|
||||
inference_logger().warning(
|
||||
f"The split-K setting may be suboptimal for shape {tokens}x{in_channels}x{out_channels}...")
|
||||
|
||||
workspace = self.get_workspace(out_channels, tokens, in_channels, split_k, torch.float, hidden_states.device)
|
||||
self.kernel(output, hidden_states, weights_2bit, weights_4bit, scale, workspace, out_channels, tokens,
|
||||
in_channels, split_k)
|
||||
|
||||
def get_workspace(self, out_channels: int, tokens: int, in_channels: int, split_k: int, dtype,
|
||||
device) -> torch.Tensor:
|
||||
"""
|
||||
Allocate workspace for the kernel. The workspace is used to store the intermediate results of the matmul before
|
||||
split-K. The split-K size is determined by the size of the matmul.
|
||||
"""
|
||||
workspace = torch.empty((split_k, out_channels, tokens), dtype=dtype, device=device)
|
||||
|
||||
return workspace
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
// This is a copy of FP6-LLM kernel code: https://arxiv.org/abs/2401.14112
|
||||
|
||||
#ifndef CONFIGS_H
|
||||
#define CONFIGS_H
|
||||
|
||||
// #define DEBUG_MODE
|
||||
#define PIPELINE_LEVEL_GMEM 2
|
||||
#define PIPELINE_LEVEL_SMEM 2 // only support 2
|
||||
|
||||
/************************ Hardware Parameters ************************/
|
||||
#define WARP_SIZE 32
|
||||
#define REG_BIT_WIDTH 32
|
||||
// mma: M=16 K=16 N=8
|
||||
#define MMA_8 8
|
||||
#define MMA_16 16
|
||||
// for memory access
|
||||
#define THREAD_OPT_ACCESS_BIT_WIDTH_128 128 // LDS.128, cp_async.128, ...
|
||||
#define BIT_WIDTH_PER_HALF 16 // Half precision: FP16
|
||||
|
||||
/******************** Register Allocation For GEMM ********************/
|
||||
#define REG_PER_THREAD_C_TENSOR_16_16 8 // 8 for FP32 Accumulation
|
||||
/********************** Memory Padding Parameters **********************/
|
||||
// Eliminating bank-conflict
|
||||
#define PADDING_BYTES_16 16 // Padding 16 bytes each column
|
||||
#define PADDING_SHARED_MEM_FOR_B_8 \
|
||||
8 // Padding 8 half each column, during CopyFromGlobalToShared() for B
|
||||
#define PADDING_SHARED_MEM_FOR_C_4 \
|
||||
4 // Padding 4 float each column, during StoreToSharedMemoryFromRegister() for C
|
||||
/************************* WARP Tiling part-1 *************************/
|
||||
#define WARP_ROW_MMA_TENSORS 4
|
||||
#define WARP_M (WARP_ROW_MMA_TENSORS * MMA_16) // 64
|
||||
#define WARP_K_MMA_TENSORS 4
|
||||
#define WARP_K (WARP_K_MMA_TENSORS * MMA_16) // 64
|
||||
template <int BLOCK_ROW_WARPS_, int BLOCK_COL_WARPS_, int WARP_COL_MMA_TENSORS_>
|
||||
struct TilingConfig {
|
||||
// Depending on "n" dimension of the GEMM
|
||||
static constexpr int BLOCK_ROW_WARPS = BLOCK_ROW_WARPS_;
|
||||
static constexpr int BLOCK_COL_WARPS = BLOCK_COL_WARPS_;
|
||||
static constexpr int WARP_COL_MMA_TENSORS = WARP_COL_MMA_TENSORS_;
|
||||
/************************* WARP Tiling part-2 *************************/
|
||||
static constexpr int WARP_N = WARP_COL_MMA_TENSORS * MMA_8;
|
||||
/*************************Thread Block Tiling *************************/
|
||||
static constexpr int TILE_M = WARP_M * BLOCK_ROW_WARPS;
|
||||
static constexpr int TILE_N = MMA_8 * WARP_COL_MMA_TENSORS * BLOCK_COL_WARPS;
|
||||
static constexpr int TILE_K = WARP_K;
|
||||
/********************** #Thread per Thread Block **********************/
|
||||
static constexpr int BLOCK_WARPS = BLOCK_ROW_WARPS * BLOCK_COL_WARPS;
|
||||
static constexpr int BLOCK_THREADS = BLOCK_WARPS * WARP_SIZE;
|
||||
/******************************* Others *******************************/
|
||||
static constexpr int SMEM_SIZE_B_TILE = TILE_N * (TILE_K + PADDING_BYTES_16) * 2 *
|
||||
PIPELINE_LEVEL_GMEM; // sizeof(half)=2, doubleBuffer=2
|
||||
static constexpr int SMEM_SIZE_C_TILE =
|
||||
TILE_N * (TILE_M + PADDING_BYTES_16) * 4; // sizeof(float)=4
|
||||
};
|
||||
|
||||
/************************ General Config for Quant-LLM **********************/
|
||||
#define WEIGHT_FRAG1_BIT_WIDTH 2
|
||||
#define WEIGHT_FRAG2_BIT_WIDTH 4
|
||||
#define WEIGHT_BIT_WIDTH (WEIGHT_FRAG1_BIT_WIDTH + WEIGHT_FRAG2_BIT_WIDTH) // 6
|
||||
// #define QUANT_GROUP_SIZE_DIVIDED_BY_64 4 //
|
||||
// QuantGroupSize: 4*64 = 256
|
||||
/*************************** 64*64 Weghts of A WARP *************************/
|
||||
#define WEIGHT_PER_UNIT (WARP_M * WARP_K) // 64*64
|
||||
#define SMEM_SIZE_IN_BYTES_PER_WARP_A1 \
|
||||
(WEIGHT_PER_UNIT * WEIGHT_FRAG1_BIT_WIDTH / \
|
||||
8) // 1024 Bytes #doubleBuffer not takedn into consideration
|
||||
#define SMEM_SIZE_IN_BYTES_PER_WARP_A2 \
|
||||
(WEIGHT_PER_UNIT * WEIGHT_FRAG2_BIT_WIDTH / \
|
||||
8) // 2048 Bytes #doubleBuffer not takedn into consideration
|
||||
#define SMEM_SIZE_A1_TILE \
|
||||
(SMEM_SIZE_IN_BYTES_PER_WARP_A1 * 4 * \
|
||||
PIPELINE_LEVEL_GMEM) // #WARP=4, #Trible-Buffer for 3-level pipeline for A = 12 KB; double
|
||||
// buffer for 2-level pipeline A= 8 KB.
|
||||
#define SMEM_SIZE_A2_TILE \
|
||||
(SMEM_SIZE_IN_BYTES_PER_WARP_A2 * 4 * \
|
||||
PIPELINE_LEVEL_GMEM) // #WARP=4, #Trible-Buffer for 3-level pipeline for A = 24 KB; double
|
||||
// buffer for 2-level pipeline A= 16 KB.
|
||||
/******************** Global Memory Layout For QUANTIZED DATA ******************/
|
||||
#define NUM_INT4_PER_UNIT_2BIT_FRAG (WEIGHT_PER_UNIT * WEIGHT_FRAG1_BIT_WIDTH / 128) // 64
|
||||
#define NUM_INT4_PER_UNIT_4BIT_FRAG (WEIGHT_PER_UNIT * WEIGHT_FRAG2_BIT_WIDTH / 128) // 128
|
||||
/******************** Register Allocation For QUANTIZED DATA ******************/
|
||||
#define WEIGHT_PER_THREAD (WEIGHT_PER_UNIT / WARP_SIZE) // 128
|
||||
#define REG_PER_THREAD_2BIT_FRAG (WEIGHT_PER_THREAD / REG_BIT_WIDTH * 2) // 8
|
||||
#define REG_PER_THREAD_4BIT_FRAG (WEIGHT_PER_THREAD / REG_BIT_WIDTH * 4) // 16
|
||||
/******************** Register Allocation For QUANT Scales ******************/
|
||||
#define WARP_REG_QUANT_SCALE 4 // 8 rows per thread -> 8 FP16 scales -> 4 registers
|
||||
#define WARP_REG_QUANT_SCALE_DISTRIBUTED \
|
||||
1 // T0-T3, T4-T7, ..., T28-T31 share the same scales, using shfl to get all the scales for
|
||||
// each thread
|
||||
|
||||
#endif // CONFIGS_H
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
// This is a copy of FP6-LLM kernel code: https://arxiv.org/abs/2401.14112
|
||||
|
||||
#ifndef DEEPSPEED_CUDA_LINEAR_WEIGHT_PREPACKING_H
|
||||
#define DEEPSPEED_CUDA_LINEAR_WEIGHT_PREPACKING_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
void Padding_8_FP6_To_8_Bytes(unsigned char Padded_FP6[],
|
||||
unsigned char* FP6_Array) // padding 0 to the lowerest bit location
|
||||
{
|
||||
Padded_FP6[0] = FP6_Array[0] & 0xfc;
|
||||
Padded_FP6[1] = (FP6_Array[0] << 6) | ((FP6_Array[1] >> 2) & 0xfc);
|
||||
Padded_FP6[2] = (FP6_Array[1] << 4) | ((FP6_Array[2] >> 4) & 0xfc);
|
||||
Padded_FP6[3] = FP6_Array[2] << 2;
|
||||
Padded_FP6[4] = FP6_Array[3] & 0xfc;
|
||||
Padded_FP6[5] = (FP6_Array[3] << 6) | ((FP6_Array[4] >> 2) & 0xfc);
|
||||
Padded_FP6[6] = (FP6_Array[4] << 4) | ((FP6_Array[5] >> 4) & 0xfc);
|
||||
Padded_FP6[7] = FP6_Array[5] << 2;
|
||||
}
|
||||
|
||||
unsigned char Extract_2_Bits_From_4_PaddedFP6(unsigned char B1,
|
||||
unsigned char B2,
|
||||
unsigned char B3,
|
||||
unsigned char B4)
|
||||
{
|
||||
unsigned char out;
|
||||
out = (B1 & 0xc0) | ((B2 & 0xc0) >> 2) | ((B3 & 0xc0) >> 4) | ((B4 & 0xc0) >> 6);
|
||||
return out;
|
||||
}
|
||||
|
||||
unsigned char Extract_4_Bits_From_2_PaddedFP6(
|
||||
unsigned char B1,
|
||||
unsigned char
|
||||
B2) // The highest two bits are already extracted by Extract_2_Bits_From_4_PaddedFP6();
|
||||
{
|
||||
unsigned char out;
|
||||
out = ((B1 << 2) & 0xf0) | ((B2 >> 2) & 0x0f);
|
||||
return out;
|
||||
}
|
||||
|
||||
// dealing with 4 1*8 blocks of FP6
|
||||
void Assign_32_FP6_To_4_Thread(std::vector<unsigned char> Seg_2bit[],
|
||||
std::vector<unsigned char> Seg_4bit[],
|
||||
unsigned char* PTR_1,
|
||||
unsigned char* PTR_2,
|
||||
unsigned char* PTR_3,
|
||||
unsigned char* PTR_4)
|
||||
{
|
||||
unsigned char Padded_8_FP8[4][8];
|
||||
Padding_8_FP6_To_8_Bytes(Padded_8_FP8[0], PTR_1);
|
||||
Padding_8_FP6_To_8_Bytes(Padded_8_FP8[1], PTR_2);
|
||||
Padding_8_FP6_To_8_Bytes(Padded_8_FP8[2], PTR_3);
|
||||
Padding_8_FP6_To_8_Bytes(Padded_8_FP8[3], PTR_4);
|
||||
//
|
||||
unsigned char Seg1_Byte1_T[4];
|
||||
unsigned char Seg1_Byte2_T[4];
|
||||
unsigned char Seg2_Byte1_T[4];
|
||||
unsigned char Seg2_Byte2_T[4];
|
||||
unsigned char Seg2_Byte3_T[4];
|
||||
unsigned char Seg2_Byte4_T[4];
|
||||
for (int t = 0; t < 4; t++) {
|
||||
Seg1_Byte1_T[t] = Extract_2_Bits_From_4_PaddedFP6(Padded_8_FP8[0][0 + t * 2],
|
||||
Padded_8_FP8[0][1 + t * 2],
|
||||
Padded_8_FP8[1][0 + t * 2],
|
||||
Padded_8_FP8[1][1 + t * 2]);
|
||||
Seg1_Byte2_T[t] = Extract_2_Bits_From_4_PaddedFP6(Padded_8_FP8[2][0 + t * 2],
|
||||
Padded_8_FP8[2][1 + t * 2],
|
||||
Padded_8_FP8[3][0 + t * 2],
|
||||
Padded_8_FP8[3][1 + t * 2]);
|
||||
Seg2_Byte1_T[t] =
|
||||
Extract_4_Bits_From_2_PaddedFP6(Padded_8_FP8[0][0 + t * 2], Padded_8_FP8[0][1 + t * 2]);
|
||||
Seg2_Byte2_T[t] =
|
||||
Extract_4_Bits_From_2_PaddedFP6(Padded_8_FP8[1][0 + t * 2], Padded_8_FP8[1][1 + t * 2]);
|
||||
Seg2_Byte3_T[t] =
|
||||
Extract_4_Bits_From_2_PaddedFP6(Padded_8_FP8[2][0 + t * 2], Padded_8_FP8[2][1 + t * 2]);
|
||||
Seg2_Byte4_T[t] =
|
||||
Extract_4_Bits_From_2_PaddedFP6(Padded_8_FP8[3][0 + t * 2], Padded_8_FP8[3][1 + t * 2]);
|
||||
}
|
||||
//
|
||||
for (int t = 0; t < 4; t++) {
|
||||
Seg_2bit[t].push_back(Seg1_Byte1_T[t]);
|
||||
Seg_2bit[t].push_back(Seg1_Byte2_T[t]);
|
||||
Seg_4bit[t].push_back(Seg2_Byte1_T[t]);
|
||||
Seg_4bit[t].push_back(Seg2_Byte2_T[t]);
|
||||
Seg_4bit[t].push_back(Seg2_Byte3_T[t]);
|
||||
Seg_4bit[t].push_back(Seg2_Byte4_T[t]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void BitInterleaving_2bit(unsigned char* PTR_4Bytes)
|
||||
{
|
||||
unsigned int* PTR_UINT = reinterpret_cast<unsigned int*>(PTR_4Bytes);
|
||||
unsigned int input = *PTR_UINT;
|
||||
//
|
||||
// int order_2bit[16] = {1,5,9,13,3,7,11,15,2,6,10,14,4,8,12,16}; // pre-defined order for
|
||||
// bit-interleaving in QuantLLM
|
||||
int order_2bit[16] = {
|
||||
2, 6, 10, 14, 4, 8, 12, 16, 1, 5, 9, 13, 3, 7, 11, 15}; // pre-defined order for
|
||||
// bit-interleaving in QuantLLM
|
||||
unsigned int Frags_2bit[16]; // The highest 2 bits are used to store the extracted fragments.
|
||||
for (int i = 0; i < 16; i++) Frags_2bit[i] = (input << 2 * (order_2bit[i] - 1)) & 0xc0000000;
|
||||
//
|
||||
unsigned int output = 0x00000000;
|
||||
for (int i = 0; i < 16; i++) output |= (Frags_2bit[i] >> (i * 2));
|
||||
//
|
||||
*PTR_UINT = output;
|
||||
}
|
||||
|
||||
void BitInterleaving_4bit(unsigned char* PTR_4Bytes)
|
||||
{
|
||||
unsigned int* PTR_UINT = reinterpret_cast<unsigned int*>(PTR_4Bytes);
|
||||
unsigned int input = *PTR_UINT;
|
||||
//
|
||||
// int order_4bit[8] = {1,5,3,7,2,6,4,8}; // pre-defined order for bit-interleaving in QuantLLM
|
||||
int order_4bit[8] = {
|
||||
2, 6, 4, 8, 1, 5, 3, 7}; // pre-defined order for bit-interleaving in QuantLLM
|
||||
unsigned int Frags_4bit[8]; // The highest4 bits are used to store the extracted fragments.
|
||||
for (int i = 0; i < 8; i++) Frags_4bit[i] = (input << 4 * (order_4bit[i] - 1)) & 0xf0000000;
|
||||
//
|
||||
unsigned int output = 0x00000000;
|
||||
for (int i = 0; i < 8; i++) output |= (Frags_4bit[i] >> (i * 4));
|
||||
//
|
||||
*PTR_UINT = output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inputs:
|
||||
* (1) unsigned char Weight_6bit [M*K*6/8]
|
||||
* Outputs:
|
||||
* (1) unsigned char Weight_2bit [M*K*2/8]
|
||||
* (2) unsigned char Weight_4bit [M*K*4/8]
|
||||
*
|
||||
* Assumption: Weight_6bit, Weight_2bit, Weight_4bit all stored continuously in row-major.
|
||||
* 8 FP6 = 6 Bytes
|
||||
* 8 FP4 = 4 Bytes
|
||||
* 8 FP2 = 2 Bytes
|
||||
*/
|
||||
void weight_matrix_prepacking(int* FP6Weights, size_t M, size_t K)
|
||||
{
|
||||
assert(M % 64 == 0);
|
||||
assert(K % 64 == 0);
|
||||
//
|
||||
unsigned char* Weight_6bit = reinterpret_cast<unsigned char*>(FP6Weights);
|
||||
unsigned char* Weight_2bit = Weight_6bit;
|
||||
unsigned char* Weight_4bit = Weight_6bit + M * K * 2 / 8;
|
||||
//
|
||||
std::vector<unsigned char> A_Segment_2bit[32];
|
||||
std::vector<unsigned char> A_Segment_4bit[32];
|
||||
//
|
||||
size_t BytesPerRow = K * 6 / 8;
|
||||
// Pass-1: (1) 2+4 split; (2) assign weights to 32 threads.
|
||||
for (size_t i = 0; i < M / 64; i++) //
|
||||
{
|
||||
for (size_t j = 0; j < K / 16; j++) {
|
||||
for (size_t k = 0; k < 64 / 16; k++) {
|
||||
size_t row = i * 64 + k * 16;
|
||||
size_t col = j * 16;
|
||||
unsigned char* StartPTR_1 = Weight_6bit + row * BytesPerRow + col * 6 / 8;
|
||||
unsigned char* StartPTR_2 = StartPTR_1 + 8 * BytesPerRow;
|
||||
unsigned char* StartPTR_3 = StartPTR_1 + 8 * 6 / 8;
|
||||
unsigned char* StartPTR_4 = StartPTR_2 + 8 * 6 / 8;
|
||||
// Dealing with each 16*16 blocks then...
|
||||
for (int l = 0; l < 8; l++)
|
||||
Assign_32_FP6_To_4_Thread(&A_Segment_2bit[l * 4],
|
||||
&A_Segment_4bit[l * 4],
|
||||
StartPTR_1 + l * BytesPerRow,
|
||||
StartPTR_2 + l * BytesPerRow,
|
||||
StartPTR_3 + l * BytesPerRow,
|
||||
StartPTR_4 + l * BytesPerRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Verifying the length of 2_bit segments and 4_bit segments
|
||||
size_t BytesPerThread_2bit = M * K * 2 / 8 / 32;
|
||||
size_t BytesPerThread_4bit = M * K * 4 / 8 / 32;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
assert(A_Segment_2bit[i].size() == BytesPerThread_2bit);
|
||||
assert(A_Segment_4bit[i].size() == BytesPerThread_4bit);
|
||||
}
|
||||
// Pass-2: Optimizing coleasced global memory access
|
||||
for (size_t i = 0; i < BytesPerThread_2bit / 4; i++)
|
||||
for (int t = 0; t < 32; t++)
|
||||
for (int b = 0; b < 4; b++)
|
||||
Weight_2bit[i * 128 + t * 4 + (3 - b)] =
|
||||
A_Segment_2bit[t]
|
||||
[i * 4 + b]; // why (3-b): special byte order within a register
|
||||
for (size_t i = 0; i < BytesPerThread_4bit / 4; i++)
|
||||
for (int t = 0; t < 32; t++)
|
||||
for (int b = 0; b < 4; b++)
|
||||
Weight_4bit[i * 128 + t * 4 + (3 - b)] =
|
||||
A_Segment_4bit[t][i * 4 + b]; // why (3-b):special byte order within a register
|
||||
// Pass-3: Bit-level interleaving
|
||||
for (size_t i = 0; i < BytesPerThread_2bit * 32 / 4; i++)
|
||||
BitInterleaving_2bit(Weight_2bit + 4 * i);
|
||||
for (size_t i = 0; i < BytesPerThread_4bit * 32 / 4; i++)
|
||||
BitInterleaving_4bit(Weight_4bit + 4 * i);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
|
||||
#include "linear_kernels.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// For bit-level debugging.
|
||||
template <typename T>
|
||||
void print_bits(T num)
|
||||
{
|
||||
char bits[sizeof(T) * 8 + 1] = {'\0'};
|
||||
for (int bit = 0; bit < (sizeof(T) * 8); bit++) {
|
||||
bits[sizeof(T) * 8 - 1 - bit] = '0' + (num & 0x01);
|
||||
num = num >> 1;
|
||||
}
|
||||
printf("%s\n", bits);
|
||||
}
|
||||
|
||||
void print_bits(half num)
|
||||
{
|
||||
char bits[sizeof(half) * 8 + 1] = {'\0'};
|
||||
auto int_num = *reinterpret_cast<uint16_t*>(&num);
|
||||
for (int bit = 0; bit < (sizeof(half) * 8); bit++) {
|
||||
bits[sizeof(half) * 8 - 1 - bit] = '0' + (int_num & 0x01);
|
||||
int_num = int_num >> 1;
|
||||
}
|
||||
printf("%s\n", bits);
|
||||
}
|
||||
|
||||
/*
|
||||
* Function to pack 4 fake quantized FP16 value into continuously stored 4 FP6 values.
|
||||
*/
|
||||
void cast_fp16_fp6(uint16_t* FP16x4, uint8_t* FP6x4)
|
||||
{
|
||||
// Constants for FP6
|
||||
constexpr int exponent_nbits_fp6 = 3;
|
||||
constexpr int mantissa_nbits_fp6 = 2;
|
||||
constexpr int exp_bias_fp6 = (1 << (exponent_nbits_fp6 - 1)) - 1;
|
||||
// Constants for FP16
|
||||
constexpr int exponent_nbits_fp16 = 5;
|
||||
constexpr int mantissa_nbits_fp16 = 10;
|
||||
constexpr int exp_bias_fp16 = (1 << (exponent_nbits_fp16 - 1)) - 1;
|
||||
|
||||
int fp6_temp[4];
|
||||
|
||||
float absmin_nonzero_fp6 = 0.0625;
|
||||
// Note that we regard the exponent of '111' as a regular value rather than NaN or inf. This is
|
||||
// the same with that in qtorch.
|
||||
float absmax_fp6 = 28;
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
uint16_t source = FP16x4[i];
|
||||
float fp6_value_abs = std::abs(__half2float(*((half*)(&source))));
|
||||
if ((fp6_value_abs != 0 && fp6_value_abs < absmin_nonzero_fp6) ||
|
||||
fp6_value_abs > absmax_fp6) {
|
||||
// TODO(zhen): a better way may be rounding it to the nearest FP6 value.
|
||||
throw std::invalid_argument("Input value out of range for FP6.");
|
||||
}
|
||||
|
||||
// It is not safe to do shift operation on uint16_t. So we promote it to int.
|
||||
int source_promote = int(source);
|
||||
|
||||
int sign_bit = (source_promote >> 15);
|
||||
// Extracting exponent represented in FP16. The sign mask 0x7FFF is '0111 1111 1111 1111'
|
||||
int exp_bit = (source_promote & 0x7FFF) >> mantissa_nbits_fp16;
|
||||
// Extracting mantissa represented in FP16
|
||||
int mant_bit = source_promote & ((1 << mantissa_nbits_fp16) - 1);
|
||||
|
||||
int new_exp_bit;
|
||||
int new_mant_bit;
|
||||
|
||||
if (exp_bit == 0) {
|
||||
// Subnormal FP16 number. Too small for FP6.
|
||||
new_exp_bit = 0;
|
||||
new_mant_bit = 0;
|
||||
} else {
|
||||
new_mant_bit = mant_bit >> (mantissa_nbits_fp16 - mantissa_nbits_fp6);
|
||||
new_exp_bit = exp_bit - exp_bias_fp16 + exp_bias_fp6;
|
||||
|
||||
// Deal with subnormal FP6 values.
|
||||
int target_exp_val = exp_bit - exp_bias_fp16;
|
||||
int min_fp6_exp_val = -exp_bias_fp6 + 1;
|
||||
bool subnormal_fp6 = target_exp_val < min_fp6_exp_val;
|
||||
if (subnormal_fp6) {
|
||||
// TODO(zhen): add the rounding logic.
|
||||
new_exp_bit = 0;
|
||||
// The implicit 1 in the mantissa of FP16 is not present in subnormal FP6. Thus we
|
||||
// need to add it
|
||||
new_mant_bit = (new_mant_bit | (1 << mantissa_nbits_fp6)) >>
|
||||
(min_fp6_exp_val - target_exp_val);
|
||||
}
|
||||
}
|
||||
|
||||
fp6_temp[i] = (sign_bit << (exponent_nbits_fp6 + mantissa_nbits_fp6)) |
|
||||
(new_exp_bit << mantissa_nbits_fp6) | new_mant_bit;
|
||||
}
|
||||
// Pack the values
|
||||
FP6x4[0] = fp6_temp[0] << 2 | (fp6_temp[1] >> 4);
|
||||
FP6x4[1] = (fp6_temp[1] & 0x0F) << 4 | (fp6_temp[2] >> 2);
|
||||
FP6x4[2] = (fp6_temp[2] & 0x03) << 6 | fp6_temp[3];
|
||||
}
|
||||
|
||||
/*
|
||||
* Function to prepack FP16 weights into continuous FP6 values.
|
||||
*
|
||||
* Parameters:
|
||||
* weight_16bit: input weight in FP16, size M*K
|
||||
* weight_6bit: output weight in packed FP6, continuously stored, size M*K*6/8
|
||||
* M, K: the shape of the weight
|
||||
*/
|
||||
void weight_prepacking_fp16_to_fp6(uint16_t* weight_16bit,
|
||||
uint8_t* weight_6bit_packed,
|
||||
size_t M,
|
||||
size_t K)
|
||||
{
|
||||
// Every four 16-bit elements are packed into three 6-bit values (4*6bit == 3*8bit).
|
||||
if (K * 6 % 8 != 0) { throw std::invalid_argument("(K * 6 % 8) should be 0"); }
|
||||
size_t K_fp6_packed = K * 6 / 8;
|
||||
// #pragma omp parallel for
|
||||
for (auto m = 0; m < M; m++) {
|
||||
uint8_t* ptr_6bit = weight_6bit_packed + m * K_fp6_packed;
|
||||
uint16_t* ptr_16bit = weight_16bit + m * K;
|
||||
for (auto k = 0; k < K; k += 4) {
|
||||
cast_fp16_fp6(ptr_16bit, ptr_6bit);
|
||||
ptr_16bit += 4;
|
||||
ptr_6bit += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/*
|
||||
* Function to execute the FP6 linear kernel.
|
||||
*
|
||||
* Parameters:
|
||||
* output: output tensor, size M*N
|
||||
* hidden_states: input activation tensor, size N*K
|
||||
* weights_2bit: packed 2bit weights, size M*K*2/8
|
||||
* weights_4bit: packed 4bit weights, size M*K*4/8
|
||||
* scales: scale tensor, size M
|
||||
* workspace: workspace tensor, size M*N*split_k
|
||||
* M: the output channel number of the weight
|
||||
* N: the token number of the activation
|
||||
* K: the input channel number of the weight
|
||||
* split_k: the split size of the GEMM calculation
|
||||
*/
|
||||
void cuda_wf6af16_linear(torch::Tensor& output,
|
||||
torch::Tensor& hidden_states,
|
||||
torch::Tensor& weights_2bit,
|
||||
torch::Tensor& weights_4bit,
|
||||
torch::Tensor& scales,
|
||||
torch::Tensor& workspace,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
int split_k)
|
||||
{
|
||||
TORCH_CHECK(weights_2bit.device().type() == torch::kCUDA, "weight_2bit must be on CUDA");
|
||||
TORCH_CHECK(weights_4bit.device().type() == torch::kCUDA, "weight_4bit must be on CUDA");
|
||||
TORCH_CHECK(hidden_states.device().type() == torch::kCUDA, "X must be on CUDA");
|
||||
TORCH_CHECK(scales.device().type() == torch::kCUDA, "scales must be on CUDA");
|
||||
|
||||
auto status = fp6_linear_kernel(at::cuda::getCurrentCUDAStream(),
|
||||
(uint4*)(weights_2bit.data_ptr<uint8_t>()),
|
||||
(uint4*)(weights_4bit.data_ptr<uint8_t>()),
|
||||
(half*)(scales.data_ptr<at::Half>()),
|
||||
(half*)(hidden_states.data_ptr<at::Half>()),
|
||||
(half*)(output.data_ptr<at::Half>()),
|
||||
M,
|
||||
N,
|
||||
K,
|
||||
workspace.data_ptr<float>(),
|
||||
split_k);
|
||||
if (status != cudaSuccess) {
|
||||
AT_ERROR("fp6_linear_kernel failed with error: ", cudaGetErrorString(status));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Function to prepack the fake 6-bit-quantized FP16 weights into 2bit and 4bit.
|
||||
*
|
||||
* Parameters:
|
||||
* weight: input weight in FP16 (containing the quantized FP6-ranged value), size M*K
|
||||
* Returns:
|
||||
* weight_2bit: output weight in 2bit, size M*K*2/8
|
||||
* weight_4bit: output weight in 4bit, size M*K*4/8
|
||||
*/
|
||||
std::vector<torch::Tensor> preprocess_weight(torch::Tensor& weight)
|
||||
{
|
||||
TORCH_CHECK(weight.dim() == 2, "weight must be 2-dimensional");
|
||||
TORCH_CHECK(weight.scalar_type() == torch::kFloat16, "weight must be FP16");
|
||||
TORCH_CHECK(weight.is_contiguous(), "weight must be contiguous");
|
||||
TORCH_CHECK(weight.device().type() == torch::kCPU, "weight must be on CPU");
|
||||
auto M = weight.size(0);
|
||||
auto K = weight.size(1);
|
||||
TORCH_CHECK(K % 4 == 0, "K must be multiple of 4");
|
||||
|
||||
// Pack weight from FP16 to FP6.
|
||||
uint16_t* weight_16bit_ptr = reinterpret_cast<uint16_t*>(weight.data_ptr<at::Half>());
|
||||
std::vector<uint8_t> weight_6bit_packed(M * K * 6 / 8);
|
||||
uint8_t* weight_6bit_ptr = weight_6bit_packed.data();
|
||||
weight_prepacking_fp16_to_fp6(weight_16bit_ptr, weight_6bit_ptr, M, K);
|
||||
|
||||
// Split weight into 2bit and 4bit.
|
||||
weight_matrix_prepacking(reinterpret_cast<int*>(weight_6bit_ptr), M, K);
|
||||
uint8_t* weight_2bit_ptr = weight_6bit_ptr;
|
||||
|
||||
// Make sure that the new split tensor does not share the underlying memory with the original
|
||||
// one. Otherwise it will incur some problems when the original tensor is deleted. It also
|
||||
// makes the memory flattern risky.
|
||||
auto weight_2bit =
|
||||
torch::from_blob(weight_2bit_ptr, {M * K * 2 / 8}, torch::kUInt8).clone().detach();
|
||||
uint8_t* weight_4bit_ptr = weight_2bit_ptr + M * K * 2 / 8;
|
||||
auto weight_4bit =
|
||||
torch::from_blob(weight_4bit_ptr, {M * K * 4 / 8}, torch::kUInt8).clone().detach();
|
||||
|
||||
return {weight_2bit, weight_4bit};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#ifndef DEEPSPEED_CUDA_LINEAR_KERNELS_H
|
||||
#define DEEPSPEED_CUDA_LINEAR_KERNELS_H
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <torch/extension.h>
|
||||
#include "ds_kernel_utils.h"
|
||||
|
||||
#include "linear_kernels_cuda.h"
|
||||
|
||||
void cuda_wf6af16_linear(torch::Tensor& output,
|
||||
torch::Tensor& hidden_states,
|
||||
torch::Tensor& weights_2bit,
|
||||
torch::Tensor& weights_4bit,
|
||||
torch::Tensor& scale,
|
||||
torch::Tensor& workspace,
|
||||
int M,
|
||||
int N,
|
||||
int K,
|
||||
int split_k);
|
||||
|
||||
std::vector<torch::Tensor> preprocess_weight(torch::Tensor& Weight);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,318 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
// This is a copy of FP6-LLM kernel code: https://arxiv.org/abs/2401.14112
|
||||
|
||||
// clang-format off
|
||||
// Put the torch headers at the front to avoid conflict with other headers on
|
||||
// `at::nullopt` and `at::optional`.
|
||||
#include <torch/extension.h>
|
||||
#include <ATen/ATen.h>
|
||||
// clang-format on
|
||||
|
||||
#include "include/kernel_matmul.cuh"
|
||||
#include "include/kernel_reduction.cuh"
|
||||
#include "include/weight_prepacking.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "linear_kernels_cuda.h"
|
||||
|
||||
template <typename TilingConfig, typename OutputDataType>
|
||||
static void Kernel_Ex(cudaStream_t stream,
|
||||
const uint4* Weight1,
|
||||
const uint4* Weight2,
|
||||
const half* Scales,
|
||||
const half* B,
|
||||
OutputDataType* C,
|
||||
const size_t M_Global,
|
||||
const size_t N_Global,
|
||||
const size_t K_Global,
|
||||
int Split_K)
|
||||
{
|
||||
#ifdef DEBUG_MODE
|
||||
printf("\n");
|
||||
printf("Launcher.cu->Kernel_Ex():\n");
|
||||
printf("M: %d, N: %d, K: %d, SplitK: %d\n", M_Global, N_Global, K_Global, Split_K);
|
||||
printf("TILE_M: %d, TILE_K: %d, TILE_N: %d\n",
|
||||
TilingConfig::TILE_M,
|
||||
TilingConfig::TILE_K,
|
||||
TilingConfig::TILE_N);
|
||||
#endif
|
||||
static size_t SHMEM_SZ =
|
||||
max(TilingConfig::SMEM_SIZE_B_TILE + SMEM_SIZE_A1_TILE + SMEM_SIZE_A2_TILE,
|
||||
TilingConfig::SMEM_SIZE_C_TILE);
|
||||
cudaFuncSetAttribute(QUANT_GEMM_Kernel<TilingConfig, OutputDataType>,
|
||||
cudaFuncAttributeMaxDynamicSharedMemorySize,
|
||||
SHMEM_SZ);
|
||||
size_t dimN = (N_Global - 1) / TilingConfig::TILE_N + 1;
|
||||
size_t dimM = M_Global * Split_K / TilingConfig::TILE_M;
|
||||
dim3 GridDim(dimN, dimM, 1);
|
||||
dim3 BlockDim(WARP_SIZE * TilingConfig::BLOCK_WARPS, 1, 1);
|
||||
|
||||
#ifdef DEBUG_MODE
|
||||
printf(
|
||||
"GridDim.x: %d, GridDim.y: %d, GridDim.z: %d, BlockDim.x: %d, BlockDim.y: %d, BlockDim.z: "
|
||||
"%d SHMEM_SZ: %d\n",
|
||||
GridDim.x,
|
||||
GridDim.y,
|
||||
GridDim.z,
|
||||
BlockDim.x,
|
||||
BlockDim.y,
|
||||
BlockDim.z,
|
||||
SHMEM_SZ);
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
QUANT_GEMM_Kernel<TilingConfig, OutputDataType><<<GridDim, BlockDim, SHMEM_SZ, stream>>>(
|
||||
Weight1, Weight2, Scales, B, C, M_Global, N_Global, K_Global, Split_K);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
cudaError_t fp6_linear_kernel(cudaStream_t stream,
|
||||
const uint4* Weight1,
|
||||
const uint4* Weight2,
|
||||
const half* Scales,
|
||||
const half* B,
|
||||
half* C,
|
||||
const size_t M_Global,
|
||||
const size_t N_Global,
|
||||
const size_t K_Global,
|
||||
float* Reduction_Workspace, // Reduction_Workspace_Size = Split_K *
|
||||
// M_Global * N_Global * sizeof(fp32)
|
||||
int Split_K)
|
||||
{
|
||||
assert(M_Global % 256 == 0);
|
||||
assert(K_Global % 64 == 0);
|
||||
assert(N_Global > 0);
|
||||
|
||||
// Work around to support more N shapes:
|
||||
size_t N_PowerOf2;
|
||||
if (N_Global > 0 && N_Global <= 8) N_PowerOf2 = 8;
|
||||
if (N_Global > 8 && N_Global <= 16) N_PowerOf2 = 16;
|
||||
if (N_Global > 16 && N_Global <= 32) N_PowerOf2 = 32;
|
||||
if (N_Global > 32 && N_Global <= 64) N_PowerOf2 = 64;
|
||||
if (N_Global > 64 && N_Global <= 128) N_PowerOf2 = 128;
|
||||
if (N_Global > 128) N_PowerOf2 = ((N_Global - 1) / 128 + 1) * 128;
|
||||
|
||||
if (Split_K == 1) {
|
||||
switch (N_PowerOf2) {
|
||||
case 8:
|
||||
Kernel_Ex<TilingConfig<4, 1, 1>, half>(
|
||||
stream, Weight1, Weight2, Scales, B, C, M_Global, N_Global, K_Global, Split_K);
|
||||
break;
|
||||
case 16:
|
||||
Kernel_Ex<TilingConfig<4, 1, 2>, half>(
|
||||
stream, Weight1, Weight2, Scales, B, C, M_Global, N_Global, K_Global, Split_K);
|
||||
break;
|
||||
case 32:
|
||||
Kernel_Ex<TilingConfig<4, 1, 4>, half>(
|
||||
stream, Weight1, Weight2, Scales, B, C, M_Global, N_Global, K_Global, Split_K);
|
||||
break;
|
||||
case 64:
|
||||
Kernel_Ex<TilingConfig<4, 1, 8>, half>(
|
||||
stream, Weight1, Weight2, Scales, B, C, M_Global, N_Global, K_Global, Split_K);
|
||||
break;
|
||||
case 128:
|
||||
Kernel_Ex<TilingConfig<4, 1, 8>, half>(
|
||||
stream, Weight1, Weight2, Scales, B, C, M_Global, N_Global, K_Global, Split_K);
|
||||
break;
|
||||
default:
|
||||
if (N_PowerOf2 % 128 != 0) {
|
||||
printf("QuantLLM_API Error: Unsupported N dimension %lu!\n", N_PowerOf2);
|
||||
return cudaErrorUnknown;
|
||||
}
|
||||
Kernel_Ex<TilingConfig<4, 1, 8>, half>(
|
||||
stream, Weight1, Weight2, Scales, B, C, M_Global, N_Global, K_Global, Split_K);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (N_PowerOf2) {
|
||||
case 8:
|
||||
Kernel_Ex<TilingConfig<4, 1, 1>, float>(stream,
|
||||
Weight1,
|
||||
Weight2,
|
||||
Scales,
|
||||
B,
|
||||
Reduction_Workspace,
|
||||
M_Global,
|
||||
N_Global,
|
||||
K_Global,
|
||||
Split_K);
|
||||
break;
|
||||
case 16:
|
||||
Kernel_Ex<TilingConfig<4, 1, 2>, float>(stream,
|
||||
Weight1,
|
||||
Weight2,
|
||||
Scales,
|
||||
B,
|
||||
Reduction_Workspace,
|
||||
M_Global,
|
||||
N_Global,
|
||||
K_Global,
|
||||
Split_K);
|
||||
break;
|
||||
case 32:
|
||||
Kernel_Ex<TilingConfig<4, 1, 4>, float>(stream,
|
||||
Weight1,
|
||||
Weight2,
|
||||
Scales,
|
||||
B,
|
||||
Reduction_Workspace,
|
||||
M_Global,
|
||||
N_Global,
|
||||
K_Global,
|
||||
Split_K);
|
||||
break;
|
||||
case 64:
|
||||
Kernel_Ex<TilingConfig<4, 1, 8>, float>(stream,
|
||||
Weight1,
|
||||
Weight2,
|
||||
Scales,
|
||||
B,
|
||||
Reduction_Workspace,
|
||||
M_Global,
|
||||
N_Global,
|
||||
K_Global,
|
||||
Split_K);
|
||||
break;
|
||||
case 128:
|
||||
Kernel_Ex<TilingConfig<4, 1, 8>, float>(stream,
|
||||
Weight1,
|
||||
Weight2,
|
||||
Scales,
|
||||
B,
|
||||
Reduction_Workspace,
|
||||
M_Global,
|
||||
N_Global,
|
||||
K_Global,
|
||||
Split_K);
|
||||
break;
|
||||
default:
|
||||
if (N_PowerOf2 % 128 != 0) {
|
||||
printf("QuantLLM_API Error: Unsupported N dimension %lu!\n", N_PowerOf2);
|
||||
return cudaErrorUnknown;
|
||||
}
|
||||
Kernel_Ex<TilingConfig<4, 1, 8>, float>(stream,
|
||||
Weight1,
|
||||
Weight2,
|
||||
Scales,
|
||||
B,
|
||||
Reduction_Workspace,
|
||||
M_Global,
|
||||
N_Global,
|
||||
K_Global,
|
||||
Split_K);
|
||||
break;
|
||||
}
|
||||
// Reduction for SplitK
|
||||
dim3 GridDim((M_Global * N_Global) / REDUCTION_ELEMENT_PER_THREADBLOCK, 1, 1);
|
||||
dim3 BlockDim(WARP_SIZE, 1, 1);
|
||||
SplitK_Reduction<<<GridDim, BlockDim, 0, stream>>>(
|
||||
C, Reduction_Workspace, M_Global, N_Global, Split_K);
|
||||
}
|
||||
return cudaGetLastError();
|
||||
}
|
||||
|
||||
/*
|
||||
Computes FP6-FP16 GEMM (PyTorch interface).
|
||||
|
||||
[Mathematical Formula]
|
||||
Standard definition of linear layer: Out = In * trans(W), where In, Out, and W are stored in
|
||||
row-major. After Equivalent transformation : trans(Out) = W * trans(In). Note that we do not
|
||||
perform "transpose" during runtime, we instead interpret the In/Out as column-major matrices when
|
||||
calling our CUDA kernel.
|
||||
|
||||
[Inputs]
|
||||
_in_feats: tensor of shape [B, IC]; // half
|
||||
_weights: int tensor of shape [OC, IC // 16 * 3]; // 3 INT32 words contains 16 FP6 weights.
|
||||
_scales: tensor of shape [OC]; // half
|
||||
splitK: splitting the MatMul problem along K dimension for higher GPU utilization, default 1.
|
||||
[Outputs]
|
||||
_out_feats: tensor of shape [B, OC]; // half
|
||||
*/
|
||||
torch::Tensor fp6_linear_forward_cuda(torch::Tensor _in_feats,
|
||||
torch::Tensor _weights,
|
||||
torch::Tensor _scales,
|
||||
int splitK)
|
||||
{
|
||||
int num_in_feats = _in_feats.size(0);
|
||||
int num_in_channels = _in_feats.size(1);
|
||||
int num_out_channels = _weights.size(0);
|
||||
assert(num_in_channels % 64 == 0);
|
||||
assert((num_in_channels / 16 * 3) ==
|
||||
_weights.size(1)); // Making sure the K dimension is matched.
|
||||
//
|
||||
int M = num_out_channels;
|
||||
int K = num_in_channels;
|
||||
int N = num_in_feats;
|
||||
// Input Tensors
|
||||
auto weight1 = reinterpret_cast<const uint4*>(
|
||||
_weights.data_ptr<int>()); // weights is [OC, IC] but in FP6.
|
||||
auto weight2 = weight1 + num_in_channels * num_out_channels * 2 / 128;
|
||||
auto in_feats = reinterpret_cast<const half*>(_in_feats.data_ptr<at::Half>());
|
||||
auto scales = reinterpret_cast<const half*>(_scales.data_ptr<at::Half>());
|
||||
// Output Tensors
|
||||
auto options = torch::TensorOptions().dtype(_in_feats.dtype()).device(_in_feats.device());
|
||||
at::Tensor _out_feats = torch::empty({num_in_feats, num_out_channels}, options);
|
||||
auto out_feats = reinterpret_cast<half*>(_out_feats.data_ptr<at::Half>());
|
||||
|
||||
float* Reduction_Workspace = nullptr;
|
||||
if (splitK != 1) {
|
||||
auto options = torch::TensorOptions().dtype(torch::kFloat32).device(_in_feats.device());
|
||||
at::Tensor _workspace = torch::empty({splitK, num_in_feats, num_out_channels}, options);
|
||||
auto Reduction_Workspace = reinterpret_cast<float*>(
|
||||
_out_feats.data_ptr<float>()); // Reduction_Workspace_Size = Split_K * M_Global *
|
||||
// N_Global * sizeof(fp32)
|
||||
}
|
||||
|
||||
fp6_linear_kernel(0, // Using default stream here.
|
||||
weight1,
|
||||
weight2,
|
||||
scales,
|
||||
in_feats,
|
||||
out_feats,
|
||||
M,
|
||||
N,
|
||||
K,
|
||||
Reduction_Workspace,
|
||||
splitK);
|
||||
|
||||
return _out_feats;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inputs:
|
||||
* (1) unsigned char Weight_6bit [M*K*6/8]
|
||||
* Outputs:
|
||||
* (1) unsigned char Weight_2bit [M*K*2/8]
|
||||
* (2) unsigned char Weight_4bit [M*K*4/8]
|
||||
*
|
||||
* Assumption: Weight_6bit, Weight_2bit, Weight_4bit all stored continuously in row-major.
|
||||
* 8 FP6 = 6 Bytes
|
||||
* 8 FP4 = 4 Bytes
|
||||
* 8 FP2 = 2 Bytes
|
||||
*/
|
||||
|
||||
/*
|
||||
* Weight prepacking (Pytorch interface).
|
||||
* [Input & Output]
|
||||
* fp6_tensor: int tensor of shape [OC, IC // 16 * 3]; // 3 INT32 words contains 16 FP6 weights.
|
||||
* [Output]
|
||||
* packed_tensor: int tensor of shape [OC, IC // 16 * 3];
|
||||
*/
|
||||
torch::Tensor weight_matrix_prepacking_cpu(torch::Tensor fp6_tensor, size_t OC, size_t IC)
|
||||
{
|
||||
assert((OC % 256 == 0) && (IC % 64 == 0));
|
||||
assert((fp6_tensor.size(0) == OC) && (fp6_tensor.size(1) == IC / 16 * 3));
|
||||
// auto packed_tensor = torch::empty_like(fp6_tensor);
|
||||
// auto packed_tensor_ptr = reinterpret_cast<int*>(packed_tensor.data_ptr<int>());
|
||||
auto fp6_tensor_ptr = reinterpret_cast<int*>(fp6_tensor.data_ptr<int>());
|
||||
weight_matrix_prepacking(fp6_tensor_ptr, OC, IC);
|
||||
return fp6_tensor;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
// This is a copy of FP6-LLM kernel code: https://arxiv.org/abs/2401.14112
|
||||
|
||||
#ifndef DEEPSPEED_CUDA_LINEAR_FP6_LINEAR_CUH
|
||||
#define DEEPSPEED_CUDA_LINEAR_FP6_LINEAR_CUH
|
||||
|
||||
#include <cuda.h>
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include <torch/extension.h>
|
||||
|
||||
/*
|
||||
* Computes FP6-FP16 GEMM (C++ interface).
|
||||
*/
|
||||
cudaError_t fp6_linear_kernel(cudaStream_t stream,
|
||||
const uint4* Weight1,
|
||||
const uint4* Weight2,
|
||||
const half* Scales,
|
||||
const half* B,
|
||||
half* C,
|
||||
const size_t M_Global,
|
||||
const size_t N_Global,
|
||||
const size_t K_Global,
|
||||
float* Reduction_Workspace, // Reduction_Workspace_Size = Split_K *
|
||||
// M_Global * N_Global * sizeof(fp32)
|
||||
int Split_K);
|
||||
|
||||
/*
|
||||
* Computes FP6-FP16 GEMM (PyTorch interface).
|
||||
*/
|
||||
torch::Tensor fp6_linear_forward_cuda(torch::Tensor _in_feats,
|
||||
torch::Tensor _weights,
|
||||
torch::Tensor _scales,
|
||||
int splitK = 1);
|
||||
|
||||
/*
|
||||
* In-place weight prepacking (C++ interface).
|
||||
*/
|
||||
void weight_matrix_prepacking(int* FP6Weights, size_t M, size_t K);
|
||||
|
||||
/*
|
||||
* Weight prepacking (Pytorch interface).
|
||||
*/
|
||||
torch::Tensor weight_matrix_prepacking_cpu(torch::Tensor fp6_tensor, size_t M, size_t K);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .rms_norm import CUDARMSNorm
|
||||
from .rms_pre_norm import CUDARMSPreNorm
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "rms_norm.h"
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
#define DISPATCH_FOR_FLOAT(DTYPE, ...) \
|
||||
[&] { \
|
||||
if (DTYPE == torch::kFloat32) { \
|
||||
using scalar_t = float; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kFloat16) { \
|
||||
using scalar_t = __half; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kBFloat16) { \
|
||||
using scalar_t = __nv_bfloat16; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
TORCH_CHECK(false, "Unsupported dtype for BiasActivation"); \
|
||||
} \
|
||||
}()
|
||||
#else
|
||||
#define DISPATCH_FOR_FLOAT(DTYPE, ...) \
|
||||
[&] { \
|
||||
if (DTYPE == torch::kFloat32) { \
|
||||
using scalar_t = float; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kFloat16) { \
|
||||
using scalar_t = __half; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
TORCH_CHECK(false, "Unsupported dtype for BiasActivation"); \
|
||||
} \
|
||||
}()
|
||||
#endif
|
||||
|
||||
void rms_norm(torch::Tensor& norm_output,
|
||||
torch::Tensor& norm_input,
|
||||
torch::Tensor& gamma,
|
||||
float epsilon)
|
||||
{
|
||||
TORCH_CHECK(norm_output.scalar_type() == norm_input.scalar_type(),
|
||||
"norm_output and norm_input should have the same data type");
|
||||
TORCH_CHECK(norm_output.scalar_type() == gamma.scalar_type(),
|
||||
"norm_output and gamma should have the same data type");
|
||||
|
||||
const int32_t rows = norm_input.size(0);
|
||||
const int32_t cols = norm_input.size(1);
|
||||
|
||||
TORCH_CHECK(norm_output.size(0) == rows,
|
||||
"norm_output and norm_input should have the same first dimension");
|
||||
TORCH_CHECK(norm_output.size(1) == cols,
|
||||
"norm_output and norm_input should have the same second dimension");
|
||||
|
||||
DISPATCH_FOR_FLOAT(norm_output.scalar_type(), [&] {
|
||||
scalar_t* norm_output_ptr = reinterpret_cast<scalar_t*>(norm_output.data_ptr());
|
||||
scalar_t* norm_input_ptr = reinterpret_cast<scalar_t*>(norm_input.data_ptr());
|
||||
scalar_t* gamma_ptr = reinterpret_cast<scalar_t*>(gamma.data_ptr());
|
||||
scalar_t* null_t = nullptr;
|
||||
|
||||
launch_rms_norm(norm_output_ptr,
|
||||
null_t,
|
||||
norm_input_ptr,
|
||||
null_t,
|
||||
gamma_ptr,
|
||||
epsilon,
|
||||
rows,
|
||||
cols,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
});
|
||||
}
|
||||
|
||||
void rms_pre_norm(torch::Tensor& norm_output,
|
||||
torch::Tensor& residual_output,
|
||||
torch::Tensor& norm_input,
|
||||
torch::Tensor& residual_input,
|
||||
torch::Tensor& gamma,
|
||||
float epsilon)
|
||||
{
|
||||
TORCH_CHECK(norm_output.scalar_type() == norm_input.scalar_type(),
|
||||
"norm_output and norm_input should have the same data type");
|
||||
TORCH_CHECK(norm_output.scalar_type() == gamma.scalar_type(),
|
||||
"norm_output and gamma should have the same data type");
|
||||
|
||||
const int32_t rows = norm_input.size(0);
|
||||
const int32_t cols = norm_input.size(1);
|
||||
|
||||
TORCH_CHECK(norm_output.size(0) == rows,
|
||||
"norm_output and norm_input should have the same first dimension");
|
||||
TORCH_CHECK(norm_output.size(1) == cols,
|
||||
"norm_output and norm_input should have the same second dimension");
|
||||
|
||||
TORCH_CHECK(residual_output.size(0) == rows,
|
||||
"residual_output and norm_input should have the same first dimension");
|
||||
TORCH_CHECK(residual_output.size(1) == cols,
|
||||
"residual_output and norm_input should have the same second dimension");
|
||||
|
||||
TORCH_CHECK(residual_input.size(0) == rows,
|
||||
"residual_input and norm_input should have the same first dimension");
|
||||
TORCH_CHECK(residual_input.size(1) == cols,
|
||||
"residual_input and norm_input should have the same second dimension");
|
||||
|
||||
DISPATCH_FOR_FLOAT(norm_output.scalar_type(), [&] {
|
||||
scalar_t* norm_output_ptr = reinterpret_cast<scalar_t*>(norm_output.data_ptr());
|
||||
scalar_t* residual_output_ptr = reinterpret_cast<scalar_t*>(residual_output.data_ptr());
|
||||
const scalar_t* norm_input_ptr = reinterpret_cast<const scalar_t*>(norm_input.data_ptr());
|
||||
const scalar_t* residual_input_ptr =
|
||||
reinterpret_cast<const scalar_t*>(residual_input.data_ptr());
|
||||
const scalar_t* gamma_ptr = reinterpret_cast<const scalar_t*>(gamma.data_ptr());
|
||||
|
||||
launch_rms_norm(norm_output_ptr,
|
||||
residual_output_ptr,
|
||||
norm_input_ptr,
|
||||
residual_input_ptr,
|
||||
gamma_ptr,
|
||||
epsilon,
|
||||
rows,
|
||||
cols,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <torch/extension.h>
|
||||
#include "ds_kernel_utils.h"
|
||||
|
||||
template <typename T>
|
||||
void launch_rms_norm(T* norm_output,
|
||||
T* res_output,
|
||||
const T* vals,
|
||||
const T* residual,
|
||||
const T* gamma,
|
||||
float epsilon,
|
||||
int rows,
|
||||
int elems_per_row,
|
||||
cudaStream_t stream);
|
||||
|
||||
void rms_norm(torch::Tensor& norm_output,
|
||||
torch::Tensor& norm_input,
|
||||
torch::Tensor& gamma,
|
||||
float epsilon);
|
||||
|
||||
void rms_pre_norm(torch::Tensor& norm_output,
|
||||
torch::Tensor& residual_output,
|
||||
torch::Tensor& norm_input,
|
||||
torch::Tensor& residual_input,
|
||||
torch::Tensor& gamma,
|
||||
float epsilon);
|
||||
@@ -0,0 +1,28 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from .rms_norm_base import CUDARMSNormBase
|
||||
|
||||
|
||||
class CUDARMSNorm(CUDARMSNormBase):
|
||||
"""
|
||||
Floating point layer norm kernel for CUDA/RoCM.
|
||||
|
||||
Performs: z = ln(x)
|
||||
"""
|
||||
|
||||
def __call__(self, output_z: torch.Tensor, input_x: torch.Tensor, gamma: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
output_z may alias input_x directly. All Tensors should have the same shape.
|
||||
|
||||
Parameters:
|
||||
output_z (torch.Tensor): Output tensor.
|
||||
input_x (torch.Tensor): Input tensor.
|
||||
gamma (torch.Tensor): Gamma tensor.
|
||||
"""
|
||||
self.inf_module.rms_norm(output_z, input_x, gamma, self.epsilon)
|
||||
return output_z
|
||||
@@ -0,0 +1,37 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from ... import DSKernelBase
|
||||
from ....inference_utils import elem_size
|
||||
from deepspeed.ops.op_builder import InferenceCoreBuilder
|
||||
|
||||
|
||||
class CUDARMSNormBase(DSKernelBase):
|
||||
"""
|
||||
Base class for CUDA LN kernels. They all same the same validation logic,
|
||||
so we can share it here.
|
||||
"""
|
||||
|
||||
supported_dtypes = [torch.float16, torch.bfloat16, torch.float32]
|
||||
|
||||
def __init__(self, channels: int, fp_dtype: torch.dtype, epsilon: float = 1e-5):
|
||||
"""
|
||||
Parameters:
|
||||
channels (int): Number of channels in the input tensor. Must be divisible to align
|
||||
to 16 bytes.
|
||||
fp_dtype (torch.dtype): Data type for the input/output/gamma. Supported values
|
||||
are torch.float16, torch.bfloat16, and torch.float32.
|
||||
"""
|
||||
if fp_dtype not in CUDARMSNormBase.supported_dtypes:
|
||||
raise ValueError("Unsupported data type: {}, supported_dtypes are {}".format(
|
||||
fp_dtype, CUDARMSNormBase.supported_dtypes))
|
||||
|
||||
if elem_size(fp_dtype) * channels % 16 != 0:
|
||||
raise ValueError("channels must be divisible by 16 bytes")
|
||||
|
||||
self.inf_module = InferenceCoreBuilder().load()
|
||||
self.epsilon = epsilon
|
||||
@@ -0,0 +1,262 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "conversion_utils.h"
|
||||
#include "ds_kernel_utils.h"
|
||||
#include "memory_access_utils.h"
|
||||
#include "reduction_utils.h"
|
||||
|
||||
namespace cg = cooperative_groups;
|
||||
using rop = reduce::ROpType;
|
||||
|
||||
namespace rms {
|
||||
constexpr int granularity = 16;
|
||||
} // namespace rms
|
||||
|
||||
template <typename T, int UNROLL, int threadsPerGroup, int maxThreads>
|
||||
__global__ void rms_norm(T* output, const T* vals, const T* gamma, float epsilon, int elems_per_row)
|
||||
{
|
||||
constexpr int T_per_load = rms::granularity / sizeof(T);
|
||||
|
||||
cg::thread_block tb = cg::this_thread_block();
|
||||
cg::thread_block_tile<hw_warp_size> warp = cg::tiled_partition<hw_warp_size>(tb);
|
||||
|
||||
// X-dimension of the block
|
||||
const int block_offset = (tb.group_index().x * (maxThreads / threadsPerGroup) * elems_per_row) +
|
||||
(tb.thread_index().y * elems_per_row);
|
||||
const int thread_offset = tb.thread_index().x * T_per_load;
|
||||
const int base_offset = block_offset + thread_offset;
|
||||
const int stride = blockDim.x * T_per_load;
|
||||
|
||||
float var_sum = reduce::init<rop::Add, float>();
|
||||
|
||||
const T* input_base = vals + base_offset;
|
||||
|
||||
T local_buffer[UNROLL * T_per_load];
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < UNROLL; i++) {
|
||||
T* iteration_buffer = local_buffer + (i * T_per_load);
|
||||
|
||||
mem_access::load_global<rms::granularity>(iteration_buffer,
|
||||
input_base + (i * stride),
|
||||
thread_offset + (i * stride) < elems_per_row);
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < T_per_load; j++) {
|
||||
float up_cast = conversion::to<float>(iteration_buffer[j]);
|
||||
float sq_val = up_cast * up_cast;
|
||||
var_sum = reduce::element<rop::Add, float>(var_sum, sq_val);
|
||||
}
|
||||
}
|
||||
|
||||
reduce::partitioned_block<rop::Add, threadsPerGroup>(tb, warp, var_sum);
|
||||
const float var = var_sum / elems_per_row;
|
||||
const T denom = conversion::to<T>(__frsqrt_rn(var + epsilon));
|
||||
|
||||
T* block_output = output + block_offset;
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < UNROLL; i++) {
|
||||
T* iteration_buffer = local_buffer + (i * T_per_load);
|
||||
const int iter_idx = i * stride + thread_offset;
|
||||
const bool do_loads = (iter_idx < elems_per_row);
|
||||
|
||||
T gamma_local[T_per_load];
|
||||
|
||||
mem_access::load_global<rms::granularity>(gamma_local, gamma + iter_idx, do_loads);
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < T_per_load; j++) {
|
||||
iteration_buffer[j] *= denom;
|
||||
iteration_buffer[j] *= gamma_local[j];
|
||||
}
|
||||
|
||||
if (do_loads) {
|
||||
mem_access::store_global<rms::granularity>(block_output + iter_idx, iteration_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, int UNROLL, int threadsPerGroup, int maxThreads>
|
||||
__global__ void pre_rms_norm(T* output,
|
||||
T* res_out,
|
||||
const T* vals,
|
||||
const T* residual,
|
||||
const T* gamma,
|
||||
float epsilon,
|
||||
int elems_per_row)
|
||||
{
|
||||
constexpr int T_per_load = rms::granularity / sizeof(T);
|
||||
|
||||
cg::thread_block tb = cg::this_thread_block();
|
||||
cg::thread_block_tile<hw_warp_size> warp = cg::tiled_partition<hw_warp_size>(tb);
|
||||
|
||||
// X-dimension of the block
|
||||
const int block_offset = (tb.group_index().x * (maxThreads / threadsPerGroup) * elems_per_row) +
|
||||
(tb.thread_index().y * elems_per_row);
|
||||
const int thread_offset = tb.thread_index().x * T_per_load;
|
||||
const int base_offset = block_offset + thread_offset;
|
||||
const int stride = blockDim.x * T_per_load;
|
||||
|
||||
float var_sum = reduce::init<rop::Add, float>();
|
||||
|
||||
const T* input_base = vals + base_offset;
|
||||
const T* residual_base = residual + base_offset;
|
||||
T* res_output = res_out + base_offset;
|
||||
|
||||
T local_buffer[UNROLL * T_per_load];
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < UNROLL; i++) {
|
||||
T* iteration_buffer = local_buffer + (i * T_per_load);
|
||||
T residual_buffer[T_per_load];
|
||||
|
||||
const int iter_offset = i * stride + thread_offset;
|
||||
const bool do_loads = (iter_offset < elems_per_row);
|
||||
|
||||
mem_access::load_global<rms::granularity>(
|
||||
iteration_buffer, input_base + (i * stride), do_loads);
|
||||
mem_access::load_global<rms::granularity>(
|
||||
residual_buffer, residual_base + (i * stride), do_loads);
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < T_per_load; j++) {
|
||||
iteration_buffer[j] += residual_buffer[j];
|
||||
float vals_up_cast = conversion::to<float>(iteration_buffer[j]);
|
||||
|
||||
var_sum = reduce::element<rop::Add, float>(var_sum, vals_up_cast * vals_up_cast);
|
||||
}
|
||||
|
||||
if (do_loads) {
|
||||
mem_access::store_global<rms::granularity>(res_output + i * stride, iteration_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
reduce::partitioned_block<rop::Add, threadsPerGroup>(tb, warp, var_sum);
|
||||
const float var = var_sum / elems_per_row;
|
||||
const T denom = conversion::to<T>(__frsqrt_rn(var + epsilon));
|
||||
|
||||
T* block_output = output + block_offset;
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < UNROLL; i++) {
|
||||
T* iteration_buffer = local_buffer + (i * T_per_load);
|
||||
const int iter_idx = i * stride + thread_offset;
|
||||
const bool do_loads = (iter_idx < elems_per_row);
|
||||
|
||||
T gamma_local[T_per_load];
|
||||
|
||||
mem_access::load_global<rms::granularity>(gamma_local, gamma + iter_idx, do_loads);
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < T_per_load; j++) {
|
||||
iteration_buffer[j] *= denom;
|
||||
iteration_buffer[j] *= gamma_local[j];
|
||||
}
|
||||
|
||||
if (do_loads) {
|
||||
mem_access::store_global<rms::granularity>(block_output + iter_idx, iteration_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define LAUNCH_RMS_NORM(UNROLL, threadsPerGroup, maxThreads) \
|
||||
rms_norm<T, UNROLL, threadsPerGroup, maxThreads> \
|
||||
<<<grid, block, 0, stream>>>(norm_output, vals, gamma, epsilon, elems_per_row);
|
||||
|
||||
#define LAUNCH_PRE_RMS_NORM(UNROLL, threadsPerGroup, maxThreads) \
|
||||
pre_rms_norm<T, UNROLL, threadsPerGroup, maxThreads><<<grid, block, 0, stream>>>( \
|
||||
norm_output, res_output, vals, residual, gamma, epsilon, elems_per_row);
|
||||
|
||||
#define LAUNCH_ALL_RMS_NORM(UNROLL, threadsPerGroup, maxThreads) \
|
||||
if (pre_norm) { \
|
||||
LAUNCH_PRE_RMS_NORM(UNROLL, threadsPerGroup, maxThreads) \
|
||||
} else { \
|
||||
LAUNCH_RMS_NORM(UNROLL, threadsPerGroup, maxThreads) \
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void launch_rms_norm(T* norm_output,
|
||||
T* res_output,
|
||||
const T* vals,
|
||||
const T* residual,
|
||||
const T* gamma,
|
||||
float epsilon,
|
||||
int rows,
|
||||
int elems_per_row,
|
||||
cudaStream_t stream)
|
||||
{
|
||||
// 8 for __half, 4 for float
|
||||
constexpr int T_per_load = rms::granularity / sizeof(T);
|
||||
constexpr int maxThreads = 256;
|
||||
constexpr int internalUnroll = sizeof(T) == 4 ? 4 : 2;
|
||||
|
||||
const bool is_subblock_schedule = (elems_per_row <= 128) ? true : false;
|
||||
const int h_per_step = is_subblock_schedule ? T_per_load : T_per_load * internalUnroll;
|
||||
|
||||
// Scheduling concern: may be slightly faster for some inputs to assign multiple stages of
|
||||
// warp-sized blocks rather than stepping up to 64/96 threads
|
||||
const int one_step_threads = next_pow2((elems_per_row + h_per_step - 1) / h_per_step);
|
||||
const int threads_per_group = (one_step_threads < maxThreads) ? one_step_threads : maxThreads;
|
||||
|
||||
const int groups_per_block_max =
|
||||
is_subblock_schedule ? (maxThreads + threads_per_group - 1) / threads_per_group : 1;
|
||||
const int groups_per_block = (rows < groups_per_block_max) ? rows : groups_per_block_max;
|
||||
const int groups_launch = (groups_per_block + rows - 1) / groups_per_block;
|
||||
|
||||
dim3 block(threads_per_group, groups_per_block);
|
||||
dim3 grid(groups_launch);
|
||||
|
||||
const int elems_per_step = threads_per_group * h_per_step;
|
||||
const int external_unRoll = (elems_per_row + elems_per_step - 1) / elems_per_step;
|
||||
|
||||
bool pre_norm = (residual == nullptr) ? false : true;
|
||||
|
||||
if (is_subblock_schedule) {
|
||||
// <=128
|
||||
if (threads_per_group == 1) {
|
||||
LAUNCH_ALL_RMS_NORM(1, 1, maxThreads);
|
||||
} else if (threads_per_group == 2) {
|
||||
LAUNCH_ALL_RMS_NORM(1, 2, maxThreads);
|
||||
} else if (threads_per_group == 4) {
|
||||
LAUNCH_ALL_RMS_NORM(1, 4, maxThreads);
|
||||
} else if (threads_per_group == 8) {
|
||||
LAUNCH_ALL_RMS_NORM(1, 8, maxThreads);
|
||||
} else if (threads_per_group == 16) {
|
||||
LAUNCH_ALL_RMS_NORM(1, 16, maxThreads);
|
||||
}
|
||||
} else if (external_unRoll == 1) {
|
||||
// 129 - 4096 elems
|
||||
// (this can launch with 1-7 warps as well)
|
||||
LAUNCH_ALL_RMS_NORM(1 * internalUnroll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 2) {
|
||||
// 4097 - 8192 elems
|
||||
LAUNCH_ALL_RMS_NORM(2 * internalUnroll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 3) {
|
||||
// 8193 - 12288 elems
|
||||
LAUNCH_ALL_RMS_NORM(3 * internalUnroll, maxThreads, maxThreads);
|
||||
} else if (external_unRoll == 4) {
|
||||
// 12289 - 16384 elems
|
||||
LAUNCH_ALL_RMS_NORM(4 * internalUnroll, maxThreads, maxThreads);
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE_LAUNCH_RMS_NORM(T) \
|
||||
template void launch_rms_norm<T>(T * norm_output, \
|
||||
T * res_output, \
|
||||
const T* vals, \
|
||||
const T* residual, \
|
||||
const T* gamma, \
|
||||
float epsilon, \
|
||||
int rows, \
|
||||
int elems_per_row, \
|
||||
cudaStream_t stream);
|
||||
|
||||
INSTANTIATE_LAUNCH_RMS_NORM(float)
|
||||
INSTANTIATE_LAUNCH_RMS_NORM(__half)
|
||||
#ifdef BF16_AVAILABLE
|
||||
INSTANTIATE_LAUNCH_RMS_NORM(__nv_bfloat16)
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
import torch
|
||||
|
||||
from .rms_norm_base import CUDARMSNormBase
|
||||
|
||||
|
||||
class CUDARMSPreNorm(CUDARMSNormBase):
|
||||
"""
|
||||
Floating point pre-LayerNorm kernel for CUDA/RoCM.
|
||||
|
||||
Performs: z_res = x_res + y_hid
|
||||
z_hid = ln(z_hid)
|
||||
"""
|
||||
|
||||
def __call__(self, z_res: torch.Tensor, z_hid: torch.Tensor, x_res: torch.Tensor, y_hid: torch.Tensor,
|
||||
gamma: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
z_res can alias x_res. All non-parameter input/output tensors
|
||||
must have the same shape. z_hid can alias y_hid.
|
||||
|
||||
Parameters:
|
||||
z_res (torch.Tensor): Output residual.
|
||||
z_hid (torch.Tensor): Output hidden states.
|
||||
x_res (torch.Tensor): Input residual.
|
||||
y_hid (torch.Tensor): Input hidden states.
|
||||
gamma (torch.Tensor): Gamma tensor.
|
||||
beta (torch.Tensor): Beta tensor.
|
||||
|
||||
Returns:
|
||||
output (torch.Tensor): Output tensor.
|
||||
"""
|
||||
self.inf_module.rms_pre_norm(z_hid, z_res, y_hid, x_res, gamma, self.epsilon)
|
||||
return z_res, z_hid
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .gated_activation import *
|
||||
@@ -0,0 +1,65 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
from ... import DSKernelBase
|
||||
from ....inference_utils import ActivationType, elem_size
|
||||
from deepspeed.ops.op_builder import InferenceCoreBuilder
|
||||
|
||||
|
||||
class CUDAGatedActivation(DSKernelBase):
|
||||
"""
|
||||
CUDA implementation of gated activation kernel. This kernel assumes that the input
|
||||
tensor has gate and activation values in adjacent channels. The output tensor should
|
||||
have half the dimensionality of the input tensor.
|
||||
"""
|
||||
|
||||
supported_dtypes = [torch.float16, torch.bfloat16, torch.float32]
|
||||
supported_act_fns = [ActivationType.GEGLU, ActivationType.ReGLU, ActivationType.SiGLU]
|
||||
|
||||
def __init__(self, channels: int, fp_dtype: torch.dtype, act_fn: ActivationType) -> None:
|
||||
"""
|
||||
Compile and validate for the gated activation function.
|
||||
|
||||
Args:
|
||||
channels (int): Number of columns in the output tensor. Must be divisible to align
|
||||
to 8 bytes.
|
||||
fp_dtype (torch.dtype): Data type for the input/output/gamma. Supported values
|
||||
are torch.float16, torch.bfloat16, and torch.float32.
|
||||
act_fn (ActivationType): Activation function to use. Only GEGLU is supported.
|
||||
"""
|
||||
if fp_dtype not in CUDAGatedActivation.supported_dtypes:
|
||||
raise ValueError("Unsupported data type: {}, supported_dtypes are {}".format(
|
||||
fp_dtype, CUDAGatedActivation.supported_dtypes))
|
||||
|
||||
act_fn = ActivationType(act_fn)
|
||||
if act_fn not in CUDAGatedActivation.supported_act_fns:
|
||||
raise ValueError("Unsupported activation function: {}, supported_act_fns are {}".format(
|
||||
act_fn, CUDAGatedActivation.supported_act_fns))
|
||||
|
||||
if elem_size(fp_dtype) * channels % 8 != 0:
|
||||
raise ValueError("Channels must be divisible by 16 bytes")
|
||||
|
||||
if elem_size(fp_dtype) * channels > 98304:
|
||||
raise ValueError(
|
||||
"Kernel only compiled to support 98304 bytes per row, please file an issue if your model requires more."
|
||||
)
|
||||
|
||||
self.inf_module = InferenceCoreBuilder().load()
|
||||
self.act_fn = act_fn
|
||||
self.kernel = self.inf_module.gated_activation
|
||||
|
||||
def __call__(self, output: torch.Tensor, input: torch.Tensor, bias: Optional[torch.Tensor] = None) -> None:
|
||||
"""
|
||||
Performs gated activation on the input tensor, writing the result to the output tensor.
|
||||
|
||||
Args:
|
||||
output (torch.Tensor): Output tensor. Can be of [T, C // 2] or [B, S, C // 2]
|
||||
input (torch.Tensor): Input tensor. Can be of [T, C] or [B, S, C]
|
||||
"""
|
||||
self.kernel(output, input, bias, self.act_fn.value)
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "gated_activation_kernels.h"
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
#define DISPATCH_FOR_FLOAT(DTYPE, ...) \
|
||||
[&] { \
|
||||
if (DTYPE == torch::kFloat32) { \
|
||||
using scalar_t = float; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kFloat16) { \
|
||||
using scalar_t = __half; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kBFloat16) { \
|
||||
using scalar_t = __nv_bfloat16; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
TORCH_CHECK(false, "Unsupported dtype for BiasActivation"); \
|
||||
} \
|
||||
}()
|
||||
#else
|
||||
#define DISPATCH_FOR_FLOAT(DTYPE, ...) \
|
||||
[&] { \
|
||||
if (DTYPE == torch::kFloat32) { \
|
||||
using scalar_t = float; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kFloat16) { \
|
||||
using scalar_t = __half; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
TORCH_CHECK(false, "Unsupported dtype for BiasActivation"); \
|
||||
} \
|
||||
}()
|
||||
#endif
|
||||
|
||||
void ds_gated_activation(at::Tensor& output,
|
||||
at::Tensor& input,
|
||||
c10::optional<torch::Tensor>& bias,
|
||||
int activation_type_raw)
|
||||
{
|
||||
bool ragged_input = input.dim() == 2;
|
||||
|
||||
const ActivationType activation_type = static_cast<ActivationType>(activation_type_raw);
|
||||
|
||||
const int rows = ragged_input ? input.size(0) : input.size(0) * input.size(1);
|
||||
const int cols = ragged_input ? input.size(1) : input.size(2);
|
||||
|
||||
DISPATCH_FOR_FLOAT(input.scalar_type(), [&] {
|
||||
scalar_t* bias_ptr = nullptr;
|
||||
if (bias.has_value()) {
|
||||
TORCH_CHECK(bias.value().scalar_type() == input.scalar_type(),
|
||||
"Bias type must match input type");
|
||||
TORCH_CHECK(bias.value().numel() == cols,
|
||||
"Bias must have the same number of elements as the input channels");
|
||||
bias_ptr = reinterpret_cast<scalar_t*>(bias.value().data_ptr());
|
||||
}
|
||||
|
||||
scalar_t* output_ptr = reinterpret_cast<scalar_t*>(output.data_ptr());
|
||||
const scalar_t* input_ptr = reinterpret_cast<const scalar_t*>(input.data_ptr());
|
||||
|
||||
launch_gated_activation(output_ptr,
|
||||
input_ptr,
|
||||
bias_ptr,
|
||||
rows,
|
||||
cols,
|
||||
activation_type,
|
||||
c10::cuda::getCurrentCUDAStream());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <torch/extension.h>
|
||||
#include "activation_type.h"
|
||||
#include "ds_kernel_utils.h"
|
||||
|
||||
template <typename T>
|
||||
void launch_gated_activation(T* output,
|
||||
const T* vals,
|
||||
const T* bias,
|
||||
int rows,
|
||||
int cols,
|
||||
ActivationType activation_type,
|
||||
cudaStream_t stream);
|
||||
|
||||
void ds_gated_activation(at::Tensor& output,
|
||||
at::Tensor& input,
|
||||
c10::optional<torch::Tensor>& bias,
|
||||
int activation_type_raw);
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include <stdexcept>
|
||||
#include "activation_type.h"
|
||||
#include "conversion_utils.h"
|
||||
#include "ds_kernel_utils.h"
|
||||
#include "memory_access_utils.h"
|
||||
|
||||
namespace cg = cooperative_groups;
|
||||
|
||||
namespace gated_act {
|
||||
|
||||
constexpr int access_size = 16;
|
||||
constexpr int threads = 1024;
|
||||
|
||||
template <ActivationType ActType>
|
||||
DS_D_INLINE float gated_act_fn(float x, float y);
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float gated_act_fn<ActivationType::GEGLU>(float x, float y)
|
||||
{
|
||||
constexpr float sqrt_param = 0.79788456080286535587989211986876f;
|
||||
constexpr float mul_param = 0.044715;
|
||||
return y * x * 0.5f * (1.0f + tanhf(sqrt_param * (x + mul_param * x * x * x)));
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float gated_act_fn<ActivationType::ReGLU>(float x, float y)
|
||||
{
|
||||
return y * (x > 0.0f ? x : 0.0f);
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float gated_act_fn<ActivationType::SiGLU>(float x, float y)
|
||||
{
|
||||
return y * (x / (1.0f + expf(-x)));
|
||||
}
|
||||
|
||||
} // namespace gated_act
|
||||
|
||||
template <typename T, ActivationType ActType, int loopUnroll>
|
||||
__global__ void gated_activation_kernel(T* output,
|
||||
const T* input,
|
||||
const T* bias,
|
||||
int rows,
|
||||
int cols)
|
||||
{
|
||||
constexpr int read_vector = gated_act::access_size / sizeof(T);
|
||||
constexpr int write_vector = read_vector / 2;
|
||||
|
||||
const int row = blockIdx.x;
|
||||
const int col = threadIdx.x * read_vector;
|
||||
|
||||
const T* input_row = input + row * cols;
|
||||
T* output_row = output + row * cols / 2;
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < loopUnroll; i++) {
|
||||
T read[read_vector];
|
||||
T bias_read[read_vector];
|
||||
T store[write_vector];
|
||||
|
||||
const int read_offset = col + gated_act::threads * read_vector * i;
|
||||
const int write_offset = col / 2 + gated_act::threads * write_vector * i;
|
||||
|
||||
if (i != loopUnroll - 1 || read_offset < cols) {
|
||||
mem_access::load_global<gated_act::access_size>(read, input_row + read_offset);
|
||||
mem_access::load_global<gated_act::access_size>(
|
||||
bias_read, bias + read_offset, bias != nullptr);
|
||||
|
||||
for (int j = 0; j < write_vector; j++) {
|
||||
float g_val =
|
||||
conversion::to<float>(read[j * 2]) + conversion::to<float>(bias_read[j * 2]);
|
||||
float a_val = conversion::to<float>(read[j * 2 + 1]) +
|
||||
conversion::to<float>(bias_read[j * 2 + 1]);
|
||||
|
||||
float act_val = gated_act::gated_act_fn<ActType>(g_val, a_val);
|
||||
store[j] = conversion::to<T>(act_val);
|
||||
}
|
||||
|
||||
mem_access::store_global<gated_act::access_size / 2>(output_row + write_offset, store);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define DISPATCH_UNROLL(unroll_val) \
|
||||
gated_activation_kernel<T, ActType, unroll_val> \
|
||||
<<<grid, block, 0, stream>>>(output, input, bias, rows, cols);
|
||||
|
||||
template <typename T, ActivationType ActType>
|
||||
void launch_gated_activation_impl(T* output,
|
||||
const T* input,
|
||||
const T* bias,
|
||||
int rows,
|
||||
int cols,
|
||||
cudaStream_t stream)
|
||||
{
|
||||
constexpr int read_vector = gated_act::access_size / sizeof(T);
|
||||
constexpr int cols_per_unroll = gated_act::threads * read_vector;
|
||||
const int req_threads = (cols + read_vector - 1) / read_vector;
|
||||
const int threads = std::min(req_threads, gated_act::threads);
|
||||
|
||||
const dim3 grid(rows);
|
||||
const dim3 block(threads);
|
||||
const int unroll = (cols + cols_per_unroll - 1) / cols_per_unroll;
|
||||
|
||||
if (unroll == 1) {
|
||||
DISPATCH_UNROLL(1);
|
||||
} else if (unroll == 2) {
|
||||
DISPATCH_UNROLL(2);
|
||||
} else if (unroll == 3) {
|
||||
DISPATCH_UNROLL(3);
|
||||
} else if (unroll == 4) {
|
||||
DISPATCH_UNROLL(4);
|
||||
} else if (unroll == 5) {
|
||||
DISPATCH_UNROLL(5);
|
||||
} else if (unroll == 6) {
|
||||
DISPATCH_UNROLL(6);
|
||||
} else if (unroll == 7) {
|
||||
DISPATCH_UNROLL(7);
|
||||
} else {
|
||||
// TODO: provide a kernel with an outer loop to handle larger columns.
|
||||
throw std::runtime_error(
|
||||
"Called with more columns than supported, please report this bug and this limit will "
|
||||
"be increased.");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void launch_gated_activation(T* output,
|
||||
const T* input,
|
||||
const T* bias,
|
||||
int rows,
|
||||
int cols,
|
||||
ActivationType act_type,
|
||||
cudaStream_t stream)
|
||||
{
|
||||
switch (act_type) {
|
||||
case ActivationType::GEGLU:
|
||||
launch_gated_activation_impl<T, ActivationType::GEGLU>(
|
||||
output, input, bias, rows, cols, stream);
|
||||
break;
|
||||
case ActivationType::ReGLU:
|
||||
launch_gated_activation_impl<T, ActivationType::ReGLU>(
|
||||
output, input, bias, rows, cols, stream);
|
||||
break;
|
||||
case ActivationType::SiGLU:
|
||||
launch_gated_activation_impl<T, ActivationType::SiGLU>(
|
||||
output, input, bias, rows, cols, stream);
|
||||
break;
|
||||
default: throw std::runtime_error("Unsupported activation type");
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE_FOR_TYPE(T) \
|
||||
template void launch_gated_activation<T>(T * output, \
|
||||
const T* input, \
|
||||
const T* bias, \
|
||||
int rows, \
|
||||
int cols, \
|
||||
ActivationType act_type, \
|
||||
cudaStream_t stream);
|
||||
|
||||
INSTANTIATE_FOR_TYPE(float)
|
||||
INSTANTIATE_FOR_TYPE(__half)
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
INSTANTIATE_FOR_TYPE(__nv_bfloat16)
|
||||
#endif
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .mixed_gemm import *
|
||||
from .moe_gemm import *
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include <torch/extension.h>
|
||||
|
||||
#include "mixed_gemm.h"
|
||||
#include "moe_gemm.h"
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m)
|
||||
{
|
||||
// mixed_gemm.h
|
||||
m.def("mixed_gemm", &mixed_gemm, "Mixed-precision GEMM");
|
||||
|
||||
// moe_gemm.h
|
||||
m.def("moe_gemm", &moe_gemm, "MultiGEMM for MoE (16-bit weights)");
|
||||
m.def("mixed_moe_gemm", &mixed_moe_gemm, "MultiGEMM for MoE (4-bit/8-bit weights)");
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .mixed_gemm import *
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include "mixed_gemm.h"
|
||||
#include "mixed_gemm_api.h"
|
||||
#include "weight_variant.h"
|
||||
|
||||
// Switch helpers inspired by
|
||||
// https://github.com/NVIDIA/DALI/blob/main/include/dali/core/static_switch.h
|
||||
// and https://github.com/pytorch/pytorch/blob/master/aten/src/ATen/Dispatch.h
|
||||
|
||||
#define ACT_DTYPE_SWITCH(COND, ...) \
|
||||
[&] { \
|
||||
if (COND) { \
|
||||
using ActivationDtype = __half; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
using ActivationDtype = __nv_bfloat16; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
}()
|
||||
|
||||
#define WEIGHT_VARIANT_SWITCH(COND, ...) \
|
||||
[&] { \
|
||||
if (COND) { \
|
||||
constexpr WeightVariant WVariant = WeightVariant::kFP8; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
constexpr WeightVariant WVariant = WeightVariant::kFP4; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
}()
|
||||
|
||||
void mixed_gemm(at::Tensor& output,
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& weight,
|
||||
at::Tensor& scales,
|
||||
c10::optional<at::Tensor>& bias,
|
||||
int num_bits,
|
||||
int activation_raw)
|
||||
{
|
||||
TORCH_CHECK(output.dtype() == hidden_states.dtype(),
|
||||
"Output and hidden states must have the same dtype");
|
||||
TORCH_CHECK(num_bits == 4 || num_bits == 8, "Data width must be 4 or 8");
|
||||
TORCH_CHECK(output.size(0) == hidden_states.size(0), "Token dimension mismatch");
|
||||
|
||||
int32_t m = output.size(0);
|
||||
int32_t k = hidden_states.size(1);
|
||||
int32_t n = weight.size(1);
|
||||
|
||||
TORCH_CHECK(weight.size(0) == k, "Weight dimension mismatch");
|
||||
|
||||
ACT_DTYPE_SWITCH(hidden_states.dtype() == torch::kFloat16, [&] {
|
||||
WEIGHT_VARIANT_SWITCH(num_bits == 8, [&] {
|
||||
fastertransformer::CutlassFpAIntBGemmRunner<ActivationDtype, WVariant> runner =
|
||||
*MixedGemmContext<ActivationDtype, WVariant>::Instance().GeMM_Runner();
|
||||
|
||||
ActivationType activation_type = (ActivationType)activation_raw;
|
||||
if (!bias.has_value() && activation_type == ActivationType::IDENTITY) {
|
||||
runner.gemm((ActivationDtype*)hidden_states.data_ptr(),
|
||||
(const char*)weight.data_ptr(),
|
||||
(ActivationDtype*)scales.data_ptr(),
|
||||
(ActivationDtype*)output.data_ptr(),
|
||||
m,
|
||||
n,
|
||||
k,
|
||||
nullptr,
|
||||
0,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
return;
|
||||
} else {
|
||||
ActivationDtype* bias_ptr = nullptr;
|
||||
if (bias.has_value()) { bias_ptr = (ActivationDtype*)bias.value().data_ptr(); }
|
||||
runner.gemm_bias_act((ActivationDtype*)hidden_states.data_ptr(),
|
||||
(char*)weight.data_ptr(),
|
||||
(ActivationDtype*)scales.data_ptr(),
|
||||
bias_ptr,
|
||||
(ActivationDtype*)output.data_ptr(),
|
||||
m,
|
||||
n,
|
||||
k,
|
||||
activation_type,
|
||||
nullptr,
|
||||
0,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <torch/extension.h>
|
||||
|
||||
void mixed_gemm(at::Tensor& output,
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& weight,
|
||||
at::Tensor& scales,
|
||||
c10::optional<at::Tensor>& bias,
|
||||
int num_bits,
|
||||
int activation_raw);
|
||||
@@ -0,0 +1,64 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from ... import DSKernelBase
|
||||
from ....inference_utils import ActivationType, DtypeEnum
|
||||
from deepspeed.ops.op_builder import InferenceCutlassBuilder
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class MixedGEMM(DSKernelBase):
|
||||
"""
|
||||
CUTLASS implementation of MoE GEMM.
|
||||
"""
|
||||
|
||||
supported_dtypes = [DtypeEnum.fp16, DtypeEnum.bf16]
|
||||
supported_act_fns = [ActivationType.GELU, ActivationType.SILU, ActivationType.RELU, ActivationType.IDENTITY]
|
||||
|
||||
def __init__(self, fp_dtype: DtypeEnum, act_fn: ActivationType, num_bits: int) -> None:
|
||||
|
||||
if not isinstance(fp_dtype, DtypeEnum):
|
||||
fp_dtype = DtypeEnum(fp_dtype)
|
||||
|
||||
if fp_dtype not in MixedGEMM.supported_dtypes:
|
||||
raise ValueError("Unsupported data type: {}, supported_dtypes are {}".format(
|
||||
fp_dtype, MixedGEMM.supported_dtypes))
|
||||
|
||||
if act_fn not in MixedGEMM.supported_act_fns:
|
||||
raise ValueError("Unsupported activation function: {}, supported_act_fns are {}".format(
|
||||
act_fn, MixedGEMM.supported_act_fns))
|
||||
|
||||
if num_bits != 4 and num_bits != 8:
|
||||
raise ValueError("Unsupported num_bits: {}, supported num_bits are 4 and 8".format(num_bits))
|
||||
|
||||
inf_module = InferenceCutlassBuilder().load()
|
||||
self.num_bits = num_bits
|
||||
self.kernel = inf_module.moe_gemm
|
||||
self.act_fn = act_fn
|
||||
|
||||
def __call__(self,
|
||||
output: torch.Tensor,
|
||||
hidden_states: torch.Tensor,
|
||||
weights: torch.Tensor,
|
||||
scales: torch.Tensor,
|
||||
biases: Optional[torch.Tensor] = None) -> None:
|
||||
"""
|
||||
Performs a MoE GEMM. Note that the stride between token inputs must be even (the distance between byte 1 of token 0 and token 1 must be the same as the distance between byte 1 of token 1 and token 2).
|
||||
|
||||
Arguments:
|
||||
output (torch.Tensor): The output of the MoE GEMM of shape [n_tokens, out_neurons].
|
||||
hidden_states (torch.Tensor): The direct input for the MoE GEMM of shape [n_tokens, in_neurons].
|
||||
weights (torch.Tensor): The weights of shape [in_neurons, out_neurons]. These weights must be contiguous.
|
||||
scales (torch.Tensor): The scales of shape [out_neurons]. These scales must be contiguous.
|
||||
biases (torch.Tensor): The biases of shape [out_neurons]. These biases must be contiguous.
|
||||
|
||||
Returns:
|
||||
output
|
||||
"""
|
||||
self.kernel(output, hidden_states, weights, biases, self.num_bits, self.act_fn)
|
||||
return output
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "activation_type.h"
|
||||
#include "weight_variant.h"
|
||||
|
||||
namespace fastertransformer {
|
||||
|
||||
template <typename T, WeightVariant V>
|
||||
class CutlassFpAIntBGemmRunner {
|
||||
public:
|
||||
void gemm(const T* A,
|
||||
const char* B,
|
||||
const T* weight_scales,
|
||||
T* C,
|
||||
int m,
|
||||
int n,
|
||||
int k,
|
||||
char* workspace_ptr,
|
||||
const size_t workspace_bytes,
|
||||
cudaStream_t stream);
|
||||
|
||||
void gemm_bias_act(const T* A,
|
||||
const char* B,
|
||||
const T* weight_scales,
|
||||
const T* biases,
|
||||
T* C,
|
||||
int m,
|
||||
int n,
|
||||
int k,
|
||||
ActivationType activation_type,
|
||||
char* workspace_ptr,
|
||||
const size_t workspace_bytes,
|
||||
cudaStream_t stream);
|
||||
};
|
||||
|
||||
} // namespace fastertransformer
|
||||
|
||||
template <typename T, WeightVariant V>
|
||||
class MixedGemmContext {
|
||||
public:
|
||||
MixedGemmContext() { _runner = new fastertransformer::CutlassFpAIntBGemmRunner<T, V>(); }
|
||||
|
||||
virtual ~MixedGemmContext() { delete _runner; }
|
||||
|
||||
static MixedGemmContext& Instance()
|
||||
{
|
||||
static MixedGemmContext _ctx;
|
||||
return _ctx;
|
||||
}
|
||||
|
||||
fastertransformer::CutlassFpAIntBGemmRunner<T, V>* GeMM_Runner() const { return _runner; }
|
||||
|
||||
fastertransformer::CutlassFpAIntBGemmRunner<T, V>* _runner;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .mixed_moe_gemm import *
|
||||
from .moe_gemm import *
|
||||
@@ -0,0 +1,67 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from ... import DSKernelBase
|
||||
from ....inference_utils import ActivationType, DtypeEnum
|
||||
from deepspeed.ops.op_builder import InferenceCutlassBuilder
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class MixedMoEGEMM(DSKernelBase):
|
||||
"""
|
||||
CUTLASS implementation of MoE GEMM.
|
||||
"""
|
||||
|
||||
supported_dtypes = [DtypeEnum.fp16, DtypeEnum.bf16]
|
||||
supported_act_fns = [ActivationType.GELU, ActivationType.SILU, ActivationType.RELU, ActivationType.IDENTITY]
|
||||
|
||||
def __init__(self, fp_dtype: DtypeEnum, act_fn: ActivationType, num_bits: int) -> None:
|
||||
|
||||
if not isinstance(fp_dtype, DtypeEnum):
|
||||
fp_dtype = DtypeEnum(fp_dtype)
|
||||
|
||||
if fp_dtype not in MixedMoEGEMM.supported_dtypes:
|
||||
raise ValueError("Unsupported data type: {}, supported_dtypes are {}".format(
|
||||
fp_dtype, MixedMoEGEMM.supported_dtypes))
|
||||
|
||||
if act_fn not in MixedMoEGEMM.supported_act_fns:
|
||||
raise ValueError("Unsupported activation function: {}, supported_act_fns are {}".format(
|
||||
act_fn, MixedMoEGEMM.supported_act_fns))
|
||||
|
||||
if num_bits != 4 and num_bits != 8:
|
||||
raise ValueError("Unsupported num_bits: {}, supported num_bits are 4 and 8".format(num_bits))
|
||||
|
||||
inf_module = InferenceCutlassBuilder().load()
|
||||
self.num_bits = num_bits
|
||||
self.kernel = inf_module.moe_gemm
|
||||
self.act_fn = act_fn
|
||||
|
||||
def __call__(self,
|
||||
ordered_output: torch.Tensor,
|
||||
ordered_input: torch.Tensor,
|
||||
weights: torch.Tensor,
|
||||
scales: torch.Tensor,
|
||||
total_rows_before_expert: torch.Tensor,
|
||||
biases: Optional[torch.Tensor] = None) -> None:
|
||||
"""
|
||||
Performs a MoE GEMM. Note that the stride between token inputs must be even (the distance between byte 1 of token 0 and token 1 must be the same as the distance between byte 1 of token 1 and token 2).
|
||||
|
||||
Arguments:
|
||||
ordered_output (torch.Tensor): The output of the MoE GEMM of shape [n_tokens, out_neurons].
|
||||
ordered_input (torch.Tensor): The direct input for the MoE GEMM of shape [n_tokens, in_neurons].
|
||||
weights (torch.Tensor): The weights of shape [n_experts, in_neurons, out_neurons]. These weights must be contiguous.
|
||||
scales (torch.Tensor): The scales of shape [n_experts, out_neurons]. These scales must be contiguous.
|
||||
total_rows_before_expert (torch.Tensor): The total number of rows before each expert of shape [n_experts].
|
||||
biases (torch.Tensor): The biases of shape [n_experts, out_neurons]. These biases must be contiguous.
|
||||
|
||||
Returns:
|
||||
ordered_output
|
||||
"""
|
||||
self.kernel(ordered_output, ordered_input, weights, scales, biases, total_rows_before_expert, self.num_bits,
|
||||
self.act_fn)
|
||||
return ordered_output
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include "moe_gemm.h"
|
||||
#include "moe_gemm_api.h"
|
||||
#include "weight_variant.h"
|
||||
|
||||
// Switch helpers inspired by
|
||||
// https://github.com/NVIDIA/DALI/blob/main/include/dali/core/static_switch.h
|
||||
// and https://github.com/pytorch/pytorch/blob/master/aten/src/ATen/Dispatch.h
|
||||
|
||||
#define HIDDEN_DTYPE_SWITCH(COND, ...) \
|
||||
[&] { \
|
||||
if (COND) { \
|
||||
using ActivationDtype = __half; \
|
||||
constexpr WeightVariant WVariant = WeightVariant::kFP16; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
using ActivationDtype = __nv_bfloat16; \
|
||||
constexpr WeightVariant WVariant = WeightVariant::kBF16; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
}()
|
||||
|
||||
void moe_gemm(at::Tensor& output,
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& weight,
|
||||
c10::optional<at::Tensor>& bias,
|
||||
at::Tensor& total_rows_before_expert,
|
||||
int activation_raw)
|
||||
{
|
||||
TORCH_CHECK(output.dtype() == hidden_states.dtype(),
|
||||
"Output and hidden states must have the same dtype");
|
||||
TORCH_CHECK(output.dtype() == weight.dtype(), "Output and weight must have the same dtype");
|
||||
|
||||
int64_t total_rows = hidden_states.size(0);
|
||||
int64_t gemm_k = hidden_states.size(1);
|
||||
int64_t gemm_n = weight.size(2);
|
||||
int num_experts = weight.size(0);
|
||||
|
||||
TORCH_CHECK(total_rows == output.size(0), "Total rows dimension mismatch");
|
||||
TORCH_CHECK(gemm_k == weight.size(1), "GEMM K dimension mismatch");
|
||||
TORCH_CHECK(gemm_n == output.size(1), "GEMM N dimension mismatch");
|
||||
TORCH_CHECK(num_experts == total_rows_before_expert.size(0), "Number of experts mismatch");
|
||||
|
||||
HIDDEN_DTYPE_SWITCH(hidden_states.dtype() == torch::kFloat16, [&] {
|
||||
fastertransformer::MoeGemmRunner<ActivationDtype, WVariant> runner =
|
||||
*MoeGemmContext<ActivationDtype, WVariant>::Instance().GeMM_Runner();
|
||||
|
||||
ActivationType activation_type = (ActivationType)activation_raw;
|
||||
if (!bias.has_value() && activation_type == ActivationType::IDENTITY) {
|
||||
runner.moe_gemm((ActivationDtype*)hidden_states.data_ptr(),
|
||||
(char*)weight.data_ptr(),
|
||||
nullptr,
|
||||
(ActivationDtype*)output.data_ptr(),
|
||||
(int64_t*)total_rows_before_expert.data_ptr(),
|
||||
total_rows,
|
||||
gemm_n,
|
||||
gemm_k,
|
||||
num_experts,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
return;
|
||||
} else {
|
||||
ActivationDtype* bias_ptr = nullptr;
|
||||
if (bias.has_value()) {
|
||||
bias_ptr = (ActivationDtype*)bias.value().data_ptr();
|
||||
TORCH_CHECK(num_experts == bias.value().size(0), "Number of experts mismatch");
|
||||
TORCH_CHECK(gemm_n == bias.value().size(1), "GEMM N dimension mismatch");
|
||||
}
|
||||
runner.moe_gemm_bias_act((ActivationDtype*)hidden_states.data_ptr(),
|
||||
(char*)weight.data_ptr(),
|
||||
nullptr,
|
||||
bias_ptr,
|
||||
(ActivationDtype*)output.data_ptr(),
|
||||
(int64_t*)total_rows_before_expert.data_ptr(),
|
||||
total_rows,
|
||||
gemm_n,
|
||||
gemm_k,
|
||||
num_experts,
|
||||
activation_type,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#define ACT_DTYPE_SWITCH(COND, ...) \
|
||||
[&] { \
|
||||
if (COND) { \
|
||||
using ActivationDtype = __half; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
using ActivationDtype = __nv_bfloat16; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
}()
|
||||
|
||||
#define WEIGHT_VARIANT_SWITCH(COND, ...) \
|
||||
[&] { \
|
||||
if (COND) { \
|
||||
constexpr WeightVariant WVariant = WeightVariant::kFP8; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
constexpr WeightVariant WVariant = WeightVariant::kFP4; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
}()
|
||||
|
||||
void mixed_moe_gemm(at::Tensor& output,
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& weight,
|
||||
at::Tensor& scales,
|
||||
c10::optional<at::Tensor>& bias,
|
||||
at::Tensor& total_rows_before_expert,
|
||||
int num_bits,
|
||||
int activation_raw)
|
||||
{
|
||||
TORCH_CHECK(output.dtype() == hidden_states.dtype(),
|
||||
"Output and hidden states must have the same dtype");
|
||||
|
||||
int64_t total_rows = hidden_states.size(0);
|
||||
int64_t gemm_k = hidden_states.size(1);
|
||||
int64_t gemm_n = weight.size(2);
|
||||
int num_experts = weight.size(0);
|
||||
|
||||
TORCH_CHECK(total_rows == output.size(0), "Total rows dimension mismatch");
|
||||
TORCH_CHECK(gemm_k == weight.size(1), "GEMM K dimension mismatch");
|
||||
TORCH_CHECK(gemm_n == output.size(1), "GEMM N dimension mismatch");
|
||||
TORCH_CHECK(num_experts == total_rows_before_expert.size(0), "Number of experts mismatch");
|
||||
|
||||
ACT_DTYPE_SWITCH(hidden_states.dtype() == torch::kFloat16, [&] {
|
||||
WEIGHT_VARIANT_SWITCH(num_bits == 8, [&] {
|
||||
fastertransformer::MoeGemmRunner<ActivationDtype, WVariant> runner =
|
||||
*MoeGemmContext<ActivationDtype, WVariant>::Instance().GeMM_Runner();
|
||||
|
||||
ActivationType activation_type = (ActivationType)activation_raw;
|
||||
if (!bias.has_value() && activation_type == ActivationType::IDENTITY) {
|
||||
runner.moe_gemm((ActivationDtype*)hidden_states.data_ptr(),
|
||||
(char*)weight.data_ptr(),
|
||||
(ActivationDtype*)scales.data_ptr(),
|
||||
(ActivationDtype*)output.data_ptr(),
|
||||
(int64_t*)total_rows_before_expert.data_ptr(),
|
||||
total_rows,
|
||||
gemm_n,
|
||||
gemm_k,
|
||||
num_experts,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
return;
|
||||
} else {
|
||||
ActivationDtype* bias_ptr = nullptr;
|
||||
if (bias.has_value()) {
|
||||
bias_ptr = (ActivationDtype*)bias.value().data_ptr();
|
||||
TORCH_CHECK(num_experts == bias.value().size(0), "Number of experts mismatch");
|
||||
TORCH_CHECK(gemm_n == bias.value().size(1), "GEMM N dimension mismatch");
|
||||
}
|
||||
runner.moe_gemm_bias_act((ActivationDtype*)hidden_states.data_ptr(),
|
||||
(char*)weight.data_ptr(),
|
||||
(ActivationDtype*)scales.data_ptr(),
|
||||
bias_ptr,
|
||||
(ActivationDtype*)output.data_ptr(),
|
||||
(int64_t*)total_rows_before_expert.data_ptr(),
|
||||
total_rows,
|
||||
gemm_n,
|
||||
gemm_k,
|
||||
num_experts,
|
||||
activation_type,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <torch/extension.h>
|
||||
|
||||
void moe_gemm(at::Tensor& output,
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& weight,
|
||||
c10::optional<at::Tensor>& bias,
|
||||
at::Tensor& total_rows_before_expert,
|
||||
int activation_raw);
|
||||
|
||||
void mixed_moe_gemm(at::Tensor& output,
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& weight,
|
||||
at::Tensor& scales,
|
||||
c10::optional<at::Tensor>& bias,
|
||||
at::Tensor& total_rows_before_expert,
|
||||
int num_bits,
|
||||
int activation_raw);
|
||||
@@ -0,0 +1,60 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from ... import DSKernelBase
|
||||
from ....inference_utils import ActivationType, DtypeEnum
|
||||
from deepspeed.ops.op_builder import InferenceCutlassBuilder
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class MoEGEMM(DSKernelBase):
|
||||
"""
|
||||
CUTLASS implementation of MoE GEMM.
|
||||
"""
|
||||
|
||||
supported_dtypes = [DtypeEnum.fp16, DtypeEnum.bf16]
|
||||
supported_act_fns = [ActivationType.GELU, ActivationType.SILU, ActivationType.RELU, ActivationType.IDENTITY]
|
||||
|
||||
def __init__(self, fp_dtype: DtypeEnum, act_fn: ActivationType) -> None:
|
||||
|
||||
if not isinstance(fp_dtype, DtypeEnum):
|
||||
fp_dtype = DtypeEnum(fp_dtype)
|
||||
|
||||
if fp_dtype not in MoEGEMM.supported_dtypes:
|
||||
raise ValueError("Unsupported data type: {}, supported_dtypes are {}".format(
|
||||
fp_dtype, MoEGEMM.supported_dtypes))
|
||||
|
||||
if act_fn not in MoEGEMM.supported_act_fns:
|
||||
raise ValueError("Unsupported activation function: {}, supported_act_fns are {}".format(
|
||||
act_fn, MoEGEMM.supported_act_fns))
|
||||
|
||||
inf_module = InferenceCutlassBuilder().load()
|
||||
self.kernel = inf_module.moe_gemm
|
||||
self.act_fn = act_fn
|
||||
|
||||
def __call__(self,
|
||||
ordered_output: torch.Tensor,
|
||||
ordered_input: torch.Tensor,
|
||||
weights: torch.Tensor,
|
||||
total_rows_before_expert: torch.Tensor,
|
||||
biases: Optional[torch.Tensor] = None) -> None:
|
||||
"""
|
||||
Performs a MoE GEMM. Note that the stride between token inputs must be even (the distance between byte 1 of token 0 and token 1 must be the same as the distance between byte 1 of token 1 and token 2).
|
||||
|
||||
Arguments:
|
||||
ordered_output (torch.Tensor): The output of the MoE GEMM of shape [n_tokens, out_neurons].
|
||||
ordered_input (torch.Tensor): The direct input for the MoE GEMM of shape [n_tokens, in_neurons].
|
||||
weights (torch.Tensor): The weights of shape [n_experts, in_neurons, out_neurons]. These weights must be contiguous.
|
||||
total_rows_before_expert (torch.Tensor): The total number of rows before each expert of shape [n_experts].
|
||||
biases (torch.Tensor): The biases of shape [n_experts, out_neurons]. These biases must be contiguous.
|
||||
|
||||
Returns:
|
||||
ordered_output
|
||||
"""
|
||||
self.kernel(ordered_output, ordered_input, weights, biases, total_rows_before_expert, self.act_fn)
|
||||
return ordered_output
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "activation_type.h"
|
||||
#include "weight_variant.h"
|
||||
|
||||
namespace fastertransformer {
|
||||
|
||||
template <typename T, /*The type used for activations/scales/compute*/
|
||||
WeightVariant V /* The type for the MoE weights */>
|
||||
class MoeGemmRunner {
|
||||
public:
|
||||
MoeGemmRunner();
|
||||
|
||||
void moe_gemm_bias_act(const T* A,
|
||||
const char* B,
|
||||
const T* weight_scales,
|
||||
const T* biases,
|
||||
T* C,
|
||||
int64_t* total_rows_before_expert,
|
||||
int64_t total_rows,
|
||||
int64_t gemm_n,
|
||||
int64_t gemm_k,
|
||||
int num_experts,
|
||||
ActivationType activation_type,
|
||||
cudaStream_t stream);
|
||||
|
||||
void moe_gemm(const T* A,
|
||||
const char* B,
|
||||
const T* weight_scales,
|
||||
T* C,
|
||||
int64_t* total_rows_before_expert,
|
||||
int64_t total_rows,
|
||||
int64_t gemm_n,
|
||||
int64_t gemm_k,
|
||||
int num_experts,
|
||||
cudaStream_t stream);
|
||||
|
||||
private:
|
||||
int sm_;
|
||||
int multi_processor_count_;
|
||||
};
|
||||
|
||||
} // namespace fastertransformer
|
||||
|
||||
template <typename T, WeightVariant V>
|
||||
class MoeGemmContext {
|
||||
public:
|
||||
MoeGemmContext() { _runner = new fastertransformer::MoeGemmRunner<T, V>(); }
|
||||
|
||||
virtual ~MoeGemmContext() { delete _runner; }
|
||||
|
||||
static MoeGemmContext& Instance()
|
||||
{
|
||||
static MoeGemmContext _ctx;
|
||||
return _ctx;
|
||||
}
|
||||
|
||||
fastertransformer::MoeGemmRunner<T, V>* GeMM_Runner() const { return _runner; }
|
||||
|
||||
fastertransformer::MoeGemmRunner<T, V>* _runner;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
// Data structure that allows us to abstract internal CUTLASS datatypes/mappings
|
||||
// to the DeepSpeed-Kernels repo.
|
||||
|
||||
#pragma once
|
||||
|
||||
enum WeightVariant { kFP16, kBF16, kFP8, kFP4 };
|
||||
@@ -0,0 +1,32 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class DSKernelBase(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
If necessary trigger compilation and warmup
|
||||
Autotuning of the kernel would happen at this stage to
|
||||
eliminate any potential hangs that might occur mid-deployment
|
||||
Validate that the desired run configuration is compatible.
|
||||
|
||||
It is not necessary to call super on this method.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""
|
||||
However the kernel needs to be called, it can be called here. Auto-tuning
|
||||
should never be performed here.
|
||||
|
||||
All inputs/outputs should be passed as arguments to this function. No allocations
|
||||
should be performed here.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
enum ActivationType {
|
||||
GELU = 0,
|
||||
RELU = 1,
|
||||
SILU = 2,
|
||||
GEGLU = 3,
|
||||
ReGLU = 4,
|
||||
SiGLU = 5,
|
||||
IDENTITY = 6,
|
||||
InvalidType = -1
|
||||
};
|
||||
@@ -0,0 +1,708 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ds_kernel_utils.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
#include <cuda_bf16.h>
|
||||
#endif
|
||||
|
||||
namespace conversion {
|
||||
|
||||
// Basic primitive for constructing conversions
|
||||
template <typename TO, typename FROM>
|
||||
DS_D_INLINE TO to(FROM val)
|
||||
{
|
||||
return to(val);
|
||||
}
|
||||
|
||||
// Specializations
|
||||
|
||||
/********************* Identity Conversions *********************/
|
||||
/*
|
||||
Identity conversions are useful in templated functions where we might have
|
||||
a fixed destination type. For example, I might have a kernel that accepts
|
||||
__half, __nv_bfloat16, and float but always want to do the core computation
|
||||
at floating point:
|
||||
|
||||
T mem_value = input[idx];
|
||||
float compute_value = conversion::to<float, T>(mem_value);
|
||||
|
||||
In practice, we should be able to elide the second template parameter:
|
||||
float compute_val = conversion::to<float>(mem_value);
|
||||
|
||||
In this case, we need an implementation to handle the T = float case
|
||||
|
||||
NOTE: The type inferencing system appears to be unable to handle inferring the first
|
||||
template parameter, even in the trivial case.
|
||||
*/
|
||||
|
||||
// Floating point types
|
||||
template <>
|
||||
DS_D_INLINE double to(double val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE float to(float val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half to(__half val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(__nv_bfloat16 val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Integer types
|
||||
template <>
|
||||
DS_D_INLINE int8_t to(int8_t val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint8_t to(uint8_t val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int16_t to(int16_t val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint16_t to(uint16_t val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int32_t to(int32_t val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint32_t to(uint32_t val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int64_t to(int64_t val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint64_t to(uint64_t val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
// TODO: evaluate if we want bools
|
||||
|
||||
/********************* To Double Conversions *********************/
|
||||
|
||||
// * to double variants
|
||||
|
||||
// Would normally like to not use C cast, but this is an important enough conversion
|
||||
// to keep
|
||||
template <>
|
||||
DS_D_INLINE double to(float val)
|
||||
{
|
||||
#ifdef PTX_AVAILABLE
|
||||
double ret_val;
|
||||
asm("ctv.rn.f64.f32 %0, %1;\n" : "=d"(ret_val) : "f"(val));
|
||||
return ret_val;
|
||||
#else
|
||||
return double(val);
|
||||
#endif
|
||||
}
|
||||
// Note: there is a CVT instruction for __half -> double, but there's no inline interface
|
||||
// for passing a single half value
|
||||
template <>
|
||||
DS_D_INLINE double to(__half val)
|
||||
{
|
||||
return to<double>(__half2float(val));
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE double to(int64_t val)
|
||||
{
|
||||
return __ll2double_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE double to(int32_t val)
|
||||
{
|
||||
return __int2double_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE double to(int16_t val)
|
||||
{
|
||||
return __int2double_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE double to(int8_t val)
|
||||
{
|
||||
return __int2double_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE double to(uint64_t val)
|
||||
{
|
||||
return __ull2double_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE double to(uint32_t val)
|
||||
{
|
||||
return __uint2double_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE double to(uint16_t val)
|
||||
{
|
||||
return __uint2double_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE double to(uint8_t val)
|
||||
{
|
||||
return __uint2double_rn(val);
|
||||
}
|
||||
|
||||
// Same applies here
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE double to(__nv_bfloat16 val)
|
||||
{
|
||||
return to<double>(__bfloat162float(val));
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To Float Conversions *********************/
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float to(double val)
|
||||
{
|
||||
return __double2float_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE float to(__half val)
|
||||
{
|
||||
return __half2float(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE float to(int64_t val)
|
||||
{
|
||||
return __ll2float_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE float to(int32_t val)
|
||||
{
|
||||
return __int2float_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE float to(int16_t val)
|
||||
{
|
||||
return __int2float_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE float to(int8_t val)
|
||||
{
|
||||
return __int2float_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE float to(uint64_t val)
|
||||
{
|
||||
return __ull2float_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE float to(uint32_t val)
|
||||
{
|
||||
return __uint2float_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE float to(uint16_t val)
|
||||
{
|
||||
return __uint2float_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE float to(uint8_t val)
|
||||
{
|
||||
return __uint2float_rn(val);
|
||||
}
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE float to(__nv_bfloat16 val)
|
||||
{
|
||||
return __bfloat162float(val);
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To Float2 Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE float2 to(__half2 val)
|
||||
{
|
||||
return __half22float2(val);
|
||||
}
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE float2 to(__nv_bfloat162 val)
|
||||
{
|
||||
return __bfloat1622float2(val);
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To Half Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE __half to(double val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
float val_f = __double2float_rn(val);
|
||||
return __float2half(val_f);
|
||||
#else
|
||||
return __double2half(val);
|
||||
#endif
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half to(float val)
|
||||
{
|
||||
return __float2half(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half to(int64_t val)
|
||||
{
|
||||
return __ll2half_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half to(int32_t val)
|
||||
{
|
||||
return __int2half_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half to(int16_t val)
|
||||
{
|
||||
return __short2half_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half to(int8_t val)
|
||||
{
|
||||
return __int2half_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half to(uint64_t val)
|
||||
{
|
||||
return __ull2half_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half to(uint32_t val)
|
||||
{
|
||||
return __uint2half_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half to(uint16_t val)
|
||||
{
|
||||
return __ushort2half_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half to(uint8_t val)
|
||||
{
|
||||
return __uint2half_rn(val);
|
||||
}
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
// No direct conversion
|
||||
template <>
|
||||
DS_D_INLINE __half to(__nv_bfloat16 val)
|
||||
{
|
||||
return to<__half>(to<float>(val));
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To Half2 Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE __half2 to(float2 val)
|
||||
{
|
||||
return __float22half2_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __half2 to(float val)
|
||||
{
|
||||
return __float2half2_rn(val);
|
||||
}
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
// No direct conversion
|
||||
template <>
|
||||
DS_D_INLINE __half2 to(__nv_bfloat162 val)
|
||||
{
|
||||
return to<__half2>(to<float2>(val));
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To BF16 Conversions *********************/
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(double val)
|
||||
{
|
||||
return __double2bfloat16(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(float val)
|
||||
{
|
||||
return __float2bfloat16(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(int64_t val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __double2bfloat16(__ll2double_rn(val));
|
||||
#else
|
||||
return __ll2bfloat16_rn(val);
|
||||
#endif
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(int32_t val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2bfloat16(__int2float_rn(val));
|
||||
#else
|
||||
return __int2bfloat16_rn(val);
|
||||
#endif
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(int16_t val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2bfloat16(__int2float_rn(val));
|
||||
#else
|
||||
return __short2bfloat16_rn(val);
|
||||
#endif
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(int8_t val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2bfloat16(__int2float_rn(val));
|
||||
#else
|
||||
return __int2bfloat16_rn(val);
|
||||
#endif
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(uint64_t val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __double2bfloat16(__ull2double_rn(val));
|
||||
#else
|
||||
return __ull2bfloat16_rn(val);
|
||||
#endif
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(uint32_t val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2bfloat16(__uint2float_rn(val));
|
||||
#else
|
||||
return __uint2bfloat16_rn(val);
|
||||
#endif
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(uint16_t val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2bfloat16(__uint2float_rn(val));
|
||||
#else
|
||||
return __ushort2bfloat16_rn(val);
|
||||
#endif
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat16 to(uint8_t val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2bfloat16(__uint2float_rn(val));
|
||||
#else
|
||||
return __uint2bfloat16_rn(val);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To BF162 Conversions *********************/
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat162 to(float2 val)
|
||||
{
|
||||
return __float22bfloat162_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat162 to(float val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __bfloat162bfloat162(__float2bfloat16(val));
|
||||
#else
|
||||
return __float2bfloat162_rn(val);
|
||||
#endif
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE __nv_bfloat162 to(__half2 val)
|
||||
{
|
||||
return to<__nv_bfloat162>(to<float2>(val));
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To INT64_T Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE int64_t to(double val)
|
||||
{
|
||||
return __double2ll_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int64_t to(float val)
|
||||
{
|
||||
return __float2ll_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int64_t to(__half val)
|
||||
{
|
||||
return __half2ll_rn(val);
|
||||
}
|
||||
// No direct support for integer casts at the C++ level and I don't feel they're so important
|
||||
// to demand an PTX at this time
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE int64_t to(__nv_bfloat16 val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2ll_rn(__bfloat162float(val));
|
||||
#else
|
||||
return __bfloat162ll_rn(val);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To INT32_T Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE int32_t to(double val)
|
||||
{
|
||||
return __double2int_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int32_t to(float val)
|
||||
{
|
||||
return __float2int_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int32_t to(__half val)
|
||||
{
|
||||
return __half2int_rn(val);
|
||||
}
|
||||
// No direct support for integer casts at the C++ level and I don't feel they're so important
|
||||
// to demand an PTX at this time
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE int32_t to(__nv_bfloat16 val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2int_rn(__bfloat162float(val));
|
||||
#else
|
||||
return __bfloat162int_rn(val);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To INT16_T Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE int16_t to(double val)
|
||||
{
|
||||
return __double2int_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int16_t to(float val)
|
||||
{
|
||||
return __float2int_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int16_t to(__half val)
|
||||
{
|
||||
return __half2int_rn(val);
|
||||
}
|
||||
// No direct support for integer casts at the C++ level and I don't feel they're so important
|
||||
// to demand an PTX at this time
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE int16_t to(__nv_bfloat16 val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2int_rn(__bfloat162float(val));
|
||||
#else
|
||||
return __bfloat162int_rn(val);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To INT8_T Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE int8_t to(double val)
|
||||
{
|
||||
return __double2int_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int8_t to(float val)
|
||||
{
|
||||
return __float2int_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE int8_t to(__half val)
|
||||
{
|
||||
return __half2int_rn(val);
|
||||
}
|
||||
// No direct support for integer casts at the C++ level and I don't feel they're so important
|
||||
// to demand an PTX at this time
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE int8_t to(__nv_bfloat16 val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2int_rn(__bfloat162float(val));
|
||||
#else
|
||||
return __bfloat162int_rn(val);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To UINT64_T Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE uint64_t to(double val)
|
||||
{
|
||||
return __double2ull_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint64_t to(float val)
|
||||
{
|
||||
return __float2ull_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint64_t to(__half val)
|
||||
{
|
||||
return __half2ull_rn(val);
|
||||
}
|
||||
// No direct support for integer casts at the C++ level and I don't feel they're so important
|
||||
// to demand an PTX at this time
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE uint64_t to(__nv_bfloat16 val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2ull_rn(__bfloat162float(val));
|
||||
#else
|
||||
return __bfloat162ull_rn(val);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To UINT32_T Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE uint32_t to(double val)
|
||||
{
|
||||
return __double2uint_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint32_t to(float val)
|
||||
{
|
||||
return __float2uint_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint32_t to(__half val)
|
||||
{
|
||||
return __half2uint_rn(val);
|
||||
}
|
||||
// No direct support for integer casts at the C++ level and I don't feel they're so important
|
||||
// to demand an PTX at this time
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE uint32_t to(__nv_bfloat16 val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2uint_rn(__bfloat162float(val));
|
||||
#else
|
||||
return __bfloat162uint_rn(val);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To UINT16_T Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE uint16_t to(double val)
|
||||
{
|
||||
return __double2uint_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint16_t to(float val)
|
||||
{
|
||||
return __float2uint_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint16_t to(__half val)
|
||||
{
|
||||
return __half2uint_rn(val);
|
||||
}
|
||||
// No direct support for integer casts at the C++ level and I don't feel they're so important
|
||||
// to demand an PTX at this time
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE uint16_t to(__nv_bfloat16 val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2uint_rn(__bfloat162float(val));
|
||||
#else
|
||||
return __bfloat162uint_rn(val);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/********************* To UINT8_T Conversions *********************/
|
||||
template <>
|
||||
DS_D_INLINE uint8_t to(double val)
|
||||
{
|
||||
return __double2uint_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint8_t to(float val)
|
||||
{
|
||||
return __float2uint_rn(val);
|
||||
}
|
||||
template <>
|
||||
DS_D_INLINE uint8_t to(__half val)
|
||||
{
|
||||
return __half2uint_rn(val);
|
||||
}
|
||||
// No direct support for integer casts at the C++ level and I don't feel they're so important
|
||||
// to demand an PTX at this time
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
template <>
|
||||
DS_D_INLINE uint8_t to(__nv_bfloat16 val)
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __float2uint_rn(__bfloat162float(val));
|
||||
#else
|
||||
return __bfloat162uint_rn(val);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace conversion
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
/*
|
||||
Centralized header file for preprocessor macros and constants
|
||||
used throughout the codebase.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cuda.h>
|
||||
#include <cuda_fp16.h>
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
#include <cuda_bf16.h>
|
||||
#endif
|
||||
|
||||
#define DS_HD_INLINE __host__ __device__ __forceinline__
|
||||
#define DS_D_INLINE __device__ __forceinline__
|
||||
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
|
||||
// constexpr variant of warpSize for templating
|
||||
constexpr int hw_warp_size = ROCM_WAVEFRONT_SIZE;
|
||||
#define HALF_PRECISION_AVAILABLE = 1
|
||||
#include <hip/hip_cooperative_groups.h>
|
||||
#include <hip/hip_fp16.h>
|
||||
|
||||
#else // !__HIP_PLATFORM_AMD__
|
||||
|
||||
// constexpr variant of warpSize for templating
|
||||
constexpr int hw_warp_size = 32;
|
||||
|
||||
#if __CUDA_ARCH__ >= 530
|
||||
#define HALF_PRECISION_AVAILABLE = 1
|
||||
#define PTX_AVAILABLE
|
||||
#endif // __CUDA_ARCH__ >= 530
|
||||
|
||||
#if __CUDA_ARCH__ >= 800
|
||||
#define ASYNC_COPY_AVAILABLE
|
||||
#endif // __CUDA_ARCH__ >= 800
|
||||
|
||||
#include <cooperative_groups.h>
|
||||
#include <cuda_fp16.h>
|
||||
|
||||
#endif //__HIP_PLATFORM_AMD__
|
||||
|
||||
inline int next_pow2(const int val)
|
||||
{
|
||||
int rounded_val = val - 1;
|
||||
rounded_val |= rounded_val >> 1;
|
||||
rounded_val |= rounded_val >> 2;
|
||||
rounded_val |= rounded_val >> 4;
|
||||
rounded_val |= rounded_val >> 8;
|
||||
return rounded_val + 1;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,778 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "conversion_utils.h"
|
||||
#include "ds_kernel_utils.h"
|
||||
#include "memory_access_utils.h"
|
||||
|
||||
namespace cg = cooperative_groups;
|
||||
|
||||
namespace reduce {
|
||||
|
||||
enum class ROpType {
|
||||
// Addition
|
||||
Add,
|
||||
|
||||
// Maximum reduction
|
||||
Max,
|
||||
|
||||
// Minimum reduction
|
||||
Min,
|
||||
};
|
||||
|
||||
constexpr int max_threads = 1024;
|
||||
constexpr int max_warps = max_threads / hw_warp_size;
|
||||
|
||||
/*
|
||||
High level API. The API takes in a set of operations and variables
|
||||
and performs that reduction operation on that variable. The reductions
|
||||
of each of the arguments are completely independent of each other (
|
||||
i.e., the val1-op1 combination has no impact on val2-op2).
|
||||
|
||||
Example usage:
|
||||
``` cpp
|
||||
float max_val;
|
||||
float min_val;
|
||||
reduce::block<rop::Max, rop::Min>(tb, warp, max_val, min_val);
|
||||
```
|
||||
|
||||
TODO(cmikeh2): In theory, we might be able to do this sequentially with
|
||||
device functions and rely on the assembler correctly behaving. My initial
|
||||
instinct is this won't work, but if it does it would reduce implementation
|
||||
cost significantly.
|
||||
|
||||
TODO(cmikeh2): We need to support sub-block reductions. The warp intrinsic
|
||||
currently supports this (more incidentally than anything else). It is not
|
||||
uncommon in something like softmax or a fused attention kernel to map multiple
|
||||
reductions to a thread block, but each reduction itself is only scoped
|
||||
to part of the threads (i.e block size = 512, 128 threads per reduction).
|
||||
*/
|
||||
template <ROpType Op, int warp_bound = max_warps>
|
||||
DS_D_INLINE void block(cg::thread_block& tb, cg::thread_block_tile<hw_warp_size>& warp, float& val);
|
||||
|
||||
template <ROpType Op1, ROpType Op2, int warp_bound = max_warps>
|
||||
DS_D_INLINE void block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2);
|
||||
|
||||
template <ROpType Op1, ROpType Op2, ROpType Op3, int warp_bound = max_warps>
|
||||
DS_D_INLINE void block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2,
|
||||
float& val3);
|
||||
|
||||
template <ROpType Op1, ROpType Op2, ROpType Op3, ROpType Op4, int warp_bound = max_warps>
|
||||
DS_D_INLINE void block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2,
|
||||
float& val3,
|
||||
float& val4);
|
||||
|
||||
/*
|
||||
The partitioned block is a special case of the above where in the warps of a threadblock are
|
||||
partitioned into separate independent reductions. For example, I might have an 8 warp thread block
|
||||
in which each pair of warps is processing an independent piece of data. I would then reduce that
|
||||
data with the something like the following:
|
||||
``` cpp
|
||||
float max_val;
|
||||
reduce::partitioned_block<rop::Max, 2>(tb, warp, max_val);
|
||||
```
|
||||
After which, each pair of warps would have coherent data with each other. Note, this API will not
|
||||
provide correct results if the number of warps per partition is not a power of 2.
|
||||
*/
|
||||
template <ROpType Op, int num_threads>
|
||||
DS_D_INLINE void partitioned_block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val);
|
||||
|
||||
template <ROpType Op1, ROpType Op2, int num_threads>
|
||||
DS_D_INLINE void partitioned_block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2);
|
||||
|
||||
template <ROpType Op1, ROpType Op2, ROpType Op3, int num_threads>
|
||||
DS_D_INLINE void partitioned_block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2,
|
||||
float& val3);
|
||||
|
||||
template <ROpType Op1, ROpType Op2, ROpType Op3, ROpType Op4, int num_threads>
|
||||
DS_D_INLINE void partitioned_block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2,
|
||||
float& val3,
|
||||
float& val4);
|
||||
|
||||
/*
|
||||
Single element reduction primitives. Used inside serial collection
|
||||
loops.
|
||||
|
||||
Example usage:
|
||||
using rop = reduce::OpType;
|
||||
float min = init<rop::Min>();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
min = reduce::element<rop::Min>(min, data[i]);
|
||||
}
|
||||
*/
|
||||
|
||||
template <ROpType Op, typename T>
|
||||
DS_D_INLINE T element(const T lhs, const T rhs);
|
||||
|
||||
template <ROpType OType, typename T = float>
|
||||
DS_D_INLINE T init();
|
||||
|
||||
/********************** Internal reduction APIs **********************/
|
||||
|
||||
/*
|
||||
Single element "reductions". TODO(cmikeh2): this sort of "op" concept
|
||||
should be refactored into its own implementation at some point. This interface
|
||||
may be easily expanded for new types/operations, but the typical reductions
|
||||
we need are covered with min/max/add on float.
|
||||
|
||||
NOTE: there is no mean reduction because that relies on knowledge of how
|
||||
many values were already reduced into each scalar. Implementing this on top
|
||||
of reduce should be straightforward (can just wrap the sum reduction) and
|
||||
would be a good extension of the header.
|
||||
*/
|
||||
|
||||
DS_D_INLINE int _warp_rank()
|
||||
{
|
||||
const int thread_rank =
|
||||
threadIdx.x + threadIdx.y * blockDim.x + threadIdx.z * blockDim.x * blockDim.y;
|
||||
return thread_rank / hw_warp_size;
|
||||
}
|
||||
|
||||
/* Float element reduce implementations */
|
||||
template <>
|
||||
DS_D_INLINE float element<ROpType::Add>(const float lhs, const float rhs)
|
||||
{
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float element<ROpType::Max>(const float lhs, const float rhs)
|
||||
{
|
||||
return fmaxf(lhs, rhs);
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float element<ROpType::Min>(const float lhs, const float rhs)
|
||||
{
|
||||
return fminf(lhs, rhs);
|
||||
}
|
||||
|
||||
/* __half element reduce implementation */
|
||||
template <>
|
||||
DS_D_INLINE __half element<ROpType::Add>(const __half lhs, const __half rhs)
|
||||
{
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE __half element<ROpType::Max>(const __half lhs, const __half rhs)
|
||||
{
|
||||
#if __CUDA_ARCH__ >= 800
|
||||
// Intrinsic limited to Ampere + newer
|
||||
return __hmax(lhs, rhs);
|
||||
#else
|
||||
return (lhs > rhs) ? lhs : rhs;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE __half element<ROpType::Min>(const __half lhs, const __half rhs)
|
||||
{
|
||||
#if __CUDA_ARCH__ >= 800
|
||||
// Intrinsic limited to Ampere + newer
|
||||
return __hmin(lhs, rhs);
|
||||
#else
|
||||
return (lhs < rhs) ? lhs : rhs;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* __half2 element reduce implementation */
|
||||
template <>
|
||||
DS_D_INLINE __half2 element<ROpType::Add>(const __half2 lhs, const __half2 rhs)
|
||||
{
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE __half2 element<ROpType::Max>(const __half2 lhs, const __half2 rhs)
|
||||
{
|
||||
#if __CUDA_ARCH__ >= 800
|
||||
return __hmax2(lhs, rhs);
|
||||
#else
|
||||
__half2 ret_val;
|
||||
ret_val.x = (lhs.x > rhs.x) ? lhs.x : rhs.x;
|
||||
ret_val.y = (lhs.y > rhs.y) ? lhs.y : rhs.y;
|
||||
return ret_val;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE __half2 element<ROpType::Min>(const __half2 lhs, const __half2 rhs)
|
||||
{
|
||||
#if __CUDA_ARCH__ >= 800
|
||||
return __hmin2(lhs, rhs);
|
||||
#else
|
||||
__half2 ret_val;
|
||||
ret_val.x = (lhs.x < rhs.x) ? lhs.x : rhs.x;
|
||||
ret_val.y = (lhs.y < rhs.y) ? lhs.y : rhs.y;
|
||||
return ret_val;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int32_t element<ROpType::Add>(const int32_t lhs, const int32_t rhs)
|
||||
{
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int32_t element<ROpType::Max>(const int32_t lhs, const int32_t rhs)
|
||||
{
|
||||
return (lhs > rhs) ? lhs : rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int32_t element<ROpType::Min>(const int32_t lhs, const int32_t rhs)
|
||||
{
|
||||
return (lhs < rhs) ? lhs : rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE uint32_t element<ROpType::Add>(const uint32_t lhs, const uint32_t rhs)
|
||||
{
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE uint32_t element<ROpType::Max>(const uint32_t lhs, const uint32_t rhs)
|
||||
{
|
||||
return (lhs > rhs) ? lhs : rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE uint32_t element<ROpType::Min>(const uint32_t lhs, const uint32_t rhs)
|
||||
{
|
||||
return (lhs < rhs) ? lhs : rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int64_t element<ROpType::Add>(const int64_t lhs, const int64_t rhs)
|
||||
{
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int64_t element<ROpType::Max>(const int64_t lhs, const int64_t rhs)
|
||||
{
|
||||
return (lhs > rhs) ? lhs : rhs;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int64_t element<ROpType::Min>(const int64_t lhs, const int64_t rhs)
|
||||
{
|
||||
return (lhs < rhs) ? lhs : rhs;
|
||||
}
|
||||
|
||||
/*
|
||||
Reduction initialization primitives
|
||||
*/
|
||||
template <>
|
||||
DS_D_INLINE float init<ROpType::Add>()
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float init<ROpType::Min>()
|
||||
{
|
||||
// Positive infinity
|
||||
return INFINITY;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE float init<ROpType::Max>()
|
||||
{
|
||||
// Negative infinity
|
||||
return -INFINITY;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE __half init<ROpType::Add>()
|
||||
{
|
||||
constexpr __half_raw zero = {0x0000};
|
||||
return __half(zero);
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE __half init<ROpType::Min>()
|
||||
{
|
||||
constexpr __half_raw inf = {0x7C00};
|
||||
return __half(inf);
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE __half init<ROpType::Max>()
|
||||
{
|
||||
constexpr __half_raw neg_inf = {0xFC00};
|
||||
return __half(neg_inf);
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE __half2 init<ROpType::Add>()
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __half2{_Float16_2{0x0000, 0x0000}};
|
||||
#else
|
||||
constexpr __half2_raw zero = {0x0000, 0x0000};
|
||||
return __half2(zero);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE __half2 init<ROpType::Min>()
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __half2{_Float16_2{0x7C00, 0x7C00}};
|
||||
#else
|
||||
constexpr __half2_raw inf = {0x7C00, 0x7C00};
|
||||
return __half2(inf);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE __half2 init<ROpType::Max>()
|
||||
{
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
return __half2{_Float16_2{0xFC00, 0xFC00}};
|
||||
#else
|
||||
constexpr __half2_raw neg_inf = {0xFC00, 0xFC00};
|
||||
return __half2(neg_inf);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int32_t init<ROpType::Add>()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int32_t init<ROpType::Min>()
|
||||
{
|
||||
return 0x7FFFFFFF;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int32_t init<ROpType::Max>()
|
||||
{
|
||||
return 0x80000000;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE uint32_t init<ROpType::Add>()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE uint32_t init<ROpType::Min>()
|
||||
{
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE uint32_t init<ROpType::Max>()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int64_t init<ROpType::Add>()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int64_t init<ROpType::Min>()
|
||||
{
|
||||
return 0x7FFFFFFFFFFFFFFF;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE int64_t init<ROpType::Max>()
|
||||
{
|
||||
return 0x8000000000000000;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE uint64_t init<ROpType::Add>()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE uint64_t init<ROpType::Min>()
|
||||
{
|
||||
return 0xFFFFFFFFFFFFFFFF;
|
||||
}
|
||||
|
||||
template <>
|
||||
DS_D_INLINE uint64_t init<ROpType::Max>()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <ROpType Op, typename T>
|
||||
DS_D_INLINE void init(T* data)
|
||||
{
|
||||
data[0] = init<Op, T>();
|
||||
}
|
||||
|
||||
template <ROpType Op1, ROpType Op2, typename T>
|
||||
DS_D_INLINE void init(T* data)
|
||||
{
|
||||
data[0] = init<Op1, T>();
|
||||
data[1] = init<Op2, T>();
|
||||
}
|
||||
|
||||
template <ROpType Op1, ROpType Op2, ROpType Op3, typename T>
|
||||
DS_D_INLINE void init(T* data)
|
||||
{
|
||||
data[0] = init<Op1, T>();
|
||||
data[1] = init<Op2, T>();
|
||||
data[2] = init<Op3, T>();
|
||||
}
|
||||
|
||||
template <ROpType Op1, ROpType Op2, ROpType Op3, ROpType Op4, typename T>
|
||||
DS_D_INLINE void init(T* data)
|
||||
{
|
||||
data[0] = init<Op1, T>();
|
||||
data[1] = init<Op2, T>();
|
||||
data[2] = init<Op3, T>();
|
||||
data[3] = init<Op4, T>();
|
||||
}
|
||||
|
||||
/*
|
||||
Warp reduction primitives
|
||||
|
||||
`reduction_width` is an unsafe template parameter, that is that
|
||||
when using `reduction_width` < hw_warp_size the warp is partitioned
|
||||
into `hw_warp_size` / `reduction_width` groups of partial sums.
|
||||
|
||||
If someone can figure out how to use variadic templates in a reasonable way
|
||||
here (fold is C++17 only and I don't think helps and recursion feels like
|
||||
huge overkill that harms readability) that would be wonderful.
|
||||
*/
|
||||
|
||||
template <typename T, ROpType Op, int reduce_width = hw_warp_size>
|
||||
DS_D_INLINE void _warp(cg::thread_block_tile<hw_warp_size>& warp, T* data)
|
||||
{
|
||||
#pragma unroll
|
||||
for (int i = 1; i < reduce_width; i *= 2) {
|
||||
data[0] = element<Op>(data[0], warp.shfl_xor(data[0], i));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, ROpType Op1, ROpType Op2, int reduce_width = hw_warp_size>
|
||||
DS_D_INLINE void _warp(cg::thread_block_tile<hw_warp_size>& warp, T* data)
|
||||
{
|
||||
#pragma unroll
|
||||
for (int i = 1; i < reduce_width; i *= 2) {
|
||||
data[0] = element<Op1>(data[0], warp.shfl_xor(data[0], i));
|
||||
data[1] = element<Op2>(data[1], warp.shfl_xor(data[1], i));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, ROpType Op1, ROpType Op2, ROpType Op3, int reduce_width = hw_warp_size>
|
||||
DS_D_INLINE void _warp(cg::thread_block_tile<hw_warp_size>& warp, T* data)
|
||||
{
|
||||
#pragma unroll
|
||||
for (int i = 1; i < reduce_width; i *= 2) {
|
||||
data[0] = element<Op1>(data[0], warp.shfl_xor(data[0], i));
|
||||
data[1] = element<Op2>(data[1], warp.shfl_xor(data[1], i));
|
||||
data[2] = element<Op3>(data[2], warp.shfl_xor(data[2], i));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
ROpType Op1,
|
||||
ROpType Op2,
|
||||
ROpType Op3,
|
||||
ROpType Op4,
|
||||
int reduce_width = hw_warp_size>
|
||||
DS_D_INLINE void _warp(cg::thread_block_tile<hw_warp_size>& warp, T* data)
|
||||
{
|
||||
#pragma unroll
|
||||
for (int i = 1; i < reduce_width; i *= 2) {
|
||||
data[0] = element<Op1>(data[0], warp.shfl_xor(data[0], i));
|
||||
data[1] = element<Op2>(data[1], warp.shfl_xor(data[1], i));
|
||||
data[2] = element<Op3>(data[2], warp.shfl_xor(data[2], i));
|
||||
data[3] = element<Op4>(data[3], warp.shfl_xor(data[3], i));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Implementation for primary block reduction that serves both `block` and
|
||||
`partitioned_block`.
|
||||
|
||||
Total warps refers to the reduction width of the reduction, not
|
||||
the number of warps in the block (which may exceed that
|
||||
if the block is partitioned or if we do a conservative bound at
|
||||
compile time).
|
||||
*/
|
||||
template <typename T, int total_warps, ROpType... Ops>
|
||||
DS_D_INLINE void _block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp_arg,
|
||||
T* data)
|
||||
{
|
||||
constexpr int elems = sizeof...(Ops);
|
||||
constexpr int bytes = sizeof(T);
|
||||
// Unused when `partition_size == 1` or total_warps == 1
|
||||
__shared__ T reduce_buffer[max_warps * elems];
|
||||
|
||||
#ifdef __HIP_PLATFORM_AMD__
|
||||
const int total_threads = blockDim.x * blockDim.y * blockDim.z;
|
||||
const int running_warps = total_threads / hw_warp_size;
|
||||
#else
|
||||
const int running_warps = warp_arg.meta_group_size();
|
||||
#endif
|
||||
|
||||
// Always perform warp-scope reduction
|
||||
_warp<T, Ops...>(warp_arg, data);
|
||||
|
||||
// If max_warps == 1 let's skip the runtime check
|
||||
if (total_warps != 1) {
|
||||
if (warp_arg.thread_rank() == 0) {
|
||||
#pragma unroll
|
||||
for (int i = 0; i < elems; i++) {
|
||||
mem_access::store_shared<bytes>(reduce_buffer + elems * _warp_rank() + i, data + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Synchronization inside block-uniform conditional is safe
|
||||
tb.sync();
|
||||
|
||||
if (_warp_rank() == 0) {
|
||||
if (warp_arg.thread_rank() < running_warps) {
|
||||
#pragma unroll
|
||||
for (int i = 0; i < elems; i++) {
|
||||
mem_access::load_shared<bytes>(
|
||||
data + i, reduce_buffer + elems * warp_arg.thread_rank() + i);
|
||||
}
|
||||
} else {
|
||||
init<Ops...>(data);
|
||||
}
|
||||
|
||||
_warp<T, Ops..., total_warps>(warp_arg, data);
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < elems; i++) {
|
||||
mem_access::store_shared<bytes>(reduce_buffer + elems * warp_arg.thread_rank() + i,
|
||||
data + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Synchronization inside block-uniform conditional is safe
|
||||
tb.sync();
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < elems; i++) {
|
||||
mem_access::load_shared<bytes>(data + i, reduce_buffer + _warp_rank() * elems + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Main API implementations. For the most part, they just convert the individual
|
||||
variables into arrays, which makes working with them easier with a single
|
||||
implementation. In theory, we could use the `_block` implementation as another
|
||||
option, but the nature of using a pointer is a little less safe and this allows
|
||||
us to obfuscate the details of the partitioned implementation.
|
||||
*/
|
||||
template <ROpType Op, int warp_bound>
|
||||
DS_D_INLINE void block(cg::thread_block& tb, cg::thread_block_tile<hw_warp_size>& warp, float& val)
|
||||
{
|
||||
_block<float, warp_bound, Op>(tb, warp, &val);
|
||||
}
|
||||
|
||||
template <ROpType Op1, ROpType Op2, int warp_bound>
|
||||
DS_D_INLINE void block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2)
|
||||
{
|
||||
float data[2] = {val1, val2};
|
||||
_block<float, warp_bound, Op1, Op2>(tb, warp, data);
|
||||
val1 = data[0];
|
||||
val2 = data[1];
|
||||
}
|
||||
|
||||
template <ROpType Op1, ROpType Op2, ROpType Op3, int warp_bound>
|
||||
DS_D_INLINE void block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2,
|
||||
float& val3)
|
||||
{
|
||||
float data[3] = {val1, val2, val3};
|
||||
_block<float, warp_bound, Op1, Op2, Op3>(tb, warp, data);
|
||||
val1 = data[0];
|
||||
val2 = data[1];
|
||||
val3 = data[2];
|
||||
}
|
||||
|
||||
template <ROpType Op1, ROpType Op2, ROpType Op3, ROpType Op4, int warp_bound>
|
||||
DS_D_INLINE void block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2,
|
||||
float& val3,
|
||||
float& val4)
|
||||
{
|
||||
float data[4] = {val1, val2, val3, val4};
|
||||
_block<float, warp_bound, Op1, Op2, Op3, Op4>(tb, warp, data);
|
||||
val1 = data[0];
|
||||
val2 = data[1];
|
||||
val3 = data[2];
|
||||
val4 = data[3];
|
||||
}
|
||||
|
||||
/*
|
||||
Note: for the partitioned blocks, the implementation does not support non-power of 2 blocks in order
|
||||
to shorten block scale reduction length.
|
||||
*/
|
||||
template <ROpType Op, int num_threads>
|
||||
DS_D_INLINE void partitioned_block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val)
|
||||
{
|
||||
if (num_threads <= hw_warp_size) {
|
||||
_warp<float, Op, num_threads>(warp, &val);
|
||||
} else {
|
||||
constexpr int num_warps = num_threads / hw_warp_size;
|
||||
_block<float, num_warps, Op>(tb, warp, &val);
|
||||
}
|
||||
}
|
||||
|
||||
template <ROpType Op1, ROpType Op2, int num_threads>
|
||||
DS_D_INLINE void partitioned_block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2)
|
||||
{
|
||||
float data[2] = {val1, val2};
|
||||
|
||||
if (num_threads <= hw_warp_size) {
|
||||
_warp<float, Op1, Op2, num_threads>(warp, data);
|
||||
} else {
|
||||
constexpr int num_warps = num_threads / hw_warp_size;
|
||||
_block<float, num_warps, Op1, Op2>(tb, warp, data);
|
||||
}
|
||||
|
||||
val1 = data[0];
|
||||
val2 = data[1];
|
||||
}
|
||||
|
||||
template <ROpType Op1, ROpType Op2, ROpType Op3, int num_threads>
|
||||
DS_D_INLINE void partitioned_block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2,
|
||||
float& val3)
|
||||
{
|
||||
float data[3] = {val1, val2, val3};
|
||||
|
||||
if (num_threads <= hw_warp_size) {
|
||||
_warp<float, Op1, Op2, Op3, num_threads>(warp, data);
|
||||
} else {
|
||||
constexpr int num_warps = num_threads / hw_warp_size;
|
||||
_block<float, num_warps, Op1, Op2, Op3>(tb, warp, data);
|
||||
}
|
||||
|
||||
val1 = data[0];
|
||||
val2 = data[1];
|
||||
val3 = data[2];
|
||||
}
|
||||
|
||||
template <ROpType Op1, ROpType Op2, ROpType Op3, ROpType Op4, int num_threads>
|
||||
DS_D_INLINE void partitioned_block(cg::thread_block& tb,
|
||||
cg::thread_block_tile<hw_warp_size>& warp,
|
||||
float& val1,
|
||||
float& val2,
|
||||
float& val3,
|
||||
float& val4)
|
||||
{
|
||||
float data[4] = {val1, val2, val3, val4};
|
||||
|
||||
if (num_threads <= hw_warp_size) {
|
||||
_warp<float, Op1, Op2, Op3, Op4, num_threads>(warp, data);
|
||||
} else {
|
||||
constexpr int num_warps = num_threads / hw_warp_size;
|
||||
_block<float, num_warps, Op1, Op2, Op3, Op4>(tb, warp, data);
|
||||
}
|
||||
|
||||
val1 = data[0];
|
||||
val2 = data[1];
|
||||
val3 = data[2];
|
||||
val4 = data[3];
|
||||
}
|
||||
|
||||
/*
|
||||
Arg-reduce is a specialization of the above. We only support this with a single reduction
|
||||
parameter. This only works for max/min reductions.
|
||||
*/
|
||||
|
||||
__align__(8) struct IdxReduceResult {
|
||||
/*
|
||||
NOTE: ORDERING MATTERS HERE! The idx is the least significant set of bits
|
||||
and the val is the most significant. Changing the order of this declaration
|
||||
will break the code.
|
||||
*/
|
||||
int idx;
|
||||
float val;
|
||||
};
|
||||
|
||||
template <ROpType Op, int warpBound>
|
||||
DS_D_INLINE IdxReduceResult
|
||||
idx_reduce(cg::thread_block& tb, cg::thread_block_tile<hw_warp_size>& warp, float val, int idx)
|
||||
{
|
||||
IdxReduceResult res = {idx, val};
|
||||
|
||||
// Clear out the nan. This shouldn't be an issue for our initial applications
|
||||
if (isnan(val)) res.val = init<Op>();
|
||||
|
||||
// Can do float compares as integers. By packing the index into the lower bits
|
||||
// we can just do a single int64 rather than a branch, compare, and select.
|
||||
// One side benefit of this is that it is by nature a stable algorithm and
|
||||
// will always bias ties to the higher index.
|
||||
int64_t* res_as_int = reinterpret_cast<int64_t*>(&res);
|
||||
|
||||
// The way floating point compare works is normally to perform a sign comparison
|
||||
// and if they match, then do a comparison of the rest of the bits as unsigned
|
||||
// integers. Since we are bundling these, that means for negative values we need
|
||||
// to reverse the sort order, which we can do with an XOR.
|
||||
if (val < 0) { *res_as_int ^= 0x7fffffff00000000; }
|
||||
|
||||
_block<int64_t, warpBound, Op>(tb, warp, res_as_int);
|
||||
|
||||
// Sign bit is preserved, so we can check if we need to invert the mantissa back
|
||||
if (res.val < 0) { *res_as_int ^= 0x7fffffff00000000; }
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace reduce
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .atom_builder import *
|
||||
from .blocked_flash import *
|
||||
from .embed import *
|
||||
from .linear_blocked_kv_rotary import *
|
||||
from .logits_gather import *
|
||||
from .moe_gather import *
|
||||
from .moe_scatter import *
|
||||
from .top_k_gating import *
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .atom_builder import *
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "atom_builder.h"
|
||||
#include "attention_atom.h"
|
||||
#include "ragged_dtypes.h"
|
||||
|
||||
int32_t build_atoms(torch::Tensor& atoms_ten,
|
||||
torch::Tensor& batch_metadata,
|
||||
torch::Tensor& seq_metadata,
|
||||
torch::Tensor& kv_ptrs,
|
||||
const int32_t q_block_size,
|
||||
const int32_t kv_block_size)
|
||||
{
|
||||
const RaggedBatchDescriptor* batch_desc =
|
||||
reinterpret_cast<const RaggedBatchDescriptor*>(batch_metadata.data_ptr());
|
||||
|
||||
const InflightSeqDescriptor* seq_desc =
|
||||
reinterpret_cast<const InflightSeqDescriptor*>(seq_metadata.data_ptr());
|
||||
|
||||
int32_t** kv_ptr_list = reinterpret_cast<int32_t**>(kv_ptrs.data_ptr());
|
||||
|
||||
AttentionAtom* atoms = reinterpret_cast<AttentionAtom*>(atoms_ten.data_ptr());
|
||||
|
||||
int32_t n_atoms = 0;
|
||||
for (int i = 0; i < batch_desc->n_sequences; i++) {
|
||||
const int seq_atoms = (seq_desc[i].n_tokens + q_block_size - 1) / q_block_size;
|
||||
int32_t cur_start_idx = seq_desc[i].start_idx;
|
||||
int32_t global_start_idx = seq_desc[i].seen_tokens;
|
||||
int32_t remaining_toks = seq_desc[i].n_tokens;
|
||||
|
||||
for (int j = 0; j < seq_atoms; j++) {
|
||||
atoms[n_atoms].block_idx_list = kv_ptr_list[i];
|
||||
atoms[n_atoms].q_start_idx = cur_start_idx;
|
||||
atoms[n_atoms].q_len = std::min(remaining_toks, q_block_size);
|
||||
atoms[n_atoms].global_q_idx = global_start_idx;
|
||||
|
||||
const int32_t end_toks = global_start_idx + atoms[n_atoms].q_len;
|
||||
// TODO(cmikeh2): This logic needs to be changed for sparse implementations
|
||||
atoms[n_atoms].kv_blocks = (end_toks + kv_block_size - 1) / kv_block_size;
|
||||
atoms[n_atoms].total_extent = end_toks;
|
||||
|
||||
cur_start_idx += atoms[n_atoms].q_len;
|
||||
global_start_idx += atoms[n_atoms].q_len;
|
||||
remaining_toks -= atoms[n_atoms].q_len;
|
||||
n_atoms++;
|
||||
}
|
||||
}
|
||||
|
||||
return n_atoms;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <torch/extension.h>
|
||||
|
||||
/*
|
||||
Construct the attention atoms given the ragged metadata for the current batch.
|
||||
This could largely be done at the Python level, but since we pack the KV ptr
|
||||
alongside the int32_t metadata, it gets very ugly to handle the mixed-width
|
||||
data structures (since we're packing them in a single tensor).
|
||||
*/
|
||||
int32_t build_atoms(torch::Tensor& atoms_ten,
|
||||
torch::Tensor& batch_metadata,
|
||||
torch::Tensor& seq_metadata,
|
||||
torch::Tensor& kv_ptrs,
|
||||
const int32_t q_block_size,
|
||||
const int32_t kv_block_size);
|
||||
@@ -0,0 +1,50 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
import torch
|
||||
|
||||
from ... import DSKernelBase
|
||||
from deepspeed.ops.op_builder import RaggedOpsBuilder
|
||||
from ....ragged import RaggedBatchWrapper
|
||||
|
||||
|
||||
class AtomBuilder(DSKernelBase):
|
||||
"""
|
||||
C++ implementation to populate the attention atoms for the blocked attention
|
||||
kernel.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Triggers compilation of the C++ implementation.
|
||||
"""
|
||||
inf_module = RaggedOpsBuilder().load()
|
||||
self.kernel = inf_module.build_atoms
|
||||
|
||||
def __call__(self, atoms: torch.Tensor, ragged_batch: RaggedBatchWrapper, q_block_size: int,
|
||||
kv_block_size: int) -> Tuple[torch.Tensor, int]:
|
||||
"""
|
||||
Populates the attention atoms for the blocked attention kernel.
|
||||
|
||||
Args:
|
||||
atoms (torch.Tensor): Pre-allocated int32 tensor of shape [max_atoms, 8]
|
||||
ragged_batch (torch.Tensor): Wrapper for the ragged batch.
|
||||
q_block_size (int): The block size for the queries (as determined by the
|
||||
attention implementation)
|
||||
kv_block_size (int): The block size for the keys/values (as determined by the
|
||||
attention implementation)
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if atoms.device != torch.device("cpu"):
|
||||
raise RuntimeError("AtomBuilder must be called on tensors")
|
||||
|
||||
n_atoms = self.kernel(atoms, ragged_batch.batch_metadata_buffer(on_device=False),
|
||||
ragged_batch.inflight_seq_descriptors(on_device=False),
|
||||
ragged_batch.kv_ptrs(on_device=False), q_block_size, kv_block_size)
|
||||
return atoms, n_atoms
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .blocked_flash import *
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include "cuda.h"
|
||||
|
||||
struct AttentionAtom {
|
||||
/*
|
||||
The attention atom describes the workload of a particular query. The attention
|
||||
kernel will execute each ``AttentionAtom`` for each head of the model.
|
||||
*/
|
||||
|
||||
// Pointer to a list of KV block indices.
|
||||
int32_t* block_idx_list;
|
||||
|
||||
// Index of first token in the ragged batch associated with this atom.
|
||||
int32_t q_start_idx;
|
||||
|
||||
// Number of tokens in the ragged batch associated with this atom.
|
||||
int32_t q_len;
|
||||
|
||||
// Number of key/value blocks associated with this atom. All but the last are
|
||||
// assumed to be fully dense.
|
||||
int32_t kv_blocks;
|
||||
|
||||
// Number of tokens in the last key/value block.
|
||||
int32_t total_extent;
|
||||
|
||||
// Global index of the first token in the atom. For example, in a prompt continuation
|
||||
// in which we have already processed 768 tokens, this would be 768.
|
||||
int32_t global_q_idx;
|
||||
|
||||
// Unused
|
||||
int32_t unused;
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2023, Tri Dao.
|
||||
******************************************************************************/
|
||||
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <c10/cuda/CUDAGuard.h>
|
||||
#include <torch/extension.h>
|
||||
|
||||
#include "blocked_flash.h"
|
||||
#include "flash.h"
|
||||
|
||||
#define CHECK_SHAPE(x, ...) \
|
||||
TORCH_CHECK(x.sizes() == torch::IntArrayRef({__VA_ARGS__}), \
|
||||
#x " must have shape (" #__VA_ARGS__ ")")
|
||||
|
||||
void flash_attn_by_atoms(at::Tensor& out,
|
||||
at::Tensor& q,
|
||||
at::Tensor& k,
|
||||
at::Tensor& v,
|
||||
at::Tensor& attention_atoms,
|
||||
const float softmax_scale,
|
||||
const bool is_causal)
|
||||
{
|
||||
auto dprops = at::cuda::getCurrentDeviceProperties();
|
||||
|
||||
bool is_sm8x = dprops->major == 8 && dprops->minor >= 0;
|
||||
bool is_sm90 = dprops->major == 9 && dprops->minor == 0;
|
||||
TORCH_CHECK(is_sm90 || is_sm8x, "FlashAttention only supports Ampere GPUs or newer.");
|
||||
|
||||
auto q_dtype = q.dtype();
|
||||
TORCH_CHECK(q_dtype == torch::kFloat16 || q_dtype == torch::kBFloat16,
|
||||
"FlashAttention only support fp16 and bf16 data type");
|
||||
if (q_dtype == torch::kBFloat16) {
|
||||
TORCH_CHECK(is_sm90 || is_sm8x, "bfloat16 is only supported on Ampere GPUs or newer");
|
||||
}
|
||||
TORCH_CHECK(k.dtype() == q_dtype, "query and key must have the same dtype");
|
||||
TORCH_CHECK(v.dtype() == q_dtype, "query and value must have the same dtype");
|
||||
|
||||
TORCH_CHECK(q.is_cuda(), "Input tensor must be on CUDA device");
|
||||
TORCH_CHECK(k.is_cuda(), "Input tensor must be on CUDA device");
|
||||
TORCH_CHECK(v.is_cuda(), "Input tensor must be on CUDA device");
|
||||
|
||||
TORCH_CHECK(q.stride(-1) == 1, "Input tensor must have contiguous last dimension");
|
||||
TORCH_CHECK(k.stride(-1) == 1, "Input tensor must have contiguous last dimension");
|
||||
TORCH_CHECK(v.stride(-1) == 1, "Input tensor must have contiguous last dimension");
|
||||
|
||||
const int total_q = q.size(0);
|
||||
const int head_size = k.size(-1);
|
||||
const int num_heads_kv = k.size(-2);
|
||||
const int num_heads_q = q.size(-1) / head_size;
|
||||
|
||||
TORCH_CHECK(head_size <= 256, "head_size must be <= 256");
|
||||
TORCH_CHECK(head_size % 8 == 0, "head_size must be divisible by 8");
|
||||
TORCH_CHECK(num_heads_q % num_heads_kv == 0, "num_heads_q must be divisible by num_heads_kv");
|
||||
|
||||
Flash_fwd_params params;
|
||||
|
||||
params.is_bf16 = q.dtype() == torch::kBFloat16;
|
||||
|
||||
// Set the pointers and strides.
|
||||
params.q_ptr = q.data_ptr();
|
||||
params.k_ptr = k.data_ptr();
|
||||
params.v_ptr = v.data_ptr();
|
||||
params.o_ptr = out.data_ptr();
|
||||
params.atoms = reinterpret_cast<AttentionAtom*>(attention_atoms.data_ptr());
|
||||
|
||||
// All stride are in elements, not bytes.
|
||||
params.q_row_stride = q.stride(0);
|
||||
params.k_row_stride = k.stride(1);
|
||||
params.v_row_stride = v.stride(1);
|
||||
params.o_row_stride = out.stride(0);
|
||||
|
||||
// Assume heads are contiguous.
|
||||
params.q_head_stride = head_size;
|
||||
params.k_head_stride = head_size;
|
||||
params.v_head_stride = head_size;
|
||||
params.o_head_stride = head_size;
|
||||
|
||||
// Head params
|
||||
params.h = num_heads_q;
|
||||
params.h_k = num_heads_kv;
|
||||
params.h_h_k_ratio = num_heads_q / num_heads_kv;
|
||||
params.d = head_size;
|
||||
auto round_multiple = [](int x, int m) { return (x + m - 1) / m * m; };
|
||||
params.d_rounded = round_multiple(head_size, 32);
|
||||
params.num_atoms = attention_atoms.size(0);
|
||||
|
||||
// Set the different scale values.
|
||||
params.scale_softmax = softmax_scale;
|
||||
params.scale_softmax_log2 = softmax_scale * M_LOG2E;
|
||||
|
||||
params.is_causal = is_causal;
|
||||
|
||||
auto stream = at::cuda::getCurrentCUDAStream().stream();
|
||||
run_mha_fwd(params, stream);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <torch/extension.h>
|
||||
|
||||
void flash_attn_by_atoms(at::Tensor& out,
|
||||
at::Tensor& q,
|
||||
at::Tensor& k,
|
||||
at::Tensor& v,
|
||||
at::Tensor& attention_atoms,
|
||||
const float softmax_scale,
|
||||
const bool is_causal);
|
||||
@@ -0,0 +1,107 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
import torch
|
||||
|
||||
from deepspeed.accelerator import get_accelerator
|
||||
from ....inference_utils import DtypeEnum
|
||||
from deepspeed.ops.op_builder import RaggedOpsBuilder
|
||||
|
||||
from ... import DSKernelBase
|
||||
|
||||
|
||||
def get_q_block_size(head_size: int) -> int:
|
||||
"""
|
||||
Returns the query block size required by the kernel given a head size.
|
||||
"""
|
||||
cc_major, cc_minor = torch.cuda.get_device_capability(get_accelerator().current_device()) #ignore-cuda
|
||||
|
||||
if cc_major < 8:
|
||||
raise RuntimeError("Blocked attention requires CUDA compute capability >= 8.0")
|
||||
|
||||
if head_size <= 64:
|
||||
return 128
|
||||
elif head_size <= 160:
|
||||
if cc_minor != 0:
|
||||
return 64
|
||||
else:
|
||||
return 128
|
||||
elif head_size == 192:
|
||||
return 128
|
||||
elif head_size == 224:
|
||||
if cc_minor != 0:
|
||||
return 64
|
||||
else:
|
||||
return 128
|
||||
else:
|
||||
if cc_major == 8 and cc_minor == 0:
|
||||
return 128
|
||||
else:
|
||||
return 64
|
||||
|
||||
|
||||
def get_kv_block_size(head_size: int) -> int:
|
||||
"""
|
||||
Return preferred granulatity for blocked KV-cache implementation.
|
||||
"""
|
||||
cc_major, cc_minor = torch.cuda.get_device_capability(get_accelerator().current_device()) #ignore-cuda
|
||||
|
||||
if cc_major < 8:
|
||||
raise RuntimeError("Blocked attention requires CUDA compute capability >= 8.0")
|
||||
|
||||
if head_size <= 64:
|
||||
return 128
|
||||
elif head_size != 160 or cc_minor != 0:
|
||||
return 64
|
||||
else:
|
||||
return 32
|
||||
|
||||
|
||||
class BlockedFlashAttn(DSKernelBase):
|
||||
"""
|
||||
Modified implementation of flash-attn-2 tuned for inference on blocked KV-cache and wider
|
||||
range of input sequence lengths.
|
||||
"""
|
||||
|
||||
supported_dtypes = [DtypeEnum.fp16, DtypeEnum.bf16]
|
||||
|
||||
def __init__(self, head_size: int, dtype: DtypeEnum) -> None:
|
||||
"""
|
||||
Triggers any compilation of the kernels.
|
||||
"""
|
||||
if not isinstance(dtype, DtypeEnum):
|
||||
dtype = DtypeEnum(dtype)
|
||||
|
||||
if dtype not in BlockedFlashAttn.supported_dtypes:
|
||||
raise ValueError("Unsupported data type: {}, supported data types are {}".format(
|
||||
dtype, BlockedFlashAttn.supported_dtypes))
|
||||
|
||||
# For testing, need to revert to 32
|
||||
if head_size % 16 != 0:
|
||||
raise ValueError("Head size must be divisible by 32 (configured with {})".format(head_size))
|
||||
|
||||
inf_module = RaggedOpsBuilder().load()
|
||||
self.kernel = inf_module.flash_attn_by_atoms
|
||||
|
||||
def __call__(self, out: torch.Tensor, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, atoms: torch.Tensor,
|
||||
softmax_scale: float) -> torch.Tensor:
|
||||
"""
|
||||
Flash attention implementation atop a blocked KV-cache. Atoms should be pre-populated.
|
||||
See attention_atom.h for further details on the structure of the information.
|
||||
|
||||
Arguments:
|
||||
out (torch.Tensor): Output tensor of shape [tokens, hidden_size]
|
||||
q (torch.Tensor): Query tensor of shape [tokens, hidden_size]
|
||||
k (torch.Tensor): Key cache tensor of shape [n_blocks, block_size, n_heads_kv, head_size]. This Tensor only needs to be contiguous on the final dimension.
|
||||
v (torch.Tensor): Value cache tensor of shape [n_blocks, block_size, n_heads_kv, head_size]. This Tensor only needs to be contiguous on the final dimension.
|
||||
atoms (torch.Tensor): Atom information tensor of shape [num_atoms, 8] and type int32.
|
||||
Not all data is readable in this format. See attention_atom.h for further details.
|
||||
softmax_scale (float): Softmax scale factor.
|
||||
|
||||
Returns:
|
||||
out (torch.Tensor): Output tensor of shape [tokens, hidden_size]
|
||||
"""
|
||||
self.kernel(out, q, k, v, atoms, softmax_scale, True)
|
||||
return out
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
/******************************************************************************
|
||||
Copyright (c) 2023, Tri Dao.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cuda.h>
|
||||
#include <vector>
|
||||
|
||||
#include "attention_atom.h"
|
||||
|
||||
constexpr int TOTAL_DIM = 0;
|
||||
constexpr int H_DIM = 1;
|
||||
constexpr int D_DIM = 2;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct Qkv_params {
|
||||
using index_t = uint32_t;
|
||||
// The QKV matrices.
|
||||
void* __restrict__ q_ptr;
|
||||
void* __restrict__ k_ptr;
|
||||
void* __restrict__ v_ptr;
|
||||
|
||||
// The stride between rows of the Q, K and V matrices.
|
||||
index_t q_row_stride;
|
||||
index_t k_row_stride;
|
||||
index_t v_row_stride;
|
||||
index_t q_head_stride;
|
||||
index_t k_head_stride;
|
||||
index_t v_head_stride;
|
||||
|
||||
// The number of heads.
|
||||
int h, h_k;
|
||||
// In the case of multi-query and grouped-query attention (MQA/GQA), nheads_k could be
|
||||
// different from nheads (query).
|
||||
int h_h_k_ratio; // precompute h / h_k,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct Flash_fwd_params : public Qkv_params {
|
||||
// The O matrix (output).
|
||||
void* __restrict__ o_ptr;
|
||||
|
||||
// The attention metadata
|
||||
AttentionAtom* __restrict__ atoms;
|
||||
|
||||
// Total attention atoms
|
||||
int num_atoms;
|
||||
|
||||
// The stride between rows of O.
|
||||
index_t o_row_stride;
|
||||
index_t o_head_stride;
|
||||
|
||||
// The dimensions
|
||||
int d, d_rounded;
|
||||
|
||||
// The scaling factors for the kernel.
|
||||
float scale_softmax;
|
||||
float scale_softmax_log2;
|
||||
|
||||
bool is_bf16;
|
||||
bool is_causal;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void run_mha_fwd(Flash_fwd_params& params, cudaStream_t stream);
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .embed import RaggedEmbeddingKernel
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "embed.h"
|
||||
#include "ragged_kernel_helpers.h"
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
#define DISPATCH_FOR_FLOAT(DTYPE, ...) \
|
||||
[&] { \
|
||||
if (DTYPE == torch::kFloat32) { \
|
||||
using float_t = float; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kFloat16) { \
|
||||
using float_t = __half; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kBFloat16) { \
|
||||
using float_t = __nv_bfloat16; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
TORCH_CHECK(false, "Unsupported dispatch type"); \
|
||||
} \
|
||||
}()
|
||||
#else
|
||||
#define DISPATCH_FOR_FLOAT(DTYPE, ...) \
|
||||
[&] { \
|
||||
if (DTYPE == torch::kFloat32) { \
|
||||
using float_t = float; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kFloat16) { \
|
||||
using float_t = __half; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
TORCH_CHECK(false, "Unsupported dispatch type"); \
|
||||
} \
|
||||
}()
|
||||
#endif
|
||||
|
||||
#define DISPATCH_FOR_INT(DTYPE, ...) \
|
||||
[&] { \
|
||||
if (DTYPE == torch::kInt32) { \
|
||||
using int_t = int32_t; \
|
||||
return __VA_ARGS__(); \
|
||||
} else if (DTYPE == torch::kInt64) { \
|
||||
using int_t = int64_t; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
TORCH_CHECK(false, "Unsupported dispatch type"); \
|
||||
} \
|
||||
}()
|
||||
|
||||
/*
|
||||
Embeddings kernel aware of ragged batch structure.
|
||||
*/
|
||||
void ragged_embed(torch::Tensor& embedded_tokens,
|
||||
torch::Tensor& input_ids,
|
||||
torch::Tensor& embedding_weight,
|
||||
c10::optional<torch::Tensor>& position_embedding_weight,
|
||||
int32_t pos_embed_offset,
|
||||
torch::Tensor& batch_metadata,
|
||||
torch::Tensor& seq_metadata,
|
||||
torch::Tensor& tokens_to_seq,
|
||||
torch::Tensor& kv_ptrs)
|
||||
{
|
||||
// We don't care about KV cache here, so just hardcoding 0s for block_size/num_blocks
|
||||
BatchWrapperCPP batch_wrapper =
|
||||
make_cpp_batch_wrapper(batch_metadata, seq_metadata, tokens_to_seq, kv_ptrs, 0, 0);
|
||||
|
||||
const int32_t n_tokens = input_ids.numel();
|
||||
const int32_t embed_dim = embedding_weight.size(1);
|
||||
const int32_t vocab_size = embedding_weight.size(0);
|
||||
|
||||
DISPATCH_FOR_INT(input_ids.scalar_type(), [&] {
|
||||
DISPATCH_FOR_FLOAT(embedding_weight.scalar_type(), [&] {
|
||||
float_t* pos_embed_ptr = nullptr;
|
||||
int32_t max_position_embed_idx = 0;
|
||||
if (position_embedding_weight.has_value()) {
|
||||
TORCH_CHECK(
|
||||
position_embedding_weight.value().options().dtype() ==
|
||||
embedding_weight.options().dtype(),
|
||||
"position_embedding_weight and embedding_weight must have the same dtype");
|
||||
pos_embed_ptr =
|
||||
reinterpret_cast<float_t*>(position_embedding_weight.value().data_ptr());
|
||||
max_position_embed_idx = position_embedding_weight.value().size(0) - 1;
|
||||
}
|
||||
|
||||
launch_ragged_embed_kernel((float_t*)embedded_tokens.data_ptr(),
|
||||
(const int_t*)input_ids.data_ptr(),
|
||||
(const float_t*)embedding_weight.data_ptr(),
|
||||
pos_embed_ptr,
|
||||
batch_wrapper,
|
||||
n_tokens,
|
||||
embed_dim,
|
||||
vocab_size,
|
||||
max_position_embed_idx,
|
||||
pos_embed_offset,
|
||||
at::cuda::getCurrentCUDAStream());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <torch/extension.h>
|
||||
#include "embed.cuh"
|
||||
|
||||
/*
|
||||
Embeddings kernel aware of ragged batch structure.
|
||||
*/
|
||||
void ragged_embed(torch::Tensor& embedded_tokens,
|
||||
torch::Tensor& input_ids,
|
||||
torch::Tensor& embedding_weight,
|
||||
c10::optional<torch::Tensor>& position_weight,
|
||||
int32_t position_embed_offset,
|
||||
torch::Tensor& batch_metadata,
|
||||
torch::Tensor& seq_metadata,
|
||||
torch::Tensor& tokens_to_seq,
|
||||
torch::Tensor& kv_ptrs);
|
||||
@@ -0,0 +1,67 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
from ... import DSKernelBase
|
||||
from deepspeed.ops.op_builder import RaggedOpsBuilder
|
||||
from ....inference_utils import elem_size
|
||||
from ....ragged import RaggedBatchWrapper
|
||||
|
||||
|
||||
class RaggedEmbeddingKernel(DSKernelBase):
|
||||
"""
|
||||
Ragged-aware CUDA kernel implementation for an embedding lookup. This will only lookup
|
||||
the necessary tokens for a padded batch (i.e. if we are CGed and running with a slightly
|
||||
larger batch size than the actual tokens).
|
||||
"""
|
||||
|
||||
supported_dtypes = [torch.float16, torch.bfloat16, torch.float32]
|
||||
supported_token_dtypes = [torch.int32, torch.int64]
|
||||
|
||||
def __init__(self, embed_dtype: torch.dtype, token_dtype: torch.dtype, embed_dim: int) -> None:
|
||||
"""
|
||||
Args:
|
||||
fp_dtype (torch.dtype): Data type of the embedding table and output dtype.
|
||||
Supported values are torch.float16, torch.bfloat16, and torch.float32.
|
||||
token_dtype (torch.dtype): Data type of the token ids. Supported values are
|
||||
torch.int32 and torch.int64.
|
||||
embed_dim (int): Embedding dimension. Must be aligned to 16 bytes.
|
||||
"""
|
||||
if embed_dtype not in RaggedEmbeddingKernel.supported_dtypes:
|
||||
raise ValueError("Unsupported embedding data type: {}, supported_dtypes are {}".format(
|
||||
embed_dtype, RaggedEmbeddingKernel.supported_dtypes))
|
||||
|
||||
if token_dtype not in RaggedEmbeddingKernel.supported_token_dtypes:
|
||||
raise ValueError("Unsupported token data type: {}, supported_dtypes are {}".format(
|
||||
token_dtype, RaggedEmbeddingKernel.supported_token_dtypes))
|
||||
|
||||
if elem_size(embed_dtype) * embed_dim % 16 != 0:
|
||||
raise ValueError("Embedding dimension must be aligned to 16 bytes, got {}".format(embed_dim))
|
||||
|
||||
inf_module = RaggedOpsBuilder().load()
|
||||
self.kernel = inf_module.ragged_embed
|
||||
|
||||
def __call__(self,
|
||||
embedded_tokens: torch.Tensor,
|
||||
ragged_wrapper: RaggedBatchWrapper,
|
||||
embedding_weight: torch.Tensor,
|
||||
position_embed_weight: Optional[torch.Tensor] = None,
|
||||
position_embed_offset: int = 0) -> torch.Tensor:
|
||||
"""
|
||||
Ragged aware embedding lookup.
|
||||
|
||||
Args:
|
||||
embedded_tokens (torch.Tensor): Output tensor of shape [num_tokens, embed_dim]
|
||||
ragged_wrapper (RaggedBatchWrapper): Wrapper for the ragged batch.
|
||||
embedding_weight (torch.Tensor): Embedding table of shape [vocab_size, embed_dim]
|
||||
"""
|
||||
self.kernel(embedded_tokens, ragged_wrapper.input_ids(),
|
||||
embedding_weight, position_embed_weight, position_embed_offset,
|
||||
ragged_wrapper.batch_metadata_buffer(), ragged_wrapper.inflight_seq_descriptors(),
|
||||
ragged_wrapper.tokens_to_seq(), ragged_wrapper.kv_ptrs())
|
||||
return embedded_tokens
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "ds_kernel_utils.h"
|
||||
#include "embed.cuh"
|
||||
#include "memory_access_utils.h"
|
||||
#include "ragged_dtypes.h"
|
||||
|
||||
namespace embed {
|
||||
|
||||
constexpr int granularity = 16;
|
||||
constexpr int threads = 512;
|
||||
|
||||
} // namespace embed
|
||||
|
||||
template <typename TokenType, typename EmbedType>
|
||||
__global__ void ragged_embed_kernel(EmbedType* embedded_tokens,
|
||||
const TokenType* input_ids,
|
||||
const EmbedType* embedding_weight,
|
||||
const EmbedType* position_weight,
|
||||
const BatchWrapperCPP batch_desc,
|
||||
const int32_t embed_dim,
|
||||
const int32_t vocab_size,
|
||||
const int32_t max_position_embed_idx,
|
||||
const int32_t position_embed_offset)
|
||||
{
|
||||
constexpr int T_vector = embed::granularity / sizeof(EmbedType);
|
||||
|
||||
const int32_t token_idx = blockIdx.y;
|
||||
|
||||
// It's possible our batch is padded (under CG conditions typically)
|
||||
if (token_idx >= batch_desc.batch_metadata->n_tokens) return;
|
||||
|
||||
TokenType token_value = input_ids[token_idx];
|
||||
|
||||
if (token_value >= vocab_size || token_value < 0) {
|
||||
// TODO(cmikeh2): This is invalid, but not sure how we want to handle it being invalid
|
||||
// yet.
|
||||
return;
|
||||
}
|
||||
|
||||
const EmbedType* embedding_row = embedding_weight + token_value * embed_dim;
|
||||
EmbedType* dest_row = embedded_tokens + token_idx * embed_dim;
|
||||
|
||||
const int channel_offset = (threadIdx.x + embed::threads * blockIdx.x) * T_vector;
|
||||
|
||||
if (channel_offset < embed_dim) {
|
||||
EmbedType reg_buf[T_vector];
|
||||
|
||||
mem_access::load_global<embed::granularity>(reg_buf, embedding_row + channel_offset);
|
||||
|
||||
if (position_weight != nullptr) {
|
||||
// Map the token to its global idx (indirect memory accesses aren't great but whatever)
|
||||
const int32_t seq_idx = batch_desc.tokens_to_seq[token_idx];
|
||||
const InflightSeqDescriptor seq_desc = batch_desc.seq_metadata[seq_idx];
|
||||
int32_t pos_emb_idx = seq_desc.seen_tokens + (token_idx - seq_desc.start_idx);
|
||||
|
||||
// Position embed offset is an OPT-specific feature I think?
|
||||
pos_emb_idx = pos_emb_idx + position_embed_offset;
|
||||
|
||||
// This clamping is technically
|
||||
pos_emb_idx = (pos_emb_idx < 0) ? 0 : pos_emb_idx;
|
||||
pos_emb_idx = (pos_emb_idx >= max_position_embed_idx) ? max_position_embed_idx
|
||||
: pos_emb_idx;
|
||||
|
||||
const EmbedType* position_embedding_row = position_weight + pos_emb_idx * embed_dim;
|
||||
|
||||
EmbedType pos_buf[T_vector];
|
||||
mem_access::load_global<embed::granularity>(pos_buf,
|
||||
position_embedding_row + channel_offset);
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < T_vector; i++) { reg_buf[i] += pos_buf[i]; }
|
||||
}
|
||||
|
||||
mem_access::store_global<embed::granularity>(dest_row + channel_offset, reg_buf);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TokenType, typename EmbedType>
|
||||
void launch_ragged_embed_kernel(EmbedType* embedded_tokens,
|
||||
const TokenType* input_ids,
|
||||
const EmbedType* embedding_weight,
|
||||
const EmbedType* position_weight,
|
||||
const BatchWrapperCPP batch_desc,
|
||||
const int32_t n_tokens,
|
||||
const int32_t embed_dim,
|
||||
const int32_t vocab_size,
|
||||
const int32_t max_position_embed_idx,
|
||||
const int32_t position_embed_offset,
|
||||
cudaStream_t stream)
|
||||
{
|
||||
constexpr int T_vector = embed::granularity / sizeof(EmbedType);
|
||||
constexpr int elems_per_block = embed::threads * T_vector;
|
||||
const int parallel_blocks = (embed_dim + elems_per_block - 1) / elems_per_block;
|
||||
|
||||
const dim3 grid_dim(parallel_blocks, n_tokens, 1);
|
||||
const dim3 block_dim(embed::threads, 1, 1);
|
||||
|
||||
ragged_embed_kernel<TokenType, EmbedType>
|
||||
<<<grid_dim, block_dim, 0, stream>>>(embedded_tokens,
|
||||
input_ids,
|
||||
embedding_weight,
|
||||
position_weight,
|
||||
batch_desc,
|
||||
embed_dim,
|
||||
vocab_size,
|
||||
max_position_embed_idx,
|
||||
position_embed_offset);
|
||||
}
|
||||
|
||||
#define INSTANTIATE_EMBED_FOR_TYPES(TOKEN_TYPE, EMBED_TYPE) \
|
||||
template void launch_ragged_embed_kernel<TOKEN_TYPE, EMBED_TYPE>( \
|
||||
EMBED_TYPE * embedded_tokens, \
|
||||
const TOKEN_TYPE* input_ids, \
|
||||
const EMBED_TYPE* embedding_weight, \
|
||||
const EMBED_TYPE* position_weight, \
|
||||
const BatchWrapperCPP batch_descriptor, \
|
||||
const int32_t n_tokens, \
|
||||
const int32_t embed_dim, \
|
||||
const int32_t vocab_size, \
|
||||
const int32_t max_position_embed_idx, \
|
||||
const int32_t position_embed_offset, \
|
||||
cudaStream_t stream);
|
||||
|
||||
INSTANTIATE_EMBED_FOR_TYPES(int32_t, float)
|
||||
INSTANTIATE_EMBED_FOR_TYPES(int64_t, float)
|
||||
|
||||
INSTANTIATE_EMBED_FOR_TYPES(int32_t, __half)
|
||||
INSTANTIATE_EMBED_FOR_TYPES(int64_t, __half)
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
INSTANTIATE_EMBED_FOR_TYPES(int32_t, __nv_bfloat16)
|
||||
INSTANTIATE_EMBED_FOR_TYPES(int64_t, __nv_bfloat16)
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#define TOP_K_SWITCH(N_TOP_K, ...) \
|
||||
[&] { \
|
||||
if (1 == N_TOP_K) { \
|
||||
constexpr int CONST_TOP_K = 1; \
|
||||
__VA_ARGS__(); \
|
||||
} else if (2 == N_TOP_K) { \
|
||||
constexpr int CONST_TOP_K = 2; \
|
||||
__VA_ARGS__(); \
|
||||
} else if (4 == N_TOP_K) { \
|
||||
constexpr int CONST_TOP_K = 4; \
|
||||
__VA_ARGS__(); \
|
||||
} else if (8 == N_TOP_K) { \
|
||||
constexpr int CONST_TOP_K = 8; \
|
||||
__VA_ARGS__(); \
|
||||
} \
|
||||
}()
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# DeepSpeed Team
|
||||
|
||||
from .blocked_kv_rotary import *
|
||||
from .blocked_trained_kv_rotary import *
|
||||
from .linear_blocked_kv_copy import *
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#include "blocked_kv_rotary.h"
|
||||
#include "ragged_kernel_helpers.h"
|
||||
|
||||
#define DISPATCH_KV_ROTARY(T_TYPE, C_TYPE) \
|
||||
if (q.options().dtype() == torch::T_TYPE) { \
|
||||
launch_kv_rotary_kernel<C_TYPE>((C_TYPE*)kv_cache.data_ptr(), \
|
||||
(C_TYPE*)q.data_ptr(), \
|
||||
(C_TYPE*)k.data_ptr(), \
|
||||
(C_TYPE*)v.data_ptr(), \
|
||||
(C_TYPE*)inv_freq_ptr, \
|
||||
rotary_dim, \
|
||||
theta_base, \
|
||||
batch_wrapper, \
|
||||
qkv_stride, \
|
||||
kv_cache_stride, \
|
||||
v_offset, \
|
||||
inv_freq_stride, \
|
||||
q_ratio, \
|
||||
head_size, \
|
||||
n_tokens, \
|
||||
n_q_heads, \
|
||||
at::cuda::getCurrentCUDAStream()); \
|
||||
}
|
||||
|
||||
/*
|
||||
Rotary position embeddings + copy into KV cache. This implementation assumes
|
||||
that the inverse frequencies should be ready from global memory rather than
|
||||
synthesized in the kernel.
|
||||
|
||||
Arguments:
|
||||
kv_cache: [n_blocks, block_size, 2, n_kv_heads, head_size]
|
||||
q: [n_tokens, n_q_heads * head_size]
|
||||
k: [n_tokens, n_kv_heads * head_size]
|
||||
v: [n_tokens, n_kv_heads * head_size]
|
||||
inv_freq: [max_seq_len, head_size // 2]
|
||||
*/
|
||||
void kv_trained_rotary_embeddings(torch::Tensor& kv_cache,
|
||||
torch::Tensor& q,
|
||||
torch::Tensor& k,
|
||||
torch::Tensor& v,
|
||||
torch::Tensor& inv_freq,
|
||||
torch::Tensor& batch_metadata,
|
||||
torch::Tensor& seq_metadata,
|
||||
torch::Tensor& tokens_to_seq,
|
||||
torch::Tensor& kv_ptrs)
|
||||
{
|
||||
const int32_t n_tokens = q.size(0);
|
||||
TORCH_CHECK(n_tokens == k.size(0));
|
||||
TORCH_CHECK(n_tokens == v.size(0));
|
||||
|
||||
const float theta_base = 0.f;
|
||||
const int32_t rotary_dim = inv_freq.size(0) * 2;
|
||||
|
||||
// Dimensions
|
||||
const int32_t block_size = kv_cache.size(1);
|
||||
const int32_t n_kv_heads = kv_cache.size(3);
|
||||
const int32_t head_size = kv_cache.size(4);
|
||||
|
||||
// Strides
|
||||
const int32_t qkv_stride = q.stride(0); // Per token
|
||||
const int32_t kv_cache_stride = kv_cache.stride(1); // Per token
|
||||
const int32_t v_offset = kv_cache.stride(2); // From k_cache to v_cache
|
||||
const int32_t inv_freq_stride = inv_freq.stride(0); // Per token idx
|
||||
|
||||
const int n_q_heads = q.size(1) / head_size;
|
||||
const int q_ratio = n_q_heads / n_kv_heads;
|
||||
|
||||
void* inv_freq_ptr = (void*)inv_freq.data_ptr();
|
||||
|
||||
BatchWrapperCPP batch_wrapper = make_cpp_batch_wrapper(
|
||||
batch_metadata, seq_metadata, tokens_to_seq, kv_ptrs, block_size, kv_cache.size(0));
|
||||
|
||||
DISPATCH_KV_ROTARY(kHalf, __half);
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
DISPATCH_KV_ROTARY(kBFloat16, __nv_bfloat16);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
Rotary position embeddings + copy into KV cache. This implementation assumes
|
||||
that the inverse frequencies should be synthesized in the kernel.
|
||||
|
||||
Arguments:
|
||||
kv_cache: [n_blocks, block_size, 2, n_kv_heads, head_size]
|
||||
q: [n_tokens, n_q_heads * head_size]
|
||||
k: [n_tokens, n_kv_heads * head_size]
|
||||
v: [n_tokens, n_kv_heads * head_size]
|
||||
*/
|
||||
void kv_rotary_embeddings(torch::Tensor& kv_cache,
|
||||
torch::Tensor& q,
|
||||
torch::Tensor& k,
|
||||
torch::Tensor& v,
|
||||
const int32_t rotary_dim,
|
||||
const float theta_base,
|
||||
torch::Tensor& batch_metadata,
|
||||
torch::Tensor& seq_metadata,
|
||||
torch::Tensor& tokens_to_seq,
|
||||
torch::Tensor& kv_ptrs)
|
||||
{
|
||||
const int32_t n_tokens = q.size(0);
|
||||
TORCH_CHECK(n_tokens == k.size(0));
|
||||
TORCH_CHECK(n_tokens == v.size(0));
|
||||
|
||||
// Dimensions
|
||||
const int32_t block_size = kv_cache.size(1);
|
||||
const int32_t n_kv_heads = kv_cache.size(3);
|
||||
const int32_t head_size = kv_cache.size(4);
|
||||
|
||||
// Strides
|
||||
const int32_t qkv_stride = q.stride(0); // Per token
|
||||
const int32_t kv_cache_stride = kv_cache.stride(1); // Per token
|
||||
const int32_t v_offset = kv_cache.stride(2); // From k_cache to v_cache
|
||||
const int32_t inv_freq_stride = 0; // Per token idx
|
||||
|
||||
const int n_q_heads = q.size(1) / head_size;
|
||||
const int q_ratio = n_q_heads / n_kv_heads;
|
||||
|
||||
void* inv_freq_ptr = nullptr;
|
||||
|
||||
BatchWrapperCPP batch_wrapper = make_cpp_batch_wrapper(
|
||||
batch_metadata, seq_metadata, tokens_to_seq, kv_ptrs, block_size, kv_cache.size(0));
|
||||
|
||||
DISPATCH_KV_ROTARY(kHalf, __half);
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
DISPATCH_KV_ROTARY(kBFloat16, __nv_bfloat16);
|
||||
#endif
|
||||
}
|
||||
|
||||
#define DISPATCH_KV_COPY(T_TYPE, C_TYPE) \
|
||||
if (q.options().dtype() == torch::T_TYPE) { \
|
||||
launch_kv_copy_kernel<C_TYPE>((C_TYPE*)kv_cache.data_ptr(), \
|
||||
(C_TYPE*)q.data_ptr(), \
|
||||
(C_TYPE*)k.data_ptr(), \
|
||||
(C_TYPE*)v.data_ptr(), \
|
||||
batch_wrapper, \
|
||||
qkv_stride, \
|
||||
kv_cache_stride, \
|
||||
v_offset, \
|
||||
q_ratio, \
|
||||
head_size, \
|
||||
n_tokens, \
|
||||
n_q_heads, \
|
||||
at::cuda::getCurrentCUDAStream()); \
|
||||
}
|
||||
|
||||
/*
|
||||
Copy into linear KV cache.
|
||||
*/
|
||||
void linear_kv_copy(torch::Tensor& kv_cache,
|
||||
torch::Tensor& q,
|
||||
torch::Tensor& k,
|
||||
torch::Tensor& v,
|
||||
torch::Tensor& batch_metadata,
|
||||
torch::Tensor& seq_metadata,
|
||||
torch::Tensor& tokens_to_seq,
|
||||
torch::Tensor& kv_ptrs)
|
||||
{
|
||||
const int32_t n_tokens = q.size(0);
|
||||
TORCH_CHECK(n_tokens == k.size(0));
|
||||
TORCH_CHECK(n_tokens == v.size(0));
|
||||
|
||||
// Dimensions
|
||||
const int32_t block_size = kv_cache.size(1);
|
||||
const int32_t n_kv_heads = kv_cache.size(3);
|
||||
const int32_t head_size = kv_cache.size(4);
|
||||
|
||||
// Strides
|
||||
const int32_t qkv_stride = q.stride(0); // Per token
|
||||
TORCH_CHECK(qkv_stride == k.stride(0));
|
||||
TORCH_CHECK(qkv_stride == v.stride(0));
|
||||
|
||||
const int32_t kv_cache_stride = kv_cache.stride(1); // Per token
|
||||
const int32_t v_offset = kv_cache.stride(2); // From k_cache to v_cache
|
||||
|
||||
const int n_q_heads = q.size(1) / head_size;
|
||||
|
||||
TORCH_CHECK(n_q_heads % n_kv_heads == 0);
|
||||
const int q_ratio = n_q_heads / n_kv_heads;
|
||||
|
||||
BatchWrapperCPP batch_wrapper = make_cpp_batch_wrapper(
|
||||
batch_metadata, seq_metadata, tokens_to_seq, kv_ptrs, block_size, kv_cache.size(0));
|
||||
|
||||
DISPATCH_KV_COPY(kHalf, __half);
|
||||
|
||||
#ifdef BF16_AVAILABLE
|
||||
DISPATCH_KV_COPY(kBFloat16, __nv_bfloat16);
|
||||
#endif
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DeepSpeed Team
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
#include <torch/extension.h>
|
||||
#include "blocked_kv_rotary.cuh"
|
||||
|
||||
/*
|
||||
Rotary position embeddings + copy into KV cache. This implementation assumes
|
||||
that the inverse frequencies should be ready from global memory rather than
|
||||
synthesized in the kernel.
|
||||
|
||||
Arguments:
|
||||
kv_cache: [n_blocks, block_size, 2, n_kv_heads, head_size]
|
||||
q: [n_tokens, n_q_heads * head_size]
|
||||
k: [n_tokens, n_kv_heads * head_size]
|
||||
v: [n_tokens, n_kv_heads * head_size]
|
||||
inv_freq: [max_seq_len, head_size // 2]
|
||||
*/
|
||||
void kv_trained_rotary_embeddings(torch::Tensor& kv_cache,
|
||||
torch::Tensor& q,
|
||||
torch::Tensor& k,
|
||||
torch::Tensor& v,
|
||||
torch::Tensor& inv_freq,
|
||||
torch::Tensor& batch_metadata,
|
||||
torch::Tensor& seq_metadata,
|
||||
torch::Tensor& tokens_to_seq,
|
||||
torch::Tensor& kv_ptrs);
|
||||
|
||||
/*
|
||||
Rotary position embeddings + copy into KV cache. This implementation assumes
|
||||
that the inverse frequencies should be synthesized in the kernel.
|
||||
|
||||
Arguments:
|
||||
kv_cache: [n_blocks, block_size, 2, n_kv_heads, head_size]
|
||||
q: [n_tokens, n_q_heads * head_size]
|
||||
k: [n_tokens, n_kv_heads * head_size]
|
||||
v: [n_tokens, n_kv_heads * head_size]
|
||||
*/
|
||||
void kv_rotary_embeddings(torch::Tensor& kv_cache,
|
||||
torch::Tensor& q,
|
||||
torch::Tensor& k,
|
||||
torch::Tensor& v,
|
||||
const int32_t rotary_dim,
|
||||
const float theta_base,
|
||||
torch::Tensor& batch_metadata,
|
||||
torch::Tensor& seq_metadata,
|
||||
torch::Tensor& tokens_to_seq,
|
||||
torch::Tensor& kv_ptrs);
|
||||
|
||||
/*
|
||||
Copy into linear KV cache.
|
||||
*/
|
||||
void linear_kv_copy(torch::Tensor& kv_cache,
|
||||
torch::Tensor& q,
|
||||
torch::Tensor& k,
|
||||
torch::Tensor& v,
|
||||
torch::Tensor& batch_metadata,
|
||||
torch::Tensor& seq_metadata,
|
||||
torch::Tensor& tokens_to_seq,
|
||||
torch::Tensor& kv_ptrs);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user