chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:18:33 +08:00
commit 4ececc111a
2017 changed files with 331736 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,419 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import numpy as np
import torch
import torch.nn as nn
from unit.common import DistributedTest
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.quantization.quantization import _init_group_wise_weight_quantization
from deepspeed.inference.quantization.utils import Quantizer, DeQuantizer
from deepspeed.inference.quantization.layers import QuantizedLinear
from deepspeed.utils.torch import required_torch_version
from transformers.models.opt.modeling_opt import OPTDecoderLayer
from transformers import AutoConfig, OPTConfig, AutoModel
import pytest
from collections import OrderedDict
from typing import Dict
from deepspeed.ops.aio import AsyncIOBuilder
device = get_accelerator().device_name() if get_accelerator().is_available() else 'cpu'
if not required_torch_version(min_version=1.11):
pytest.skip("torch.Tensor.bitwise_left_shift in INT4 quantizer needs torch 1.11 or above.",
allow_module_level=True)
def reset_random(seed=1234):
np.random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
get_accelerator().manual_seed_all(seed)
def quantization_test_helper(pre_quant_type: torch.dtype, num_bits: int):
reset_random()
num_group = 1024 * 32
group_size = 64
quantization_config = {'num_bits': num_bits, 'group_size': group_size, 'group_dim': 1, 'symmetric': False}
quantizer = Quantizer(config=quantization_config)
dequantizer = DeQuantizer(config=quantization_config, dtype=pre_quant_type)
data = torch.randn(num_group, group_size, dtype=pre_quant_type, device=device)
quantized_data, scale_buf, min_vals = quantizer.quantize(data)
dequantized_data = dequantizer.dequantize(quantized_data, scale_buf, min_vals)
max_diff = torch.max(torch.abs(data - dequantized_data))
mean_diff = torch.mean(torch.abs(data - dequantized_data))
# This threshold value is emperically selected.
assert mean_diff < 0.15 and max_diff < 0.5, f'Numeric error exceed threshold, mean diff {mean_diff} (threshold 0.15), max diff {max_diff} (threshold 0.5)'
def zero3_post_init_quantization_test_helper(cpu_offload: bool, nvme_offload: bool, bits: int, nvme_path=None):
import deepspeed
from transformers.integrations.deepspeed import HfDeepSpeedConfig
if nvme_offload and not deepspeed.ops.__compatible_ops__[AsyncIOBuilder.NAME]:
pytest.skip('Skip tests since async-io is not compatible')
def get_zero3_ds_config(hf_config: OPTConfig, cpu_offload: bool, nvme_offload: bool, bits: int) -> Dict:
GB = 1 << 30
ds_config = {
"fp16": {
"enabled": True,
},
"zero_optimization": {
"stage": 3,
"stage3_prefetch_bucket_size": 2 * hf_config.hidden_size * hf_config.hidden_size,
"stage3_param_persistence_threshold": hf_config.hidden_size,
"stage3_max_live_parameters": 2 * hf_config.hidden_size * hf_config.hidden_size
},
"steps_per_print": 2000,
"train_micro_batch_size_per_gpu": 1,
"wall_clock_breakdown": False,
'weight_quantization': {
'post_init_quant': {
'fc': {
'num_bits': bits,
'group_size': 32,
'group_dim': 1,
'symmetric': False
},
'self_attn.q_proj': {
'num_bits': bits,
'group_size': 32,
'group_dim': 1,
'symmetric': False
},
'self_attn.k_proj': {
'num_bits': bits,
'group_size': 32,
'group_dim': 1,
'symmetric': False
},
'self_attn.v_proj': {
'num_bits': bits,
'group_size': 32,
'group_dim': 1,
'symmetric': False
},
'self_attn.out_proj': {
'num_bits': bits,
'group_size': 32,
'group_dim': 1,
'symmetric': False
},
'lm_head': {
'num_bits': bits,
'group_size': 32,
'group_dim': 1,
'symmetric': False
},
'embed_tokens': {
'num_bits': bits,
'group_size': 32,
'group_dim': 1,
'symmetric': False
},
}
}
}
if cpu_offload:
ds_config["zero_optimization"]["offload_param"] = dict(device="cpu", pin_memory=1)
if nvme_offload:
ds_config["zero_optimization"]["offload_param"] = dict(
device="nvme",
pin_memory=True,
nvme_path=nvme_path or '~/tmp_offload_dir',
buffer_count=5,
buffer_size=1 * GB,
)
ds_config["aio"] = {
"block_size": 1048576,
"queue_depth": 8,
"thread_count": 1,
"single_submit": False,
"overlap_events": True,
}
return ds_config
hf_config = AutoConfig.from_pretrained('facebook/opt-125m')
ds_config = get_zero3_ds_config(hf_config=hf_config, cpu_offload=cpu_offload, nvme_offload=nvme_offload, bits=bits)
input_ids = torch.ones(1, 16, dtype=torch.int32, device=device)
attention_mask = torch.ones(1, 16, dtype=torch.float32, device=device)
with torch.no_grad():
ref_model = AutoModel.from_pretrained('facebook/opt-125m', torch_dtype=torch.float16).to(device)
ref_model.eval()
ref_output = ref_model(input_ids=input_ids, attention_mask=attention_mask)
with torch.no_grad():
dschf = HfDeepSpeedConfig(ds_config)
model = AutoModel.from_pretrained('facebook/opt-125m', torch_dtype=torch.float16)
model = model.eval()
model = _init_group_wise_weight_quantization(model, ds_config)
ds_engine = deepspeed.initialize(model=model, config_params=ds_config)[0]
ds_engine.module.eval()
model = ds_engine.module
output = model(input_ids=input_ids, attention_mask=attention_mask)
mean_diff = torch.mean(torch.abs(output.last_hidden_state - ref_output.last_hidden_state))
# This threshold value is emperically selected.
assert mean_diff < 0.4, f'Numeric error exceed threshold, relative error {mean_diff} (threshold 0.4)'
def zero3_quantized_initialization_test_helper(cpu_offload: bool, nvme_offload: bool, bits: int, nvme_path=None):
import deepspeed
from transformers.integrations.deepspeed import HfDeepSpeedConfig
if nvme_offload and not deepspeed.ops.__compatible_ops__[AsyncIOBuilder.NAME]:
pytest.skip('Skip tests since async-io is not compatible')
def get_zero3_ds_config(hf_config: OPTConfig, cpu_offload: bool, nvme_offload: bool, bits: int) -> Dict:
GB = 1 << 30
ds_config = {
"fp16": {
"enabled": True,
},
"zero_optimization": {
"stage": 3,
"stage3_prefetch_bucket_size": 2 * hf_config.hidden_size * hf_config.hidden_size,
"stage3_param_persistence_threshold": hf_config.hidden_size,
"stage3_max_live_parameters": 2 * hf_config.hidden_size * hf_config.hidden_size
},
"steps_per_print": 2000,
"train_micro_batch_size_per_gpu": 1,
"wall_clock_breakdown": False,
'weight_quantization': {
'quantized_initialization': {
'num_bits': bits,
'group_size': 32,
'group_dim': 1,
'symmetric': False
},
}
}
if cpu_offload:
ds_config["zero_optimization"]["offload_param"] = dict(device="cpu", pin_memory=1)
if nvme_offload:
ds_config["zero_optimization"]["offload_param"] = dict(
device="nvme",
pin_memory=True,
nvme_path=nvme_path or '~/tmp_offload_dir',
buffer_count=5,
buffer_size=1 * GB,
)
ds_config["aio"] = {
"block_size": 1048576,
"queue_depth": 8,
"thread_count": 1,
"single_submit": False,
"overlap_events": True,
}
return ds_config
hf_config = AutoConfig.from_pretrained('facebook/opt-125m')
ds_config = get_zero3_ds_config(hf_config=hf_config, cpu_offload=cpu_offload, nvme_offload=nvme_offload, bits=bits)
input_ids = torch.ones(1, 16, dtype=torch.int32, device=device)
attention_mask = torch.ones(1, 16, dtype=torch.float32, device=device)
with torch.no_grad():
ref_model = AutoModel.from_pretrained('facebook/opt-125m', torch_dtype=torch.float16).to(device)
ref_model.eval()
ref_output = ref_model(input_ids=input_ids, attention_mask=attention_mask)
with torch.no_grad():
dschf = HfDeepSpeedConfig(ds_config)
model = AutoModel.from_pretrained('facebook/opt-125m', torch_dtype=torch.float16)
model = model.eval()
ds_engine = deepspeed.initialize(model=model, config_params=ds_config)[0]
ds_engine.module.eval()
model = ds_engine.module
output = model(input_ids=input_ids, attention_mask=attention_mask)
mean_diff = torch.mean(torch.abs(output.last_hidden_state - ref_output.last_hidden_state))
# This threshold value is emperically selected.
assert mean_diff < 0.4, f'Numeric error exceed threshold, relative error {mean_diff} (threshold 0.4)'
@pytest.fixture(params=[4, 8], ids=["4bits", "8bits"])
def quantization_bits(request):
return request.param
@pytest.fixture(params=[0, 1], ids=["0", "1"])
def group_dim(request):
return request.param
class TestQuantizedInt(DistributedTest):
def test_model_quantization(self, quantization_bits):
reset_random()
config = AutoConfig.from_pretrained('facebook/opt-125m')
with torch.no_grad():
model = OPTDecoderLayer(config).half().to(device)
bits = quantization_bits
ds_config = {
'weight_quantization': {
'post_init_quant': {
'fc': {
'num_bits': bits,
'group_size': 64,
'group_dim': 0,
'symmetric': False
},
'self_attn.q_proj': {
'num_bits': bits,
'group_size': 64,
'group_dim': 0,
'symmetric': False
},
'self_attn.k_proj': {
'num_bits': bits,
'group_size': 64,
'group_dim': 0,
'symmetric': False
},
'self_attn.v_proj': {
'num_bits': bits,
'group_size': 64,
'group_dim': 0,
'symmetric': False
},
'self_attn.out_proj': {
'num_bits': bits,
'group_size': 64,
'group_dim': 0,
'symmetric': False
}
}
}
}
model = _init_group_wise_weight_quantization(model, ds_config)
assert type(model.fc1) is QuantizedLinear
assert type(model.fc2) is QuantizedLinear
assert type(model.self_attn.q_proj) is QuantizedLinear
assert type(model.self_attn.k_proj) is QuantizedLinear
assert type(model.self_attn.v_proj) is QuantizedLinear
assert type(model.self_attn.out_proj) is QuantizedLinear
@pytest.mark.skipif(device == 'cpu', reason='CPU does support FP16 GEMM')
def test_quantized_linear(self, quantization_bits, group_dim):
reset_random()
layers = []
for idx in range(5):
layers.append(
(f'layer_{idx}', nn.Linear(in_features=128, out_features=128, dtype=torch.float16, device=device)))
input_tensor = torch.randn(32, 128, dtype=torch.float16, device=device)
with torch.no_grad():
model = nn.Sequential(OrderedDict(layers))
ref_output = model(input_tensor)
ds_config = {
'weight_quantization': {
'post_init_quant': {
'layer': {
'num_bits': quantization_bits,
'group_size': 64,
'group_dim': group_dim,
'symmetric': False
}
}
}
}
model = _init_group_wise_weight_quantization(model, ds_config)
assert type(model.layer_0) is QuantizedLinear
assert type(model.layer_1) is QuantizedLinear
assert type(model.layer_2) is QuantizedLinear
assert type(model.layer_3) is QuantizedLinear
assert type(model.layer_4) is QuantizedLinear
output = model(input_tensor)
mean_diff = torch.mean(torch.abs(ref_output - output))
# This threshold value is emperically selected.
assert mean_diff < 0.15, f'Numeric error exceed threshold, mean diff {mean_diff}'
def test_float_int4_quantization(self):
reset_random()
quantization_test_helper(torch.float32, 4)
@pytest.mark.skipif(device == 'cpu', reason='CPU does support FP16 GEMM')
def test_half_int4_quantization(self):
reset_random()
quantization_test_helper(torch.float16, 4)
def test_float_int8_quantization(self):
reset_random()
quantization_test_helper(torch.float32, 8)
def test_half_int8_quantization(self):
reset_random()
quantization_test_helper(torch.float16, 8)
@pytest.mark.skipif(device == 'cpu', reason='CPU does support FP16 GEMM')
def test_zero3_int4_post_init_quant(self, quantization_bits):
reset_random()
zero3_post_init_quantization_test_helper(cpu_offload=False, nvme_offload=False, bits=quantization_bits)
@pytest.mark.skipif(device == 'cpu', reason='CPU does support FP16 GEMM')
def test_zero3_int4_post_init_quant_cpu_offload(self, quantization_bits):
reset_random()
zero3_post_init_quantization_test_helper(cpu_offload=True, nvme_offload=False, bits=quantization_bits)
@pytest.mark.skipif(device == 'cpu', reason='CPU does support FP16 GEMM')
def test_zero3_int4_post_init_quant_nvme_offload(self, tmpdir):
reset_random()
zero3_post_init_quantization_test_helper(cpu_offload=False,
nvme_offload=True,
bits=4,
nvme_path=str(tmpdir.join("nvme_offload")))
@pytest.mark.skipif(device == 'cpu', reason='CPU does support FP16 GEMM')
def test_zero3_int4_quantized_initialization(self, quantization_bits):
reset_random()
zero3_quantized_initialization_test_helper(cpu_offload=False, nvme_offload=False, bits=quantization_bits)
@pytest.mark.skipif(device == 'cpu', reason='CPU does support FP16 GEMM')
def test_zero3_int4_quantized_initialization_cpu_offload(self, quantization_bits):
reset_random()
zero3_quantized_initialization_test_helper(cpu_offload=True, nvme_offload=False, bits=quantization_bits)
@pytest.mark.skipif(device == 'cpu', reason='CPU does support FP16 GEMM')
def test_zero3_int4_quantized_initialization_nvme_offload(self, tmpdir):
reset_random()
zero3_quantized_initialization_test_helper(cpu_offload=False,
nvme_offload=True,
bits=4,
nvme_path=str(tmpdir.join("nvme_offload")))
@@ -0,0 +1,150 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import os
import pytest
import torch
import deepspeed
from deepspeed.model_implementations import DeepSpeedTransformerInference
from unit.common import DistributedTest, DistributedFixture
from transformers import AutoConfig, AutoModelForCausalLM
import deepspeed.comm as dist
from huggingface_hub import snapshot_download
from deepspeed.ops.op_builder import InferenceBuilder
# Handle different versions of transformers
try:
from transformers.utils import is_offline_mode
except ImportError:
# For transformers >= 5.0, is_offline_mode was removed
# transformers >= 5.0 requires huggingface_hub >= 1.2.1 which has is_offline_mode
from huggingface_hub import is_offline_mode
from deepspeed.accelerator import get_accelerator
if not deepspeed.ops.__compatible_ops__[InferenceBuilder.NAME]:
pytest.skip("This op had not been implemented on this system.", allow_module_level=True)
def check_dtype(model, expected_dtype):
def find_dtype(module):
for child in module.children():
if isinstance(child, DeepSpeedTransformerInference):
return child.attention.attn_qkvw.dtype
else:
found_dtype = find_dtype(child)
if found_dtype:
return found_dtype
found_dtype = find_dtype(model)
assert found_dtype, "Did not find DeepSpeedTransformerInference in model"
assert (found_dtype == expected_dtype), f"Expected transformer dtype {expected_dtype}, but found {found_dtype}"
@pytest.fixture(params=[
"bigscience/bloom-560m", "EleutherAI/gpt-j-6B", "EleutherAI/gpt-neo-125M", "facebook/opt-350m", "facebook/opt-125m"
])
def model_name(request):
return request.param
@pytest.fixture(params=[torch.float16, torch.int8], ids=["fp16", "int8"])
def dtype(request):
if request.param not in get_accelerator().supported_dtypes():
pytest.skip(f"{request.param} not supported by {get_accelerator().device_name()}.")
return request.param
class save_shard(DistributedFixture):
world_size = 2
def run(self, model_name, class_tmpdir):
# Only write a checkpoint if one does not exist
if not os.path.isdir(os.path.join(class_tmpdir, model_name)):
world_size = int(os.getenv("WORLD_SIZE", "1"))
inf_config = {
"replace_with_kernel_inject": True,
"dtype": torch.float16,
"enable_cuda_graph": False,
"tensor_parallel": {
"tp_size": world_size
},
"save_mp_checkpoint_path": os.path.join(str(class_tmpdir), model_name),
}
# Load model and save sharded checkpoint
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16)
model = deepspeed.init_inference(model, config=inf_config)
@pytest.mark.seq_inference
class TestCheckpointShard(DistributedTest):
world_size = 2
def test(self, model_name, dtype, class_tmpdir, save_shard):
world_size = int(os.getenv("WORLD_SIZE", "1"))
inf_config = {
"replace_with_kernel_inject": True,
"dtype": dtype,
"enable_cuda_graph": False,
"tensor_parallel": {
"tp_size": world_size
},
"checkpoint": os.path.join(class_tmpdir, model_name, "ds_inference_config.json"),
}
# Load model on meta tensors
model_config = AutoConfig.from_pretrained(model_name)
# Note that we use half precision to load initially, even for int8
with deepspeed.OnDevice(dtype=torch.float16, device="meta"):
model = AutoModelForCausalLM.from_config(model_config, torch_dtype=torch.bfloat16)
model = model.eval()
model = deepspeed.init_inference(model, config=inf_config)
check_dtype(model, dtype)
@pytest.mark.seq_inference
class TestCheckpointShardinAutoTP(DistributedTest):
world_size = 2
def test(self, model_name, class_tmpdir):
def write_checkpoints_json(model_name, class_tmpdir):
import json
from pathlib import Path
local_rank = int(os.getenv("LOCAL_RANK", "0"))
if local_rank == 0:
# download only on first process
cached_repo_dir = snapshot_download(
model_name,
local_files_only=is_offline_mode(),
cache_dir=os.getenv("HF_HOME", None),
ignore_patterns=["*.safetensors", "*.msgpack", "*.h5"],
)
file_list = [str(entry) for entry in Path(cached_repo_dir).rglob("*.[bp][it][n]") if entry.is_file()]
data = {"type": "ds_model", "checkpoints": file_list, "version": 1.0}
os.makedirs(os.path.join(class_tmpdir, model_name), exist_ok=True)
json.dump(data, open(os.path.join(class_tmpdir, model_name, "ds_inference_config.json"), "w"))
dist.barrier()
world_size = int(os.getenv("WORLD_SIZE", "1"))
inf_config = {
"replace_with_kernel_inject": False,
"tensor_parallel": {
"tp_size": world_size
},
"checkpoint": os.path.join(class_tmpdir, model_name, "ds_inference_config.json"),
}
write_checkpoints_json(model_name, class_tmpdir)
# Load model on meta tensors
model_config = AutoConfig.from_pretrained(model_name)
# Note that we use half precision to load initially, even for int8
with deepspeed.OnDevice(dtype=torch.bfloat16, device="meta"):
model = AutoModelForCausalLM.from_config(model_config, torch_dtype=torch.bfloat16)
model = model.eval()
model = deepspeed.init_inference(model, config=inf_config)
+743
View File
@@ -0,0 +1,743 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import os
import time
import deepspeed
import torch
from packaging import version as pkg_version
from torch import nn
from transformers import pipeline
from transformers.models.t5.modeling_t5 import T5Block
from transformers.models.roberta.modeling_roberta import RobertaLayer
from deepspeed.accelerator import get_accelerator
from deepspeed.git_version_info import torch_info
from deepspeed.model_implementations import DeepSpeedTransformerInference
from deepspeed.ops.op_builder import InferenceBuilder
from deepspeed.ops.op_builder import OpBuilder
from unit.common import DistributedTest
rocm_version = OpBuilder.installed_rocm_version()
if rocm_version != (0, 0):
pytest.skip("skip inference tests on rocm for now", allow_module_level=True)
_bert_models = [
"google-bert/bert-base-cased",
"google-bert/bert-base-uncased",
"google-bert/bert-large-cased",
"google-bert/bert-large-uncased",
"google-bert/bert-base-multilingual-cased",
"google-bert/bert-base-multilingual-uncased",
"deepset/minilm-uncased-squad2",
"cross-encoder/ms-marco-MiniLM-L-12-v2",
"dslim/bert-base-NER",
"google-bert/bert-large-uncased-whole-word-masking-finetuned-squad",
"distilbert/distilbert-base-cased-distilled-squad",
]
_roberta_models = [
"FacebookAI/roberta-large",
"FacebookAI/roberta-base",
"deepset/roberta-base-squad2",
"j-hartmann/emotion-english-distilroberta-base",
"Jean-Baptiste/roberta-large-ner-english",
]
_gpt_models = [
"openai-community/gpt2",
"distilbert/distilgpt2",
"Norod78/hebrew-bad_wiki-gpt_neo-tiny",
"EleutherAI/gpt-j-6b",
"EleutherAI/pythia-70m-deduped",
"bigscience/bloom-560m",
]
_opt_models = [
"facebook/opt-125m", # 125m, 1.7B, ..., 175B variants have the same model architecture.
"facebook/opt-350m", # 350m applies layer norm after attention layer which is different than other variants.
]
_test_models = set(_bert_models + _roberta_models + _gpt_models + _opt_models)
_test_tasks = [
"fill-mask", "question-answering", "text-classification", "token-classification", "text-generation",
"text2text-generation", "summarization", "translation"
]
_test_model_list = _bert_models + _roberta_models + _gpt_models + _opt_models
_model_pipeline_tags = {
"google-bert/bert-base-cased": "fill-mask",
"google-bert/bert-base-uncased": "fill-mask",
"google-bert/bert-large-cased": "fill-mask",
"google-bert/bert-large-uncased": "fill-mask",
"google-bert/bert-base-multilingual-cased": "fill-mask",
"google-bert/bert-base-multilingual-uncased": "fill-mask",
"deepset/minilm-uncased-squad2": "question-answering",
"cross-encoder/ms-marco-MiniLM-L-12-v2": "text-ranking",
"dslim/bert-base-NER": "token-classification",
"google-bert/bert-large-uncased-whole-word-masking-finetuned-squad": "question-answering",
"distilbert/distilbert-base-cased-distilled-squad": "question-answering",
"FacebookAI/roberta-large": "fill-mask",
"FacebookAI/roberta-base": "fill-mask",
"deepset/roberta-base-squad2": "question-answering",
"j-hartmann/emotion-english-distilroberta-base": "text-classification",
"Jean-Baptiste/roberta-large-ner-english": "token-classification",
"openai-community/gpt2": "text-generation",
"distilbert/distilgpt2": "text-generation",
"Norod78/hebrew-bad_wiki-gpt_neo-tiny": "text-generation",
"EleutherAI/gpt-j-6b": "text-generation",
"EleutherAI/pythia-70m-deduped": "text-generation",
"bigscience/bloom-560m": "text-generation",
"facebook/opt-125m": "text-generation",
"facebook/opt-350m": "text-generation",
}
# Build the deterministic model/task matrix without querying Hugging Face during collection.
_model_w_tasks = [(model, task) for model in _test_model_list for task in [_model_pipeline_tags[model]]
if task in _test_tasks]
# Assign to pytest variables for testing
pytest.model_w_tasks = _model_w_tasks
pytest.mt_names = [f"{m}-{t}" for m, t in pytest.model_w_tasks]
@pytest.fixture(scope="module", autouse=True)
def verify_models():
models_without_tags = _test_models.difference(_model_pipeline_tags)
if models_without_tags:
pytest.fail(f"Model(s) missing a test pipeline tag: {models_without_tags}")
unknown_models = set(_model_pipeline_tags).difference(_test_models)
if unknown_models:
pytest.fail(f"Model pipeline tag mapping contains unknown model(s): {unknown_models}")
missing_tags = {model for model, tag in _model_pipeline_tags.items() if not tag}
if missing_tags:
pytest.fail(f"Model pipeline tag mapping contains empty tag(s): {missing_tags}")
""" Fixtures for inference config """
@pytest.fixture(params=pytest.model_w_tasks, ids=pytest.mt_names)
def model_w_task(request):
return request.param
@pytest.fixture(params=[torch.float, torch.half], ids=["fp32", "fp16"])
def dtype(request):
return request.param
@pytest.fixture(params=[True, False], ids=["CG", "noCG"])
def enable_cuda_graph(request):
return request.param
@pytest.fixture(params=[True, False], ids=["Triton", "noTriton"])
def enable_triton(request):
return request.param
@pytest.fixture(params=[1, 2], ids=["ws1", "ws2"])
def world_size(request):
return request.param
""" Fixtures for running query """
@pytest.fixture
def query(model_w_task):
model, task = model_w_task
angle_bracket_mask_models = ["roberta", "camembert", "esm", "ibert", "luke", "mpnet", "yoso", "mpnet"]
if task == "fill-mask":
if any(map(lambda x: x in model, angle_bracket_mask_models)):
return "Hello I'm a <mask> model."
else:
return "Hell I'm a [MASK] model."
elif task == "question-answering":
return {
"question": "What's my name?",
"context": "My name is Clara and I live in Berkeley",
}
elif task == "text-classification":
return "DeepSpeed is the greatest"
elif task == "token-classification":
return "My name is jean-baptiste and I live in montreal."
elif task == "text-generation":
return "DeepSpeed is the greatest"
elif task == "text2text-generation":
return "Is this review positive or negative? Review: this is the best cast iron skillet you will ever buy"
elif task == "translation" or task == "summarization":
return "Hello, my dog is cute"
else:
NotImplementedError(f'query for task "{task}" is not implemented')
@pytest.fixture
def inf_kwargs(model_w_task):
model, task = model_w_task
if task == "text-generation":
if model == "EleutherAI/gpt-j-6b":
# This model on V100 is hitting memory problems that limit the number of output tokens
return {"do_sample": False, "temperature": 1.0, "max_length": 12}
return {"do_sample": False, "temperature": 1.0, "max_length": 20}
else:
return {}
""" Assertion fixture for verifying model outputs """
def fill_mask_assert(x, y):
return set(res["token_str"] for res in x) == set(res["token_str"] for res in y)
def question_answering_assert(x, y):
return x["answer"] == y["answer"]
def text_classification_assert(x, y):
return set(res["label"] for res in x) == set(res["label"] for res in y)
def token_classification_assert(x, y):
return set(ent["word"] for ent in x) == set(ent["word"] for ent in y)
def text_generation_assert(x, y):
return set(res["generated_text"] for res in x) == set(res["generated_text"] for res in y)
def text2text_generation_assert(x, y):
return set(res["generated_text"] for res in x) == set(res["generated_text"] for res in y)
def translation_assert(x, y):
return set(res["translation_text"] for res in x) == set(res["translation_text"] for res in y)
def summarization_assert(x, y):
return set(res["summary_text"] for res in x) == set(res["summary_text"] for res in y)
@pytest.fixture
def assert_fn(model_w_task):
model, task = model_w_task
assert_fn_dict = {
"fill-mask": fill_mask_assert,
"question-answering": question_answering_assert,
"text-classification": text_classification_assert,
"token-classification": token_classification_assert,
"text-generation": text_generation_assert,
"text2text-generation": text2text_generation_assert,
"translation": translation_assert,
"summarization": summarization_assert
}
assert_fn = assert_fn_dict.get(task, None)
if assert_fn is None:
NotImplementedError(f'assert_fn for task "{task}" is not implemented')
return assert_fn
# Used to verify DeepSpeed kernel injection worked with a model
def check_injection(model):
def verify_injection(module):
for child in module.children():
if isinstance(child, nn.ModuleList):
assert isinstance(child[0], DeepSpeedTransformerInference),\
"DeepSpeed-Inference Transformer kernels has not been injected in the model"
break
else:
verify_injection(child)
verify_injection(model)
# Used to Get Device name
def getDeviceId(local_rank):
device = torch.device(f"{get_accelerator().device_name(local_rank)}")
return device
# Verify that test is valid
def validate_test(model_w_task, dtype, enable_cuda_graph, enable_triton):
model, task = model_w_task
msg = ""
if enable_cuda_graph and (torch_info["cuda_version"] == "0.0"):
msg = "CUDA not detected, cannot use CUDA Graph"
elif enable_cuda_graph and pkg_version.parse(torch.__version__) < pkg_version.parse("1.10"):
msg = "CUDA Graph is only available in torch versions >= 1.10"
elif "gpt-j-6b" in model:
if dtype != torch.half:
msg = f"Not enough GPU memory to run {model} with dtype {dtype}"
elif enable_cuda_graph:
msg = f"Not enough GPU memory to run {model} with CUDA Graph enabled"
elif "gpt-neox-20b" in model: # TODO: remove this when neox issues resolved
msg = "Skipping gpt-neox-20b for now"
elif ("gpt-neox-20b" in model) and (dtype != torch.half):
msg = f"Not enough GPU memory to run {model} with dtype {dtype}"
elif ("bloom" in model) and (dtype != torch.half):
msg = f"Bloom models only support half precision, cannot use dtype {dtype}"
elif (model not in _bert_models + _roberta_models) and enable_cuda_graph:
msg = "Non bert/roberta models do no support CUDA Graph"
elif enable_triton and not (dtype in [torch.half]):
msg = "Triton is for fp16"
elif enable_triton and not deepspeed.HAS_TRITON:
msg = "triton needs to be installed for the test"
elif (model not in _bert_models + _roberta_models) and enable_triton:
msg = "Triton kernels do not support Non bert/roberta models yet"
# These should be removed once we fix several inference tests failing
if model in [
"EleutherAI/pythia-70m-deduped", "distilbert/distilbert-base-cased-distilled-squad", "EleutherAI/gpt-j-6b"
]:
msg = "Test is currently broken"
return msg
@pytest.mark.inference
class TestModelTask(DistributedTest):
world_size = 1
def test(
self,
model_w_task,
dtype,
enable_cuda_graph,
enable_triton,
query,
inf_kwargs,
assert_fn,
perf_meas=True,
):
invalid_test_msg = validate_test(model_w_task, dtype, enable_cuda_graph, enable_triton)
if invalid_test_msg:
pytest.skip(invalid_test_msg)
if dtype not in get_accelerator().supported_dtypes():
pytest.skip(f"Acceleraor {get_accelerator().device_name()} does not support {dtype}.")
if not deepspeed.ops.__compatible_ops__[InferenceBuilder.NAME]:
pytest.skip("This op had not been implemented on this system.", allow_module_level=True)
model, task = model_w_task
local_rank = int(os.getenv("LOCAL_RANK", "0"))
# Load the model on CPU first to avoid OOM for large models @fp32
pipe = pipeline(task, model=model, device=torch.device("cpu"), framework="pt")
if dtype == torch.half:
pipe.model.half()
# Switch device to GPU after converting to half
device = torch.device(get_accelerator().device_name(local_rank))
pipe.device = device
pipe.model.to(device)
# Warm-up queries for perf measurement
#for i in range(10):
# _ = pipe(query, **inf_kwargs)
get_accelerator().synchronize()
start = time.time()
bs_output = pipe(query, **inf_kwargs)
get_accelerator().synchronize()
bs_time = time.time() - start
args = {
'mp_size': 1,
'dtype': dtype,
'replace_with_kernel_inject': True,
'enable_cuda_graph': enable_cuda_graph,
'use_triton': enable_triton,
'triton_autotune': False,
}
if pipe.tokenizer.model_max_length < deepspeed.ops.transformer.inference.config.DeepSpeedInferenceConfig(
).max_out_tokens:
args.update({'max_out_tokens': pipe.tokenizer.model_max_length})
pipe.model = deepspeed.init_inference(pipe.model, **args)
check_injection(pipe.model)
# Warm-up queries for perf measurement
#for i in range(10):
# _ = pipe(query, **inf_kwargs)
get_accelerator().synchronize()
start = time.time()
ds_output = pipe(query, **inf_kwargs)
get_accelerator().synchronize()
ds_time = time.time() - start
if perf_meas:
print(
f"model={model}, task={task}, dtype={dtype}, cuda_graph={enable_cuda_graph}, triton={enable_triton}, bs_time={bs_time}, ds_time={ds_time}"
)
# facebook/opt* and some bigscient/bloom* models are not matching
# baseline exactly, adding an exception to them for now
if ("opt" in model) or ("bloom" in model):
bs_output = pipe(query, **inf_kwargs)
# These performance tests are only measuring the time for a single
# inference request, we just want to check that performance isn't terrible
#assert ds_time <= (bs_time * 1.1)
assert assert_fn(bs_output, ds_output)
@pytest.mark.seq_inference
@pytest.mark.parametrize("model_w_task", [("EleutherAI/gpt-neo-1.3B", "text-generation"),
("EleutherAI/gpt-neox-20b", "text-generation"),
("bigscience/bloom-3b", "text-generation"),
("EleutherAI/gpt-j-6b", "text-generation")],
ids=["gpt-neo", "gpt-neox", "bloom", "gpt-j"])
class TestMPSize(DistributedTest):
world_size = 2
def test(
self,
model_w_task,
dtype,
query,
inf_kwargs,
assert_fn,
):
invalid_test_msg = validate_test(model_w_task, dtype, enable_cuda_graph=False, enable_triton=False)
if invalid_test_msg:
pytest.skip(invalid_test_msg)
if not deepspeed.ops.__compatible_ops__[InferenceBuilder.NAME]:
pytest.skip("This op had not been implemented on this system.", allow_module_level=True)
model, task = model_w_task
local_rank = int(os.getenv("LOCAL_RANK", "0"))
# We have to load these large models on CPU with pipeline because not
# enough GPU memory
pipe = pipeline(task, model=model, device=torch.device("cpu"), framework="pt")
bs_output = pipe(query, **inf_kwargs)
pipe.model = deepspeed.init_inference(pipe.model,
mp_size=self.world_size,
dtype=dtype,
replace_with_kernel_inject=True)
check_injection(pipe.model)
# Switch device to GPU so that input tensors are not on CPU
pipe.device = torch.device(get_accelerator().device_name(local_rank))
ds_output = pipe(query, **inf_kwargs)
print(local_rank, "baseline", bs_output)
print(local_rank, "deepspeed", ds_output)
assert assert_fn(bs_output, ds_output)
@pytest.mark.inference
@pytest.mark.parametrize("model_w_task", [("openai-community/gpt2", "text-generation")], ids=["gpt2"])
class TestLowCpuMemUsage(DistributedTest):
world_size = 1
def test(
self,
model_w_task,
query,
inf_kwargs,
assert_fn,
):
model, task = model_w_task
dtype = torch.float16
if dtype not in get_accelerator().supported_dtypes():
pytest.skip(f"Acceleraor {get_accelerator().device_name()} does not support {dtype}.")
local_rank = int(os.getenv("LOCAL_RANK", "0"))
device = getDeviceId(local_rank)
pipe = pipeline(task, model=model, model_kwargs={"low_cpu_mem_usage": True}, device=device, framework="pt")
bs_output = pipe(query, **inf_kwargs)
pipe.model = deepspeed.init_inference(pipe.model,
mp_size=self.world_size,
dtype=dtype,
replace_method="auto",
replace_with_kernel_inject=True)
ds_output = pipe(query, **inf_kwargs)
assert assert_fn(bs_output, ds_output)
@pytest.mark.seq_inference
@pytest.mark.parametrize(
"model_w_task, injection_policy",
[
(("google/t5-v1_1-small", "text2text-generation"), {
T5Block: ('SelfAttention.o', 'EncDecAttention.o', 'DenseReluDense.wo')
}),
(("FacebookAI/roberta-large", "fill-mask"), {
RobertaLayer: ('output.dense')
}),
],
ids=["t5", "roberta"],
)
@pytest.mark.parametrize("dtype", [torch.float], ids=["fp32"])
class TestInjectionPolicy(DistributedTest):
def test(self, model_w_task, injection_policy, query, inf_kwargs, assert_fn, dtype, world_size):
invalid_test_msg = validate_test(model_w_task, dtype, enable_cuda_graph=False, enable_triton=False)
if invalid_test_msg:
pytest.skip(invalid_test_msg)
model, task = model_w_task
local_rank = int(os.getenv("LOCAL_RANK", "0"))
pipe = pipeline(task,
model=model,
device=torch.device(get_accelerator().device_name(local_rank)),
framework="pt")
bs_output = pipe(query, **inf_kwargs)
pipe.model = deepspeed.init_inference(pipe.model,
mp_size=world_size,
dtype=dtype,
injection_policy=injection_policy)
ds_output = pipe(query, **inf_kwargs)
print(local_rank, "baseline", bs_output)
print(local_rank, "deepspeed", ds_output)
assert assert_fn(bs_output, ds_output)
@pytest.mark.seq_inference
@pytest.mark.parametrize("model_w_task", [("Felladrin/Llama-160M-Chat-v1", "text-generation")], ids=["llama"])
@pytest.mark.parametrize("dtype", [torch.half], ids=["fp16"])
class TestLlamaInjection(DistributedTest):
world_size = 1
def test(self, model_w_task, dtype, query, inf_kwargs, assert_fn):
invalid_test_msg = validate_test(model_w_task, dtype, enable_cuda_graph=False, enable_triton=False)
if invalid_test_msg:
pytest.skip(invalid_test_msg)
if dtype not in get_accelerator().supported_dtypes():
pytest.skip(f"Accelerator {get_accelerator().device_name()} does not support {dtype}.")
if not deepspeed.ops.__compatible_ops__[InferenceBuilder.NAME]:
pytest.skip("This op had not been implemented on this system.", allow_module_level=True)
model, task = model_w_task
local_rank = int(os.getenv("LOCAL_RANK", "0"))
device = torch.device(get_accelerator().device_name(local_rank))
pipe = pipeline(task,
model=model,
device=torch.device("cpu"),
model_kwargs={"low_cpu_mem_usage": True},
framework="pt")
if dtype == torch.half:
pipe.model.half()
pipe.device = device
pipe.model.to(device)
bs_output = pipe(query, **inf_kwargs)
try:
pipe.model = deepspeed.init_inference(pipe.model,
mp_size=self.world_size,
dtype=dtype,
replace_with_kernel_inject=True)
check_injection(pipe.model)
except AttributeError as e:
if "'LlamaAttention' object has no attribute 'num_heads'" in str(e):
pytest.skip("Skipping due to transformers version compatibility issue with self-attention")
raise e
ds_output = pipe(query, **inf_kwargs)
print(local_rank, "baseline", bs_output)
print(local_rank, "deepspeed", ds_output)
# Llama models are not matching baseline exactly
# We skip the result check for now, since this is irrelevant to this test
# assert assert_fn(bs_output, ds_output)
@pytest.mark.seq_inference
@pytest.mark.parametrize('keep_module_on_host', [True, False])
@pytest.mark.parametrize(
"model_w_task",
[("Helsinki-NLP/opus-mt-en-de", "translation"), ("Salesforce/codegen-350M-mono", "text-generation")],
ids=["marian", "codegen"], #codegen has fusedqkv weight.
)
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16], ids=["fp16", "bf16"])
class TestAutoTensorParallelism(DistributedTest):
world_size = [2]
def test(
self,
model_w_task,
query,
inf_kwargs,
assert_fn,
dtype,
keep_module_on_host,
):
invalid_test_msg = validate_test(model_w_task, dtype, enable_cuda_graph=False, enable_triton=False)
if invalid_test_msg:
pytest.skip(invalid_test_msg)
model, task = model_w_task
local_rank = int(os.getenv("LOCAL_RANK", "0"))
world_size = int(os.getenv("WORLD_SIZE", "2"))
if dtype not in get_accelerator().supported_dtypes():
pytest.skip(f"Acceleraor {get_accelerator().device_name()} does not support {dtype}.")
if model == "Salesforce/codegen-350M-mono":
pytest.skip("Disable Codegen model due to slight result difference")
#TODO: re-enable this test once we have a fix for the slight result difference
pipe = pipeline(task,
model=model,
device=torch.device(get_accelerator().device_name(local_rank)),
framework="pt")
bs_output = pipe(query, **inf_kwargs)
pipe.model = deepspeed.init_inference(pipe.model,
mp_size=world_size,
dtype=dtype,
keep_module_on_host=keep_module_on_host)
ds_output = pipe(query, **inf_kwargs)
print(local_rank, "baseline", bs_output)
print(local_rank, "deepspeed", ds_output)
assert assert_fn(bs_output, ds_output)
if keep_module_on_host:
for name, param in model.named_parameters():
assert param.device == torch.device('cpu'), f"keep_module_on_host is on but param {name} is not on cpu"
@pytest.mark.world_size(3)
def test_odd_world_size(
self,
model_w_task,
query,
inf_kwargs,
assert_fn,
dtype,
keep_module_on_host,
):
invalid_test_msg = validate_test(model_w_task, dtype, enable_cuda_graph=False, enable_triton=False)
if invalid_test_msg:
pytest.skip(invalid_test_msg)
model, task = model_w_task
if model == "Salesforce/codegen-350M-mono":
pytest.skip("codegen does not supported by odd world_size")
local_rank = int(os.getenv("LOCAL_RANK", "0"))
world_size = int(os.getenv("WORLD_SIZE", "3"))
pipe = pipeline(task,
model=model,
device=torch.device(get_accelerator().device_name(local_rank)),
framework="pt")
bs_output = pipe(query, **inf_kwargs)
pipe.model = deepspeed.init_inference(pipe.model,
mp_size=world_size,
dtype=dtype,
keep_module_on_host=keep_module_on_host)
ds_output = pipe(query, **inf_kwargs)
print(local_rank, "baseline", bs_output)
print(local_rank, "deepspeed", ds_output)
assert assert_fn(bs_output, ds_output)
if keep_module_on_host:
for name, param in model.named_parameters():
assert param.device == torch.device('cpu'), f"keep_module_on_host is on but param {name} is not on cpu"
@pytest.mark.nightly
@pytest.mark.parametrize(
"model_family, model_name",
(
["gpt2", "EleutherAI/gpt-neo-2.7B"],
#["gpt2", "EleutherAI/gpt-j-6b"], # Causing OOM for this test
["gpt2", "openai-community/gpt2-xl"],
),
)
@pytest.mark.parametrize("task", ["lambada_standard"])
class TestLMCorrectness(DistributedTest):
world_size = 1
exec_timeout = 1200 # Give these tests longer to complete
def test(self, model_family, model_name, task):
# imports here to avoid import errors when pytest collects tests
import lm_eval
import lm_eval.models
import lm_eval.tasks
import lm_eval.evaluator
# The bootstrap_stderr function in lm_eval.metrics uses a
# multiprocessing Pool to increase performance. Since we use a Pool for
# our distributed tests and cannot nest Pools, we must redefine and
# patch this function with a version that does not use Pool.
def no_pool_bootstrap_stderr(f, xs, iters):
from lm_eval.metrics import _bootstrap_internal
from lm_eval.metrics import sample_stddev
res = []
chunk_size = min(1000, iters)
for i in range(iters // chunk_size):
res.extend(_bootstrap_internal(f, chunk_size)((i, xs)))
return sample_stddev(res)
lm_eval.metrics.bootstrap_stderr = no_pool_bootstrap_stderr
local_rank = os.getenv("LOCAL_RANK", "0")
device = torch.device(get_accelerator().device_name(local_rank))
dtype = torch.float
task_dict = lm_eval.tasks.get_task_dict([task])
if 'gpt-j-6b' in model_name:
dtype = torch.half
lm = lm_eval.models.get_model(model_family).create_from_arg_string(f"pretrained={model_name}",
{"device": "cpu"})
setattr(lm, model_family, getattr(lm, model_family).half().to(device))
lm._device = device
else:
if get_accelerator().device_name() == 'hpu':
#lm_eval not supporting HPU device, so get model with CPU and move it to HPU.
lm = lm_eval.models.get_model(model_family).create_from_arg_string(f"pretrained={model_name}",
{"device": "cpu"})
setattr(lm, model_family, getattr(lm, model_family).to(device))
lm._device = device
else:
lm = lm_eval.models.get_model(model_family).create_from_arg_string(
f"pretrained={model_name}", {"device": get_accelerator().device_name()})
get_accelerator().synchronize()
start = time.time()
bs_output = lm_eval.evaluator.evaluate(lm=lm, task_dict=task_dict)
get_accelerator().synchronize()
bs_time = time.time() - start
getattr(lm, model_family).to("cpu")
ds_model = deepspeed.init_inference(
getattr(lm, model_family),
mp_size=1,
dtype=dtype,
replace_with_kernel_inject=True,
enable_cuda_graph=False,
)
check_injection(ds_model)
setattr(lm, model_family, ds_model)
get_accelerator().synchronize()
start = time.time()
ds_output = lm_eval.evaluator.evaluate(lm=lm, task_dict=task_dict)
get_accelerator().synchronize()
ds_time = time.time() - start
ppl_diff = abs(bs_output["results"][task]["ppl"] - ds_output["results"][task]["ppl"])
#assert ds_time <= bs_time
assert ppl_diff < 0.01
@@ -0,0 +1,44 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
import deepspeed
from unit.common import DistributedTest
from unit.simple_model import create_config_from_dict
@pytest.mark.inference
class TestInferenceConfig(DistributedTest):
world_size = 1
def test_overlap_kwargs(self):
config = {"replace_with_kernel_inject": True, "dtype": torch.float32}
kwargs = {"replace_with_kernel_inject": True}
engine = deepspeed.init_inference(torch.nn.Module(), config=config, **kwargs)
assert engine._config.replace_with_kernel_inject
def test_overlap_kwargs_conflict(self):
config = {"replace_with_kernel_inject": True}
kwargs = {"replace_with_kernel_inject": False}
with pytest.raises(ValueError):
engine = deepspeed.init_inference(torch.nn.Module(), config=config, **kwargs)
def test_kwargs_and_config(self):
config = {"replace_with_kernel_inject": True}
kwargs = {"dtype": torch.float32}
engine = deepspeed.init_inference(torch.nn.Module(), config=config, **kwargs)
assert engine._config.replace_with_kernel_inject
assert engine._config.dtype == kwargs["dtype"]
def test_json_config(self, tmpdir):
config = {"replace_with_kernel_inject": True, "dtype": "torch.float32"}
config_json = create_config_from_dict(tmpdir, config)
engine = deepspeed.init_inference(torch.nn.Module(), config=config_json)
assert engine._config.replace_with_kernel_inject
@@ -0,0 +1,61 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import os
import time
import pytest
import torch
import deepspeed
from transformers import pipeline
from unit.common import DistributedTest
from deepspeed.accelerator import get_accelerator
from deepspeed.ops.op_builder import InferenceBuilder
if not deepspeed.ops.__compatible_ops__[InferenceBuilder.NAME]:
pytest.skip("This op had not been implemented on this system.", allow_module_level=True)
if torch.half not in get_accelerator().supported_dtypes():
pytest.skip(f"fp16 not supported, valid dtype: {get_accelerator().supported_dtypes()}", allow_module_level=True)
@pytest.mark.inference
@pytest.mark.parametrize("use_cuda_events", [True, False])
@pytest.mark.parametrize("enable_cuda_graph", [True, False])
class TestModelProfiling(DistributedTest):
world_size = 1
def test(self, enable_cuda_graph, use_cuda_events):
task = "fill-mask"
model = "bert-base-cased"
dtype = torch.float16
query = "I am a [MASK] model"
local_rank = int(os.getenv("LOCAL_RANK", "0"))
world_size = int(os.getenv("WORLD_SIZE", "1"))
pipe = pipeline(task, model, framework="pt", device=get_accelerator().device_name(local_rank))
pipe.model = deepspeed.init_inference(pipe.model,
dtype=dtype,
mp_size=world_size,
replace_with_kernel_inject=True,
enable_cuda_graph=enable_cuda_graph)
pipe.model.profile_model_time(use_cuda_events=use_cuda_events)
e2e_times = []
model_times = []
for _ in range(10):
get_accelerator().synchronize()
start = time.perf_counter_ns()
r = pipe(query)
get_accelerator().synchronize()
end = time.perf_counter_ns()
e2e_times.append((end - start) / 1e6) # convert ns to ms
model_times.extend(pipe.model.model_times())
for e2e_t, model_t in zip(e2e_times, model_times):
assert e2e_t >= model_t
@@ -0,0 +1,48 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import os
import torch
import pytest
import deepspeed
import numpy
from unit.common import DistributedTest
from deepspeed.accelerator import get_accelerator
# Setup for these models is different from other pipelines, so we add a separate test
@pytest.mark.stable_diffusion
class TestStableDiffusion(DistributedTest):
world_size = 1
def test(self):
from diffusers import DiffusionPipeline
from image_similarity_measures.quality_metrics import rmse
dev = get_accelerator().device_name()
generator = torch.Generator(device=dev)
seed = 0xABEDABE7
generator.manual_seed(seed)
prompt = "a dog on a rocket"
model = "prompthero/midjourney-v4-diffusion"
local_rank = int(os.getenv("LOCAL_RANK", "0"))
device = torch.device(f"{dev}:{local_rank}")
pipe = DiffusionPipeline.from_pretrained(model, torch_dtype=torch.half)
pipe = pipe.to(device)
baseline_image = pipe(prompt, guidance_scale=7.5, generator=generator).images[0]
pipe = deepspeed.init_inference(
pipe,
mp_size=1,
dtype=torch.half,
replace_with_kernel_inject=True,
enable_cuda_graph=True,
)
generator.manual_seed(seed)
deepspeed_image = pipe(prompt, guidance_scale=7.5, generator=generator).images[0]
rmse_value = rmse(org_img=numpy.asarray(baseline_image), pred_img=numpy.asarray(deepspeed_image))
# RMSE threshold value is arbitrary, may need to adjust as needed
assert rmse_value <= 0.01
+4
View File
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,46 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Tuple
import torch
from deepspeed.accelerator import get_accelerator
TOLERANCES = None
def get_tolerances():
global TOLERANCES
if TOLERANCES is None:
TOLERANCES = {torch.float32: (5e-4, 5e-5), torch.float16: (3e-2, 2e-3)}
if get_accelerator().is_bf16_supported():
# Note: BF16 tolerance is higher than FP16 because of the lower precision (7 (+1) bits vs
# 10 (+1) bits)
TOLERANCES[torch.bfloat16] = (4.8e-1, 3.2e-2)
return TOLERANCES
DTYPES = None
def get_dtypes(include_float=True):
global DTYPES
if DTYPES is None:
DTYPES = [torch.float16, torch.float32] if include_float else [torch.float16]
try:
if get_accelerator().is_bf16_supported():
DTYPES.append(torch.bfloat16)
except (AssertionError, AttributeError):
pass
return DTYPES
def allclose(x, y, tolerances: Tuple[int, int] = None):
assert x.dtype == y.dtype
if tolerances is None:
rtol, atol = get_tolerances()[x.dtype]
else:
rtol, atol = tolerances
return torch.allclose(x, y, rtol=rtol, atol=atol)
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,101 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.inference_utils import ActivationType, DtypeEnum
from deepspeed.inference.v2.kernels.core_ops import CUDABiasActivation
from ....v2.inference_test_utils import get_dtypes, allclose
def reference_bias_act_implementation(input: torch.Tensor, bias: Optional[torch.Tensor],
act_type: ActivationType) -> torch.Tensor:
bias_func_map = {
ActivationType.RELU: torch.nn.functional.relu,
ActivationType.GELU: torch.nn.functional.gelu,
ActivationType.SILU: torch.nn.functional.silu,
ActivationType.IDENTITY: lambda x: x,
}
dtype = input.dtype
input_f = input.to(torch.float32)
if bias is not None:
bias_f = bias.to(torch.float32)
output_f = input_f + bias_f
else:
output_f = input_f
output_f = bias_func_map[act_type](output_f)
return output_f.to(dtype)
def _bias_activation_test_helper(tokens: int,
channels: int,
act_fn: ActivationType,
dtype: DtypeEnum,
use_bias: bool = True) -> None:
"""
Fully parameterized testing entry point.
"""
# Input vals
input_tensor = torch.randn((tokens, channels), dtype=dtype.value, device=get_accelerator().current_device_name())
if use_bias:
bias = torch.randn((channels), dtype=dtype.value, device=get_accelerator().current_device_name())
else:
bias = None
# Reference output
ref_output = reference_bias_act_implementation(input_tensor, bias, act_fn)
bias_act = CUDABiasActivation(channels, dtype, act_fn)
# New output
ds_tensor = input_tensor.clone()
bias_act(ds_tensor, bias)
# Check
assert allclose(ds_tensor, ref_output)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("tokens, channels", [(1, 4096), (37, 2048), (112, 14432), (1024, 6144)])
@pytest.mark.parametrize("dtype", get_dtypes(include_float=False))
def test_token_channels_permutations(tokens: int, channels: int, dtype: torch.dtype) -> None:
"""
Validate bias activation kernel with different token and channel permutations when using the RELU
activation function.
"""
act_fn = ActivationType.RELU
dtype = DtypeEnum(dtype)
_bias_activation_test_helper(tokens, channels, act_fn, dtype)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("act_fn",
[ActivationType.RELU, ActivationType.GELU, ActivationType.SILU, ActivationType.IDENTITY])
def test_act_fns(act_fn: ActivationType) -> None:
"""
Validate bias activation kernel with different activation functions.
"""
tokens = 223
channels = 4096
dtype = DtypeEnum.fp16
_bias_activation_test_helper(tokens, channels, act_fn, dtype)
@pytest.mark.inference_v2_ops
def test_no_bias() -> None:
"""
Validate bias activation kernel with no bias.
"""
tokens = 223
channels = 4096
dtype = DtypeEnum.fp16
act_fn = ActivationType.IDENTITY
_bias_activation_test_helper(tokens, channels, act_fn, dtype, use_bias=False)
@@ -0,0 +1,73 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Tuple
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.kernels.core_ops import BlasLibLinear
from ....v2.inference_test_utils import allclose
# Note: only testing with FP16 and BF16 because we use TF32 on Ampere and we don't have a good
# set of tolerances. Since this is just on top of BLAS though, the test is more about
# making sure the stride/contiguity is correct and that's data type agnostic.
def reference_implementation(hidden_states, weights):
return hidden_states @ weights.t()
problem_shapes = [
(1, 1, 1024, 1024),
(1, 1024, 1024, 1024),
(2, 1024, 1024, 1024),
(1, 128, 768, 3072),
(1, 128, 3072, 768),
(1, 1024, 8192, 8192),
(1, 733, 8192, 32768),
(1, 13, 32768, 8192),
]
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("fp_dtype", [torch.float16, torch.bfloat16])
@pytest.mark.parametrize("problem_shape", problem_shapes)
def test_blas_linear(fp_dtype: torch.dtype, problem_shape: Tuple[int, int, int, int]):
batch, seq_len, in_features, out_features = problem_shape
hidden_states = torch.randn(batch, seq_len, in_features, dtype=fp_dtype,
device=get_accelerator().current_device()) * 0.1
weights = torch.randn(out_features, in_features, dtype=fp_dtype, device=get_accelerator().current_device()) * 0.01
ds_output = torch.empty(batch, seq_len, out_features, dtype=fp_dtype, device=get_accelerator().current_device())
ds_kernel = BlasLibLinear(fp_dtype)
ds_output = ds_kernel(ds_output, hidden_states, weights)
ref_output = reference_implementation(hidden_states, weights)
assert allclose(ds_output, ref_output)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("fp_dtype", [torch.float16, torch.bfloat16])
@pytest.mark.parametrize("problem_shape", problem_shapes)
def test_blas_linear_t(fp_dtype: torch.dtype, problem_shape: Tuple[int, int, int, int]):
batch, seq_len, in_features, out_features = problem_shape
hidden_states = torch.randn(batch, seq_len, in_features, dtype=fp_dtype,
device=get_accelerator().current_device()) * 0.1
weights = torch.randn(out_features, in_features, dtype=fp_dtype, device=get_accelerator().current_device()) * 0.01
ds_output = torch.empty(batch, seq_len, out_features, dtype=fp_dtype, device=get_accelerator().current_device())
ds_kernel = BlasLibLinear(fp_dtype)
# Transpose the weights then revert to the format we expect.
weights = weights.t().contiguous()
weights = weights.t()
ds_output = ds_kernel(ds_output, hidden_states, weights)
ref_output = reference_implementation(hidden_states, weights)
assert allclose(ds_output, ref_output)
@@ -0,0 +1,133 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Iterable, Optional
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.kernels.core_ops import CUDAGatedActivation
from deepspeed.inference.v2.inference_utils import ActivationType
from ....v2.inference_test_utils import get_dtypes, allclose
def reference_geglu_implementation(input: torch.Tensor,
bias: Optional[torch.Tensor] = None,
act_fn: Optional[ActivationType] = ActivationType.GEGLU) -> torch.Tensor:
act_func_map = {
ActivationType.ReGLU: torch.nn.functional.relu,
ActivationType.GEGLU: lambda x: torch.nn.functional.gelu(x, approximate="tanh"),
ActivationType.SiGLU: torch.nn.functional.silu,
}
dtype = input.dtype
input = input.to(torch.float32)
if bias is not None:
bias = bias.to(torch.float32)
input = input + bias
act_act = input[..., ::2]
act_linear = input[..., 1::2]
act_act = act_func_map[act_fn](act_act)
return (act_act * act_linear).to(dtype)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("shape", [(1372, 16384), (2, 743, 22016)])
@pytest.mark.parametrize("dtype", get_dtypes())
def test_dtypes(shape: Iterable[int], dtype: torch.dtype) -> None:
input_tensor = torch.randn(shape, dtype=dtype, device=get_accelerator().current_device_name())
# Reference output
ref_output = reference_geglu_implementation(input_tensor, act_fn=ActivationType.GEGLU)
# Build kernel
geglu = CUDAGatedActivation(input_tensor.size(-1), input_tensor.dtype, ActivationType.GEGLU)
# New output
output_shape = list(input_tensor.shape)
output_shape[-1] //= 2
output_tensor = torch.empty(output_shape, dtype=input_tensor.dtype, device=get_accelerator().current_device_name())
geglu(output_tensor, input_tensor)
# Check
assert allclose(output_tensor, ref_output)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("act_fn", [ActivationType.GEGLU, ActivationType.ReGLU, ActivationType.SiGLU])
def test_act_fn(act_fn: ActivationType) -> None:
input_tensor = torch.randn(832, 4096, dtype=torch.float16, device=get_accelerator().current_device())
# Reference output
ref_output = reference_geglu_implementation(input_tensor, act_fn=act_fn)
cuda_act = CUDAGatedActivation(4096, torch.float16, act_fn)
# New output
output_tensor = torch.empty(832, 2048, dtype=torch.float16, device=get_accelerator().current_device())
cuda_act(output_tensor, input_tensor)
assert allclose(output_tensor, ref_output)
@pytest.mark.inference_v2_ops
def test_act_with_bias():
input_tensor = torch.randn(832, 4096, dtype=torch.float16, device=get_accelerator().current_device())
bias = torch.randn(4096, dtype=torch.float16, device=get_accelerator().current_device())
# Reference output
ref_output = reference_geglu_implementation(input_tensor, bias=bias, act_fn=ActivationType.GEGLU)
cuda_act = CUDAGatedActivation(4096, torch.float16, ActivationType.GEGLU)
# New output
output_tensor = torch.empty(832, 2048, dtype=torch.float16, device=get_accelerator().current_device())
cuda_act(output_tensor, input_tensor, bias)
assert allclose(output_tensor, ref_output)
@pytest.mark.inference_v2_ops
def test_max_channels():
input_tensor = torch.randn(832, 48152, dtype=torch.float16, device=get_accelerator().current_device())
ref_output = reference_geglu_implementation(input_tensor, act_fn=ActivationType.GEGLU)
cuda_act = CUDAGatedActivation(48152, torch.float16, ActivationType.GEGLU)
output_tensor = torch.empty(832, 24076, dtype=torch.float16, device=get_accelerator().current_device())
cuda_act(output_tensor, input_tensor)
assert allclose(output_tensor, ref_output)
@pytest.mark.inference_v2_ops
def test_bad_dtype() -> None:
with pytest.raises(ValueError):
CUDAGatedActivation(128, torch.int8, ActivationType.GEGLU)
@pytest.mark.inference_v2_ops
def test_bad_act_fn() -> None:
with pytest.raises(ValueError):
CUDAGatedActivation(128, torch.float16, ActivationType.RELU)
@pytest.mark.inference_v2_ops
def test_bad_alignment() -> None:
with pytest.raises(ValueError):
CUDAGatedActivation(127, torch.float16, ActivationType.GEGLU)
@pytest.mark.inference_v2_ops
def test_too_many_channels() -> None:
with pytest.raises(ValueError):
CUDAGatedActivation(49160, torch.float16, ActivationType.GEGLU)
@@ -0,0 +1,47 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.kernels.core_ops import CUDAFPPostLN
from ....v2.inference_test_utils import get_dtypes, allclose
def reference_implementation(residual: torch.Tensor, hidden_states: torch.Tensor, gamma: torch.Tensor,
beta: torch.Tensor, epsilon: float) -> torch.Tensor:
residual_f = residual.to(torch.float32)
hidden_states_f = hidden_states.to(torch.float32)
gamma_f = gamma.to(torch.float32)
beta_f = beta.to(torch.float32)
return torch.nn.functional.layer_norm(residual_f + hidden_states_f, (hidden_states_f.size(-1), ),
weight=gamma_f,
bias=beta_f,
eps=epsilon).to(hidden_states.dtype)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("tokens, channels", [(1, 4096), (37, 2048), (112, 14432), (1024, 6144)])
@pytest.mark.parametrize("dtype", get_dtypes())
def test_cuda_post_ln(tokens: int, channels: int, dtype: torch.dtype) -> None:
# Input vals
hidden_states = torch.randn((tokens, channels), dtype=dtype, device=get_accelerator().current_device_name())
residual = torch.randn((tokens, channels), dtype=dtype, device=get_accelerator().current_device_name())
gamma = torch.randn((channels), dtype=dtype, device=get_accelerator().current_device_name())
beta = torch.rand((channels), dtype=dtype, device=get_accelerator().current_device_name())
epsilon = 1e-5
# Reference output
ref_output = reference_implementation(residual, hidden_states, gamma, beta, epsilon)
# New output
post_ln_kernel = CUDAFPPostLN(hidden_states.size(-1), residual.dtype)
ds_output = torch.empty_like(residual)
post_ln_kernel(ds_output, residual, hidden_states, gamma, beta)
# Check
assert allclose(ds_output, ref_output)
@@ -0,0 +1,51 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.kernels.core_ops import CUDAFPPreLN
from ....v2.inference_test_utils import get_dtypes, allclose
def reference_implementation(residual: torch.Tensor, hidden_states: torch.Tensor, gamma: torch.Tensor,
beta: torch.Tensor, epsilon: float) -> torch.Tensor:
residual_f = residual.to(torch.float32)
hidden_states_f = hidden_states.to(torch.float32)
gamma_f = gamma.to(torch.float32)
beta_f = beta.to(torch.float32)
residual_out = residual_f + hidden_states_f
hidden_out = torch.nn.functional.layer_norm(residual_out, (hidden_states_f.size(-1), ),
weight=gamma_f,
bias=beta_f,
eps=epsilon)
return residual_out.to(hidden_states.dtype), hidden_out.to(hidden_states.dtype)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("tokens, channels", [(1, 4096), (37, 2048), (112, 14432), (1024, 6144)])
@pytest.mark.parametrize("dtype", get_dtypes())
def test_cuda_pre_ln(tokens: int, channels: int, dtype: torch.dtype) -> None:
# Input vals
hidden_states = torch.randn((tokens, channels), dtype=dtype, device=get_accelerator().current_device_name())
residual = torch.randn((tokens, channels), dtype=dtype, device=get_accelerator().current_device_name())
gamma = torch.randn((channels), dtype=dtype, device=get_accelerator().current_device_name())
beta = torch.rand((channels), dtype=dtype, device=get_accelerator().current_device_name())
epsilon = 1e-5
# Reference output
ref_output_res, ref_output_hid = reference_implementation(residual, hidden_states, gamma, beta, epsilon)
# New output
pre_ln_kernel = CUDAFPPreLN(hidden_states.size(-1), residual.dtype)
ds_output_res = torch.empty_like(residual)
ds_output_hid = torch.empty_like(hidden_states)
pre_ln_kernel(ds_output_res, ds_output_hid, residual, hidden_states, gamma, beta)
# Check
assert allclose(ds_output_res, ref_output_res)
assert allclose(ds_output_hid, ref_output_hid)
@@ -0,0 +1,77 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.inference_utils import DtypeEnum
from deepspeed.inference.v2.kernels.core_ops import CUDARMSNorm, CUDARMSPreNorm
from ....v2.inference_test_utils import get_dtypes, allclose
def reference_rms_norm(vals: torch.Tensor, gamma: torch.Tensor, epsilon: float = 1e-5) -> torch.Tensor:
variance = vals.to(torch.float32).pow(2).mean(-1, keepdim=True)
vals = vals * torch.rsqrt(variance + epsilon)
if gamma.dtype in [torch.float16, torch.bfloat16]:
vals = vals.to(gamma.dtype)
return gamma * vals
def reference_rms_pre_norm(vals: torch.Tensor,
residual: torch.Tensor,
gamma: torch.Tensor,
epsilon: float = 1e-5) -> torch.Tensor:
residual = residual + vals
return residual, reference_rms_norm(residual, gamma, epsilon)
def _rms_norm_testing_helper(rows: int, channels: int, do_residual: bool, dtype: DtypeEnum) -> None:
device = get_accelerator().current_device_name()
t_dtype = dtype.value
vals = torch.randn((rows, channels), dtype=t_dtype, device=device)
gamma = torch.randn((channels), dtype=t_dtype, device=device)
epsilon = 1e-5
if do_residual:
residual_in = torch.randn((rows, channels), dtype=t_dtype, device=device)
ds_residual = residual_in.clone()
ref_residual, ref_output = reference_rms_pre_norm(vals, residual_in, gamma, epsilon)
kernel = CUDARMSPreNorm(channels, t_dtype, epsilon=epsilon)
ds_out = torch.empty_like(ds_residual)
kernel(ds_residual, ds_out, residual_in, vals, gamma)
assert allclose(ds_out, ref_output)
assert allclose(ds_residual, ref_residual)
else:
ref_output = reference_rms_norm(vals, gamma, epsilon)
kernel = CUDARMSNorm(channels, t_dtype, epsilon=epsilon)
ds_out = torch.empty_like(vals)
kernel(ds_out, vals, gamma)
assert allclose(ds_out, ref_output)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("dtype", get_dtypes())
@pytest.mark.parametrize("do_residual", [True, False])
def test_rms_dtypes(dtype: DtypeEnum, do_residual: bool) -> None:
_rms_norm_testing_helper(883, 1024, do_residual, DtypeEnum(dtype))
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("rows, cols", [(1, 4096), (37, 2048), (112, 14432), (1024, 6144)])
@pytest.mark.parametrize("do_residual", [True, False])
def test_rms_shapes(rows: int, cols: int, do_residual: bool) -> None:
_rms_norm_testing_helper(rows, cols, do_residual, DtypeEnum.fp16)
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,113 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.inference_utils import ActivationType, DtypeEnum
from deepspeed.inference.v2.kernels.cutlass_ops import MoEGEMM
from ....v2.inference_test_utils import allclose
SINGLE_EXPERT_CASES = [(13, 2048, 2048), (256, 1024, 4096), (278, 5120, 2048), (893, 5120, 2560)]
PYTORCH_ACT_FN_MAP = {
ActivationType.GELU: torch.nn.functional.gelu,
ActivationType.SILU: torch.nn.functional.silu,
ActivationType.RELU: torch.nn.functional.relu
}
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("n_tokens, in_neurons, out_neurons", SINGLE_EXPERT_CASES)
def test_single_expert(n_tokens: int, in_neurons: int, out_neurons: int) -> None:
"""
Validate that the GEMM kernel produces identical results for a single GEMM instance.
"""
device = get_accelerator().current_device()
activations = torch.rand((n_tokens, in_neurons), device=device, dtype=torch.float16) - 0.5
weights = torch.rand((1, in_neurons, out_neurons), device=device, dtype=torch.float16) - 0.5
biases = torch.randn((1, out_neurons), device=device, dtype=torch.float16)
weights_ref = weights.reshape(in_neurons, out_neurons)
biases_ref = biases.reshape(out_neurons)
ref_output = torch.matmul(activations, weights_ref) + biases_ref
moe_gemm = MoEGEMM(DtypeEnum.fp16, ActivationType.IDENTITY)
output = torch.empty((n_tokens, out_neurons), device=device, dtype=torch.float16)
cumsum_rows = torch.tensor([n_tokens], dtype=torch.int64, device=device)
moe_gemm(output, activations, weights, cumsum_rows, biases)
assert allclose(output, ref_output, tolerances=(1e-2, 1e-2))
get_accelerator().synchronize()
def moe_test_helper(in_neurons: int, out_neurons: int, n_experts: int, max_tokens_per_expert: int,
act_fn: ActivationType, dtype: DtypeEnum) -> None:
"""
Helper function for validating the GEMM kernel for a single expert.
"""
device = get_accelerator().current_device()
expert_allocations = torch.randint(0, max_tokens_per_expert, (n_experts, ), device=device, dtype=torch.int32)
cumsum_rows = expert_allocations.cumsum(dim=0)
print(cumsum_rows.dtype)
activations = torch.rand((cumsum_rows[-1], in_neurons), device=device, dtype=dtype.value) - 0.5
weights = torch.rand((n_experts, in_neurons, out_neurons), device=device, dtype=dtype.value) - 0.5
biases = torch.randn((n_experts, out_neurons), device=device, dtype=dtype.value)
out_ref = torch.empty((cumsum_rows[-1], out_neurons), device=device, dtype=dtype.value)
for expert_idx in range(n_experts):
start = cumsum_rows[expert_idx - 1] if expert_idx > 0 else 0
end = cumsum_rows[expert_idx]
activations_slice = activations[start:end]
weights_slice = weights[expert_idx]
biases_slice = biases[expert_idx]
out_ref[start:end] = torch.matmul(activations_slice, weights_slice) + biases_slice
if act_fn != ActivationType.IDENTITY:
act_fn_fn = PYTORCH_ACT_FN_MAP[act_fn]
out_ref = act_fn_fn(out_ref)
moe_gemm = MoEGEMM(DtypeEnum.fp16, act_fn)
output = torch.empty((cumsum_rows[-1], out_neurons), device=device, dtype=dtype.value)
moe_gemm(output, activations, weights, cumsum_rows, biases)
if dtype == DtypeEnum.bf16:
assert allclose(output, out_ref, tolerances=(1e-1, 1e-1))
else:
assert allclose(output, out_ref, tolerances=(1e-2, 1e-2))
get_accelerator().synchronize()
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("max_tokens_per_expert", [1, 4, 16, 64, 128])
def test_multi_expert(max_tokens_per_expert: int) -> None:
"""
Validate for multi-expert GEMM instances that the output is identical to the reference.
"""
moe_test_helper(5120, 2048, 64, max_tokens_per_expert, ActivationType.IDENTITY, DtypeEnum.fp16)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("act_fn", [ActivationType.GELU, ActivationType.SILU, ActivationType.RELU])
def test_act_fns(act_fn: ActivationType) -> None:
"""
Validate activation function behavior.
"""
moe_test_helper(5120, 2048, 64, 32, act_fn, DtypeEnum.fp16)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("dtype", [DtypeEnum.fp16, DtypeEnum.bf16])
def test_dtypes(dtype: DtypeEnum) -> None:
"""
Validate data type behavior.
"""
moe_test_helper(5120, 2048, 64, 32, ActivationType.IDENTITY, dtype)
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,300 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import random
from typing import List, Optional, Tuple
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.ragged import (
AllocationMode,
DSSequenceDescriptor,
DSStateManager,
DSStateManagerConfig,
KVCacheConfig,
MemoryConfig,
PlaceholderSequenceDescriptor,
RaggedBatchWrapper,
)
from ....v2.inference_test_utils import allclose
def build_simple_batch(seq_lens: List[int],
vocab_range: Optional[int] = 100,
padding: Optional[bool] = False) -> RaggedBatchWrapper:
"""
Construct a simple batch with the given sequence lengths. This method should not
be used for for testing scenarios that require information about KV or sequence
history.
"""
total_tokens = max(sum(seq_lens), 1024)
n_seqs = max(len(seq_lens), 128)
config = DSStateManagerConfig(max_tracked_sequences=n_seqs,
max_ragged_sequence_count=n_seqs,
max_ragged_batch_size=total_tokens)
batch = RaggedBatchWrapper(config)
batch.clear()
for seq_len in seq_lens:
seq_desc = PlaceholderSequenceDescriptor()
tokens = torch.randint(0, vocab_range, (seq_len, ))
batch.insert_sequence(seq_desc, tokens)
batch.finalize(padding=padding)
return batch
def build_complex_batch(seq_params: List[Tuple[int, int, int]],
kv_block_size: int,
vocab_range: Optional[int] = 100,
padding: Optional[bool] = False) -> Tuple[RaggedBatchWrapper, int]:
"""
Construct a fully paramtrized batch with the given sequence lengths. This method
can be used to construct more realistic inputs for testing scenarios that will interact
with all the members of the RaggedBatchWrapper.
"""
seq_lens = [seq_param[0] for seq_param in seq_params]
total_tokens = max(sum(seq_lens), 1024)
n_seqs = max(len(seq_lens), 128)
config = DSStateManagerConfig(max_tracked_sequences=n_seqs,
max_ragged_sequence_count=n_seqs,
max_ragged_batch_size=total_tokens)
batch = RaggedBatchWrapper(config)
batch.clear()
total_kv_blocks = 0
for seq_len, n_seen_tokens, kv_ptr in seq_params:
n_kv_blocks = (seq_len + n_seen_tokens + kv_block_size - 1) // kv_block_size
seq_desc = PlaceholderSequenceDescriptor(seen_tokens=n_seen_tokens,
cur_allocated_blocks=n_kv_blocks,
kv_blocks_ptr=kv_ptr)
tokens = torch.randint(0, vocab_range, (seq_len, ))
batch.insert_sequence(seq_desc, tokens)
total_kv_blocks += n_kv_blocks
batch.finalize(padding=padding)
return batch, total_kv_blocks
def build_batch_and_manager(
seq_params: List[Tuple[int, int]],
head_size: int,
n_heads_kv: int,
kv_block_size: int,
vocab_range: Optional[int] = 100,
padding: Optional[bool] = False,
kv_fill: Optional[List[torch.Tensor]] = None
) -> Tuple[RaggedBatchWrapper, DSStateManager, List[DSSequenceDescriptor]]:
"""
Will construct and populate a batch and KVCache with the given sequence parameters.
Arguments:
seq_params (List[Tuple[int, int]]): A list of tuples containing the sequence length and
the number of tokens that have already been seen for that sequence.
head_size (int): The size of each attention head.
n_heads_kv (int): The number of attention heads for the KV-cache.
kv_block_size (int): The size of each block in the KV-cache.
vocab_range (Optional[int]): The range of the vocabulary. Defaults to 100.
padding (Optional[bool]): Whether to pad the batch. Defaults to False.
kv_fill (Optional[List[torch.Tensor]]): A list of tensors to use to populate the KV-cache.
If this is not provided, the KV-cache will be treated as empty and the contents should
not be relied upon. NOTE(cmikeh2): This functionality relies on the functionality
of LinearBlockedKVCopy. If tests relying on this feature are failing, make sure that
LinearBlockedKVCopy is working correctly.
"""
seq_lens = [seq_param[0] for seq_param in seq_params]
fill_lens = [seq_param[1] for seq_param in seq_params]
max_created_batch_len = max(sum(seq_lens), sum(fill_lens))
total_tokens = max(max_created_batch_len, 1024)
n_seqs = max(len(seq_lens), 128)
req_kv_blocks = [None] * n_seqs
total_kv_blocks = 0
for i, (seq_len, n_seen_tokens) in enumerate(seq_params):
req_kv_blocks[i] = (seq_len + n_seen_tokens + kv_block_size - 1) // kv_block_size
total_kv_blocks += req_kv_blocks[i]
kv_config = KVCacheConfig(block_size=kv_block_size,
num_allocation_groups=1,
cache_shape=(1, n_heads_kv, head_size))
memory_config = MemoryConfig(mode=AllocationMode.ALLOCATE, size=total_kv_blocks)
config = DSStateManagerConfig(max_tracked_sequences=n_seqs,
max_ragged_sequence_count=n_seqs,
max_ragged_batch_size=total_tokens,
memory_config=memory_config)
batch = RaggedBatchWrapper(config)
state_manager = DSStateManager(config, (kv_config, ))
# At the beginning of operation, the design of the allocator is such that it will return
# linear blocks of memory. The following will "warm up" the allocator so that we can be
# more certain that code is not dependent on this behavior.
all_allocs = []
for _ in range(20):
decision = random.randint(0, 1)
if decision == 0:
blocks_to_allocate = random.randint(0, total_kv_blocks)
if blocks_to_allocate <= state_manager.free_blocks[0] and blocks_to_allocate > 0:
all_allocs.append(state_manager.allocate_blocks(blocks_to_allocate))
else:
if len(all_allocs) > 0:
idx = random.randint(0, len(all_allocs) - 1)
state_manager._kv_cache.free(all_allocs[idx])
del all_allocs[idx]
for alloc in all_allocs:
state_manager._kv_cache.free(alloc)
assert state_manager.free_blocks[0] == total_kv_blocks
batch.clear()
seq_descs = []
if kv_fill is None or sum(fill_lens) == 0:
for i, (seq_len, n_seen_tokens) in enumerate(seq_params):
# Create empty descriptor
seq_desc = state_manager.get_or_create_sequence(i)
# Update `seen_tokens` in the descriptor
seq_desc.pre_forward(n_seen_tokens)
seq_desc.post_forward()
# Ensure there's enough KV-cache for the sequence
kv_block_ids = state_manager.allocate_blocks(req_kv_blocks[i])
print(f"Allocated {req_kv_blocks[i]} blocks for sequence {i}: {kv_block_ids}")
seq_desc.extend_kv_cache(kv_block_ids)
# Insert sequence into batch
tokens = torch.randint(0, vocab_range, (seq_len, ))
batch.insert_sequence(seq_desc, tokens)
seq_desc.pre_forward(seq_len)
seq_descs.append(seq_desc)
else:
qkv = torch.empty((total_tokens, (n_heads_kv * 3) * head_size),
dtype=torch.float16,
device=get_accelerator().current_device())
fills_as_tensor = torch.tensor(fill_lens, dtype=torch.int32)
fill_cumsum = torch.cat((torch.tensor([0], dtype=torch.int32), torch.cumsum(fills_as_tensor, dim=0)))
for i, (_, n_seen_tokens) in enumerate(seq_params):
# Create empty descriptor
seq_desc = state_manager.get_or_create_sequence(i)
# Update `seen_tokens` in the descriptor
if n_seen_tokens > 0:
dummy_fill_toks = torch.randint(0, vocab_range, (n_seen_tokens, ))
batch.insert_sequence(seq_desc, dummy_fill_toks)
seq_desc.pre_forward(n_seen_tokens)
# Ensure there's enough KV-cache for the sequence
kv_block_ids = state_manager.allocate_blocks(req_kv_blocks[i])
print(f"Allocated {req_kv_blocks[i]} blocks for sequence {i}: {kv_block_ids}")
seq_desc.extend_kv_cache(kv_block_ids)
seq_descs.append(seq_desc)
if n_seen_tokens == 0:
continue
assert kv_fill[i].shape[0] == n_seen_tokens
assert kv_fill[i].shape[1] == n_heads_kv * head_size * 2
local_q = torch.randn((n_seen_tokens, n_heads_kv * head_size), dtype=torch.float16, device=qkv.device)
local_qkv = torch.cat((local_q, kv_fill[i]), dim=1)
qkv[fill_cumsum[i]:fill_cumsum[i + 1]] = local_qkv
batch.finalize(padding=padding)
from deepspeed.inference.v2.kernels.ragged_ops import LinearBlockedKVCopy
kv_copy = LinearBlockedKVCopy(head_size, n_heads_kv, n_heads_kv, torch.float16)
kv_cache = state_manager.get_cache(0)
kv_copy(kv_cache, qkv, batch)
for seq_desc in seq_descs:
if seq_desc.in_flight_tokens > 0:
seq_desc.post_forward()
batch.clear()
for i, (seq_len, _) in enumerate(seq_params):
seq_desc = state_manager.get_or_create_sequence(i)
tokens = torch.randint(0, vocab_range, (seq_len, ))
batch.insert_sequence(seq_desc, tokens)
seq_desc.pre_forward(seq_len)
# We will skip KV cache allocation here because we did a lump allocation above
# for both the fill and the sequence itself.
batch.finalize(padding=padding)
return batch, state_manager, seq_descs
def validate_kv_cache(kv_cache: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
seq_descs: List[DSSequenceDescriptor],
batch: RaggedBatchWrapper,
exact: bool = True) -> None:
"""
Given a QKV tensor and a KV cache, validate that the cache contains the correct values.
"""
block_size = kv_cache.shape[1]
n_kv_heads = kv_cache.shape[3]
head_size = kv_cache.shape[4]
inflight_descs = batch.inflight_seq_descriptors(on_device=False)[:batch.current_sequences]
if inflight_descs.shape[0] != len(seq_descs):
raise ValueError("The number of sequence descriptors does not match the number of sequences in the batch.")
for seq_desc, inflight_seq in zip(seq_descs, inflight_descs):
start_idx = inflight_seq[0]
assigned_kv_blocks = seq_desc.kv_cache_ids(on_device=False)
real_k_values = k[start_idx:start_idx + seq_desc.in_flight_tokens]
real_v_values = v[start_idx:start_idx + seq_desc.in_flight_tokens]
start_block_idx = seq_desc.seen_tokens // block_size
local_start_idx = 0
cur_start_idx = seq_desc.seen_tokens
for block_idx in range(start_block_idx, seq_desc.cur_allocated_blocks):
block = kv_cache[assigned_kv_blocks[0, block_idx].item()]
block_start_idx = cur_start_idx % block_size
n_tokens_to_check = min(block_size - block_start_idx, seq_desc.in_flight_tokens - local_start_idx)
block_end_idx = block_start_idx + n_tokens_to_check
if exact:
assert torch.equal(
block[block_start_idx:block_end_idx, 0, :, :],
real_k_values[local_start_idx:local_start_idx + n_tokens_to_check].reshape(
n_tokens_to_check, n_kv_heads, head_size))
assert torch.equal(
block[block_start_idx:block_end_idx, 1, :, :],
real_v_values[local_start_idx:local_start_idx + n_tokens_to_check].reshape(
n_tokens_to_check, n_kv_heads, head_size))
else:
assert allclose(
block[block_start_idx:block_end_idx, 0, :, :],
real_k_values[local_start_idx:local_start_idx + n_tokens_to_check].reshape(
n_tokens_to_check, n_kv_heads, head_size))
assert allclose(
block[block_start_idx:block_end_idx, 1, :, :],
real_v_values[local_start_idx:local_start_idx + n_tokens_to_check].reshape(
n_tokens_to_check, n_kv_heads, head_size))
local_start_idx += n_tokens_to_check
cur_start_idx += n_tokens_to_check
@@ -0,0 +1,45 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.inference.v2.kernels.ragged_ops import AtomBuilder
from .ragged_testing_utils import build_complex_batch
Q_BLOCK_SIZE = 128
KV_BLOCK_SIZE = 128
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize('seq_params', [(1, 0, 0), (1, 228, 0), (383, 0, 0), (1, 494, 0)])
def test_single_sequence(seq_params) -> None:
seq_len, n_seen_tokens, _ = seq_params
batch, _ = build_complex_batch([seq_params], kv_block_size=KV_BLOCK_SIZE, padding=False)
atom_builder = AtomBuilder()
atoms = torch.empty((8, 8), dtype=torch.int32, device=torch.device("cpu"))
atoms, n_atoms = atom_builder(atoms, batch, Q_BLOCK_SIZE, KV_BLOCK_SIZE)
calc_n_atoms = (seq_len + 127) // 128
assert n_atoms == calc_n_atoms
for i, atom in enumerate(atoms[:n_atoms]):
# Since the ptr was 0, first 2 elements should be 0
assert atom[0] == 0
assert atom[1] == 0
# Since we have a single sequence, the q_start_idx should always be
# whichever atom we're on multiplied by the block size
assert atom[2] == i * Q_BLOCK_SIZE
assert atom[3] == min(Q_BLOCK_SIZE, seq_len - i * Q_BLOCK_SIZE)
total_toks = i * Q_BLOCK_SIZE + min(Q_BLOCK_SIZE, seq_len - i * Q_BLOCK_SIZE)
assert atom[4] == (total_toks + n_seen_tokens + KV_BLOCK_SIZE - 1) // KV_BLOCK_SIZE
assert atom[5] == (total_toks + n_seen_tokens)
assert atom[6] == n_seen_tokens + i * Q_BLOCK_SIZE
@@ -0,0 +1,197 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import itertools
from typing import List, Tuple
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.inference_utils import DtypeEnum
from deepspeed.inference.v2.kernels.ragged_ops import (
AtomBuilder,
BlockedFlashAttn,
get_q_block_size,
get_kv_block_size,
LinearBlockedKVCopy,
)
from deepspeed.inference.v2.ragged import split_kv
from deepspeed.ops.op_builder import RaggedUtilsBuilder
from .ragged_testing_utils import build_batch_and_manager
from ....v2.inference_test_utils import allclose
try:
from flash_attn.flash_attn_interface import flash_attn_varlen_func
validate_accuracy = True
except ImportError:
validate_accuracy = False
"""
NOTE(cmikeh2): These tests depend on atom construction and KV-cache copying to behave correctly.
If one or the other of those is not working, then these tests will fail. Before debugging here,
make sure that the atom construction and KV-cache copying tests are passing.
"""
def _blocked_flash_testing_helper(head_size: int, n_heads_q: int, n_heads_kv: int,
seq_params: List[Tuple[int, int]]) -> None:
"""
Helper function for testing blocked flash attention. Used to enable parametrize to only set up
a subset of parameters before being passed to the unified test function.
"""
q_block_size = get_q_block_size(head_size)
kv_block_size = get_kv_block_size(head_size)
kvs = []
for _, history_len in seq_params:
if history_len > 0:
kvs.append(
torch.randn((history_len, 2 * n_heads_kv * head_size),
device=get_accelerator().current_device(),
dtype=torch.float16))
else:
kvs.append(None)
batch, state_manager, _ = build_batch_and_manager(seq_params, head_size, n_heads_kv, kv_block_size, kv_fill=kvs)
atom_builder = AtomBuilder()
kv_copy = LinearBlockedKVCopy(head_size, n_heads_q, n_heads_kv, DtypeEnum.fp16)
atom_flash = BlockedFlashAttn(head_size, DtypeEnum.fp16)
total_atoms = sum((seq[0] + q_block_size - 1) // q_block_size for seq in seq_params)
atoms = torch.empty((total_atoms, 8), dtype=torch.int32, device=get_accelerator().current_device())
alloc_func = RaggedUtilsBuilder().load().allocate_fast_host_buffer
atoms_host = alloc_func(atoms)
qkv = torch.randn((batch.current_tokens, (n_heads_q + 2 * n_heads_kv) * head_size),
device=get_accelerator().current_device(),
dtype=torch.float16)
atoms_host, n_atoms = atom_builder(atoms_host, batch, q_block_size, kv_block_size)
atoms.copy_(atoms_host[:n_atoms])
kv_cache = state_manager.get_cache(0)
kv_copy(kv_cache, qkv, batch)
out = torch.empty((batch.current_tokens, head_size * n_heads_q),
device=get_accelerator().current_device(),
dtype=torch.float16)
k_cache, v_cache = split_kv(kv_cache)
q = qkv[:, :head_size * n_heads_q]
atom_flash(out, q, k_cache, v_cache, atoms, 1.0)
if validate_accuracy:
cu_seqlens_q = torch.tensor([0] + list(itertools.accumulate([seq[0] for seq in seq_params])),
dtype=torch.int32,
device=get_accelerator().current_device())
cu_seqlens_kv = torch.tensor([0] + list(itertools.accumulate([seq[1] + seq[0] for seq in seq_params])),
dtype=torch.int32,
device=get_accelerator().current_device())
inflight_kv = qkv[:, head_size * n_heads_q:]
full_kvs = []
for i, kv in enumerate(kvs):
if kv is not None:
full_kvs.append(torch.cat([kv, inflight_kv[cu_seqlens_q[i]:cu_seqlens_q[i + 1]]], dim=0))
else:
full_kvs.append(inflight_kv[cu_seqlens_q[i]:cu_seqlens_q[i + 1]])
run_kvs = torch.cat(full_kvs, dim=0)
k = run_kvs[:, :head_size * n_heads_kv]
v = run_kvs[:, head_size * n_heads_kv:]
q_ref = q.reshape((batch.current_tokens, n_heads_q, head_size))
k_ref = k.reshape((k.shape[0], n_heads_kv, head_size))
v_ref = v.reshape((v.shape[0], n_heads_kv, head_size))
max_seqlen_q = max([seq[0] for seq in seq_params])
max_seqlen_kv = max([seq[1] + seq[0] for seq in seq_params])
ref_o = flash_attn_varlen_func(q_ref,
k_ref,
v_ref,
cu_seqlens_q,
cu_seqlens_kv,
max_seqlen_q,
max_seqlen_kv,
softmax_scale=1.0,
causal=True)
ref_o = ref_o.reshape(batch.current_tokens, head_size * n_heads_q)
assert allclose(out, ref_o)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("n_tokens", [2, 33, 65, 128, 256, 2037])
def test_single_prompt(n_tokens: int) -> None:
head_size = 64
n_heads_q = 16
n_heads_kv = 16
seq_params = [(n_tokens, 0)]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("prompt_lengths", [(128, 128), (192, 38), (514, 713), (83, 312, 610)])
def test_multiple_prompts(prompt_lengths: Tuple[int, int]) -> None:
"""
Test multiple prompts in a single batch.
"""
head_size = 64
n_heads_q = 16
n_heads_kv = 16
seq_params = [(prompt_lengths[i], 0) for i in range(len(prompt_lengths))]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("seq_params", [(1, 34), (43, 40), (1, 144), (64, 128), (332, 628)])
def test_continuation(seq_params: Tuple[int, int]) -> None:
"""
Test continued generation/prompt processing.
"""
head_size = 64
n_heads_q = 32
n_heads_kv = 32
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, [seq_params])
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("head_size", [64, 128])
def test_head_size(head_size: int) -> None:
n_heads_q = 16
n_heads_kv = 16
seq_params = [(128, 128), (192, 38), (1, 814)]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("head_config", [(32, 8), (64, 16), (40, 8)])
def test_gqa(head_config: Tuple[int, int]) -> None:
head_size = 128
n_heads_q = head_config[0]
n_heads_kv = head_config[1]
seq_params = [(128, 128), (192, 38), (1, 814)]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params)
@pytest.mark.inference_v2_ops
def test_fully_composed() -> None:
head_size = 64
n_heads_q = 16
n_heads_kv = 16
seq_params = [(332, 628), (1, 718), (1, 323), (180, 5), (224, 0)]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params)
@@ -0,0 +1,112 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.kernels.ragged_ops import LinearBlockedKVCopy
from .ragged_testing_utils import build_batch_and_manager, validate_kv_cache
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("n_tokens, history_size", [(1, 0), (17, 0), (33, 8), (63, 1)])
@pytest.mark.parametrize("head_size", [64, 80, 96, 128])
def test_single_sequence_single_block(n_tokens: int, history_size: int, head_size: int):
"""
Validate that the copy works correctly
"""
n_heads_q = 16
n_heads_kv = 16
kv_block_size = 64
device = get_accelerator().current_device()
batch, state_manager, seq_descs = build_batch_and_manager([(n_tokens, history_size)], head_size, n_heads_kv,
kv_block_size)
assert batch.current_sequences == 1
assert batch.current_tokens == n_tokens
qkv = torch.randn((batch.current_tokens, (n_heads_q + 2 * n_heads_kv) * head_size),
device=device,
dtype=torch.float16)
kv_cache = state_manager.get_cache(0)
copy_impl = LinearBlockedKVCopy(head_size, n_heads_q, n_heads_kv, torch.float16)
copy_impl(kv_cache, qkv, batch)
k = qkv[:, head_size * n_heads_q:head_size * (n_heads_q + n_heads_kv)]
v = qkv[:, head_size * (n_heads_q + n_heads_kv):]
validate_kv_cache(kv_cache, k, v, seq_descs, batch)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("n_tokens, history_size", [(128, 0), (177, 0), (169, 8), (117, 88)])
@pytest.mark.parametrize("head_size", [64, 80, 96, 128])
def test_single_sequence_multiple_blocks(n_tokens: int, history_size: int, head_size: int):
"""
Validate that the copy works correctly
"""
n_heads_q = 16
n_heads_kv = 16
kv_block_size = 64
device = get_accelerator().current_device()
batch, state_manager, seq_descs = build_batch_and_manager([(n_tokens, history_size)], head_size, n_heads_kv,
kv_block_size)
assert batch.current_sequences == 1
assert batch.current_tokens == n_tokens
qkv = torch.randn((batch.current_tokens, (n_heads_q + 2 * n_heads_kv) * head_size),
device=device,
dtype=torch.float16)
kv_cache = state_manager.get_cache(0)
copy_impl = LinearBlockedKVCopy(head_size, n_heads_q, n_heads_kv, torch.float16)
copy_impl(kv_cache, qkv, batch)
k = qkv[:, head_size * n_heads_q:head_size * (n_heads_q + n_heads_kv)]
v = qkv[:, head_size * (n_heads_q + n_heads_kv):]
validate_kv_cache(kv_cache, k, v, seq_descs, batch)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("head_size", [64, 80, 96, 128])
def test_multi_sequence(head_size: int) -> None:
n_heads_q = 16
n_heads_kv = 16
kv_block_size = 64
device = get_accelerator().current_device()
batch_config = [
(128, 0),
(177, 0),
(169, 8),
(117, 88),
(1, 293),
(1, 733),
(1, 33),
]
batch, state_manager, seq_descs = build_batch_and_manager(batch_config, head_size, n_heads_kv, kv_block_size)
qkv = torch.randn((batch.current_tokens, (n_heads_q + 2 * n_heads_kv) * head_size),
device=device,
dtype=torch.float16)
kv_cache = state_manager.get_cache(0)
copy_impl = LinearBlockedKVCopy(head_size, n_heads_q, n_heads_kv, torch.float16)
copy_impl(kv_cache, qkv, batch)
k = qkv[:, head_size * n_heads_q:head_size * (n_heads_q + n_heads_kv)]
v = qkv[:, head_size * (n_heads_q + n_heads_kv):]
validate_kv_cache(kv_cache, k, v, seq_descs, batch)
@@ -0,0 +1,258 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import List, Tuple
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.kernels.ragged_ops import BlockedRotaryEmbeddings, BlockedTrainedRotaryEmbeddings
from deepspeed.inference.v2.ragged import RaggedBatchWrapper, DSSequenceDescriptor
from .ragged_testing_utils import build_batch_and_manager, validate_kv_cache
from ....v2.inference_test_utils import allclose
"""
NOTE(cmikeh2): It is very possible to see unit test failures (even on FP16) depending on when
certain values are casted up to or down from float32. If we are seeing accuracy issues, we should
make sure we are aligning on the training implementation's cast pattern here, given these tolerances
tend to be sufficient elsewhere.
"""
def rotary_pos_embs(q: torch.Tensor,
k: torch.Tensor,
seq_descs: List[DSSequenceDescriptor],
batch: RaggedBatchWrapper,
head_size: int,
rotary_dim: int = -1) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
rotary_dim = rotary_dim if rotary_dim >= 0 else head_size
def make_cos_sin_emb(seq_len: int) -> Tuple[torch.Tensor, torch.Tensor]:
t = torch.arange(seq_len, dtype=torch.float32, device=get_accelerator().current_device())
inv_freq = (1.0 / (10000.0**(torch.arange(
0, rotary_dim, 2, dtype=torch.float32, device=get_accelerator().current_device()) / rotary_dim))).half()
freqs = torch.einsum("i,j->ij", t, inv_freq)
emb = torch.cat((freqs, freqs), dim=-1)
return torch.cos(emb)[:, None, :], torch.sin(emb)[:, None, :], inv_freq
def rotate_half(x: torch.Tensor) -> torch.Tensor:
return torch.cat((-x[..., x.shape[-1] // 2:], x[..., :x.shape[-1] // 2]), dim=-1)
cos, sin, freqs = make_cos_sin_emb(1024)
q_out = torch.empty_like(q)
k_out = torch.empty_like(k)
n_heads_q = q.shape[1] // head_size
n_heads_kv = k.shape[1] // head_size
inflight_descs = batch.inflight_seq_descriptors(on_device=False)[:batch.current_sequences]
if inflight_descs.shape[0] != len(seq_descs):
raise ValueError("The number of sequence descriptors does not match the number of sequences in the batch.")
for seq_desc, inflight_seq in zip(seq_descs, inflight_descs):
start_idx = inflight_seq[0]
n_tokens = seq_desc.in_flight_tokens
q_src = q[start_idx:start_idx + n_tokens].reshape(n_tokens, n_heads_q, head_size).float()
k_src = k[start_idx:start_idx + n_tokens].reshape(n_tokens, n_heads_kv, head_size).float()
freq_start_offset = seq_desc.seen_tokens
q_src_rot = q_src[:, :, :rotary_dim]
k_src_rot = k_src[:, :, :rotary_dim]
cos_chunk = cos[range(freq_start_offset, freq_start_offset + n_tokens)]
sin_chunk = sin[range(freq_start_offset, freq_start_offset + n_tokens)]
q_rot = q_src_rot * cos_chunk + rotate_half(q_src_rot) * sin_chunk
k_rot = k_src_rot * cos_chunk + rotate_half(k_src_rot) * sin_chunk
q_emb = torch.cat((q_rot, q_src[:, :, rotary_dim:]), dim=-1)
k_emb = torch.cat((k_rot, k_src[:, :, rotary_dim:]), dim=-1)
q_out[start_idx:start_idx + n_tokens] = q_emb.reshape(n_tokens, n_heads_q * head_size).to(q_out.dtype)
k_out[start_idx:start_idx + n_tokens] = k_emb.reshape(n_tokens, n_heads_kv * head_size).to(k_out.dtype)
return q_out, k_out, freqs
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("n_tokens, history_size", [(1, 0), (17, 0), (33, 15), (1, 63)])
@pytest.mark.parametrize("trained_emb", [False, True])
@pytest.mark.parametrize("head_size", [64, 80, 96])
def test_single_sequence_single_block(n_tokens: int, history_size: int, trained_emb: bool, head_size: int):
"""
Validate that the copy works correctly
"""
n_heads_q = 16
n_heads_kv = 16
kv_block_size = 64
device = get_accelerator().current_device()
batch, state_manager, seq_descs = build_batch_and_manager([(n_tokens, history_size)], head_size, n_heads_kv,
kv_block_size)
assert batch.current_sequences == 1
assert batch.current_tokens == n_tokens
qkv = torch.randn((batch.current_tokens, (n_heads_q + 2 * n_heads_kv) * head_size),
device=device,
dtype=torch.float16)
qkv_ref = qkv.clone()
q = qkv_ref[:, :head_size * n_heads_q]
k = qkv_ref[:, head_size * n_heads_q:head_size * (n_heads_q + n_heads_kv)]
v = qkv_ref[:, head_size * (n_heads_q + n_heads_kv):]
q_ref, k, freqs = rotary_pos_embs(q, k, seq_descs, batch, head_size)
freqs = freqs.half()
kv_cache = state_manager.get_cache(0)
if trained_emb:
copy_impl = BlockedTrainedRotaryEmbeddings(head_size, n_heads_q, n_heads_kv, torch.float16)
copy_impl(kv_cache, qkv, batch, freqs)
else:
copy_impl = BlockedRotaryEmbeddings(head_size, n_heads_q, n_heads_kv, torch.float16, head_size, 10000.0)
copy_impl(kv_cache, qkv, batch)
assert allclose(qkv[:, :head_size * n_heads_q], q_ref)
validate_kv_cache(kv_cache, k, v, seq_descs, batch, exact=False)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("n_tokens, history_size", [(128, 0), (177, 0), (169, 8), (117, 88)])
@pytest.mark.parametrize("trained_emb", [False, True])
@pytest.mark.parametrize("head_size", [64, 80, 96])
def test_single_sequence_multiple_blocks(n_tokens: int, history_size: int, trained_emb: bool, head_size: int):
"""
Validate that the copy works correctly
"""
n_heads_q = 16
n_heads_kv = 16
kv_block_size = 64
device = get_accelerator().current_device()
batch, state_manager, seq_descs = build_batch_and_manager([(n_tokens, history_size)], head_size, n_heads_kv,
kv_block_size)
assert batch.current_sequences == 1
assert batch.current_tokens == n_tokens
qkv = torch.randn((batch.current_tokens, (n_heads_q + 2 * n_heads_kv) * head_size),
device=device,
dtype=torch.float16)
qkv_ref = qkv.clone()
q = qkv_ref[:, :head_size * n_heads_q]
k = qkv_ref[:, head_size * n_heads_q:head_size * (n_heads_q + n_heads_kv)]
v = qkv_ref[:, head_size * (n_heads_q + n_heads_kv):]
q_ref, k, freqs = rotary_pos_embs(q, k, seq_descs, batch, head_size)
freqs = freqs.half()
kv_cache = state_manager.get_cache(0)
if trained_emb:
copy_impl = BlockedTrainedRotaryEmbeddings(head_size, n_heads_q, n_heads_kv, torch.float16)
copy_impl(kv_cache, qkv, batch, freqs)
else:
copy_impl = BlockedRotaryEmbeddings(head_size, n_heads_q, n_heads_kv, torch.float16, head_size, 10000.0)
copy_impl(kv_cache, qkv, batch)
assert allclose(qkv[:, :head_size * n_heads_q], q_ref)
validate_kv_cache(kv_cache, k, v, seq_descs, batch, exact=False)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("trained_emb", [False, True])
@pytest.mark.parametrize("head_size", [64, 80, 96])
def test_multi_sequences(trained_emb: bool, head_size: int) -> None:
n_heads_q = 16
n_heads_kv = 16
kv_block_size = 64
device = get_accelerator().current_device()
batch_config = [
(128, 0),
(177, 0),
(169, 8),
(117, 88),
(1, 293),
(1, 733),
(1, 33),
]
batch, state_manager, seq_descs = build_batch_and_manager(batch_config, head_size, n_heads_kv, kv_block_size)
qkv = torch.randn((batch.current_tokens, (n_heads_q + 2 * n_heads_kv) * head_size),
device=device,
dtype=torch.float16)
qkv_ref = qkv.clone()
q = qkv_ref[:, :head_size * n_heads_q]
k = qkv_ref[:, head_size * n_heads_q:head_size * (n_heads_q + n_heads_kv)]
v = qkv_ref[:, head_size * (n_heads_q + n_heads_kv):]
q_ref, k, freqs = rotary_pos_embs(q, k, seq_descs, batch, head_size)
freqs = freqs.half()
kv_cache = state_manager.get_cache(0)
if trained_emb:
copy_impl = BlockedTrainedRotaryEmbeddings(head_size, n_heads_q, n_heads_kv, torch.float16)
copy_impl(kv_cache, qkv, batch, freqs)
else:
copy_impl = BlockedRotaryEmbeddings(head_size, n_heads_q, n_heads_kv, torch.float16, head_size, 10000.0)
copy_impl(kv_cache, qkv, batch)
assert allclose(qkv[:, :head_size * n_heads_q], q_ref)
validate_kv_cache(kv_cache, k, v, seq_descs, batch, exact=False)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("head_size", [80, 96])
def test_rotary_dim(head_size: int) -> None:
trained_emb = False
rotary_dim = 64
n_heads_q = 16
n_heads_kv = 16
kv_block_size = 64
device = get_accelerator().current_device()
batch_config = [
(128, 0),
(177, 0),
(169, 8),
(117, 88),
(1, 293),
(1, 733),
(1, 33),
]
batch, state_manager, seq_descs = build_batch_and_manager(batch_config, head_size, n_heads_kv, kv_block_size)
qkv = torch.randn((batch.current_tokens, (n_heads_q + 2 * n_heads_kv) * head_size),
device=device,
dtype=torch.float16)
qkv_ref = qkv.clone()
q = qkv_ref[:, :head_size * n_heads_q]
k = qkv_ref[:, head_size * n_heads_q:head_size * (n_heads_q + n_heads_kv)]
v = qkv_ref[:, head_size * (n_heads_q + n_heads_kv):]
q_ref, k, freqs = rotary_pos_embs(q, k, seq_descs, batch, head_size, rotary_dim=rotary_dim)
freqs = freqs.half()
kv_cache = state_manager.get_cache(0)
copy_impl = BlockedRotaryEmbeddings(head_size, n_heads_q, n_heads_kv, torch.float16, rotary_dim, 10000.0)
copy_impl(kv_cache, qkv, batch)
assert allclose(qkv[:, :head_size * n_heads_q], q_ref)
validate_kv_cache(kv_cache, k, v, seq_descs, batch, exact=False)
@@ -0,0 +1,96 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import List
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.kernels.ragged_ops import RaggedLogitsGather
from ....v2.inference_test_utils import allclose, get_dtypes
from .ragged_testing_utils import build_simple_batch
def baseline_implementation(hidden_states: torch.Tensor, seq_lens: List[int]) -> torch.Tensor:
output = torch.empty((len(seq_lens), hidden_states.shape[1]),
dtype=hidden_states.dtype,
device=hidden_states.device)
offset = 0
for i, seq_len in enumerate(seq_lens):
output[i] = hidden_states[offset + seq_len - 1]
offset += seq_len
return output
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize('dtype', get_dtypes())
def test_supported_dtypes(dtype: torch.dtype) -> None:
"""
Validate support on nominally supported data types.
"""
model_dim = 4096
batch = build_simple_batch([256], padding=False)
hidden_states = torch.randn((batch.current_tokens, model_dim),
dtype=dtype,
device=get_accelerator().current_device())
reference_result = baseline_implementation(hidden_states, [256])
kernel = RaggedLogitsGather(model_dim, dtype)
output = torch.empty_like(reference_result)
kernel(output, hidden_states, batch)
assert allclose(output, reference_result)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize('seq_lens', [[128, 64, 192, 32], [57, 112, 63, 89, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1],
[63, 27, 74, 83, 32, 17, 1, 1, 1, 1, 1]])
def test_multiple_sequences(seq_lens: List[int]) -> None:
"""
Validate support on more multi-sequence inputs.
"""
model_dim = 4096
dtype = torch.float16
batch = build_simple_batch(seq_lens, padding=False)
hidden_states = torch.randn((batch.current_tokens, model_dim),
dtype=dtype,
device=get_accelerator().current_device())
reference_result = baseline_implementation(hidden_states, seq_lens)
kernel = RaggedLogitsGather(model_dim, dtype)
output = torch.empty_like(reference_result)
kernel(output, hidden_states, batch)
assert allclose(output, reference_result)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("model_dim", [1024, 6144, 6784])
def test_problem_size_permutations(model_dim: int) -> None:
"""
Validate for different embedding sizes.
"""
dtype = torch.float16
seq_lens = [128, 64, 192, 32]
batch = build_simple_batch(seq_lens, padding=False)
hidden_states = torch.randn((batch.current_tokens, model_dim),
dtype=dtype,
device=get_accelerator().current_device())
reference_result = baseline_implementation(hidden_states, seq_lens)
kernel = RaggedLogitsGather(model_dim, dtype)
output = torch.empty_like(reference_result)
kernel(output, hidden_states, batch)
assert allclose(output, reference_result)
@@ -0,0 +1,122 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.inference_utils import DtypeEnum
from deepspeed.inference.v2.kernels.ragged_ops import (
MoEGather,
MoEScatter,
RaggedTopKGating,
)
from .ragged_testing_utils import build_simple_batch
"""
For simplicity's sake, these tests do rely on ``RaggedTopKGating`` and
``MoEScatter`` to produce correct inputs. If either of these kernels is broken
these tests will fail, so double check the unit test results there before
debugging here.
"""
TEST_CASES = [
# (n_tokens, n_experts, n_top_k)
(13, 64, 1),
(278, 64, 1),
(1977, 64, 1),
(13, 8, 2),
(278, 8, 2),
(1977, 8, 2),
]
def build_inputs(n_tokens: int, n_experts: int, n_top_k: int, do_padding: bool):
assert n_tokens <= 2048, "This test will break if n_tokens > 2048"
# Sequence composition shouldn't matter here
batch = build_simple_batch([n_tokens], padding=do_padding)
logits = torch.randn((batch.tensor_toks, n_experts),
dtype=torch.float16,
device=get_accelerator().current_device())
# This will make each token's value equal to its index. NOTE: This will break for
# tokens with index > 2048.
hidden_states = torch.arange(batch.tensor_toks, dtype=torch.float16,
device=get_accelerator().current_device()).repeat_interleave(4096, dim=0).reshape(
batch.tensor_toks, 4096).contiguous()
gate = RaggedTopKGating(DtypeEnum.fp16)
# Gating outputs
expert_counts = torch.zeros((n_experts, ), dtype=torch.int32, device=get_accelerator().current_device())
scores = torch.empty((batch.tensor_toks, n_top_k), dtype=torch.float32, device=get_accelerator().current_device())
expert_assignment = torch.empty((batch.tensor_toks, n_top_k),
dtype=torch.int32,
device=get_accelerator().current_device())
expert_offset = torch.empty((batch.tensor_toks, n_top_k),
dtype=torch.int32,
device=get_accelerator().current_device())
gate(expert_counts, scores, expert_assignment, expert_offset, logits, batch)
# Scatter outputs
moe_input = torch.empty((batch.tensor_toks * n_top_k, 4096),
dtype=torch.float16,
device=get_accelerator().current_device())
expert_cumsum = torch.empty((n_experts, ), dtype=torch.int64, device=get_accelerator().current_device())
mapped_slots = torch.empty((batch.tensor_toks, n_top_k),
dtype=torch.int32,
device=get_accelerator().current_device())
scatter = MoEScatter(DtypeEnum.fp16, 4096)
scatter(moe_input, expert_cumsum, mapped_slots, hidden_states, expert_counts, expert_assignment, expert_offset)
return batch, moe_input, scores, mapped_slots, expert_counts
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("n_tokens, n_experts, n_top_k", TEST_CASES)
@pytest.mark.parametrize("do_padding", [False])
def test_moe_gather(n_tokens: int, n_experts: int, n_top_k: int, do_padding: bool):
get_accelerator().manual_seed(0xC0FFEE)
batch, moe_input, scores, mapped_slots, expert_counts = build_inputs(n_tokens, n_experts, n_top_k, do_padding)
output = torch.randn((batch.tensor_toks, 4096), dtype=torch.float16, device=get_accelerator().current_device())
gather = MoEGather(DtypeEnum.fp16, 4096)
gather(output, moe_input, scores, mapped_slots, expert_counts)
for token_idx in range(n_tokens):
effective_score = scores[token_idx].sum().item()
assert torch.equal(
output[token_idx],
torch.full((4096, ),
token_idx * effective_score,
dtype=torch.float16,
device=get_accelerator().current_device()))
@pytest.mark.inference_v2_ops
def test_moe_gather_normalize_scales():
get_accelerator().manual_seed(0xC0FFEE)
n_tokens = 72
n_experts = 8
n_top_k = 2
do_padding = False
batch, moe_input, scores, mapped_slots, expert_counts = build_inputs(n_tokens, n_experts, n_top_k, do_padding)
output = torch.randn((batch.tensor_toks, 4096), dtype=torch.float16, device=get_accelerator().current_device())
gather = MoEGather(DtypeEnum.fp16, 4096, normalize_scores=True)
gather(output, moe_input, scores, mapped_slots, expert_counts)
for token_idx in range(n_tokens):
assert torch.equal(
output[token_idx],
torch.full((4096, ), token_idx, dtype=torch.float16, device=get_accelerator().current_device()))
@@ -0,0 +1,95 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.inference_utils import DtypeEnum
from deepspeed.inference.v2.kernels.ragged_ops import MoEScatter, RaggedTopKGating
from .ragged_testing_utils import build_simple_batch
"""
For simplicity's sake, these tests do rely on ``RaggedTopKGating`` to produce correct
inputs. If ``RaggedTopKGating`` is broken, these tests will fail, so double check
the unit test results there before debugging here.
"""
TEST_CONFIGS = [
(13, 64, 1),
(278, 64, 1),
(1977, 64, 1),
(13, 8, 2),
(278, 8, 2),
(1977, 8, 2),
]
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("n_tokens, n_experts, n_top_k", TEST_CONFIGS)
@pytest.mark.parametrize("do_padding", [False, True])
def test_moe_scatter(n_tokens, n_experts, n_top_k, do_padding):
# Sequence composition shouldn't matter here
batch = build_simple_batch([n_tokens], padding=do_padding)
logits = torch.randn((batch.tensor_toks, n_experts),
dtype=torch.float16,
device=get_accelerator().current_device())
# This will make each token's value equal to its index. NOTE: This will break for
# tokens with index > 2048.
hidden_states = torch.arange(batch.tensor_toks, dtype=torch.float16,
device=get_accelerator().current_device()).repeat_interleave(4096, dim=0).reshape(
batch.tensor_toks, 4096).contiguous()
gate = RaggedTopKGating(DtypeEnum.fp16)
# Gating outputs
expert_counts = torch.zeros((n_experts, ), dtype=torch.int32, device=get_accelerator().current_device())
scores = torch.empty((batch.tensor_toks, n_top_k), dtype=torch.float32, device=get_accelerator().current_device())
expert_assignment = torch.empty((batch.tensor_toks, n_top_k),
dtype=torch.int32,
device=get_accelerator().current_device())
expert_offset = torch.empty((batch.tensor_toks, n_top_k),
dtype=torch.int32,
device=get_accelerator().current_device())
gate(expert_counts, scores, expert_assignment, expert_offset, logits, batch)
# Scatter outputs
moe_input = torch.empty((batch.tensor_toks * n_top_k, 4096),
dtype=torch.float16,
device=get_accelerator().current_device())
expert_cumsum = torch.empty((n_experts, ), dtype=torch.int64, device=get_accelerator().current_device())
mapped_slots = torch.empty((batch.tensor_toks, n_top_k),
dtype=torch.int32,
device=get_accelerator().current_device())
scatter = MoEScatter(DtypeEnum.fp16, 4096)
scatter(moe_input, expert_cumsum, mapped_slots, hidden_states, expert_counts, expert_assignment, expert_offset)
get_accelerator().synchronize()
assert torch.equal(expert_cumsum, torch.cumsum(expert_counts, dim=0).to(torch.int64))
if not do_padding:
assert torch.unique(mapped_slots).size(0) == n_top_k * n_tokens
for token_idx in range(batch.tensor_toks):
if token_idx < n_tokens:
for k in range(n_top_k):
expert_idx = expert_assignment[token_idx][k].item()
if expert_idx == 0:
expert_cumsum_val = 0
else:
expert_cumsum_val = expert_cumsum[expert_idx - 1]
offset = expert_offset[token_idx][k]
total_offset = offset + expert_cumsum_val
assert total_offset == mapped_slots[token_idx][k].item()
assert torch.equal(moe_input[total_offset], hidden_states[token_idx])
else:
for k in range(n_top_k):
assert mapped_slots[token_idx][k].item() == -1
assert expert_cumsum[-1] == n_tokens * n_top_k
@@ -0,0 +1,177 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import List, Optional, Tuple
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.kernels.ragged_ops import RaggedEmbeddingKernel
from ....v2.inference_test_utils import allclose, get_dtypes
from .ragged_testing_utils import build_batch_and_manager
def baseline_implementation(token_ids: torch.Tensor,
embedding_table: torch.Tensor,
unpadded_size: int,
positional_embedding_table: Optional[torch.Tensor] = None,
positional_ids: Optional[torch.Tensor] = None) -> torch.Tensor:
"""
Baseline implementation for our ragged embedding kernel.
"""
if unpadded_size == token_ids.shape[0]:
token_embed = torch.nn.functional.embedding(token_ids, embedding_table)
if positional_embedding_table is not None:
pos_embed = torch.nn.functional.embedding(positional_ids, positional_embedding_table)
token_embed += pos_embed
return token_embed
else:
real_token_ids = token_ids[:unpadded_size]
output = torch.empty((token_ids.shape[0], embedding_table.shape[1]),
dtype=embedding_table.dtype,
device=get_accelerator().current_device())
unpadded_output = torch.nn.functional.embedding(real_token_ids, embedding_table)
# Positional embeddings aren't padded because it's simulated
if positional_embedding_table is not None:
pos_embed = torch.nn.functional.embedding(positional_ids, positional_embedding_table)
unpadded_output += pos_embed
output[:unpadded_size] = unpadded_output
return output
def _ragged_embed_test_helper(sequence_config: List[Tuple[int, int]],
embed_dtype: torch.dtype,
token_dtype: torch.dtype,
embed_dim: int,
vocab_size: int,
do_padding: bool = False,
pos_embed_size: int = -1,
pos_embed_offset: int = 0) -> None:
"""
Helper for embedding test to limit the number of tests to run.
Params:
embed_dim (int): Model dimension
vocab_size (int): Leading dimension on embedding weight
pos_embed_size (int): Size of positional embedding. If negative, no positional embedding
is used.
pos_embed_offset (int): Offset for positional embedding. Effectively, the raw offsets
of a token into a sequence are offset by this amount into the embedding matrix. (
i.e. the shape of the positional embeddings is (pos_embed_size + pos_embed_offset
embed_dim)
"""
device = get_accelerator().current_device()
# Heads/Block size are irrelevant here but need something.
batch, _, _, = build_batch_and_manager(sequence_config, 64, 16, 64, vocab_range=vocab_size, padding=do_padding)
embedding_table = torch.randn((vocab_size, embed_dim), dtype=embed_dtype, device=device)
if pos_embed_size > 0:
pos_embedding_table = torch.randn((pos_embed_size + pos_embed_offset, embed_dim),
dtype=embed_dtype,
device=device)
positional_ids = torch.cat([
torch.arange(start_idx, start_idx + seq_len, dtype=token_dtype, device=device)
for seq_len, start_idx in sequence_config
]) + pos_embed_offset
else:
pos_embedding_table = None
positional_ids = None
baseline_output = baseline_implementation(batch.input_ids().to(token_dtype), embedding_table, batch.current_tokens,
pos_embedding_table, positional_ids)
kernel = RaggedEmbeddingKernel(embed_dtype, token_dtype, embed_dim)
output = torch.empty_like(baseline_output)
kernel(output,
batch,
embedding_table,
position_embed_weight=pos_embedding_table,
position_embed_offset=pos_embed_offset)
if do_padding:
assert output.shape[0] != batch.current_tokens
assert allclose(output[:batch.current_tokens], baseline_output[:batch.current_tokens])
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize('token_dtype', [torch.int32, torch.int64])
@pytest.mark.parametrize('embed_dtype', get_dtypes())
def test_dtype_permutations(token_dtype: torch.dtype, embed_dtype: torch.dtype) -> None:
"""
Validate (on a single problem size) that the kernel support for different data types
is correct.
"""
embed_dim = 4096
vocab_size = 50304
_ragged_embed_test_helper([(256, 0)], embed_dtype, token_dtype, embed_dim, vocab_size)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize('vocab_size, embed_dim', [(1024, 1024), (32000, 5120), (50304, 6144)])
def test_problem_size_permutations(vocab_size: int, embed_dim: int) -> None:
"""
Validate on wider range of problem sizes.
"""
_ragged_embed_test_helper([(256, 0)], torch.float16, torch.int32, embed_dim, vocab_size)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize('seq_lens', [[128, 64, 192, 32], [57, 112, 63, 89, 1, 1, 1, 1]])
@pytest.mark.parametrize('do_padding', [True, False])
def test_complex_sequences(seq_lens: List[int], do_padding: bool) -> None:
"""
Validate on different ragged batch construction scenarios.
"""
embed_dim = 4096
vocab_size = 50304
_ragged_embed_test_helper([(seq_len, 0) for seq_len in seq_lens],
torch.float16,
torch.int32,
embed_dim,
vocab_size,
do_padding=do_padding)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("seq_lens", [[(256, 0)], [(256, 0),
(128, 0)], [(256, 0), (128, 0),
(64, 0)], [(1, 877), (619, 0), (213, 372), (1, 45)]])
def test_positional_embedding(seq_lens: List[Tuple[int, int]]) -> None:
"""
Validate that positional embedding works correctly.
"""
embed_dim = 4096
vocab_size = 50304
_ragged_embed_test_helper(seq_lens, torch.float16, torch.int32, embed_dim, vocab_size, pos_embed_size=2048)
@pytest.mark.inference_v2_ops
def test_positional_embedding_offset() -> None:
"""
Validate that positional embedding works correctly with an offset.
"""
embed_dim = 4096
vocab_size = 50304
seq_config = [(1, 877), (619, 0), (213, 372), (1, 45)]
_ragged_embed_test_helper(seq_config,
torch.float16,
torch.int32,
embed_dim,
vocab_size,
pos_embed_size=2048,
pos_embed_offset=2)
@@ -0,0 +1,171 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
import torch.nn.functional as F
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.inference_utils import DtypeEnum
from deepspeed.inference.v2.kernels.ragged_ops import RaggedTopKGating
from .ragged_testing_utils import build_simple_batch
from ...inference_test_utils import allclose
def _top_k_gating_testing_helper(n_tokens: int, n_experts: int, n_top_k: int, seed: int = 0xC0FFEE) -> None:
torch.manual_seed(seed)
logits = torch.randn((n_tokens, n_experts), dtype=torch.float16, device=get_accelerator().current_device())
batch = build_simple_batch([n_tokens], padding=False)
gate = RaggedTopKGating(DtypeEnum.fp16)
expert_counts = torch.zeros((n_experts, ), dtype=torch.int32, device=get_accelerator().current_device())
scores = torch.empty((n_tokens, n_top_k), dtype=torch.float32, device=get_accelerator().current_device())
expert_assignment = torch.empty((n_tokens, n_top_k), dtype=torch.int32, device=get_accelerator().current_device())
expert_offset = torch.empty((n_tokens, n_top_k), dtype=torch.int32, device=get_accelerator().current_device())
gate(expert_counts, scores, expert_assignment, expert_offset, logits, batch)
ref_weights = F.softmax(logits, dim=-1, dtype=torch.float32)
ref_scores, ref_indices = torch.topk(ref_weights, n_top_k, dim=-1)
assert allclose(scores, ref_scores), f"expected {ref_scores}, got {scores}"
assert torch.equal(expert_assignment,
ref_indices.to(torch.int32)), f"expected {ref_indices}, got {expert_assignment}"
assert expert_counts.sum(
) == n_tokens * n_top_k, f"expected {n_tokens * n_top_k} tokens, got {expert_counts.sum()}"
# Ensure that the expert offsets are unique
for i in range(n_experts):
expert_idxs = torch.where(expert_assignment == i, expert_offset, 0)
if expert_counts[i] > 0:
assert expert_idxs.unique().shape[0] == expert_counts[
i], f"expected {expert_counts[i]} unique offsets, got {expert_idxs.unique().shape[0]}"
assert expert_idxs.max(
) == expert_counts[i] - 1, f"expected max offset {expert_counts[i] - 1}, got {expert_idxs.max()}"
else:
# Should have all 0's so one unique value
assert expert_idxs.unique().shape[0] == 1
assert expert_idxs.max() == 0
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize('n_tokens', [1, 17, 32, 89, 433])
def test_top_2_e_8_gating(n_tokens: int) -> None:
_top_k_gating_testing_helper(n_tokens=n_tokens, n_experts=8, n_top_k=2)
def _test_single_mapping_helper(n_tokens: int,
n_experts: int,
assigned_expert: int,
logit_fill: float = 0.0,
match_fill: float = 1.0) -> None:
n_top_k = 1
logits = torch.full((n_tokens, n_experts),
logit_fill,
dtype=torch.float16,
device=get_accelerator().current_device())
logits[:, assigned_expert] = match_fill
gate = RaggedTopKGating(DtypeEnum.fp16)
expert_counts = torch.zeros((n_experts, ), dtype=torch.int32, device=get_accelerator().current_device())
scores = torch.empty((n_tokens, n_top_k), dtype=torch.float32, device=get_accelerator().current_device())
expert_assignment = torch.empty((n_tokens, n_top_k), dtype=torch.int32, device=get_accelerator().current_device())
expert_offset = torch.empty((n_tokens, n_top_k), dtype=torch.int32, device=get_accelerator().current_device())
batch = build_simple_batch([n_tokens], padding=False)
gate(expert_counts, scores, expert_assignment, expert_offset, logits, batch)
assert expert_counts[assigned_expert] == n_tokens
assert torch.all(expert_assignment == assigned_expert)
assert torch.unique(expert_offset).shape[0] == n_tokens
assert allclose(scores, F.softmax(logits.float(), dim=1)[:, assigned_expert].reshape(-1, n_top_k))
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize('n_tokens, n_experts', [(1, 16), (17, 16), (32, 128), (89, 128), (433, 128)])
def test_single_mapping_gating(n_tokens: int, n_experts: int) -> None:
"""
Evaluate our expert stacking behavior in complete isolation. This ensures all tokens
mapped to the same expert are getting unique offsets and identical scores.
"""
assigned_expert = 13
_test_single_mapping_helper(n_tokens, n_experts, assigned_expert)
@pytest.mark.inference_v2_ops
def test_negative_logits():
"""
Ensure that scores/values are propagated correctly when all the logits are negative. An
earlier implementation of the scoring would return NaN for this case.
"""
_test_single_mapping_helper(128, 32, 13, logit_fill=-2.0, match_fill=-1.0)
@pytest.mark.inference_v2_ops
def test_determinism():
"""
Ensure that ties between two logits are broken deterministically. This is essential when
the gating is distributed across multiple devices that need to map the same token to
the same expert.
"""
n_tokens = 512
n_experts = 64
n_top_k = 1
logits = torch.zeros((n_tokens, n_experts), dtype=torch.float16, device=get_accelerator().current_device())
batch = build_simple_batch([n_tokens], padding=False)
logits[:, 19] = 1.0
logits[:, 26] = 1.0
gate = RaggedTopKGating(DtypeEnum.fp16)
for _ in range(1024):
expert_counts = torch.zeros((n_experts, ), dtype=torch.int32, device=get_accelerator().current_device())
scores = torch.empty((n_tokens, n_top_k), dtype=torch.float32, device=get_accelerator().current_device())
expert_assignment = torch.empty((n_tokens, n_top_k),
dtype=torch.int32,
device=get_accelerator().current_device())
expert_offset = torch.empty((n_tokens, n_top_k), dtype=torch.int32, device=get_accelerator().current_device())
batch = build_simple_batch([n_tokens], padding=False)
gate(expert_counts, scores, expert_assignment, expert_offset, logits, batch)
assert expert_counts[19] == n_tokens
assert expert_counts[26] == 0
assert torch.all(expert_assignment == 19)
assert torch.unique(expert_offset).shape[0] == n_tokens
assert allclose(scores, F.softmax(logits.float(), dim=1)[:, 19].reshape(-1, 1))
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize('n_tokens, n_experts', [(1, 16), (17, 16), (32, 128), (89, 128), (433, 2)])
def test_score_accuracy(n_tokens: int, n_experts: int) -> None:
"""
Validate expert scores are correct.
"""
logits = torch.randn((n_tokens, n_experts), dtype=torch.float16, device=get_accelerator().current_device())
batch = build_simple_batch([n_tokens], padding=False)
n_top_k = 1
gate = RaggedTopKGating(DtypeEnum.fp16)
expert_counts = torch.zeros((n_experts, ), dtype=torch.int32, device=get_accelerator().current_device())
scores = torch.empty((n_tokens, n_top_k), dtype=torch.float32, device=get_accelerator().current_device())
expert_assignment = torch.empty((n_tokens, n_top_k), dtype=torch.int32, device=get_accelerator().current_device())
expert_offset = torch.empty((n_tokens, n_top_k), dtype=torch.int32, device=get_accelerator().current_device())
ref_scores = F.softmax(logits.float(), dim=1).max(dim=1).values
ref_scores = ref_scores.reshape(-1, 1)
gate(expert_counts, scores, expert_assignment, expert_offset, logits, batch)
assert allclose(scores, ref_scores)
assert expert_counts.sum() == n_tokens
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,120 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import List
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.model_implementations.flat_model_helpers import (
flatten_inference_model,
restore_inference_model,
)
from deepspeed.inference.v2.model_implementations.layer_container_base import LayerContainer
from .utils import SimpleParam, DummyInferenceModel
class TransformerLayerContainer(LayerContainer):
"""
Stub layer container
"""
PARAM_MAPPING = {
"param_1": "param_1.param",
"param_2": "param_2.param",
}
param_1: SimpleParam
param_2: SimpleParam
class NonTransformerContainer(LayerContainer):
"""
Stub layer container
"""
PARAM_MAPPING = {
"param_1": "param_1.param",
"param_2": "param_2.param",
"param_3": "param_3.param",
}
param_1: SimpleParam
param_2: SimpleParam
param_3: SimpleParam
@pytest.mark.inference_v2
def test_contiguify_roundtrip():
"""
Validate that contiguify round trips and reconstructions are correct.
"""
model = DummyInferenceModel()
n_layers = 2
transformer_params = []
transformer_containers = []
# Create parameters and populate them into the containers
for i in range(n_layers):
transformer_containers.append(TransformerLayerContainer(model))
layer_params = []
for j in range(2):
layer_params.append(torch.rand(16, 16))
transformer_containers[i].set_dependency(f"param_{j+1}", layer_params[j])
layer_params = [p.to(get_accelerator().current_device()) for p in layer_params]
transformer_params.append(layer_params)
assert transformer_containers[i].is_populated == True
non_transformer_params = []
non_transformer_container = NonTransformerContainer(model)
for i in range(3):
non_transformer_params.append(torch.rand(16, 16).permute(1, 0))
non_transformer_container.set_dependency(f"param_{i+1}", non_transformer_params[i])
non_transformer_params = [p.to(get_accelerator().current_device()) for p in non_transformer_params]
def validate_containers(t_containers: List[LayerContainer], n_t_containers: LayerContainer,
t_params: List[List[torch.Tensor]], n_t_params: List[torch.Tensor]):
"""
Validate params match what is on the containers.
"""
for i in range(n_layers):
l_c = t_containers[i]
assert l_c.is_initialized == True
assert torch.equal(l_c.param_1, t_params[i][0])
assert torch.equal(l_c.param_2, t_params[i][1])
assert n_t_containers.is_initialized == True
assert torch.equal(n_t_containers.param_1, n_t_params[0])
assert torch.equal(n_t_containers.param_2, n_t_params[1])
assert torch.equal(n_t_containers.param_3, n_t_params[2])
assert not n_t_containers.param_1.is_contiguous()
assert not n_t_containers.param_2.is_contiguous()
assert not n_t_containers.param_3.is_contiguous()
buffer, metadata = flatten_inference_model(transformer_containers, non_transformer_container, "NoOpPolicy")
# Validate containers before contiguify
validate_containers(transformer_containers, non_transformer_container, transformer_params, non_transformer_params)
# Validate restore pass
transformer_containers_r = []
for i in range(n_layers):
transformer_containers_r.append(TransformerLayerContainer(model))
non_transformer_container_r = NonTransformerContainer(model)
restore_inference_model(buffer, metadata, transformer_containers_r, non_transformer_container_r)
validate_containers(transformer_containers_r, non_transformer_container_r, transformer_params,
non_transformer_params)
@@ -0,0 +1,48 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.inference.v2.inference_parameter import InferenceParameter
from deepspeed.inference.v2.model_implementations.layer_container_base import LayerContainer
from .utils import SimpleParam, DummyInferenceModel
class ParentLayer(LayerContainer):
"""
A layer that has a dependency on a simple parameter.
"""
param_1: SimpleParam
class ChildLayer(ParentLayer):
"""
A layer that inherits from another layer.
"""
param_2: SimpleParam
@pytest.mark.inference_v2
def test_layer_inheritance():
inference_model = DummyInferenceModel()
multi_param_layer = ChildLayer(inference_model)
assert multi_param_layer.n_params == 2
assert multi_param_layer.is_initialized is False
multi_param_layer.param_1.param = torch.ones(16, 16)
assert multi_param_layer.is_initialized is False
multi_param_layer.param_2.param = torch.full((16, 16), 2.0)
assert multi_param_layer.is_populated is True
assert isinstance(multi_param_layer.param_1, InferenceParameter)
assert isinstance(multi_param_layer.param_2, InferenceParameter)
@@ -0,0 +1,168 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.inference.v2.allocator import on_device
from deepspeed.inference.v2.inference_parameter import InferenceParameter
from deepspeed.inference.v2.model_implementations.parameter_base import ParameterBase, ParamList
from deepspeed.inference.v2.model_implementations.layer_container_base import LayerContainer
class MultiDependencyContainer(ParameterBase):
dependency_1: torch.Tensor
dependency_2: torch.Tensor
@on_device
def finalize(self) -> torch.Tensor:
param = torch.cat([self.dependency_1, self.dependency_2])
return InferenceParameter.initialize(param)
class ListDependencyContainer(ParameterBase):
dependencies: ParamList("list_items") # noqa: F821
@on_device
def finalize(self) -> torch.Tensor:
param = torch.cat(tuple(self.dependencies))
return InferenceParameter.initialize(param)
class MappingLayer(LayerContainer):
PARAM_MAPPING = {
"model.val.item.d_1": "multi_depend.dependency_1",
"model.val.item.d_2": "multi_depend.dependency_2",
"model.list_vals.*.d": "list_depend.dependencies"
}
multi_depend: MultiDependencyContainer
list_depend: ListDependencyContainer
class SubMappingLayer(MappingLayer):
PARAM_MAPPING = {
"model.val.item2.d_1": "multi_depend2.dependency_1",
"model.val.item2.d_2": "multi_depend2.dependency_2",
}
multi_depend2: MultiDependencyContainer
class DoubleMappingLayer(LayerContainer):
PARAM_MAPPING = {
"model.val.item.d_1": ["multi_depend.dependency_1", "multi_depend.dependency_2"],
}
multi_depend: MultiDependencyContainer
class InferenceModel:
@property
def list_items(self) -> int:
return 16
@pytest.mark.inference_v2
def test_mapping_syntax():
model = InferenceModel()
mapping_layer = MappingLayer(model)
mapping_layer.set_dependency("model.val.item.d_1", torch.ones(1))
mapping_layer.set_dependency("model.val.item.d_2", torch.ones(1) * 2)
assert isinstance(mapping_layer.multi_depend, torch.Tensor)
for i in range(16):
mapping_layer.set_dependency(f"model.list_vals.{i}.d", torch.ones(1) * i)
if i != 16 - 1:
assert mapping_layer.is_populated == False
assert isinstance(mapping_layer.list_depend, InferenceParameter)
assert mapping_layer.is_populated == True
@pytest.mark.inference_v2
def test_sub_mapping_syntax():
model = InferenceModel()
mapping_layer = SubMappingLayer(model)
mapping_layer.set_dependency("model.val.item.d_1", torch.ones(1))
mapping_layer.set_dependency("model.val.item.d_2", torch.ones(1) * 2)
assert isinstance(mapping_layer.multi_depend, InferenceParameter)
mapping_layer.set_dependency("model.val.item2.d_1", torch.ones(1))
mapping_layer.set_dependency("model.val.item2.d_2", torch.ones(1) * 2)
assert isinstance(mapping_layer.multi_depend2, InferenceParameter)
# We want to check into double digits to make sure that this isn't specific
# to single difit indexing.
for i in range(16):
mapping_layer.set_dependency(f"model.list_vals.{i}.d", torch.ones(1) * i)
if i != 16 - 1:
assert mapping_layer.is_populated == False
assert isinstance(mapping_layer.list_depend, InferenceParameter)
assert mapping_layer.is_populated == True
@pytest.mark.inference_v2
def test_double_mapping_syntax():
model = InferenceModel()
mapping_layer = DoubleMappingLayer(model)
mapping_layer.set_dependency("model.val.item.d_1", torch.ones(1))
# The single parameter setting should immediately make the parameter finalized
# and the whole layer initialized.
assert isinstance(mapping_layer.multi_depend, InferenceParameter)
assert mapping_layer.is_populated == True
@pytest.mark.inference_v2
def test_insufficient_mapping_syntax():
"""
In the above example, we don't have a mapping for `multi_depend2.dependency_2`.
"""
with pytest.raises(ValueError):
class InsuffienctMappingLayer(LayerContainer):
PARAM_MAPPING = {
"model.val.item.d_1": "multi_depend1.dependency_1",
"model.val.item.d_2": "multi_depend1.dependency_2",
"model.val.item2.d_1": "multi_depend2.dependency_1",
}
multi_depend1: MultiDependencyContainer
multi_depend2: MultiDependencyContainer
@pytest.mark.inference_v2
def test_unknown_target_mapping_syntax():
"""
In the above example, `multi_depend_unknown` does not exist
"""
with pytest.raises(ValueError):
class UnknownTargetMappingLayer(LayerContainer):
PARAM_MAPPING = {
"model.val.item.d_1": "multi_depend1.dependency_1",
"model.val.item.d_2": "multi_depend1.dependency_2",
"model.val.item2.d_1": "multi_depend_unknown.dependency_1",
}
multi_depend: MultiDependencyContainer
@@ -0,0 +1,79 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.inference.v2.inference_parameter import InferenceParameter
from deepspeed.inference.v2.model_implementations.layer_container_base import LayerContainer
from .utils import validate_device, SimpleParam, ListParam, DummyInferenceModel
class MultiParameterLayer(LayerContainer):
"""
Two dependencies, both of which are simple parameters.
"""
param_1: SimpleParam
param_2: SimpleParam
class MixedMultiParameterLayer(LayerContainer):
"""
Two dependencies, one of which is a simple parameter, the other is a list parameter.
"""
param_1: SimpleParam
param_2: ListParam
@pytest.mark.inference_v2
def test_multi_parameter_layer():
inference_model = DummyInferenceModel()
multi_param_layer = MultiParameterLayer(inference_model)
assert multi_param_layer.n_params == 2
assert multi_param_layer.is_populated is False
multi_param_layer.param_1.param = torch.ones(16, 16)
assert multi_param_layer.is_populated is False
multi_param_layer.param_2.param = torch.full((16, 16), 2.0)
assert multi_param_layer.is_populated is True
assert isinstance(multi_param_layer.param_1, InferenceParameter)
assert isinstance(multi_param_layer.param_2, InferenceParameter)
@pytest.mark.inference_v2
def test_mixed_multi_parameter_layer():
inference_model = DummyInferenceModel()
mixed_multi_param_layer = MixedMultiParameterLayer(inference_model)
assert mixed_multi_param_layer.n_params == 2
assert mixed_multi_param_layer.is_populated is False
mixed_multi_param_layer.param_2.params[1] = torch.full((16, 16), 2.0)
assert mixed_multi_param_layer.is_populated is False
assert not isinstance(mixed_multi_param_layer.param_2, InferenceParameter)
mixed_multi_param_layer.param_1.param = torch.ones(16, 16)
assert mixed_multi_param_layer.is_populated is False
assert isinstance(mixed_multi_param_layer.param_1, InferenceParameter)
validate_device(mixed_multi_param_layer.param_1)
mixed_multi_param_layer.param_2.params[0] = torch.full((16, 16), 2.0)
assert mixed_multi_param_layer.is_populated is True
assert isinstance(mixed_multi_param_layer.param_2, InferenceParameter)
validate_device(mixed_multi_param_layer.param_2)
@@ -0,0 +1,105 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.inference.v2.allocator import on_device
from deepspeed.inference.v2.inference_parameter import InferenceParameter
from deepspeed.inference.v2.model_implementations.parameter_base import ParameterBase, ParamList
from deepspeed.inference.v2.model_implementations.layer_container_base import LayerContainer
from deepspeed.inference.v2.model_implementations.common_parameters import *
from .utils import validate_device
class SimpleMoELayer(LayerContainer):
moe_mlp_1: UnfusedMoEMLP1Parameter
class DummyInferenceModel:
def __init__(self, experts_per_rank: int) -> None:
self._num_experts = experts_per_rank
@property
def n_experts(self) -> int:
return self._num_experts
@on_device
def transform_moe_mlp_1_param(self, param: torch.Tensor) -> torch.Tensor:
return InferenceParameter.initialize(param)
@pytest.mark.inference_v2
def test_simple_moe_layer():
inference_model = DummyInferenceModel(experts_per_rank=2)
simple_moe_layer = SimpleMoELayer(inference_model)
assert simple_moe_layer.moe_mlp_1.experts[0] is None
assert simple_moe_layer.moe_mlp_1.experts[1] is None
# Set the first expert
simple_moe_layer.moe_mlp_1.experts[0] = torch.zeros(16, 16)
assert simple_moe_layer.moe_mlp_1.experts[0] is not None
assert simple_moe_layer.moe_mlp_1.experts[1] is None
assert not simple_moe_layer.is_initialized
# Set the second expert
simple_moe_layer.moe_mlp_1.experts[1] = torch.ones(16, 16)
# We have all the experts, so the layer should be initialized
assert simple_moe_layer.is_initialized
assert isinstance(simple_moe_layer.moe_mlp_1, torch.Tensor)
validate_device(simple_moe_layer.moe_mlp_1)
"""
Check that we can mix the number of elements in lists in the same context and have that
be tracked correctly.
"""
class CustomListParam1(ParameterBase):
deps: ParamList("attr_1")
class CustomListParam2(ParameterBase):
deps: ParamList("attr_2")
class MixedLayer(LayerContainer):
list_1: CustomListParam1
list_2: CustomListParam2
class MixedInferenceModel:
@property
def attr_1(self) -> int:
return 1
@property
def attr_2(self) -> int:
return 2
@pytest.mark.inference_v2
def test_mixed_param_lists():
model = MixedInferenceModel()
layer = MixedLayer(model)
assert layer.list_1.deps.n_params == 1
assert layer.list_2.deps.n_params == 2
@@ -0,0 +1,60 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.allocator import on_device
from deepspeed.inference.v2.inference_parameter import InferenceParameter
from deepspeed.inference.v2.model_implementations.parameter_base import ParameterBase, ParametrizedList
class SimpleParam(ParameterBase):
"""
Parameter with single dependency.
"""
param: torch.Tensor
@on_device
def finalize(self) -> torch.Tensor:
return self.inference_model.transform(self.param)
class SimpleParametrizedList(ParametrizedList):
"""
Parameter list based on `num_dependencies` attribute.
"""
count_attr: str = "num_dependencies"
class ListParam(ParameterBase):
"""
Parameter with list dependency.
NOTE: This uses the tuple workaround for the `ParametrizedList` class
as described in the docstring of `ParametrizedList`.
"""
params: SimpleParametrizedList
@on_device
def finalize(self) -> torch.Tensor:
return self.inference_model.transform(torch.cat(tuple(self.params)))
class DummyInferenceModel:
@property
def num_dependencies(self) -> int:
return 2
def transform(self, param: torch.Tensor) -> torch.Tensor:
return InferenceParameter.initialize(param)
def validate_device(tensor: torch.Tensor):
assert tensor.device == torch.device(get_accelerator().current_device())
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,129 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.model_implementations.sharding import *
# None of the logic should be dependent on head size.
HEAD_SIZE = 64
def fill_with_head_ids(head_size: int, n_heads: int) -> torch.Tensor:
"""
Fills a tensor with the associated head ids. All columns should have the same value.
"""
head_ids = torch.arange(n_heads, dtype=torch.half, device=get_accelerator().current_device())
head_ids = head_ids.repeat_interleave(head_size).repeat(head_size * n_heads).reshape(n_heads * head_size, -1)
return head_ids
@pytest.mark.inference_v2
@pytest.mark.parametrize("n_heads, n_shards", [(1, 1), (8, 4), (32, 8)])
def test_mha_even_sharding(n_heads: int, n_shards: int):
"""
Even head sharding for MHA.
Args:
n_heads (int): The number QKV heads.
n_shards (int): The number of shards to test for.
"""
param = fill_with_head_ids(HEAD_SIZE, n_heads)
n_local_heads = n_heads // n_shards
sharded_shape = (HEAD_SIZE * n_heads, HEAD_SIZE * n_local_heads)
for shard_rank in range(n_shards):
sharded_param = shard_attn_out_param(param, shard_rank, n_shards, HEAD_SIZE)
n_heads_local_q, _ = get_local_heads(shard_rank, n_shards, n_heads)
assert sharded_param.shape[-1] == HEAD_SIZE * n_heads_local_q
assert sharded_param.shape == sharded_shape
heads = torch.chunk(sharded_param, n_local_heads, dim=1)
for i, head in enumerate(heads):
assert torch.all(head == i + shard_rank * n_local_heads)
@pytest.mark.inference_v2
@pytest.mark.parametrize("n_heads, n_shards", [(3, 2), (20, 8)])
def test_mha_unbalanced_sharding(n_heads: int, n_shards: int):
"""
Unbalanced head sharding for MHA.
Args:
n_heads (int): The number QKV heads.
n_shards (int): The number of shards to test for.
"""
param = fill_with_head_ids(HEAD_SIZE, n_heads)
max_heads = 0
min_heads = n_heads
seen_heads = set()
total_heads = 0
for shard_rank in range(n_shards):
sharded_param = shard_attn_out_param(param, shard_rank, n_shards, HEAD_SIZE)
n_heads_local_q, _ = get_local_heads(shard_rank, n_shards, n_heads)
assert sharded_param.shape[-1] == HEAD_SIZE * n_heads_local_q
n_local_heads = sharded_param.shape[1] // HEAD_SIZE
total_heads += n_local_heads
max_heads = max(max_heads, n_local_heads)
min_heads = min(min_heads, n_local_heads)
for i in range(n_local_heads):
head_ids = torch.unique_consecutive(sharded_param[:, i * HEAD_SIZE:(i + 1) * HEAD_SIZE])
assert len(head_ids) == 1
seen_heads.add(head_ids.item())
assert max_heads == min_heads + 1
assert total_heads == n_heads
assert len(seen_heads) == n_heads
@pytest.mark.inference_v2
@pytest.mark.parametrize("n_heads_q, n_heads_kv, n_shards", [(20, 4, 8)])
def test_gqa_uneven_sharding(n_heads_q: int, n_heads_kv: int, n_shards: int):
"""
We only test the uneven GQA test case because even GQA shards the attention output
in the exact same manner as MHA.
Args:
n_heads_q (int): The number of query heads.
n_heads_kv (int): The number of key/value heads.
n_shards (int): The number of shards to test for.
"""
param = fill_with_head_ids(HEAD_SIZE, n_heads_q)
min_heads = n_heads_q
max_heads = 0
seen_heads = set()
total_heads = 0
for shard_rank in range(n_shards):
sharded_param = shard_attn_out_param(param, shard_rank, n_shards, HEAD_SIZE, n_heads_q, n_heads_kv)
n_heads_local_q, _ = get_local_heads(shard_rank, n_shards, n_heads_q, n_heads_kv)
assert sharded_param.shape[-1] == HEAD_SIZE * n_heads_local_q
n_local_heads = sharded_param.shape[1] // HEAD_SIZE
total_heads += n_local_heads
max_heads = max(max_heads, n_local_heads)
min_heads = min(min_heads, n_local_heads)
for i in range(n_local_heads):
head_id = torch.unique_consecutive(sharded_param[:, i * HEAD_SIZE:(i + 1) * HEAD_SIZE])
assert len(head_id) == 1
seen_heads.add(head_id.item())
assert max_heads == min_heads + 1
assert total_heads == n_heads_q
assert len(seen_heads) == n_heads_q
@@ -0,0 +1,116 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.model_implementations.sharding import *
def round_up_to_256(x: int) -> int:
"""
Round up to the nearest multiple of 256.
"""
return x + (256 - x % 256)
def make_params(model_dim: int, ffn_multiplier: int, n_experts: int, gated: bool = False) -> torch.Tensor:
"""
"""
if gated:
mlp_1_intermediate = round_up_to_256(int(model_dim * ffn_multiplier * 4 / 3))
mlp_2_intermediate = mlp_1_intermediate // 2
else:
mlp_1_intermediate = ffn_multiplier * model_dim
mlp_2_intermediate = ffn_multiplier * model_dim
mlp_1_shared_dim = torch.arange(mlp_1_intermediate, dtype=torch.float32, device=get_accelerator().current_device())
mlp_1_w = mlp_1_shared_dim.repeat_interleave(model_dim).reshape(mlp_1_intermediate, model_dim)
mlp_1_b = mlp_1_shared_dim
mlp_2_shared_dim = torch.arange(mlp_2_intermediate, dtype=torch.float32, device=get_accelerator().current_device())
mlp_2_w = mlp_2_shared_dim.repeat(model_dim).reshape(model_dim, mlp_2_intermediate)
mlp_2_b = torch.ones(model_dim, dtype=torch.float32, device=get_accelerator().current_device())
if n_experts > 1:
mlp_1_w = mlp_1_w.expand(n_experts, -1, -1)
mlp_1_b = mlp_1_b.expand(n_experts, -1)
mlp_2_w = mlp_2_w.expand(n_experts, -1, -1)
mlp_2_b = mlp_2_b.expand(n_experts, -1)
return (mlp_1_w, mlp_1_b, mlp_2_w, mlp_2_b)
@pytest.mark.inference_v2
@pytest.mark.parametrize("model_dim, ffn_multiplier, n_shards", [(1024, 4, 1), (1024, 4, 8), (1024, 4, 6)])
@pytest.mark.parametrize("n_experts", [1, 16])
def test_even_ffn_sharding(model_dim: int, ffn_multiplier: int, n_shards: int, n_experts: int):
"""
FFN sharding tends to be much simpler than attention sharding since it works on larger granularities.
While the test case of (1024, 4, 6) is not a use case we're likely to see, this does ensure that
the sharding logic will round correctly for the alignments we care about.
"""
mlp_1_w, mlp_1_b, mlp_2_w, mlp_2_b = make_params(model_dim, ffn_multiplier, n_experts)
total_ffn_dim = model_dim * ffn_multiplier
mapped_neurons = 0
is_moe = n_experts > 1
for shard_rank in range(n_shards):
shard_1_w = shard_mlp_1_param(mlp_1_w, shard_rank, n_shards, is_moe=is_moe)
shard_1_b = shard_mlp_1_param(mlp_1_b, shard_rank, n_shards, is_moe=is_moe)
shard_2_w = shard_mlp_2_param(mlp_2_w, shard_rank, n_shards, is_moe=is_moe)
shard_2_b = shard_mlp_2_param(mlp_2_b, shard_rank, n_shards, is_moe=is_moe)
assert shard_1_w.shape[-2] == shard_2_w.shape[-1]
assert shard_1_w.shape[-2] % DEFAULT_SHARD_GRANULARITY == 0
assert shard_1_w.shape[-2] == shard_1_b.shape[-1]
mapped_neurons += shard_1_w.shape[-2]
if shard_rank != 0:
assert shard_2_b is None
else:
assert shard_2_b.shape[-1] == model_dim
assert mapped_neurons == total_ffn_dim
@pytest.mark.inference_v2
@pytest.mark.parametrize("model_dim, ffn_multiplier, n_shards", [(1024, 4, 1), (1024, 4, 8), (1024, 4, 6)])
@pytest.mark.parametrize("n_experts", [1, 16])
def test_gated_ffn_sharding(model_dim: int, ffn_multiplier: int, n_shards: int, n_experts: int):
"""
Test the same cases assuming a gated regime.
"""
mlp_1_w, mlp_1_b, mlp_2_w, mlp_2_b = make_params(model_dim, ffn_multiplier, n_experts, gated=True)
total_ffn_dim = round_up_to_256(int(model_dim * ffn_multiplier * 4 / 3))
mapped_neurons = 0
is_moe = n_experts > 1
for shard_rank in range(n_shards):
shard_1_w = shard_mlp_1_param(mlp_1_w, shard_rank, n_shards, gated=True, is_moe=is_moe)
shard_1_b = shard_mlp_1_param(mlp_1_b, shard_rank, n_shards, gated=True, is_moe=is_moe)
shard_2_w = shard_mlp_2_param(mlp_2_w, shard_rank, n_shards, is_moe=is_moe)
shard_2_b = shard_mlp_2_param(mlp_2_b, shard_rank, n_shards, is_moe=is_moe)
assert shard_1_w.shape[-2] == shard_2_w.shape[-1] * 2
assert shard_1_w.shape[-2] % DEFAULT_SHARD_GRANULARITY == 0
assert shard_1_w.shape[-2] == shard_1_b.shape[-1]
mapped_neurons += shard_1_w.shape[-2]
if shard_rank != 0:
assert shard_2_b is None
else:
assert shard_2_b.shape[-1] == model_dim
assert mapped_neurons == total_ffn_dim
@@ -0,0 +1,251 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.model_implementations.sharding import *
def fill_with_head_ids(head_size: int, n_heads_q: int, n_heads_kv: Optional[int] = None) -> torch.Tensor:
"""
"""
head_ids_q = torch.arange(n_heads_q, dtype=torch.half, device=get_accelerator().current_device())
head_vals_q = head_ids_q.repeat_interleave(head_size * head_size * n_heads_q).reshape(n_heads_q * head_size, -1)
if n_heads_kv is None:
return torch.cat([head_vals_q, head_vals_q, head_vals_q], dim=0)
head_ids_k = torch.arange(n_heads_kv, dtype=torch.half, device=get_accelerator().current_device())
head_vals_k = head_ids_k.repeat_interleave(head_size * head_size * n_heads_q).reshape(n_heads_kv * head_size, -1)
return torch.cat([head_vals_q, head_vals_k, head_vals_k], dim=0)
def validate_inferred_shape(shard: torch.Tensor, head_size: int, n_local_q_heads: int, n_local_kv_heads: int):
"""
Validate that the leading dim of the shard is of the expected size and aligns with the sharding
logic for the attention computation itself.
"""
inferred_leading_dim = head_size * (n_local_q_heads + 2 * n_local_kv_heads)
assert shard.shape[0] == inferred_leading_dim
@pytest.mark.inference_v2
@pytest.mark.parametrize("head_size", [64])
@pytest.mark.parametrize("n_heads,n_shards", [(1, 1), (32, 1), (32, 8)])
def test_even_mha_sharding(head_size: int, n_heads: int, n_shards: int):
"""
Test for MHA sharding. In these scenarios, we expect that each of the shards
should be the same size.
"""
param = fill_with_head_ids(head_size, n_heads)
heads_per_shard = n_heads // n_shards
for shard_rank in range(n_shards):
shard = shard_qkv_param(param, shard_rank, n_shards, head_size, n_heads, n_heads)
n_local_q_heads, n_local_kv_heads = get_local_heads(shard_rank, n_shards, n_heads, n_heads)
validate_inferred_shape(shard, head_size, n_local_q_heads, n_local_kv_heads)
assert shard.shape == (3 * head_size * heads_per_shard, head_size * n_heads)
heads = shard.chunk(heads_per_shard * 3, dim=0)
for i in range(heads_per_shard):
assert torch.all(heads[i] == i + shard_rank * heads_per_shard)
assert torch.all(heads[i + heads_per_shard] == i + shard_rank * heads_per_shard)
assert torch.all(heads[i + heads_per_shard * 2] == i + shard_rank * heads_per_shard)
@pytest.mark.inference_v2
@pytest.mark.parametrize("head_size", [64])
@pytest.mark.parametrize("n_heads, n_shards", [(3, 2), (20, 8)])
def test_unbalanced_mha_sharding(head_size: int, n_heads: int, n_shards: int):
"""
Test MHA sharding when the distribution of heads will not be equal across all ranks.
"""
param = fill_with_head_ids(head_size, n_heads)
max_heads = 0
min_heads = n_heads
total_heads = 0
seen_heads = set()
for shard_rank in range(n_shards):
shard = shard_qkv_param(param, shard_rank, n_shards, head_size, n_heads, n_heads)
n_local_q_heads, n_local_kv_heads = get_local_heads(shard_rank, n_shards, n_heads, n_heads)
validate_inferred_shape(shard, head_size, n_local_q_heads, n_local_kv_heads)
n_heads_in_shard = shard.shape[0] // head_size // 3
max_heads = max(max_heads, n_heads_in_shard)
min_heads = min(min_heads, n_heads_in_shard)
total_heads += n_heads_in_shard
heads = shard.chunk(n_heads_in_shard * 3, dim=0)
for local_head_id in range(n_heads_in_shard):
head_qkv = torch.cat([
heads[local_head_id], heads[local_head_id + n_heads_in_shard],
heads[local_head_id + 2 * n_heads_in_shard]
],
dim=0)
assert head_qkv.shape == (3 * head_size, head_size * n_heads)
global_head_id = torch.unique_consecutive(head_qkv)
assert len(global_head_id) == 1
seen_heads.add(global_head_id.item())
assert max_heads - min_heads <= 1
assert total_heads == n_heads
assert len(seen_heads) == n_heads
@pytest.mark.inference_v2
@pytest.mark.parametrize("head_size", [64])
@pytest.mark.parametrize("n_heads_q, n_heads_kv, n_shards", [(4, 2, 1), (8, 2, 1), (64, 16, 8)])
def test_gqa_even_sharding(head_size: int, n_heads_q: int, n_heads_kv: int, n_shards: int):
"""
Test GQA sharding when the KV heads are evenly divisible by the number of shards.
"""
param = fill_with_head_ids(head_size, n_heads_q, n_heads_kv)
n_kv_heads_in_shard = n_heads_kv // n_shards
n_q_heads_in_shard = n_heads_q // n_shards
for shard_rank in range(n_shards):
shard = shard_qkv_param(param, shard_rank, n_shards, head_size, n_heads_q, n_heads_kv)
n_local_q_heads, n_local_kv_heads = get_local_heads(shard_rank, n_shards, n_heads_q, n_heads_kv)
validate_inferred_shape(shard, head_size, n_local_q_heads, n_local_kv_heads)
assert shard.shape[0] == (n_q_heads_in_shard + n_kv_heads_in_shard * 2) * head_size
q = shard[:n_q_heads_in_shard * head_size]
k = shard[n_q_heads_in_shard * head_size:(n_q_heads_in_shard + n_kv_heads_in_shard) * head_size]
v = shard[(n_q_heads_in_shard + n_kv_heads_in_shard) * head_size:]
for local_head_id in range(n_q_heads_in_shard):
assert torch.all(q[local_head_id * head_size:(local_head_id + 1) * head_size] == local_head_id +
shard_rank * n_q_heads_in_shard)
for local_head_id in range(n_kv_heads_in_shard):
assert torch.all(k[local_head_id * head_size:(local_head_id + 1) * head_size] == local_head_id +
shard_rank * n_kv_heads_in_shard)
assert torch.all(v[local_head_id * head_size:(local_head_id + 1) * head_size] == local_head_id +
shard_rank * n_kv_heads_in_shard)
@pytest.mark.inference_v2
@pytest.mark.parametrize("head_size", [64])
@pytest.mark.parametrize("n_heads_q, n_heads_kv, n_shards", [(4, 2, 4), (20, 4, 8)])
def test_gqa_uneven_sharding(head_size: int, n_heads_q: int, n_heads_kv: int, n_shards: int):
"""
Test GQA sharding when there are more shards than KV heads.
"""
param = fill_with_head_ids(head_size, n_heads_q, n_heads_kv)
n_kv_heads_in_shard = 1
n_shards_per_kv_head = n_shards // n_heads_kv
max_heads = 0
min_heads = n_heads_q
total_heads = 0
seen_heads = set()
for shard_rank in range(n_shards):
shard = shard_qkv_param(param, shard_rank, n_shards, head_size, n_heads_q, n_heads_kv)
n_local_q_heads, n_local_kv_heads = get_local_heads(shard_rank, n_shards, n_heads_q, n_heads_kv)
validate_inferred_shape(shard, head_size, n_local_q_heads, n_local_kv_heads)
local_n_heads_q = (shard.shape[0] - 2 * n_kv_heads_in_shard * head_size) // head_size
max_heads = max(max_heads, local_n_heads_q)
min_heads = min(min_heads, local_n_heads_q)
total_heads += local_n_heads_q
q = shard[:local_n_heads_q * head_size]
kv = shard[local_n_heads_q * head_size:]
for local_head_id in range(local_n_heads_q):
q_head_id = torch.unique_consecutive(q[local_head_id * head_size:(local_head_id + 1) * head_size])
assert len(q_head_id) == 1
seen_heads.add(q_head_id.item())
kv_id_calc = shard_rank // n_shards_per_kv_head
kv_id = torch.unique_consecutive(kv)
assert len(kv_id) == 1
assert kv_id.item() == kv_id_calc
assert max_heads - min_heads <= 1
assert total_heads == n_heads_q
assert len(seen_heads) == n_heads_q
@pytest.mark.inference_v2
@pytest.mark.parametrize("head_size", [64])
@pytest.mark.parametrize("n_heads, n_shards", [(6, 8)])
def test_unsupported_mha_configs(head_size: int, n_heads: int, n_shards: int):
"""
Sharding should fail if there are fewer heads than shards.
TODO(cmikeh2): Look to support this configuration.
"""
param = fill_with_head_ids(head_size, n_heads)
for shard_rank in range(n_shards):
with pytest.raises(ValueError):
shard_qkv_param(param, shard_rank, n_shards, head_size, n_heads, n_heads)
@pytest.mark.inference_v2
@pytest.mark.parametrize("head_size", [64])
@pytest.mark.parametrize("n_heads_q, n_heads_kv, n_shards", [(5, 2, 1), (40, 10, 8), (30, 5, 8)])
def test_unsupported_gqa_configs(head_size: int, n_heads_q: int, n_heads_kv: int, n_shards: int):
"""
GQA has stricter requirements. We must be able to evenly shard or distribute the KV heads.
Test cases are to test the following preconditions specifically:
1. n_heads_q % n_heads_kv == 0
2. We must be able to evenly distribute KV heads
3. We must be able to evely split KV heads
"""
param = fill_with_head_ids(head_size, n_heads_q, n_heads_kv)
for shard_rank in range(n_shards):
with pytest.raises(ValueError):
shard_qkv_param(param, shard_rank, n_shards, head_size, n_heads_q, n_heads_kv)
@pytest.mark.inference_v2
def test_mha_input_shape_error():
param = torch.empty(256, 128)
n_heads = 2
head_size = 64
with pytest.raises(ValueError):
shard_qkv_param(param, 0, 1, 64)
@pytest.mark.inference_v2
def test_gqa_input_shape_error():
head_size = 64
n_heads_q = 16
n_heads_kv = 4
# Correct shape is 1536 (=16 * 64 + 2 * 4 * 64), 1024
param = torch.empty(2048, 1024)
with pytest.raises(ValueError):
shard_qkv_param(param, 0, 1, head_size, n_heads_q, n_heads_kv)
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,111 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.inference_utils import ActivationType, DtypeEnum, is_gated
from deepspeed.inference.v2.modules import ConfigBundle
from deepspeed.inference.v2.modules.configs import DSLinearConfig
from deepspeed.inference.v2.modules.interfaces import DSLinearRegistry
from ...v2.inference_test_utils import allclose
def reference_implementation(hidden_states: torch.Tensor, weight: torch.Tensor, bias: Optional[torch.Tensor],
act_type: ActivationType) -> torch.Tensor:
dtype = hidden_states.dtype
out_states = torch.nn.functional.linear(hidden_states, weight, bias)
out_states.float()
if is_gated(act_type):
act_func_map = {
ActivationType.ReGLU: torch.nn.functional.relu,
ActivationType.GEGLU: lambda x: torch.nn.functional.gelu(x, approximate="tanh"),
ActivationType.SiGLU: torch.nn.functional.silu,
}
act_act = out_states[..., ::2]
act_linear = out_states[..., 1::2]
act_act = act_func_map[act_type](act_act)
out_states = act_act * act_linear
else:
act_func_map = {
ActivationType.RELU: torch.nn.functional.relu,
ActivationType.GELU: torch.nn.functional.gelu,
ActivationType.SILU: torch.nn.functional.silu,
ActivationType.IDENTITY: lambda x: x,
}
out_states = act_func_map[act_type](out_states)
return out_states.to(dtype)
def _blas_linear_helper(tokens: int,
in_channels: int,
out_channels: int,
dtype: DtypeEnum,
act_fn: ActivationType,
use_bias: bool = True) -> None:
linear_config = DSLinearConfig(max_tokens=2048,
in_channels=in_channels,
out_channels=out_channels,
activation=act_fn,
input_dtype=dtype,
output_dtype=dtype)
bundle = ConfigBundle(name='blas_fp_linear', config=linear_config)
module = DSLinearRegistry.instantiate_config(bundle)
# Input vals
hidden_states = torch.randn(
(tokens, in_channels), dtype=dtype.value, device=get_accelerator().current_device_name()) * .01
weight_out_channels = 2 * out_channels if is_gated(act_fn) else out_channels
weight = torch.randn(
(weight_out_channels, in_channels), dtype=dtype.value, device=get_accelerator().current_device_name()) * .01
if use_bias:
bias = torch.randn(
(weight_out_channels), dtype=dtype.value, device=get_accelerator().current_device_name()) * .01
else:
bias = None
# Reference output
ref_output = reference_implementation(hidden_states, weight, bias, act_fn)
# New output
ds_output = module(hidden_states, weight, bias)
# Check
assert allclose(ds_output, ref_output)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("tokens, in_channels, out_channels", [(1, 4608, 1728), (37, 8192, 4096), (1280, 3072, 6144)])
def test_blas_linear_shapes(tokens: int, in_channels: int, out_channels: int) -> None:
_blas_linear_helper(tokens, in_channels, out_channels, DtypeEnum.fp16, ActivationType.IDENTITY)
all_acts = [
ActivationType.RELU,
ActivationType.GELU,
ActivationType.SILU,
ActivationType.GEGLU,
ActivationType.ReGLU,
ActivationType.SiGLU,
]
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("act_fn", all_acts)
@pytest.mark.parametrize("use_bias", [True, False])
def test_blas_linear_act_fn(act_fn: ActivationType, use_bias: bool) -> None:
_blas_linear_helper(283, 512, 4096, DtypeEnum.fp16, act_fn, use_bias=use_bias)
@@ -0,0 +1,207 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import itertools
from typing import List, Tuple
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.modules import ConfigBundle
from deepspeed.inference.v2.modules.configs import DSSelfAttentionConfig, PositionalEmbeddingType, RotateHalfConfig
from deepspeed.inference.v2.modules.interfaces import DSSelfAttentionRegistry, DSSelfAttentionBase
from ..kernels.ragged_ops.ragged_testing_utils import build_batch_and_manager
from ...v2.inference_test_utils import allclose
try:
from flash_attn.flash_attn_interface import flash_attn_varlen_func
validate_accuracy = True
except ImportError:
validate_accuracy = False
def _blocked_flash_testing_helper(head_size: int,
n_heads_q: int,
n_heads_kv: int,
seq_params: List[Tuple[int, int]],
trained_freqs: bool = None) -> None:
"""
Helper function for testing blocked flash attention. This implementation is based on
the implemnentation in ``unit.inference.kernels.ragged_ops.test_blocked_flash`` but
integrates functionality to validate the composability.
"""
if trained_freqs is None:
embed_type = PositionalEmbeddingType.none
embed_args = None
else:
embed_type = PositionalEmbeddingType.rotate_half
embed_args = RotateHalfConfig(use_trained_freqs=trained_freqs)
attn_config = DSSelfAttentionConfig(max_tokens=2048,
n_heads_q=n_heads_q,
n_heads_kv=n_heads_kv,
head_size=head_size,
max_sequences=32,
positional_embedding_type=embed_type,
positional_embedding_config=embed_args)
config = ConfigBundle(name='dense_blocked_attention', config=attn_config)
attn_module: DSSelfAttentionBase = DSSelfAttentionRegistry.instantiate_config(config)
kv_block_size = attn_module.kv_block_size
kvs = []
for _, history_len in seq_params:
if history_len > 0:
kvs.append(
torch.randn((history_len, 2 * n_heads_kv * head_size),
device=get_accelerator().current_device(),
dtype=torch.float16))
else:
kvs.append(None)
batch, state_manager, _ = build_batch_and_manager(seq_params, head_size, n_heads_kv, kv_block_size, kv_fill=kvs)
qkv = torch.randn((batch.current_tokens, (n_heads_q + 2 * n_heads_kv) * head_size),
device=get_accelerator().current_device(),
dtype=torch.float16)
kv_cache = state_manager.get_cache(0)
attn_module.build_atoms(batch)
if not trained_freqs:
out = attn_module(qkv, kv_cache, batch)
else:
inv_freqs = torch.randn((head_size // 2, ), device=get_accelerator().current_device(), dtype=torch.float16)
out = attn_module(qkv, kv_cache, batch, inv_freqs)
if validate_accuracy and trained_freqs is None:
cu_seqlens_q = torch.tensor([0] + list(itertools.accumulate([seq[0] for seq in seq_params])),
dtype=torch.int32,
device=get_accelerator().current_device())
cu_seqlens_kv = torch.tensor([0] + list(itertools.accumulate([seq[1] + seq[0] for seq in seq_params])),
dtype=torch.int32,
device=get_accelerator().current_device())
inflight_kv = qkv[:, head_size * n_heads_q:]
full_kvs = []
for i, kv in enumerate(kvs):
if kv is not None:
full_kvs.append(torch.cat([kv, inflight_kv[cu_seqlens_q[i]:cu_seqlens_q[i + 1]]], dim=0))
else:
full_kvs.append(inflight_kv[cu_seqlens_q[i]:cu_seqlens_q[i + 1]])
run_kvs = torch.cat(full_kvs, dim=0)
k = run_kvs[:, :head_size * n_heads_kv]
v = run_kvs[:, head_size * n_heads_kv:]
q = qkv[:, :head_size * n_heads_q]
q_ref = q.reshape((batch.current_tokens, n_heads_q, head_size))
k_ref = k.reshape((k.shape[0], n_heads_kv, head_size))
v_ref = v.reshape((v.shape[0], n_heads_kv, head_size))
max_seqlen_q = max([seq[0] for seq in seq_params])
max_seqlen_kv = max([seq[1] + seq[0] for seq in seq_params])
ref_o = flash_attn_varlen_func(q_ref,
k_ref,
v_ref,
cu_seqlens_q,
cu_seqlens_kv,
max_seqlen_q,
max_seqlen_kv,
softmax_scale=1.0,
causal=True)
ref_o = ref_o.reshape(batch.current_tokens, head_size * n_heads_q)
assert allclose(out, ref_o)
get_accelerator().synchronize()
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("n_tokens", [2, 33, 65, 128, 256, 2037])
def test_single_prompt(n_tokens: int) -> None:
head_size = 64
n_heads_q = 16
n_heads_kv = 16
seq_params = [(n_tokens, 0)]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("prompt_lengths", [(128, 128), (192, 38), (514, 713), (83, 312, 610)])
def test_multiple_prompts(prompt_lengths: Tuple[int, int]) -> None:
"""
Test multiple prompts in a single batch.
"""
head_size = 64
n_heads_q = 16
n_heads_kv = 16
seq_params = [(prompt_lengths[i], 0) for i in range(len(prompt_lengths))]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("seq_params", [(1, 34), (43, 40), (1, 144), (64, 128), (332, 628)])
def test_continuation(seq_params: Tuple[int, int]) -> None:
"""
Test continued generation/prompt processing.
"""
head_size = 64
n_heads_q = 32
n_heads_kv = 32
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, [seq_params])
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("head_size", [64, 128])
def test_head_size(head_size: int) -> None:
n_heads_q = 16
n_heads_kv = 16
seq_params = [(128, 128), (192, 38), (1, 814)]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("head_config", [(32, 8), (64, 16), (40, 8)])
def test_gqa(head_config: Tuple[int, int]) -> None:
head_size = 128
n_heads_q = head_config[0]
n_heads_kv = head_config[1]
seq_params = [(128, 128), (192, 38), (1, 814)]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params)
@pytest.mark.inference_v2_ops
def test_fully_composed() -> None:
head_size = 64
n_heads_q = 16
n_heads_kv = 16
seq_params = [(332, 628), (1, 718), (1, 323), (180, 5), (224, 0)]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("trained_freqs", [True, False])
def test_rotary_emb(trained_freqs: bool) -> None:
head_size = 64
n_heads_q = 16
n_heads_kv = 16
seq_params = [(332, 628), (1, 718), (1, 323), (180, 5), (224, 0)]
_blocked_flash_testing_helper(head_size, n_heads_q, n_heads_kv, seq_params, trained_freqs=trained_freqs)
@@ -0,0 +1,88 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional, Tuple
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.modules import ConfigBundle
from deepspeed.inference.v2.modules.configs import DSNormConfig
from deepspeed.inference.v2.modules.interfaces import DSPreNormRegistry
from ...v2.inference_test_utils import get_dtypes, allclose
def reference_implementation(residual: torch.Tensor, hidden_states: Optional[torch.Tensor], gamma: torch.Tensor,
beta: torch.Tensor, epsilon: float) -> Tuple[torch.Tensor, torch.Tensor]:
dtype = residual.dtype
residual = residual.to(torch.float32)
gamma = gamma.to(torch.float32)
beta = beta.to(torch.float32)
if hidden_states is not None:
hidden_states = hidden_states.to(torch.float32)
residual = residual + hidden_states
hidden_states = torch.nn.functional.layer_norm(residual, (residual.size(-1), ),
weight=gamma,
bias=beta,
eps=epsilon)
return residual.to(dtype), hidden_states.to(dtype)
def _pre_ln_test_helper(n_tokens: int, n_channels: int, dtype: torch.dtype, res_add: bool = False):
config = DSNormConfig(max_tokens=2048,
type="layer_norm",
channels=n_channels,
residual_dtype=dtype,
input_dtype=dtype,
output_dtype=dtype,
eps=1e-5)
bundle = ConfigBundle(name='cuda_pre_ln', config=config)
# Input vals
if res_add:
hidden_states = torch.randn((n_tokens, n_channels),
dtype=dtype,
device=get_accelerator().current_device_name())
else:
hidden_states = None
residual = torch.randn((n_tokens, n_channels), dtype=dtype, device=get_accelerator().current_device_name())
gamma = torch.randn((n_channels), dtype=torch.float32, device=get_accelerator().current_device_name())
beta = torch.rand((n_channels), dtype=torch.float32, device=get_accelerator().current_device_name())
epsilon = 1e-5
# Reference output
ref_residual, ref_output = reference_implementation(residual, hidden_states, gamma, beta, epsilon)
# New output
pre_ln_module = DSPreNormRegistry.instantiate_config(bundle)
gamma = pre_ln_module.transform_param(gamma)
beta = pre_ln_module.transform_param(beta)
ds_residual, ds_output = pre_ln_module(residual, hidden_states, gamma, beta)
# Check
assert allclose(ds_residual, ref_residual)
assert allclose(ds_output, ref_output)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("tokens, channels", [(1, 2048), (37, 8192), (1280, 768), (2048, 5120)])
def test_token_channels(tokens: int, channels: int) -> None:
_pre_ln_test_helper(tokens, channels, torch.float16)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("dtype", get_dtypes(include_float=False))
def test_dtype(dtype: torch.dtype) -> None:
_pre_ln_test_helper(733, 2560, dtype)
@pytest.mark.inference_v2_ops
def test_no_res_add():
_pre_ln_test_helper(733, 2560, torch.float16, res_add=False)
@@ -0,0 +1,76 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.modules import ConfigBundle
from deepspeed.inference.v2.modules.interfaces import DSPostNormRegistry
from deepspeed.inference.v2.modules.configs import DSNormConfig
from deepspeed.inference.v2.modules.implementations import cuda_post_ln
from ...v2.inference_test_utils import allclose
def reference_implementation(residual: torch.Tensor, hidden_states: torch.Tensor, gamma: torch.Tensor,
beta: torch.Tensor, epsilon: float) -> torch.Tensor:
residual_f = residual.to(torch.float32)
hidden_states_f = hidden_states.to(torch.float32)
gamma_f = gamma.to(torch.float32)
beta_f = beta.to(torch.float32)
return torch.nn.functional.layer_norm(residual_f + hidden_states_f, (hidden_states_f.size(-1), ),
weight=gamma_f,
bias=beta_f,
eps=epsilon).to(hidden_states.dtype)
@DSPostNormRegistry.register_module
class CustomPostLNModule(cuda_post_ln.DSPostLNCUDAModule):
@staticmethod
def name():
return 'custom_post_ln'
"""
Here, we explicitly register an LN implementation outside the core deepspeed repo. This should
validate that the registry is working as expected and we can implement modules outside the core
repo.
"""
@pytest.mark.inference_v2_ops
def test_custom_registration():
channels = 4096
dtype = torch.float16
tokens = 1024
config = DSNormConfig(max_tokens=2048,
type="layer_norm",
channels=channels,
residual_dtype=dtype,
input_dtype=dtype,
output_dtype=dtype,
eps=1e-5)
bundle = ConfigBundle(name='custom_post_ln', config=config)
# Input vals
hidden_states = torch.randn((tokens, channels), dtype=dtype, device=get_accelerator().current_device_name())
residual = torch.randn((tokens, channels), dtype=dtype, device=get_accelerator().current_device_name())
gamma = torch.randn((channels), dtype=torch.float32, device=get_accelerator().current_device_name())
beta = torch.rand((channels), dtype=torch.float32, device=get_accelerator().current_device_name())
epsilon = 1e-5
# Reference output
ref_output = reference_implementation(residual, hidden_states, gamma, beta, epsilon)
# New output
post_ln_module = DSPostNormRegistry.instantiate_config(bundle)
gamma = post_ln_module.transform_param(gamma)
beta = post_ln_module.transform_param(beta)
ds_output, _ = post_ln_module(residual, hidden_states, gamma, beta)
# Check
assert allclose(ds_output, ref_output)
@@ -0,0 +1,328 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Tuple
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.inference_utils import ActivationType, DtypeEnum
from deepspeed.inference.v2.modules import ConfigBundle
from deepspeed.inference.v2.modules.configs import DSMoEConfig
from deepspeed.inference.v2.modules.interfaces import DSMoERegistry
from ..kernels.ragged_ops.ragged_testing_utils import build_simple_batch
from ...v2.inference_test_utils import allclose, get_dtypes
def _gating_reference(logits: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
"""
Reference gating code.
"""
logits = logits.float()
probs = torch.nn.functional.softmax(logits, dim=1)
indices1_s = torch.argmax(probs, dim=-1)
mask1 = torch.nn.functional.one_hot(indices1_s, num_classes=logits.shape[-1])
indices_mask = mask1.sum(dim=1) * logits.shape[-1] - 1
indices1_s = torch.min(indices1_s, indices_mask)
gates1_s = (probs * mask1).sum(dim=1)
sorted_indices = indices1_s.sort()[1]
original_indices = sorted_indices.sort()[1]
exp_count = torch.bincount(indices1_s, minlength=logits.shape[-1]).long()
exp_count_cumsum = exp_count.cumsum(dim=0)
return sorted_indices, original_indices, exp_count_cumsum, gates1_s
def _reference_impl(hidden_states: torch.Tensor, gate_weight: torch.Tensor, mlp_1_w: torch.Tensor,
mlp_2_w: torch.Tensor, mlp_1_b: torch.Tensor, mlp_2_b: torch.Tensor,
act_fn: ActivationType) -> torch.Tensor:
"""
Reference implementation of the MoE module.
"""
act_fn_dict = {
ActivationType.GELU: torch.nn.functional.gelu,
ActivationType.RELU: torch.nn.functional.relu,
ActivationType.SILU: torch.nn.functional.silu,
ActivationType.IDENTITY: lambda x: x,
}
logits = torch.matmul(hidden_states, gate_weight.t())
sorted_indices, original_indices, exp_count_cumsum, gate_scales = _gating_reference(logits)
moe_input = hidden_states[sorted_indices]
output_unordered = torch.empty_like(hidden_states)
for expert_idx in range(mlp_1_w.shape[0]):
min_bound = 0 if expert_idx == 0 else exp_count_cumsum[expert_idx - 1]
max_bound = exp_count_cumsum[expert_idx]
input_slice = moe_input[min_bound:max_bound]
intermediate = torch.nn.functional.linear(input_slice, mlp_1_w[expert_idx], mlp_1_b[expert_idx])
intermediate = act_fn_dict[act_fn](intermediate)
output_slice = torch.nn.functional.linear(intermediate, mlp_2_w[expert_idx], mlp_2_b[expert_idx])
output_unordered[min_bound:max_bound] = output_slice
output = output_unordered[original_indices]
output.mul_(gate_scales.unsqueeze(-1)).reshape(hidden_states.shape)
return output
def _cutlass_moe_testing_helper(tokens: int,
in_channels: int,
intermediate_dim: int,
experts: int,
dtype: int,
activation_type: ActivationType = ActivationType.GELU,
use_bias: bool = True,
iters: int = 1) -> None:
config = DSMoEConfig(max_tokens=4096,
model_dim=in_channels,
intermediate_features=intermediate_dim,
n_experts=experts,
activation=activation_type,
input_dtype=dtype,
output_dtype=dtype)
implementation_config = {"weight_dtype": DtypeEnum(dtype)}
bundle = ConfigBundle(name='cutlass_multi_gemm_moe', config=config, implementation_config=implementation_config)
moe_module = DSMoERegistry.instantiate_config(bundle)
batch = build_simple_batch([tokens])
# Parameters
gate_weight = torch.randn(
(experts, in_channels), dtype=dtype.value, device=get_accelerator().current_device()) * .1
mlp_1_w = torch.randn(
(experts, intermediate_dim, in_channels), dtype=dtype.value, device=get_accelerator().current_device()) * .1
mlp_2_w = torch.randn(
(experts, in_channels, intermediate_dim), dtype=dtype.value, device=get_accelerator().current_device()) * .1
if use_bias:
mlp_1_b = torch.randn(
(experts, intermediate_dim), dtype=dtype.value, device=get_accelerator().current_device()) * .1
mlp_2_b = torch.randn(
(experts, in_channels), dtype=dtype.value, device=get_accelerator().current_device()) * .1
else:
mlp_1_b = None
mlp_2_b = None
gate_ds = moe_module.transform_gate_param(gate_weight)
mlp_1_w_ds = moe_module.transform_moe_mlp_1_param(mlp_1_w)
mlp_1_b_ds = moe_module.transform_moe_mlp_1_param(mlp_1_b)
mlp_2_w_ds = moe_module.transform_moe_mlp_2_param(mlp_2_w)
mlp_2_b_ds = moe_module.transform_moe_mlp_2_param(mlp_2_b)
for _ in range(iters):
# Input vals
hidden_states = torch.randn(
(tokens, in_channels), dtype=dtype.value, device=get_accelerator().current_device()) * .1
# Reference implementation
ref_output = _reference_impl(hidden_states, gate_weight, mlp_1_w, mlp_2_w, mlp_1_b, mlp_2_b, activation_type)
output = moe_module(hidden_states,
batch,
gate_ds,
mlp_1_w_ds,
mlp_2_w_ds,
mlp_1_b=mlp_1_b_ds,
mlp_2_b=mlp_2_b_ds)
# Increase the tolerance for larger meta ops since the error is additive
assert allclose(output, ref_output, tolerances=(1e-2, 1e-2))
get_accelerator().synchronize()
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("experts", [2, 32, 64])
def test_expert_variance(experts: int) -> None:
_cutlass_moe_testing_helper(tokens=876,
in_channels=4096,
intermediate_dim=2048,
experts=experts,
dtype=DtypeEnum.fp16,
activation_type=ActivationType.IDENTITY,
use_bias=True)
@pytest.mark.inference_v2_ops
def test_successive_inputs():
"""
The CUTLASS MoE uses persistent state (expert counts) that is assumed to be cleared
on each forward pass. This ensures that the module is clearing that metadata.
"""
_cutlass_moe_testing_helper(tokens=876,
in_channels=4096,
intermediate_dim=2048,
experts=64,
dtype=DtypeEnum.fp16,
activation_type=ActivationType.IDENTITY,
use_bias=True,
iters=10)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("dtype", get_dtypes(include_float=False))
def test_dtypes(dtype: torch.dtype) -> None:
_cutlass_moe_testing_helper(tokens=876,
in_channels=4096,
intermediate_dim=2048,
experts=64,
dtype=DtypeEnum(dtype),
activation_type=ActivationType.IDENTITY,
use_bias=True)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("activation_type", [ActivationType.GELU, ActivationType.RELU, ActivationType.SILU])
def test_activation_types(activation_type: ActivationType) -> None:
_cutlass_moe_testing_helper(tokens=876,
in_channels=4096,
intermediate_dim=2048,
experts=64,
dtype=DtypeEnum.fp16,
activation_type=activation_type,
use_bias=True)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("in_channels, out_channels", [(4096, 2048), (2048, 8192), (6144, 3072)])
def test_in_out_channels(in_channels: int, out_channels: int) -> None:
_cutlass_moe_testing_helper(tokens=876,
in_channels=in_channels,
intermediate_dim=out_channels,
experts=64,
dtype=DtypeEnum.fp16,
activation_type=ActivationType.IDENTITY,
use_bias=True)
def _mixtral_moe_baseline(hidden_states: torch.Tensor,
gate_weight: torch.Tensor,
mlp_w1: torch.Tensor,
mlp_w2: torch.Tensor,
mlp_w3: torch.Tensor,
force_float: bool = False) -> torch.Tensor:
"""
Baseline implementation for mixtral MoE module.
Based on transformers implementation: https://github.com/huggingface/transformers/blob/main/src/transformers/models/mixtral/modeling_mixtral.py
"""
output_dtype = hidden_states.dtype
if force_float:
hidden_states = hidden_states.float()
gate_weight = gate_weight.float()
mlp_w1 = mlp_w1.float()
mlp_w2 = mlp_w2.float()
mlp_w3 = mlp_w3.float()
router_logits = torch.nn.functional.linear(hidden_states, gate_weight)
routing_weights = torch.nn.functional.softmax(router_logits, dim=-1, dtype=torch.float)
routing_weights, selected_experts = routing_weights.topk(k=2, dim=-1)
routing_weights /= routing_weights.sum(dim=-1, keepdim=True)
# NOTE(cmikeh2): This is a difference implementation, ours will preserve the original scale
# as float32 and perform in-kernel fused FP16->FP32->FP16 conversion.
routing_weights = routing_weights.to(hidden_states.dtype)
final_hidden_states = torch.zeros_like(hidden_states)
expert_mask = torch.nn.functional.one_hot(selected_experts, num_classes=gate_weight.shape[0]).permute(2, 1, 0)
get_accelerator().synchronize()
for expert_idx in range(gate_weight.shape[0]):
exp_mlp_w1 = mlp_w1[expert_idx]
exp_mlp_w2 = mlp_w2[expert_idx]
exp_mlp_w3 = mlp_w3[expert_idx]
idx, top_x = torch.where(expert_mask[expert_idx])
if top_x.shape[0] == 0:
continue
top_x_list = top_x.tolist()
idx_list = idx.tolist()
current_state = hidden_states[top_x_list]
linear = torch.nn.functional.linear
intermediate = torch.nn.functional.silu(linear(current_state, exp_mlp_w1)) * linear(current_state, exp_mlp_w3)
output = linear(intermediate, exp_mlp_w2) * routing_weights[top_x_list, idx_list].unsqueeze(-1)
final_hidden_states.index_add_(0, top_x, output.to(final_hidden_states.dtype))
return final_hidden_states.to(output_dtype)
@pytest.mark.inference_v2_ops
def test_mixtral_moe_config():
experts = 8
n_top_k = 2
in_channels = 4096
intermediate_dim = 2048
dtype = DtypeEnum.bf16
# Parameters
gate_weight = torch.randn(
(experts, in_channels), dtype=dtype.value, device=get_accelerator().current_device()) * .1
mlp_w1 = torch.randn(
(experts, intermediate_dim, in_channels), dtype=dtype.value, device=get_accelerator().current_device()) * .1
mlp_w3 = torch.randn(
(experts, intermediate_dim, in_channels), dtype=dtype.value, device=get_accelerator().current_device()) * .1
mlp_w2 = torch.randn(
(experts, in_channels, intermediate_dim), dtype=dtype.value, device=get_accelerator().current_device()) * .1
n_tokens = 256
hidden_states = torch.randn(
(n_tokens, in_channels), dtype=dtype.value, device=get_accelerator().current_device()) * .1
baseline = _mixtral_moe_baseline(hidden_states, gate_weight, mlp_w1, mlp_w2, mlp_w3)
mlp_w13_fused = torch.cat([mlp_w1, mlp_w3], dim=-1).reshape(experts, 2 * intermediate_dim, in_channels)
config = DSMoEConfig(max_tokens=4096,
model_dim=in_channels,
intermediate_features=intermediate_dim,
n_experts=experts,
activation=ActivationType.SiGLU,
input_dtype=dtype,
output_dtype=dtype,
top_k=n_top_k,
normalize_scores=True)
implementation_config = {"weight_dtype": DtypeEnum(dtype)}
bundle = ConfigBundle(name='cutlass_multi_gemm_moe', config=config, implementation_config=implementation_config)
moe_module = DSMoERegistry.instantiate_config(bundle)
batch = build_simple_batch([n_tokens])
gate_ds = moe_module.transform_gate_param(gate_weight)
mlp_w1_ds = moe_module.transform_moe_mlp_1_param(mlp_w13_fused)
mlp_w2_ds = moe_module.transform_moe_mlp_2_param(mlp_w2)
output = moe_module(hidden_states, batch, gate_ds, mlp_w1_ds, mlp_w2_ds)
# NOTE(cmikeh2): These are higher than the other tests for reasons that aren't quite
# clear to me. My best guess is that the SiGLU activation is causing larger numerical
# divergence. The thresholds chosen here is based on the observed error between the
# float and bfloat16 reference implementations.
assert allclose(output, baseline.to(dtype.value), tolerances=(5e-2, 5e-2))
@@ -0,0 +1,58 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.modules import ConfigBundle
from deepspeed.inference.v2.modules.configs import DSNormConfig
from deepspeed.inference.v2.modules.interfaces import DSPostNormRegistry
from ...v2.inference_test_utils import get_dtypes, allclose
def reference_implementation(residual: torch.Tensor, hidden_states: torch.Tensor, gamma: torch.Tensor,
beta: torch.Tensor, epsilon: float) -> torch.Tensor:
residual_f = residual.to(torch.float32)
hidden_states_f = hidden_states.to(torch.float32)
gamma_f = gamma.to(torch.float32)
beta_f = beta.to(torch.float32)
return torch.nn.functional.layer_norm(residual_f + hidden_states_f, (hidden_states_f.size(-1), ),
weight=gamma_f,
bias=beta_f,
eps=epsilon).to(hidden_states.dtype)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("tokens, channels", [(1, 2048), (37, 8192), (1280, 768), (2048, 5120)])
@pytest.mark.parametrize("dtype", get_dtypes())
def test_cuda_post_ln_module(tokens: int, channels: int, dtype: torch.dtype) -> None:
config = DSNormConfig(max_tokens=2048,
type="layer_norm",
channels=channels,
residual_dtype=dtype,
input_dtype=dtype,
output_dtype=dtype,
eps=1e-5)
bundle = ConfigBundle(name='cuda_post_ln', config=config)
# Input vals
hidden_states = torch.randn((tokens, channels), dtype=dtype, device=get_accelerator().current_device_name())
residual = torch.randn((tokens, channels), dtype=dtype, device=get_accelerator().current_device_name())
gamma = torch.randn((channels), dtype=torch.float32, device=get_accelerator().current_device_name())
beta = torch.rand((channels), dtype=torch.float32, device=get_accelerator().current_device_name())
epsilon = 1e-5
# Reference output
ref_output = reference_implementation(residual, hidden_states, gamma, beta, epsilon)
# New output
post_ln_module = DSPostNormRegistry.instantiate_config(bundle)
gamma = post_ln_module.transform_param(gamma)
beta = post_ln_module.transform_param(beta)
ds_output, _ = post_ln_module(residual, hidden_states, gamma, beta)
# Check
assert allclose(ds_output, ref_output)
@@ -0,0 +1,88 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional, Tuple
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.modules import ConfigBundle
from deepspeed.inference.v2.modules.configs import DSNormConfig
from deepspeed.inference.v2.modules.interfaces import DSPreNormRegistry
from ...v2.inference_test_utils import get_dtypes, allclose
def reference_implementation(residual: torch.Tensor, hidden_states: Optional[torch.Tensor], gamma: torch.Tensor,
epsilon: float) -> Tuple[torch.Tensor, torch.Tensor]:
dtype = residual.dtype
if hidden_states is not None:
hidden_states = hidden_states
residual = residual + hidden_states
rms_vals = residual.to(torch.float32)
variance = rms_vals.pow(2).mean(-1, keepdim=True)
rms_vals = rms_vals * torch.rsqrt(variance + epsilon)
if gamma.dtype in [torch.float16, torch.bfloat16]:
rms_vals = rms_vals.to(gamma.dtype)
hidden_states = gamma * rms_vals
return residual.to(dtype), hidden_states.to(dtype)
def _pre_rms_test_helper(n_tokens: int, n_channels: int, dtype: torch.dtype, res_add: bool = False):
config = DSNormConfig(max_tokens=2048,
type="rms_norm",
channels=n_channels,
residual_dtype=dtype,
input_dtype=dtype,
output_dtype=dtype,
eps=1e-5)
bundle = ConfigBundle(name='cuda_pre_rms', config=config)
# Input vals
if res_add:
hidden_states = torch.randn((n_tokens, n_channels),
dtype=dtype,
device=get_accelerator().current_device_name())
else:
hidden_states = None
residual = torch.randn((n_tokens, n_channels), dtype=dtype, device=get_accelerator().current_device_name())
gamma = torch.randn((n_channels), dtype=torch.float32, device=get_accelerator().current_device_name())
epsilon = 1e-5
# Reference output
ref_residual, ref_output = reference_implementation(residual, hidden_states, gamma, epsilon)
# New output
pre_ln_module = DSPreNormRegistry.instantiate_config(bundle)
gamma = pre_ln_module.transform_param(gamma)
ds_residual, ds_output = pre_ln_module(residual, hidden_states, gamma)
# Check
assert allclose(ds_residual, ref_residual)
assert allclose(ds_output, ref_output)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("tokens, channels", [(1, 2048), (37, 8192), (1280, 768), (2048, 5120)])
def test_token_channels(tokens: int, channels: int) -> None:
_pre_rms_test_helper(tokens, channels, torch.float16)
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("dtype", get_dtypes(include_float=False))
def test_dtype(dtype: torch.dtype) -> None:
_pre_rms_test_helper(733, 2560, dtype)
@pytest.mark.inference_v2_ops
def test_no_res_add():
_pre_rms_test_helper(733, 2560, torch.float16, res_add=False)
@@ -0,0 +1,183 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import Optional
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.inference_utils import ActivationType, DtypeEnum, is_gated
from deepspeed.inference.v2.modules import ConfigBundle
from deepspeed.inference.v2.modules.configs import DSLinearConfig
from deepspeed.inference.v2.modules.interfaces import DSLinearRegistry
from ...v2.inference_test_utils import allclose
def reference_implementation(hidden_states: torch.Tensor, weight: torch.Tensor, bias: Optional[torch.Tensor],
act_type: ActivationType) -> torch.Tensor:
dtype = hidden_states.dtype
out_states = torch.nn.functional.linear(hidden_states, weight, bias)
out_states.float()
if is_gated(act_type):
act_func_map = {
ActivationType.ReGLU: torch.nn.functional.relu,
ActivationType.GEGLU: lambda x: torch.nn.functional.gelu(x, approximate="tanh"),
ActivationType.SiGLU: torch.nn.functional.silu,
}
act_act = out_states[..., ::2]
act_linear = out_states[..., 1::2]
act_act = act_func_map[act_type](act_act)
out_states = act_act * act_linear
else:
act_func_map = {
ActivationType.RELU: torch.nn.functional.relu,
ActivationType.GELU: torch.nn.functional.gelu,
ActivationType.SILU: torch.nn.functional.silu,
ActivationType.IDENTITY: lambda x: x,
}
out_states = act_func_map[act_type](out_states)
return out_states.to(dtype)
def _fp6_quant_dequant_weights(weight: torch.Tensor) -> torch.Tensor:
from deepspeed.inference.v2.modules.implementations.linear.quantized_linear import fp_quantize
weight_quantized_fake_fp6, scales = fp_quantize(weight, num_bits=6, exp_bits=3)
return weight_quantized_fake_fp6 * scales
def quant_dequant_implementation(hidden_states: torch.Tensor, weight: torch.Tensor, bias: Optional[torch.Tensor],
act_type: ActivationType) -> torch.Tensor:
dtype = hidden_states.dtype
weight_dequantized = _fp6_quant_dequant_weights(weight)
out_states = torch.nn.functional.linear(hidden_states, weight_dequantized, bias)
out_states.float()
if is_gated(act_type):
act_func_map = {
ActivationType.ReGLU: torch.nn.functional.relu,
ActivationType.GEGLU: lambda x: torch.nn.functional.gelu(x, approximate="tanh"),
ActivationType.SiGLU: torch.nn.functional.silu,
}
act_act = out_states[..., ::2]
act_linear = out_states[..., 1::2]
act_act = act_func_map[act_type](act_act)
out_states = act_act * act_linear
else:
act_func_map = {
ActivationType.RELU: torch.nn.functional.relu,
ActivationType.GELU: torch.nn.functional.gelu,
ActivationType.SILU: torch.nn.functional.silu,
ActivationType.IDENTITY: lambda x: x,
}
out_states = act_func_map[act_type](out_states)
return out_states.to(dtype)
def _fp6_quantized_linear_helper(tokens: int,
in_channels: int,
out_channels: int,
dtype: DtypeEnum,
act_fn: ActivationType,
use_bias: bool = True,
expect_failure: bool = False) -> None:
# The current FP6 kernel only supports NVIDIA Ampere GPUs.
if not 'cuda' in get_accelerator().current_device_name():
return
major, _ = torch.cuda.get_device_capability() #ignore-cuda
if major != 8:
return
# Input vals
hidden_states = torch.randn(
(tokens, in_channels), dtype=dtype.value, device=get_accelerator().current_device_name()) * .01
weight_out_channels = 2 * \
out_channels if is_gated(act_fn) else out_channels
weight = torch.randn(
(weight_out_channels, in_channels), dtype=dtype.value, device=get_accelerator().current_device_name()) * .01
if use_bias:
bias = torch.randn(
(weight_out_channels), dtype=dtype.value, device=get_accelerator().current_device_name()) * .01
else:
bias = None
# quantize and dequantize output
ref_quant_dequant_output = quant_dequant_implementation(hidden_states, weight, bias, act_fn)
linear_config = DSLinearConfig(max_tokens=2048,
in_channels=in_channels,
out_channels=out_channels,
activation=act_fn,
input_dtype=dtype,
output_dtype=dtype)
bundle = ConfigBundle(name='quantized_wf6af16_linear', config=linear_config)
fp6_linear_module = DSLinearRegistry.instantiate_config(bundle)
weight_fp6 = fp6_linear_module.transform_param(weight.clone().cpu()).to(get_accelerator().current_device_name())
if expect_failure:
with pytest.raises(ValueError) as excinfo:
ds_output = fp6_linear_module(hidden_states, weight_fp6, bias)
assert "The out and in channel should be multiple of 256 and 64 respectively." in str(excinfo.value)
else:
ds_output = fp6_linear_module(hidden_states, weight_fp6, bias)
# The current FP6 kernel uses FP16 Tensor Core.
tolerances = (3e-2, 2e-3) # tolerances for fp16
# Check DeepSpeed implementation
assert allclose(ds_output, ref_quant_dequant_output, tolerances=tolerances)
all_acts = [
ActivationType.RELU,
ActivationType.GELU,
ActivationType.SILU,
ActivationType.GEGLU,
ActivationType.ReGLU,
ActivationType.SiGLU,
]
all_tokens = [37]
all_in_out_channels = [
(4096, 4096),
]
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("tokens", all_tokens)
@pytest.mark.parametrize("in_channels, out_channels", all_in_out_channels)
@pytest.mark.parametrize("act_fn", all_acts)
@pytest.mark.parametrize("use_bias", [True, False])
def test_fp6_quantized_linear_act_fn(tokens: int, in_channels: int, out_channels: int, act_fn: ActivationType,
use_bias: bool) -> None:
_fp6_quantized_linear_helper(tokens=tokens,
in_channels=in_channels,
out_channels=out_channels,
dtype=DtypeEnum.fp16,
act_fn=act_fn,
use_bias=use_bias)
# Other shapes, not supported by FP6 kernels. Will raise ValueError.
@pytest.mark.inference_v2_ops
@pytest.mark.parametrize("tokens", all_tokens)
@pytest.mark.parametrize("in_channels, out_channels", [(4608, 1728)])
@pytest.mark.parametrize("act_fn", all_acts)
@pytest.mark.parametrize("use_bias", [True, False])
def test_fp6_quantized_linear_act_fn_fail(tokens: int, in_channels: int, out_channels: int, act_fn: ActivationType,
use_bias: bool) -> None:
_fp6_quantized_linear_helper(tokens=tokens,
in_channels=in_channels,
out_channels=out_channels,
dtype=DtypeEnum.fp16,
act_fn=act_fn,
use_bias=use_bias,
expect_failure=True)
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
@@ -0,0 +1,166 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import random
from typing import List
import pytest
import torch
from deepspeed.inference.v2.ragged.blocked_allocator import BlockedAllocator
@pytest.mark.inference_v2
@pytest.mark.parametrize('bad_size', [0, -1])
def test_bad_initialization(bad_size: int) -> None:
with pytest.raises(ValueError):
BlockedAllocator(bad_size)
@pytest.mark.inference_v2
def test_allocation() -> None:
allocator = BlockedAllocator(16)
a1 = allocator.allocate(4)
assert a1.numel() == 4
assert allocator.free_blocks == 12
a2_allocs = []
for i in range(3):
a2_allocs.append(allocator.allocate(2))
assert allocator.free_blocks == 12 - (i + 1) * 2
a3 = allocator.allocate(6)
assert a3.numel() == 6
assert allocator.free_blocks == 0
# Test that we can't allocate more blocks than we have.
with pytest.raises(ValueError):
allocator.allocate(1)
all_vals = torch.cat([a1, *a2_allocs, a3], dim=0)
unique_vals = torch.unique(all_vals, sorted=False)
assert unique_vals.numel() == all_vals.numel()
@pytest.mark.inference_v2
def test_too_large_allocation():
allocator = BlockedAllocator(16)
with pytest.raises(ValueError):
allocator.allocate(17)
@pytest.mark.inference_v2
def test_deallocation() -> None:
allocator = BlockedAllocator(16)
# Allocate
all_blocks = allocator.allocate(16)
assert allocator.free_blocks == 0
# Deallocate all blocks
allocator.free(all_blocks)
assert allocator.free_blocks == 16
# Get all the blocks again
all_blocks = allocator.allocate(16)
# Deallocate in chunks
c1 = all_blocks[:4]
c2 = all_blocks[4:8]
allocator.free(c1)
assert allocator.free_blocks == 4
allocator.free(c2)
assert allocator.free_blocks == 8
with pytest.raises(ValueError):
allocator.free(c1)
with pytest.raises(ValueError):
allocator.free(c2)
@pytest.mark.inference_v2
@pytest.mark.parametrize('index', [-1, 2])
def test_invalid_dealloc_indices(index: int):
allocator = BlockedAllocator(1)
with pytest.raises(ValueError):
allocator.free(torch.tensor([index]))
@pytest.mark.inference_v2
@pytest.mark.parametrize('index', [-1, 2])
def test_invalid_alloc_indices(index: int):
allocator = BlockedAllocator(1)
allocator.allocate(1)
to_free = [0, index]
with pytest.raises(ValueError):
allocator.free(torch.tensor(to_free))
# Block 0 should not be freed if passed with an invalid index.
assert allocator.free_blocks == 0
allocator.free(torch.tensor([0]))
assert allocator.free_blocks == 1
@pytest.mark.inference_v2
@pytest.mark.parametrize('test_iters', [8192])
def test_long_running_allocation(test_iters: int) -> None:
"""
Evaluate the stability of the allocator over a longer sequence of allocations/deallocations.
"""
TOTAL_BLOCKS = 128
allocator = BlockedAllocator(TOTAL_BLOCKS)
def validate_uniqueness(all_blocks: List[torch.Tensor]) -> None:
all_vals = torch.cat(all_blocks, dim=0)
assert all_vals.numel() <= TOTAL_BLOCKS
unique_vals = torch.unique(all_vals, sorted=False)
assert unique_vals.numel() == all_vals.numel()
all_allocs: List[torch.Tensor] = []
num_allocs = 0
num_frees = 0
num_blocks_allocated = 0
num_blocks_freed = 0
for _ in range(test_iters):
decision = random.randint(0, 1)
if decision == 0:
blocks_to_allocate = random.randint(1, 24)
if blocks_to_allocate > allocator.free_blocks:
with pytest.raises(ValueError):
allocator.allocate(blocks_to_allocate)
else:
all_allocs.append(allocator.allocate(blocks_to_allocate))
num_allocs += 1
num_blocks_allocated += blocks_to_allocate
else:
if len(all_allocs) > 0:
idx = random.randint(0, len(all_allocs) - 1)
allocator.free(all_allocs[idx])
num_frees += 1
num_blocks_freed += all_allocs[idx].numel()
del all_allocs[idx]
if len(all_allocs) > 0:
validate_uniqueness(all_allocs)
assert num_allocs == num_frees + len(all_allocs)
assert num_blocks_allocated == num_blocks_freed + (TOTAL_BLOCKS - allocator.free_blocks)
@@ -0,0 +1,58 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
import pytest
from pydantic import ValidationError
from deepspeed.inference.v2.ragged import DSStateManagerConfig
@pytest.mark.inference_v2
def test_negative_max_tracked_sequences() -> None:
with pytest.raises(ValidationError):
DSStateManagerConfig(max_tracked_sequences=-1)
@pytest.mark.inference_v2
def test_zero_max_tracked_sequences() -> None:
with pytest.raises(ValidationError):
DSStateManagerConfig(max_tracked_sequences=0)
@pytest.mark.inference_v2
def test_negative_max_ragged_batch_size() -> None:
with pytest.raises(ValidationError):
DSStateManagerConfig(max_ragged_batch_size=-1)
@pytest.mark.inference_v2
def test_zero_max_ragged_batch_size() -> None:
with pytest.raises(ValidationError):
DSStateManagerConfig(max_ragged_batch_size=0)
@pytest.mark.inference_v2
def test_negative_max_ragged_sequence_count() -> None:
with pytest.raises(ValidationError):
DSStateManagerConfig(max_ragged_sequence_count=-1)
@pytest.mark.inference_v2
def test_zero_max_ragged_sequence_count() -> None:
with pytest.raises(ValidationError):
DSStateManagerConfig(max_ragged_sequence_count=0)
@pytest.mark.inference_v2
def test_too_small_max_ragged_batch_size() -> None:
with pytest.raises(ValidationError):
DSStateManagerConfig(max_ragged_batch_size=512, max_ragged_sequence_count=1024)
@pytest.mark.inference_v2
def test_too_small_max_tracked_sequences() -> None:
with pytest.raises(ValidationError):
DSStateManagerConfig(max_tracked_sequences=512, max_ragged_sequence_count=1024)
@@ -0,0 +1,112 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0
# DeepSpeed Team
from typing import List
import pytest
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.inference.v2.ragged import (
PlaceholderSequenceDescriptor,
RaggedBatchWrapper,
DSStateManagerConfig,
)
@pytest.mark.inference_v2
@pytest.mark.parametrize('max_ragged_sequence_count, max_ragged_batch_size', [(128, 512), (128, 1024)])
def test_wrapper_initialization(max_ragged_sequence_count: int, max_ragged_batch_size: int) -> None:
config = DSStateManagerConfig(max_tracked_sequences=max_ragged_sequence_count,
max_ragged_batch_size=max_ragged_batch_size,
max_ragged_sequence_count=max_ragged_sequence_count)
batch = RaggedBatchWrapper(config)
assert batch.current_tokens == 0
assert batch.current_sequences == 0
@pytest.mark.inference_v2
@pytest.mark.parametrize('seq_len', [1, 37, 128, 512])
def test_single_sequence_batch(seq_len: int) -> None:
"""
Test we successfully construct single sequence batches and the on device metadata is accurate.
"""
config = DSStateManagerConfig()
batch = RaggedBatchWrapper(config)
batch.clear()
assert batch.current_tokens == 0
assert batch.current_sequences == 0
seq_desc = PlaceholderSequenceDescriptor()
tokens = torch.randint(0, 100, (seq_len, ))
batch.insert_sequence(seq_desc, tokens)
batch.finalize()
assert batch.current_tokens == seq_len
assert batch.current_sequences == 1
assert torch.equal(batch.input_ids(), tokens.to(get_accelerator().current_device()))
assert torch.equal(batch.tokens_to_seq(), torch.zeros_like(tokens, device=get_accelerator().current_device()))
assert torch.equal(batch.batch_metadata_buffer(),
torch.tensor([seq_len, 1], device=get_accelerator().current_device()))
batch.clear()
assert batch.current_tokens == 0
assert batch.current_sequences == 0
@pytest.mark.inference_v2
@pytest.mark.parametrize('seq_lens', [[128, 128], [1, 32, 243], [64, 1, 1, 1, 1, 393, 27, 2]])
def test_multi_sequence_batch(seq_lens: List[int]) -> None:
"""
Test sequentially adding new tokens to a batch and validate device data structures hold
the appropriate data.
"""
config = DSStateManagerConfig()
batch = RaggedBatchWrapper(config)
batch.clear()
assert batch.current_tokens == 0
assert batch.current_sequences == 0
all_toks = [torch.randint(0, 100, (seq_len, )) for seq_len in seq_lens]
for i, toks in enumerate(all_toks):
seq_desc = PlaceholderSequenceDescriptor()
batch.insert_sequence(seq_desc, toks)
assert batch.current_tokens == sum(seq_lens[:i + 1])
assert batch.current_sequences == i + 1
batch.finalize()
assert batch.current_tokens == sum(seq_lens)
assert batch.current_sequences == len(seq_lens)
assert torch.equal(batch.input_ids(), torch.cat(all_toks, dim=0).to(get_accelerator().current_device()))
assert torch.equal(
batch.tokens_to_seq(),
torch.cat([torch.full((seq_len, ), i, dtype=torch.int32) for i, seq_len in enumerate(seq_lens)],
dim=0).to(get_accelerator().current_device()))
for i, seq_len in enumerate(seq_lens):
assert batch.inflight_seq_descriptors()[i][0] == sum(seq_lens[:i])
assert batch.inflight_seq_descriptors()[i][1] == seq_len
assert batch.inflight_seq_descriptors()[i][2] == 0
assert torch.equal(batch.batch_metadata_buffer(),
torch.tensor([sum(seq_lens), len(seq_lens)], device=get_accelerator().current_device()))
batch.clear()
assert batch.current_tokens == 0
assert batch.current_sequences == 0