Files
2026-07-13 12:40:42 +08:00

857 lines
30 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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()