MMDeploy使用说明

一、简介

MMDeploy 是一个开源的深度学习模型部署工具集,是OpenMMLab项目的一部分。能更轻松的将 OpenMMLab 下的算法部署到各种设备与平台上。

主要提供以下功能:

  • 中间表示 ONNX 的定义标准。
  • PyTorch 模型转换到 ONNX 模型的方法。
  • 推理引擎 ONNX Runtime、TensorRT 的使用方法。
  • 部署流水线 PyTorch - ONNX - ONNX Runtime/TensorRT 的示例及常见部署问题的解决方法。
  • MMDeploy C/C++ 推理 SDK。

1.MMDeploy 模型部署流程

  • Model Converter
    • 能将 PyTorch 模型转换为 ONNX、TorchScript 等和设备无关的 IR 模型;也能将 ONNX 模型转换为推理后端模型。
    • 通过Model Converter python推理API对onnx模型或trt模型进行推理【在线推理】。
  • MMDeploy Model
    • 是模型转换结果的集合。不仅包括后端模型,还包括模型的元信息。
  • Inference SDK
    • 封装了模型的前处理、网络推理和后处理过程。对外提供多语言的模型推理接口。
    • 通过mmdeploy-runtime可以对MMDeploy Model进行推理。
    • SDK 本质是设计了一套计算图的抽象,把多个模型的预处理、推理和后处理 调度起来,同时提供多种语言的 ffi。

MMDeploy v1.0.0 是 MMDeploy 1.x 的第一个正式发布版本,是 OpenMMLab 2.0 项目的一部分。截至发布时,MMDeploy 1.x 支持基于 OpenMMLab 2.0 的项目:MMCls 1.x、MMDet 3.x、MMDet3d 1.x、MMSeg 1.x、MMEdit 1.x、MMOCR 1.x、MMPose 1.x、 MMAction2 1.x.、MMRotate 1.x、MMYOLO。

2.OpenMMLab常用框架

1)MMDetection

MMDetection 是一个目标检测工具箱,包含了丰富的目标检测、实例分割、全景分割算法以及相关的组件和模块, 主分支代码目前支持 PyTorch 1.6 以上的版本。

MMDetection由 7 个主要部分组成,apis、structures、datasets、models、engine、evaluation 和 visualization。

  • apis 为模型推理提供高级 API。
  • structures 提供 bbox、mask 和 DetDataSample 等数据结构。
  • datasets 支持用于目标检测、实例分割和全景分割的各种数据集。
    • transforms 包含各种数据增强变换。
    • samplers 定义了不同的数据加载器采样策略。
  • models 是检测器最重要的部分,包含检测器的不同组件。
    • detectors 定义所有检测模型类。
    • data_preprocessors 用于预处理模型的输入数据。
    • backbones 包含各种骨干网络。
    • necks 包含各种模型颈部组件。
    • dense_heads 包含执行密集预测的各种检测头。
    • roi_heads 包含从 RoI 预测的各种检测头。
    • seg_heads 包含各种分割头。
    • losses 包含各种损失函数。
    • task_modules 为检测任务提供模块,例如 assigners、samplers、box coders 和 prior generators。
    • layers 提供了一些基本的神经网络层。
  • engine 是运行时组件的一部分。
    • runner 为 MMEngine 的执行器提供扩展。
    • schedulers 提供用于调整优化超参数的调度程序。
    • optimizers 提供优化器和优化器封装。
    • hooks 提供执行器的各种钩子。
  • evaluation 为评估模型性能提供不同的指标。
  • visualization 用于可视化检测结果。

模型支持列表参考此处

2)MMSegmentation

MMSegmentation 是一个基于 PyTorch 的语义分割开源工具箱,是 OpenMMLab 项目的一部分,目前支持 PyTorch 1.6 以上的版本。

3)MMOCR

MMOCR 是基于 PyTorch 和 mmdetection 的开源工具箱,专注于文本检测,文本识别以及相应的下游任务,如关键信息提取。 它是 OpenMMLab 项目的一部分。主分支目前支持 PyTorch 1.6 以上的版本。

二、MMDeploy安装

  • MMDeploy Converter 依赖
    • torch>=1.8.0
    • mmcv
  • MMDeploy SDK 依赖
    • OpenCV (>=3.0)
    • pplcv:openPPL 开发的高性能图像处理库

mim 是 OpenMMLab 项目的包管理工具,会自动检查 CUDA 和 PyTorch 环境并尽量帮我们安装和环境匹配的预编译版本的 MMCV-full,从而省去编译的耗时。

pip install -U openmim
mim install mmengine
mim install mmcv-full

1.使用pypi预编译包安装【仅python可用】

请根据目标软硬件平台,从这里 选择最新版本下载并安装自定义算子库。

# 1. 安装 MMDeploy Model Converter(含trt/ort自定义算子)
pip install mmdeploy==1.0.0 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com 

# 2. 安装 MMDeploy Inference SDK(仅pypi 预编译包,c++包需要编译安装)
# 根据是否需要GPU推理可任选其一进行下载安装
# pip install mmdeploy-runtime==1.0.0   # 支持 onnxruntime 推理
pip install mmdeploy-runtime-gpu==1.0.0 # 支持 onnxruntime-gpu tensorrt 推理

# 3. 安装推理引擎【基于python binding】
# 3.1 安装推理引擎 TensorRT
pip install TensorRT-8.2.3.0/python/tensorrt-8.2.3.0-cp38-none-linux_x86_64.whl
pip install pycuda
export TENSORRT_DIR=$(pwd)/TensorRT-8.2.3.0
export LD_LIBRARY_PATH=${TENSORRT_DIR}/lib:$LD_LIBRARY_PATH
# !!! 另外还需要从 NVIDIA 官网下载 cuDNN 8.2.1 CUDA 11.x 安装包并解压到当前目录
export CUDNN_DIR=$(pwd)/cuda
export LD_LIBRARY_PATH=$CUDNN_DIR/lib64:$LD_LIBRARY_PATH

# 3.2 安装推理引擎 ONNX Runtime 【可选】
# 3.2.1 onnxruntime
wget https://github.com/microsoft/onnxruntime/releases/download/v1.8.1/onnxruntime-linux-x64-1.8.1.tgz
tar -zxvf onnxruntime-linux-x64-1.8.1.tgz
export ONNXRUNTIME_DIR=$(pwd)/onnxruntime-linux-x64-1.8.1
export LD_LIBRARY_PATH=$ONNXRUNTIME_DIR/lib:$LD_LIBRARY_PATH
# 3.2.2 onnxruntime-gpu
pip install onnxruntime-gpu==1.8.1
wget https://github.com/microsoft/onnxruntime/releases/download/v1.8.1/onnxruntime-linux-x64-gpu-1.8.1.tgz
tar -zxvf onnxruntime-linux-x64-gpu-1.8.1.tgz
export ONNXRUNTIME_DIR=$(pwd)/onnxruntime-linux-x64-gpu-1.8.1
export LD_LIBRARY_PATH=$ONNXRUNTIME_DIR/lib:$LD_LIBRARY_PATH

2.使用 Docker 镜像

1)使用dockerfile 构建

mmdeploy dockerfile

mmvc dockerfile

2)现成镜像

https://hub.docker.com/r/openmmlab/mmdeploy/tags

sudo docker pull openmmlab/mmdeploy:ubuntu20.04-cuda11.3-mmdeploy1.0.0
sudo docker run --gpus all -it openmmlab/mmdeploy:ubuntu20.04-cuda11.3-mmdeploy1.0.0

3.编译码源安装

Inference SDK 的 c/cpp 库可从这里 选择版本下载并安装。

目录结构

# 获取码源
$ git clone -b 1.x https://github.com/open-mmlab/mmdeploy.git --recursive
$ tree -L 1 mmdeploy
├── CMakeLists.txt    # 编译模型转换自定义算子和 SDK 的 cmake 配置
├── configs                   # 模型转换要用的算法库配置
├── csrc                          # SDK 和自定义算子
├── demo                      # 各语言的 ffi 接口应用实例,如 csharp、java、python 等
├── docker                   #  docker build
├── mmdeploy           # 用于模型转换的 python 包
├── requirements      # python 包安装依赖
├── service                    # 有些小板子不能跑 python,模型转换用的 C/S 模式。这个目录放 Server
├── tests                         # 单元测试
├── third_party           # SDK 和 ffi 要的第三方依赖
└── tools                        # 工具,也是一切功能的入口。除了 deploy.py 还有 onnx2xx.py、profiler.py 和 test.py
  • csrc/mmdeploy 目录结构:

    ├── apis           # Csharp、java、go、Rust 等 ffi 接口
    ├── backend_ops    # 各推理框架的自定义算子
    ├── CMakeLists.txt
    ├── codebase       # 各 mm 算法框架偏好的结果类型,例如检测任务多用 bbox
    ├── core           # 脚手架,对图、算子、设备的抽象
    ├── device         # CPU/GPU device 抽象的实现
    ├── execution      # 对 exec 抽象的实现
    ├── graph          # 对图抽象的实现
    ├── model          # 实现 zip 压缩和非压缩两种工作目录
    ├── net            # net 的具体实现,例如封装了 ncnn forward C 接口
    ├── preprocess     # 预处理的实现
    └── utils          # OCV 工具类
    

1)一键式脚本安装

cd mmdeploy
python3 tools/scripts/ build_ubuntu_x64_ort.py
# 或
# bash tools/scripts/build_linux_nvidia.sh

2)手动编译安装【推荐】

通过预编译包只能安装特定版本的mmdeploy python库,且没有c++库,如果需要特定推理后端版本且支持c++ api的mmdeploy,则需要手动编译。

安装依赖
  • 编译工具链依赖

    # 保证 cmake的版本 >= 3.14.0
    wget https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0-linux-x86_64.tar.gz
    tar -xzvf cmake-3.20.0-linux-x86_64.tar.gz
    sudo ln -sf $(pwd)/cmake-3.20.0-linux-x86_64/bin/* /usr/bin/
    
    # MMDeploy SDK 使用了 C++17 特性,因此需要安装gcc 7+以上的版本。
    # 如果 Ubuntu 版本 < 18.04,需要加入仓库
    sudo add-apt-repository ppa:ubuntu-toolchain-r/test
    sudo apt-get update
    sudo apt-get install gcc-7
    sudo apt-get install g++-7
    
编译MMDeploy 后端自定义算子库
git clone -b 1.x git@github.com:open-mmlab/mmdeploy.git --recursive
cd mmdeploy
export MMDEPLOY_DIR=$(pwd)

# get onnxruntime
ONNXRUNTIME_VERSION=1.11.1
wget https://github.com/microsoft/onnxruntime/releases/download/v${ONNXRUNTIME_VERSION}/onnxruntime-linux-x64-${ONNXRUNTIME_VERSION}.tgz 
tar -zxvf onnxruntime-linux-x64-${ONNXRUNTIME_VERSION}.tgz
export ONNXRUNTIME_DIR=$(pwd)/onnxruntime-linux-x64-${ONNXRUNTIME_VERSION}

# get tensorrt
tar -zxvf TensorRT-8.2.1.8.Linux.x86_64-gnu.cuda-11.4.cudnn8.2.tar.gz
pip install TensorRT-8.2.1.8/python/tensorrt-8.2.1.8-cp38-none-linux_x86_64.whl
export TENSORRT_DIR=$(pwd)/TensorRT-8.2.1.8
export LD_LIBRARY_PATH=$TENSORRT_DIR/lib:$LD_LIBRARY_PATH
pip install pycuda

# 单独编译trt后端对应的自定义算子库
mkdir -p build && cd build
cmake \
    -DMMDEPLOY_TARGET_BACKENDS=trt \
    -DTENSORRT_DIR=${TENSORRT_DIR} ..
make -j$(nproc) && make install

# 编译ONNXRuntime后端对应的自定义算子库
rm -rf ./*
cmake \
    -DMMDEPLOY_TARGET_BACKENDS=ort \
    -DONNXRUNTIME_DIR=${ONNXRUNTIME_DIR} ..
make -j$(nproc) && make install

# 固定安装到 ${MMDEPLOY_DIR}/mmdeploy/lib/
$ cd -  && ls mmdeploy/lib/
libmmdeploy_onnxruntime_ops.so  libmmdeploy_tensorrt_ops.so

# 安装 Model Converter
$ mim install -e .
$ pip list|grep mm
mmcv-full              1.6.0
mmdeploy               1.0.0rc3             /home/share/mmdeploy
mmengine               0.7.3
  • DMMDEPLOY_TARGET_BACKENDS支持 ort、trt、ncnn和torchscript等。
  • 自定义后端算子查看此处
编译Inference SDK C++及python库
# 编译ppl.cv
cd /workspace
git clone https://github.com/openppl-public/ppl.cv.git
cd ppl.cv
export PPLCV_DIR=$(pwd)
git checkout tags/v0.7.0 -b v0.7.0
./build.sh cuda
# 获取文件 cuda-build/install/lib/cmake/ppl
export PLLCV_DIR=$(pwd)/cuda-build/install/lib/cmake/ppl

# 编译Inference sdk
cd ${MMDEPLOY_DIR}
rm -rf build/*
mkdir -p build && cd build
cmake .. \
    -DMMDEPLOY_BUILD_SDK=ON \
    -DMMDEPLOY_BUILD_EXAMPLES=ON \
    -DCMAKE_CXX_COMPILER=g++ \
    -Dpplcv_DIR=${PLLCV_DIR} \
    -DTENSORRT_DIR=${TENSORRT_DIR} \
    -DONNXRUNTIME_DIR=${ONNXRUNTIME_DIR} \
    -DMMDEPLOY_BUILD_SDK_PYTHON_API=ON \
    -DMMDEPLOY_TARGET_DEVICES="cuda;cpu" \
    -DMMDEPLOY_TARGET_BACKENDS="ort;trt" \
    -DMMDEPLOY_CODEBASES=all
make -j$(nproc) && make install

# 默认安装到 install目录
$ ls install
bin  example  include  lib

cd -
export  LD_LIBRARY_PATH=$(pwd)/build/lib:${LD_LIBRARY_PATH}
  • 编译Inference sdk也会重新编译自定义算子库

三、模型转换

1.tools/deploy.py

使用 MMDeploy 中的工具 tools/deploy.py,将 OpenMMLab 的 PyTorch 模型转换成推理后端支持的格式。

python ./tools/deploy.py \
    ${DEPLOY_CFG_PATH} \
    ${MODEL_CFG_PATH} \
    ${MODEL_CHECKPOINT_PATH} \
    ${INPUT_IMG} \
    --test-img ${TEST_IMG} \
    --work-dir ${WORK_DIR} \
    --calib-dataset-cfg ${CALIB_DATA_CFG} \
    --device ${DEVICE} \
    --log-level INFO \
    --show \
    --dump-info
  • deploy_cfg : mmdeploy 针对此模型的部署配置,包含推理框架类型、是否量化、输入 shape 是否动态等。配置文件之间可能有引用关系。
  • model_cfg : mm 算法库的模型配置,例如 mmpretrain/configs/vision_transformer/vit-base-p32_ft-64xb64_in1k-384.py
  • checkpoint : torch 模型路径。可以 http/https 开头。
  • img : 模型转换时,用做测试的图像或点云文件路径。
  • --test-img : 用于测试模型的图像文件路径。默认设置成None
  • --work-dir : 工作目录,用来保存日志和模型文件。
  • --calib-dataset-cfg : 此参数只有int8模式下生效,用于校准数据集配置文件。若在int8模式下未传入参数,则会自动使用模型配置文件中的'val'数据集进行校准。
  • --device : 用于模型转换的设备。 默认是cpu,对于 trt 可使用 cuda:0 这种形式。
  • --log-level : 设置日记的等级,选项包括'CRITICAL', 'FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'。 默认是INFO
  • --show : 是否显示检测的结果。
  • --dump-info : 是否输出 SDK 信息

转换过程

示例

# 克隆 mmdeploy 仓库。转换时,需要使用 mmdeploy 仓库中的配置文件,建立转换流水线, `--recursive` 不是必须的
git clone -b main --recursive https://github.com/open-mmlab/mmdeploy.git

# 以 MMDetection 中的 Faster R-CNN 为例,将 PyTorch 模型转换为 TenorRT 模型
# 安装 mmdetection。转换时,需要使用 mmdetection 仓库中的模型配置文件,构建 PyTorch nn module
git clone -b 3.x https://github.com/open-mmlab/mmdetection.git
cd mmdetection && mim install -v -e . && cd -

# 执行转换命令,实现端到端的转换
python mmdeploy/tools/deploy.py \
    mmdeploy/configs/mmdet/detection/detection_tensorrt_dynamic-320x320-1344x1344.py \
    mmdetection/configs/faster_rcnn/faster-rcnn_r50_fpn_1x_coco.py \
    checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth \
    mmdetection/demo/demo.jpg \
    --work-dir mmdeploy_model/faster-rcnn \
    --device cuda \
    --dump-info

2.其他工具

https://github.com/open-mmlab/mmdeploy/blob/main/docs/zh_cn/02-how-to-run/useful_tools.md

1)tools/torch2onnx.py

把 OpenMMLab 模型转 onnx 格式

python tools/torch2onnx.py \
    ${DEPLOY_CFG} \
    ${MODEL_CFG} \
    ${CHECKPOINT} \
    ${INPUT_IMG} \
    --work-dir ${WORK_DIR} \
    --device cpu \
    --log-level INFO

2)tools/onnx2tensorrt.py

把 onnx 转成 trt .engine 格式

python tools/onnx2tensorrt.py \
    ${DEPLOY_CFG} \
    ${ONNX_PATH} \
    ${OUTPUT} \
    --device-id 0 \
    --log-level INFO \
    --calib-file  /path/to/file

四、模型推理

在转换完成后,既可以使用 Model Converter 进行推理,也可以使用 Inference SDK。

1.使用 Model Converter 的Python API推理

from mmdeploy.apis import inference_model
result = inference_model(
  model_cfg='mmdetection/configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py',
  deploy_cfg='mmdeploy/configs/mmdet/detection/detection_tensorrt_dynamic-320x320-1344x1344.py',
  backend_files=['mmdeploy_model/faster-rcnn/end2end.engine'],
  img='mmdetection/demo/demo.jpg',
  device='cuda:0')

2.使用Inference SDK

可以直接运行预编译包中的 demo 程序,输入 mmdeploy_model和图像,进行推理,并查看推理结果。

wget https://github.com/open-mmlab/mmdeploy/releases/download/v1.0.0/mmdeploy-1.0.0-linux-x86_64-cuda11.3.tar.gz
tar xf mmdeploy-1.0.0-linux-x86_64-cuda11.3

cd mmdeploy-1.0.0-linux-x86_64-cuda11.3
# 运行 python demo
python example/python/object_detection.py cuda ../mmdeploy_model/faster-rcnn ../mmdetection/demo/demo.jpg
# 运行 C/C++ demo
# 根据文件夹内的 README.md 进行编译
./bin/object_detection cuda ../mmdeploy_model/faster-rcnn ../mmdetection/demo/demo.jpg

1)Python API

from mmdeploy_runtime import Detector
import cv2

# 读取图片
img = cv2.imread('mmdetection/demo/demo.jpg')

# 创建检测器
detector = Detector(model_path='mmdeploy_models/faster-rcnn', device_name='cuda', device_id=0)
# 执行推理
bboxes, labels, _ = detector(img)
# 使用阈值过滤推理结果,并绘制到原图中
indices = [i for i in range(len(bboxes))]
for index, bbox, label_id in zip(indices, bboxes, labels):
  [left, top, right, bottom], score = bbox[0:4].astype(int),  bbox[4]
  if score < 0.3:
      continue
  cv2.rectangle(img, (left, top), (right, bottom), (0, 255, 0))

cv2.imwrite('output_detection.png', img)

2)C++ API

#include <cstdlib>
#include <opencv2/opencv.hpp>
#include "mmdeploy/detector.hpp"

int main() {
  const char* device_name = "cuda";
  int device_id = 0;

  // mmdeploy SDK model,以转出的 faster r-cnn 模型为例
  std::string model_path = "mmdeploy_model/faster-rcnn";
  std::string image_path = "mmdetection/demo/demo.jpg";

  // 1. 读取模型
  mmdeploy::Model model(model_path);
  // 2. 创建预测器
  mmdeploy::Detector detector(model, mmdeploy::Device{device_name, device_id});
  // 3. 读取图像
  cv::Mat img = cv::imread(image_path);
  // 4. 应用预测器推理
  auto dets = detector.Apply(img);
  // 5. 处理推理结果: 此处我们选择可视化推理结果
  for (int i = 0; i < dets.size(); ++i) {
    const auto& box = dets[i].bbox;
    fprintf(stdout, "box %d, left=%.2f, top=%.2f, right=%.2f, bottom=%.2f, label=%d, score=%.4f\n",
            i, box.left, box.top, box.right, box.bottom, dets[i].label_id, dets[i].score);
    if (bboxes[i].score < 0.3) {
      continue;
    }
    cv::rectangle(img, cv::Point{(int)box.left, (int)box.top},
                  cv::Point{(int)box.right, (int)box.bottom}, cv::Scalar{0, 255, 0});
  }
  cv::imwrite("output_detection.png", img);
  return 0;
}

CMakeLists

find_package(MMDeploy REQUIRED)
target_link_libraries(${name} PRIVATE mmdeploy ${OpenCV_LIBS})

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容