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

515 lines
18 KiB
C++

// Copyright (c) 2019 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.
#include <memory>
#include <type_traits>
#include <vector>
#include "gtest/gtest.h"
#include "paddle/fluid/framework/convert_utils.h"
#include "paddle/fluid/framework/variable.h"
#include "paddle/fluid/imperative/gradient_accumulator.h"
#include "paddle/phi/core/memory/memcpy.h"
#include "paddle/phi/kernels/funcs/math_function.h"
namespace paddle {
namespace imperative {
TEST(Test__SelectedRowsMerge_Test, SelectedRowsMerge) {
phi::CPUPlace cpu;
std::vector<int64_t> rows{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int64_t table_size = 10;
int64_t embedding_width = 10;
auto sr1 = std::make_shared<phi::SelectedRows>(rows, table_size);
auto sr2 = std::make_shared<phi::SelectedRows>(rows, table_size);
// initialize a sparse table 1
sr1->mutable_value()->Resize(
common::make_ddim({table_size, embedding_width}));
auto* data_sr1 = sr1->mutable_value()->mutable_data<float>(cpu);
for (int64_t i = 0; i < table_size; ++i) {
for (int64_t j = 0; j < embedding_width; ++j) {
data_sr1[i * embedding_width + j] = static_cast<float>(i);
}
}
// initialize a sparse table 2
sr2->mutable_value()->Resize(
common::make_ddim({table_size, embedding_width}));
auto* data_sr2 = sr2->mutable_value()->mutable_data<float>(cpu);
for (int64_t i = 0; i < table_size; ++i) {
for (int64_t j = 0; j < embedding_width; ++j) {
data_sr2[i * embedding_width + j] = static_cast<float>(i);
}
}
// new 2 phi::Tensor
paddle::Tensor t1(sr1);
paddle::Tensor t2(sr2);
// call SelectedRowsMerge
auto new_buffer =
paddle::imperative::SelectedRowsMerge<paddle::Tensor>(t1, t2);
auto* new_buffer_tensor =
static_cast<phi::SelectedRows*>(new_buffer->impl().get());
auto* new_buffer_data_sr1 =
new_buffer_tensor->mutable_value()->mutable_data<float>(cpu);
// verify the MergeAdd result
for (int64_t i = 0; i < table_size; ++i) {
for (int64_t j = 0; j < embedding_width; ++j) {
EXPECT_EQ(new_buffer_data_sr1[i * embedding_width + j],
(static_cast<float>(i) + static_cast<float>(i)));
}
}
}
template <typename Place1, typename Place2, typename T>
int TensorAddTest(Place1 place1, Place2 place2, T t1, T t2) {
framework::Variable var1;
framework::Variable var2;
std::vector<T> src_data(10, t1);
std::vector<T> dst_data(10, t2);
std::vector<T> result;
phi::CPUPlace src_place;
for (unsigned int i = 0; i < 10; i++) {
result.emplace_back(src_data[i] + dst_data[i]);
}
std::vector<int64_t> dims = {2, 5};
auto* src = var1.GetMutable<phi::DenseTensor>();
auto* dst = var2.GetMutable<phi::DenseTensor>();
src->Resize(common::make_ddim(dims));
dst->Resize(common::make_ddim(dims));
auto* src_mutable = src->mutable_data<T>(place1);
auto* dst_mutable = dst->mutable_data<T>(place2);
if (!std::is_same<Place1, phi::GPUPlace>::value) {
paddle::memory::Copy(place1,
src_mutable,
src_place,
src_data.data(),
sizeof(T) * src_data.size());
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
} else {
paddle::memory::Copy(place1,
src_mutable,
src_place,
src_data.data(),
sizeof(T) * src_data.size(),
0);
#endif
}
if (!std::is_same<Place2, phi::GPUPlace>::value) {
paddle::memory::Copy(place2,
dst_mutable,
src_place,
dst_data.data(),
sizeof(T) * dst_data.size());
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
} else {
paddle::memory::Copy(place2,
dst_mutable,
src_place,
dst_data.data(),
sizeof(T) * dst_data.size(),
0);
#endif
}
imperative::TensorAdd<framework::Variable>(var1, &var2);
phi::DenseTensor rlt;
phi::CPUPlace rlt_place;
framework::TensorCopySync(*dst, rlt_place, &rlt);
for (unsigned int i = 0; i < rlt.numel(); i++) {
if (rlt.data<T>()[i] != result[i]) return 1;
}
return 0;
}
TEST(test_add_functor, add_functor) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
phi::GPUPlace gpu_place(0);
#endif
phi::CPUPlace cpu_place;
int cpu_res = 1;
// float32
cpu_res = TensorAddTest(
cpu_place, cpu_place, static_cast<float>(1.0), static_cast<float>(2.0));
EXPECT_EQ(cpu_res, 0);
// float16
cpu_res = TensorAddTest(cpu_place,
cpu_place,
static_cast<phi::dtype::float16>(1.0),
static_cast<phi::dtype::float16>(2.0));
EXPECT_EQ(cpu_res, 0);
// double
cpu_res = TensorAddTest(
cpu_place, cpu_place, static_cast<double>(1.0), static_cast<double>(2.0));
EXPECT_EQ(cpu_res, 0);
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
int gpu_res = 1;
gpu_res = TensorAddTest(gpu_place, gpu_place, 1.0, 0.0);
EXPECT_EQ(gpu_res, 0);
gpu_res = TensorAddTest(
gpu_place, gpu_place, static_cast<double>(1.0), static_cast<double>(2.0));
EXPECT_EQ(gpu_res, 0);
// normal
gpu_res = TensorAddTest(
gpu_place, gpu_place, static_cast<float>(1.0), static_cast<float>(2.0));
EXPECT_EQ(gpu_res, 0);
gpu_res = TensorAddTest(gpu_place,
gpu_place,
static_cast<phi::dtype::float16>(1.0),
static_cast<phi::dtype::float16>(2.0));
EXPECT_EQ(gpu_res, 0);
// different places
gpu_res = TensorAddTest(
cpu_place, gpu_place, static_cast<float>(1.0), static_cast<float>(2.0));
EXPECT_EQ(gpu_res, 0);
gpu_res = TensorAddTest(
gpu_place, cpu_place, static_cast<float>(1.0), static_cast<float>(2.0));
EXPECT_EQ(gpu_res, 0);
gpu_res = TensorAddTest(cpu_place,
gpu_place,
static_cast<phi::dtype::float16>(1.0),
static_cast<phi::dtype::float16>(2.0));
EXPECT_EQ(gpu_res, 0);
gpu_res = TensorAddTest(gpu_place,
cpu_place,
static_cast<phi::dtype::float16>(1.0),
static_cast<phi::dtype::float16>(2.0));
EXPECT_EQ(gpu_res, 0);
#endif
#ifdef PADDLE_WITH_XPU
phi::XPUPlace xpu_place(0);
int xpu_res = 1;
// normal
xpu_res = TensorAddTest(
xpu_place, xpu_place, static_cast<float>(1.0), static_cast<float>(2.0));
EXPECT_EQ(xpu_res, 0);
xpu_res = TensorAddTest(xpu_place,
xpu_place,
static_cast<phi::dtype::float16>(1.0),
static_cast<phi::dtype::float16>(2.0));
EXPECT_EQ(xpu_res, 0);
xpu_res = TensorAddTest(
xpu_place, xpu_place, static_cast<double>(1.0), static_cast<double>(2.0));
EXPECT_EQ(xpu_res, 0);
// different places
xpu_res = TensorAddTest(
cpu_place, xpu_place, static_cast<float>(1.0), static_cast<float>(2.0));
EXPECT_EQ(xpu_res, 0);
xpu_res = TensorAddTest(
xpu_place, cpu_place, static_cast<float>(1.0), static_cast<float>(2.0));
EXPECT_EQ(xpu_res, 0);
xpu_res = TensorAddTest(cpu_place,
xpu_place,
static_cast<phi::dtype::float16>(1.0),
static_cast<phi::dtype::float16>(2.0));
EXPECT_EQ(xpu_res, 0);
xpu_res = TensorAddTest(xpu_place,
cpu_place,
static_cast<phi::dtype::float16>(1.0),
static_cast<phi::dtype::float16>(2.0));
EXPECT_EQ(xpu_res, 0);
xpu_res = TensorAddTest(
cpu_place, xpu_place, static_cast<double>(1.0), static_cast<double>(2.0));
EXPECT_EQ(xpu_res, 0);
xpu_res = TensorAddTest(
xpu_place, cpu_place, static_cast<double>(1.0), static_cast<double>(2.0));
EXPECT_EQ(xpu_res, 0);
#endif
}
TEST(test_add_functor, exception) {
phi::GPUPinnedPlace cuda_pinned_place;
phi::GPUPlace cuda_place(0);
phi::CPUPlace cpu_place;
ASSERT_ANY_THROW(TensorAddTest(cpu_place, cpu_place, 1, 0));
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
ASSERT_ANY_THROW(
TensorAddTest(cuda_pinned_place, cuda_pinned_place, 1.0, 0.0));
ASSERT_ANY_THROW(TensorAddTest(cuda_pinned_place,
cuda_pinned_place,
static_cast<phi::dtype::float16>(1.0),
static_cast<phi::dtype::float16>(2.0)));
#endif
}
static void CopyVar(const framework::Variable& var,
framework::Variable* dst_ptr) {
auto& dst = *dst_ptr;
dst.Clear();
if (var.IsType<phi::DenseTensor>()) {
const auto& src_tensor = var.Get<phi::DenseTensor>();
auto* dst_tensor = dst.GetMutable<phi::DenseTensor>();
framework::TensorCopySync(src_tensor, src_tensor.place(), dst_tensor);
} else {
const auto& src_selected_rows = var.Get<phi::SelectedRows>();
auto* dst_selected_rows = dst.GetMutable<phi::SelectedRows>();
dst_selected_rows->set_rows(src_selected_rows.rows());
dst_selected_rows->set_height(src_selected_rows.height());
framework::TensorCopySync(src_selected_rows.value(),
src_selected_rows.value().place(),
dst_selected_rows->mutable_value());
}
}
static bool IsEqualVar(const framework::Variable& var1,
const framework::Variable& var2) {
if (var1.Type() != var2.Type()) {
return false;
}
phi::DenseTensor t1, t2;
if (var1.IsType<phi::DenseTensor>()) {
framework::TensorCopySync(
var1.Get<phi::DenseTensor>(), phi::CPUPlace(), &t1);
framework::TensorCopySync(
var2.Get<phi::DenseTensor>(), phi::CPUPlace(), &t2);
} else {
auto& s1 = var1.Get<phi::SelectedRows>();
auto& s2 = var2.Get<phi::SelectedRows>();
if (s1.height() != s2.height()) {
return false;
}
if (s1.rows().size() != s2.rows().size()) {
return false;
}
auto row1_data = s1.rows().data();
auto row2_data = s2.rows().data();
if (std::memcmp(
row1_data, row2_data, s1.rows().size() * sizeof(*row1_data)) != 0) {
return false;
}
framework::TensorCopySync(
var1.Get<phi::SelectedRows>().value(), phi::CPUPlace(), &t1);
framework::TensorCopySync(
var2.Get<phi::SelectedRows>().value(), phi::CPUPlace(), &t2);
}
if (t1.type() != t2.type() || t1.dims() != t2.dims()) {
return false;
}
auto* t1_p = t1.data();
auto* t2_p = t2.data();
return std::memcmp(
t1_p,
t2_p,
t1.numel() * framework::SizeOfType(
framework::TransToProtoVarType(t1.dtype()))) == 0;
}
template <typename T>
static framework::Variable RandomTensor(const phi::DDim& dims,
const phi::Place& place,
int low = -10,
int high = 10) {
phi::DenseTensor cpu_tensor;
cpu_tensor.Resize(dims);
auto* ptr = cpu_tensor.mutable_data<T>(phi::CPUPlace());
std::uniform_int_distribution<int> dist(low, high);
std::random_device rd;
std::mt19937 engine(rd());
for (int64_t i = 0; i < cpu_tensor.numel(); ++i) {
ptr[i] = dist(engine);
}
framework::Variable ret;
framework::TensorCopySync(
cpu_tensor, place, ret.GetMutable<phi::DenseTensor>());
return ret;
}
template <typename T>
static framework::Variable RandomSelectedRows(phi::DDim dims,
const phi::Place& place,
int64_t row_number,
int low = -10,
int high = 10) {
auto height = dims[0];
dims[0] = row_number;
framework::Variable ret;
auto* sr = ret.GetMutable<phi::SelectedRows>();
auto tensor_var = RandomTensor<T>(dims, place, low, high);
sr->mutable_value()->ShareDataWith(
tensor_var.template Get<phi::DenseTensor>());
sr->set_height(height);
sr->mutable_rows()->resize(row_number);
auto* row_data = sr->mutable_rows()->data();
std::uniform_int_distribution<int64_t> dist(0, height - 1);
std::random_device rd;
std::mt19937 engine(rd());
for (int64_t i = 0; i < dims[0]; ++i) {
row_data[i] = dist(engine);
}
return ret;
}
static std::unique_ptr<GradientAccumulator> CreateAccumulator(
const std::shared_ptr<VariableWrapper>& var, bool sort_gradient) {
if (sort_gradient) { // NOLINT
return std::unique_ptr<GradientAccumulator>(
new SortedGradientAccumulator(var.get()));
} else {
return std::unique_ptr<GradientAccumulator>(
new EagerGradientAccumulator(var.get()));
}
}
static void TestGradientAccumulatorTestUnchangeInput(const phi::Place& place,
bool sort_gradient) {
phi::DDim dim{10, 20};
int64_t maximum_row_number = 100;
std::uniform_int_distribution<int64_t> dist(1, maximum_row_number);
int seed = 0;
{
std::random_device rd;
seed = static_cast<int>(rd());
}
std::mt19937 engine(seed);
auto create_var = [&](bool use_tensor) {
if (use_tensor) { // NOLINT
return RandomTensor<float>(dim, place);
} else {
return RandomSelectedRows<float>(dim, place, dist(engine));
}
};
std::vector<bool> use_tensors = {false, true};
for (auto use_tensor1 : use_tensors) {
for (auto use_tensor2 : use_tensors) {
/** g_accum1 && g_accum2: has not been initialized
* test accumulate on this graph
*/
auto g_var1 = std::make_shared<VariableWrapper>("g_var1");
g_var1->SetOverriddenStopGradient(false);
auto g_accum1 = CreateAccumulator(g_var1, sort_gradient);
g_accum1->IncreaseRefCnt();
g_accum1->IncreaseRefCnt();
auto g_var2 = std::make_shared<VariableWrapper>("g_var2");
g_var2->SetOverriddenStopGradient(false);
auto g_accum2 = CreateAccumulator(g_var2, sort_gradient);
g_accum2->IncreaseRefCnt();
g_accum2->IncreaseRefCnt();
auto var1 = create_var(use_tensor1);
auto var_wrapper1_1 = std::make_shared<VariableWrapper>("tmp1_1");
auto var_wrapper2_1 = std::make_shared<VariableWrapper>("tmp2_1");
ASSERT_EQ(var_wrapper1_1->IsEmpty(), true);
CopyVar(var1, var_wrapper1_1->MutableVar());
ASSERT_EQ(var_wrapper1_1->IsEmpty(), false);
ASSERT_EQ(var_wrapper2_1->IsEmpty(), true);
CopyVar(var1, var_wrapper2_1->MutableVar());
ASSERT_EQ(var_wrapper2_1->IsEmpty(), false);
auto var2 = create_var(use_tensor2);
auto var_wrapper1_2 = std::make_shared<VariableWrapper>("tmp1_2");
auto var_wrapper2_2 = std::make_shared<VariableWrapper>("tmp2_2");
CopyVar(var2, var_wrapper1_2->MutableVar());
CopyVar(var2, var_wrapper2_2->MutableVar());
// g_accum1: inner_var_ = var1 + var2
g_accum1->SumGrad(var_wrapper1_1, 0, false);
g_accum1->SumGrad(var_wrapper1_2, 1, false);
ASSERT_EQ(g_accum1->CurCnt(), g_accum1->RefCnt());
ASSERT_TRUE(g_accum1->SumGradCompleted());
// g_accum1: inner_var_ -> var_
g_accum1->AccumulateGrad();
// g_accum2: inner_var_ = var1 + var2
g_accum2->SumGrad(var_wrapper2_1, 0, true);
g_accum2->SumGrad(var_wrapper2_2, 1, true);
ASSERT_EQ(g_accum2->CurCnt(), g_accum2->RefCnt());
ASSERT_TRUE(g_accum2->SumGradCompleted());
// g_accum2: inner_var_ -> var_
g_accum2->AccumulateGrad();
ASSERT_TRUE(IsEqualVar(var_wrapper2_1->Var(), var1));
ASSERT_TRUE(IsEqualVar(var_wrapper2_2->Var(), var2));
ASSERT_TRUE(IsEqualVar(g_var1->Var(), g_var2->Var()));
/** g_accum3 && g_accum4: has been initialized
* test accumulate on previous graph
*/
auto var3 = create_var(use_tensor1);
auto var_wrapper3_3 = std::make_shared<VariableWrapper>("tmp1_3");
auto var_wrapper4_3 = std::make_shared<VariableWrapper>("tmp2_3");
var_wrapper3_3->SetOverriddenStopGradient(false);
var_wrapper4_3->SetOverriddenStopGradient(false);
CopyVar(var3, var_wrapper3_3->MutableVar());
CopyVar(var3, var_wrapper4_3->MutableVar());
auto g_accum3 = CreateAccumulator(var_wrapper3_3, sort_gradient);
g_accum3->IncreaseRefCnt();
auto g_accum4 = CreateAccumulator(var_wrapper4_3, sort_gradient);
g_accum4->IncreaseRefCnt();
auto var4 = create_var(use_tensor2);
auto var_wrapper3_4 = std::make_shared<VariableWrapper>("tmp1_4");
auto var_wrapper4_4 = std::make_shared<VariableWrapper>("tmp2_4");
CopyVar(var4, var_wrapper3_4->MutableVar());
CopyVar(var4, var_wrapper4_4->MutableVar());
g_accum3->SumGrad(var_wrapper3_4, 0, false);
ASSERT_TRUE(g_accum3->SumGradCompleted());
// g_accum4: var_(var_wrapper3_3) + inner_var_ -> var_
g_accum3->AccumulateGrad();
g_accum4->SumGrad(var_wrapper4_4, 0, false);
ASSERT_TRUE(g_accum4->SumGradCompleted());
// g_accum4: var_(var_wrapper4_3) + inner_var_ -> var_
g_accum4->AccumulateGrad();
ASSERT_TRUE(IsEqualVar(var_wrapper3_3->Var(), var_wrapper4_3->Var()));
}
}
}
TEST(test_gradient_accumulator, test_unchange_input) {
for (auto sort_gradient : {false, true}) {
TestGradientAccumulatorTestUnchangeInput(phi::CPUPlace(), sort_gradient);
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
TestGradientAccumulatorTestUnchangeInput(phi::GPUPlace(0), sort_gradient);
#endif
}
}
} // namespace imperative
} // namespace paddle