Files
paddlepaddle--paddle/test/collective/fleet/parallel_margin_cross_entropy.py
2026-07-13 12:40:42 +08:00

217 lines
8.5 KiB
Python

# Copyright (c) 2021 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 random
import unittest
import numpy as np
import paddle
import paddle.distributed as dist
from paddle.distributed import fleet
def set_random_seed(seed):
"""Set random seed for reproducibility."""
random.seed(seed)
np.random.seed(seed)
paddle.seed(seed)
fleet.meta_parallel.model_parallel_random_seed(seed)
class TestParallelMarginSoftmaxCrossEntropyOp(unittest.TestCase):
def setUp(self):
strategy = fleet.DistributedStrategy()
fleet.init(is_collective=True, strategy=strategy)
def test_parallel_margin_softmax_cross_entropy(self):
margin1s = [1.0, 1.0, 1.35]
margin2s = [0.5, 0.0, 0.0]
margin3s = [0.0, 0.35, 0.0]
scales = [64.0, 64.0, 64.0]
rank_id = dist.get_rank()
num_trainer = dist.get_world_size()
batch_size = 2
feature_length = 4
seed = 1025
set_random_seed(seed)
paddle.seed(rank_id * 10)
random.seed(seed)
np.random.seed(seed)
check_group = dist.new_group(list(range(num_trainer)))
for dtype in ('float32', 'float64'):
num_class_per_cards = [[4, 8], [2, 2], [4, 2], [3, 9]]
for num_class_per_card in num_class_per_cards:
num_class = np.sum(num_class_per_card)
for margin1, margin2, margin3, scale in zip(
margin1s, margin2s, margin3s, scales
):
for _ in range(5):
np_label = np.random.randint(
0, num_class, (batch_size,)
)
label = paddle.to_tensor(np_label, dtype="int64")
input = paddle.randn(
shape=[batch_size, feature_length], dtype=dtype
)
input.stop_gradient = False
input_l2 = paddle.sqrt(
paddle.sum(
paddle.square(input), axis=1, keepdim=True
)
)
norm_input = paddle.divide(input, input_l2)
weight = paddle.randn(
shape=[feature_length, num_class_per_card[rank_id]],
dtype=dtype,
)
weight.stop_gradient = False
weight_l2 = paddle.sqrt(
paddle.sum(
paddle.square(weight), axis=0, keepdim=True
)
)
norm_weight = paddle.divide(weight, weight_l2)
data = paddle.matmul(norm_input, norm_weight)
data.retain_grads()
data.stop_gradient = False
sta = (
np.sum(num_class_per_card[:rank_id])
if rank_id > 0
else 0
)
end = np.sum(num_class_per_card[: rank_id + 1])
integral_data = np.zeros(
(batch_size, num_class), dtype=dtype
)
integral_data[:, sta:end] = (
data.clone().detach().numpy()
)
integral_data = paddle.to_tensor(
integral_data, dtype=dtype
)
paddle.distributed.all_reduce(
integral_data,
op=paddle.distributed.ReduceOp.SUM,
group=check_group,
)
integral_data = integral_data.detach().clone()
integral_data.retain_grads()
integral_data.stop_gradient = False
# add arcface margin to logit
theta = paddle.acos(integral_data)
one_hot_label = paddle.nn.functional.one_hot(
label, num_classes=num_class
)
one_hot_label.stop_gradient = False
if margin1 != 1.0:
theta = margin1 * theta
if margin2 != 0.0:
theta = theta + margin2
margin_cos = paddle.cos(theta)
if margin3 != 0.0:
margin_cos = margin_cos - margin3
diff = one_hot_label * (margin_cos - integral_data)
arc_data = (integral_data + diff) * scale
(
loss_a,
softmax_a,
) = paddle.nn.functional.margin_cross_entropy(
data,
label,
margin1=margin1,
margin2=margin2,
margin3=margin3,
scale=scale,
group=check_group,
return_softmax=True,
reduction=None,
)
(
loss_b,
softmax_b,
) = paddle.nn.functional.softmax_with_cross_entropy(
logits=arc_data,
label=paddle.reshape(label, (-1, 1)),
return_softmax=True,
)
np.testing.assert_allclose(
loss_a.numpy(), loss_b.numpy(), rtol=1e-5, atol=1e-7
)
integral_prob = np.zeros(
(batch_size, num_class), dtype=dtype
)
integral_prob[:, sta:end] = (
softmax_a.clone().detach().numpy()
)
integral_prob = paddle.to_tensor(
integral_prob, dtype=dtype
)
paddle.distributed.all_reduce(
integral_prob,
op=paddle.distributed.ReduceOp.SUM,
group=check_group,
)
integral_prob = integral_prob.detach().clone()
integral_prob.stop_gradient = False
np.testing.assert_allclose(
integral_prob.numpy(),
softmax_b.numpy(),
rtol=1e-5,
atol=1e-6,
)
loss_a = loss_a.sum() / batch_size
loss_b = loss_b.sum() / batch_size
loss_a.backward()
loss_b.backward()
integral_grad = np.zeros(
(batch_size, num_class), dtype=dtype
)
integral_grad[:, sta:end] = data.grad.clone().detach()
integral_grad = paddle.to_tensor(
integral_grad, dtype=dtype
)
paddle.distributed.all_reduce(
integral_grad,
op=paddle.distributed.ReduceOp.SUM,
group=check_group,
)
np.testing.assert_allclose(
integral_data.grad.numpy(False),
integral_grad.numpy(False),
rtol=1e-5,
atol=1e-7,
)
if __name__ == '__main__':
unittest.main()