178 lines
6.0 KiB
C++
178 lines
6.0 KiB
C++
// Copyright (c) 2022 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.
|
|
|
|
#pragma once
|
|
|
|
#include "paddle/phi/kernels/funcs/blas/blas.h"
|
|
#include "paddle/phi/kernels/funcs/eigen/common.h"
|
|
#include "paddle/phi/kernels/funcs/math_function.h"
|
|
|
|
namespace phi {
|
|
|
|
using Array1 = Eigen::DSizes<int64_t, 1>;
|
|
using Array2 = Eigen::DSizes<int64_t, 2>;
|
|
using IndexPair = Eigen::IndexPair<int>;
|
|
|
|
template <typename Context, typename T>
|
|
static inline void TransCompute2DTo5D(const Context& dev_ctx,
|
|
const DenseTensor& in,
|
|
const int rank,
|
|
const std::vector<int>& perm,
|
|
DenseTensor* out) {
|
|
if (rank <= 1 || rank > 5) {
|
|
PADDLE_THROW(common::errors::Fatal(
|
|
"Weight rank of SpectralNorm should be in range [2, 5], but got %d.",
|
|
rank));
|
|
}
|
|
|
|
switch (rank) {
|
|
case 2:
|
|
funcs::Transpose<Context, T, 2> trans2;
|
|
trans2(dev_ctx, in, out, perm);
|
|
break;
|
|
case 3:
|
|
funcs::Transpose<Context, T, 3> trans3;
|
|
trans3(dev_ctx, in, out, perm);
|
|
break;
|
|
case 4:
|
|
funcs::Transpose<Context, T, 4> trans4;
|
|
trans4(dev_ctx, in, out, perm);
|
|
break;
|
|
case 5:
|
|
funcs::Transpose<Context, T, 5> trans5;
|
|
trans5(dev_ctx, in, out, perm);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
template <typename Context, typename T>
|
|
static inline void CalcMatrixSigmaAndNormWeight(const Context& dev_ctx,
|
|
DenseTensor* weight,
|
|
DenseTensor* u,
|
|
DenseTensor* v,
|
|
DenseTensor* sigma,
|
|
const int power_iters,
|
|
const float eps) {
|
|
auto& place = *dev_ctx.eigen_device();
|
|
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
|
|
auto sigma_t = EigenTensor<T, 2>::From(*sigma);
|
|
auto weight_t = EigenTensor<T, 2>::From(*weight);
|
|
auto u_t = EigenTensor<T, 2>::From(*u);
|
|
auto v_t = EigenTensor<T, 2>::From(*v);
|
|
|
|
const int64_t h = weight->dims()[0];
|
|
const int64_t w = weight->dims()[1];
|
|
|
|
for (int i = 0; i < power_iters; i++) {
|
|
// V = W^T * U / ||W^T * U||_2
|
|
blas.MatMul(*weight, true, *u, false, T(1), v, T(0));
|
|
auto v_t_norm =
|
|
v_t.square().sum().sqrt().eval().reshape(Array1(1)).broadcast(
|
|
Array1(w));
|
|
v_t.device(place) = v_t / (v_t_norm + v_t_norm.constant(eps));
|
|
// U = W^T * V / ||W^T * V||_2
|
|
blas.MatMul(*weight, false, *v, false, T(1), u, T(0));
|
|
auto u_t_norm =
|
|
u_t.square().sum().sqrt().eval().reshape(Array1(1)).broadcast(
|
|
Array1(h));
|
|
u_t.device(place) = u_t / (u_t_norm + u_t_norm.constant(eps));
|
|
}
|
|
DenseTensor weight_v;
|
|
weight_v.Resize({h, 1});
|
|
dev_ctx.template Alloc<T>(&weight_v);
|
|
blas.MatMul(*weight, false, *v, false, T(1), &weight_v, T(0));
|
|
auto weight_v_t = EigenTensor<T, 2>::From(weight_v);
|
|
sigma_t.device(place) = (u_t * weight_v_t)
|
|
.sum()
|
|
.eval()
|
|
.reshape(Array2(1, 1))
|
|
.broadcast(Array2(h, w));
|
|
weight_t.device(place) = weight_t / sigma_t;
|
|
}
|
|
|
|
template <typename T, typename Context>
|
|
void SpectralNormKernel(const Context& dev_ctx,
|
|
const DenseTensor& weight,
|
|
const DenseTensor& u,
|
|
const DenseTensor& v,
|
|
int dim,
|
|
int power_iters,
|
|
float eps,
|
|
DenseTensor* out) {
|
|
const int64_t h = u.dims()[0];
|
|
const int64_t w = v.dims()[0];
|
|
|
|
DenseTensor weight_mat;
|
|
auto dims = weight.dims();
|
|
const int rank = dims.size();
|
|
std::vector<int64_t> real_dims;
|
|
if (dim != 0) {
|
|
std::vector<int> perm;
|
|
perm.push_back(dim);
|
|
real_dims.push_back(dims[dim]);
|
|
for (int i = 0; i < rank; i++) {
|
|
if (i != dim) {
|
|
perm.push_back(i);
|
|
real_dims.push_back(dims[i]);
|
|
}
|
|
}
|
|
weight_mat.Resize(real_dims);
|
|
dev_ctx.template Alloc<T>(&weight_mat);
|
|
TransCompute2DTo5D<Context, T>(dev_ctx, weight, rank, perm, &weight_mat);
|
|
} else {
|
|
for (int i = 0; i < rank; i++) {
|
|
real_dims.push_back(i);
|
|
}
|
|
Copy(dev_ctx, weight, dev_ctx.GetPlace(), true, &weight_mat);
|
|
}
|
|
weight_mat = weight_mat.Resize({h, w});
|
|
|
|
DenseTensor sigma;
|
|
sigma.Resize(weight_mat.dims());
|
|
dev_ctx.template Alloc<T>(&sigma);
|
|
DenseTensor uu, vv;
|
|
Copy(dev_ctx, u, dev_ctx.GetPlace(), true, &uu);
|
|
Copy(dev_ctx, v, dev_ctx.GetPlace(), true, &vv);
|
|
CalcMatrixSigmaAndNormWeight<Context, T>(dev_ctx,
|
|
&weight_mat,
|
|
&(uu.Resize({h, 1})),
|
|
&(vv.Resize({w, 1})),
|
|
&sigma,
|
|
power_iters,
|
|
eps);
|
|
|
|
if (dim != 0) {
|
|
std::vector<int> perm;
|
|
for (int i = 0; i < rank; i++) {
|
|
if (i < dim) {
|
|
perm.push_back(i + 1);
|
|
} else if (i == dim) {
|
|
perm.push_back(0);
|
|
} else {
|
|
perm.push_back(i);
|
|
}
|
|
}
|
|
out->Resize(dims);
|
|
dev_ctx.template Alloc<T>(out);
|
|
TransCompute2DTo5D<Context, T>(
|
|
dev_ctx, weight_mat.Resize(real_dims), rank, perm, out);
|
|
} else {
|
|
Copy(dev_ctx, weight_mat.Resize(dims), dev_ctx.GetPlace(), true, out);
|
|
}
|
|
}
|
|
|
|
} // namespace phi
|