chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
+79
@@ -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)
|
||||
Reference in New Issue
Block a user