chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
file(
|
||||
GLOB TEST_OPS
|
||||
RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"test_*.py")
|
||||
string(REPLACE ".py" "" TEST_OPS "${TEST_OPS}")
|
||||
|
||||
foreach(TEST_OP ${TEST_OPS})
|
||||
if(${TEST_OP} STREQUAL "test_strategy_conversion")
|
||||
set(WORKFLOW_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_OP}.py)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${WORKFLOW_SCRIPT} --list_tests
|
||||
OUTPUT_VARIABLE TEST_CASE_LIST
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
string(REPLACE "\n" ";" TEST_CASE_LIST "${TEST_CASE_LIST}")
|
||||
|
||||
foreach(TEST_CASE ${TEST_CASE_LIST})
|
||||
string(REPLACE "__main__.TestStrategyConversion.test_" "" TEST_CASE_ALIAS
|
||||
${TEST_CASE})
|
||||
|
||||
add_test(NAME ${TEST_OP}.${TEST_CASE_ALIAS}
|
||||
COMMAND ${PYTHON_EXECUTABLE} -m unittest ${TEST_CASE})
|
||||
endforeach()
|
||||
else()
|
||||
py_test_modules(${TEST_OP} MODULES ${TEST_OP})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(GPU_ONLY_DISTRIBUTED_TESTS
|
||||
test_sharded_state_dict test_strategy_conversion
|
||||
test_load_state_dict_transpose test_model_full_param)
|
||||
|
||||
if(TEST test_sharded_state_dict)
|
||||
set_tests_properties(test_sharded_state_dict PROPERTIES TIMEOUT 480)
|
||||
endif()
|
||||
|
||||
if(TEST test_model_full_param)
|
||||
set_tests_properties(test_model_full_param PROPERTIES TIMEOUT 480)
|
||||
endif()
|
||||
|
||||
if(NOT (WITH_DISTRIBUTE AND WITH_GPU))
|
||||
get_property(
|
||||
ALL_TESTS
|
||||
DIRECTORY
|
||||
PROPERTY TESTS)
|
||||
foreach(CURRENT_TEST_NAME ${ALL_TESTS})
|
||||
foreach(SUITE_NAME ${GPU_ONLY_DISTRIBUTED_TESTS})
|
||||
if("${CURRENT_TEST_NAME}" STREQUAL "${SUITE_NAME}"
|
||||
OR "${CURRENT_TEST_NAME}" MATCHES "^${SUITE_NAME}\\.")
|
||||
message(STATUS "Disabling GPU/Dist test: ${CURRENT_TEST_NAME}")
|
||||
set_tests_properties("${CURRENT_TEST_NAME}" PROPERTIES DISABLED TRUE)
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
endif()
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
@@ -0,0 +1,83 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import numpy as np
|
||||
|
||||
import paddle
|
||||
import paddle.distributed as dist
|
||||
from paddle.distributed import fleet
|
||||
from paddle.distributed.fleet.layers.mpu import (
|
||||
ColumnParallelLinear,
|
||||
)
|
||||
from paddle.nn import Layer
|
||||
|
||||
|
||||
class SimpleMLP(Layer):
|
||||
def __init__(self, in_features=1024, out_features=1024):
|
||||
super().__init__()
|
||||
self.linear = ColumnParallelLinear(
|
||||
in_features, out_features, has_bias=False
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.linear(x)
|
||||
return x
|
||||
|
||||
|
||||
class TestLoadStateDictCastLogic:
|
||||
def __init__(self):
|
||||
self.aoa_config = {"aoa_statements": [os.getenv("aoa_statements")]}
|
||||
self.ckpt_path = tempfile.TemporaryDirectory().name
|
||||
self.in_features = 1024
|
||||
self.out_features = 2048
|
||||
|
||||
def run_test(self):
|
||||
self.run_save_state_dict()
|
||||
model = SimpleMLP()
|
||||
model_cast = SimpleMLP()
|
||||
model_cast = paddle.amp.decorate(
|
||||
models=model_cast,
|
||||
optimizers=None,
|
||||
level="O2",
|
||||
dtype="float16",
|
||||
)
|
||||
sharded_state_dict = model.sharded_state_dict()
|
||||
sharded_state_dict_trans = model_cast.sharded_state_dict()
|
||||
dist.load_state_dict(sharded_state_dict, self.ckpt_path)
|
||||
dist.load_state_dict(
|
||||
sharded_state_dict_trans, self.ckpt_path, aoa_config=self.aoa_config
|
||||
)
|
||||
state_dict_1_after_load = model.state_dict()
|
||||
state_dict_2_after_load = model_cast.state_dict()
|
||||
|
||||
np.testing.assert_array_equal(
|
||||
state_dict_1_after_load['linear.weight'].astype("float16"),
|
||||
state_dict_2_after_load['linear.weight'],
|
||||
)
|
||||
|
||||
def setup_dist_env(self):
|
||||
fleet.init(is_collective=True)
|
||||
|
||||
def run_save_state_dict(self):
|
||||
self.setup_dist_env()
|
||||
model = SimpleMLP()
|
||||
sharded_state_dict = model.sharded_state_dict()
|
||||
dist.save_state_dict(sharded_state_dict, self.ckpt_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
TestLoadStateDictCastLogic().run_test()
|
||||
@@ -0,0 +1,112 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import numpy as np
|
||||
|
||||
import paddle
|
||||
import paddle.distributed as dist
|
||||
from paddle.distributed import fleet
|
||||
from paddle.distributed.fleet.layers.mpu import (
|
||||
ColumnParallelLinear,
|
||||
)
|
||||
from paddle.distributed.flex_checkpoint.dcp.sharded_weight import (
|
||||
build_sharded_state_dict,
|
||||
)
|
||||
from paddle.nn import Layer
|
||||
|
||||
|
||||
class ColumnParallelLinearTransWeight(ColumnParallelLinear):
|
||||
def sharded_state_dict(
|
||||
self,
|
||||
structured_name_prefix: str = "",
|
||||
):
|
||||
state_dict = self.state_dict(structured_name_prefix="")
|
||||
for k, v in state_dict.items():
|
||||
if "weight" in k:
|
||||
state_dict[k] = v.T
|
||||
return build_sharded_state_dict(
|
||||
state_dict, {"weight": 0}, structured_name_prefix
|
||||
)
|
||||
|
||||
|
||||
class SimpleMLP(Layer):
|
||||
def __init__(self, in_features=1024, out_features=1024):
|
||||
super().__init__()
|
||||
self.linear = ColumnParallelLinear(
|
||||
in_features, out_features, has_bias=False
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.linear(x)
|
||||
return x
|
||||
|
||||
|
||||
class SimpleMLPTransCastWeight(Layer):
|
||||
def __init__(self, in_features=1024, out_features=1024):
|
||||
super().__init__()
|
||||
self.linear = ColumnParallelLinearTransWeight(
|
||||
in_features, out_features, has_bias=False
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.linear(x)
|
||||
return x
|
||||
|
||||
|
||||
class TestLoadStateDictTransposeCastLogic:
|
||||
def __init__(self):
|
||||
self.aoa_config = {"aoa_statements": [os.getenv("aoa_statements")]}
|
||||
self.ckpt_path = tempfile.TemporaryDirectory().name
|
||||
self.in_features = 1024
|
||||
self.out_features = 2048
|
||||
|
||||
def run_test(self):
|
||||
self.run_save_state_dict()
|
||||
model = SimpleMLP()
|
||||
model_trans_cast = SimpleMLPTransCastWeight()
|
||||
model_trans_cast = paddle.amp.decorate(
|
||||
models=model_trans_cast,
|
||||
optimizers=None,
|
||||
level="O2",
|
||||
dtype="float16",
|
||||
)
|
||||
sharded_state_dict = model.sharded_state_dict()
|
||||
sharded_state_dict_trans = model_trans_cast.sharded_state_dict()
|
||||
dist.load_state_dict(sharded_state_dict, self.ckpt_path)
|
||||
dist.load_state_dict(
|
||||
sharded_state_dict_trans, self.ckpt_path, aoa_config=self.aoa_config
|
||||
)
|
||||
state_dict_1_after_load = model.state_dict()
|
||||
state_dict_2_after_load = model_trans_cast.state_dict()
|
||||
|
||||
np.testing.assert_array_equal(
|
||||
state_dict_1_after_load['linear.weight'].astype("float16"),
|
||||
state_dict_2_after_load['linear.weight'],
|
||||
)
|
||||
|
||||
def setup_dist_env(self):
|
||||
fleet.init(is_collective=True)
|
||||
|
||||
def run_save_state_dict(self):
|
||||
self.setup_dist_env()
|
||||
model = SimpleMLP()
|
||||
sharded_state_dict = model.sharded_state_dict()
|
||||
dist.save_state_dict(sharded_state_dict, self.ckpt_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
TestLoadStateDictTransposeCastLogic().run_test()
|
||||
@@ -0,0 +1,113 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import numpy as np
|
||||
|
||||
import paddle.distributed as dist
|
||||
from paddle.distributed import fleet
|
||||
from paddle.distributed.fleet.layers.mpu import (
|
||||
ColumnParallelLinear,
|
||||
)
|
||||
from paddle.distributed.flex_checkpoint.dcp.sharded_weight import (
|
||||
build_sharded_state_dict,
|
||||
)
|
||||
from paddle.nn import Layer
|
||||
|
||||
|
||||
class ColumnParallelLinearTransWeight(ColumnParallelLinear):
|
||||
def sharded_state_dict(
|
||||
self,
|
||||
structured_name_prefix: str = "",
|
||||
):
|
||||
state_dict = self.state_dict(structured_name_prefix="")
|
||||
for k, v in state_dict.items():
|
||||
if "weight" in k:
|
||||
state_dict[k] = v.T
|
||||
return build_sharded_state_dict(
|
||||
state_dict, {"weight": 0, "bias": 0}, structured_name_prefix
|
||||
)
|
||||
|
||||
|
||||
class SimpleMLP(Layer):
|
||||
def __init__(self, in_features=1024, out_features=1024):
|
||||
super().__init__()
|
||||
self.linear = ColumnParallelLinear(
|
||||
in_features, out_features, has_bias=True
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.linear(x)
|
||||
return x
|
||||
|
||||
|
||||
class SimpleMLPTransWeight(Layer):
|
||||
def __init__(self, in_features=1024, out_features=1024):
|
||||
super().__init__()
|
||||
self.linear = ColumnParallelLinearTransWeight(
|
||||
in_features, out_features, has_bias=True
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.linear(x)
|
||||
return x
|
||||
|
||||
|
||||
class TestLoadStateDictTransposeLogic:
|
||||
def __init__(self):
|
||||
self.aoa_config = {"aoa_statements": [os.getenv("aoa_statements")]}
|
||||
self.ckpt_path = tempfile.TemporaryDirectory().name
|
||||
self.in_features = 1024
|
||||
self.out_features = 2048
|
||||
|
||||
def run_test(self):
|
||||
self.run_save_state_dict()
|
||||
model = SimpleMLP()
|
||||
model_trans = SimpleMLPTransWeight()
|
||||
sharded_state_dict = model.sharded_state_dict()
|
||||
sharded_state_dict_trans = model_trans.sharded_state_dict()
|
||||
dist.load_state_dict(sharded_state_dict, self.ckpt_path)
|
||||
dist.load_state_dict(
|
||||
sharded_state_dict_trans, self.ckpt_path, aoa_config=self.aoa_config
|
||||
)
|
||||
state_dict_1_after_load = model.state_dict()
|
||||
state_dict_2_after_load = model_trans.state_dict()
|
||||
|
||||
np.testing.assert_array_equal(
|
||||
state_dict_1_after_load['linear.weight'],
|
||||
state_dict_2_after_load['linear.weight'],
|
||||
)
|
||||
|
||||
def setup_dist_env(self):
|
||||
fleet.init(is_collective=True)
|
||||
|
||||
def run_save_state_dict(self):
|
||||
self.setup_dist_env()
|
||||
model = SimpleMLP()
|
||||
sharded_state_dict = model.sharded_state_dict()
|
||||
dist.save_state_dict(sharded_state_dict, self.ckpt_path)
|
||||
|
||||
|
||||
class TestLoadStateDictTransposeLogic2(TestLoadStateDictTransposeLogic):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.in_features = 1024
|
||||
self.out_features = 1024
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
TestLoadStateDictTransposeLogic().run_test()
|
||||
TestLoadStateDictTransposeLogic2().run_test()
|
||||
@@ -0,0 +1,101 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
|
||||
import paddle.distributed as dist
|
||||
from paddle.distributed import fleet
|
||||
from paddle.distributed.fleet.layers.mpu import (
|
||||
ColumnParallelLinear,
|
||||
)
|
||||
from paddle.nn import Layer
|
||||
|
||||
|
||||
class SimpleMLP(Layer):
|
||||
def __init__(self, hidden_size=1024):
|
||||
super().__init__()
|
||||
self.linear = ColumnParallelLinear(
|
||||
hidden_size, hidden_size * 2, has_bias=True
|
||||
)
|
||||
self.linear1 = ColumnParallelLinear(
|
||||
hidden_size, hidden_size * 2, has_bias=True
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.linear(x)
|
||||
x = self.linear1(x)
|
||||
return x
|
||||
|
||||
|
||||
class TestDistCheckpoint:
|
||||
def __init__(self):
|
||||
np.random.seed(42)
|
||||
self.temp_dir = "./state_dict_merge"
|
||||
self.test_type = os.getenv("test_type")
|
||||
self.layer_type = os.getenv("layer_type")
|
||||
self.tp_degree = int(os.getenv("tp"))
|
||||
self.dp_degree = int(os.getenv("dp"))
|
||||
self.world_size = int(os.getenv("world_size"))
|
||||
self.has_bias = os.getenv("has_bias", "True").lower() == "true"
|
||||
|
||||
self.hidden_size = 32
|
||||
self.vocab_size = 1024
|
||||
|
||||
def run_layer_test(self):
|
||||
strategy = fleet.DistributedStrategy()
|
||||
strategy.hybrid_configs = {
|
||||
"dp_degree": self.dp_degree,
|
||||
"mp_degree": self.tp_degree,
|
||||
"pp_degree": 1,
|
||||
}
|
||||
fleet.init(is_collective=True, strategy=strategy)
|
||||
hcg = fleet.get_hybrid_communicate_group()
|
||||
tp_group = hcg.get_model_parallel_group()
|
||||
|
||||
model_path = os.path.join(self.temp_dir, 'model')
|
||||
single_path = os.path.join(self.temp_dir, 'single_model')
|
||||
model = SimpleMLP()
|
||||
sharded_state_dict = model.sharded_state_dict()
|
||||
state_dict = model.state_dict()
|
||||
|
||||
dist.save_state_dict(sharded_state_dict, model_path, safetensors=False)
|
||||
|
||||
dist.flex_checkpoint.dcp.load_state_dict.merge_sharded_state_dict(
|
||||
model_path,
|
||||
single_path,
|
||||
offload=True,
|
||||
safetensors=False,
|
||||
)
|
||||
|
||||
import paddle.distributed
|
||||
|
||||
paddle.distributed.barrier()
|
||||
|
||||
if paddle.distributed.get_rank() == 0:
|
||||
import safetensors
|
||||
|
||||
load_result = {}
|
||||
for i in range(1, 3):
|
||||
load_result.update(
|
||||
safetensors.paddle.load_file(
|
||||
f"{single_path}/model-0000{i}-of-00002.safetensors"
|
||||
)
|
||||
)
|
||||
assert len(load_result) == 4
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
TestDistCheckpoint().run_layer_test()
|
||||
@@ -0,0 +1,414 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
import paddle
|
||||
from paddle import nn
|
||||
from paddle.distributed import fleet
|
||||
from paddle.distributed.fleet.meta_parallel import (
|
||||
ColumnParallelLinear,
|
||||
LayerDesc,
|
||||
PipelineLayer,
|
||||
RowParallelLinear,
|
||||
SharedLayerDesc,
|
||||
VocabParallelEmbedding,
|
||||
)
|
||||
from paddle.distributed.fleet.meta_parallel.sharding.group_sharded_stage3 import (
|
||||
GroupShardedStage3,
|
||||
)
|
||||
|
||||
|
||||
class SimpleMLP(nn.Layer):
|
||||
def __init__(self, hidden_size=100, has_bias=False):
|
||||
super().__init__()
|
||||
self.embedding = VocabParallelEmbedding(24, hidden_size)
|
||||
self.linear1 = ColumnParallelLinear(
|
||||
hidden_size, hidden_size, gather_output=False, has_bias=has_bias
|
||||
)
|
||||
self.linear2 = RowParallelLinear(
|
||||
hidden_size, hidden_size, input_is_parallel=True, has_bias=has_bias
|
||||
)
|
||||
self.llm_head = self.embedding
|
||||
|
||||
def forward(self, x):
|
||||
x = self.embedding(x)
|
||||
x = self.linear1(x)
|
||||
x = self.linear2(x)
|
||||
x = paddle.matmul(x, self.llm_head.weight, transpose_y=True)
|
||||
return x
|
||||
|
||||
|
||||
class MLP(paddle.nn.Layer):
|
||||
def __init__(self, linear_size=1000, param_attr=None, bias_attr=None):
|
||||
super().__init__()
|
||||
self._linear1 = nn.Linear(linear_size, linear_size)
|
||||
self._linear2 = nn.Linear(linear_size, linear_size)
|
||||
self._linear3 = nn.Linear(linear_size, 10)
|
||||
|
||||
def forward(self, inputs):
|
||||
y = self._linear1(inputs)
|
||||
y = self._linear2(y)
|
||||
y = self._linear3(y)
|
||||
return y
|
||||
|
||||
|
||||
class SimpleMLPPipeline(PipelineLayer):
|
||||
def __init__(
|
||||
self, hcg, hidden_size=100, has_bias=False, vocab_size=24, pp_degree=2
|
||||
):
|
||||
shared_embedding = SharedLayerDesc(
|
||||
key="shared_embedding",
|
||||
layer_func=VocabParallelEmbedding,
|
||||
num_embeddings=vocab_size,
|
||||
embedding_dim=hidden_size,
|
||||
)
|
||||
|
||||
shared_head = SharedLayerDesc(
|
||||
key="shared_embedding",
|
||||
layer_func=VocabParallelEmbedding,
|
||||
num_embeddings=vocab_size,
|
||||
embedding_dim=hidden_size,
|
||||
forward_func=lambda layer, x: paddle.matmul(
|
||||
x, layer.weight, transpose_y=True
|
||||
),
|
||||
)
|
||||
|
||||
layers = [
|
||||
shared_embedding,
|
||||
LayerDesc(
|
||||
ColumnParallelLinear,
|
||||
hidden_size,
|
||||
hidden_size,
|
||||
gather_output=False,
|
||||
has_bias=has_bias,
|
||||
),
|
||||
LayerDesc(
|
||||
RowParallelLinear,
|
||||
hidden_size,
|
||||
hidden_size,
|
||||
input_is_parallel=True,
|
||||
has_bias=has_bias,
|
||||
),
|
||||
shared_head,
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
layers=layers,
|
||||
loss_fn=None,
|
||||
seg_method="uniform",
|
||||
topology=hcg._topo,
|
||||
)
|
||||
|
||||
|
||||
class TestFullParamLogic:
|
||||
def __init__(self):
|
||||
self.tp_degree = int(os.getenv("tp", "1"))
|
||||
self.dp_degree = int(os.getenv("dp", "1"))
|
||||
self.sharding_degree = int(os.getenv("sharding_degree", "1"))
|
||||
self.pp_degree = int(os.getenv("pp", "1"))
|
||||
self.world_size = int(os.getenv("world_size"))
|
||||
self.has_bias = os.getenv("has_bias", "True").lower() == "true"
|
||||
self.batch_size = 2
|
||||
self.hidden_size = 32
|
||||
self.vocab_size = 24
|
||||
self.seq_len = 2
|
||||
self.hcg = None
|
||||
|
||||
def run_test(self):
|
||||
strategy = fleet.DistributedStrategy()
|
||||
strategy.hybrid_configs = {
|
||||
"dp_degree": self.dp_degree,
|
||||
"mp_degree": self.tp_degree,
|
||||
"sharding_degree": self.sharding_degree,
|
||||
"pp_degree": 1,
|
||||
}
|
||||
fleet.init(is_collective=True, strategy=strategy)
|
||||
self.run_full_param_test()
|
||||
self.run_full_param_with_aoa_test()
|
||||
|
||||
def run_full_param_test(self):
|
||||
model = SimpleMLP(hidden_size=self.hidden_size, has_bias=self.has_bias)
|
||||
model = fleet.distributed_model(model)
|
||||
model.train()
|
||||
model_state_dict = model.state_dict()
|
||||
|
||||
for k, v in model_state_dict.items():
|
||||
ones = paddle.ones_like(v)
|
||||
paddle.assign(ones, v)
|
||||
|
||||
full_param_iter = model.full()
|
||||
full_param = dict(full_param_iter)
|
||||
|
||||
param_shape = {
|
||||
"_layers.embedding.weight": [24, 32],
|
||||
"_layers.linear1.weight": [32, 32],
|
||||
"_layers.linear1.bias": [32],
|
||||
"_layers.linear2.weight": [32, 32],
|
||||
"_layers.linear2.bias": [32],
|
||||
"_layers.llm_head.weight": [24, 32],
|
||||
}
|
||||
for name, shape in param_shape.items():
|
||||
if not self.has_bias:
|
||||
if ".bias" in name:
|
||||
continue
|
||||
assert name in full_param.keys()
|
||||
tensor = full_param[name]
|
||||
answer = paddle.ones_like(tensor)
|
||||
assert tensor._md5sum() == answer._md5sum()
|
||||
|
||||
def run_full_param_with_aoa_test(self):
|
||||
model = SimpleMLP(hidden_size=self.hidden_size, has_bias=self.has_bias)
|
||||
model = paddle.amp.decorate(
|
||||
models=model, optimizers=None, level="O2", dtype="float16"
|
||||
)
|
||||
model = fleet.distributed_model(model)
|
||||
model.train()
|
||||
model_state_dict = model.state_dict()
|
||||
|
||||
for k, v in model_state_dict.items():
|
||||
ones = paddle.ones_like(v)
|
||||
paddle.assign(ones, v)
|
||||
if k == "_layers.linear1.weight":
|
||||
zeros = paddle.zeros_like(v)
|
||||
paddle.assign(zeros, v)
|
||||
|
||||
aoa_config = {
|
||||
"aoa_statements": [
|
||||
"_layers.linear1.weight, _layers.linear2.weight -> _layers.fused_weight, axis=1"
|
||||
"_layers.embedding.weight -> _layers.embedding.weight, dtype = 'float32'"
|
||||
]
|
||||
}
|
||||
|
||||
full_param_iter = model.full(aoa_config)
|
||||
full_param = dict(full_param_iter)
|
||||
|
||||
param_shape = {
|
||||
# "_layers.linear1.weight" : [32,32],
|
||||
# "_layers.linear2.weight" : [32, 32],
|
||||
"_layers.embedding.weight": [24, 32],
|
||||
"_layers.linear1.bias": [32],
|
||||
"_layers.linear2.bias": [32],
|
||||
"_layers.llm_head.weight": [24, 32],
|
||||
"_layers.fused_weight": [32, 64],
|
||||
}
|
||||
|
||||
for name, shape in param_shape.items():
|
||||
if name == "_layers.fused_weight":
|
||||
continue
|
||||
if not self.has_bias:
|
||||
if ".bias" in name:
|
||||
continue
|
||||
assert name in full_param.keys()
|
||||
tensor = full_param[name]
|
||||
answer = paddle.ones_like(tensor)
|
||||
assert tensor._md5sum() == answer._md5sum()
|
||||
if name == "_layers.embedding.weight":
|
||||
assert tensor.dtype == paddle.float32
|
||||
assert "_layers.fused_weight" in full_param.keys()
|
||||
ones = paddle.ones([32, 32], 'float16')
|
||||
zeros = paddle.zeros([32, 32], 'float16')
|
||||
answer = paddle.concat([zeros, ones], axis=1)
|
||||
assert full_param["_layers.fused_weight"]._md5sum() == answer._md5sum()
|
||||
|
||||
|
||||
class TestFullParamHVGroupLogic(TestFullParamLogic):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def run_test(self):
|
||||
strategy = fleet.DistributedStrategy()
|
||||
strategy.hybrid_configs = {
|
||||
"dp_degree": self.dp_degree,
|
||||
"mp_degree": self.tp_degree,
|
||||
"sharding_degree": self.sharding_degree,
|
||||
"pp_degree": self.pp_degree,
|
||||
}
|
||||
fleet.init(is_collective=True, strategy=strategy)
|
||||
self.run_full_param_test()
|
||||
self.run_full_param_with_aoa_test()
|
||||
self.run_full_param_memory_growth_threshold_test()
|
||||
|
||||
def run_full_param_test(self, memory_growth_threshold=8 * (2**30)):
|
||||
hcg = fleet.get_hybrid_communicate_group()
|
||||
model = SimpleMLPPipeline(
|
||||
hcg=hcg,
|
||||
hidden_size=self.hidden_size,
|
||||
has_bias=self.has_bias,
|
||||
vocab_size=self.vocab_size,
|
||||
pp_degree=self.pp_degree,
|
||||
)
|
||||
model = fleet.distributed_model(model)
|
||||
model.train()
|
||||
|
||||
model_state_dict = model.state_dict()
|
||||
tp_group = hcg.get_model_parallel_group()
|
||||
pp_group = hcg.get_pipe_parallel_group()
|
||||
full_param_iter = model.full(
|
||||
h_group=tp_group,
|
||||
v_group=pp_group,
|
||||
memory_growth_threshold=memory_growth_threshold,
|
||||
)
|
||||
|
||||
full_param = dict(full_param_iter)
|
||||
param_shape = {
|
||||
"_layers.shared_layers.shared_embedding.weight": [24, 32],
|
||||
"_layers.1.weight": [32, 32],
|
||||
"_layers.1.bias": [32],
|
||||
"_layers.2.weight": [32, 32],
|
||||
"_layers.2.bias": [32],
|
||||
}
|
||||
param_split_axis = {
|
||||
"_layers.shared_layers.shared_embedding.weight": 0,
|
||||
"_layers.1.weight": 1,
|
||||
"_layers.1.bias": 0,
|
||||
"_layers.2.weight": 0,
|
||||
"_layers.2.bias": -1,
|
||||
}
|
||||
model_parallel_rank = hcg.get_model_parallel_rank()
|
||||
|
||||
for name, shape in param_shape.items():
|
||||
assert name in full_param.keys()
|
||||
assert tuple(full_param[name].shape) == tuple(shape)
|
||||
|
||||
for name, param in full_param.items():
|
||||
if name not in model_state_dict:
|
||||
continue
|
||||
splited_axis = param_split_axis[name]
|
||||
|
||||
if splited_axis == -1:
|
||||
splited = [param, param]
|
||||
else:
|
||||
splited = paddle.split(
|
||||
param, num_or_sections=2, axis=splited_axis
|
||||
)
|
||||
t = splited[model_parallel_rank]
|
||||
assert t._md5sum() == model_state_dict[name]._md5sum()
|
||||
|
||||
def run_full_param_with_aoa_test(self, memory_growth_threshold=8 * (2**30)):
|
||||
hcg = fleet.get_hybrid_communicate_group()
|
||||
model = SimpleMLPPipeline(
|
||||
hcg=hcg,
|
||||
hidden_size=self.hidden_size,
|
||||
has_bias=self.has_bias,
|
||||
vocab_size=self.vocab_size,
|
||||
pp_degree=self.pp_degree,
|
||||
)
|
||||
model = fleet.distributed_model(model)
|
||||
model.train()
|
||||
|
||||
model_state_dict = model.state_dict()
|
||||
|
||||
tp_group = hcg.get_model_parallel_group()
|
||||
pp_group = hcg.get_pipe_parallel_group()
|
||||
|
||||
aoa_config = {
|
||||
"aoa_statements": [
|
||||
"_layers.1.weight, _layers.2.weight -> _layers.fused_weight, axis=1",
|
||||
"_layers.2.bias -> _",
|
||||
]
|
||||
}
|
||||
|
||||
full_param_iter = model.full(
|
||||
aoa_config,
|
||||
h_group=tp_group,
|
||||
v_group=pp_group,
|
||||
memory_growth_threshold=memory_growth_threshold,
|
||||
)
|
||||
|
||||
full_param = dict(full_param_iter)
|
||||
|
||||
param_shape = {
|
||||
"_layers.shared_layers.shared_embedding.weight": [24, 32],
|
||||
"_layers.1.bias": [32],
|
||||
"_layers.fused_weight": [32, 64],
|
||||
}
|
||||
|
||||
param_split_axis = {
|
||||
"_layers.shared_layers.shared_embedding.weight": 0,
|
||||
"_layers.1.weight": 1,
|
||||
"_layers.1.bias": 0,
|
||||
"_layers.2.weight": 0,
|
||||
}
|
||||
|
||||
for name, shape in param_shape.items():
|
||||
assert name in full_param.keys()
|
||||
assert tuple(full_param[name].shape) == tuple(shape)
|
||||
|
||||
assert "_layers.2.bias" not in full_param
|
||||
assert "_layers.1.weight" not in full_param
|
||||
assert "_layers.2.weight" not in full_param
|
||||
|
||||
fused_weight = full_param.pop("_layers.fused_weight")
|
||||
splited = paddle.split(fused_weight, num_or_sections=2, axis=1)
|
||||
|
||||
full_param["_layers.1.weight"] = splited[0]
|
||||
full_param["_layers.2.weight"] = splited[1]
|
||||
|
||||
model_parallel_rank = hcg.get_model_parallel_rank()
|
||||
|
||||
for name, param in full_param.items():
|
||||
if name not in model_state_dict:
|
||||
continue
|
||||
splited_axis = param_split_axis[name]
|
||||
|
||||
if splited_axis == -1:
|
||||
splited = [param, param]
|
||||
else:
|
||||
splited = paddle.split(
|
||||
param, num_or_sections=2, axis=splited_axis
|
||||
)
|
||||
t = splited[model_parallel_rank]
|
||||
assert t._md5sum() == model_state_dict[name]._md5sum()
|
||||
|
||||
def run_full_param_memory_growth_threshold_test(self):
|
||||
self.run_full_param_test(memory_growth_threshold=1)
|
||||
self.run_full_param_with_aoa_test(memory_growth_threshold=1)
|
||||
|
||||
|
||||
class TestFullParamLogicWithSharding3:
|
||||
def __init__(self):
|
||||
paddle.distributed.init_parallel_env()
|
||||
|
||||
def run_test(self):
|
||||
model = MLP()
|
||||
opt = paddle.optimizer.AdamW(parameters=model.parameters())
|
||||
model = GroupShardedStage3(model, opt, segment_size=256)
|
||||
model.train()
|
||||
x = paddle.randn([4, 1000])
|
||||
loss = model(x).mean()
|
||||
loss.backward()
|
||||
opt.step()
|
||||
opt.clear_grad()
|
||||
model.get_all_parameters(convert2cpu=True)
|
||||
param_md5_list = []
|
||||
for param in model.parameters():
|
||||
param_md5_list.append(param._md5sum())
|
||||
full_param_iter = model.full()
|
||||
full_param = dict(full_param_iter)
|
||||
full_param_md5_list = []
|
||||
for name, param in full_param.items():
|
||||
full_param_md5_list.append(param._md5sum())
|
||||
assert param_md5_list == full_param_md5_list
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_using_hv_group = int(os.getenv("test_using_hv_group", "0")) == 1
|
||||
test_with_sharding3 = int(os.getenv("test_with_sharding3", "0")) == 1
|
||||
if test_using_hv_group:
|
||||
TestFullParamHVGroupLogic().run_test()
|
||||
elif test_with_sharding3:
|
||||
TestFullParamLogicWithSharding3().run_test()
|
||||
else:
|
||||
TestFullParamLogic().run_test()
|
||||
@@ -0,0 +1,531 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import math
|
||||
import os
|
||||
|
||||
import paddle
|
||||
from paddle import nn
|
||||
from paddle.distributed import ShardedWeight, fleet
|
||||
from paddle.distributed.fleet.layers.mpu import (
|
||||
ColumnParallelLinear,
|
||||
RowParallelLinear,
|
||||
VocabParallelEmbedding,
|
||||
)
|
||||
from paddle.distributed.fleet.meta_optimizers.dygraph_optimizer.dygraph_sharding_optimizer import (
|
||||
DygraphShardingOptimizer,
|
||||
DygraphShardingOptimizerV2,
|
||||
)
|
||||
from paddle.distributed.fleet.meta_parallel.sharding.group_sharded_optimizer_stage2 import (
|
||||
GroupShardedOptimizerStage2,
|
||||
)
|
||||
from paddle.distributed.fleet.meta_parallel.sharding.group_sharded_stage3 import (
|
||||
GroupShardedStage3,
|
||||
)
|
||||
from paddle.distributed.fleet.utils.sequence_parallel_utils import (
|
||||
ColumnSequenceParallelLinear,
|
||||
RowSequenceParallelLinear,
|
||||
)
|
||||
|
||||
|
||||
class SimpleMLP(
|
||||
nn.Layer
|
||||
): # embedding_weight_size=24*100=2400,it can't be divided by 256,which is using to check the padding logic
|
||||
def __init__(self, hidden_size=100, has_bias=False):
|
||||
super().__init__()
|
||||
self.embedding = VocabParallelEmbedding(24, hidden_size)
|
||||
self.linear1 = ColumnParallelLinear(
|
||||
hidden_size, hidden_size, gather_output=False, has_bias=has_bias
|
||||
)
|
||||
self.linear2 = RowParallelLinear(
|
||||
hidden_size, hidden_size, input_is_parallel=True, has_bias=has_bias
|
||||
)
|
||||
self.llm_head = self.embedding # test the shared weight
|
||||
|
||||
def forward(self, x):
|
||||
x = self.embedding(x)
|
||||
x = self.linear1(x)
|
||||
x = self.linear2(x)
|
||||
x = paddle.matmul(x, self.llm_head.weight, transpose_y=True)
|
||||
return x
|
||||
|
||||
|
||||
class TestParallelLayersLogic:
|
||||
def __init__(self):
|
||||
self.optimizer_var_suffix = [".moment1_0", ".moment2_0", ".w_0"]
|
||||
self.test_type = os.getenv("test_type")
|
||||
self.layer_type = os.getenv("layer_type")
|
||||
self.tp_degree = int(os.getenv("tp", "1"))
|
||||
self.dp_degree = int(os.getenv("dp", "1"))
|
||||
self.sharding_degree = int(os.getenv("sharding_degree", "1"))
|
||||
self.world_size = int(os.getenv("world_size"))
|
||||
self.has_bias = os.getenv("has_bias", "True").lower() == "true"
|
||||
self.master_weight = (
|
||||
os.getenv("master_weight", "False").lower() == "true"
|
||||
)
|
||||
self.batch_size = 2
|
||||
self.hidden_size = 32
|
||||
self.vocab_size = 24
|
||||
self.seq_len = 2
|
||||
self.hcg = None
|
||||
|
||||
def run_test(self):
|
||||
strategy = fleet.DistributedStrategy()
|
||||
strategy.hybrid_configs = {
|
||||
"dp_degree": self.dp_degree,
|
||||
"mp_degree": self.tp_degree,
|
||||
"sharding_degree": self.sharding_degree,
|
||||
"pp_degree": 1,
|
||||
}
|
||||
fleet.init(is_collective=True, strategy=strategy)
|
||||
self.hcg = fleet.get_hybrid_communicate_group()
|
||||
if self.test_type == "layer":
|
||||
self.run_layer_test()
|
||||
elif self.test_type == "optimizer":
|
||||
self.run_optimizer_test()
|
||||
else:
|
||||
raise ValueError(f"Unknown test_type: {self.test_type}")
|
||||
|
||||
def run_layer_test(self):
|
||||
tp_group = self.hcg.get_model_parallel_group()
|
||||
layer = self._get_layer()
|
||||
sharded_dict = layer.sharded_state_dict()
|
||||
self._verify_parallel_layer(
|
||||
sharded_dict, tp_group.rank, tp_group.nranks
|
||||
)
|
||||
|
||||
def _get_layer(self):
|
||||
if self.layer_type == "ColumnParallelLinear":
|
||||
return ColumnParallelLinear(
|
||||
self.hidden_size, self.hidden_size * 2, has_bias=self.has_bias
|
||||
)
|
||||
elif self.layer_type == "RowParallelLinear":
|
||||
return RowParallelLinear(
|
||||
self.hidden_size * 2, self.hidden_size, has_bias=self.has_bias
|
||||
)
|
||||
elif self.layer_type == "VocabParallelEmbedding":
|
||||
return VocabParallelEmbedding(self.vocab_size, self.hidden_size)
|
||||
elif self.layer_type == "ColumnSequenceParallelLinear":
|
||||
return ColumnSequenceParallelLinear(
|
||||
self.hidden_size,
|
||||
self.hidden_size * 2,
|
||||
has_bias=self.has_bias,
|
||||
gather_output=False,
|
||||
)
|
||||
elif self.layer_type == "RowSequenceParallelLinear":
|
||||
return RowSequenceParallelLinear(
|
||||
self.hidden_size * 2,
|
||||
self.hidden_size,
|
||||
has_bias=self.has_bias,
|
||||
input_is_parallel=True,
|
||||
)
|
||||
raise ValueError(f"Unknown layer_type: {self.layer_type}")
|
||||
|
||||
def _verify_parallel_layer(self, sharded_dict, tp_rank, tp_world_size):
|
||||
if self.has_bias:
|
||||
assert 'bias' in sharded_dict
|
||||
bias_shard = sharded_dict['bias']
|
||||
assert isinstance(bias_shard, ShardedWeight)
|
||||
else:
|
||||
assert 'bias' not in sharded_dict
|
||||
|
||||
assert 'weight' in sharded_dict
|
||||
weight_shard = sharded_dict['weight']
|
||||
assert isinstance(weight_shard, ShardedWeight)
|
||||
|
||||
if self.layer_type == "ColumnParallelLinear":
|
||||
in_f, out_f = self.hidden_size, self.hidden_size * 2
|
||||
assert weight_shard.global_shape == (in_f, out_f)
|
||||
assert weight_shard.local_shape == (in_f, out_f // tp_world_size)
|
||||
assert weight_shard.global_offset == (
|
||||
0,
|
||||
tp_rank * (out_f // tp_world_size),
|
||||
)
|
||||
if self.has_bias:
|
||||
assert bias_shard.global_shape == (out_f,)
|
||||
assert bias_shard.local_shape == (out_f // tp_world_size,)
|
||||
assert bias_shard.global_offset == (
|
||||
tp_rank * (out_f // tp_world_size),
|
||||
)
|
||||
|
||||
elif self.layer_type == "RowParallelLinear":
|
||||
in_f, out_f = self.hidden_size * 2, self.hidden_size
|
||||
# Weight is sharded on axis 1
|
||||
assert weight_shard.global_shape == (in_f, out_f)
|
||||
assert weight_shard.local_shape == (in_f // tp_world_size, out_f)
|
||||
assert weight_shard.global_offset == (
|
||||
tp_rank * (in_f // tp_world_size),
|
||||
0,
|
||||
)
|
||||
|
||||
if self.has_bias:
|
||||
# Bias is replicated, not sharded
|
||||
assert bias_shard.global_shape == (out_f,)
|
||||
assert bias_shard.local_shape == bias_shard.global_shape
|
||||
assert bias_shard.global_offset == (0,)
|
||||
|
||||
elif self.layer_type == "VocabParallelEmbedding":
|
||||
assert weight_shard.global_shape == (
|
||||
self.vocab_size,
|
||||
self.hidden_size,
|
||||
)
|
||||
assert weight_shard.local_shape == (
|
||||
self.vocab_size // tp_world_size,
|
||||
self.hidden_size,
|
||||
)
|
||||
assert weight_shard.global_offset == (
|
||||
tp_rank * (self.vocab_size // tp_world_size),
|
||||
0,
|
||||
)
|
||||
|
||||
elif self.layer_type == "ColumnSequenceParallelLinear":
|
||||
in_f, out_f = self.hidden_size, self.hidden_size * 2
|
||||
assert weight_shard.global_shape == (in_f, out_f)
|
||||
assert weight_shard.local_shape == (in_f, out_f // tp_world_size)
|
||||
assert weight_shard.global_offset == (
|
||||
0,
|
||||
tp_rank * (out_f // tp_world_size),
|
||||
)
|
||||
if self.has_bias:
|
||||
assert bias_shard.global_shape == (out_f,)
|
||||
assert bias_shard.local_shape == (out_f // tp_world_size,)
|
||||
assert bias_shard.global_offset == (
|
||||
tp_rank * (out_f // tp_world_size),
|
||||
)
|
||||
|
||||
elif self.layer_type == "RowSequenceParallelLinear":
|
||||
in_f, out_f = self.hidden_size * 2, self.hidden_size
|
||||
assert weight_shard.global_shape == (in_f, out_f)
|
||||
assert weight_shard.local_shape == (in_f // tp_world_size, out_f)
|
||||
assert weight_shard.global_offset == (
|
||||
tp_rank * (in_f // tp_world_size),
|
||||
0,
|
||||
)
|
||||
if self.has_bias:
|
||||
assert bias_shard.global_shape == (out_f,)
|
||||
assert bias_shard.local_shape == bias_shard.global_shape
|
||||
assert bias_shard.global_offset == (0,)
|
||||
|
||||
def run_optimizer_test(self):
|
||||
model = SimpleMLP(has_bias=self.has_bias)
|
||||
model = paddle.amp.decorate(
|
||||
models=model, optimizers=None, level="O2", dtype="float16"
|
||||
)
|
||||
if self.master_weight: # test the master_weight
|
||||
opt = paddle.optimizer.AdamW(
|
||||
learning_rate=0.01,
|
||||
parameters=model.parameters(),
|
||||
multi_precision=True,
|
||||
)
|
||||
else:
|
||||
opt = paddle.optimizer.AdamW(
|
||||
learning_rate=0.01,
|
||||
parameters=model.parameters(),
|
||||
multi_precision=False,
|
||||
)
|
||||
if self.layer_type == "AdamW":
|
||||
model = fleet.distributed_model(model)
|
||||
model.train()
|
||||
x = paddle.randint(
|
||||
low=0,
|
||||
high=self.vocab_size,
|
||||
shape=[self.batch_size, self.seq_len, self.hidden_size],
|
||||
dtype='int64',
|
||||
)
|
||||
y = model(x).mean()
|
||||
y.backward()
|
||||
opt.step()
|
||||
opt.clear_grad()
|
||||
|
||||
model_sharded_state_dict = model.sharded_state_dict()
|
||||
opt_sharded_state_dict = opt.sharded_state_dict(
|
||||
model_sharded_state_dict
|
||||
)
|
||||
for key, value in model_sharded_state_dict.items():
|
||||
for state_name in self.optimizer_var_suffix:
|
||||
opt__var_name = key + state_name
|
||||
if opt__var_name in opt_sharded_state_dict:
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].local_shape
|
||||
) == tuple(value.local_shape)
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].global_shape
|
||||
) == tuple(value.global_shape)
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].global_offset
|
||||
) == tuple(value.global_offset)
|
||||
elif self.layer_type == "DygraphShardingOptimizer":
|
||||
opt = DygraphShardingOptimizer(opt, self.hcg)
|
||||
model.train()
|
||||
x = paddle.randint(
|
||||
low=0,
|
||||
high=self.vocab_size,
|
||||
shape=[self.batch_size, self.seq_len, self.hidden_size],
|
||||
dtype='int64',
|
||||
)
|
||||
rank = paddle.distributed.get_rank()
|
||||
sharidng_x = (
|
||||
x[0 : self.batch_size // 2]
|
||||
if rank == 0
|
||||
else x[self.batch_size // 2 :]
|
||||
)
|
||||
y = model(sharidng_x).mean()
|
||||
y.backward()
|
||||
opt.step()
|
||||
opt.clear_grad()
|
||||
|
||||
model_sharded_state_dict = model.sharded_state_dict()
|
||||
opt_sharded_state_dict = opt.sharded_state_dict(
|
||||
model_sharded_state_dict
|
||||
)
|
||||
|
||||
for key, value in model_sharded_state_dict.items():
|
||||
for state_name in self.optimizer_var_suffix:
|
||||
opt__var_name = key + state_name
|
||||
if opt__var_name in opt_sharded_state_dict:
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].local_shape
|
||||
) == tuple(value.local_shape)
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].global_shape
|
||||
) == tuple(value.global_shape)
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].global_offset
|
||||
) == tuple(value.global_offset)
|
||||
elif self.layer_type == "DygraphShardingOptimizerV2":
|
||||
opt = DygraphShardingOptimizerV2(opt, self.hcg)
|
||||
model.train()
|
||||
x = paddle.randint(
|
||||
low=0,
|
||||
high=self.vocab_size,
|
||||
shape=[self.batch_size, self.seq_len, self.hidden_size],
|
||||
dtype='int64',
|
||||
)
|
||||
rank = paddle.distributed.get_rank()
|
||||
sharidng_x = (
|
||||
x[0 : self.batch_size // 2]
|
||||
if rank == 0
|
||||
else x[self.batch_size // 2 :]
|
||||
)
|
||||
y = model(sharidng_x).mean()
|
||||
y.backward()
|
||||
opt.step()
|
||||
opt.clear_grad()
|
||||
|
||||
model_sharded_state_dict = model.sharded_state_dict()
|
||||
opt_sharded_state_dict = opt.sharded_state_dict(
|
||||
model_sharded_state_dict
|
||||
)
|
||||
for key, value in model_sharded_state_dict.items():
|
||||
for state_name in self.optimizer_var_suffix:
|
||||
opt__var_name = key + state_name
|
||||
if opt__var_name in opt_sharded_state_dict:
|
||||
if opt_sharded_state_dict[
|
||||
opt__var_name
|
||||
].flattened_range.stop - opt_sharded_state_dict[
|
||||
opt__var_name
|
||||
].flattened_range.start != math.prod(
|
||||
value.local_shape
|
||||
): # check the optimizer_var which isFragment
|
||||
opt_var_globle_flattened_range = []
|
||||
paddle.distributed.all_gather_object(
|
||||
opt_var_globle_flattened_range,
|
||||
opt_sharded_state_dict[
|
||||
opt__var_name
|
||||
].flattened_range,
|
||||
)
|
||||
|
||||
first_fragment = opt_var_globle_flattened_range[0]
|
||||
second_fragment = opt_var_globle_flattened_range[1]
|
||||
assert (
|
||||
first_fragment.stop == second_fragment.start
|
||||
) # the first_flattened_range_stop == the second_flattened_range_start
|
||||
opt_var_globle_size_flattened = (
|
||||
second_fragment.stop - first_fragment.start
|
||||
)
|
||||
model_var_globle_size_flattened = math.prod(
|
||||
value.local_shape
|
||||
)
|
||||
assert (
|
||||
opt_var_globle_size_flattened
|
||||
== model_var_globle_size_flattened
|
||||
)
|
||||
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].local_shape
|
||||
) == tuple(value.local_shape)
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].global_shape
|
||||
) == tuple(value.global_shape)
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].global_offset
|
||||
) == tuple(value.global_offset)
|
||||
|
||||
elif self.layer_type == "GroupShardedOptimizerStage2":
|
||||
opt = GroupShardedOptimizerStage2(
|
||||
opt._parameter_list, opt, self.hcg.get_sharding_parallel_group()
|
||||
)
|
||||
|
||||
model.train()
|
||||
x = paddle.randint(
|
||||
low=0,
|
||||
high=self.vocab_size,
|
||||
shape=[self.batch_size, self.seq_len, self.hidden_size],
|
||||
dtype='int64',
|
||||
)
|
||||
rank = paddle.distributed.get_rank()
|
||||
sharidng_x = (
|
||||
x[0 : self.batch_size // 2]
|
||||
if rank == 0
|
||||
else x[self.batch_size // 2 :]
|
||||
)
|
||||
y = model(sharidng_x).mean()
|
||||
y.backward()
|
||||
opt.step()
|
||||
opt.clear_grad()
|
||||
|
||||
model_sharded_state_dict = model.sharded_state_dict()
|
||||
opt_sharded_state_dict = opt.sharded_state_dict(
|
||||
model_sharded_state_dict
|
||||
)
|
||||
|
||||
for key, value in model_sharded_state_dict.items():
|
||||
for state_name in self.optimizer_var_suffix:
|
||||
opt__var_name = key + state_name
|
||||
if opt__var_name in opt_sharded_state_dict:
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].local_shape
|
||||
) == tuple(value.local_shape)
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].global_shape
|
||||
) == tuple(value.global_shape)
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].global_offset
|
||||
) == tuple(value.global_offset)
|
||||
|
||||
elif self.layer_type == "GroupShardedStage3":
|
||||
model = fleet.distributed_model(model)
|
||||
wrapped_model = GroupShardedStage3(
|
||||
model, opt, segment_size=2**12
|
||||
) # slice the linear1、linear2 weight
|
||||
for param in opt._parameter_list:
|
||||
if hasattr(param, "fw_storage"):
|
||||
assert len(param.shape) != 1
|
||||
wrapped_model.init_optimizer_for_slice_param()
|
||||
for param in opt._parameter_list:
|
||||
if hasattr(param, "fw_storage"):
|
||||
assert len(param.shape) == 1
|
||||
model_sharded_state_dict = model.sharded_state_dict()
|
||||
for k, v in model_sharded_state_dict.items():
|
||||
if (
|
||||
k == "_layers.linear1.weight"
|
||||
or k == "_layers.linear2.weight"
|
||||
):
|
||||
assert not v.local_tensor._is_initialized()
|
||||
wrapped_model.init_slice_param()
|
||||
for k, v in model_sharded_state_dict.items():
|
||||
if (
|
||||
k == "_layers.linear1.weight"
|
||||
or k == "_layers.linear2.weight"
|
||||
):
|
||||
assert v.local_tensor._is_initialized()
|
||||
wrapped_model.align_param_to_buffer_and_clear_slice_param()
|
||||
for k, v in model_sharded_state_dict.items():
|
||||
if (
|
||||
k == "_layers.linear1.weight"
|
||||
or k == "_layers.linear2.weight"
|
||||
):
|
||||
assert not v.local_tensor._is_initialized()
|
||||
model.train()
|
||||
x = paddle.randint(
|
||||
low=0,
|
||||
high=self.vocab_size,
|
||||
shape=[self.batch_size, self.seq_len, self.hidden_size],
|
||||
dtype='int64',
|
||||
)
|
||||
rank = paddle.distributed.get_rank()
|
||||
sharidng_x = (
|
||||
x[0 : self.batch_size // 2]
|
||||
if rank == 0
|
||||
else x[self.batch_size // 2 :]
|
||||
)
|
||||
y = model(sharidng_x).mean()
|
||||
y.backward()
|
||||
opt.step()
|
||||
opt.clear_grad()
|
||||
model_sharded_state_dict = model.sharded_state_dict()
|
||||
for k, v in model_sharded_state_dict.items():
|
||||
if (
|
||||
k == "_layers.linear1.weight"
|
||||
or k == "_layers.linear2.weight"
|
||||
):
|
||||
assert not v.local_tensor._is_initialized()
|
||||
wrapped_model.get_all_parameters()
|
||||
opt_sharded_state_dict = opt.sharded_state_dict(
|
||||
model_sharded_state_dict
|
||||
)
|
||||
|
||||
for k, v in model_sharded_state_dict.items():
|
||||
if (
|
||||
k == "_layers.linear1.weight"
|
||||
or k == "_layers.linear2.weight"
|
||||
):
|
||||
assert v.local_tensor._is_initialized()
|
||||
|
||||
for key, value in model_sharded_state_dict.items():
|
||||
for state_name in self.optimizer_var_suffix:
|
||||
opt__var_name = key + state_name
|
||||
if opt__var_name in opt_sharded_state_dict:
|
||||
if hasattr(
|
||||
value.local_tensor, "fw_storage"
|
||||
): # check the optimizer_var which isFragment
|
||||
opt_var_globle_flattened_range = []
|
||||
paddle.distributed.all_gather_object(
|
||||
opt_var_globle_flattened_range,
|
||||
opt_sharded_state_dict[
|
||||
opt__var_name
|
||||
].flattened_range,
|
||||
)
|
||||
|
||||
first_fragment = opt_var_globle_flattened_range[0]
|
||||
second_fragment = opt_var_globle_flattened_range[1]
|
||||
assert (
|
||||
first_fragment.stop == second_fragment.start
|
||||
) # the first_flattened_range_stop == the second_flattened_range_start
|
||||
opt_var_globle_size_flattened = (
|
||||
second_fragment.stop - first_fragment.start
|
||||
)
|
||||
model_var_globle_size_flattened = math.prod(
|
||||
value.local_shape
|
||||
)
|
||||
assert (
|
||||
opt_var_globle_size_flattened
|
||||
== model_var_globle_size_flattened
|
||||
)
|
||||
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].local_shape
|
||||
) == tuple(value.local_shape)
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].global_shape
|
||||
) == tuple(value.global_shape)
|
||||
assert tuple(
|
||||
opt_sharded_state_dict[opt__var_name].global_offset
|
||||
) == tuple(value.global_offset)
|
||||
else:
|
||||
raise ValueError(f"Unknown layer_type: {self.layer_type}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
TestParallelLayersLogic().run_test()
|
||||
@@ -0,0 +1,294 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# strategy_conversion_engine.py
|
||||
import argparse
|
||||
import hashlib
|
||||
|
||||
import paddle
|
||||
import paddle.distributed as dist
|
||||
from paddle import nn
|
||||
from paddle.distributed import fleet
|
||||
from paddle.distributed.fleet.layers.mpu import (
|
||||
ColumnParallelLinear,
|
||||
RowParallelLinear,
|
||||
)
|
||||
|
||||
# ==============================================================================
|
||||
# 1. Model Definitions
|
||||
# A model zoo with simple models supporting different parallelism strategies.
|
||||
# ==============================================================================
|
||||
|
||||
|
||||
class MLPBlock(nn.Layer):
|
||||
"""
|
||||
A basic building block compatible with Tensor Parallelism,
|
||||
mimicking a transformer's FFN layer.
|
||||
"""
|
||||
|
||||
def __init__(self, hidden_size=32):
|
||||
super().__init__()
|
||||
self.linear1 = ColumnParallelLinear(
|
||||
hidden_size, hidden_size * 4, has_bias=True, gather_output=False
|
||||
)
|
||||
self.relu = nn.ReLU()
|
||||
self.linear2 = RowParallelLinear(
|
||||
hidden_size * 4, hidden_size, has_bias=True, input_is_parallel=True
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.linear2(self.relu(self.linear1(x)))
|
||||
|
||||
|
||||
class UnifiedMLP(nn.Sequential):
|
||||
"""
|
||||
A unified model composed of multiple MLPBlocks.
|
||||
This sequential structure is suitable for all parallelism types:
|
||||
- TP is handled inside each MLPBlock.
|
||||
- PP wraps this entire Sequential model.
|
||||
- DP/EP treats this entire Sequential model as a single unit.
|
||||
"""
|
||||
|
||||
def __init__(self, hidden_size=32, num_blocks=4):
|
||||
super().__init__(*[MLPBlock(hidden_size) for _ in range(num_blocks)])
|
||||
|
||||
|
||||
class Top1Router(nn.Layer):
|
||||
"""A simple Top-1 Gating network for MoE."""
|
||||
|
||||
def __init__(self, d_model, num_experts):
|
||||
super().__init__()
|
||||
self.gate = nn.Linear(d_model, num_experts)
|
||||
|
||||
def forward(self, x):
|
||||
gate_logits = self.gate(x)
|
||||
expert_weights, expert_indices = paddle.topk(gate_logits, k=1, axis=-1)
|
||||
return nn.functional.softmax(expert_weights, axis=-1), expert_indices
|
||||
|
||||
|
||||
class MoELayer(nn.Layer):
|
||||
"""
|
||||
A more robust MoE layer that handles both EP > 1 (distributed)
|
||||
and EP = 1 (local) scenarios.
|
||||
"""
|
||||
|
||||
def __init__(self, d_model, num_experts, num_blocks=2, moe_group=None):
|
||||
super().__init__()
|
||||
self.d_model = d_model
|
||||
self.num_experts = num_experts
|
||||
self.moe_group = moe_group
|
||||
self.ep_world_size = moe_group.nranks if moe_group else 1
|
||||
|
||||
self.router = Top1Router(d_model, num_experts)
|
||||
self.experts = nn.LayerList(
|
||||
[UnifiedMLP(d_model, num_blocks) for _ in range(self.num_experts)]
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
original_shape = x.shape
|
||||
x = x.reshape([-1, self.d_model])
|
||||
expert_weights, expert_indices = self.router(x)
|
||||
final_output = paddle.zeros_like(x)
|
||||
|
||||
if self.ep_world_size > 1:
|
||||
# Simplified distributed routing for testing purposes.
|
||||
ep_rank = dist.get_rank(self.moe_group)
|
||||
for i in range(self.num_experts):
|
||||
if i % self.ep_world_size == ep_rank:
|
||||
mask = (expert_indices == i).astype('float32')
|
||||
expert_output = self.experts[i](x)
|
||||
final_output += expert_output * mask
|
||||
else:
|
||||
# Local routing for EP = 1
|
||||
for i in range(self.num_experts):
|
||||
token_mask = (expert_indices == i).squeeze(-1)
|
||||
if not token_mask.any():
|
||||
continue
|
||||
selected_tokens = x[token_mask]
|
||||
selected_weights = expert_weights[token_mask]
|
||||
expert_output = self.experts[i](selected_tokens)
|
||||
indices_to_scatter = paddle.where(token_mask)[0]
|
||||
final_output = paddle.scatter(
|
||||
final_output,
|
||||
indices_to_scatter,
|
||||
expert_output * selected_weights,
|
||||
overwrite=False,
|
||||
)
|
||||
|
||||
return final_output.reshape(original_shape)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# 2. Core Logic (Environment Setup, Execution, and Verification)
|
||||
# ==============================================================================
|
||||
|
||||
|
||||
def get_model_and_strategy(args, hcg):
|
||||
"""Builds model and DistributedStrategy based on parsed arguments."""
|
||||
strategy = fleet.DistributedStrategy()
|
||||
strategy.hybrid_configs = {
|
||||
"dp_degree": args.dp,
|
||||
"mp_degree": args.tp,
|
||||
"pp_degree": args.pp,
|
||||
}
|
||||
|
||||
if args.model_type == "moe":
|
||||
model = MoELayer(d_model=32, num_experts=4)
|
||||
else:
|
||||
model = UnifiedMLP()
|
||||
|
||||
if args.ep > 1:
|
||||
model = MoELayer(
|
||||
d_model=32, num_experts=4, moe_group=hcg.get_data_parallel_group()
|
||||
)
|
||||
strategy.hybrid_configs["ep_degree"] = args.ep
|
||||
elif args.pp > 1:
|
||||
# For PP, the model must be wrapped by PipelineLayer
|
||||
model = fleet.meta_parallel.PipelineLayer(
|
||||
layers=model, num_stages=args.pp, topology=hcg.topology()
|
||||
)
|
||||
|
||||
return model, strategy
|
||||
|
||||
|
||||
def setup_execution_environment(config_args):
|
||||
"""A unified function to initialize Fleet and the model."""
|
||||
strategy = fleet.DistributedStrategy()
|
||||
strategy.hybrid_configs = {
|
||||
"dp_degree": config_args.dp,
|
||||
"mp_degree": config_args.tp,
|
||||
"pp_degree": config_args.pp,
|
||||
}
|
||||
|
||||
fleet.init(is_collective=True, strategy=strategy)
|
||||
hcg = fleet.get_hybrid_communicate_group()
|
||||
|
||||
model, strategy = get_model_and_strategy(config_args, hcg)
|
||||
|
||||
# Re-initialize with the final strategy (in case ep_degree was added)
|
||||
fleet.init(is_collective=True, strategy=strategy)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def verify_by_md5(sd1, sd2):
|
||||
"""Compares two state_dicts by the MD5 hash of each parameter."""
|
||||
|
||||
def get_tensor_md5(tensor):
|
||||
return hashlib.md5(tensor.numpy().tobytes()).hexdigest()
|
||||
|
||||
assert sd1.keys() == sd2.keys(), (
|
||||
f"State dicts have different keys! Got {sd1.keys()} vs {sd2.keys()}"
|
||||
)
|
||||
for key in sd1.keys():
|
||||
md5_1 = get_tensor_md5(sd1[key])
|
||||
md5_2 = get_tensor_md5(sd2[key])
|
||||
assert md5_1 == md5_2, (
|
||||
f"MD5 mismatch for param '{key}': baseline={md5_1} vs roundtrip={md5_2}"
|
||||
)
|
||||
|
||||
|
||||
def run_step1_save_source(args):
|
||||
"""Step 1: In the source configuration, save a distributed checkpoint."""
|
||||
model = setup_execution_environment(args.src)
|
||||
dist.save_state_dict(model.sharded_state_dict(), args.src_ckpt_path)
|
||||
|
||||
|
||||
def run_step2_convert(args):
|
||||
"""Step 2: In the target configuration, load the source checkpoint and resave."""
|
||||
model = setup_execution_environment(args.tgt)
|
||||
dist.load_state_dict(model.sharded_state_dict(), args.src_ckpt_path)
|
||||
dist.save_state_dict(model.sharded_state_dict(), args.tgt_ckpt_path)
|
||||
|
||||
|
||||
def run_step3_verify(args):
|
||||
"""Step 3: In the source configuration, load both checkpoints and compare them."""
|
||||
# 1. Create the "round-trip" model by loading the target checkpoint
|
||||
model_roundtrip = setup_execution_environment(args.src)
|
||||
dist.load_state_dict(
|
||||
model_roundtrip.sharded_state_dict(), args.tgt_ckpt_path
|
||||
)
|
||||
|
||||
# 2. Create the "baseline" model by loading the original source checkpoint
|
||||
model_baseline = setup_execution_environment(args.src)
|
||||
dist.load_state_dict(
|
||||
model_baseline.sharded_state_dict(), args.src_ckpt_path
|
||||
)
|
||||
|
||||
dist.barrier()
|
||||
|
||||
# 3. Each rank verifies its own part of the state_dict.
|
||||
# This works for all strategies, including Pipeline Parallelism.
|
||||
final_sd = model_roundtrip.state_dict()
|
||||
initial_sd = model_baseline.state_dict()
|
||||
|
||||
if final_sd and initial_sd:
|
||||
verify_by_md5(initial_sd, final_sd)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# 3. Main Entry Point
|
||||
# ==============================================================================
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--step",
|
||||
type=str,
|
||||
required=True,
|
||||
choices=["save_source", "convert", "verify"],
|
||||
)
|
||||
parser.add_argument("--src_ckpt_path", type=str)
|
||||
parser.add_argument("--tgt_ckpt_path", type=str)
|
||||
parser.add_argument(
|
||||
"--model_type",
|
||||
default="mlp",
|
||||
choices=["mlp", "moe"],
|
||||
help="Model architecture.",
|
||||
)
|
||||
|
||||
# Add all strategy parameters dynamically for source and target
|
||||
for prefix in ["src", "tgt"]:
|
||||
for p in ["world_size", "tp", "dp", "pp", "ep"]:
|
||||
parser.add_argument(f"--{prefix}_{p}", type=int, default=0)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Reorganize parsed args into src/tgt namespaces
|
||||
def organize_args(prefix):
|
||||
config = {
|
||||
p: getattr(args, f"{prefix}_{p}")
|
||||
for p in ["world_size", "tp", "dp", "pp", "ep"]
|
||||
}
|
||||
config["model_type"] = args.model_type
|
||||
# Default parallelism degree to 1 if not specified
|
||||
if config["tp"] == 0:
|
||||
config["tp"] = 1
|
||||
if config["dp"] == 0:
|
||||
config["dp"] = 1
|
||||
if config["pp"] == 0:
|
||||
config["pp"] = 1
|
||||
if config["ep"] == 0:
|
||||
config["ep"] = 1
|
||||
return argparse.Namespace(**config)
|
||||
|
||||
args.src = organize_args("src")
|
||||
args.tgt = organize_args("tgt")
|
||||
|
||||
# Execute the requested step
|
||||
engine = {
|
||||
"save_source": run_step1_save_source,
|
||||
"convert": run_step2_convert,
|
||||
"verify": run_step3_verify,
|
||||
}
|
||||
engine[args.step](args)
|
||||
@@ -0,0 +1,103 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
from paddle.distributed.flex_checkpoint.aoa.aoa_engine import AOAEngine
|
||||
from paddle.distributed.flex_checkpoint.dcp.sharded_weight import (
|
||||
ShardedWeightDesc,
|
||||
)
|
||||
|
||||
|
||||
def create_shard_info(keys, shape=(4, 4), dtype="float32"):
|
||||
info = {}
|
||||
for k in keys:
|
||||
desc = ShardedWeightDesc(
|
||||
key=k,
|
||||
local_shape=shape,
|
||||
global_shape=shape,
|
||||
global_offset=(0, 0),
|
||||
dtype=dtype,
|
||||
)
|
||||
info[k] = [desc]
|
||||
return info
|
||||
|
||||
|
||||
class TestMacroLayerOffsetError(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.source_keys = [f"model.layers.{i}.weight" for i in range(10)]
|
||||
self.dest_keys = [f"model.layers.{i}.weight_out" for i in range(10)] + [
|
||||
f"model.layers.{i}.weight_out2" for i in range(10)
|
||||
]
|
||||
|
||||
self.src_info = create_shard_info(self.source_keys)
|
||||
self.dst_info = create_shard_info(self.dest_keys)
|
||||
|
||||
def test_macro_error_chain(self):
|
||||
"""
|
||||
The statement contains fused_qkv_old and is missing a comma, expecting to trigger the assertion and print the chain.
|
||||
"""
|
||||
aoa_config = {
|
||||
"aoa_statements": [
|
||||
"model.layers.$LAYER_ID.weight^T -> model.layers.$LAYER_ID.weight_out, axis=0 fused_qkv_old, num_heads=20,num_key_value_groups=4",
|
||||
],
|
||||
"enable_traceback": True,
|
||||
}
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
AOAEngine(
|
||||
aoa_config=aoa_config,
|
||||
source_state_shard_info=self.src_info,
|
||||
destination_state_shard_info=self.dst_info,
|
||||
)
|
||||
|
||||
def test_no_error_should_be_raised(self):
|
||||
# No error should be raised
|
||||
source_keys = ["model.layers.0.weight"]
|
||||
dest_keys = ["model.layers.0.weight_out"]
|
||||
src_info = create_shard_info(source_keys)
|
||||
dst_info = create_shard_info(dest_keys)
|
||||
aoa_config = {
|
||||
"aoa_statements": [
|
||||
"model.layers.0.weight^T -> model.layers.0.weight_out",
|
||||
],
|
||||
"enable_traceback": True,
|
||||
}
|
||||
AOAEngine(
|
||||
aoa_config=aoa_config,
|
||||
source_state_shard_info=src_info,
|
||||
destination_state_shard_info=dst_info,
|
||||
)
|
||||
|
||||
def test_shape_propagation_error_chain(self):
|
||||
"""
|
||||
when split/concat, only support one attr named `axis`, but got multiple attrs.
|
||||
"""
|
||||
aoa_config = {
|
||||
"aoa_statements": [
|
||||
"model.layers.0.weight -> model.layers.0.weight_out,model.layers.0.weight_out2,axis=0,axis=1",
|
||||
],
|
||||
"enable_traceback": True,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
AOAEngine(
|
||||
aoa_config=aoa_config,
|
||||
source_state_shard_info=self.src_info,
|
||||
destination_state_shard_info=self.dst_info,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,741 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
from paddle.distributed.flex_checkpoint.aoa.aoa_engine import (
|
||||
AOAEngine,
|
||||
ShardedWeightDesc,
|
||||
ShardMappingEntry,
|
||||
)
|
||||
|
||||
|
||||
class TestAOAEngine(unittest.TestCase):
|
||||
def test_aoa_spilt_merge(self):
|
||||
# ------------------------------------------------------
|
||||
# 1. Define source tensor shards (s0 and s1).
|
||||
# Each is a (2,2) tensor, fully covering its global shape.
|
||||
#
|
||||
# s0 (2,2): s1 (2,2):
|
||||
# +----+----+ +----+----+
|
||||
# | | | | | |
|
||||
# +----+----+ +----+----+
|
||||
# | | | | | |
|
||||
# +----+----+ +----+----+
|
||||
s0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
s1 = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# ------------------------------------------------------
|
||||
# 2. Define destination tensor shards (d0 and d1).
|
||||
# Both are (1,4) tensors, i.e., a single row with 4 columns.
|
||||
#
|
||||
# d0 (1,4): d1 (1,4):
|
||||
# +--+--+--+--+ +--+--+--+--+
|
||||
# | | | | | | | | | |
|
||||
# +--+--+--+--+ +--+--+--+--+
|
||||
d0 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 4),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
d1 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 4),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# ------------------------------------------------------
|
||||
# 3. Record the shard info for sources and destinations
|
||||
source_state_shard_info = {
|
||||
"s0": [s0],
|
||||
"s1": [s1],
|
||||
}
|
||||
destination_state_shard_info = {
|
||||
"d0": [d0],
|
||||
"d1": [d1],
|
||||
}
|
||||
|
||||
# ------------------------------------------------------
|
||||
# 4. AOA statements define axis mapping for concatenation and splitting:
|
||||
# - "s" is formed by concatenating s0 and s1 along axis 1 (columns).
|
||||
# - d0 and d1 are obtained by splitting "s" along axis 0 (rows).
|
||||
aoa_statements = [
|
||||
"s0, s1 -> s, axis = 1 \n",
|
||||
"s -> d0, d1, axis = 0 \n",
|
||||
]
|
||||
|
||||
# ------------------------------------------------------
|
||||
# 5. Create the AOAEngine with this configuration
|
||||
aoa_engine = AOAEngine(
|
||||
aoa_config={"aoa_statements": aoa_statements},
|
||||
source_state_shard_info=source_state_shard_info,
|
||||
destination_state_shard_info=destination_state_shard_info,
|
||||
)
|
||||
|
||||
queries = []
|
||||
answers = []
|
||||
|
||||
# ======================================================
|
||||
# Query 1: Find source for the first half of d0 (columns 0-1)
|
||||
# d0 shard: key="d0", local_shape=(1,2), global_shape=(1,4), global_offset=(0,0)
|
||||
# Covers d0[:, 0:2]
|
||||
#
|
||||
# d0 (1,4):
|
||||
# +------+------+------+------+
|
||||
# |(0,0) |(0,1) | | |
|
||||
# +------+------+------+------+
|
||||
#
|
||||
# This region is mapped from s0, row 0, columns 0-1
|
||||
query = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
src_sharded_weight_desc = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
shard_mapping_entry = ShardMappingEntry(
|
||||
target_slice=query,
|
||||
source_slice=src_sharded_weight_desc,
|
||||
postprocess_list=None,
|
||||
)
|
||||
answer = [shard_mapping_entry]
|
||||
queries.append(query)
|
||||
answers.append(answer)
|
||||
|
||||
# ======================================================
|
||||
# Query 2: Find source for the second half of d1 (columns 2-3)
|
||||
# d1 shard: key="d1", local_shape=(1,2), global_shape=(1,4), global_offset=(0,2)
|
||||
# Covers d1[:, 2:4]
|
||||
#
|
||||
# d1 (1,4):
|
||||
# +------+------+------+------+
|
||||
# | | |(0,2)|(0,3)|
|
||||
# +------+------+------+------+
|
||||
#
|
||||
# This region is mapped from s1, row 1, columns 0-1
|
||||
query = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 2),
|
||||
)
|
||||
src_sharded_weight_desc = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0),
|
||||
)
|
||||
shard_mapping_entry = ShardMappingEntry(
|
||||
target_slice=query,
|
||||
source_slice=src_sharded_weight_desc,
|
||||
postprocess_list=None,
|
||||
)
|
||||
answer = [shard_mapping_entry]
|
||||
queries.append(query)
|
||||
answers.append(answer)
|
||||
|
||||
# ======================================================
|
||||
# Query 3: Find sources for the entire d1 (full row)
|
||||
# d1 shard: key="d1", local_shape=(1,4), global_shape=(1,4), global_offset=(0,0)
|
||||
# Layout: covers all columns
|
||||
#
|
||||
# d1 (1,4):
|
||||
# +------+------+------+------+
|
||||
# | s0 | s0 | s1 | s1 |
|
||||
# |(0,0) |(0,1) |(0,2) |(0,3) |
|
||||
# +------+------+------+------+
|
||||
# The first two columns come from s0, the last two from s1.
|
||||
#
|
||||
# Source slices:
|
||||
# s0, local_shape=(1,2), global_shape=(2,2), global_offset=(1,0)
|
||||
# +----+----+
|
||||
# |(1,0)|(1,1)| <- used for d1 (0,0)-(0,1)
|
||||
# +----+----+
|
||||
#
|
||||
# s1, local_shape=(1,2), global_shape=(2,2), global_offset=(1,0)
|
||||
# +----+----+
|
||||
# |(1,0)|(1,1)| <- used for d1 (0,2)-(0,3)
|
||||
# +----+----+
|
||||
#
|
||||
# The answer consists of two mapping entries:
|
||||
# 1. d1[:, 0:2] <-- s0[1, :]
|
||||
# 2. d1[:, 2:4] <-- s1[1, :]
|
||||
query = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 4),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# d1[:, 0:2] <--- s0[1, :]
|
||||
src_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0), # row 1, columns 0:2
|
||||
)
|
||||
dst_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
# Visual mapping:
|
||||
# d1 (0,0)-(0,1) <--- s0 (1,0)-(1,1)
|
||||
# +------+------+------+------+
|
||||
# |==s0==|==s0==| | |
|
||||
# +------+------+------+------+
|
||||
src_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0),
|
||||
)
|
||||
dst_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 2),
|
||||
)
|
||||
# Visual mapping:
|
||||
# d1 (0,2)-(0,3) <--- s1 (1,0)-(1,1)
|
||||
# +------+------+------+------+
|
||||
# | | |==s1==|==s1==|
|
||||
# +------+------+------+------+
|
||||
|
||||
shard_mapping_entry0 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc0,
|
||||
source_slice=src_sharded_weight_desc0,
|
||||
postprocess_list=None,
|
||||
)
|
||||
shard_mapping_entry1 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc1,
|
||||
source_slice=src_sharded_weight_desc1,
|
||||
postprocess_list=None,
|
||||
)
|
||||
answer = [shard_mapping_entry0, shard_mapping_entry1]
|
||||
queries.append(query)
|
||||
answers.append(answer)
|
||||
# Visual answer summary:
|
||||
# d1 (row 0):
|
||||
# +------+------+------+------+
|
||||
# |==s0==|==s0==|==s1==|==s1==|
|
||||
# +------+------+------+------+
|
||||
# ^ ^ ^ ^
|
||||
# | | | |
|
||||
# |______| |______|
|
||||
# from s0 from s1
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
# ======================================================
|
||||
# Query 4: for optimizer state
|
||||
query = ShardedWeightDesc(
|
||||
key="d1.moment1_0",
|
||||
local_shape=(1, 4),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# d1[:, 0:2] <--- s0[1, :]
|
||||
src_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="s0.moment1_0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0), # row 1, columns 0:2
|
||||
)
|
||||
dst_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="d1.moment1_0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
src_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="s1.moment1_0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0),
|
||||
)
|
||||
dst_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="d1.moment1_0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 2),
|
||||
)
|
||||
|
||||
shard_mapping_entry0 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc0,
|
||||
source_slice=src_sharded_weight_desc0,
|
||||
postprocess_list=None,
|
||||
)
|
||||
shard_mapping_entry1 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc1,
|
||||
source_slice=src_sharded_weight_desc1,
|
||||
postprocess_list=None,
|
||||
)
|
||||
|
||||
answer = [shard_mapping_entry0, shard_mapping_entry1]
|
||||
queries.append(query)
|
||||
answers.append(answer)
|
||||
|
||||
# ======================================================
|
||||
# Query 5: for optimizer state
|
||||
query = ShardedWeightDesc(
|
||||
key="d1.w_0",
|
||||
local_shape=(1, 4),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# d1[:, 0:2] <--- s0[1, :]
|
||||
src_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="s0.w_0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0), # row 1, columns 0:2
|
||||
)
|
||||
dst_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="d1.w_0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
src_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="s1.w_0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0),
|
||||
)
|
||||
dst_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="d1.w_0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(1, 4),
|
||||
global_offset=(0, 2),
|
||||
)
|
||||
|
||||
shard_mapping_entry0 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc0,
|
||||
source_slice=src_sharded_weight_desc0,
|
||||
postprocess_list=None,
|
||||
)
|
||||
shard_mapping_entry1 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc1,
|
||||
source_slice=src_sharded_weight_desc1,
|
||||
postprocess_list=None,
|
||||
)
|
||||
|
||||
answer = [shard_mapping_entry0, shard_mapping_entry1]
|
||||
queries.append(query)
|
||||
answers.append(answer)
|
||||
|
||||
# 6. Run the queries and check results
|
||||
for idx in range(len(queries)):
|
||||
query = queries[idx]
|
||||
answer = answers[idx]
|
||||
result = aoa_engine.find_shard_sources(query)
|
||||
self.assertEqual(result, answer)
|
||||
|
||||
def test_aoa_cast(self):
|
||||
"""Test AOA cast primitive for dtype conversion."""
|
||||
|
||||
s0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
dtype="int32",
|
||||
)
|
||||
|
||||
d0 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
source_state_shard_info = {
|
||||
"s0": [s0],
|
||||
}
|
||||
destination_state_shard_info = {
|
||||
"d0": [d0],
|
||||
}
|
||||
|
||||
aoa_statements = [
|
||||
's0 -> d0, dtype="float32" \n',
|
||||
]
|
||||
|
||||
aoa_engine = AOAEngine(
|
||||
aoa_config={"aoa_statements": aoa_statements},
|
||||
source_state_shard_info=source_state_shard_info,
|
||||
destination_state_shard_info=destination_state_shard_info,
|
||||
)
|
||||
|
||||
query = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
src_sharded_weight_desc = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
dtype="int32",
|
||||
)
|
||||
shard_mapping_entry = ShardMappingEntry(
|
||||
target_slice=query,
|
||||
source_slice=src_sharded_weight_desc,
|
||||
postprocess_list=['float32'],
|
||||
)
|
||||
answer = [shard_mapping_entry]
|
||||
|
||||
result = aoa_engine.find_shard_sources(query)
|
||||
self.assertEqual(result, answer)
|
||||
|
||||
def test_aoa_add(self):
|
||||
"""Test AOA add primitive for adding new keys that don't exist in source."""
|
||||
|
||||
d0 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
source_state_shard_info = {}
|
||||
|
||||
destination_state_shard_info = {
|
||||
"d0": [d0],
|
||||
}
|
||||
|
||||
aoa_statements = [
|
||||
"_ -> d0 \n",
|
||||
]
|
||||
|
||||
aoa_engine = AOAEngine(
|
||||
aoa_config={"aoa_statements": aoa_statements},
|
||||
source_state_shard_info=source_state_shard_info,
|
||||
destination_state_shard_info=destination_state_shard_info,
|
||||
)
|
||||
|
||||
query = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
answer = []
|
||||
|
||||
result = aoa_engine.find_shard_sources(query)
|
||||
self.assertEqual(result, answer)
|
||||
|
||||
def test_mixed_aoa_statements(self):
|
||||
# test fused_ffn and transposed,rename,test_get_var_mapping_chain_macro
|
||||
s0 = ShardedWeightDesc(
|
||||
key="layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 4),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
s1 = ShardedWeightDesc(
|
||||
key="layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 4),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 4),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
d0 = ShardedWeightDesc(
|
||||
key="new_name_layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
d1 = ShardedWeightDesc(
|
||||
key="new_name_layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 2),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
d2 = ShardedWeightDesc(
|
||||
key="new_name_layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 4),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
d3 = ShardedWeightDesc(
|
||||
key="new_name_layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 6),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
source_state_shard_info = {
|
||||
"layers.0.gate_up_fused_proj.weight": [s0, s1],
|
||||
}
|
||||
destination_state_shard_info = {
|
||||
"new_name_layers.0.gate_up_fused_proj.weight": [d0, d1, d2, d3],
|
||||
}
|
||||
|
||||
# find temp_var -> dst
|
||||
aoa_statements = [
|
||||
"layers.0.gate_up_fused_proj.weight -> temp_var, fused_ffn \n",
|
||||
"temp_var^T -> new_name_layers.0.gate_up_fused_proj.weight \n",
|
||||
]
|
||||
|
||||
aoa_engine = AOAEngine(
|
||||
aoa_config={"aoa_statements": aoa_statements},
|
||||
source_state_shard_info=source_state_shard_info,
|
||||
destination_state_shard_info=destination_state_shard_info,
|
||||
)
|
||||
|
||||
# new_name_up_proj_0
|
||||
query = ShardedWeightDesc(
|
||||
key="new_name_layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 1),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
target_slice_1 = ShardedWeightDesc(
|
||||
key='new_name_layers.0.gate_up_fused_proj.weight',
|
||||
local_shape=(1, 1),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 0),
|
||||
dtype='float32',
|
||||
)
|
||||
|
||||
target_slice_2 = ShardedWeightDesc(
|
||||
key='new_name_layers.0.gate_up_fused_proj.weight',
|
||||
local_shape=(1, 1),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(1, 0),
|
||||
dtype='float32',
|
||||
)
|
||||
|
||||
src_slice_1 = ShardedWeightDesc(
|
||||
key='layers.0.gate_up_fused_proj.weight',
|
||||
local_shape=(1, 1),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 0),
|
||||
dtype='float32',
|
||||
)
|
||||
|
||||
src_slice_2 = ShardedWeightDesc(
|
||||
key='layers.0.gate_up_fused_proj.weight',
|
||||
local_shape=(1, 1),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 2),
|
||||
dtype='float32',
|
||||
)
|
||||
|
||||
shard_mapping_entry_1 = ShardMappingEntry(
|
||||
target_slice=target_slice_1,
|
||||
source_slice=src_slice_1,
|
||||
postprocess_list=['[1, 0]'],
|
||||
)
|
||||
shard_mapping_entry_2 = ShardMappingEntry(
|
||||
target_slice=target_slice_2,
|
||||
source_slice=src_slice_2,
|
||||
postprocess_list=['[1, 0]'],
|
||||
)
|
||||
answer = [shard_mapping_entry_1, shard_mapping_entry_2]
|
||||
|
||||
result = aoa_engine.find_shard_sources(query)
|
||||
self.assertEqual(result, answer)
|
||||
|
||||
s0 = ShardedWeightDesc(
|
||||
key="layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(4, 2),
|
||||
global_shape=(8, 2),
|
||||
global_offset=(0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
s1 = ShardedWeightDesc(
|
||||
key="layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(4, 2),
|
||||
global_shape=(8, 2),
|
||||
global_offset=(4, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
d0 = ShardedWeightDesc(
|
||||
key="new_name_layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
d1 = ShardedWeightDesc(
|
||||
key="new_name_layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 2),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
d2 = ShardedWeightDesc(
|
||||
key="new_name_layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 4),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
d3 = ShardedWeightDesc(
|
||||
key="new_name_layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 6),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
source_state_shard_info = {
|
||||
"layers.0.gate_up_fused_proj.weight": [s0, s1],
|
||||
}
|
||||
destination_state_shard_info = {
|
||||
"new_name_layers.0.gate_up_fused_proj.weight": [d0, d1, d2, d3],
|
||||
}
|
||||
|
||||
# find temp_var -> src
|
||||
aoa_statements = [
|
||||
"layers.0.gate_up_fused_proj.weight^T -> temp_var \n",
|
||||
"temp_var -> new_name_layers.0.gate_up_fused_proj.weight,fused_ffn\n",
|
||||
]
|
||||
|
||||
aoa_engine = AOAEngine(
|
||||
aoa_config={"aoa_statements": aoa_statements},
|
||||
source_state_shard_info=source_state_shard_info,
|
||||
destination_state_shard_info=destination_state_shard_info,
|
||||
)
|
||||
|
||||
# new_name_up_proj_0
|
||||
query = ShardedWeightDesc(
|
||||
key="new_name_layers.0.gate_up_fused_proj.weight",
|
||||
local_shape=(2, 1),
|
||||
global_shape=(2, 8),
|
||||
global_offset=(0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
|
||||
src_slice_1 = ShardedWeightDesc(
|
||||
key='layers.0.gate_up_fused_proj.weight',
|
||||
local_shape=(1, 2),
|
||||
global_shape=(8, 2),
|
||||
global_offset=(0, 0),
|
||||
dtype='float32',
|
||||
)
|
||||
|
||||
shard_mapping_entry_1 = ShardMappingEntry(
|
||||
target_slice=query,
|
||||
source_slice=src_slice_1,
|
||||
postprocess_list=['[1, 0]'],
|
||||
)
|
||||
|
||||
answer = [shard_mapping_entry_1]
|
||||
|
||||
result = aoa_engine.find_shard_sources(query)
|
||||
self.assertEqual(result, answer)
|
||||
|
||||
def test_aoa_transpose_reverse(self):
|
||||
s0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(1, 2, 4),
|
||||
global_shape=(1, 2, 4),
|
||||
global_offset=(0, 0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
d0 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(4, 1, 2),
|
||||
global_shape=(4, 1, 2),
|
||||
global_offset=(0, 0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
aoa_statements = [
|
||||
"s0 -> d0, permute= '[2, 0, 1]' \n",
|
||||
]
|
||||
aoa_config = {
|
||||
"aoa_statements": aoa_statements,
|
||||
"aoa_config_reverse": True,
|
||||
}
|
||||
source_state_shard_info = {
|
||||
"d0": [d0],
|
||||
}
|
||||
destination_state_shard_info = {
|
||||
"s0": [s0],
|
||||
}
|
||||
aoa_engine = AOAEngine(
|
||||
aoa_config=aoa_config,
|
||||
source_state_shard_info=source_state_shard_info,
|
||||
destination_state_shard_info=destination_state_shard_info,
|
||||
)
|
||||
|
||||
query = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(1, 2, 4),
|
||||
global_shape=(1, 2, 4),
|
||||
global_offset=(0, 0, 0),
|
||||
dtype="float32",
|
||||
)
|
||||
answer = [
|
||||
ShardMappingEntry(
|
||||
target_slice=s0,
|
||||
source_slice=d0,
|
||||
postprocess_list=['[1, 2, 0]'],
|
||||
)
|
||||
]
|
||||
result = aoa_engine.find_shard_sources(query)
|
||||
self.assertEqual(result, answer)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,674 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
from paddle.distributed.flex_checkpoint.aoa.aoa_engine import (
|
||||
AOAEngine,
|
||||
ShardedWeightDesc,
|
||||
ShardMappingEntry,
|
||||
)
|
||||
|
||||
|
||||
class TestAOAEngineTransposeCast(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.setup_statements()
|
||||
self.aoa_engine = AOAEngine(
|
||||
aoa_config={"aoa_statements": self.aoa_statements},
|
||||
source_state_shard_info=self.source_state_shard_info,
|
||||
destination_state_shard_info=self.destination_state_shard_info,
|
||||
)
|
||||
self.generate_query_answer()
|
||||
|
||||
def setup_statements(self):
|
||||
s0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
s1 = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
d0 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(4, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
d1 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(4, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
self.source_state_shard_info = {
|
||||
"s0": [s0],
|
||||
"s1": [s1],
|
||||
}
|
||||
self.destination_state_shard_info = {
|
||||
"d0": [d0],
|
||||
"d1": [d1],
|
||||
}
|
||||
|
||||
self.aoa_statements = [
|
||||
"s0, s1 -> s, axis = 1 \n",
|
||||
"s -> s, dtype = 'float32'\n",
|
||||
"s^T -> d\n",
|
||||
"d -> d0, d1, axis = 1",
|
||||
]
|
||||
|
||||
def generate_query_answer(self):
|
||||
self.queries = []
|
||||
self.answers = []
|
||||
|
||||
# ======================================================
|
||||
# Query 1:
|
||||
query = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(2, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(2, 0),
|
||||
)
|
||||
src_sharded_weight_desc = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
shard_mapping_entry = ShardMappingEntry(
|
||||
target_slice=query,
|
||||
source_slice=src_sharded_weight_desc,
|
||||
postprocess_list=["float32", "[1, 0]"],
|
||||
)
|
||||
answer = [shard_mapping_entry]
|
||||
self.queries.append(query)
|
||||
self.answers.append(answer)
|
||||
|
||||
# ======================================================
|
||||
# Query 2:
|
||||
query = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(2, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
src_sharded_weight_desc = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0),
|
||||
)
|
||||
shard_mapping_entry = ShardMappingEntry(
|
||||
target_slice=query,
|
||||
source_slice=src_sharded_weight_desc,
|
||||
postprocess_list=["float32", "[1, 0]"],
|
||||
)
|
||||
answer = [shard_mapping_entry]
|
||||
self.queries.append(query)
|
||||
self.answers.append(answer)
|
||||
|
||||
# ======================================================
|
||||
# Query 3:
|
||||
query = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(4, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# d1[0:2, :] <--- s0[1, :]^T
|
||||
src_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0),
|
||||
)
|
||||
dst_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(2, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# d1[2:4, :] <--- s1[1, :]^T
|
||||
src_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0),
|
||||
)
|
||||
dst_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(2, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(2, 0),
|
||||
)
|
||||
|
||||
shard_mapping_entry0 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc0,
|
||||
source_slice=src_sharded_weight_desc0,
|
||||
postprocess_list=["float32", "[1, 0]"],
|
||||
)
|
||||
shard_mapping_entry1 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc1,
|
||||
source_slice=src_sharded_weight_desc1,
|
||||
postprocess_list=["float32", "[1, 0]"],
|
||||
)
|
||||
answer = [shard_mapping_entry0, shard_mapping_entry1]
|
||||
self.queries.append(query)
|
||||
self.answers.append(answer)
|
||||
|
||||
def test_transpose(self):
|
||||
for idx in range(len(self.queries)):
|
||||
query = self.queries[idx]
|
||||
answer = self.answers[idx]
|
||||
result = self.aoa_engine.find_shard_sources(query)
|
||||
self.assertEqual(result, answer)
|
||||
|
||||
|
||||
class TestAOAEngineTransposeCast2(TestAOAEngineTransposeCast):
|
||||
def setup_statements(self):
|
||||
s0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(4, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
s1 = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(4, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
d0 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
d1 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
self.source_state_shard_info = {
|
||||
"s0": [s0],
|
||||
"s1": [s1],
|
||||
}
|
||||
self.destination_state_shard_info = {
|
||||
"d0": [d0],
|
||||
"d1": [d1],
|
||||
}
|
||||
|
||||
self.aoa_statements = [
|
||||
"s0^T -> s0\n",
|
||||
"s1^T -> s1\n",
|
||||
"s0, s1 -> s, axis = 0\n",
|
||||
"s -> s, dtype = 'float16'\n",
|
||||
"s -> d0, d1, axis = 1",
|
||||
]
|
||||
|
||||
def generate_query_answer(self):
|
||||
self.queries = []
|
||||
self.answers = []
|
||||
|
||||
# ======================================================
|
||||
# Query 1:
|
||||
query = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0),
|
||||
)
|
||||
src_sharded_weight_desc = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(2, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
shard_mapping_entry = ShardMappingEntry(
|
||||
target_slice=query,
|
||||
source_slice=src_sharded_weight_desc,
|
||||
postprocess_list=["[1, 0]", "float16"],
|
||||
)
|
||||
answer = [shard_mapping_entry]
|
||||
self.queries.append(query)
|
||||
self.answers.append(answer)
|
||||
|
||||
# ======================================================
|
||||
# Query 2:
|
||||
query = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
src_sharded_weight_desc = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(2, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(2, 0),
|
||||
)
|
||||
shard_mapping_entry = ShardMappingEntry(
|
||||
target_slice=query,
|
||||
source_slice=src_sharded_weight_desc,
|
||||
postprocess_list=["[1, 0]", "float16"],
|
||||
)
|
||||
answer = [shard_mapping_entry]
|
||||
self.queries.append(query)
|
||||
self.answers.append(answer)
|
||||
|
||||
# ======================================================
|
||||
# Query 3:
|
||||
query = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(2, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# d1[0:1, :] <--- s0[2:4, :]^T
|
||||
src_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(2, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(2, 0),
|
||||
)
|
||||
dst_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# d1[1:2, :] <--- s1[2:4, :]^T
|
||||
src_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(2, 1),
|
||||
global_shape=(4, 1),
|
||||
global_offset=(2, 0),
|
||||
)
|
||||
dst_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 2),
|
||||
global_shape=(2, 2),
|
||||
global_offset=(1, 0),
|
||||
)
|
||||
|
||||
shard_mapping_entry0 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc0,
|
||||
source_slice=src_sharded_weight_desc0,
|
||||
postprocess_list=["[1, 0]", "float16"],
|
||||
)
|
||||
shard_mapping_entry1 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc1,
|
||||
source_slice=src_sharded_weight_desc1,
|
||||
postprocess_list=["[1, 0]", "float16"],
|
||||
)
|
||||
answer = [shard_mapping_entry0, shard_mapping_entry1]
|
||||
self.queries.append(query)
|
||||
self.answers.append(answer)
|
||||
|
||||
|
||||
class TestAOAEngineTransposeCast3(TestAOAEngineTransposeCast):
|
||||
def setup_statements(self):
|
||||
s0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(3, 4),
|
||||
global_shape=(3, 4),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
d0 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 6),
|
||||
global_shape=(1, 6),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
d1 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(6, 1),
|
||||
global_shape=(6, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
self.source_state_shard_info = {
|
||||
"s0": [s0],
|
||||
}
|
||||
self.destination_state_shard_info = {
|
||||
"d0": [d0],
|
||||
"d1": [d1],
|
||||
}
|
||||
|
||||
self.aoa_statements = [
|
||||
"s0 -> a1, a2, a3, a4, axis = 1\n",
|
||||
"a2^T -> b2\n",
|
||||
"a3^T -> b3\n",
|
||||
"b2, b3 -> d0, axis = 1\n",
|
||||
"a3, a4 -> d1, axis = 0\n",
|
||||
]
|
||||
|
||||
def generate_query_answer(self):
|
||||
self.queries = []
|
||||
self.answers = []
|
||||
|
||||
# ======================================================
|
||||
# Query 1:
|
||||
query = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 6),
|
||||
global_shape=(1, 6),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
# d0[:, 0:3] <--- s0[:, 1:2]^T
|
||||
src_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(3, 1),
|
||||
global_shape=(3, 4),
|
||||
global_offset=(0, 1),
|
||||
)
|
||||
dst_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 3),
|
||||
global_shape=(1, 6),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# d0[:, 3:6] <--- s0[:, 2:3]^T
|
||||
src_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(3, 1),
|
||||
global_shape=(3, 4),
|
||||
global_offset=(0, 2),
|
||||
)
|
||||
dst_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 3),
|
||||
global_shape=(1, 6),
|
||||
global_offset=(0, 3),
|
||||
)
|
||||
|
||||
shard_mapping_entry0 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc0,
|
||||
source_slice=src_sharded_weight_desc0,
|
||||
postprocess_list=["[1, 0]"],
|
||||
)
|
||||
shard_mapping_entry1 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc1,
|
||||
source_slice=src_sharded_weight_desc1,
|
||||
postprocess_list=["[1, 0]"],
|
||||
)
|
||||
answer = [shard_mapping_entry0, shard_mapping_entry1]
|
||||
self.queries.append(query)
|
||||
self.answers.append(answer)
|
||||
|
||||
# ======================================================
|
||||
# Query 2:
|
||||
query = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(6, 1),
|
||||
global_shape=(6, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
# d1[0:3, :] <--- s0[:, 2:3]
|
||||
src_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(3, 1),
|
||||
global_shape=(3, 4),
|
||||
global_offset=(0, 2),
|
||||
)
|
||||
dst_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(3, 1),
|
||||
global_shape=(6, 1),
|
||||
global_offset=(0, 0),
|
||||
)
|
||||
|
||||
# d1[3:6, :] <--- s0[:, 3:4]
|
||||
src_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(3, 1),
|
||||
global_shape=(3, 4),
|
||||
global_offset=(0, 3),
|
||||
)
|
||||
dst_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(3, 1),
|
||||
global_shape=(6, 1),
|
||||
global_offset=(3, 0),
|
||||
)
|
||||
|
||||
shard_mapping_entry0 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc0,
|
||||
source_slice=src_sharded_weight_desc0,
|
||||
postprocess_list=None,
|
||||
)
|
||||
shard_mapping_entry1 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc1,
|
||||
source_slice=src_sharded_weight_desc1,
|
||||
postprocess_list=None,
|
||||
)
|
||||
answer = [shard_mapping_entry0, shard_mapping_entry1]
|
||||
self.queries.append(query)
|
||||
self.answers.append(answer)
|
||||
|
||||
|
||||
class TestAOAEngineTransposeCast4(TestAOAEngineTransposeCast):
|
||||
def setup_statements(self):
|
||||
s0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(4, 1, 3),
|
||||
global_shape=(4, 1, 3),
|
||||
global_offset=(0, 0, 0),
|
||||
)
|
||||
s1 = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(4, 1, 3),
|
||||
global_shape=(4, 1, 3),
|
||||
global_offset=(0, 0, 0),
|
||||
)
|
||||
|
||||
d0 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 4, 4),
|
||||
global_shape=(1, 4, 4),
|
||||
global_offset=(0, 0, 0),
|
||||
)
|
||||
d1 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 4, 2),
|
||||
global_shape=(1, 4, 2),
|
||||
global_offset=(0, 0, 0),
|
||||
)
|
||||
|
||||
self.source_state_shard_info = {
|
||||
"s0": [s0],
|
||||
"s1": [s1],
|
||||
}
|
||||
self.destination_state_shard_info = {
|
||||
"d0": [d0],
|
||||
"d1": [d1],
|
||||
}
|
||||
|
||||
self.aoa_statements = [
|
||||
"s0, s1 -> s, axis = 1\n",
|
||||
"s -> s, dtype = 'bfloat16'\n",
|
||||
"s -> a, permute = '[2, 0, 1]'\n",
|
||||
"a -> b1, b2, b3, axis = 0\n",
|
||||
"b1 -> b1, permute = '[0, 2, 1]'\n",
|
||||
"b2 -> b2, permute = '[0, 2, 1]'\n",
|
||||
"b1, b2 -> d0, axis = 1\n",
|
||||
"b3 -> d1\n",
|
||||
"d1 -> d1, dtype = 'float32'",
|
||||
]
|
||||
|
||||
def generate_query_answer(self):
|
||||
self.queries = []
|
||||
self.answers = []
|
||||
|
||||
# ======================================================
|
||||
# Query 1:
|
||||
query = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 4, 4),
|
||||
global_shape=(1, 4, 4),
|
||||
global_offset=(0, 0, 0),
|
||||
)
|
||||
# d0[:, 0:1, :] <--- s0[:, :, 0:1].transpose([2, 0, 1]).transpose([0, 2, 1])
|
||||
src_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(4, 1, 1),
|
||||
global_shape=(4, 1, 3),
|
||||
global_offset=(0, 0, 0),
|
||||
)
|
||||
dst_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 1, 4),
|
||||
global_shape=(1, 4, 4),
|
||||
global_offset=(0, 0, 0),
|
||||
)
|
||||
|
||||
# d0[:, 1:2, :] <--- s1[:, :, 0:1].transpose([2, 0, 1]).transpose([0, 2, 1])
|
||||
src_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(4, 1, 1),
|
||||
global_shape=(4, 1, 3),
|
||||
global_offset=(0, 0, 0),
|
||||
)
|
||||
dst_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 1, 4),
|
||||
global_shape=(1, 4, 4),
|
||||
global_offset=(0, 1, 0),
|
||||
)
|
||||
|
||||
# d0[:, 2:3, :] <--- s0[:, :, 1:2].transpose([2, 0, 1]).transpose([0, 2, 1])
|
||||
src_sharded_weight_desc2 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(4, 1, 1),
|
||||
global_shape=(4, 1, 3),
|
||||
global_offset=(0, 0, 1),
|
||||
)
|
||||
dst_sharded_weight_desc2 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 1, 4),
|
||||
global_shape=(1, 4, 4),
|
||||
global_offset=(0, 2, 0),
|
||||
)
|
||||
|
||||
# d0[:, 3:4, :] <--- s1[:, :, 1:2].transpose([2, 0, 1]).transpose([0, 2, 1])
|
||||
src_sharded_weight_desc3 = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(4, 1, 1),
|
||||
global_shape=(4, 1, 3),
|
||||
global_offset=(0, 0, 1),
|
||||
)
|
||||
dst_sharded_weight_desc3 = ShardedWeightDesc(
|
||||
key="d0",
|
||||
local_shape=(1, 1, 4),
|
||||
global_shape=(1, 4, 4),
|
||||
global_offset=(0, 3, 0),
|
||||
)
|
||||
|
||||
shard_mapping_entry0 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc0,
|
||||
source_slice=src_sharded_weight_desc0,
|
||||
postprocess_list=["bfloat16", "[2, 0, 1]", "[0, 2, 1]"],
|
||||
)
|
||||
shard_mapping_entry1 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc1,
|
||||
source_slice=src_sharded_weight_desc1,
|
||||
postprocess_list=["bfloat16", "[2, 0, 1]", "[0, 2, 1]"],
|
||||
)
|
||||
shard_mapping_entry2 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc2,
|
||||
source_slice=src_sharded_weight_desc2,
|
||||
postprocess_list=["bfloat16", "[2, 0, 1]", "[0, 2, 1]"],
|
||||
)
|
||||
shard_mapping_entry3 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc3,
|
||||
source_slice=src_sharded_weight_desc3,
|
||||
postprocess_list=["bfloat16", "[2, 0, 1]", "[0, 2, 1]"],
|
||||
)
|
||||
answer = [
|
||||
shard_mapping_entry0,
|
||||
shard_mapping_entry1,
|
||||
shard_mapping_entry2,
|
||||
shard_mapping_entry3,
|
||||
]
|
||||
self.queries.append(query)
|
||||
self.answers.append(answer)
|
||||
|
||||
# ======================================================
|
||||
# Query 2:
|
||||
query = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 4, 2),
|
||||
global_shape=(1, 4, 2),
|
||||
global_offset=(0, 0, 0),
|
||||
)
|
||||
# d1[:, :, 0:1] <--- s0[:, :, 2:3].transpose([2, 0, 1])
|
||||
src_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="s0",
|
||||
local_shape=(4, 1, 1),
|
||||
global_shape=(4, 1, 3),
|
||||
global_offset=(0, 0, 2),
|
||||
)
|
||||
dst_sharded_weight_desc0 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 4, 1),
|
||||
global_shape=(1, 4, 2),
|
||||
global_offset=(0, 0, 0),
|
||||
)
|
||||
|
||||
# d1[:, :, 1:2] <--- s1[:, :, 2:3].transpose([2, 0, 1])
|
||||
src_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="s1",
|
||||
local_shape=(4, 1, 1),
|
||||
global_shape=(4, 1, 3),
|
||||
global_offset=(0, 0, 2),
|
||||
)
|
||||
dst_sharded_weight_desc1 = ShardedWeightDesc(
|
||||
key="d1",
|
||||
local_shape=(1, 4, 1),
|
||||
global_shape=(1, 4, 2),
|
||||
global_offset=(0, 0, 1),
|
||||
)
|
||||
|
||||
shard_mapping_entry0 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc0,
|
||||
source_slice=src_sharded_weight_desc0,
|
||||
postprocess_list=["bfloat16", "[2, 0, 1]", "float32"],
|
||||
)
|
||||
shard_mapping_entry1 = ShardMappingEntry(
|
||||
target_slice=dst_sharded_weight_desc1,
|
||||
source_slice=src_sharded_weight_desc1,
|
||||
postprocess_list=["bfloat16", "[2, 0, 1]", "float32"],
|
||||
)
|
||||
answer = [shard_mapping_entry0, shard_mapping_entry1]
|
||||
self.queries.append(query)
|
||||
self.answers.append(answer)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from paddle.distributed.flex_checkpoint.dcp.load_state_dict import (
|
||||
get_rank_to_read_files,
|
||||
)
|
||||
|
||||
|
||||
class TestGetRankToReadFiles(unittest.TestCase):
|
||||
"""Unit tests for get_rank_to_read_files function."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.mock_rank = 0
|
||||
self.patcher = patch(
|
||||
'paddle.distributed.get_rank', return_value=self.mock_rank
|
||||
)
|
||||
self.mock_get_rank = self.patcher.start()
|
||||
self.addCleanup(self.patcher.stop)
|
||||
|
||||
def test_local_files_assignment(self):
|
||||
"""Test assignment when rank has all required files locally."""
|
||||
# Arrange
|
||||
rank_to_required = {
|
||||
0: ['file1.distcp', 'file2.distcp'],
|
||||
1: ['file3.distcp', 'file4.distcp'],
|
||||
}
|
||||
|
||||
rank_to_available_files = {
|
||||
0: ['file1.distcp', 'file2.distcp'],
|
||||
1: ['file3.distcp', 'file4.distcp'],
|
||||
}
|
||||
|
||||
# Act
|
||||
result = get_rank_to_read_files(
|
||||
rank_to_required, rank_to_available_files
|
||||
)
|
||||
|
||||
# Assert
|
||||
expected_files = ['file1.distcp', 'file2.distcp']
|
||||
self.assertEqual(sorted(result), sorted(expected_files))
|
||||
|
||||
def test_cross_node_files_assignment(self):
|
||||
"""Test assignment when rank needs files from other nodes."""
|
||||
# Arrange
|
||||
rank_to_required = {
|
||||
0: ['file1.distcp', 'file2.distcp'],
|
||||
1: ['file1.distcp', 'file3.distcp'],
|
||||
}
|
||||
|
||||
rank_to_available_files = {
|
||||
0: ['file1.distcp'],
|
||||
1: ['file2.distcp', 'file3.distcp'],
|
||||
}
|
||||
|
||||
# Act
|
||||
result = get_rank_to_read_files(
|
||||
rank_to_required, rank_to_available_files
|
||||
)
|
||||
|
||||
# Assert
|
||||
# Rank 0 should get file1 locally and file2 might be assigned from rank 1
|
||||
self.assertIn('file1.distcp', result)
|
||||
self.assertEqual(len(result), 1) # Should balance workload
|
||||
|
||||
def test_empty_rank_assignment(self):
|
||||
"""Test when current rank has no files to read."""
|
||||
# Arrange
|
||||
rank_to_required = {
|
||||
1: ['file1.distcp', 'file2.distcp'],
|
||||
2: ['file3.distcp', 'file4.distcp'],
|
||||
}
|
||||
|
||||
rank_to_available_files = {
|
||||
1: ['file1.distcp', 'file2.distcp'],
|
||||
2: ['file3.distcp', 'file4.distcp'],
|
||||
}
|
||||
|
||||
self.mock_rank = 0 # Current rank has nothing to do
|
||||
|
||||
# Act
|
||||
result = get_rank_to_read_files(
|
||||
rank_to_required, rank_to_available_files
|
||||
)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_load_balancing_multiple_candidates(self):
|
||||
"""Test load balancing when multiple ranks can read the same file."""
|
||||
# Arrange
|
||||
rank_to_required = {
|
||||
0: ['shared_file.distcp', 'file2.distcp'],
|
||||
1: ['shared_file.distcp', 'file3.distcp'],
|
||||
2: ['file4.distcp', 'file5.distcp'],
|
||||
}
|
||||
|
||||
rank_to_available_files = {
|
||||
0: ['shared_file.distcp', 'file2.distcp'],
|
||||
1: ['shared_file.distcp', 'file3.distcp'],
|
||||
2: ['shared_file.distcp', 'file4.distcp', 'file5.distcp'],
|
||||
}
|
||||
|
||||
# Act
|
||||
result = get_rank_to_read_files(
|
||||
rank_to_required, rank_to_available_files
|
||||
)
|
||||
|
||||
# Assert
|
||||
# Should include shared_file and file2, but workload should be balanced
|
||||
self.assertIn('file2.distcp', result)
|
||||
self.assertEqual(len(result), 2)
|
||||
|
||||
def test_missing_file_warning(self):
|
||||
"""Test behavior when required file is not available on any rank."""
|
||||
# Arrange
|
||||
rank_to_required = {
|
||||
0: ['missing_file.distcp', 'existing_file.distcp'],
|
||||
1: ['file2.distcp'],
|
||||
}
|
||||
|
||||
rank_to_available_files = {
|
||||
0: ['existing_file.distcp'],
|
||||
1: ['file2.distcp'],
|
||||
}
|
||||
# missing_file.distcp is not in any rank_to_available_files
|
||||
|
||||
# Act & Assert - should not raise exception but handle gracefully
|
||||
result = get_rank_to_read_files(
|
||||
rank_to_required, rank_to_available_files
|
||||
)
|
||||
|
||||
# Should still return files that are available
|
||||
self.assertIn('existing_file.distcp', result)
|
||||
|
||||
def test_single_rank_scenario(self):
|
||||
"""Test single rank scenario (non-distributed mode)."""
|
||||
# Arrange
|
||||
rank_to_required = {0: ['file1.distcp', 'file2.distcp', 'file3.distcp']}
|
||||
rank_to_available_files = {
|
||||
0: ['file1.distcp', 'file2.distcp', 'file3.distcp']
|
||||
}
|
||||
|
||||
# Act
|
||||
result = get_rank_to_read_files(
|
||||
rank_to_required, rank_to_available_files
|
||||
)
|
||||
|
||||
# Assert
|
||||
expected_files = ['file1.distcp', 'file2.distcp', 'file3.distcp']
|
||||
self.assertEqual(sorted(result), sorted(expected_files))
|
||||
|
||||
def test_empty_inputs(self):
|
||||
"""Test with empty input dictionaries."""
|
||||
# Arrange
|
||||
rank_to_required = {}
|
||||
rank_to_available_files = {}
|
||||
|
||||
# Act
|
||||
result = get_rank_to_read_files(
|
||||
rank_to_required, rank_to_available_files
|
||||
)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_rank_with_no_local_files(self):
|
||||
"""Test when a rank has logical files but no local files available."""
|
||||
# Arrange
|
||||
rank_to_required = {
|
||||
0: ['file1.distcp', 'file2.distcp'],
|
||||
1: ['file3.distcp'],
|
||||
}
|
||||
|
||||
rank_to_available_files = {
|
||||
1: ['file1.distcp', 'file2.distcp', 'file3.distcp']
|
||||
}
|
||||
# Rank 0 has no local files but needs file1 and file2
|
||||
|
||||
# Act
|
||||
result = get_rank_to_read_files(
|
||||
rank_to_required, rank_to_available_files
|
||||
)
|
||||
|
||||
# Assert
|
||||
# Rank 0 should get files assigned from rank 1
|
||||
self.assertEqual(len(result), 0)
|
||||
|
||||
def test_rank_not_in_mappings(self):
|
||||
"""Test when current rank is not present in input mappings."""
|
||||
# Arrange
|
||||
rank_to_required = {1: ['file1.distcp'], 2: ['file2.distcp']}
|
||||
|
||||
rank_to_available_files = {1: ['file1.distcp'], 2: ['file2.distcp']}
|
||||
|
||||
self.mock_rank = 0 # Current rank not in mappings
|
||||
|
||||
# Act
|
||||
result = get_rank_to_read_files(
|
||||
rank_to_required, rank_to_available_files
|
||||
)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_duplicate_files_across_ranks(self):
|
||||
"""Test handling of duplicate files across different ranks."""
|
||||
# Arrange
|
||||
rank_to_required = {
|
||||
0: ['file1.distcp', 'file2.distcp'],
|
||||
1: ['file1.distcp', 'file3.distcp'], # file1 duplicated
|
||||
2: ['file4.distcp'],
|
||||
}
|
||||
|
||||
rank_to_available_files = {
|
||||
0: ['file1.distcp', 'file2.distcp'],
|
||||
1: ['file1.distcp', 'file3.distcp'],
|
||||
2: ['file4.distcp'],
|
||||
}
|
||||
|
||||
# Act
|
||||
result = get_rank_to_read_files(
|
||||
rank_to_required, rank_to_available_files
|
||||
)
|
||||
|
||||
# Assert
|
||||
# file1 should be assigned to only one rank (load balanced)
|
||||
self.assertIn('file1.distcp', result)
|
||||
# Should have exactly 2 files (file1 plus one more for balance)
|
||||
self.assertEqual(len(result), 2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,856 @@
|
||||
# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import paddle
|
||||
from paddle.distributed.flex_checkpoint.dcp.key_validation import (
|
||||
AOAMappingEntry,
|
||||
AOASliceMapping,
|
||||
KeyValidationResult,
|
||||
ShapeMismatchInfo,
|
||||
_append_src_lines,
|
||||
_build_aoa_mappings,
|
||||
_classify_mappings,
|
||||
_describe_ops,
|
||||
_emit,
|
||||
_format_key_list,
|
||||
_format_pattern_groups,
|
||||
_format_slice_range,
|
||||
_get_signature,
|
||||
_group_by_signature,
|
||||
_group_keys_adaptive,
|
||||
_print_aoa_report,
|
||||
_print_standard_report,
|
||||
_slice_covers_full,
|
||||
_try_fold_src_keys,
|
||||
validate_and_report_keys_aoa,
|
||||
validate_and_report_keys_standard,
|
||||
)
|
||||
from paddle.distributed.flex_checkpoint.dcp.metadata import (
|
||||
LocalTensorIndex,
|
||||
LocalTensorMetadata,
|
||||
Metadata,
|
||||
)
|
||||
|
||||
|
||||
class TestSliceCoversFull(unittest.TestCase):
|
||||
def test_covers_full(self):
|
||||
sl = (slice(0, 4), slice(0, 8))
|
||||
self.assertTrue(_slice_covers_full(sl, (4, 8)))
|
||||
|
||||
def test_not_covers_partial(self):
|
||||
sl = (slice(0, 2), slice(0, 8))
|
||||
self.assertFalse(_slice_covers_full(sl, (4, 8)))
|
||||
|
||||
def test_not_covers_mismatched_dims(self):
|
||||
sl = (slice(0, 4),)
|
||||
self.assertFalse(_slice_covers_full(sl, (4, 8)))
|
||||
|
||||
def test_non_zero_start(self):
|
||||
sl = (slice(1, 4), slice(0, 8))
|
||||
self.assertFalse(_slice_covers_full(sl, (4, 8)))
|
||||
|
||||
|
||||
class TestFormatSliceRange(unittest.TestCase):
|
||||
def test_basic(self):
|
||||
src_sl = (slice(0, 4), slice(0, 8))
|
||||
dst_sl = (slice(0, 4), slice(0, 8))
|
||||
result = _format_slice_range(src_sl, dst_sl)
|
||||
self.assertIn("0:4", result)
|
||||
self.assertIn("0:8", result)
|
||||
self.assertIn("->", result)
|
||||
|
||||
def test_partial_slices(self):
|
||||
src_sl = (slice(2, 6),)
|
||||
dst_sl = (slice(0, 4),)
|
||||
result = _format_slice_range(src_sl, dst_sl)
|
||||
self.assertIn("2:6", result)
|
||||
self.assertIn("0:4", result)
|
||||
|
||||
|
||||
class TestTryFoldSrcKeys(unittest.TestCase):
|
||||
def test_fold_consecutive(self):
|
||||
keys = [f"model.experts.{i}.weight" for i in range(8)]
|
||||
result = _try_fold_src_keys(keys)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertIn("{0..7}", result)
|
||||
|
||||
def test_no_fold_different_patterns(self):
|
||||
keys = ["model.a.weight", "model.b.weight"]
|
||||
result = _try_fold_src_keys(keys)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_no_fold_multiple_varying_positions(self):
|
||||
keys = ["layer.0.expert.0.w", "layer.1.expert.1.w"]
|
||||
result = _try_fold_src_keys(keys)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_single_key(self):
|
||||
result = _try_fold_src_keys(["a.0.b"])
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_empty(self):
|
||||
result = _try_fold_src_keys([])
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class TestDescribeOps(unittest.TestCase):
|
||||
def test_single_with_permute(self):
|
||||
entry = AOAMappingEntry(
|
||||
dst_key="a.weight",
|
||||
dst_global_shape=(4, 8),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
"b.weight",
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
["[1, 0]"],
|
||||
)
|
||||
],
|
||||
)
|
||||
result = _describe_ops(entry)
|
||||
self.assertIn("permute([1, 0])", result)
|
||||
|
||||
def test_concat_with_cast(self):
|
||||
entry = AOAMappingEntry(
|
||||
dst_key="a.weight",
|
||||
dst_global_shape=(8, 4),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
"b.weight",
|
||||
(slice(0, 4), slice(0, 4)),
|
||||
(slice(0, 4), slice(0, 4)),
|
||||
["bfloat16"],
|
||||
),
|
||||
AOASliceMapping(
|
||||
"c.weight",
|
||||
(slice(0, 4), slice(0, 4)),
|
||||
(slice(4, 8), slice(0, 4)),
|
||||
["bfloat16"],
|
||||
),
|
||||
],
|
||||
)
|
||||
result = _describe_ops(entry)
|
||||
self.assertIn("concat", result)
|
||||
self.assertIn("cast(bfloat16)", result)
|
||||
|
||||
def test_no_ops(self):
|
||||
entry = AOAMappingEntry(
|
||||
dst_key="a.weight",
|
||||
dst_global_shape=(4, 8),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
"b.weight",
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
None,
|
||||
)
|
||||
],
|
||||
)
|
||||
result = _describe_ops(entry)
|
||||
self.assertEqual(result, "")
|
||||
|
||||
def test_empty_slice_mappings(self):
|
||||
entry = AOAMappingEntry(
|
||||
dst_key="a.weight", dst_global_shape=(4,), slice_mappings=[]
|
||||
)
|
||||
result = _describe_ops(entry)
|
||||
self.assertEqual(result, "")
|
||||
|
||||
|
||||
class TestClassifyMappings(unittest.TestCase):
|
||||
def _make_entry(self, dst_key, src_key, pp=None, multi_src=False):
|
||||
if multi_src:
|
||||
sms = [
|
||||
AOASliceMapping(src_key, (slice(0, 4),), (slice(0, 4),), pp),
|
||||
AOASliceMapping(
|
||||
src_key + ".2", (slice(0, 4),), (slice(4, 8),), pp
|
||||
),
|
||||
]
|
||||
else:
|
||||
sms = [AOASliceMapping(src_key, (slice(0, 4),), (slice(0, 4),), pp)]
|
||||
return AOAMappingEntry(
|
||||
dst_key=dst_key, dst_global_shape=(8,), slice_mappings=sms
|
||||
)
|
||||
|
||||
def test_rename_only(self):
|
||||
entry = self._make_entry("model.layers.2.w", "model.layers.0.w")
|
||||
rename, transform, struct = _classify_mappings([entry])
|
||||
self.assertEqual(len(rename), 1)
|
||||
self.assertEqual(len(transform), 0)
|
||||
self.assertEqual(len(struct), 0)
|
||||
|
||||
def test_with_transform(self):
|
||||
entry = self._make_entry(
|
||||
"model.layers.2.w", "model.layers.0.w", ["[1, 0]"]
|
||||
)
|
||||
rename, transform, struct = _classify_mappings([entry])
|
||||
self.assertEqual(len(rename), 0)
|
||||
self.assertEqual(len(transform), 1)
|
||||
self.assertEqual(len(struct), 0)
|
||||
|
||||
def test_structural_multi_src(self):
|
||||
entry = self._make_entry(
|
||||
"model.layers.2.qkv", "model.layers.0.q", multi_src=True
|
||||
)
|
||||
rename, transform, struct = _classify_mappings([entry])
|
||||
self.assertEqual(len(rename), 0)
|
||||
self.assertEqual(len(transform), 0)
|
||||
self.assertEqual(len(struct), 1)
|
||||
|
||||
def test_structural_different_pattern(self):
|
||||
entry = self._make_entry("model.decoder.0.w", "model.encoder.0.w")
|
||||
rename, transform, struct = _classify_mappings([entry])
|
||||
self.assertEqual(len(struct), 1)
|
||||
|
||||
|
||||
class TestGroupBySignature(unittest.TestCase):
|
||||
def test_same_signature_grouped(self):
|
||||
entries = []
|
||||
for i in range(4):
|
||||
entries.append(
|
||||
AOAMappingEntry(
|
||||
dst_key=f"model.layers.{i}.w",
|
||||
dst_global_shape=(4,),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
f"src.layers.{i}.w",
|
||||
(slice(0, 4),),
|
||||
(slice(0, 4),),
|
||||
["[1, 0]"],
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
groups = _group_by_signature(entries)
|
||||
self.assertEqual(len(groups), 1)
|
||||
self.assertEqual(len(next(iter(groups.values()))), 4)
|
||||
|
||||
def test_different_signatures(self):
|
||||
e1 = AOAMappingEntry(
|
||||
dst_key="model.layers.0.w",
|
||||
dst_global_shape=(4,),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
"src.layers.0.w", (slice(0, 4),), (slice(0, 4),), None
|
||||
)
|
||||
],
|
||||
)
|
||||
e2 = AOAMappingEntry(
|
||||
dst_key="model.layers.0.qkv",
|
||||
dst_global_shape=(12,),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
"src.layers.0.q", (slice(0, 4),), (slice(0, 4),), None
|
||||
),
|
||||
AOASliceMapping(
|
||||
"src.layers.0.k", (slice(0, 4),), (slice(4, 8),), None
|
||||
),
|
||||
],
|
||||
)
|
||||
groups = _group_by_signature([e1, e2])
|
||||
self.assertEqual(len(groups), 2)
|
||||
|
||||
|
||||
class TestGetSignature(unittest.TestCase):
|
||||
def test_digits_normalized(self):
|
||||
entry = AOAMappingEntry(
|
||||
dst_key="model.layers.5.weight",
|
||||
dst_global_shape=(4,),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
"src.layers.5.weight", (slice(0, 4),), (slice(0, 4),), None
|
||||
)
|
||||
],
|
||||
)
|
||||
sig = _get_signature(entry)
|
||||
self.assertIn("{N}", sig)
|
||||
self.assertNotIn("5", sig)
|
||||
|
||||
|
||||
class TestFormatPatternGroups(unittest.TestCase):
|
||||
def test_basic_output(self):
|
||||
entries = [
|
||||
AOAMappingEntry(
|
||||
dst_key="model.layers.0.w",
|
||||
dst_global_shape=(4, 8),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
"src.layers.0.w",
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
["[1, 0]"],
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
groups = {"sig1": entries}
|
||||
lines, next_idx = _format_pattern_groups(groups, "test", 1)
|
||||
self.assertTrue(any("Pattern #1" in l for l in lines))
|
||||
self.assertEqual(next_idx, 2)
|
||||
|
||||
def test_numbering_continues(self):
|
||||
e1 = [
|
||||
AOAMappingEntry(
|
||||
dst_key="a.0.w",
|
||||
dst_global_shape=(4,),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
"b.0.w", (slice(0, 4),), (slice(0, 4),), None
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
e2 = [
|
||||
AOAMappingEntry(
|
||||
dst_key="c.0.w",
|
||||
dst_global_shape=(4,),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
"d.0.w", (slice(0, 4),), (slice(0, 4),), None
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
groups = {"sig1": e1, "sig2": e2}
|
||||
lines, next_idx = _format_pattern_groups(groups, "test", 5)
|
||||
self.assertEqual(next_idx, 7)
|
||||
|
||||
def test_max_patterns_truncation(self):
|
||||
import paddle.distributed.flex_checkpoint.dcp.key_validation as kv
|
||||
|
||||
old = kv._MAX_PATTERNS_SHOWN
|
||||
kv._MAX_PATTERNS_SHOWN = 2
|
||||
try:
|
||||
groups = {}
|
||||
for i in range(5):
|
||||
groups[f"sig{i}"] = [
|
||||
AOAMappingEntry(
|
||||
dst_key=f"x.{i}.w",
|
||||
dst_global_shape=(4,),
|
||||
slice_mappings=[
|
||||
AOASliceMapping(
|
||||
f"y.{i}.w", (slice(0, 4),), (slice(0, 4),), None
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
lines, _ = _format_pattern_groups(groups, "test", 1)
|
||||
self.assertTrue(any("more" in l for l in lines))
|
||||
finally:
|
||||
kv._MAX_PATTERNS_SHOWN = old
|
||||
|
||||
|
||||
class TestAppendSrcLines(unittest.TestCase):
|
||||
def test_few_srcs(self):
|
||||
sms = [
|
||||
AOASliceMapping("a.w", (slice(0, 4),), (slice(0, 4),), None),
|
||||
AOASliceMapping("b.w", (slice(0, 4),), (slice(4, 8),), None),
|
||||
]
|
||||
lines = []
|
||||
_append_src_lines(lines, sms)
|
||||
self.assertEqual(len(lines), 2)
|
||||
self.assertIn("SRC:", lines[0])
|
||||
self.assertIn("+", lines[1])
|
||||
|
||||
def test_many_srcs_foldable(self):
|
||||
sms = [
|
||||
AOASliceMapping(
|
||||
f"experts.{i}.w",
|
||||
(slice(0, 4),),
|
||||
(slice(i * 4, (i + 1) * 4),),
|
||||
None,
|
||||
)
|
||||
for i in range(10)
|
||||
]
|
||||
lines = []
|
||||
_append_src_lines(lines, sms)
|
||||
# Should fold into single line with ×N
|
||||
self.assertTrue(any("\u00d7" in l for l in lines))
|
||||
|
||||
def test_many_srcs_not_foldable(self):
|
||||
sms = [
|
||||
AOASliceMapping(
|
||||
"src_alpha.w", (slice(0, 4),), (slice(0, 4),), None
|
||||
),
|
||||
AOASliceMapping("src_beta.w", (slice(0, 4),), (slice(4, 8),), None),
|
||||
AOASliceMapping(
|
||||
"src_gamma.w", (slice(0, 4),), (slice(8, 12),), None
|
||||
),
|
||||
AOASliceMapping(
|
||||
"src_delta.w", (slice(0, 4),), (slice(12, 16),), None
|
||||
),
|
||||
AOASliceMapping(
|
||||
"src_epsilon.w", (slice(0, 4),), (slice(16, 20),), None
|
||||
),
|
||||
AOASliceMapping(
|
||||
"src_zeta.w", (slice(0, 4),), (slice(20, 24),), None
|
||||
),
|
||||
]
|
||||
lines = []
|
||||
_append_src_lines(lines, sms)
|
||||
# Should show first 2, ..., last 1
|
||||
self.assertTrue(any("more" in l for l in lines))
|
||||
|
||||
|
||||
class TestGroupKeysAdaptive(unittest.TestCase):
|
||||
def test_basic_grouping(self):
|
||||
keys = [
|
||||
"model.layers.0.weight",
|
||||
"model.layers.1.weight",
|
||||
"model.layers.2.weight",
|
||||
"model.embed.weight",
|
||||
]
|
||||
groups = _group_keys_adaptive(keys)
|
||||
self.assertEqual(len(groups), 2)
|
||||
# layers.* grouped together
|
||||
layer_group = [g for g in groups.values() if len(g) == 3]
|
||||
self.assertEqual(len(layer_group), 1)
|
||||
|
||||
def test_no_digits(self):
|
||||
keys = ["model.weight", "model.bias"]
|
||||
groups = _group_keys_adaptive(keys)
|
||||
self.assertEqual(len(groups), 2)
|
||||
|
||||
|
||||
class TestFormatKeyList(unittest.TestCase):
|
||||
def test_few_keys(self):
|
||||
keys = {"a.w", "b.w", "c.w"}
|
||||
lines = _format_key_list(keys)
|
||||
self.assertEqual(len(lines), 3)
|
||||
|
||||
def test_many_keys_grouped(self):
|
||||
keys = {f"model.layers.{i}.weight" for i in range(100)}
|
||||
lines = _format_key_list(keys)
|
||||
# Should be grouped and folded
|
||||
self.assertTrue(len(lines) < 100)
|
||||
self.assertTrue(any("[" in l for l in lines))
|
||||
|
||||
def test_empty(self):
|
||||
lines = _format_key_list(set())
|
||||
self.assertEqual(lines, [])
|
||||
|
||||
|
||||
class TestEmit(unittest.TestCase):
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation.logger")
|
||||
def test_normal_output(self, mock_logger):
|
||||
lines = ["line1", "line2", "line3"]
|
||||
_emit(lines)
|
||||
self.assertEqual(mock_logger.info.call_count, 3)
|
||||
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation.logger")
|
||||
def test_truncation(self, mock_logger):
|
||||
import paddle.distributed.flex_checkpoint.dcp.key_validation as kv
|
||||
|
||||
old_max = kv._MAX_TOTAL_LINES
|
||||
kv._MAX_TOTAL_LINES = 5
|
||||
try:
|
||||
lines = ["x"] * 20
|
||||
_emit(lines)
|
||||
# 5 lines + 1 truncation msg = 6
|
||||
self.assertEqual(mock_logger.info.call_count, 6)
|
||||
finally:
|
||||
kv._MAX_TOTAL_LINES = old_max
|
||||
|
||||
|
||||
class TestPrintStandardReport(unittest.TestCase):
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_all_matched(self, mock_emit):
|
||||
result = KeyValidationResult()
|
||||
_print_standard_report(result, "/tmp/ckpt", 100)
|
||||
lines = mock_emit.call_args[0][0]
|
||||
self.assertTrue(any("[OK]" in l for l in lines))
|
||||
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_with_missing_and_unexpected(self, mock_emit):
|
||||
result = KeyValidationResult(
|
||||
missing_keys={"a.w", "b.w"},
|
||||
unexpected_keys={"c.w"},
|
||||
shape_mismatches=[ShapeMismatchInfo("d.w", (4, 8), (4, 16))],
|
||||
)
|
||||
_print_standard_report(result, "/tmp/ckpt", 100)
|
||||
lines = mock_emit.call_args[0][0]
|
||||
self.assertTrue(any("Missing" in l for l in lines))
|
||||
self.assertTrue(any("Unexpected" in l for l in lines))
|
||||
self.assertTrue(any("Shape" in l for l in lines))
|
||||
self.assertTrue(any("Matched: 98/100" in l for l in lines))
|
||||
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_shape_mismatch_truncation(self, mock_emit):
|
||||
import paddle.distributed.flex_checkpoint.dcp.key_validation as kv
|
||||
|
||||
old = kv._MAX_SHAPE_MISMATCHES
|
||||
kv._MAX_SHAPE_MISMATCHES = 2
|
||||
try:
|
||||
mismatches = [
|
||||
ShapeMismatchInfo(f"k{i}", (4,), (8,)) for i in range(5)
|
||||
]
|
||||
result = KeyValidationResult(
|
||||
missing_keys={"x"}, shape_mismatches=mismatches
|
||||
)
|
||||
_print_standard_report(result, "/tmp/ckpt", 10)
|
||||
lines = mock_emit.call_args[0][0]
|
||||
self.assertTrue(any("and 3 more" in l for l in lines))
|
||||
finally:
|
||||
kv._MAX_SHAPE_MISMATCHES = old
|
||||
|
||||
|
||||
class TestPrintAoaReport(unittest.TestCase):
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_all_resolved(self, mock_emit):
|
||||
mappings = [
|
||||
AOAMappingEntry(
|
||||
dst_key="a.w",
|
||||
dst_global_shape=(4,),
|
||||
slice_mappings=[
|
||||
AOASliceMapping("b.w", (slice(0, 4),), (slice(0, 4),), None)
|
||||
],
|
||||
is_identity=False,
|
||||
),
|
||||
]
|
||||
result = KeyValidationResult()
|
||||
_print_aoa_report(result, mappings, set(), "/tmp/ckpt")
|
||||
lines = mock_emit.call_args[0][0]
|
||||
self.assertTrue(any("[OK]" in l for l in lines))
|
||||
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_with_missing(self, mock_emit):
|
||||
mappings = [
|
||||
AOAMappingEntry(
|
||||
"a.w",
|
||||
(4,),
|
||||
[AOASliceMapping("b.w", (slice(0, 4),), (slice(0, 4),), None)],
|
||||
)
|
||||
]
|
||||
result = KeyValidationResult(
|
||||
missing_keys={"c.w"}, unexpected_keys={"d.w"}
|
||||
)
|
||||
_print_aoa_report(result, mappings, {"removed.w"}, "/tmp/ckpt")
|
||||
lines = mock_emit.call_args[0][0]
|
||||
self.assertTrue(any("Missing" in l for l in lines))
|
||||
self.assertTrue(any("Unexpected" in l for l in lines))
|
||||
self.assertTrue(any("Removed" in l for l in lines))
|
||||
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_randomly_initialized_keys(self, mock_emit):
|
||||
mappings = []
|
||||
result = KeyValidationResult(
|
||||
randomly_initialized_keys={"init.w", "init.b"}
|
||||
)
|
||||
_print_aoa_report(result, mappings, set(), "/tmp/ckpt")
|
||||
lines = mock_emit.call_args[0][0]
|
||||
self.assertTrue(any("Initialized (2)" in l for l in lines))
|
||||
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_removed_keys_truncation(self, mock_emit):
|
||||
mappings = []
|
||||
removed = {f"removed.key.{i}" for i in range(10)}
|
||||
result = KeyValidationResult()
|
||||
_print_aoa_report(result, mappings, removed, "/tmp/ckpt")
|
||||
lines = mock_emit.call_args[0][0]
|
||||
self.assertTrue(any("more" in l for l in lines))
|
||||
|
||||
|
||||
class TestBuildAoaMappings(unittest.TestCase):
|
||||
def test_basic(self):
|
||||
engine = MagicMock()
|
||||
td1 = MagicMock()
|
||||
td1.shape = [4, 8]
|
||||
td1.slices = [
|
||||
(
|
||||
"src.w",
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
None,
|
||||
)
|
||||
]
|
||||
td2 = MagicMock()
|
||||
td2.shape = [8, 8]
|
||||
td2.slices = [
|
||||
(
|
||||
"src.q",
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
["[1, 0]"],
|
||||
),
|
||||
(
|
||||
"src.k",
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
(slice(4, 8), slice(0, 8)),
|
||||
["[1, 0]"],
|
||||
),
|
||||
]
|
||||
ov = MagicMock()
|
||||
ov.items.return_value = sorted({"dst.qkv": td2, "dst.w": td1}.items())
|
||||
engine.output_vars = ov
|
||||
|
||||
results = _build_aoa_mappings(engine)
|
||||
self.assertEqual(len(results), 2)
|
||||
qkv = next(r for r in results if r.dst_key == "dst.qkv")
|
||||
self.assertFalse(qkv.is_identity)
|
||||
self.assertEqual(len(qkv.slice_mappings), 2)
|
||||
|
||||
def test_identity_detection(self):
|
||||
engine = MagicMock()
|
||||
td = MagicMock()
|
||||
td.shape = [4, 8]
|
||||
td.slices = [
|
||||
(
|
||||
"same.key",
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
None,
|
||||
)
|
||||
]
|
||||
ov = MagicMock()
|
||||
ov.items.return_value = [("same.key", td)]
|
||||
engine.output_vars = ov
|
||||
|
||||
results = _build_aoa_mappings(engine)
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertTrue(results[0].is_identity)
|
||||
|
||||
def test_none_tensor_desc_skipped(self):
|
||||
engine = MagicMock()
|
||||
ov = MagicMock()
|
||||
ov.items.return_value = [("a", None), ("b", None)]
|
||||
engine.output_vars = ov
|
||||
results = _build_aoa_mappings(engine)
|
||||
self.assertEqual(len(results), 0)
|
||||
|
||||
|
||||
class TestValidateAndReportKeysStandard(unittest.TestCase):
|
||||
def _make_metadata(self, keys_shapes):
|
||||
"""keys_shapes: dict of {key: shape_tuple}"""
|
||||
storage_metadata = {}
|
||||
state_dict_metadata = {}
|
||||
for key, shape in keys_shapes.items():
|
||||
idx = LocalTensorIndex(
|
||||
tensor_key=key,
|
||||
global_offset=tuple([0] * len(shape)),
|
||||
replica_id=0,
|
||||
)
|
||||
storage_metadata[idx] = f"{key}.distcp"
|
||||
state_dict_metadata[key] = [
|
||||
LocalTensorMetadata(
|
||||
global_offset=tuple([0] * len(shape)),
|
||||
local_shape=shape,
|
||||
dtype="float32",
|
||||
global_shape=shape,
|
||||
)
|
||||
]
|
||||
return Metadata(
|
||||
state_dict_metadata=state_dict_metadata,
|
||||
storage_metadata=storage_metadata,
|
||||
)
|
||||
|
||||
@patch("paddle.distributed.get_rank", return_value=0)
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_all_match(self, mock_emit, mock_rank):
|
||||
metadata = self._make_metadata({"w1": (4, 8), "w2": (4, 8)})
|
||||
state_dict = {
|
||||
"w1": paddle.zeros([4, 8]),
|
||||
"w2": paddle.zeros([4, 8]),
|
||||
}
|
||||
result = validate_and_report_keys_standard(
|
||||
[metadata], {"w1", "w2"}, None, False, "/tmp/ckpt", state_dict
|
||||
)
|
||||
self.assertEqual(len(result.missing_keys), 0)
|
||||
self.assertEqual(len(result.unexpected_keys), 0)
|
||||
|
||||
@patch("paddle.distributed.get_rank", return_value=0)
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_missing_keys(self, mock_emit, mock_rank):
|
||||
metadata = self._make_metadata({"w1": (4,)})
|
||||
state_dict = {
|
||||
"w1": paddle.zeros([4]),
|
||||
"w2": paddle.zeros([4]),
|
||||
}
|
||||
result = validate_and_report_keys_standard(
|
||||
[metadata], {"w1", "w2"}, None, False, "/tmp/ckpt", state_dict
|
||||
)
|
||||
self.assertIn("w2", result.missing_keys)
|
||||
|
||||
@patch("paddle.distributed.get_rank", return_value=0)
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_unexpected_keys(self, mock_emit, mock_rank):
|
||||
metadata = self._make_metadata({"w1": (4,), "w2": (4,), "w3": (4,)})
|
||||
state_dict = {"w1": paddle.zeros([4])}
|
||||
result = validate_and_report_keys_standard(
|
||||
[metadata], {"w1"}, None, False, "/tmp/ckpt", state_dict
|
||||
)
|
||||
self.assertIn("w2", result.unexpected_keys)
|
||||
self.assertIn("w3", result.unexpected_keys)
|
||||
|
||||
@patch("paddle.distributed.get_rank", return_value=0)
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_shape_mismatch(self, mock_emit, mock_rank):
|
||||
metadata = self._make_metadata({"w1": (4, 8)})
|
||||
state_dict = {"w1": paddle.zeros([4, 16])}
|
||||
result = validate_and_report_keys_standard(
|
||||
[metadata], {"w1"}, None, False, "/tmp/ckpt", state_dict
|
||||
)
|
||||
self.assertEqual(len(result.shape_mismatches), 1)
|
||||
self.assertEqual(result.shape_mismatches[0].src_global_shape, (4, 8))
|
||||
self.assertEqual(result.shape_mismatches[0].dst_global_shape, (4, 16))
|
||||
|
||||
@patch("paddle.distributed.get_rank", return_value=0)
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_replica_id_filtered(self, mock_emit, mock_rank):
|
||||
"""Keys with replica_id != 0 should be filtered out."""
|
||||
storage_metadata = {
|
||||
LocalTensorIndex(
|
||||
tensor_key="w1", global_offset=(0,), replica_id=0
|
||||
): "f1",
|
||||
LocalTensorIndex(
|
||||
tensor_key="w2", global_offset=(0,), replica_id=1
|
||||
): "f2",
|
||||
}
|
||||
metadata = Metadata(
|
||||
state_dict_metadata={
|
||||
"w1": [LocalTensorMetadata((0,), (4,), "float32", (4,))]
|
||||
},
|
||||
storage_metadata=storage_metadata,
|
||||
)
|
||||
state_dict = {"w1": paddle.zeros([4])}
|
||||
result = validate_and_report_keys_standard(
|
||||
[metadata], {"w1"}, None, False, "/tmp/ckpt", state_dict
|
||||
)
|
||||
self.assertEqual(len(result.unexpected_keys), 0)
|
||||
|
||||
@patch(
|
||||
"paddle.distributed.flex_checkpoint.dcp.key_validation._get_rank",
|
||||
return_value=1,
|
||||
)
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
@patch("paddle.distributed.all_gather_object")
|
||||
def test_non_rank0_no_print(self, mock_gather, mock_emit, mock_rank):
|
||||
metadata = self._make_metadata({"w1": (4,)})
|
||||
state_dict = {"w1": paddle.zeros([4])}
|
||||
|
||||
def gather_side_effect(out_list, obj, group=None):
|
||||
out_list.clear()
|
||||
out_list.append(obj)
|
||||
|
||||
mock_gather.side_effect = gather_side_effect
|
||||
validate_and_report_keys_standard(
|
||||
[metadata], {"w1"}, None, True, "/tmp/ckpt", state_dict
|
||||
)
|
||||
mock_emit.assert_not_called()
|
||||
|
||||
|
||||
class TestValidateAndReportKeysAoa(unittest.TestCase):
|
||||
def _make_mock_engine(self):
|
||||
engine = MagicMock()
|
||||
td1 = MagicMock()
|
||||
td1.shape = [4, 8]
|
||||
td1.slices = [
|
||||
(
|
||||
"src.w1",
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
None,
|
||||
)
|
||||
]
|
||||
td2 = MagicMock()
|
||||
td2.shape = [8, 8]
|
||||
td2.slices = [
|
||||
(
|
||||
"src.q",
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
["[1, 0]"],
|
||||
),
|
||||
(
|
||||
"src.k",
|
||||
(slice(0, 4), slice(0, 8)),
|
||||
(slice(4, 8), slice(0, 8)),
|
||||
["[1, 0]"],
|
||||
),
|
||||
]
|
||||
# output_vars: need .items() for _build_aoa_mappings and iteration for values()
|
||||
ov = MagicMock()
|
||||
ov.items.return_value = sorted({"dst.w1": td1, "dst.qkv": td2}.items())
|
||||
ov.values.return_value = [td1, td2]
|
||||
ov.__iter__ = lambda self: iter({"dst.w1": td1, "dst.qkv": td2})
|
||||
ov.__getitem__ = lambda self, k: {"dst.w1": td1, "dst.qkv": td2}[k]
|
||||
engine.output_vars = ov
|
||||
engine.need_add_output_vars = ["dst.init"]
|
||||
engine.need_remove_input_vars = ["src.removed"]
|
||||
engine.input_vars = MagicMock()
|
||||
engine.input_vars.keys.return_value = [
|
||||
"src.w1",
|
||||
"src.q",
|
||||
"src.k",
|
||||
"src.removed",
|
||||
"src.leftover",
|
||||
]
|
||||
engine.context = MagicMock()
|
||||
engine.context.get_all_dst_state_keys.return_value = {
|
||||
"dst.w1",
|
||||
"dst.qkv",
|
||||
"dst.init",
|
||||
}
|
||||
return engine
|
||||
|
||||
@patch("paddle.distributed.get_rank", return_value=0)
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_all_resolved(self, mock_emit, mock_rank):
|
||||
engine = self._make_mock_engine()
|
||||
metadata = MagicMock()
|
||||
result = validate_and_report_keys_aoa(engine, metadata, "/tmp/ckpt")
|
||||
# dst.w1 and dst.qkv are covered; dst.init is randomly initialized
|
||||
self.assertEqual(len(result.missing_keys), 0)
|
||||
# src.leftover not consumed and not removed
|
||||
self.assertIn("src.leftover", result.unexpected_keys)
|
||||
self.assertIn("dst.init", result.randomly_initialized_keys)
|
||||
|
||||
@patch("paddle.distributed.get_rank", return_value=0)
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_truly_missing(self, mock_emit, mock_rank):
|
||||
engine = self._make_mock_engine()
|
||||
# Add a dst key that is NOT covered
|
||||
engine.context.get_all_dst_state_keys = lambda: {
|
||||
"dst.w1",
|
||||
"dst.qkv",
|
||||
"dst.init",
|
||||
"dst.missing",
|
||||
}
|
||||
metadata = MagicMock()
|
||||
result = validate_and_report_keys_aoa(engine, metadata, "/tmp/ckpt")
|
||||
self.assertIn("dst.missing", result.missing_keys)
|
||||
|
||||
@patch("paddle.distributed.get_rank", return_value=1)
|
||||
@patch("paddle.distributed.flex_checkpoint.dcp.key_validation._emit")
|
||||
def test_non_rank0_no_print(self, mock_emit, mock_rank):
|
||||
engine = self._make_mock_engine()
|
||||
metadata = MagicMock()
|
||||
validate_and_report_keys_aoa(engine, metadata, "/tmp/ckpt")
|
||||
mock_emit.assert_not_called()
|
||||
|
||||
|
||||
class TestColorHelpers(unittest.TestCase):
|
||||
def test_no_color(self):
|
||||
from paddle.distributed.flex_checkpoint.dcp.key_validation import _C
|
||||
|
||||
self.assertEqual(_C.green("test"), "test")
|
||||
self.assertEqual(_C.yellow("test"), "test")
|
||||
self.assertEqual(_C.red("test"), "test")
|
||||
self.assertEqual(_C.cyan("test"), "test")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,63 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
import collective.test_communication_api_base as test_base
|
||||
|
||||
|
||||
class TestLoadStateDictTranspose(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=2)
|
||||
|
||||
def test_metadata(self):
|
||||
envs = {
|
||||
"aoa_statements": "linear.weight^T -> linear.weight",
|
||||
}
|
||||
self.run_test_case(
|
||||
"load_state_dict_transpose_logic.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
class TestLoadStateDictCast(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=2)
|
||||
|
||||
def test_cast(self):
|
||||
envs = {
|
||||
"aoa_statements": 'linear.weight -> linear.weight, dtype="float16"',
|
||||
}
|
||||
self.run_test_case(
|
||||
"load_state_dict_cast_logic.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
class TestLoadStateDictTransposeCast(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=2)
|
||||
|
||||
def test_transpose_cast(self):
|
||||
envs = {
|
||||
"aoa_statements": 'linear.weight^T -> linear.weight, dtype="float16"',
|
||||
}
|
||||
self.run_test_case(
|
||||
"load_state_dict_transpose_cast_logic.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,580 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from paddle.distributed.flex_checkpoint.aoa.aoa_engine import (
|
||||
AOAShardInfoContext,
|
||||
)
|
||||
from paddle.distributed.flex_checkpoint.aoa.lexer import Lexer
|
||||
from paddle.distributed.flex_checkpoint.aoa.macros import macro_registry
|
||||
from paddle.distributed.flex_checkpoint.dcp.sharded_weight import (
|
||||
ShardedWeightDesc,
|
||||
)
|
||||
|
||||
|
||||
class MacroContext:
|
||||
def __init__(self):
|
||||
self.source_keys = {
|
||||
"embed_tokens.weight",
|
||||
"layers.1.mlp.gate_up_fused_proj.weight",
|
||||
"layers.1.post_attention_layernorm.weight",
|
||||
"layers.2.self_attn.qkv_proj.weight",
|
||||
"layers.2.self_attn.o_proj.weight",
|
||||
"layers.2.mlp.gate_up_fused_proj.weight",
|
||||
"layers.2.mlp.down_proj.weight",
|
||||
"layers.2.input_layernorm.weight",
|
||||
"layers.1.mlp.gate_up_fused_proj.weight_test1",
|
||||
"layers.2.post_attention_layernorm.weight",
|
||||
"layers.1.experts.0.weight",
|
||||
"layers.0.qkv_proj.weight",
|
||||
"fused_qkv_old_test_name",
|
||||
"layers.shared.qkv_proj.weight",
|
||||
"layers.5.experts.0.up_gate_proj.weight",
|
||||
"layers.5.experts.1.up_gate_proj.weight",
|
||||
"layers.2.experts.0.weight",
|
||||
"layers.2.experts.1.weight",
|
||||
"layers.2.self_attn.qkv_proj.bias",
|
||||
"layers.2.mlp.gate_up_fused_proj.bias",
|
||||
"layers.3.experts.0.up_gate_proj.weight",
|
||||
"layers.3.experts.1.up_gate_proj.weight",
|
||||
}
|
||||
|
||||
self.dst_keys = {
|
||||
"embed_tokens.weight",
|
||||
"layers.0.self_attn.qkv_proj.weight",
|
||||
"layers.0.self_attn.o_proj.weight",
|
||||
"layers.0.mlp.gate_up_fused_proj.weight",
|
||||
"layers.0.mlp.down_proj.weight",
|
||||
"layers.0.input_layernorm.weight",
|
||||
"layers.0.post_attention_layernorm.weight",
|
||||
"layers.1.mlp.gate_up_fused_proj.weight",
|
||||
"layers.1.mlp.gate_up_fused_proj.weight_test2",
|
||||
"layers.1.post_attention_layernorm.weight",
|
||||
"layers.0.experts.0.weight",
|
||||
"layers.0.experts.1.weight",
|
||||
"layers.1.experts.0.weight",
|
||||
"layers.0.q_proj.weight",
|
||||
"layers.0.k_proj.weight",
|
||||
"layers.0.v_proj.weight",
|
||||
"q_test_name",
|
||||
"k_test_name",
|
||||
"v_test_name",
|
||||
"layers.0.shared.q_proj.weight",
|
||||
"layers.0.shared.k_proj.weight",
|
||||
"layers.0.shared.v_proj.weight",
|
||||
"layers.1.shared.q_proj.weight",
|
||||
"layers.1.shared.k_proj.weight",
|
||||
"layers.1.shared.v_proj.weight",
|
||||
"layers.5.experts.0.gate_proj.weight",
|
||||
"layers.5.experts.1.gate_proj.weight",
|
||||
"layers.5.experts.0.up_proj.weight",
|
||||
"layers.5.experts.1.up_proj.weight",
|
||||
"layers.2.self_attn.qkv_proj.weight",
|
||||
"layers.2.self_attn.qkv_proj.bias",
|
||||
"layers.2.mlp.gate_up_fused_proj.bias",
|
||||
"layers.2.mlp.gate_up_fused_proj.weight",
|
||||
"layers.3.experts.0.up_gate_proj.weight",
|
||||
"layers.3.experts.1.up_gate_proj.weight",
|
||||
}
|
||||
|
||||
# Build _ShardInfo mapping for AOAShardInfoContext based on existing keys
|
||||
def make_shard_info(keys: set[str], num_shards: int):
|
||||
shard_info: dict[str, list[ShardedWeightDesc]] = {}
|
||||
for k in keys:
|
||||
descs: list[ShardedWeightDesc] = []
|
||||
for i in range(num_shards):
|
||||
descs.append(
|
||||
ShardedWeightDesc(
|
||||
key=k,
|
||||
local_shape=(1,),
|
||||
global_shape=(num_shards,),
|
||||
global_offset=(i,),
|
||||
)
|
||||
)
|
||||
shard_info[k] = descs
|
||||
return shard_info
|
||||
|
||||
self.source_state_shard_info = make_shard_info(self.source_keys, 2)
|
||||
self.destination_state_shard_info = make_shard_info(self.dst_keys, 4)
|
||||
|
||||
self._ctx = AOAShardInfoContext(
|
||||
source_state_shard_info=self.source_state_shard_info,
|
||||
destination_state_shard_info=self.destination_state_shard_info,
|
||||
)
|
||||
|
||||
def set_aoa_config_reverse(
|
||||
self,
|
||||
): # when aoa_config_reverse is True, the src and dst of AOAShardInfoContext are reversed
|
||||
self._ctx = AOAShardInfoContext(
|
||||
source_state_shard_info=self.destination_state_shard_info,
|
||||
destination_state_shard_info=self.source_state_shard_info,
|
||||
)
|
||||
self._ctx.aoa_config_reverse = True
|
||||
|
||||
|
||||
def get_macro(macro_name):
|
||||
for macro in macro_registry.macros:
|
||||
if macro["name"] == macro_name:
|
||||
return macro["func"]
|
||||
raise ValueError(f"Macro '{macro_name}' not found.")
|
||||
|
||||
|
||||
class TestMacro(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.macro_func = None
|
||||
self.source = None
|
||||
self.expected_expanded = None
|
||||
|
||||
def macro_name(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def source_code(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def expected(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def start_macro_test(self, aoa_config_reverse: bool = False):
|
||||
self.macro_func = get_macro(self.macro_name())
|
||||
self.source = self.source_code()
|
||||
self.expected_expanded = self.expected()
|
||||
self.ctx = MacroContext()
|
||||
if aoa_config_reverse:
|
||||
self.ctx.set_aoa_config_reverse()
|
||||
self.lexer = Lexer(self.ctx._ctx)
|
||||
self.lexer.apply_macro(
|
||||
self.source, get_macro("get_var_mapping_chain_macro")
|
||||
)
|
||||
else:
|
||||
self.lexer = Lexer(self.ctx._ctx)
|
||||
actual_expanded = self.lexer.apply_macro(self.source, self.macro_func)
|
||||
self.assertEqual(actual_expanded, self.expected_expanded)
|
||||
|
||||
|
||||
class TestStarMacro(TestMacro):
|
||||
def macro_name(self):
|
||||
return "star_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.2.experts.*.weight -> fused_experts, axis = 1"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.2.experts.0.weight,layers.2.experts.1.weight->fused_experts,axis=1\n'
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestLayerIdMacro(TestMacro):
|
||||
def macro_name(self):
|
||||
return "id_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.$LAYER_ID.qkv_proj.weight->layers.$LAYER_ID.q_proj.weight,layer.$LAYER_ID.k_proj.weight,layer.$LAYER_ID.v_proj.weight\n"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.0.qkv_proj.weight->layers.0.q_proj.weight,layer.0.k_proj.weight,layer.0.v_proj.weight\n',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class Test_expert_id_Macro(TestMacro):
|
||||
def macro_name(self):
|
||||
return "id_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.5.experts.$EXPERT_ID.up_gate_proj.weight -> layers.5.experts.$EXPERT_ID.gate_proj.weight, layers.5.experts.$EXPERT_ID.up_proj.weight"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.5.experts.0.up_gate_proj.weight->layers.5.experts.0.gate_proj.weight,layers.5.experts.0.up_proj.weight\n',
|
||||
'layers.5.experts.1.up_gate_proj.weight->layers.5.experts.1.gate_proj.weight,layers.5.experts.1.up_proj.weight\n',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class Test_ID_macro_reverse(TestMacro):
|
||||
def macro_name(self):
|
||||
return "id_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.5.experts.$EXPERT_ID.up_gate_proj.weight -> layers.5.experts.$EXPERT_ID.gate_proj.weight, layers.5.experts.$EXPERT_ID.up_proj.weight"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.5.experts.0.up_gate_proj.weight->layers.5.experts.0.gate_proj.weight,layers.5.experts.0.up_proj.weight\n',
|
||||
'layers.5.experts.1.up_gate_proj.weight->layers.5.experts.1.gate_proj.weight,layers.5.experts.1.up_proj.weight\n',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test(aoa_config_reverse=True)
|
||||
|
||||
|
||||
class TestFusedQkvOldMacro(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_qkv_old_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.2.self_attn.qkv_proj.weight -> layers.2.self_attn.qkv_proj.weight, fused_qkv_old, num_heads = 8, num_key_value_groups = 4"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.2.self_attn.qkv_proj.weight -> fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.V_3, axis=1',
|
||||
'fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_3 -> layers.2.self_attn.qkv_proj.weight, axis=1',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestTransposeMacro(TestMacro):
|
||||
def macro_name(self):
|
||||
return "transpose_macro"
|
||||
|
||||
def source_code(self):
|
||||
return (
|
||||
"layers.2.mlp.down_proj.weight^T -> layers.2.mlp.down_proj.weight_T"
|
||||
)
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.2.mlp.down_proj.weight -> layers.2.mlp.down_proj.weight_transpose_tmp, permute = "[]"',
|
||||
'layers.2.mlp.down_proj.weight_transpose_tmp->layers.2.mlp.down_proj.weight_T\n',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedQKVMacro(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_qkv_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.2.self_attn.qkv_proj.weight -> Q, K, V, fused_qkv, num_heads = 8, num_key_value_groups = 2"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.2.self_attn.qkv_proj.weight -> Q0,Q1,Q2,Q3,K0,V0,Q4,Q5,Q6,Q7,K1,V1, axis=1',
|
||||
'Q0,Q1,Q2,Q3,Q4,Q5,Q6,Q7 -> Q, axis=1',
|
||||
'K0,K1 -> K, axis=1',
|
||||
'V0,V1 -> V, axis=1',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedQKVMacro2(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_qkv_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "Q, K, V -> layers.2.self_attn.qkv_proj.weight, fused_qkv, num_heads = 8, num_key_value_groups = 8"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'Q -> Q0,Q1,Q2,Q3,Q4,Q5,Q6,Q7, axis=1',
|
||||
'K -> K0,K1,K2,K3,K4,K5,K6,K7, axis=1',
|
||||
'V -> V0,V1,V2,V3,V4,V5,V6,V7, axis=1',
|
||||
'Q0,K0,V0,Q1,K1,V1,Q2,K2,V2,Q3,K3,V3,Q4,K4,V4,Q5,K5,V5,Q6,K6,V6,Q7,K7,V7 -> layers.2.self_attn.qkv_proj.weight, axis=1',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedQkvOldMacro2(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_qkv_old_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "Q,K,V -> layers.2.self_attn.qkv_proj.weight, fused_qkv_old, num_heads = 8, num_key_value_groups = 4"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'Q,K,V -> Q.K.V.tmp, axis=1',
|
||||
'Q.K.V.tmp -> fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.V_3, axis=1',
|
||||
'fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_3 -> layers.2.self_attn.qkv_proj.weight, axis=1',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedQkvOldMacro3(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_qkv_old_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "fused_qkv_old_test_name -> q_test_name ,k_test_name, v_test_name, fused_qkv_old, num_heads = 8, num_key_value_groups = 4 "
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'fused_qkv_old_test_name -> fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.V_3, axis=1',
|
||||
'fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7 -> q_test_name, axis=1',
|
||||
'fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.K_3 -> k_test_name, axis=1',
|
||||
'fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.V_3 -> v_test_name, axis=1',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedQkvOldMacro4(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_qkv_old_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "fused_qkv_old_test_name -> layers.2.self_attn.qkv_proj.weight,fused_qkv_old, num_heads = 8, num_key_value_groups = 8 "
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'fused_qkv_old_test_name -> fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.V_3,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_4,fused_qkv_old_tmp.K_5,fused_qkv_old_tmp.K_6,fused_qkv_old_tmp.K_7,fused_qkv_old_tmp.V_4,fused_qkv_old_tmp.V_5,fused_qkv_old_tmp.V_6,fused_qkv_old_tmp.V_7, axis=1',
|
||||
'fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.V_3,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.K_4,fused_qkv_old_tmp.K_5,fused_qkv_old_tmp.V_4,fused_qkv_old_tmp.V_5,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_6,fused_qkv_old_tmp.K_7,fused_qkv_old_tmp.V_6,fused_qkv_old_tmp.V_7 -> layers.2.self_attn.qkv_proj.weight, axis=1',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedQkvOldMacro5(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_qkv_old_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.2.self_attn.qkv_proj.bias -> layers.2.self_attn.qkv_proj.bias, fused_qkv_old, num_heads = 8, num_key_value_groups = 4, axis = 0"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.2.self_attn.qkv_proj.bias -> fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.V_3, axis=0',
|
||||
'fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_3 -> layers.2.self_attn.qkv_proj.bias, axis=0',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedQkvOldMacro6(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_qkv_old_macro"
|
||||
|
||||
def source_code(self):
|
||||
return [
|
||||
"fused_qkv_old_test_name -> A_TEST_NAME,fused_qkv_old, num_heads = 8, num_key_value_groups = 8 ",
|
||||
"A_TEST_NAME -> layers.2.self_attn.qkv_proj.weight",
|
||||
]
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'fused_qkv_old_test_name -> fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.V_3,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_4,fused_qkv_old_tmp.K_5,fused_qkv_old_tmp.K_6,fused_qkv_old_tmp.K_7,fused_qkv_old_tmp.V_4,fused_qkv_old_tmp.V_5,fused_qkv_old_tmp.V_6,fused_qkv_old_tmp.V_7, axis=1',
|
||||
'fused_qkv_old_tmp.Q_0,fused_qkv_old_tmp.Q_1,fused_qkv_old_tmp.K_0,fused_qkv_old_tmp.K_1,fused_qkv_old_tmp.V_0,fused_qkv_old_tmp.V_1,fused_qkv_old_tmp.Q_2,fused_qkv_old_tmp.Q_3,fused_qkv_old_tmp.K_2,fused_qkv_old_tmp.K_3,fused_qkv_old_tmp.V_2,fused_qkv_old_tmp.V_3,fused_qkv_old_tmp.Q_4,fused_qkv_old_tmp.Q_5,fused_qkv_old_tmp.K_4,fused_qkv_old_tmp.K_5,fused_qkv_old_tmp.V_4,fused_qkv_old_tmp.V_5,fused_qkv_old_tmp.Q_6,fused_qkv_old_tmp.Q_7,fused_qkv_old_tmp.K_6,fused_qkv_old_tmp.K_7,fused_qkv_old_tmp.V_6,fused_qkv_old_tmp.V_7 -> A_TEST_NAME, axis=1',
|
||||
'A_TEST_NAME -> layers.2.self_attn.qkv_proj.weight',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test(aoa_config_reverse=True)
|
||||
|
||||
|
||||
class TestFusedFfnMacro(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_ffn_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.2.mlp.gate_up_fused_proj.weight -> layers.2.mlp.gate_up_fused_proj.weight, fused_ffn"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.2.mlp.gate_up_fused_proj.weight -> fused_ffn_tmp.GATE_0,fused_ffn_tmp.GATE_1,fused_ffn_tmp.UP_0,fused_ffn_tmp.UP_1,fused_ffn_tmp.GATE_2,fused_ffn_tmp.GATE_3,fused_ffn_tmp.UP_2,fused_ffn_tmp.UP_3, axis=1',
|
||||
'fused_ffn_tmp.GATE_0,fused_ffn_tmp.UP_0,fused_ffn_tmp.GATE_1,fused_ffn_tmp.UP_1,fused_ffn_tmp.GATE_2,fused_ffn_tmp.UP_2,fused_ffn_tmp.GATE_3,fused_ffn_tmp.UP_3 -> layers.2.mlp.gate_up_fused_proj.weight, axis=1',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedFfnMacro2(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_ffn_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.1.mlp.gate_up_fused_proj.weight -> layers.1.mlp.gate_proj.weight,layers.1.mlp.up_proj.weight, fused_ffn "
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.1.mlp.gate_up_fused_proj.weight -> fused_ffn_tmp.GATE_0,fused_ffn_tmp.UP_0,fused_ffn_tmp.GATE_1,fused_ffn_tmp.UP_1, axis=1',
|
||||
'fused_ffn_tmp.GATE_0,fused_ffn_tmp.GATE_1 -> layers.1.mlp.gate_proj.weight, axis=1',
|
||||
'fused_ffn_tmp.UP_0,fused_ffn_tmp.UP_1 -> layers.1.mlp.up_proj.weight, axis=1',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedFfnMacro3(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_ffn_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.1.mlp.gate_up_fused_proj.weight -> layers.1.mlp.gate_proj.weight,layers.1.mlp.up_proj.weight, fused_ffn "
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.1.mlp.gate_up_fused_proj.weight -> fused_ffn_tmp.GATE_0,fused_ffn_tmp.UP_0,fused_ffn_tmp.GATE_1,fused_ffn_tmp.UP_1, axis=1',
|
||||
'fused_ffn_tmp.GATE_0,fused_ffn_tmp.GATE_1 -> layers.1.mlp.gate_proj.weight, axis=1',
|
||||
'fused_ffn_tmp.UP_0,fused_ffn_tmp.UP_1 -> layers.1.mlp.up_proj.weight, axis=1',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedFfnMacro4(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_ffn_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.2.mlp.gate_up_fused_proj.bias -> layers.2.mlp.gate_up_fused_proj.bias, fused_ffn, axis=0"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.2.mlp.gate_up_fused_proj.bias -> fused_ffn_tmp.GATE_0,fused_ffn_tmp.GATE_1,fused_ffn_tmp.UP_0,fused_ffn_tmp.UP_1,fused_ffn_tmp.GATE_2,fused_ffn_tmp.GATE_3,fused_ffn_tmp.UP_2,fused_ffn_tmp.UP_3, axis=0',
|
||||
'fused_ffn_tmp.GATE_0,fused_ffn_tmp.UP_0,fused_ffn_tmp.GATE_1,fused_ffn_tmp.UP_1,fused_ffn_tmp.GATE_2,fused_ffn_tmp.UP_2,fused_ffn_tmp.GATE_3,fused_ffn_tmp.UP_3 -> layers.2.mlp.gate_up_fused_proj.bias, axis=0',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestFusedFfnMacro5(TestMacro):
|
||||
def macro_name(self):
|
||||
return "fused_ffn_macro"
|
||||
|
||||
def source_code(self):
|
||||
return [
|
||||
"layers.1.mlp.gate_up_fused_proj.weight_test1 -> A_TEST_NAME, fused_ffn ",
|
||||
"A_TEST_NAME -> layers.1.mlp.gate_up_fused_proj.weight_test2",
|
||||
]
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.1.mlp.gate_up_fused_proj.weight_test1 -> fused_ffn_tmp.GATE_0,fused_ffn_tmp.GATE_1,fused_ffn_tmp.UP_0,fused_ffn_tmp.UP_1,fused_ffn_tmp.GATE_2,fused_ffn_tmp.GATE_3,fused_ffn_tmp.UP_2,fused_ffn_tmp.UP_3, axis=1',
|
||||
'fused_ffn_tmp.GATE_0,fused_ffn_tmp.UP_0,fused_ffn_tmp.GATE_1,fused_ffn_tmp.UP_1,fused_ffn_tmp.GATE_2,fused_ffn_tmp.UP_2,fused_ffn_tmp.GATE_3,fused_ffn_tmp.UP_3 -> A_TEST_NAME, axis=1',
|
||||
'A_TEST_NAME -> layers.1.mlp.gate_up_fused_proj.weight_test2',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test(aoa_config_reverse=True)
|
||||
|
||||
|
||||
class TestLayerIdOffsetMacro(TestMacro):
|
||||
def macro_name(self):
|
||||
return "layer_id_offset_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.$LAYER_ID_OFFSET.experts.0.weight -> layers.$LAYER_ID_OFFSET.experts.0.weight, axis = 1"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.1.experts.0.weight->layers.0.experts.0.weight,axis=1\n',
|
||||
'layers.2.experts.0.weight->layers.1.experts.0.weight,axis=1\n',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestIdMacroCase0(TestMacro):
|
||||
def macro_name(self):
|
||||
return "id_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.$LAYER_ID.qkv_proj.weight->layers.$LAYER_ID.q_proj.weight,layer.$LAYER_ID.k_proj.weight,layer.$LAYER_ID.v_proj.weight, fused_qkv_old, num_heads = 8, num_key_value_groups = 4\n"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.0.qkv_proj.weight->layers.0.q_proj.weight,layer.0.k_proj.weight,layer.0.v_proj.weight,fused_qkv_old,num_heads=8,num_key_value_groups=4\n',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestIdMacroCase1(TestMacro):
|
||||
def macro_name(self):
|
||||
return "id_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.5.experts.$EXPERT_ID.up_gate_proj.weight -> layers.5.experts.$EXPERT_ID.gate_proj.weight, layers.5.experts.$EXPERT_ID.up_proj.weight, fused_ffn"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.5.experts.0.up_gate_proj.weight->layers.5.experts.0.gate_proj.weight,layers.5.experts.0.up_proj.weight,fused_ffn\n',
|
||||
'layers.5.experts.1.up_gate_proj.weight->layers.5.experts.1.gate_proj.weight,layers.5.experts.1.up_proj.weight,fused_ffn\n',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestIdMacroCase2(TestMacro):
|
||||
def macro_name(self):
|
||||
return "id_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.$LAYER_ID.experts.$EXPERT_ID.up_gate_proj.weight -> layers.$LAYER_ID.experts.$EXPERT_ID.gate_proj.weight, fused_ffn"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.3.experts.0.up_gate_proj.weight->layers.3.experts.0.gate_proj.weight,fused_ffn\n',
|
||||
'layers.5.experts.0.up_gate_proj.weight->layers.5.experts.0.gate_proj.weight,fused_ffn\n',
|
||||
'layers.3.experts.1.up_gate_proj.weight->layers.3.experts.1.gate_proj.weight,fused_ffn\n',
|
||||
'layers.5.experts.1.up_gate_proj.weight->layers.5.experts.1.gate_proj.weight,fused_ffn\n',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
class TestIdMacroCase3(TestMacro):
|
||||
def macro_name(self):
|
||||
return "id_macro"
|
||||
|
||||
def source_code(self):
|
||||
return "layers.$LAYER_ID.experts.$EXPERT_ID.up_gate_proj.weight^T -> layers.$LAYER_ID.experts.$EXPERT_ID.gate_proj.weight, fused_ffn"
|
||||
|
||||
def expected(self):
|
||||
return [
|
||||
'layers.3.experts.0.up_gate_proj.weight^T->layers.3.experts.0.gate_proj.weight,fused_ffn\n',
|
||||
'layers.5.experts.0.up_gate_proj.weight^T->layers.5.experts.0.gate_proj.weight,fused_ffn\n',
|
||||
'layers.3.experts.1.up_gate_proj.weight^T->layers.3.experts.1.gate_proj.weight,fused_ffn\n',
|
||||
'layers.5.experts.1.up_gate_proj.weight^T->layers.5.experts.1.gate_proj.weight,fused_ffn\n',
|
||||
]
|
||||
|
||||
def test(self):
|
||||
self.start_macro_test()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,328 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
import collective.test_communication_api_base as test_base
|
||||
|
||||
import paddle
|
||||
from paddle import nn
|
||||
|
||||
TEST_CONFIGS = {
|
||||
"2_card_tests": [
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "ColumnSequenceParallelLinear",
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
"master_weight": "True",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "True",
|
||||
"master_weight": "True",
|
||||
},
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "True",
|
||||
"master_weight": "True",
|
||||
},
|
||||
],
|
||||
"4_card_tests": [
|
||||
{
|
||||
"world_size": 4,
|
||||
"tp": 4,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"world_size": 4,
|
||||
"tp": 4,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"world_size": 4,
|
||||
"tp": 2,
|
||||
"dp": 2,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"world_size": 4,
|
||||
"tp": 2,
|
||||
"dp": 2,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
],
|
||||
"4_card_hv_group_tests": [
|
||||
{
|
||||
"world_size": 4,
|
||||
"tp": 2,
|
||||
"pp": 2,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
"test_using_hv_group": 1,
|
||||
},
|
||||
],
|
||||
"2_card_hv_group_tests": [
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"pp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
"test_using_hv_group": 1,
|
||||
},
|
||||
],
|
||||
"sharding3_with_convert2cpu_tests": [
|
||||
{
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"pp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "True",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class TestFullParamWith2Devices(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=2, timeout=240)
|
||||
|
||||
def test_full_param(self):
|
||||
for config in TEST_CONFIGS["2_card_tests"]:
|
||||
envs = {k: str(v) for k, v in config.items()}
|
||||
envs["test_using_hv_group"] = "0"
|
||||
self.run_test_case(
|
||||
"model_full_param_logic.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
class TestFullParamWith4Devices(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=4, timeout=240)
|
||||
|
||||
def test_full_param(self):
|
||||
for config in TEST_CONFIGS["4_card_tests"]:
|
||||
envs = {k: str(v) for k, v in config.items()}
|
||||
envs["test_using_hv_group"] = "0"
|
||||
self.run_test_case(
|
||||
"model_full_param_logic.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
class TestFullParamWithSingleDevices(unittest.TestCase):
|
||||
class SimpleMLP(nn.Layer):
|
||||
def __init__(self, hidden_size=100, has_bias=False):
|
||||
super().__init__()
|
||||
self.embedding = nn.Embedding(24, hidden_size)
|
||||
self.linear1 = nn.Linear(
|
||||
hidden_size, hidden_size, bias_attr=has_bias
|
||||
)
|
||||
self.linear2 = nn.Linear(
|
||||
hidden_size, hidden_size, bias_attr=has_bias
|
||||
)
|
||||
self.llm_head = nn.Linear(hidden_size, 24, bias_attr=False)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.embedding(x)
|
||||
x = self.linear1(x)
|
||||
x = self.linear2(x)
|
||||
x = self.llm_head(x)
|
||||
return x
|
||||
|
||||
def test_full_param(self):
|
||||
self.batch_size = 2
|
||||
self.hidden_size = 32
|
||||
self.has_bias = True
|
||||
model = self.SimpleMLP(
|
||||
hidden_size=self.hidden_size, has_bias=self.has_bias
|
||||
)
|
||||
model = paddle.amp.decorate(
|
||||
models=model, optimizers=None, level="O2", dtype="float16"
|
||||
)
|
||||
model.train()
|
||||
model_state_dict = model.state_dict()
|
||||
|
||||
for k, v in model_state_dict.items():
|
||||
ones = paddle.ones_like(v)
|
||||
paddle.assign(ones, v)
|
||||
if k == "linear1.weight":
|
||||
zeros = paddle.zeros_like(v)
|
||||
paddle.assign(zeros, v)
|
||||
|
||||
aoa_config = {
|
||||
"aoa_statements": [
|
||||
"linear1.weight, linear2.weight -> fused_weight, axis=1"
|
||||
"embedding.weight -> embedding.weight, dtype = 'float32'"
|
||||
]
|
||||
}
|
||||
|
||||
full_param_iter = model.full(aoa_config)
|
||||
full_param = dict(full_param_iter)
|
||||
|
||||
param_shape = {
|
||||
# "linear1.weight" : [32,32],
|
||||
# "linear2.weight" : [32, 32],
|
||||
"embedding.weight": [24, 32],
|
||||
"linear1.bias": [32],
|
||||
"linear2.bias": [32],
|
||||
"llm_head.weight": [24, 32],
|
||||
"fused_weight": [32, 64],
|
||||
}
|
||||
|
||||
for name, shape in param_shape.items():
|
||||
if name == "fused_weight":
|
||||
continue
|
||||
if not self.has_bias:
|
||||
if ".bias" in name:
|
||||
continue
|
||||
assert name in full_param.keys()
|
||||
tensor = full_param[name]
|
||||
answer = paddle.ones_like(tensor)
|
||||
assert tensor._md5sum() == answer._md5sum()
|
||||
if name == "embedding.weight":
|
||||
assert tensor.dtype == paddle.float32
|
||||
assert "fused_weight" in full_param.keys()
|
||||
ones = paddle.ones([32, 32], 'float16')
|
||||
zeros = paddle.zeros([32, 32], 'float16')
|
||||
answer = paddle.concat([zeros, ones], axis=1)
|
||||
assert full_param["fused_weight"]._md5sum() == answer._md5sum()
|
||||
|
||||
|
||||
class TestFullParamHVGroupWith2Devices(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=2, timeout=240)
|
||||
|
||||
def test_full_param(self):
|
||||
for config in TEST_CONFIGS["2_card_hv_group_tests"]:
|
||||
envs = {k: str(v) for k, v in config.items()}
|
||||
envs["test_using_hv_group"] = "1"
|
||||
self.run_test_case(
|
||||
"model_full_param_logic.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
class TestFullParamHVGroupWith4Devices(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=4, timeout=240)
|
||||
|
||||
def test_full_param(self):
|
||||
for config in TEST_CONFIGS["4_card_hv_group_tests"]:
|
||||
envs = {k: str(v) for k, v in config.items()}
|
||||
envs["test_using_hv_group"] = "1"
|
||||
self.run_test_case(
|
||||
"model_full_param_logic.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
class TestFullParamWithSharding3(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=2, timeout=240)
|
||||
|
||||
def test_full_param(self):
|
||||
for config in TEST_CONFIGS["sharding3_with_convert2cpu_tests"]:
|
||||
envs = {k: str(v) for k, v in config.items()}
|
||||
envs["test_using_hv_group"] = "0"
|
||||
envs["test_with_sharding3"] = "1"
|
||||
self.run_test_case(
|
||||
"model_full_param_logic.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,235 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
import collective.test_communication_api_base as test_base
|
||||
|
||||
TEST_CONFIGS = {
|
||||
"2_card_tests": [
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "ColumnParallelLinear",
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "RowParallelLinear",
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "VocabParallelEmbedding",
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "ColumnParallelLinear",
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "RowParallelLinear",
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "ColumnSequenceParallelLinear",
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "RowSequenceParallelLinear",
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "optimizer",
|
||||
"layer_type": "AdamW",
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"test_type": "optimizer",
|
||||
"layer_type": "DygraphShardingOptimizer",
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"test_type": "optimizer",
|
||||
"layer_type": "DygraphShardingOptimizerV2",
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "False",
|
||||
},
|
||||
{
|
||||
"test_type": "optimizer",
|
||||
"layer_type": "AdamW",
|
||||
"world_size": 2,
|
||||
"tp": 2,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
"master_weight": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "optimizer",
|
||||
"layer_type": "DygraphShardingOptimizer",
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "True",
|
||||
"master_weight": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "optimizer",
|
||||
"layer_type": "DygraphShardingOptimizerV2",
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "True",
|
||||
"master_weight": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "optimizer",
|
||||
"layer_type": "GroupShardedOptimizerStage2",
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "True",
|
||||
"master_weight": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "optimizer",
|
||||
"layer_type": "GroupShardedStage3",
|
||||
"world_size": 2,
|
||||
"tp": 1,
|
||||
"sharding_degree": 2,
|
||||
"has_bias": "True",
|
||||
"master_weight": "True",
|
||||
},
|
||||
],
|
||||
"4_card_tests": [
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "ColumnParallelLinear",
|
||||
"world_size": 4,
|
||||
"tp": 4,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "RowParallelLinear",
|
||||
"world_size": 4,
|
||||
"tp": 4,
|
||||
"dp": 1,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "ColumnParallelLinear",
|
||||
"world_size": 4,
|
||||
"tp": 2,
|
||||
"dp": 2,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
{
|
||||
"test_type": "layer",
|
||||
"layer_type": "RowParallelLinear",
|
||||
"world_size": 4,
|
||||
"tp": 2,
|
||||
"dp": 2,
|
||||
"sharding_degree": 1,
|
||||
"has_bias": "True",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class TestParallelLayersWith2Devices(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=2, timeout=240)
|
||||
|
||||
def test_metadata(self):
|
||||
for config in TEST_CONFIGS["2_card_tests"]:
|
||||
envs = {k: str(v) for k, v in config.items()}
|
||||
self.run_test_case(
|
||||
"sharded_state_dict_logic.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
class TestParallelLayersWith4Devices(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=4, timeout=240)
|
||||
|
||||
def test_metadata(self):
|
||||
for config in TEST_CONFIGS["4_card_tests"]:
|
||||
envs = {k: str(v) for k, v in config.items()}
|
||||
self.run_test_case(
|
||||
"sharded_state_dict_logic.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
class TestMergeShardedAOA(test_base.CommunicationTestDistBase):
|
||||
def setUp(self):
|
||||
super().setUp(num_of_devices=2, timeout=120)
|
||||
|
||||
def test_merge_sharded(self):
|
||||
config = TEST_CONFIGS["2_card_tests"][0]
|
||||
envs = {k: str(v) for k, v in config.items()}
|
||||
self.run_test_case(
|
||||
"merge_sharded_state_dict.py",
|
||||
user_defined_envs=envs,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,289 @@
|
||||
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
def p_str_to_dict(p_str):
|
||||
"""Parses a strategy string like 'd2·t2' into a config dictionary."""
|
||||
config = {"tp": 1, "dp": 1, "pp": 1, "ep": 1}
|
||||
parts = p_str.split('·')
|
||||
for part in parts:
|
||||
if part.startswith('d'):
|
||||
config['dp'] = int(part[1:])
|
||||
elif part.startswith('t'):
|
||||
config['tp'] = int(part[1:])
|
||||
elif part.startswith('p'):
|
||||
config['pp'] = int(part[1:])
|
||||
elif part.startswith('e'):
|
||||
config['ep'] = int(part[1:])
|
||||
|
||||
if config['ep'] > 1 and config['dp'] < config['ep']:
|
||||
config['dp'] = config['ep']
|
||||
|
||||
config["num_cards"] = config["tp"] * config["dp"] * config["pp"]
|
||||
if p_str in ["d1", "t1", "p1", "e1"]:
|
||||
config["num_cards"] = 1
|
||||
|
||||
return config
|
||||
|
||||
|
||||
TEST_CASES = [
|
||||
{
|
||||
"id": "B1_d2_to_d4",
|
||||
"src": p_str_to_dict("d2"),
|
||||
"tgt": p_str_to_dict("d4"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
{
|
||||
"id": "B2_t2_to_t4",
|
||||
"src": p_str_to_dict("t2"),
|
||||
"tgt": p_str_to_dict("t4"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
{
|
||||
"id": "B3_p2_to_p4",
|
||||
"src": p_str_to_dict("p2"),
|
||||
"tgt": p_str_to_dict("p4"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
{
|
||||
"id": "B4_e2_to_e4",
|
||||
"src": p_str_to_dict("e2"),
|
||||
"tgt": p_str_to_dict("e4"),
|
||||
"model_type": "moe",
|
||||
"gpu_num": 4,
|
||||
},
|
||||
# Case 5 (pp2 -> tp4)
|
||||
{
|
||||
"id": "X5_pp2_to_tp4",
|
||||
"src": p_str_to_dict("p2"),
|
||||
"tgt": p_str_to_dict("t4"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
# Case 6 (tp2 -> pp2)
|
||||
{
|
||||
"id": "X6_tp2_to_pp2",
|
||||
"src": p_str_to_dict("t2"),
|
||||
"tgt": p_str_to_dict("p2"),
|
||||
"gpu_num": 2,
|
||||
},
|
||||
# Case 7 (dp4 -> tp2·dp2)
|
||||
{
|
||||
"id": "X7_dp4_to_tp2dp2",
|
||||
"src": p_str_to_dict("d4"),
|
||||
"tgt": p_str_to_dict("t2·d2"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
# Case 8 (dp2 -> pp2)
|
||||
{
|
||||
"id": "X8_dp2_to_pp2",
|
||||
"src": p_str_to_dict("d2"),
|
||||
"tgt": p_str_to_dict("p2"),
|
||||
"gpu_num": 2,
|
||||
},
|
||||
# Case 9 (dp2 -> ep2)
|
||||
{
|
||||
"id": "X9_dp2_to_ep2",
|
||||
"src": p_str_to_dict("d2"),
|
||||
"tgt": p_str_to_dict("e2"),
|
||||
"model_type": "moe",
|
||||
"gpu_num": 2,
|
||||
},
|
||||
# Case 10 (ep2 -> tp2)
|
||||
{
|
||||
"id": "X10_ep2_to_tp2",
|
||||
"src": p_str_to_dict("e2"),
|
||||
"tgt": p_str_to_dict("t2"),
|
||||
"model_type": "moe",
|
||||
"gpu_num": 2,
|
||||
},
|
||||
# Case 11 (tp2 -> ep2)
|
||||
{
|
||||
"id": "X11_tp2_to_ep2",
|
||||
"src": p_str_to_dict("t2"),
|
||||
"tgt": p_str_to_dict("e2"),
|
||||
"model_type": "moe",
|
||||
"gpu_num": 2,
|
||||
},
|
||||
{
|
||||
"id": "M12_dp2tp2_to_tp4",
|
||||
"src": p_str_to_dict("d2·t2"),
|
||||
"tgt": p_str_to_dict("t4"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
{
|
||||
"id": "M13_dp2tp2_to_pp4",
|
||||
"src": p_str_to_dict("d2·t2"),
|
||||
"tgt": p_str_to_dict("p4"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
{
|
||||
"id": "M14_dp2pp2_to_tp4",
|
||||
"src": p_str_to_dict("d2·p2"),
|
||||
"tgt": p_str_to_dict("t4"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
{
|
||||
"id": "M15_tp2pp2_to_dp4",
|
||||
"src": p_str_to_dict("t2·p2"),
|
||||
"tgt": p_str_to_dict("d4"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
{
|
||||
"id": "M16_tp2pp2_to_dp2tp2",
|
||||
"src": p_str_to_dict("t2·p2"),
|
||||
"tgt": p_str_to_dict("d2·t2"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
{
|
||||
"id": "M17_dp2ep2_to_dp4",
|
||||
"src": p_str_to_dict("d2·e2"),
|
||||
"tgt": p_str_to_dict("d4"),
|
||||
"model_type": "moe",
|
||||
"gpu_num": 4,
|
||||
},
|
||||
{
|
||||
"id": "M18_tp2ep2_to_tp4",
|
||||
"src": p_str_to_dict("t2·e2"),
|
||||
"tgt": p_str_to_dict("t4"),
|
||||
"model_type": "moe",
|
||||
"gpu_num": 4,
|
||||
},
|
||||
# Case 19 (dp2·tp2 -> pp2)
|
||||
{
|
||||
"id": "M19_dp2tp2_to_pp2",
|
||||
"src": p_str_to_dict("d2·t2"),
|
||||
"tgt": p_str_to_dict("p2"),
|
||||
"gpu_num": 4,
|
||||
},
|
||||
# E1 (e2->e4) is covered by B4
|
||||
{
|
||||
"id": "E2_dp2ep2_to_tp2ep2",
|
||||
"src": p_str_to_dict("d2·e2"),
|
||||
"tgt": p_str_to_dict("t2·e2"),
|
||||
"model_type": "moe",
|
||||
"gpu_num": 4,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class TestStrategyConversion(unittest.TestCase):
|
||||
def _run_workflow(self, case, logic_script="strategy_conversion_engine.py"):
|
||||
import paddle
|
||||
|
||||
if case["gpu_num"] > paddle.device.cuda.device_count():
|
||||
self.skipTest("number of GPUs is not enough")
|
||||
|
||||
case_id = case['id']
|
||||
src_config = case['src']
|
||||
tgt_config = case['tgt']
|
||||
|
||||
src_gpus_count = src_config.pop("num_cards")
|
||||
tgt_gpus_count = tgt_config.pop("num_cards")
|
||||
src_gpus = ",".join(map(str, range(src_gpus_count)))
|
||||
tgt_gpus = ",".join(map(str, range(tgt_gpus_count)))
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
src_ckpt_path = os.path.join(tmpdir, "src_ckpt")
|
||||
tgt_ckpt_path = os.path.join(tmpdir, "tgt_ckpt")
|
||||
|
||||
def config_to_args(config, prefix):
|
||||
return [
|
||||
f"--{prefix}_{k}={v}"
|
||||
for k, v in config.items()
|
||||
if not k.startswith('s_')
|
||||
]
|
||||
|
||||
common_args = config_to_args(src_config, "src") + config_to_args(
|
||||
tgt_config, "tgt"
|
||||
)
|
||||
if "model_type" in case:
|
||||
common_args.append(f"--model_type={case['model_type']}")
|
||||
path_args = [
|
||||
f"--src_ckpt_path={src_ckpt_path}",
|
||||
f"--tgt_ckpt_path={tgt_ckpt_path}",
|
||||
]
|
||||
base_cmd = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"paddle.distributed.launch",
|
||||
"--log_dir",
|
||||
os.path.join(tmpdir, "logs"),
|
||||
]
|
||||
|
||||
steps = ["save_source", "convert", "verify"]
|
||||
gpus_per_step = [src_gpus, tgt_gpus, src_gpus]
|
||||
|
||||
for i, step_name in enumerate(steps):
|
||||
cmd = [
|
||||
*base_cmd,
|
||||
f"--gpus={gpus_per_step[i]}",
|
||||
logic_script,
|
||||
f"--step={step_name}",
|
||||
*common_args,
|
||||
*path_args,
|
||||
]
|
||||
process = subprocess.run(
|
||||
cmd, capture_output=True, text=True, check=False
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
process.returncode,
|
||||
0,
|
||||
f"Step '{step_name}' FAILED for case '{case_id}'!\n"
|
||||
f"STDOUT:\n{process.stdout}\nSTDERR:\n{process.stderr}",
|
||||
)
|
||||
|
||||
|
||||
def _create_test_method(case):
|
||||
def test_method(self):
|
||||
self._run_workflow(case)
|
||||
|
||||
return test_method
|
||||
|
||||
|
||||
for case_info in TEST_CASES:
|
||||
test_name = f"test_{case_info['id']}"
|
||||
test_func = _create_test_method(case_info)
|
||||
setattr(TestStrategyConversion, test_name, test_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'--list_tests',
|
||||
action='store_true',
|
||||
help='List all test case names that unittest can discover and exit.',
|
||||
)
|
||||
args, unknown = parser.parse_known_args()
|
||||
|
||||
if args.list_tests:
|
||||
for case in TEST_CASES:
|
||||
module_name = os.path.splitext(os.path.basename(__file__))[0]
|
||||
logging.basicConfig(
|
||||
stream=sys.stdout, level=logging.INFO, format="%(message)s"
|
||||
)
|
||||
logging.info(
|
||||
f"{module_name}.TestStrategyConversion.test_{case['id']}"
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
unittest.main(argv=[sys.argv[0]], *unknown)
|
||||
Reference in New Issue
Block a user