未验证 提交 c07c7712 编写于 作者: H Huang Jiyi 提交者: GitHub

Update from_blob API (#51646)

* remove contexts in tensor_utils

* update from_blob

* update from_blob

* update from_blob

* fix bug

* fix bug
上级 9ded5707
......@@ -19,6 +19,7 @@
#include "paddle/fluid/distributed/collective/utils.h"
#include "paddle/fluid/platform/device/xpu/bkcl_helper.h"
#include "paddle/fluid/platform/device/xpu/xpu_info.h"
#include "paddle/phi/api/lib/utils/allocator.h"
#include "paddle/phi/core/device_context.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/errors.h"
......
......@@ -431,13 +431,14 @@ if(WITH_XPU)
DEPS lod_tensor
dense_tensor
selected_rows_utils
int_array
scalar
place
phi
var_type_traits
op_info
xpu_op_list
convert_utils
phi_api_utils)
convert_utils)
else()
cc_library(
phi_utils
......@@ -445,12 +446,13 @@ else()
DEPS lod_tensor
dense_tensor
selected_rows_utils
int_array
scalar
place
phi
var_type_traits
op_info
convert_utils
phi_api_utils)
convert_utils)
endif()
if(WITH_XPU)
......
......@@ -312,7 +312,7 @@ phi::Scalar MakePhiScalarFromVar(const framework::Variable& variable) {
phi::IntArray MakePhiIntArrayFromVar(const framework::Variable& variable) {
if (variable.IsType<phi::DenseTensor>()) {
const auto& tensor = variable.Get<phi::DenseTensor>();
return paddle::experimental::MakePhiIntArray(tensor);
return phi::IntArray(tensor);
} else {
PADDLE_THROW(platform::errors::Unimplemented(
"Unsupport casting input `%s` type to IntArray when call pt "
......
......@@ -26,7 +26,6 @@ limitations under the License. */
#include "paddle/fluid/framework/variable.h"
#include "paddle/fluid/platform/macros.h"
#include "paddle/fluid/platform/place.h"
#include "paddle/phi/api/lib/utils/tensor_utils.h"
#include "paddle/phi/common/backend.h"
#include "paddle/phi/core/compat/arg_map_context.h"
#include "paddle/phi/core/kernel_factory.h"
......
......@@ -41,6 +41,8 @@
*paddle::CPUContextResource*;
*paddle::OpMetaInfoBuilder*;
*paddle::CustomOpKernelContext*;
*paddle::RegisterSymbolsFor*;
*paddle::from_blob*;
/* ut needs the following symbol, we need to modify all the ut to hidden such symbols */
......
......@@ -41,11 +41,11 @@ void LaunchElementwiseCudaKernel(
std::vector<std::unique_ptr<phi::DenseTensor>> pt_outputs_tmp;
for (auto in : ins) {
pt_inputs_tmp.emplace_back(
std::move(paddle::experimental::MakePhiDenseTensor(*in)));
std::move(std::make_unique<phi::DenseTensor>(*in)));
}
for (auto out : *outs) {
pt_outputs_tmp.emplace_back(
std::move(paddle::experimental::MakePhiDenseTensor(*out)));
std::move(std::make_unique<phi::DenseTensor>(*out)));
}
for (int i = 0; i < pt_inputs_tmp.size(); i++) {
pt_inputs.push_back(pt_inputs_tmp[i].get());
......
......@@ -28,7 +28,6 @@ limitations under the License. */
#include "paddle/fluid/memory/malloc.h"
#include "paddle/fluid/operators/elementwise/elementwise_functor.h"
#include "paddle/fluid/platform/device/gpu/gpu_info.h"
#include "paddle/phi/api/lib/utils/tensor_utils.h"
#include "paddle/phi/common/transform.h"
#include "paddle/phi/kernels/cpu/elementwise.h"
#include "paddle/phi/kernels/cpu/elementwise_grad.h"
......
......@@ -42,11 +42,11 @@ void LaunchSameDimsElementwiseCudaKernel(
std::vector<std::unique_ptr<phi::DenseTensor>> pt_outputs_tmp;
for (auto in : ins) {
pt_inputs_tmp.emplace_back(
std::move(paddle::experimental::MakePhiDenseTensor(*in)));
std::move(std::make_unique<phi::DenseTensor>(*in)));
}
for (auto out : *outs) {
pt_outputs_tmp.emplace_back(
std::move(paddle::experimental::MakePhiDenseTensor(*out)));
std::move(std::make_unique<phi::DenseTensor>(*out)));
}
for (int i = 0; i < pt_inputs_tmp.size(); i++) {
pt_inputs.push_back(pt_inputs_tmp[i].get());
......
......@@ -830,8 +830,8 @@ class ReduceCudaGradKernel : public framework::OpKernel<T> {
} else {
d_x->mutable_data(dev_ctx.GetPlace(), d_out->dtype());
}
auto pt_d_out = paddle::experimental::MakePhiDenseTensor(new_d_out);
auto pt_d_x = paddle::experimental::MakePhiDenseTensor(*d_x);
auto pt_d_out = std::make_unique<phi::DenseTensor>(new_d_out);
auto pt_d_x = std::make_unique<phi::DenseTensor>(*d_x);
if (out_dtype <= 0) {
pt_out_dtype = d_out->dtype();
}
......
......@@ -21,7 +21,6 @@ limitations under the License. */
// only can include the headers in paddle/phi/api dirs
#include "paddle/fluid/prim/api/composite_backward/composite_backward_api.h"
#include "paddle/fluid/prim/utils/static/composite_grad_desc_maker.h"
#include "paddle/phi/api/lib/utils/tensor_utils.h"
#include "paddle/phi/backends/cpu/cpu_context.h"
#include "paddle/phi/common/int_array.h"
#include "paddle/phi/core/infermeta_utils.h"
......
......@@ -19,7 +19,6 @@ limitations under the License. */
#include "paddle/phi/api/include/tensor.h"
namespace paddle {
namespace experimental {
using Deleter = std::function<void(void*)>;
......@@ -32,21 +31,24 @@ using Deleter = std::function<void(void*)>;
* @param data The pointer to the memory buffer.
* @param shape The dims of the tensor.
* @param dtype The data type of the tensor, should correspond to data type of
* `data`. See PD_FOR_EACH_DATA_TYPE in phi/common/data_type.h
* `data`. See PD_FOR_EACH_DATA_TYPE in `phi/common/data_type.h`
* @param layout The data layout of the tensor.
* @param place The place where the tensor is located, should correspond to
* place of `data`.
* If `place` use the default value, it will be inferred from
* `data`, However,the feature is only supported on CPU or GPU.
* So make sure that `place` is equal to the place of `data` when
* using other devices.
* @param deleter A function or function object that will be called to free the
* memory buffer.
*
* @return A Tensor object constructed from the buffer
*/
PADDLE_API Tensor from_blob(void* data,
const phi::DDim& shape,
const phi::IntArray& shape,
phi::DataType dtype,
phi::DataLayout layout = phi::DataLayout::NCHW,
const phi::Place& place = phi::Place(),
const Deleter& deleter = nullptr);
} // namespace experimental
} // namespace paddle
add_subdirectory(utils)
if(WITH_GPU)
nv_library(
phi_tensor_raw
SRCS tensor.cc
DEPS tensor_base dense_tensor phi_api_utils phi_enforce context_pool
tensor_api)
DEPS tensor_base
dense_tensor
phi_enforce
context_pool
tensor_api
int_array
scalar)
elseif(WITH_ROCM)
hip_library(
phi_tensor_raw
SRCS tensor.cc
DEPS tensor_base dense_tensor phi_api_utils phi_enforce context_pool
tensor_api)
DEPS tensor_base
dense_tensor
phi_enforce
context_pool
tensor_api
int_array
scalar)
else()
cc_library(
phi_tensor_raw
SRCS tensor.cc
DEPS tensor_base dense_tensor phi_api_utils phi_enforce context_pool
tensor_api)
DEPS tensor_base
dense_tensor
phi_enforce
context_pool
tensor_api
int_array
scalar)
endif()
set(api_gen_base ${CMAKE_SOURCE_DIR}/paddle/phi/api/yaml/generator/api_base.py)
......@@ -290,6 +303,10 @@ cc_library(
context_pool
SRCS context_pool.cc
DEPS phi_backends phi_enforce place init phi_device_context)
cc_library(
api_tensor_utils
SRCS tensor_utils.cc
DEPS phi_tensor_raw)
cc_library(
kernel_dispatch
......@@ -323,8 +340,8 @@ cc_library(
api_gen_utils
phi_data_transform
api_custom_impl
phi_profiler
from_blob)
api_tensor_utils
phi_profiler)
cc_library(
phi_bw_function_api
SRCS ${bw_api_source_file}
......@@ -391,10 +408,6 @@ cc_library(
api_int_array
SRCS int_array.cc
DEPS tensor_copy)
cc_library(
from_blob
SRCS from_blob.cc
DEPS phi_tensor_raw)
cc_library(
phi_tensor_operants
......
......@@ -12,7 +12,9 @@ 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 "paddle/phi/api/lib/from_blob.h"
#include "paddle/phi/api/include/tensor_utils.h"
#include <set>
#include "paddle/phi/api/lib/api_registry.h"
#include "paddle/phi/core/dense_tensor.h"
......@@ -26,37 +28,72 @@ limitations under the License. */
#endif
namespace paddle {
namespace experimental {
PD_REGISTER_API(from_blob)
phi::Place GetPlaceFromPtr(void* data);
phi::Place GetPlaceFromPtr(void* data) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
#ifdef PADDLE_WITH_CUDA
#if CUDA_VERSION >= 10000
cudaPointerAttributes attr;
cudaError_t status = cudaPointerGetAttributes(&attr, data);
if (status == cudaSuccess && attr.type == cudaMemoryTypeDevice) {
return phi::GPUPlace(attr.device);
}
#else
PADDLE_THROW(
phi::errors::Unimplemented("The GetPlaceFromPtr() method is only "
"supported when CUDA version >= 10.0."));
#endif
#else
hipPointerAttribute_t attr;
hipError_t status = hipPointerGetAttributes(&attr, data);
if (status == hipSuccess && attr.memoryType == hipMemoryTypeDevice) {
return phi::GPUPlace(attr.device);
}
#endif
#endif
return phi::CPUPlace();
}
using AllocationDeleter = void (*)(phi::Allocation*);
PADDLE_API Tensor from_blob(void* data,
const phi::DDim& shape,
const phi::IntArray& shape,
phi::DataType dtype,
phi::DataLayout layout,
const phi::Place& place,
const Deleter& deleter) {
PADDLE_ENFORCE_NOT_NULL(
data, phi::errors::InvalidArgument("data can not be nullptr"));
data, phi::errors::InvalidArgument("data can not be nullptr."));
PADDLE_ENFORCE_EQ(shape.FromTensor(),
false,
phi::errors::InvalidArgument(
"shape cannot be constructed from a Tensor."));
auto data_place = GetPlaceFromPtr(data);
// TODO(huangjiyi): We need copy data to specified place when
// the input place is different with place of data.
if (place.GetType() != phi::AllocationType::UNDEFINED) {
PADDLE_ENFORCE_EQ(
data_place,
place,
phi::errors::InvalidArgument("Specified ",
data_place.DebugString(),
" does not match place of data ",
place.DebugString()));
phi::Place data_place;
if (place.GetType() == phi::AllocationType::UNDEFINED ||
place.GetType() == phi::AllocationType::CPU ||
place.GetType() == phi::AllocationType::GPU) {
data_place = GetPlaceFromPtr(data);
if (place.GetType() != phi::AllocationType::UNDEFINED) {
PADDLE_ENFORCE_EQ(data_place,
place,
phi::errors::InvalidArgument(
"Specified place does not match place of data. ",
"Specified: %s, Exptected: %s.",
data_place.DebugString(),
place.DebugString()));
}
} else {
data_place = place;
}
auto meta = phi::DenseTensorMeta(dtype, shape, layout);
auto meta =
phi::DenseTensorMeta(dtype, phi::make_ddim(shape.GetData()), layout);
size_t size = SizeOf(dtype) * (meta.is_scalar ? 1 : product(meta.dims));
......@@ -72,30 +109,4 @@ PADDLE_API Tensor from_blob(void* data,
return Tensor(std::make_shared<phi::DenseTensor>(alloc, meta));
}
phi::Place GetPlaceFromPtr(void* data) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
#ifdef PADDLE_WITH_CUDA
#if CUDA_VERSION >= 10000
cudaPointerAttributes attr;
cudaError_t status = cudaPointerGetAttributes(&attr, data);
if (status == cudaSuccess && attr.type == cudaMemoryTypeDevice) {
return phi::GPUPlace(attr.device);
}
#else
PADDLE_THROW(
phi::errors::Unimplemented("The GetPlaceFromPtr() method is only "
"supported when CUDA version >= 10.0."));
#endif
#else
hipPointerAttribute_t attr;
hipError_t status = hipPointerGetAttributes(&attr, data);
if (status == hipSuccess && attr.memoryType == hipMemoryTypeDevice) {
return phi::GPUPlace(attr.device);
}
#endif
#endif
return phi::CPUPlace();
}
} // namespace experimental
} // namespace paddle
cc_library(
phi_api_utils
SRCS tensor_utils.cc
DEPS dense_tensor int_array scalar)
/* 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. */
#include "paddle/phi/api/lib/utils/tensor_utils.h"
#include <utility>
#include <vector>
namespace paddle {
namespace experimental {
template <typename DstLoD, typename SrcLoD>
void SetLoD(DstLoD* dst, const SrcLoD& src) {
dst->reserve(src.size());
dst->clear();
for (auto&& v : src) {
dst->emplace_back(v);
}
}
std::unique_ptr<phi::DenseTensor> MakePhiDenseTensor(
const phi::DenseTensor& src) {
return std::make_unique<phi::DenseTensor>(src);
}
phi::IntArray MakePhiIntArray(const phi::DenseTensor& src) { return {src}; }
} // namespace experimental
} // namespace paddle
/* 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. */
#pragma once
#include "paddle/phi/api/lib/utils/allocator.h"
#include "paddle/phi/common/int_array.h"
#include "paddle/phi/common/scalar.h"
#include "paddle/phi/core/dense_tensor.h"
namespace paddle {
namespace experimental {
std::unique_ptr<phi::DenseTensor> MakePhiDenseTensor(
const phi::DenseTensor& src);
phi::IntArray MakePhiIntArray(const phi::DenseTensor& src);
} // namespace experimental
} // namespace paddle
......@@ -365,7 +365,7 @@ def source_include(header_file_path):
#include "paddle/phi/api/lib/api_gen_utils.h"
#include "paddle/phi/api/lib/api_registry.h"
#include "paddle/phi/api/lib/data_transform.h"
#include "paddle/phi/api/lib/from_blob.h"
#include "paddle/phi/api/include/tensor_utils.h"
#include "paddle/phi/api/lib/kernel_dispatch.h"
#include "paddle/phi/common/type_traits.h"
#include "paddle/phi/core/kernel_registry.h"
......@@ -400,8 +400,9 @@ namespace experimental {
def declare_extension_api():
return """
namespace paddle {
PD_DECLARE_API(from_blob);
} // namespace paddle
"""
......@@ -435,11 +436,11 @@ def generate_api(api_yaml_path, header_file_path, source_file_path):
header_file.write(foward_api.gene_api_declaration())
source_file.write(foward_api.gene_api_code())
source_file.write(declare_extension_api())
header_file.write(namespace[1])
source_file.write(namespace[1])
source_file.write(declare_extension_api())
header_file.close()
source_file.close()
......
......@@ -334,7 +334,6 @@ def source_include(header_file_path):
#include "paddle/phi/core/string_tensor.h"
#include "paddle/phi/infermeta/strings/nullary.h"
#include "paddle/phi/infermeta/strings/unary.h"
#include "paddle/phi/api/lib/api_registry.h"
#include "paddle/phi/api/lib/kernel_dispatch.h"
#include "paddle/phi/core/kernel_registry.h"
......@@ -342,12 +341,6 @@ DECLARE_int32(low_precision_op_list);
"""
def api_register():
return """
PD_REGISTER_API(StringsApi);
"""
def api_namespace():
return (
"""
......@@ -390,8 +383,6 @@ def generate_api(api_yaml_path, header_file_path, source_file_path):
header_file.write(namespace[1])
source_file.write(namespace[1])
# source_file.write(api_register())
header_file.close()
source_file.close()
......
......@@ -17,11 +17,17 @@ limitations under the License. */
#include "paddle/phi/backends/context_pool.h"
#include "paddle/phi/backends/cpu/cpu_context.h"
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/ddim.h"
#include "paddle/phi/core/tensor_utils.h"
namespace paddle {
namespace experimental {
template <typename T>
IntArrayBase<T>::IntArrayBase(const phi::DDim& dims) {
AssignData(dims.Get(), dims.size());
}
template <>
IntArrayBase<phi::DenseTensor>::IntArrayBase(
const phi::DenseTensor& tensor) { // NOLINT
......
......@@ -19,6 +19,10 @@ limitations under the License. */
#include "paddle/phi/api/ext/exception.h"
#include "paddle/phi/common/data_type.h"
namespace phi {
class DDim;
} // namespace phi
namespace paddle {
class Tensor;
namespace experimental {
......@@ -50,6 +54,8 @@ class IntArrayBase {
void SetFromTensor(bool val) { is_from_tensor_ = val; }
explicit IntArrayBase(const phi::DDim& dims);
// The Tensor must have one dim
IntArrayBase(const T& tensor); // NOLINT
......
......@@ -29,6 +29,8 @@ set(COMMON_KERNEL_DEPS
sparse_coo_tensor
sparse_csr_tensor
tensor_array
int_array
scalar
kernel_context
kernel_factory
arg_map_context
......@@ -47,7 +49,7 @@ set(COMMON_KERNEL_DEPS
concat_and_split_functor
selected_rows_functor)
# remove this dep after removing fluid deps on tensor creation
set(COMMON_KERNEL_DEPS ${COMMON_KERNEL_DEPS} phi_api_utils lod_utils)
set(COMMON_KERNEL_DEPS ${COMMON_KERNEL_DEPS} lod_utils)
set(COMMON_KERNEL_DEPS ${COMMON_KERNEL_DEPS} infermeta infermeta_utils
sparse_infermeta)
set(COMMON_KERNEL_DEPS ${COMMON_KERNEL_DEPS} switch_autotune)
......
......@@ -13,7 +13,6 @@
// limitations under the License.
#pragma once
#include "paddle/phi/api/lib/utils/tensor_utils.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/funcs/broadcast_function.h"
#include "paddle/phi/kernels/funcs/compare_functors.h"
......@@ -58,10 +57,10 @@ void ReduceCudaAMaxAMinGrad(const Context& dev_ctx,
new_dout.Resize(phi::make_ddim(update_dims));
dev_ctx.Alloc(d_x, d_out->dtype());
auto new_in = paddle::experimental::MakePhiDenseTensor(*in_x);
auto new_in = std::make_unique<phi::DenseTensor>(*in_x);
auto new_in_tensor = new_in.get();
auto new_dx = paddle::experimental::MakePhiDenseTensor(*d_x);
auto new_dx = std::make_unique<phi::DenseTensor>(*d_x);
auto new_dx_tensor = new_dx.get();
// make equal_out
......
set(COMMON_API_TEST_DEPS phi_tensor phi_api phi_api_utils)
set(COMMON_API_TEST_DEPS phi_tensor phi_api api_tensor_utils)
if(WITH_GPU)
nv_test(
......
......@@ -15,7 +15,7 @@ limitations under the License. */
#include <gtest/gtest.h>
#include "paddle/phi/api/include/api.h"
#include "paddle/phi/api/lib/from_blob.h"
#include "paddle/phi/api/include/tensor_utils.h"
#include "paddle/phi/core/kernel_registry.h"
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
......@@ -31,20 +31,18 @@ PD_DECLARE_KERNEL(pow, CPU, ALL_LAYOUT);
PD_DECLARE_KERNEL(pow, GPU, ALL_LAYOUT);
#endif
using paddle::experimental::DataType;
using paddle::experimental::from_blob;
using paddle::from_blob;
using phi::DataType;
namespace paddle {
namespace experimental {
phi::Place GetPlaceFromPtr(void* data);
} // namespace experimental
} // namespace paddle
TEST(from_blob, CPU) {
// 1. create data
int64_t data[] = {4, 3, 2, 1};
ASSERT_EQ(paddle::experimental::GetPlaceFromPtr(data), phi::CPUPlace());
ASSERT_EQ(paddle::GetPlaceFromPtr(data), phi::CPUPlace());
// 2. test API
auto test_tesnor = from_blob(data, {1, 2, 2}, DataType::INT64);
......@@ -84,7 +82,7 @@ TEST(from_blob, CPU) {
using phi::memory_utils::Copy;
TEST(GetPlaceFromPtr, GPU) {
using paddle::experimental::GetPlaceFromPtr;
using paddle::GetPlaceFromPtr;
float cpu_data[6];
auto cpu_data_place = GetPlaceFromPtr(cpu_data);
......
......@@ -28,44 +28,44 @@ cc_test(
cc_test(
test_strings_lower_upper_dev_api
SRCS test_strings_lower_upper_dev_api.cc
DEPS phi phi_api_utils)
DEPS phi)
if(WITH_GPU)
nv_test(
test_strings_lower_upper_dev_gpu_api
SRCS test_strings_lower_upper_dev_api.cu
DEPS phi phi_api_utils)
DEPS phi)
elseif(WITH_ROCM)
hip_test(
test_strings_lower_upper_dev_gpu_api
SRCS test_strings_lower_upper_dev_api.cu
DEPS phi phi_api_utils)
DEPS phi)
endif()
cc_test(
test_strings_copy_dev_api
SRCS test_strings_copy_dev_api.cc
DEPS phi phi_api_utils)
DEPS phi)
if(WITH_GPU)
nv_test(
test_strings_copy_dev_gpu_api
SRCS test_strings_copy_dev_api.cu
DEPS phi phi_api_utils)
DEPS phi)
elseif(WITH_ROCM)
hip_test(
test_strings_copy_dev_gpu_api
SRCS test_strings_copy_dev_api.cu
DEPS phi phi_api_utils)
DEPS phi)
endif()
cc_test(
test_memcpy_dev_api
SRCS test_memcpy_dev_api.cc
DEPS phi phi_api_utils)
DEPS phi)
cc_test(
test_transfer_layout_dev_api
SRCS test_transfer_layout_dev_api.cc
DEPS phi phi_api_utils)
DEPS phi)
if(WITH_GPU)
nv_test(
......@@ -79,7 +79,7 @@ if(WITH_GPU)
cc_test(
test_fused_adam_kernel
SRCS test_fused_adam_kernel.cc
DEPS gtest phi phi_api_utils)
DEPS gtest phi)
elseif(WITH_ROCM)
hip_test(
test_gpu_timer
......
......@@ -6,7 +6,7 @@ if(WITH_GPU)
endif()
add_executable(print_phi_kernels print_phi_kernels.cc)
target_link_libraries(print_phi_kernels phi phi_api_utils)
target_link_libraries(print_phi_kernels phi)
if(WIN32)
target_link_libraries(print_phi_kernels shlwapi.lib)
endif()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册