blob: 5d8bd95572da6a03deb3f4412ef75f7df20d0a45 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*!
* Copyright (c) 2020 by Contributors
* \file np_matrix_rank-inl.h
* \brief Placeholder for matrix_rank
*/
#ifndef MXNET_OPERATOR_NUMPY_LINALG_NP_MATRIX_RANK_INL_H_
#define MXNET_OPERATOR_NUMPY_LINALG_NP_MATRIX_RANK_INL_H_
#include <mxnet/operator_util.h>
#include <vector>
#include <string>
#include <utility>
#include <algorithm>
#include "../../operator_common.h"
#include "../../mshadow_op.h"
#include "./np_pinv-inl.h"
namespace mxnet {
namespace op {
using namespace mshadow;
struct MatrixRankNoneTolParam : public dmlc::Parameter<MatrixRankNoneTolParam> {
float finfoEps32;
double finfoEps64;
bool hermitian;
DMLC_DECLARE_PARAMETER(MatrixRankNoneTolParam) {
DMLC_DECLARE_FIELD(finfoEps32)
.set_default(0)
.describe("Machine limits for float32 type");
DMLC_DECLARE_FIELD(finfoEps64)
.set_default(0)
.describe("Machine limits for float64 type");
DMLC_DECLARE_FIELD(hermitian)
.set_default(false)
.describe("If True, M is assumed to be Hermitian (symmetric if real-valued).");
}
void SetAttrDict(std::unordered_map<std::string, std::string>* dict) {
std::ostringstream finfoEps32_s, finfoEps64_s, hermitian_s;
finfoEps32_s << finfoEps32;
finfoEps64_s << finfoEps64;
hermitian_s << hermitian;
(*dict)["finfoEps32"] = finfoEps32_s.str();
(*dict)["finfoEps64"] = finfoEps64_s.str();
(*dict)["hermitian"] = hermitian_s.str();
}
};
struct MatrixRankParam : public dmlc::Parameter<MatrixRankParam> {
bool hermitian;
DMLC_DECLARE_PARAMETER(MatrixRankParam) {
DMLC_DECLARE_FIELD(hermitian)
.set_default(false)
.describe("If True, M is assumed to be Hermitian (symmetric if real-valued).");
}
void SetAttrDict(std::unordered_map<std::string, std::string>* dict) {
std::ostringstream hermitian_s;
hermitian_s << hermitian;
(*dict)["hermitian"] = hermitian_s.str();
}
};
template<int req>
struct VectorRankKernel {
template<typename DType>
MSHADOW_XINLINE static void Map(int i, const DType *in_data,
int64_t *out_data, const int& data_size) {
bool all_nozero = true;
for (int j = 0; j < data_size; ++j) {
if (!((in_data[j] > 0 ? in_data[j] : -in_data[j]) > 0)) {
all_nozero = false;
break;
}
}
KERNEL_ASSIGN(*out_data, req, static_cast<int64_t>(all_nozero ? 1 : 0));
}
};
template<int req>
struct MatrixRankNoneTolKernel {
template<typename DType>
MSHADOW_XINLINE static void Map(int i, const DType *in_data, int64_t *out_data,
const int& nrow, const int& ncol, const double& finfoEps,
const int& data_size, const int& batch_size) {
if (i < batch_size) {
DType max_singular_value = 0;
for (int j = 0; j < data_size; ++j) {
DType sv = in_data[j + i * data_size];
max_singular_value = sv > max_singular_value ? sv : max_singular_value;
}
double tol = (nrow > ncol ? nrow : ncol) * static_cast<double>(max_singular_value) * finfoEps;
int64_t rank_num = 0;
for (int j = 0; j < data_size; ++j) {
rank_num += in_data[j + i * data_size] > tol ? 1 : 0;
}
KERNEL_ASSIGN(out_data[i], req, rank_num);
}
}
};
template<int req>
struct MatrixRankKernel {
template<typename DType>
MSHADOW_XINLINE static void Map(int i, const DType *in_data, int64_t *out_data,
const int& data_size, const int& batch_size) {
if (i < batch_size) {
int64_t rank_num = 0;
for (int j = 0; j < data_size; ++j) {
rank_num += in_data[j + i * data_size] > 0 ? 1 : 0;
}
KERNEL_ASSIGN(out_data[i], req, rank_num);
}
}
};
struct SVDWrapper {
template<typename xpu, typename DType>
static void op(const TBlob& a, const TBlob& s,
const TBlob& u, const mxnet::TShape& ut_shape,
const TBlob& v, const mxnet::TShape& vt_shape,
const TBlob& work, const OpContext& ctx) {
Stream<xpu> *s_xpu = ctx.get_stream<xpu>();
using IndexT = typename LapackIndex<xpu>::IndexT;
const mxnet::TShape& a_shape = a.shape_;
const mxnet::TShape& ut_axis = GetTransAxis(u.shape_);
const int a_ndim = a.ndim();
const IndexT nrow = a_shape[a_ndim - 2];
const IndexT ncol = a_shape[a_ndim - 1];
if (nrow > ncol) {
const_cast<TBlob&>(u) = u.reshape(ut_shape);
const_cast<TBlob&>(v) = v.reshape(vt_shape);
mxnet::op::TransposeImpl<xpu>(ctx.run_ctx, a, u, ut_axis);
BatchSVDImpl(ncol, nrow,
v.FlatToKD<xpu, 3, DType>(s_xpu),
s.FlatToKD<xpu, 2, DType>(s_xpu),
u.FlatToKD<xpu, 3, DType>(s_xpu),
work.FlatToKD<xpu, 1, DType>(s_xpu), s_xpu);
} else {
if (a.dptr<DType>() != v.dptr<DType>()) {
Copy(v.FlatToKD<xpu, 3, DType>(s_xpu), a.FlatToKD<xpu, 3, DType>(s_xpu), s_xpu);
}
BatchSVDImpl(nrow, ncol,
u.FlatToKD<xpu, 3, DType>(s_xpu),
s.FlatToKD<xpu, 2, DType>(s_xpu),
v.FlatToKD<xpu, 3, DType>(s_xpu),
work.FlatToKD<xpu, 1, DType>(s_xpu), s_xpu);
}
}
};
inline void GetOrCheckBroadcastShape(const nnvm::NodeAttrs& attrs,
const mxnet::TShape& a_shape,
const mxnet::TShape& tol_shape,
mxnet::TShape *broadcast_shape = nullptr,
mxnet::TShape *new_tol_shape = nullptr) {
CHECK_GE(a_shape.ndim(), 2);
const int a_ndim = a_shape.ndim();
const int tol_ndim = tol_shape.ndim();
const int nrow = a_shape[a_ndim - 2];
const int ncol = a_shape[a_ndim - 1];
// Get new tol shape.
mxnet::TShape temp_new_tol_shape(tol_ndim + 1, 1);
for (int i = 0; i < tol_ndim; ++i) { temp_new_tol_shape[i] = tol_shape[i]; }
// Get singular value shape.
mxnet::TShape temp_s_shape(a_ndim - 1, 0);
for (int i = 0; i < a_ndim - 2; ++i) {
temp_s_shape[i] = a_shape[i];
}
temp_s_shape[a_ndim - 2] = std::min(nrow, ncol);
// Check binary broadcast shape.
mxnet::ShapeVector in_shape_vec({ temp_s_shape, temp_new_tol_shape });
mxnet::ShapeVector out_shape_vec(1, mxnet::TShape());
mxnet::op::BinaryBroadcastShape(attrs, &in_shape_vec, &out_shape_vec);
// Assign shape.
if (broadcast_shape) {
*broadcast_shape = out_shape_vec[0];
}
if (new_tol_shape) {
*new_tol_shape = temp_new_tol_shape;
}
}
template<typename xpu, typename DType>
struct WSQ {
static size_t SVDWorkspaceSizeQuery(const TBlob& a,
const mxnet::TShape& u_shape,
const mxnet::TShape& s_shape,
const mxnet::TShape& v_shape,
const OpContext& ctx) {
size_t workspace_size = 0;
Stream<xpu> *s = ctx.get_stream<xpu>();
const int a_ndim = a.shape_.ndim();
const int u_ndim = u_shape.ndim();
const int s_ndim = s_shape.ndim();
const int v_ndim = v_shape.ndim();
mxnet::TShape u_shape2 = Shape2(u_shape[u_ndim - 2], u_shape[u_ndim - 1]);
mxnet::TShape s_shape1 = Shape1(s_shape[s_ndim - 1]);
mxnet::TShape v_shape2 = Shape2(v_shape[v_ndim - 2], v_shape[v_ndim - 1]);
if (xpu::kDevCPU) {
std::vector<DType> u_vec(u_shape2.Size(), 0);
std::vector<DType> s_vec(s_shape1.Size(), 0);
std::vector<DType> v_vec(v_shape2.Size(), 0);
// Get workspace size in linalg_gesdd.
workspace_size += linalg_gesdd_workspace_query<xpu, DType, lapack_index_t>(
a.shape_[a_ndim - 2], a.shape_[a_ndim - 1],
TBlob(u_vec.data(), u_shape2, a.dev_mask(), a.dev_id()).get<xpu, 2, DType>(s),
TBlob(s_vec.data(), s_shape1, a.dev_mask(), a.dev_id()).get<xpu, 1, DType>(s),
TBlob(v_vec.data(), v_shape2, a.dev_mask(), a.dev_id()).get<xpu, 2, DType>(s), s);
} else {
Storage::Handle u_handle =
Storage::Get()->Alloc(sizeof(DType) * u_shape2.Size(), Context::GPU());
Storage::Handle s_handle =
Storage::Get()->Alloc(sizeof(DType) * s_shape1.Size(), Context::GPU());
Storage::Handle v_handle =
Storage::Get()->Alloc(sizeof(DType) * v_shape2.Size(), Context::GPU());
TBlob u_data(static_cast<DType*>(u_handle.dptr), u_shape2, a.dev_mask(), a.dev_id());
TBlob s_data(static_cast<DType*>(s_handle.dptr), s_shape1, a.dev_mask(), a.dev_id());
TBlob v_data(static_cast<DType*>(v_handle.dptr), v_shape2, a.dev_mask(), a.dev_id());
// Get workspace size in linalg_gesvd.
if (a.shape_[a_ndim - 2] >= a.shape_[a_ndim - 1]) {
workspace_size += linalg_gesvd_workspace_query(v_data.get<xpu, 2, DType>(s),
s_data.get<xpu, 1, DType>(s),
u_data.get<xpu, 2, DType>(s), s);
} else {
workspace_size += linalg_gesvd_workspace_query(u_data.get<xpu, 2, DType>(s),
s_data.get<xpu, 1, DType>(s),
v_data.get<xpu, 2, DType>(s), s);
}
Storage::Get()->Free(u_handle);
Storage::Get()->Free(s_handle);
Storage::Get()->Free(v_handle);
}
return workspace_size;
}
static size_t MatrixRankNoneTolForwardWSQ(size_t *svd_workspace_size,
const TBlob& a,
const OpContext& ctx) {
size_t workspace_size = 0;
mxnet::TShape u_shape, s_shape, v_shape;
GetPinvShape(a.shape_, &u_shape, &s_shape, &v_shape);
*svd_workspace_size = SVDWorkspaceSizeQuery(a, u_shape, s_shape, v_shape, ctx);
workspace_size += *svd_workspace_size; // For #gesdd_ or #gesvd work space.
workspace_size += u_shape.Size(); // For UT.
workspace_size += s_shape.Size(); // For S.
workspace_size += v_shape.Size(); // For V.
return workspace_size * sizeof(DType);
}
static size_t MatrixRankForwardWSQ(size_t *svd_workspace_size,
const TBlob& a,
const TBlob& tol,
const nnvm::NodeAttrs& attrs,
const OpContext& ctx) {
const mxnet::TShape a_shape = a.shape_;
const mxnet::TShape tol_shape = tol.shape_;
size_t workspace_size = 0;
mxnet::TShape u_shape, s_shape, v_shape;
GetPinvShape(a.shape_, &u_shape, &s_shape, &v_shape);
mxnet::TShape broadcast_shape, new_tol_shape;
GetOrCheckBroadcastShape(attrs, a_shape, tol_shape, &broadcast_shape, &new_tol_shape);
*svd_workspace_size = SVDWorkspaceSizeQuery(a, u_shape, s_shape, v_shape, ctx);
workspace_size += *svd_workspace_size; // For #gesdd_ or #gesvd work space.
workspace_size += u_shape.Size(); // For UT.
workspace_size += s_shape.Size(); // For S.
workspace_size += v_shape.Size(); // For V.
workspace_size += new_tol_shape.Size(); // For tol with newaxis.
workspace_size += broadcast_shape.Size(); // For binary broadcast shape.
return workspace_size * sizeof(DType);
}
};
template<typename xpu>
void MatrixRankNoneTolForwardImpl(const TBlob& a,
const TBlob& rank,
const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<OpReqType>& req) {
Stream<xpu> *s = ctx.get_stream<xpu>();
const mxnet::TShape& a_shape = a.shape_;
const int a_ndim = a.ndim();
MSHADOW_SGL_DBL_TYPE_SWITCH(a.type_flag_, DType, {
MXNET_ASSIGN_REQ_SWITCH(req[0], req_type, {
if (a_ndim < 2) {
mxnet_op::Kernel<VectorRankKernel<req_type>, xpu>::Launch(
s, 1, a.dptr<DType>(), rank.dptr<int64_t>(), a.Size());
return;
}
// a_ndim >= 2
const int nrow = a_shape[a_ndim - 2];
const int ncol = a_shape[a_ndim - 1];
const MatrixRankNoneTolParam& param = nnvm::get<MatrixRankNoneTolParam>(attrs.parsed);
CHECK_EQ(param.hermitian, false)
<< "matrix_rank not support param.hermitian = true at present.";
double finfoEps = a.type_flag_ == mshadow::kFloat32 ? param.finfoEps32 : param.finfoEps64;
// Step1: Calculate workspace size.
size_t svd_workspace_size = 0;
size_t workspace_size =
WSQ<xpu, DType>::MatrixRankNoneTolForwardWSQ(&svd_workspace_size, a, ctx);
Tensor<xpu, 1, char> workspace =
ctx.requested[0].get_space_typed<xpu, 1, char>(Shape1(workspace_size), s);
// Step2: Allocate memory.
mxnet::TShape s_shape, u_shape, v_shape, ut_shape, vt_shape;
GetPinvShape(a_shape, &u_shape, &s_shape, &v_shape, &ut_shape, &vt_shape);
DType *s_ptr = reinterpret_cast<DType*>(workspace.dptr_);
DType *u_ptr = s_ptr + s_shape.Size();
DType *v_ptr = u_ptr + u_shape.Size();
DType *work_ptr = v_ptr + v_shape.Size();
TBlob s_data(s_ptr, s_shape, a.dev_mask(), a.dev_id());
TBlob u_data(u_ptr, u_shape, a.dev_mask(), a.dev_id());
TBlob v_data(v_ptr, v_shape, a.dev_mask(), a.dev_id());
TBlob work_data(work_ptr, Shape1(svd_workspace_size), a.dev_mask(), a.dev_id());
// Step3: SVD.
SVDWrapper::op<xpu, DType>(a, s_data, u_data, ut_shape, v_data, vt_shape, work_data, ctx);
// Step4: Calculate rank.
const int data_size = s_data.size(s_data.ndim() - 1);
const int batch_size = a_ndim == 2 ? 1 : s_shape.ProdShape(0, s_shape.ndim() - 1);
mxnet_op::Kernel<MatrixRankNoneTolKernel<req_type>, xpu>::Launch(s, batch_size,
s_data.dptr<DType>(),
rank.dptr<int64_t>(),
nrow, ncol, finfoEps,
data_size, batch_size);
});
});
}
template<typename xpu>
void MatrixRankNoneTolForward(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
CHECK_EQ(inputs.size(), 1U);
CHECK_EQ(outputs.size(), 1U);
CHECK_EQ(req.size(), 1U);
if (kNullOp == req[0]) { return; }
CHECK(req[0] == kWriteTo || req[0] == kWriteInplace);
const TBlob& a = inputs[0];
const TBlob& rank = outputs[0];
MatrixRankNoneTolForwardImpl<xpu>(a, rank, attrs, ctx, req);
}
// Windows has issues with #ifdefs inside MSHADOW_TYPE_SWITCH
#ifndef __CUDACC__
#define NP_LINALG_MATRIX_RANK_BROADCAST(OP, RTCOP) \
mxnet::op::BinaryBroadcastCompute<xpu, op::mshadow_op::OP>
#else
#define NP_LINALG_MATRIX_RANK_BROADCAST(OP, RTCOP) mxnet::op::BinaryBroadcastRTCCompute {#RTCOP}
#endif
template<typename xpu>
void MatrixRankForwardImpl(const TBlob& a,
const TBlob& tol,
const TBlob& rank,
const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<OpReqType>& req) {
Stream<xpu> *s = ctx.get_stream<xpu>();
const mxnet::TShape& a_shape = a.shape_;
const mxnet::TShape& tol_shape = tol.shape_;
const int a_ndim = a.ndim();
MSHADOW_SGL_DBL_TYPE_SWITCH(a.type_flag_, DType, {
MXNET_ASSIGN_REQ_SWITCH(req[0], req_type, {
if (a_ndim < 2) {
mxnet_op::Kernel<VectorRankKernel<req_type>, xpu>::Launch(
s, 1, a.dptr<DType>(), rank.dptr<int64_t>(), a.Size());
return;
}
// a_ndim >= 2
const MatrixRankParam& param = nnvm::get<MatrixRankParam>(attrs.parsed);
CHECK_EQ(param.hermitian, false)
<< "matrix_rank not support param.hermitian = true at present.";
mxnet::TShape s_shape, u_shape, v_shape, ut_shape, vt_shape;
GetPinvShape(a_shape, &u_shape, &s_shape, &v_shape, &ut_shape, &vt_shape);
mxnet::TShape broadcast_shape, new_tol_shape;
GetOrCheckBroadcastShape(attrs, a_shape, tol_shape, &broadcast_shape, &new_tol_shape);
// Step1: Calculate workspace size.
size_t svd_workspace_size = 0;
size_t workspace_size =
WSQ<xpu, DType>::MatrixRankForwardWSQ(&svd_workspace_size, a, tol, attrs, ctx);
Tensor<xpu, 1, char> workspace =
ctx.requested[0].get_space_typed<xpu, 1, char>(Shape1(workspace_size), s);
// Step2: Allocate memory.
DType *s_ptr = reinterpret_cast<DType*>(workspace.dptr_);
DType *u_ptr = s_ptr + s_shape.Size();
DType *v_ptr = u_ptr + u_shape.Size();
DType *work_ptr = v_ptr + v_shape.Size();
DType *new_tol_ptr = work_ptr + svd_workspace_size;
DType *broadcast_ptr = new_tol_ptr + new_tol_shape.Size();
TBlob s_data(s_ptr, s_shape, a.dev_mask(), a.dev_id());
TBlob u_data(u_ptr, u_shape, a.dev_mask(), a.dev_id());
TBlob v_data(v_ptr, v_shape, a.dev_mask(), a.dev_id());
TBlob work_data(work_ptr, Shape1(svd_workspace_size), a.dev_mask(), a.dev_id());
TBlob new_tol_data(new_tol_ptr, new_tol_shape, a.dev_mask(), a.dev_id());
TBlob broadcast_data(broadcast_ptr, broadcast_shape, a.dev_mask(), a.dev_id());
// Step3: SVD.
SVDWrapper::op<xpu, DType>(a, s_data, u_data, ut_shape, v_data, vt_shape, work_data, ctx);
// Step4: Calculate broadcast data.
if (new_tol_data.dptr<DType>() != tol.dptr<DType>()) {
Copy(new_tol_data.FlatTo1D<xpu, DType>(s), tol.FlatTo1D<xpu, DType>(s), s);
}
NP_LINALG_MATRIX_RANK_BROADCAST(gt, greater)(attrs, ctx,
{s_data, new_tol_data},
{kWriteTo}, {broadcast_data});
// Step5: Calculate rank.
const int b_ndim = broadcast_shape.ndim();
const int data_size = broadcast_data.size(b_ndim - 1);
const int batch_size = b_ndim == 1 ? 1 : broadcast_shape.ProdShape(0, b_ndim - 1);
mxnet_op::Kernel<MatrixRankKernel<req_type>, xpu>::Launch(s, batch_size,
broadcast_data.dptr<DType>(),
rank.dptr<int64_t>(),
data_size, batch_size);
});
});
}
#undef NP_LINALG_MATRIX_RANK_BROADCAST
template<typename xpu>
void MatrixRankForward(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
CHECK_EQ(req.size(), 1U);
if (kNullOp == req[0]) { return; }
CHECK(req[0] == kWriteTo || req[0] == kWriteInplace);
const TBlob& a = inputs[0];
const TBlob& tol = inputs[1];
const TBlob& rank = outputs[0];
MatrixRankForwardImpl<xpu>(a, tol, rank, attrs, ctx, req);
}
} // namespace op
} // namespace mxnet
#endif // MXNET_OPERATOR_NUMPY_LINALG_NP_MATRIX_RANK_INL_H_