未验证 提交 31d3d857 编写于 作者: Z zyfncg 提交者: GitHub

[PHI] Code auto-generate for Sparse API (#40060)

* suppport sparse api in yaml

* support auto-gen code of sparse api

* do some refactor

* add unittest test_sparse_conv_api

* add unitest file
Co-authored-by: Nzkh2016 <zhangkaihuo@baidu.com>
上级 b8a16911
...@@ -7,9 +7,11 @@ paddle/fluid/op_use_default_grad_maker_DEV.spec ...@@ -7,9 +7,11 @@ paddle/fluid/op_use_default_grad_maker_DEV.spec
paddle/fluid/op_use_default_grad_maker_PR.spec paddle/fluid/op_use_default_grad_maker_PR.spec
paddle/phi/api/backward/backward_api.h paddle/phi/api/backward/backward_api.h
paddle/phi/api/include/api.h paddle/phi/api/include/api.h
paddle/phi/api/include/sparse_api.h
paddle/phi/api/lib/api.cc paddle/phi/api/lib/api.cc
paddle/phi/api/lib/dygraph_api.* paddle/phi/api/lib/dygraph_api.*
paddle/phi/api/lib/backward_api.cc paddle/phi/api/lib/backward_api.cc
paddle/phi/api/lib/sparse_api.cc
paddle/phi/extension.h paddle/phi/extension.h
paddle/phi/include/* paddle/phi/include/*
paddle/phi/infermeta/generated.* paddle/phi/infermeta/generated.*
......
...@@ -32,6 +32,14 @@ set(bw_api_source_file ${CMAKE_SOURCE_DIR}/paddle/phi/api/lib/backward_api.cc) ...@@ -32,6 +32,14 @@ set(bw_api_source_file ${CMAKE_SOURCE_DIR}/paddle/phi/api/lib/backward_api.cc)
set(bw_api_header_file_tmp ${bw_api_header_file}.tmp) set(bw_api_header_file_tmp ${bw_api_header_file}.tmp)
set(bw_api_source_file_tmp ${bw_api_source_file}.tmp) set(bw_api_source_file_tmp ${bw_api_source_file}.tmp)
# sparse api file
set(sparse_api_gen_file ${CMAKE_SOURCE_DIR}/python/paddle/utils/code_gen/sparse_api_gen.py)
set(sparse_api_yaml_file ${CMAKE_SOURCE_DIR}/python/paddle/utils/code_gen/sparse_api.yaml)
set(sparse_api_header_file ${CMAKE_SOURCE_DIR}/paddle/phi/api/include/sparse_api.h)
set(sparse_api_source_file ${CMAKE_SOURCE_DIR}/paddle/phi/api/lib/sparse_api.cc)
set(sparse_api_header_file_tmp ${api_header_file}.tmp)
set(sparse_api_source_file_tmp ${api_source_file}.tmp)
# wrapped infermeta file # wrapped infermeta file
set(wrapped_infermeta_gen_file ${CMAKE_SOURCE_DIR}/python/paddle/utils/code_gen/wrapped_infermeta_gen.py) set(wrapped_infermeta_gen_file ${CMAKE_SOURCE_DIR}/python/paddle/utils/code_gen/wrapped_infermeta_gen.py)
set(api_yaml_file ${CMAKE_SOURCE_DIR}/python/paddle/utils/code_gen/api.yaml) set(api_yaml_file ${CMAKE_SOURCE_DIR}/python/paddle/utils/code_gen/api.yaml)
...@@ -73,6 +81,19 @@ add_custom_command( ...@@ -73,6 +81,19 @@ add_custom_command(
DEPENDS ${bw_api_yaml_file} ${bw_api_gen_file} ${api_gen_base} DEPENDS ${bw_api_yaml_file} ${bw_api_gen_file} ${api_gen_base}
VERBATIM) VERBATIM)
# generate sparse api
add_custom_command(
OUTPUT ${sparse_api_header_file} ${sparse_api_source_file}
COMMAND ${PYTHON_EXECUTABLE} ${sparse_api_gen_file}
--api_yaml_path ${sparse_api_yaml_file}
--api_header_path ${sparse_api_header_file_tmp}
--api_source_path ${sparse_api_source_file_tmp}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${sparse_api_header_file_tmp} ${sparse_api_header_file}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${sparse_api_source_file_tmp} ${sparse_api_source_file}
COMMENT "copy_if_different ${sparse_api_header_file} ${sparse_sparse_api_source_file}"
DEPENDS ${sparse_api_yaml_file} ${sparse_api_gen_file} ${api_gen_base}
VERBATIM)
# generate wrapped infermeta # generate wrapped infermeta
add_custom_command( add_custom_command(
OUTPUT ${wrapped_infermeta_header_file} ${wrapped_infermeta_source_file} OUTPUT ${wrapped_infermeta_header_file} ${wrapped_infermeta_source_file}
...@@ -87,12 +108,14 @@ cc_library(op_meta_info SRCS op_meta_info.cc DEPS phi_tensor_raw) ...@@ -87,12 +108,14 @@ cc_library(op_meta_info SRCS op_meta_info.cc DEPS phi_tensor_raw)
cc_library(wrapped_infermeta SRCS ${wrapped_infermeta_source_file} DEPS phi) cc_library(wrapped_infermeta SRCS ${wrapped_infermeta_source_file} DEPS phi)
cc_library(kernel_dispatch SRCS kernel_dispatch.cc DEPS phi_tensor_raw phi_context kernel_factory) cc_library(kernel_dispatch SRCS kernel_dispatch.cc DEPS phi_tensor_raw phi_context kernel_factory)
cc_library(api_gen_utils SRCS api_gen_utils.cc DEPS phi_tensor_raw selected_rows sparse_csr_tensor sparse_coo_tensor)
cc_library(phi_data_transform SRCS data_transform.cc DEPS phi_tensor_raw transfer_layout_kernel cast_kernel data_device_transform) cc_library(phi_data_transform SRCS data_transform.cc DEPS phi_tensor_raw transfer_layout_kernel cast_kernel data_device_transform)
cc_library(api_custom_impl SRCS api_custom_impl.cc DEPS phi_tensor_raw phi kernel_dispatch phi_data_transform) cc_library(api_custom_impl SRCS api_custom_impl.cc DEPS phi_tensor_raw phi kernel_dispatch api_gen_utils phi_data_transform)
cc_library(sparse_api_custom_impl SRCS sparse_api_custom_impl.cc DEPS phi_tensor_raw phi kernel_dispatch api_gen_utils phi_data_transform)
cc_library(sparse_api SRCS sparse_api.cc DEPS phi_tensor_raw phi kernel_dispatch phi_data_transform) cc_library(sparse_api SRCS sparse_api.cc DEPS phi_tensor_raw phi kernel_dispatch api_gen_utils sparse_api_custom_impl)
cc_library(phi_function_api SRCS ${api_source_file} DEPS phi_tensor_raw phi kernel_dispatch phi_data_transform api_custom_impl) cc_library(phi_function_api SRCS ${api_source_file} DEPS phi_tensor_raw phi kernel_dispatch api_gen_utils phi_data_transform api_custom_impl)
cc_library(phi_dygraph_api SRCS ${dygraph_api_source_file} DEPS phi_tensor_raw phi kernel_dispatch phi_data_transform) cc_library(phi_dygraph_api SRCS ${dygraph_api_source_file} DEPS phi_tensor_raw phi kernel_dispatch api_gen_utils phi_data_transform)
cc_library(phi_bw_function_api SRCS ${bw_api_source_file} DEPS phi_tensor_raw phi kernel_dispatch backward_infermeta phi_data_transform phi_function_api api_custom_impl) cc_library(phi_bw_function_api SRCS ${bw_api_source_file} DEPS phi_tensor_raw phi kernel_dispatch api_gen_utils backward_infermeta phi_data_transform phi_function_api api_custom_impl)
cc_library(phi_tensor SRCS tensor_method.cc DEPS phi_tensor_raw phi_function_api) cc_library(phi_tensor SRCS tensor_method.cc DEPS phi_tensor_raw phi_function_api)
...@@ -14,8 +14,8 @@ limitations under the License. */ ...@@ -14,8 +14,8 @@ limitations under the License. */
#include "paddle/phi/api/lib/api_custom_impl.h" #include "paddle/phi/api/lib/api_custom_impl.h"
#include "paddle/phi/api/lib/api_gen_utils.h"
#include "paddle/phi/api/lib/api_registry.h" #include "paddle/phi/api/lib/api_registry.h"
#include "paddle/phi/api/lib/api_utils.h"
#include "paddle/phi/api/lib/data_transform.h" #include "paddle/phi/api/lib/data_transform.h"
#include "paddle/phi/api/lib/kernel_dispatch.h" #include "paddle/phi/api/lib/kernel_dispatch.h"
#include "paddle/phi/api/lib/utils/storage.h" #include "paddle/phi/api/lib/utils/storage.h"
......
...@@ -12,26 +12,18 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ...@@ -12,26 +12,18 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. */ limitations under the License. */
#pragma once #include "paddle/phi/api/lib/api_gen_utils.h"
#include "paddle/phi/api/include/tensor.h"
#include "paddle/phi/api/lib/utils/storage.h"
#include "paddle/phi/core/compat/convert_utils.h"
#include "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/core/meta_tensor.h"
#include "paddle/phi/core/selected_rows.h"
namespace paddle { namespace paddle {
namespace experimental { namespace experimental {
/* ------------------ for input ----------------------- */ /* ------------------ for input ----------------------- */
inline std::shared_ptr<phi::DenseTensor> TensorToDenseTensor( std::shared_ptr<phi::DenseTensor> TensorToDenseTensor(const Tensor& tensor) {
const Tensor& tensor) {
return std::dynamic_pointer_cast<phi::DenseTensor>(tensor.impl()); return std::dynamic_pointer_cast<phi::DenseTensor>(tensor.impl());
} }
inline std::shared_ptr<phi::DenseTensor> TensorToDenseTensor( std::shared_ptr<phi::DenseTensor> TensorToDenseTensor(
const paddle::optional<Tensor>& tensor) { const paddle::optional<Tensor>& tensor) {
if (tensor) { if (tensor) {
return std::dynamic_pointer_cast<phi::DenseTensor>(tensor->impl()); return std::dynamic_pointer_cast<phi::DenseTensor>(tensor->impl());
...@@ -39,7 +31,7 @@ inline std::shared_ptr<phi::DenseTensor> TensorToDenseTensor( ...@@ -39,7 +31,7 @@ inline std::shared_ptr<phi::DenseTensor> TensorToDenseTensor(
return nullptr; return nullptr;
} }
inline std::unique_ptr<std::vector<phi::DenseTensor>> TensorToDenseTensor( std::unique_ptr<std::vector<phi::DenseTensor>> TensorToDenseTensor(
const std::vector<Tensor>& tensors) { const std::vector<Tensor>& tensors) {
auto pt_tensors = std::make_unique<std::vector<phi::DenseTensor>>(); auto pt_tensors = std::make_unique<std::vector<phi::DenseTensor>>();
pt_tensors->reserve(tensors.size()); pt_tensors->reserve(tensors.size());
...@@ -52,12 +44,11 @@ inline std::unique_ptr<std::vector<phi::DenseTensor>> TensorToDenseTensor( ...@@ -52,12 +44,11 @@ inline std::unique_ptr<std::vector<phi::DenseTensor>> TensorToDenseTensor(
return std::move(pt_tensors); return std::move(pt_tensors);
} }
inline std::shared_ptr<phi::SelectedRows> TensorToSelectedRows( std::shared_ptr<phi::SelectedRows> TensorToSelectedRows(const Tensor& tensor) {
const Tensor& tensor) {
return std::dynamic_pointer_cast<phi::SelectedRows>(tensor.impl()); return std::dynamic_pointer_cast<phi::SelectedRows>(tensor.impl());
} }
inline std::shared_ptr<phi::SelectedRows> TensorToSelectedRows( std::shared_ptr<phi::SelectedRows> TensorToSelectedRows(
const paddle::optional<Tensor>& tensor) { const paddle::optional<Tensor>& tensor) {
if (tensor) { if (tensor) {
return std::dynamic_pointer_cast<phi::SelectedRows>(tensor->impl()); return std::dynamic_pointer_cast<phi::SelectedRows>(tensor->impl());
...@@ -67,11 +58,11 @@ inline std::shared_ptr<phi::SelectedRows> TensorToSelectedRows( ...@@ -67,11 +58,11 @@ inline std::shared_ptr<phi::SelectedRows> TensorToSelectedRows(
/* ----------------- for infer_meta --------------------- */ /* ----------------- for infer_meta --------------------- */
inline phi::MetaTensor MakeMetaTensor(const phi::DenseTensor& tensor) { phi::MetaTensor MakeMetaTensor(const phi::DenseTensor& tensor) {
return phi::MetaTensor(tensor); return phi::MetaTensor(tensor);
} }
inline paddle::optional<phi::MetaTensor> MakeMetaTensor( paddle::optional<phi::MetaTensor> MakeMetaTensor(
const paddle::optional<const phi::DenseTensor&>& tensor) { const paddle::optional<const phi::DenseTensor&>& tensor) {
if (tensor) { if (tensor) {
return {phi::MetaTensor(*tensor)}; return {phi::MetaTensor(*tensor)};
...@@ -79,7 +70,7 @@ inline paddle::optional<phi::MetaTensor> MakeMetaTensor( ...@@ -79,7 +70,7 @@ inline paddle::optional<phi::MetaTensor> MakeMetaTensor(
return {paddle::none}; return {paddle::none};
} }
inline std::vector<phi::MetaTensor> MakeMetaTensor( std::vector<phi::MetaTensor> MakeMetaTensor(
const std::vector<phi::DenseTensor>& tensors) { const std::vector<phi::DenseTensor>& tensors) {
std::vector<phi::MetaTensor> meta_tensors; std::vector<phi::MetaTensor> meta_tensors;
meta_tensors.reserve(tensors.size()); meta_tensors.reserve(tensors.size());
...@@ -89,11 +80,11 @@ inline std::vector<phi::MetaTensor> MakeMetaTensor( ...@@ -89,11 +80,11 @@ inline std::vector<phi::MetaTensor> MakeMetaTensor(
return meta_tensors; return meta_tensors;
} }
inline phi::MetaTensor MakeMetaTensor(const phi::SelectedRows& tensor) { phi::MetaTensor MakeMetaTensor(const phi::SelectedRows& tensor) {
return phi::MetaTensor(tensor); return phi::MetaTensor(tensor);
} }
inline paddle::optional<phi::MetaTensor> MakeMetaTensor( paddle::optional<phi::MetaTensor> MakeMetaTensor(
const paddle::optional<const phi::SelectedRows&>& tensor) { const paddle::optional<const phi::SelectedRows&>& tensor) {
if (tensor) { if (tensor) {
return {phi::MetaTensor(*tensor)}; return {phi::MetaTensor(*tensor)};
...@@ -103,7 +94,7 @@ inline paddle::optional<phi::MetaTensor> MakeMetaTensor( ...@@ -103,7 +94,7 @@ inline paddle::optional<phi::MetaTensor> MakeMetaTensor(
/* ------------------ for output ----------------------- */ /* ------------------ for output ----------------------- */
inline phi::DenseTensor* SetKernelOutput(Backend backend, Tensor* out) { phi::DenseTensor* SetKernelOutput(Backend backend, Tensor* out) {
if (!out->initialized()) { if (!out->initialized()) {
auto dense_tensor = std::make_shared<phi::DenseTensor>( auto dense_tensor = std::make_shared<phi::DenseTensor>(
phi::make_intrusive<SharedStorage>(phi::TransToPhiPlace(backend)), phi::make_intrusive<SharedStorage>(phi::TransToPhiPlace(backend)),
...@@ -114,8 +105,9 @@ inline phi::DenseTensor* SetKernelOutput(Backend backend, Tensor* out) { ...@@ -114,8 +105,9 @@ inline phi::DenseTensor* SetKernelOutput(Backend backend, Tensor* out) {
return static_cast<phi::DenseTensor*>(out->impl().get()); return static_cast<phi::DenseTensor*>(out->impl().get());
} }
inline std::vector<phi::DenseTensor*> SetKernelOutput( std::vector<phi::DenseTensor*> SetKernelOutput(size_t out_size,
size_t out_size, Backend backend, std::vector<Tensor>* out) { Backend backend,
std::vector<Tensor>* out) {
out->reserve(out_size); out->reserve(out_size);
std::vector<phi::DenseTensor*> results(out_size); std::vector<phi::DenseTensor*> results(out_size);
for (size_t i = 0; i < out_size; ++i) { for (size_t i = 0; i < out_size; ++i) {
...@@ -129,8 +121,7 @@ inline std::vector<phi::DenseTensor*> SetKernelOutput( ...@@ -129,8 +121,7 @@ inline std::vector<phi::DenseTensor*> SetKernelOutput(
return results; return results;
} }
inline phi::SelectedRows* SetSelectedRowsKernelOutput(Backend backend, phi::SelectedRows* SetSelectedRowsKernelOutput(Backend backend, Tensor* out) {
Tensor* out) {
if (!out->initialized()) { if (!out->initialized()) {
auto select_rows = std::make_shared<phi::SelectedRows>(); auto select_rows = std::make_shared<phi::SelectedRows>();
out->set_impl(select_rows); out->set_impl(select_rows);
...@@ -139,5 +130,29 @@ inline phi::SelectedRows* SetSelectedRowsKernelOutput(Backend backend, ...@@ -139,5 +130,29 @@ inline phi::SelectedRows* SetSelectedRowsKernelOutput(Backend backend,
return static_cast<phi::SelectedRows*>(out->impl().get()); return static_cast<phi::SelectedRows*>(out->impl().get());
} }
phi::TensorBase* SetSparseKernelOutput(Tensor* out, TensorType type) {
if (!out->initialized()) {
if (type == TensorType::SPARSE_COO) {
auto sparse_tensor = std::make_shared<phi::SparseCooTensor>(
phi::DenseTensor(), phi::DenseTensor(), phi::DDim{-1});
out->set_impl(sparse_tensor);
return sparse_tensor.get();
} else if (type == TensorType::SPARSE_CSR) {
auto sparse_tensor =
std::make_shared<phi::SparseCsrTensor>(phi::DenseTensor(),
phi::DenseTensor(),
phi::DenseTensor(),
phi::DDim{-1});
out->set_impl(sparse_tensor);
return sparse_tensor.get();
} else {
auto dense_tensor = std::make_shared<phi::DenseTensor>();
out->set_impl(dense_tensor);
return dense_tensor.get();
}
}
return out->impl().get();
}
} // namespace experimental } // namespace experimental
} // namespace paddle } // namespace paddle
/* 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/api/include/tensor.h"
#include "paddle/phi/api/lib/utils/storage.h"
#include "paddle/phi/core/compat/convert_utils.h"
#include "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/core/meta_tensor.h"
#include "paddle/phi/core/selected_rows.h"
#include "paddle/phi/core/sparse_coo_tensor.h"
#include "paddle/phi/core/sparse_csr_tensor.h"
namespace paddle {
namespace experimental {
enum class TensorType { DENSE_TENSOR, SPARSE_CSR, SPARSE_COO };
/* ------------------ for input ----------------------- */
std::shared_ptr<phi::DenseTensor> TensorToDenseTensor(const Tensor& tensor);
std::shared_ptr<phi::DenseTensor> TensorToDenseTensor(
const paddle::optional<Tensor>& tensor);
std::unique_ptr<std::vector<phi::DenseTensor>> TensorToDenseTensor(
const std::vector<Tensor>& tensors);
std::shared_ptr<phi::SelectedRows> TensorToSelectedRows(const Tensor& tensor);
std::shared_ptr<phi::SelectedRows> TensorToSelectedRows(
const paddle::optional<Tensor>& tensor);
/* ----------------- for infer_meta --------------------- */
phi::MetaTensor MakeMetaTensor(const phi::DenseTensor& tensor);
paddle::optional<phi::MetaTensor> MakeMetaTensor(
const paddle::optional<const phi::DenseTensor&>& tensor);
std::vector<phi::MetaTensor> MakeMetaTensor(
const std::vector<phi::DenseTensor>& tensors);
phi::MetaTensor MakeMetaTensor(const phi::SelectedRows& tensor);
paddle::optional<phi::MetaTensor> MakeMetaTensor(
const paddle::optional<const phi::SelectedRows&>& tensor);
/* ------------------ for output ----------------------- */
phi::DenseTensor* SetKernelOutput(Backend backend, Tensor* out);
std::vector<phi::DenseTensor*> SetKernelOutput(size_t out_size,
Backend backend,
std::vector<Tensor>* out);
phi::SelectedRows* SetSelectedRowsKernelOutput(Backend backend, Tensor* out);
phi::TensorBase* SetSparseKernelOutput(Tensor* out, TensorType type);
} // namespace experimental
} // namespace paddle
...@@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ...@@ -12,7 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. */ limitations under the License. */
#include "paddle/phi/api/include/sparse_api.h" #include "paddle/phi/api/lib/sparse_api_custom_impl.h"
#include <memory> #include <memory>
#include "glog/logging.h" #include "glog/logging.h"
...@@ -20,29 +20,12 @@ limitations under the License. */ ...@@ -20,29 +20,12 @@ limitations under the License. */
#include "paddle/phi/api/lib/kernel_dispatch.h" #include "paddle/phi/api/lib/kernel_dispatch.h"
#include "paddle/phi/api/lib/utils/storage.h" #include "paddle/phi/api/lib/utils/storage.h"
#include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/infermeta/unary.h"
PD_DECLARE_KERNEL(dense_to_sparse_coo, CPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(sparse_csr_to_coo, CPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(dense_to_sparse_csr, CPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(sparse_coo_to_csr, CPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(sparse_coo_to_dense, CPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(sparse_csr_to_dense, CPU, ALL_LAYOUT);
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
PD_DECLARE_KERNEL(dense_to_sparse_coo, GPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(sparse_csr_to_coo, GPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(dense_to_sparse_csr, GPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(sparse_coo_to_csr, GPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(sparse_coo_to_dense, GPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(sparse_csr_to_dense, GPU, ALL_LAYOUT);
#endif
namespace paddle { namespace paddle {
namespace experimental { namespace experimental {
namespace sparse { namespace sparse {
PADDLE_API Tensor to_sparse_coo(const Tensor& x, Tensor to_sparse_coo_impl(const Tensor& x,
Backend backend, Backend backend,
const int64_t sparse_dim) { const int64_t sparse_dim) {
if (x.layout() == phi::DataLayout::SPARSE_COO) { if (x.layout() == phi::DataLayout::SPARSE_COO) {
...@@ -105,7 +88,7 @@ PADDLE_API Tensor to_sparse_coo(const Tensor& x, ...@@ -105,7 +88,7 @@ PADDLE_API Tensor to_sparse_coo(const Tensor& x,
return out; return out;
} }
PADDLE_API Tensor to_sparse_csr(const Tensor& x, Backend backend) { Tensor to_sparse_csr_impl(const Tensor& x, Backend backend) {
if (x.layout() == phi::DataLayout::SPARSE_CSR) { if (x.layout() == phi::DataLayout::SPARSE_CSR) {
return x; return x;
} }
...@@ -171,7 +154,7 @@ PADDLE_API Tensor to_sparse_csr(const Tensor& x, Backend backend) { ...@@ -171,7 +154,7 @@ PADDLE_API Tensor to_sparse_csr(const Tensor& x, Backend backend) {
return out; return out;
} }
PADDLE_API Tensor to_dense(const Tensor& x, Backend backend) { Tensor to_dense_impl(const Tensor& x, Backend backend) {
if (x.layout() != phi::DataLayout::SPARSE_CSR && if (x.layout() != phi::DataLayout::SPARSE_CSR &&
x.layout() != phi::DataLayout::SPARSE_COO) { x.layout() != phi::DataLayout::SPARSE_COO) {
return x; return x;
......
...@@ -21,13 +21,13 @@ namespace paddle { ...@@ -21,13 +21,13 @@ namespace paddle {
namespace experimental { namespace experimental {
namespace sparse { namespace sparse {
PADDLE_API Tensor to_sparse_coo(const Tensor& x, Tensor to_dense_impl(const Tensor& x, Backend backend);
Tensor to_sparse_coo_impl(const Tensor& x,
Backend backend, Backend backend,
const int64_t sparse_dim); const int64_t sparse_dim);
PADDLE_API Tensor to_sparse_csr(const Tensor& x, Backend backend); Tensor to_sparse_csr_impl(const Tensor& x, Backend backend);
PADDLE_API Tensor to_dense(const Tensor& x, Backend backend);
} // namespace sparse } // namespace sparse
} // namespace experimental } // namespace experimental
......
...@@ -107,7 +107,9 @@ void ProductRuleBook(const Context& dev_ctx, ...@@ -107,7 +107,9 @@ void ProductRuleBook(const Context& dev_ctx,
f_calc_rulebook(nullptr); f_calc_rulebook(nullptr);
// alloc the rulebook // alloc the rulebook
rulebook->ResizeAndAllocate({3, rulebook_len}); DenseTensorMeta rulebook_meta(
DataType::INT32, {3, rulebook_len}, DataLayout::NCHW);
rulebook->set_meta(rulebook_meta);
dev_ctx.Alloc(rulebook, rulebook->dtype(), rulebook->numel() * sizeof(int)); dev_ctx.Alloc(rulebook, rulebook->dtype(), rulebook->numel() * sizeof(int));
int* rulebook_ptr = rulebook->data<int>(); int* rulebook_ptr = rulebook->data<int>();
f_calc_rulebook(rulebook_ptr); f_calc_rulebook(rulebook_ptr);
......
...@@ -25,3 +25,4 @@ cc_test(test_concat_api SRCS test_concat_api.cc DEPS phi_tensor phi_api phi_api_ ...@@ -25,3 +25,4 @@ cc_test(test_concat_api SRCS test_concat_api.cc DEPS phi_tensor phi_api phi_api_
cc_test(test_split_api SRCS test_split_api.cc DEPS phi_tensor phi_api phi_api_utils) cc_test(test_split_api SRCS test_split_api.cc DEPS phi_tensor phi_api phi_api_utils)
cc_test(test_data_transform SRCS test_data_transform.cc DEPS phi_tensor phi_api phi_api_utils) cc_test(test_data_transform SRCS test_data_transform.cc DEPS phi_tensor phi_api phi_api_utils)
cc_test(test_sparse_utils_api SRCS test_sparse_utils_api.cc DEPS phi_tensor phi_api phi_api_utils) cc_test(test_sparse_utils_api SRCS test_sparse_utils_api.cc DEPS phi_tensor phi_api phi_api_utils)
cc_test(test_sparse_conv_api SRCS test_sparse_conv_api.cc DEPS phi_tensor phi_api phi_api_utils)
/* 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. */
#include <gtest/gtest.h>
#include <memory>
#include "paddle/phi/api/include/api.h"
#include "paddle/phi/api/include/sparse_api.h"
#include "paddle/phi/api/lib/utils/allocator.h"
#include "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/core/sparse_coo_tensor.h"
template <typename T>
void TestConv3dBase(const std::vector<int>& indices,
const std::vector<T>& features,
const phi::DDim& x_dims,
const std::vector<T>& kernel,
const phi::DDim& kernel_dims,
const std::vector<int>& correct_out_indices,
const std::vector<T>& correct_out_features,
const phi::DDim& correct_out_dims,
const int non_zero_num,
const std::vector<int>& paddings,
const std::vector<int>& strides,
const std::vector<int>& dilations,
const float diff = 1e-3) {
const auto alloc = std::make_unique<paddle::experimental::DefaultAllocator>(
paddle::platform::CPUPlace());
const int in_channels = kernel_dims[3];
const int out_channels = kernel_dims[4];
phi::DenseTensor indices_tensor(
alloc.get(),
phi::DenseTensorMeta(
phi::DataType::INT32, {4, non_zero_num}, phi::DataLayout::NCHW));
memcpy(
indices_tensor.data<int>(), indices.data(), indices.size() * sizeof(int));
phi::DenseTensor features_tensor(
alloc.get(),
phi::DenseTensorMeta(paddle::experimental::CppTypeToDataType<T>::Type(),
{non_zero_num, in_channels},
phi::DataLayout::NHWC));
memcpy(
features_tensor.data<T>(), features.data(), features.size() * sizeof(T));
auto x_tensor = std::make_shared<phi::SparseCooTensor>(
indices_tensor, features_tensor, x_dims);
paddle::experimental::Tensor x(x_tensor);
auto kernel_tensor = std::make_shared<phi::DenseTensor>(
alloc.get(),
phi::DenseTensorMeta(paddle::experimental::CppTypeToDataType<T>::Type(),
kernel_dims,
phi::DataLayout::NHWC));
paddle::experimental::Tensor weight(kernel_tensor);
memcpy(kernel_tensor->mutable_data<T>(paddle::platform::CPUPlace()),
kernel.data(),
kernel.size() * sizeof(T));
if (!std::is_same<T, phi::dtype::float16>::value) {
auto outs = paddle::experimental::sparse::conv3d(
x, weight, paddings, dilations, strides, 1);
auto out = std::dynamic_pointer_cast<phi::SparseCooTensor>(
std::get<0>(outs).impl());
ASSERT_EQ(correct_out_dims.size(), out->dims().size());
for (int i = 0; i < correct_out_dims.size(); i++) {
ASSERT_EQ(correct_out_dims[i], out->dims()[i]);
}
ASSERT_EQ((int64_t)correct_out_features.size() / out_channels, out->nnz());
int cmp_indices = memcmp(correct_out_indices.data(),
out->non_zero_indices().data<int>(),
correct_out_indices.size() * sizeof(int));
ASSERT_EQ(cmp_indices, 0);
for (uint64_t i = 0; i < correct_out_features.size(); i++) {
float tmp = std::fabs(static_cast<float>(
correct_out_features[i] - out->non_zero_elements().data<T>()[i]));
ASSERT_LT(tmp, diff);
}
}
}
void TestConv3d(const std::vector<int>& indices,
const std::vector<float>& features,
const phi::DDim& x_dims,
const std::vector<float>& kernel,
const phi::DDim& kernel_dims,
const std::vector<int>& correct_out_indices,
const std::vector<float>& correct_out_features,
const phi::DDim& correct_out_dims,
const int non_zero_num,
const std::vector<int>& paddings,
const std::vector<int>& strides,
const std::vector<int>& dilations) {
// test float
TestConv3dBase<float>(indices,
features,
x_dims,
kernel,
kernel_dims,
correct_out_indices,
correct_out_features,
correct_out_dims,
non_zero_num,
paddings,
strides,
dilations);
}
TEST(API, sparse_conv2d) {
const auto alloc = std::make_shared<paddle::experimental::DefaultAllocator>(
paddle::platform::CPUPlace());
const int in_channels = 1;
const int out_channels = 1;
phi::DDim x_dims = {1, 1, 5, 5, in_channels};
phi::DDim kernel_dims = {1, 3, 3, in_channels, out_channels};
phi::DDim out_dims = {1, 1, 3, 3, out_channels};
std::vector<int> paddings = {0, 0, 0};
std::vector<int> strides = {1, 1, 1};
std::vector<int> dilations = {1, 1, 1};
const int non_zero_num = 3;
std::vector<int> indices_flatten = {0, 0, 0, 0, 0, 0, 0, 4, 0, 3, 2, 4};
std::vector<float> features = {-0.79394531, -0.3125, -0.55029297};
// 3*3*3=27
std::vector<float> kernel = {0.65820312,
0.75048828,
0.21411133,
0.17370605,
0.85546875,
0.53076172,
0.28833008,
0.71044922,
0.00659943};
std::vector<int> out_indices_flatten = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 2, 2, 2, 1, 2, 0, 1, 2};
std::vector<float> out_features = {
-0.17004, -0.71338, -0.00206, -0.22205, -0.09009};
TestConv3d(indices_flatten,
features,
x_dims,
kernel,
kernel_dims,
out_indices_flatten,
out_features,
out_dims,
non_zero_num,
paddings,
strides,
dilations);
}
...@@ -43,7 +43,9 @@ class BaseAPI(object): ...@@ -43,7 +43,9 @@ class BaseAPI(object):
self.is_base_api = False self.is_base_api = False
self.invoke = api_item_yaml['invoke'] self.invoke = api_item_yaml['invoke']
else: else:
self.infer_meta = self.parse_infer_meta(api_item_yaml['infer_meta']) if 'infer_meta' in api_item_yaml:
self.infer_meta = self.parse_infer_meta(api_item_yaml[
'infer_meta'])
self.kernel = self.parse_kernel(api_item_yaml['kernel']) self.kernel = self.parse_kernel(api_item_yaml['kernel'])
self.support_selected_rows_kernel = False if len(self.kernel[ self.support_selected_rows_kernel = False if len(self.kernel[
'func']) == 1 else True 'func']) == 1 else True
...@@ -182,9 +184,9 @@ class BaseAPI(object): ...@@ -182,9 +184,9 @@ class BaseAPI(object):
'Tensor': 'Tensor', 'Tensor': 'Tensor',
'Tensor[]': 'std::vector<Tensor>' 'Tensor[]': 'std::vector<Tensor>'
} }
if re.search(r'\(\w*\)', output_item): if re.search(r'\([a-zA-Z0-9_@]*\)', output_item):
result = re.search( result = re.search(
r"(?P<out_type>[a-zA-Z0-9_[\]]+)\s*\((?P<name>\w+)\)", r"(?P<out_type>[a-zA-Z0-9_[\]]+)\s*\((?P<name>[a-zA-Z0-9_@]+)\)",
output_item) output_item)
out_type = result.group('out_type') out_type = result.group('out_type')
assert out_type in output_type_map, \ assert out_type in output_type_map, \
...@@ -499,11 +501,8 @@ PADDLE_API {self.outputs['return_type']} {self.get_api_func_name() + '_'}({self. ...@@ -499,11 +501,8 @@ PADDLE_API {self.outputs['return_type']} {self.get_api_func_name() + '_'}({self.
def get_kernel_args(self, code_indent): def get_kernel_args(self, code_indent):
input_trans_map = { input_trans_map = {
'const Tensor&': 'const phi::DenseTensor&', 'const Tensor&': 'const phi::DenseTensor&',
'const Tensor &': 'const phi::DenseTensor&',
'const std::vector<Tensor>&': 'const std::vector<Tensor>&':
'const std::vector<phi::DenseTensor>&', 'const std::vector<phi::DenseTensor>&',
'const std::vector<Tensor> &':
'const std::vector<phi::DenseTensor>&',
'const paddle::optional<Tensor>&': 'const paddle::optional<Tensor>&':
'paddle::optional<const phi::DenseTensor&>', 'paddle::optional<const phi::DenseTensor&>',
'const paddle::optional<std::vector<Tensor>>&': 'const paddle::optional<std::vector<Tensor>>&':
...@@ -592,7 +591,6 @@ PADDLE_API {self.outputs['return_type']} {self.get_api_func_name() + '_'}({self. ...@@ -592,7 +591,6 @@ PADDLE_API {self.outputs['return_type']} {self.get_api_func_name() + '_'}({self.
def get_selected_rows_kernel_args(self, code_indent): def get_selected_rows_kernel_args(self, code_indent):
input_trans_map = { input_trans_map = {
'const Tensor&': 'const phi::SelectedRows&', 'const Tensor&': 'const phi::SelectedRows&',
'const Tensor &': 'const phi::SelectedRows&',
'const paddle::optional<Tensor>&': 'const paddle::optional<Tensor>&':
'paddle::optional<const phi::SelectedRows&>' 'paddle::optional<const phi::SelectedRows&>'
} }
......
...@@ -105,7 +105,7 @@ def source_include(header_file_path): ...@@ -105,7 +105,7 @@ def source_include(header_file_path):
#include "paddle/phi/api/lib/api_custom_impl.h" #include "paddle/phi/api/lib/api_custom_impl.h"
#include "paddle/phi/api/lib/api_registry.h" #include "paddle/phi/api/lib/api_registry.h"
#include "paddle/phi/api/lib/api_utils.h" #include "paddle/phi/api/lib/api_gen_utils.h"
#include "paddle/phi/api/lib/data_transform.h" #include "paddle/phi/api/lib/data_transform.h"
#include "paddle/phi/api/lib/kernel_dispatch.h" #include "paddle/phi/api/lib/kernel_dispatch.h"
#include "paddle/phi/api/lib/utils/storage.h" #include "paddle/phi/api/lib/utils/storage.h"
......
...@@ -146,7 +146,7 @@ def source_include(header_file_path): ...@@ -146,7 +146,7 @@ def source_include(header_file_path):
#include "paddle/phi/api/lib/api_custom_impl.h" #include "paddle/phi/api/lib/api_custom_impl.h"
#include "paddle/phi/api/lib/api_registry.h" #include "paddle/phi/api/lib/api_registry.h"
#include "paddle/phi/api/lib/api_utils.h" #include "paddle/phi/api/lib/api_gen_utils.h"
#include "paddle/phi/api/lib/data_transform.h" #include "paddle/phi/api/lib/data_transform.h"
#include "paddle/phi/api/lib/kernel_dispatch.h" #include "paddle/phi/api/lib/kernel_dispatch.h"
#include "paddle/phi/api/lib/utils/storage.h" #include "paddle/phi/api/lib/utils/storage.h"
......
- sparse_api : conv3d
args : (Tensor x, Tensor kernel, int[] paddings, int[] dilations, int[] strides, int groups)
output : Tensor(out@SparseCooTensor), Tensor(rulebook@DenseTensor)
kernel :
func : sparse_conv3d
layout : x
- sparse_api : to_dense
args : (Tensor x, Backend backend)
output : Tensor(out@DenseTensor)
invoke : to_dense_impl(x, backend)
- sparse_api : to_sparse_coo
args : (Tensor x, Backend backend, int64_t sparse_dim)
output : Tensor(out@SparseCooTensor)
invoke : to_sparse_coo_impl(x, backend, sparse_dim)
- sparse_api : to_sparse_csr
args : (Tensor x, Backend backend)
output : Tensor(out@SparseCsrTensor)
invoke : to_sparse_csr_impl(x, backend)
# 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.
import os
import yaml
import argparse
import re
from api_base import BaseAPI
class SparseAPI(BaseAPI):
def __init__(self, api_item_yaml):
super(SparseAPI, self).__init__(api_item_yaml)
def get_api_name(self, api_item_yaml):
return api_item_yaml['sparse_api']
def get_api_func_name(self):
return self.api
def get_return_type(self, out_type_list):
return out_type_list[0] if len(
out_type_list) == 1 else "std::tuple<" + ",".join(
out_type_list) + ">"
def gene_api_declaration(self):
return f"""
// {", ".join(self.outputs['names'])}
PADDLE_API {self.outputs['return_type']} {self.get_api_func_name()}({self.args_str['args_declare']});
"""
def get_kernel_tensor_out_type(self, output_name):
sparse_type = 'TensorType::DENSE_TENSOR'
if output_name.endswith('@SparseCooTensor'):
sparse_type = 'TensorType::SPARSE_COO'
elif output_name.endswith('@SparseCsrTensor'):
sparse_type = 'TensorType::SPARSE_CSR'
return sparse_type
def gene_output(self,
output_type_list,
set_out_func,
code_indent,
inplace_flag=False):
kernel_output = ""
output_names = []
output_create = ""
if len(output_type_list) == 1:
kernel_output = 'kernel_out'
output_names.append('kernel_out')
inplace_assign = " = " + self.inplace_map[self.outputs['names'][
0]] if inplace_flag and self.inplace_map is not None and self.outputs[
'names'][0] in self.inplace_map else ""
output_create = f"""
{self.outputs['return_type']} out{inplace_assign};
auto* kernel_out = {set_out_func}(&out, {self.get_kernel_tensor_out_type(self.outputs['names'][0])});"""
elif len(output_type_list) > 1:
output_create = f"""
{self.outputs['return_type']} out;"""
for i in range(len(output_type_list)):
kernel_output = kernel_output + f'kernel_out_{i}, '
output_names.append(f'kernel_out_{i}')
if inplace_flag and self.inplace_map is not None and self.outputs[
'names'][i] in self.inplace_map:
output_create = output_create + f"""
std::get<{i}>(out) = {self.inplace_map[self.outputs['names'][i]]};"""
output_create = output_create + f"""
auto* kernel_out_{i} = {set_out_func}(&std::get<{i}>(out), {self.get_kernel_tensor_out_type(self.outputs['names'][i])});"""
kernel_output = kernel_output[:-2]
else:
raise ValueError(
"{} : Output error: the output should not be empty.".format(
self.api))
return kernel_output, output_names, output_create
def gen_sparse_kernel_context(self, kernel_output_names):
input_trans_map = {
'const Tensor&': 'const phi::TenseBase&',
'const std::vector<Tensor>&': 'const std::vector<phi::TenseBase>&',
'const paddle::optional<Tensor>&':
'paddle::optional<const phi::TenseBase&>'
}
out_trans_map = {
'Tensor': 'phi::TenseBase*',
'std::vector<Tensor>': 'std::vector<phi::TenseBase*>'
}
input_names = self.inputs['names']
input_infos = self.inputs['input_info']
attr_names = self.attrs['names']
kernel_param = self.kernel['param']
if kernel_param is None:
kernel_param = input_names + attr_names
kernel_context_code = ""
for param in kernel_param:
if param in input_names:
if param in self.optional_vars:
raise ValueError(
f"{self.api} : Unsupport optional input({param}) for sparse api."
)
else:
kernel_context_code = kernel_context_code + f"""
kernel_context.EmplaceBackInput({param}.impl().get());"""
continue
if param in attr_names:
# set attr for kernel_context
if 'ScalarArray' in self.attrs['attr_info'][param][0]:
param = 'phi::ScalarArray(' + param + ')'
elif 'Scalar' in self.attrs['attr_info'][param][0]:
param = 'phi::Scalar(' + param + ')'
elif isinstance(param, bool):
param = str(param).lower()
else:
param + str(param) + ", "
kernel_context_code = kernel_context_code + f"""
kernel_context.EmplaceBackAttr({param});"""
for out_name in kernel_output_names:
kernel_context_code = kernel_context_code + f"""
kernel_context.EmplaceBackOutput({out_name});"""
return kernel_context_code
def gen_sparse_kernel_code(self, inplace_flag=False):
_, kernel_output_names, output_create = self.gene_output(
self.outputs['types'], 'SetSparseKernelOutput', '', inplace_flag)
kernel_context_code = self.gen_sparse_kernel_context(
kernel_output_names)
return f"""
auto phi_kernel = phi::KernelFactory::Instance().SelectKernelOrThrowError(
"{self.kernel['func'][0]}", {{kernel_backend, kernel_layout, kernel_data_type}});
VLOG(6) << "{self.api} api sparse kernel key: [" << kernel_backend << ", " << kernel_layout << ", "<< kernel_data_type << "]";
VLOG(6) << "{self.api} api sparse kernel: " << phi_kernel;
auto* dev_ctx = GetDeviceContextByBackend(kernel_backend);
auto kernel_context = phi::KernelContext(dev_ctx);
{output_create}
{kernel_context_code}
phi_kernel(&kernel_context);
return out;"""
def gene_base_api_code(self, inplace_flag=False):
api_func_name = self.get_api_func_name()
return f"""
PADDLE_API {self.outputs['return_type']} {api_func_name}({self.args_str["args_define"]}) {{
{self.gene_kernel_select()}
{self.gen_sparse_kernel_code(inplace_flag)}
}}
"""
def header_include():
return """
#include <tuple>
#include "paddle/phi/api/include/tensor.h"
#include "paddle/phi/common/scalar.h"
#include "paddle/phi/common/scalar_array.h"
#include "paddle/utils/optional.h"
"""
def source_include(header_file_path):
return f"""
#include "{header_file_path}"
#include <memory>
#include "glog/logging.h"
#include "paddle/phi/api/lib/api_registry.h"
#include "paddle/phi/api/lib/api_gen_utils.h"
#include "paddle/phi/api/lib/data_transform.h"
#include "paddle/phi/api/lib/kernel_dispatch.h"
#include "paddle/phi/api/lib/sparse_api_custom_impl.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/declarations.h"
"""
def api_register():
return """
PD_REGISTER_API(Test);
"""
def api_namespace():
return ("""
namespace paddle {
namespace experimental {
namespace sparse {
""", """
} // namespace sparse
} // namespace experimental
} // namespace paddle
""")
def generate_api(api_yaml_path, header_file_path, source_file_path):
with open(api_yaml_path, 'r') as f:
apis = yaml.load(f, Loader=yaml.FullLoader)
header_file = open(header_file_path, 'w')
source_file = open(source_file_path, 'w')
namespace = api_namespace()
header_file.write("#pragma once\n")
header_file.write(header_include())
header_file.write(namespace[0])
include_header_file = "paddle/phi/api/include/sparse_api.h"
source_file.write(source_include(include_header_file))
source_file.write(namespace[0])
for api in apis:
sparse_api = SparseAPI(api)
header_file.write(sparse_api.gene_api_declaration())
source_file.write(sparse_api.gene_api_code())
header_file.write(namespace[1])
source_file.write(namespace[1])
source_file.write(api_register())
header_file.close()
source_file.close()
def main():
parser = argparse.ArgumentParser(
description='Generate PaddlePaddle C++ Sparse API files')
parser.add_argument(
'--api_yaml_path',
help='path to sparse api yaml file',
default='python/paddle/utils/code_gen/sparse_api.yaml')
parser.add_argument(
'--api_header_path',
help='output of generated api header code file',
default='paddle/phi/api/include/sparse_api.h')
parser.add_argument(
'--api_source_path',
help='output of generated api source code file',
default='paddle/phi/api/lib/sparse_api.cc')
options = parser.parse_args()
api_yaml_path = options.api_yaml_path
header_file_path = options.api_header_path
source_file_path = options.api_source_path
generate_api(api_yaml_path, header_file_path, source_file_path)
if __name__ == '__main__':
main()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册