提交 e5f779de 编写于 作者: H hanbuhe

merge conflicts

上级 8d3d2e58
......@@ -22,9 +22,6 @@ if (WITH_PADDLE_MOBILE)
return()
endif(WITH_PADDLE_MOBILE)
# set(CMAKE_BUILD_TYPE DEBUG)
set(PADDLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(PADDLE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
set(CMAKE_CXX_STANDARD 11)
......
......@@ -90,7 +90,7 @@ ios-detection_demo/detection_demo/ViewController.mm
## 代码讲解 (如何使用Paddle-Lite C++ API 执行预测)
IOS 示例基于C++ API 开发,调用Paddle-Lite C++ API包括以下五步。更详细的API 描述参考: [Paddle-Lite C++ API](https://paddle-lite.readthedocs.io/zh/latest/api_reference/java_api_doc.html)
IOS 示例基于C++ API 开发,调用Paddle-Lite C++ API包括以下五步。更详细的API 描述参考: [Paddle-Lite C++ API](https://paddle-lite.readthedocs.io/zh/latest/api_reference/cxx_api_doc.html)
```c++
#include <iostream>
......
# Linux(ARM) Demo
## 多种应用场景
我们提供Paddle-Lite示例工程[Paddle-Lite-Demo](https://github.com/PaddlePaddle/Paddle-Lite-Demo),其中包含[Android](https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/master/PaddleLite-android-demo)[iOS](https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/master/PaddleLite-ios-demo)[Armlinux](https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/master/PaddleLite-armlinux-demo)平台的示例工程。Linux(ARM) demo涵盖[图像分类](https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/master/PaddleLite-android-demo/image_classification_demo)[目标检测](https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/master/PaddleLite-android-demo/object_detection_demo)2个应用场景。
### 1. 图像分类
Paddle-Lite提供的图像分类demo ,在移动端上提供了实时的物体识别能力,可以应用到生产线自动分拣或质检、识别医疗图像、辅助医生肉眼诊断等场景。在移动端预测的效果图如下:
<p align="center"><img width="250" height="250" src="https://paddlelite-data.bj.bcebos.com/doc_images/Android_iOS_demo/demo/tabby_cat.jpg"/>&#8194;&#8194;&#8194;&#8194;&#8194;<img width="250" height="250" src="https://paddlelite-data.bj.bcebos.com/doc_images/Android_iOS_demo/demo/tabby_cat2.jpg"/></p>
### 2. 物体检测
Paddle-Lite提供的物体检测demo ,在移动端上提供了检测多个物体的位置、名称、位置及数量的能力。可以应用到视频监控(是否有违规物体或行为)、工业质检(微小瑕疵的数量和位置)、医疗诊断(细胞计数、中药识别)等场景。在移动端预测的效果图如下:
<p align="center"><img width="250" height="250" src="https://paddlelite-data.bj.bcebos.com/doc_images/Android_iOS_demo/demo/dog.jpg"/>&#8194;&#8194;&#8194;&#8194;&#8194;<img width="250" height="250" src="https://paddlelite-data.bj.bcebos.com/doc_images/Android_iOS_demo/demo/dog2.jpg"/></p>
## Linux(ARM) demo部署方法
下面我们以**目标检测(object_detection_demo)**为例讲解如何部署iOS工程。
**目的**:将基于Paddle-Lite的预测库部署到Linux(ARM)设备,实现物体检测的目标。
**需要的环境**:Linux(ARM)设备、下载到本地的[Paddle-Lite-Demo](https://github.com/PaddlePaddle/Paddle-Lite-Demo)工程
**部署步骤**
1、 目标检测的Linux(ARM)示例位于 `Paddle-Lite-Demo\PaddleLite-armlinux-demo\object_detection_demo`
2、终端中执行 `download_models_and_libs.sh` 脚本自动下载模型和Paddle-Lite预测库
```shell
cd PaddleLite-armlinux-demo # 1. 终端中进入 Paddle-Lite-Demo\PaddleLite-armlinux-demo
sh download_models_and_libs.sh # 2. 执行脚本下载依赖项 (需要联网)
```
下载完成后会出现提示: `Download successful!`
3、终端中执行 `download_models_and_libs.sh` 脚本自动下载模型和Paddle-Lite预测库
```shell
cd object_detection_demo # 1. 终端中进入
sh run.sh # 2. 执行脚本编译并执行物体检测demo,输出预测数据和运行时间
```
demo结果如下:
<img width="836" alt="image" src="https://user-images.githubusercontent.com/50474132/82852558-da228580-9f35-11ea-837c-e4d71066da57.png">
## 使用C++接口预测
Linux(ARM) demo 示例基于C++ API 开发,调用Paddle-Lite C++ API包括以下五步。更详细的API 描述参考: [Paddle-Lite C++ API](https://paddle-lite.readthedocs.io/zh/latest/api_reference/cxx_api_doc.html)
```c++
#include <iostream>
// 引入C++ API
#include "paddle_lite/paddle_api.h"
#include "paddle_lite/paddle_use_ops.h"
#include "paddle_lite/paddle_use_kernels.h"
// 1. 设置MobileConfig
MobileConfig config;
config.set_model_from_file(<modelPath>); // 设置NaiveBuffer格式模型路径
config.set_power_mode(LITE_POWER_NO_BIND); // 设置CPU运行模式
config.set_threads(4); // 设置工作线程数
// 2. 创建PaddlePredictor
std::shared_ptr<PaddlePredictor> predictor = CreatePaddlePredictor<MobileConfig>(config);
// 3. 设置输入数据
std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInput(0)));
input_tensor->Resize({1, 3, 224, 224});
auto* data = input_tensor->mutable_data<float>();
for (int i = 0; i < ShapeProduction(input_tensor->shape()); ++i) {
data[i] = 1;
}
// 4. 执行预测
predictor->run();
// 5. 获取输出数据
std::unique_ptr<const Tensor> output_tensor(std::move(predictor->GetOutput(0)));
std::cout << "Output shape " << output_tensor->shape()[1] << std::endl;
for (int i = 0; i < ShapeProduction(output_tensor->shape()); i += 100) {
std::cout << "Output[" << i << "]: " << output_tensor->data<float>()[i]
<< std::endl;
}
```
## 使用Python接口预测
1. Python预测库编译参考[编译Linux](../user_guides/Compile/Linux),建议在开发版上编译。
2. [Paddle-Lite Python API](https://paddle-lite.readthedocs.io/zh/latest/api_reference/python_api_doc.html)
3. 代码参考,[Python预测](python_demo)
文件模式从 100755 更改为 100644
......@@ -44,6 +44,7 @@ USE_MIR_PASS(type_layout_cast_pass);
USE_MIR_PASS(type_layout_cast_preprocess_pass);
USE_MIR_PASS(memory_optimize_pass);
USE_MIR_PASS(kernel_place_correct_pass)
USE_MIR_PASS(multi_stream_analysis_pass);
USE_MIR_PASS(elementwise_mul_constant_eliminate_pass)
USE_MIR_PASS(npu_subgraph_pass);
......
......@@ -76,30 +76,55 @@ void pooling1x1s2p0_max(const float* din,
int pad_bottom,
int pad_right);
void pooling2x2s2_max(const float* din,
float* dout,
int num,
int chout,
int hout,
int wout,
int chin,
int hin,
int win,
int pad_bottom,
int pad_right);
void pooling2x2s2_avg(const float* din,
float* dout,
int num,
int chout,
int hout,
int wout,
int chin,
int hin,
int win,
bool exclusive,
int pad_bottom,
int pad_right);
void pooling2x2s2p0_max(const float* din,
float* dout,
int num,
int chout,
int hout,
int wout,
int chin,
int hin,
int win,
int pad_bottom,
int pad_right);
void pooling2x2s2p0_avg(const float* din,
float* dout,
int num,
int chout,
int hout,
int wout,
int chin,
int hin,
int win,
bool exclusive,
int pad_bottom,
int pad_right);
void pooling2x2s2p1_max(const float* din,
float* dout,
int num,
int chout,
int hout,
int wout,
int chin,
int hin,
int win,
int pad_bottom,
int pad_right);
void pooling2x2s2p1_avg(const float* din,
float* dout,
int num,
int chout,
int hout,
int wout,
int chin,
int hin,
int win,
bool exclusive,
int pad_bottom,
int pad_right);
void pooling3x3s1p1_max(const float* din,
float* dout,
......
......@@ -204,7 +204,8 @@ int get_device_info(const struct DeviceInfo &args) {
int perform_bypass(const struct BypassArgs &args) {
int ret = -1;
int size = args.image.channels * args.image.width * args.image.height;
int max_size = 1 << 20;
int max_size = 1 << 21;
float times = 1.0 * size / max_size;
int count = static_cast<int>(times);
......
......@@ -241,13 +241,10 @@ void PriorBoxPE::compute_prior_box() {
}
boxes.flush();
// boxes.syncToCPU();
boxes.syncToCPU();
variances.flush();
output_boxes->copyFrom(&boxes);
output_variances->copyFrom(&variances);
output_boxes->invalidate();
output_variances->invalidate();
}
void PriorBoxPE::apply() {}
......@@ -256,9 +253,8 @@ bool PriorBoxPE::dispatch() {
if (cachedBoxes_ == nullptr) {
cachedBoxes_ = new Tensor();
cachedVariances_ = new Tensor();
cachedBoxes_->mutableData<float16>(FP16, param_.outputBoxes->shape());
cachedVariances_->mutableData<float16>(FP16,
param_.outputVariances->shape());
cachedBoxes_->mutableData<float>(FP32, param_.outputBoxes->shape());
cachedVariances_->mutableData<float>(FP32, param_.outputVariances->shape());
cachedBoxes_->setDataLocation(CPU);
cachedVariances_->setDataLocation(CPU);
compute_prior_box();
......
......@@ -36,7 +36,6 @@ class ScalePE : public PE {
}
inline int lcm(int a, int b) { return a * b / gcd(a, b); }
bool init() {
Tensor* output = param_.output;
output->setAligned(true);
......
......@@ -164,11 +164,6 @@ class StaticKernelPickPass : public mir::StmtPass {
// might have different data layout.
// TODO(Superjomn) reconsider the idea of taking the data layout as a kernel
// specification.
if (kernel.place().target == TARGET(kFPGA)) {
final_score = 1000;
}
return final_score;
}
......
......@@ -20,9 +20,6 @@ from __future__ import division
from __future__ import print_function
import argparse
import sys
sys.path.append('../../python/lib')
from paddlelite.lite import *
# Command arguments
......@@ -42,8 +39,8 @@ def RunModel(args):
config.set_param_file(args.param_file)
else:
config.set_model_dir(args.model_dir)
# For x86, you can set places = [Place(TargetType.X86, PrecisionType.FP32)]
places = [Place(TargetType.ARM, PrecisionType.FP32)]
# For arm platform (armlinux), you can set places = [Place(TargetType.ARM, PrecisionType.FP32)]
places = [Place(TargetType.X86, PrecisionType.FP32)]
config.set_valid_places(places)
# 2. Create paddle predictor
......
......@@ -20,8 +20,6 @@ from __future__ import division
from __future__ import print_function
import argparse
import sys
sys.path.append('../../python/lib')
from paddlelite.lite import *
......@@ -33,7 +31,7 @@ parser.add_argument(
def RunModel(args):
# 1. Set config information
config = MobileConfig()
config.set_model_dir(args.model_dir)
config.set_model_from_file(args.model_dir)
# 2. Create paddle predictor
predictor = create_paddle_predictor(config)
......
......@@ -62,10 +62,6 @@ void CastCompute::Run() {
int32_t* out_data = param.Out->mutable_data<int32_t>();
std::transform(
x_data_begin, x_data_end, out_data, TransOp<int64_t, int32_t>);
} else if (param.in_dtype == 3 && param.out_dtype == 5) {
const auto* x_data = param.X->data<float>();
auto* o_data = param.Out->mutable_data<float>();
memcpy(o_data, x_data, sizeof(float) * param.X->numel());
} else {
LOG(FATAL) << "other has not been implemented";
}
......
......@@ -34,7 +34,7 @@ void LookupTableCompute::Run() {
auto table_dim = w->dims();
int64_t ids_numel = ids->numel();
auto ids_data = ids->data<float>();
auto ids_data = ids->data<int64_t>();
int64_t row_number = table_dim[0];
int64_t row_width = table_dim[1];
......@@ -73,6 +73,7 @@ REGISTER_LITE_KERNEL(lookup_table,
.BindInput("Ids", {LiteType::GetTensorTy(TARGET(kARM), PRECISION(kInt64))})
.BindOutput("Out", {LiteType::GetTensorTy(TARGET(kARM))})
.Finalize();
REGISTER_LITE_KERNEL(lookup_table_v2,
kARM,
kAny,
......
......@@ -58,6 +58,7 @@ void PoolCompute::Run() {
bool global_pooling = (paddings[0] == 0) && (ksize[0] == in_dims[2]) &&
(ksize[1] == in_dims[3]) && kps_equal && pads_equal;
global_pooling = param.global_pooling || global_pooling;
if (global_pooling) {
for (size_t i = 0; i < ksize.size(); ++i) {
paddings[2 * i] = 0;
......@@ -107,35 +108,65 @@ void PoolCompute::Run() {
} else if (ksize[0] == 2 && strides[0] == 2 && paddings[0] == 0 &&
kps_equal) {
if (pooling_type == "max") {
lite::arm::math::pooling2x2s2_max(din,
dout,
out_dims[0],
out_dims[1],
out_dims[2],
out_dims[3],
in_dims[1],
in_dims[2],
in_dims[3],
paddings[1],
paddings[3]);
lite::arm::math::pooling2x2s2p0_max(din,
dout,
out_dims[0],
out_dims[1],
out_dims[2],
out_dims[3],
in_dims[1],
in_dims[2],
in_dims[3],
paddings[1],
paddings[3]);
return;
} else if (pooling_type == "avg") {
lite::arm::math::pooling2x2s2_avg(din,
dout,
out_dims[0],
out_dims[1],
out_dims[2],
out_dims[3],
in_dims[1],
in_dims[2],
in_dims[3],
exclusive,
paddings[1],
paddings[3]);
lite::arm::math::pooling2x2s2p0_avg(din,
dout,
out_dims[0],
out_dims[1],
out_dims[2],
out_dims[3],
in_dims[1],
in_dims[2],
in_dims[3],
exclusive,
paddings[1],
paddings[3]);
return;
}
} else if (ksize[0] == 3 && strides[0] == 1 && paddings[0] == 1 &&
} else if (ksize[0] == 2 && strides[0] == 2 && paddings[0] == 1 &&
kps_equal) {
if (pooling_type == "max") {
lite::arm::math::pooling2x2s2p1_max(din,
dout,
out_dims[0],
out_dims[1],
out_dims[2],
out_dims[3],
in_dims[1],
in_dims[2],
in_dims[3],
paddings[1],
paddings[3]);
return;
} else if (pooling_type == "avg") {
lite::arm::math::pooling2x2s2p1_avg(din,
dout,
out_dims[0],
out_dims[1],
out_dims[2],
out_dims[3],
in_dims[1],
in_dims[2],
in_dims[3],
exclusive,
paddings[1],
paddings[3]);
return;
}
} else if (ksize[0] == 3 && strides[0] == 1 && paddings[0] == 1 &&
pads_equal && kps_equal) {
if (pooling_type == "max") {
lite::arm::math::pooling3x3s1p1_max(din,
dout,
......@@ -165,7 +196,7 @@ void PoolCompute::Run() {
return;
}
} else if (ksize[0] == 3 && strides[0] == 1 && paddings[0] == 0 &&
kps_equal) {
pads_equal && kps_equal) {
if (pooling_type == "max") {
lite::arm::math::pooling3x3s1p0_max(din,
dout,
......@@ -195,7 +226,7 @@ void PoolCompute::Run() {
return;
}
} else if (ksize[0] == 3 && strides[0] == 2 && paddings[0] == 0 &&
kps_equal) {
pads_equal && kps_equal) {
if (pooling_type == "max") {
lite::arm::math::pooling3x3s2p0_max(din,
dout,
......@@ -225,7 +256,7 @@ void PoolCompute::Run() {
return;
}
} else if (ksize[0] == 3 && strides[0] == 2 && paddings[0] == 1 &&
kps_equal) {
pads_equal && kps_equal) {
if (pooling_type == "max") {
lite::arm::math::pooling3x3s2p1_max(din,
dout,
......
......@@ -132,26 +132,3 @@ REGISTER_LITE_KERNEL(prior_box,
.BindOutput("Variances", {LiteType::GetTensorTy(TARGET(kARM))})
.Finalize();
// REGISTER_LITE_KERNEL(prior_box,
// kFPGA,
// kFP16,
// kNHWC,
// paddle::lite::kernels::fpga::PriorBoxCompute,
// def)
// .BindInput("Input",
// {LiteType::GetTensorTy(TARGET(kFPGA),
// PRECISION(kFP16),
// DATALAYOUT(kNHWC))})
// .BindInput("Image",
// {LiteType::GetTensorTy(TARGET(kFPGA),
// PRECISION(kFP16),
// DATALAYOUT(kNHWC))})
// .BindOutput("Boxes",
// {LiteType::GetTensorTy(TARGET(kFPGA),
// PRECISION(kFP16),
// DATALAYOUT(kNHWC))})
// .BindOutput("Variances",
// {LiteType::GetTensorTy(TARGET(kFPGA),
// PRECISION(kFP16),
// DATALAYOUT(kNHWC))})
// .Finalize();
......@@ -428,14 +428,8 @@ REGISTER_LITE_KERNEL(multiclass_nms,
kNCHW,
paddle::lite::kernels::host::MulticlassNmsCompute,
def)
.BindInput("BBoxes",
{LiteType::GetTensorTy(TARGET(kHost),
PRECISION(kAny),
DATALAYOUT(kAny))})
.BindInput("Scores",
{LiteType::GetTensorTy(TARGET(kHost),
PRECISION(kAny),
DATALAYOUT(kAny))})
.BindInput("BBoxes", {LiteType::GetTensorTy(TARGET(kHost))})
.BindInput("Scores", {LiteType::GetTensorTy(TARGET(kHost))})
.BindOutput("Out", {LiteType::GetTensorTy(TARGET(kHost))})
.Finalize();
......
......@@ -46,21 +46,17 @@ REGISTER_LITE_KERNEL(reshape,
paddle::lite::kernels::host::ReshapeCompute,
def)
.BindInput("X",
{LiteType::GetTensorTy(TARGET(kHost),
PRECISION(kAny),
DATALAYOUT(kAny))})
{LiteType::GetTensorTy(
TARGET(kHost), PRECISION(kAny), DATALAYOUT(kAny), -1)})
.BindInput("ShapeTensor",
{LiteType::GetTensorTy(TARGET(kHost),
PRECISION(kAny),
DATALAYOUT(kAny))})
{LiteType::GetTensorTy(
TARGET(kHost), PRECISION(kAny), DATALAYOUT(kAny), -1)})
.BindInput("Shape",
{LiteType::GetTensorTy(TARGET(kHost),
PRECISION(kAny),
DATALAYOUT(kAny))})
{LiteType::GetTensorTy(
TARGET(kHost), PRECISION(kAny), DATALAYOUT(kAny), -1)})
.BindOutput("Out",
{LiteType::GetTensorTy(TARGET(kHost),
PRECISION(kAny),
DATALAYOUT(kAny))})
{LiteType::GetTensorTy(
TARGET(kHost), PRECISION(kAny), DATALAYOUT(kAny), -1)})
.Finalize();
REGISTER_LITE_KERNEL(reshape2,
......
......@@ -289,6 +289,17 @@ function build_test_server {
test_model_optimize_tool_compile
}
# Build the code and run lite server tests. This is executed in the CI system.
function build_test_coverage {
mkdir -p ./build
cd ./build
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$PWD/third_party/install/mklml/lib"
cmake_x86_for_CI
build
test_server
}
# The CUDA version of CI is cuda_10.1.243_418.87.00_linux.
# The cuDNN version is cudnn-10.1-linux-x64-v7.5.0.56.
function build_test_cuda_server {
......@@ -1136,7 +1147,7 @@ function main {
shift
;;
build_check_coverage)
build_test_server
build_test_coverage
check_coverage
shift
;;
......
......@@ -678,15 +678,9 @@ void resize(const uint8_t* src,
} else if (srcFormat == NV12 || srcFormat == NV21) {
nv21_resize(src, dst, srcw, srch, dstw, dsth);
return;
num = 1;
int hout = static_cast<int>(0.5 * dsth);
dsth += hout;
} else if (srcFormat == BGR || srcFormat == RGB) {
bgr_resize(src, dst, srcw, srch, dstw, dsth);
return;
w_in = srcw * 3;
w_out = dstw * 3;
num = 3;
} else if (srcFormat == BGRA || srcFormat == RGBA) {
w_in = srcw * 4;
w_out = dstw * 4;
......
......@@ -29,6 +29,7 @@ namespace zynqmp {
class ConvPE : public PE {
public:
bool init() {
std::cout << "Conv init" << std::endl;
return true;
}
......
......@@ -10,8 +10,7 @@ import sys
from github import Github
token = os.getenv('GITHUB_API_TOKEN', 'e51cb020919a6eef689257966e8fb6477981788a')
token = os.getenv('GITHUB_API_TOKEN')
def get_pull(pull_id):
"""
......
et -xe
#!/bin/bash
# The git version of CI is 2.7.4. This script is not compatible with git version 1.7.1.
set -xe
PADDLE_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}")/../../" && pwd )"
......@@ -26,7 +28,6 @@ function gen_full_html_report() {
'/Paddle-Lite/lite/kernels/*' \
'/Paddle-Lite/lite/model_parser/*' \
'/Paddle-Lite/lite/opreators/*' \
'/Paddle-Lite/lite/tests/*' \
'/Paddle-Lite/lite/tools/*' \
'/Paddle-Lite/lite/utils/*' \
-o coverage-full.tmp \
......@@ -35,7 +36,21 @@ function gen_full_html_report() {
mv -f coverage-full.tmp coverage-full.info
lcov --remove coverage-full.info \
'/Paddle-Lite/lite/demo*' \
'/Paddle-Lite/lite/tests/*' \
'/Paddle-Lite/lite/demo/*' \
'/Paddle-Lite/lite/fluid/*_test*' \
'/Paddle-Lite/lite/model_parser/*_test*' \
'/Paddle-Lite/lite/kernels/*/*test*' \
'/Paddle-Lite/lite/kernels/*/bridges/*test*' \
'/Paddle-Lite/lite/utils/*_test*' \
'/Paddle-Lite/lite/api/*test*' \
'/Paddle-Lite/lite/core/*_test*' \
'/Paddle-Lite/lite/core/*/*test*' \
'/Paddle-Lite/lite/core/mir/*/*_test*' \
'/Paddle-Lite/lite/core/mir/*_test*' \
'/Paddle-Lite/lite/backends/x86/*/*test*' \
'/Paddle-Lite/lite/backends/opencl/*test*' \
'/Paddle-Lite/lite/operators/*test*' \
-o coverage-full.tmp \
--rc lcov_branch_coverage=0
......@@ -48,7 +63,7 @@ gen_full_html_report || true
function gen_diff_html_report() {
if [ "${GIT_PR_ID}" != "" ]; then
COVERAGE_DIFF_PATTERN="`python ${PADDLE_ROOT}/tools/coverage/pull_request.py files ${GIT_PR_ID}`"
sleep 5
python ${PADDLE_ROOT}/tools/coverage/pull_request.py diff ${GIT_PR_ID} > git-diff.out
fi
......@@ -57,6 +72,7 @@ function gen_diff_html_report() {
-o coverage-diff.info \
--rc lcov_branch_coverage=0
sleep 5
python ${PADDLE_ROOT}/tools/coverage/coverage_diff.py coverage-diff.info git-diff.out > coverage-diff.tmp
mv -f coverage-diff.tmp coverage-diff.info
......
......@@ -11,8 +11,7 @@ import os
from github import Github
token = os.getenv('GITHUB_API_TOKEN', 'e51cb020919a6eef689257966e8fb6477981788a')
token = os.getenv('GITHUB_API_TOKEN')
def get_pull(pull_id):
"""
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册