A311D_NPU DDK 使用记录

DDK 与 NPU 版本确认

Amlogic 的 DDK 与 SDK 是分开的,DDK 的获取需要单独找供应商获取,需要注意的是使用的 DDK 版本要和 npu 驱动版本一致,版本不一致会导致在运行的时候出现段错误或其他未知错误。开发板使用的是荣品的A311D开发板,镜像为荣品提供的 ubuntu20.04 系统镜像,默认安装了 NPU 的运行环境。

驱动版本可以在开发板终端使用指令查看

$ dmesg | grep Galcore
[    8.975803] Galcore version 6.4.3.279124CB # 版本信息

将 DDK 压缩包解压后目录结构如下,不同版本的 DDK 目录结构有所不同,以实际目录结构为准
DDK_XXX_SDK V0.1.zip 版本是基于模型转换出的 case demo 整理的一个 sdk 开发包。提供一个基于 case demo 去编译、集成的开发环境。

DDK_XXX_SDK_VXXX.zip版本是 Amlogic 基于 case demo 提取并封装的一套接口,用以屏蔽 DDK 版本升级时转出的 case demo 中一些接口的变动。因为当前有些流程涉及到使用 sdkv0.1 开发包,需自行选择使用哪套 sdk 进行集成开发。

6.4.3 版本

linux 系统

DDK_6.4.3_SDK V0.1.zip 文件名包含有空格,在解压之前最好重命名,去掉空格或者替换空格为 "__",解压后会产生两个压缩包,一个是 android_sdk_643.rar,另一个是 linux_sdk_643.rar ,这里是linux平台,所以继续解压 linux_sdk_643.rar 压缩包,如果是安卓平台则解压 android_sdk_643.rar,并参考[[ 安卓系统 ]](# Android 系统)

$ unrar x linux_sdk_643.rar &&  # 解压后会生成 buildroot_sdk_643 目录

$ cd buildroot_sdk_643
|-- buildroot_sdk                 #sdk 用于编译 demo 程序
`-- conversion_scripts_nbg_unify  #demo 模型加 case 源码

然后进入 conversion_scripts_nbg_unify 目录

$ cd conversion_scripts_nbg_unify 

修改编译脚本

编译之前需要修改编译脚本 build_vx.sh ,主要修改一下内容

  1. 修改 AQROOT 变量
export AQROOT=/home/test/a311d-linux/SDK/DDK6.4.3/buildroot_sdk_643/buildroot_sdk

AQROOT 变量需要配置为 buildroot_sdk 的绝对路径。

  1. 增加交叉编译工具的环境变量
#added by rppdzkj
export TOOLCHAIN=/home/test/a311d-linux/toolchain/gcc/linux-x86/aarch64/gcc-linaro-aarch64-linux-gnu-4.9/bin
export LIB_DIR=/home/test/a311d/a311d-linux/toolchain/gcc/linux-x86/aarch64/gcc-linaro-aarch64-linux-gnu-4.9/arm-linux-gnueabihf/libc/lib
export PATH=/home/test/a311d-linux/toolchain/gcc/linux-x86/aarch64/gcc-linaro-aarch64-linux-gnu-4.9/bin:$PATH
export OPENCV_ROOT=/home/test/a311d-linux/output/mesong12b_w400_release/build/opencv3-3.4.3
export KERNEL_DIR=/home/test/a311d-linux/output/mesong12b_w400_release/build/linux-amlogic-4.9-dev

TOOLCHAIN:A311D SDK 的交叉编译工具的绝对路径

LIB_DIR:A311D SDK 的交叉编译工具的连接库的绝对路径

PATH:当前系统的 PATH 变量

OPENCV_ROOT:A311D SDK 编译过的 OpenCV 绝对路径

KERNEL_DIR:A311D SDK 编译过的 kernel 绝对路径

说明:文档中的配置只是举例,具体修改要根据当前主机的实际路径进行配置。

编译

修改完成后先删除 bin_r 目录

$ rm -rf ./bin_r/

然后赋予脚本可执行权限

$ chmod +x build_vx.sh

最后编译

$ ./build_vx.sh arm64
...
make: Nothing to be done for 'all'. # 编译完成标志

如果编译成功,则会重新生成 bin_r 目录。

$ ls ./bin_r/
main.o  mobilenettf  vnn_mobilenettf.o  vnn_post_process.o  vnn_pre_process.o

运行

运行除了将编译产生的可执行文件、模型和图片复制到开发板上以外,还需要 buildroot_sdk 下的链接库文件

$ tree ../buildroot_sdk/build/sdk/drivers_64/
|-- galcore.ko
|-- libArchModelSw.so
|-- libCLC.so
|-- libGAL.so
|-- libNNArchPerf.so
|-- libOpenCL.so
|-- libOpenCL.so.1
|-- libOpenCL.so.1.2
|-- libOpenVX.so
|-- libOpenVX.so.1
|-- libOpenVX.so.1.2
|-- libOpenVXU.so
|-- libVSC.so
|-- libVivanteOpenCL.so
`-- libovxlib.so

以上库文件除了 galcore.ko 以外,都需要复制到开发板的 /lib/ 目录下,然后将 mobilenettf、network_binary_88.nb 和 space_shuttle_224.jpg 这三个文件复制到开发板的合适的位置,这里以 /npu_test/ 为例

$ ls  /npu_test/
mobilenettf network_binary_88.nb  space_shuttle_224.jpg

然后运行,在运行之前一定要将库文件放到 /lib/ 目录下

$ cd /npu_test/
$ ./mobilenettf network_binary_88.nb space_shuttle_224.jpg

如果一切顺利,就可以看到模型推理信息

Android 系统

对于安卓系统,则需要在解压的时候解压 android_sdk_643.rar 压缩包

$ unrar x android_sdk_643.rar &&  # 解压后会生成 android_sdk_643 目录

$ cd android_sdk_643

然后参考目录下的 ReadMe.txt 文档操作即可

6.4.6 版本

荣品最新的镜像已经更新到了6.4.6版本,原始包是一个zip的压缩文件包DDK_6.4.6.2.zip,这个包最好不要再linux系统下解压,在windows上解压后会看到文件名包含了很多空格,可以手动把空格删掉,再把文件拷贝到linux平台上。

linux 系统

以下操作均在A311D开发板的 ubuntu20.04 系统上操作

DDK_6.4.6.2_SDK_V1.8.2

使用 DDK_6.4.6.2_SDK_V1.8.2 的SDK 可以参考如下步骤编译

1.解压SDK

解压之前最好先重命名SDK删掉名字中包含的空格

$ tar -xvzf DDK_6.4.6.2_sdk_DDK_6.4.6.2_SDK_V1.8.2.tar.gz

解压后生成 DDK_6.4.6.2_SDK_V1.8.2 目录

$ ls -l 
drwxrwxr-x 3 test test     4096 Oct 22 19:45 DDK_6.4.6.2_SDK_V1.8.2
$ cd DDK_6.4.6.2_SDK_V1.8.2/

$ ls -l 
drwxrwxr-x 4 test test 4096 Jun 28 11:01 linux_nnsdk_v1.8.2
-rw-rw-r-- 1 test test 5353 Jun 29 19:28 release_note.txt

$ cd linux_nnsdk_v1.8.2
$ ls -l 
drwxrwxr-x 6 test test 4096 Oct 23 11:43 demo
drwxrwxr-x 4 test test 4096 Jun 28 11:10 linux_nn_sdk_V1.8
-rw-rw-r-- 1 test test 2812 Jun 28 12:06 readme.txt

$ cd demo
$ ls -l
-rw-rw-r-- 1 test test   1392 Oct 22 19:59 CMakeLists.txt
drwxrwxr-x 2 test test   4096 Jun 28 11:01 include
-rw-rw-r-- 1 test test  14089 Jun 28 11:31 main.cpp
-rw-rw-r-- 1 test test   1815 Mar 25  2021 nn_demo.h
drwxrwxr-x 5 test test   4096 Jun 28 11:01 opencv
-rw-rw-r-- 1 test test 519222 Oct 22 20:16 out_obj_det.bmp
drwxrwxr-x 2 test test   4096 Jun 28 11:22 package
-rw-rw-r-- 1 test test  15359 Mar 25  2021 postprocess_util.c
-rw-rw-r-- 1 test test 132744 Jun 28 11:38 sdk182_32
-rwxrwxr-x 1 test test 171024 Jun 28 11:38 sdk182_64

2.修改cmake配置文件

修改CMakeList.txt 文件

#SET(ARM_32 arm_32)
SET(ARM_64 arm_64)  #编译arm64
#SET(GCC_PATH /home/dengliu/work/05_compile_too)

直接在开发板上编译无需指定交叉编译工具

#注释掉交叉编译工具的指定
if(DEFINED ARM_64)
#       SET(COMPILER ${GCC_PATH}/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin)
#       SET(CMAKE_C_COMPILER ${COMPILER}/aarch64-linux-gnu-gcc)
#       SET(CMAKE_CXX_COMPILER ${COMPILER}/aarch64-linux-gnu-g++)
        SET(LINK_DIR ../linux_nn_sdk_V1.8/lib/lib64 ${OPENCV_ROOT}/lib64 ./)
elseif(DEFINED ARM_32)
#       SET(COMPILER ${GCC_PATH}/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin)
#       SET(CMAKE_C_COMPILER  ${COMPILER}/arm-linux-gnueabihf-gcc)
#       SET(CMAKE_CXX_COMPILER  ${COMPILER}/arm-linux-gnueabihf-g++)
        SET(LINK_DIR ../linux_nn_sdk_V1.8/lib/lib32 ${OPENCV_ROOT}/lib32 ./)
endif()

3.编译

创建一个build 文件夹并且进入

$ mkdir build && cd build

开始编译

$ cmake ..
$ make

编译完成后会生成一个 sdk180_64 的可执行文件

$ ls -l
-rw-rw-r-- 1 test test  13883 Oct 22 19:59 CMakeCache.txt
drwxrwxr-x 5 test test   4096 Oct 23 12:11 CMakeFiles
-rw-rw-r-- 1 test test   1569 Oct 22 19:59 cmake_install.cmake
-rw-rw-r-- 1 test test   5813 Oct 22 19:59 Makefile
-rwxrwxr-x 1 test test 171000 Oct 23 12:11 sdk180_64

4.运行

$ ./sdk180_64 ../package/object_detect_88.nb 99 ../package/416x416x3.jpg 
nettype is 99
the input type should be 416*416*3
object_num:1
class:aeroplane,prob:0.664249
$ ./sdk180_64 ../package/image_classify_88.nb 0 ../package/224x224x3.jpeg 
nettype is 0
the input type should be 224*224*3
  2: 16.344051
116: 12.786162
843: 9.673010
444: 9.561826
795: 9.117090

运行需要注意的是 A311D上只能使用后缀为 "_88" 的模型,其他模型是其他芯片上使用的,不适用于A311D

DDK_6.4.6.2_SDK_V0.1

解压DDK_6.4.6.2_sdk_DDK_6.4.6.2_SDK_V0.1.tar.gz 后进入 DDK_6.4.6.2_SDK_V0.1/buildroot_sdk_6462/demo_nbg_unify/ 目录修改编译脚本

$ vi build_vx.sh
...
export AQROOT=`realpath ../buildroot_sdk`  # 需要指定buildroot_sdk目录的绝对路径
export SDK_DIR=$AQROOT/build/sdk
...
arm-amlogic)
        echo $BUILD_OPTIONS_COMPILER_NBG_CASE
        if [ $1 = "arm64" ]; then
                export ARCH_TYPE=arm64
                export CPU_TYPE=cortex-a53
                export CPU_ARCH=armv8-a
                export FIXED_ARCH_TYPE=arm64
                export CROSS_COMPILE=   # 开发板上无需指定编译工具,直接配置为空就好

                export VIVANTE_SDK_LIB=$SDK_DIR/../so/drivers_64
                cp $VIVANTE_SDK_LIB/libovxlib.so $OVXLIB_DIR/lib/libovxlib.so
                cp $VIVANTE_SDK_LIB/libjpeg.a $OVXLIB_DIR/lib/libjpeg.a

        else
                export ARCH_TYPE=arm
                export CPU_TYPE=cortex-a9
                export CPU_ARCH=armv7-a
                export FIXED_ARCH_TYPE=arm-gnueabihf
                #export CROSS_COMPILE=arm-linux-gnueabihf-

                export VIVANTE_SDK_LIB=$SDK_DIR/../so/drivers_32
                cp $VIVANTE_SDK_LIB/libovxlib.so $OVXLIB_DIR/lib/libovxlib.so
                cp $VIVANTE_SDK_LIB/libjpeg.a $OVXLIB_DIR/lib/libjpeg.a

        fi
;;
...

然后开始编译

$ rm bin_r*
$ ./build_vx.sh arm64
...
make: Nothing to be done for 'all'. # 编译完成标志

编译完成后会生成 bin_r 目录

ls bin_r/ -l
-rw-rw-r-- 1 test test   7440 Oct 23 13:39 main.o
-rwxrwxr-x 1 test test 133152 Oct 23 13:39 mobilenettf
-rw-rw-r-- 1 test test   5352 Oct 23 13:39 vnn_mobilenettf.o
-rw-rw-r-- 1 test test   4488 Oct 23 13:39 vnn_post_process.o
-rw-rw-r-- 1 test test  21416 Oct 23 13:39 vnn_pre_process.o

运行

$ ./bin_r/mobilenettf network_binary_88.nb attach_input_out0_1_out0_1_224_224_3.tensor 
Create Neural Network: 17ms or 17066us
Verify...
Verify Graph: 1ms or 1007us
Start run graph [1] times...
Run the 1 time: 6.00ms or 6218.00us
vxProcessGraph execution time:
Total   6.00ms or 6256.00us
Average 6.26ms or 6256.00us
 --- Top5 ---
813: 0.999023
405: 0.000266
868: 0.000135
896: 0.000108
  0: 0.000000

模型转换

A311D的模型转换需要搭建 python3.5 的环境,推荐使用 ubuntu16.04 操作系统的主机或者虚拟机搭建环境,当前工具支持转换的模型有 tensorflow、caffe、tflite、darknet、onnx、pytorch,keras 类型的模型。如果开发者是其他类型的模型,需先将模型转换成上述支持的一种。

环境搭建

安装 python3 以及 pip 等工具

$ sudo apt-get install python3 python3-pip python3-virtualenv

安装相关依赖包

获取 acuity-toolkit 工具包,进入根目录。

$ for req in $(cat requirements.txt); do pip3 install $req -i \ https://pypi.tuna.tsinghua.edu.cn/simple; done

这一步的安装可能会报很多错误,比如找不到tensflow的2.3版本等,可以参考如下指令升级 pip3 后再做尝试

$ curl https://bootstrap.pypa.io/pip/3.5/get-pip.py -o get-pip.py
$ sudo python3 get-pip.py

安装完成后检查环境

$ python3 bin/checkenv.py
Env Info: Linux distribution is Ubuntu 16.04, please make sure python 3.5 installed.
Env Pass: Require machine type 64bit installed
Env Pass: Require OS type 64bit installed
Env Pass: All Processors support instruction set avx
Env Pass: Python 3.5.2 installed, require 3.5.x
2021-10-26 11:49:21.030538: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcudart.so.10.1'; dlerror: libcudart.so.10.1: cannot open shared object file: No such file or directory
2021-10-26 11:49:21.030581: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
Env Pass: Require python lib tensorflow version >= 2.3.0 installed.
Env Pass: Require python lib numpy version >= 1.18.0 installed.
Env Pass: Require python lib google.protobuf version >= 3.11.2 installed.
Env Pass: Require python lib scipy version >= 1.1.0 installed.
Env Pass: Require python lib PIL version >= 5.3.0 installed.
Env Error: Require python lib networkx import testing failed, not installed.
Env Error: Get Require python lib networkx Version failed, please check build script.
Env Error: Require python lib networkx version >= 1.11 not installed.
Env Pass: Require python lib image version >= 1.5.5 installed.
Env Pass: Require python lib lmdb version >= 0.93 installed.
Env Pass: Require python lib onnx version >= 1.6.0 installed.
Env Pass: Require python lib h5py version >= 2.10.0 installed.
Env Pass: Require python lib flatbuffers version >= 1.10 installed.
Env Pass: Require python lib matplotlib version >= 2.1.0 installed.
Env Pass: Require python lib dill version >= 0.2.8.2 installed.
Env Pass: Require python lib ruamel.yaml version >= 0.15.81 installed.
Env Pass: Require python lib ply version >= 3.11 installed.
Env Pass: Require python lib torch version >= 1.2.0 installed.
Env Error: Env check FAILED!!!
  1. libcudart.so.10.1 没找到是因为Tensorflow-gpu 2.0.0 以上 开始 需要 CUDA10.1,这个报错可以不用管。
  2. networkx version >= 1.11 not installed,这里其实 networkx 是确认已经安装了的,但是 check 失败了,在确定正常安装的前提下,可以忽略这个 check 错误。

模型转换

转换工具的demo目录下有amlogic提供的转换demo,可以使用这个demo先验证环境是否搭建成功,在demo中有三个脚本,可以按照如下顺序依次执行。

第一个脚本,导入模型

$ ./0_import_model.sh
...
D Optimizing network with conv2d_big_kernel_size_transform
D Optimizing network with auto_fill_zero_bias, auto_fill_tf_quantize, align_quantize, broadcast_quantize, qnt_adjust_coef, qnt_adjust_param
I End importing tensorflow...
I Dump net to mobilenet_tf.json
I Save net to mobilenet_tf.data
I ----------------Error(0),Warning(0)---------------- #正常执行结束
...

第二个脚本,模型量化

$ ./1_quantize_model.sh
...
I Dump net quantize tensor table to mobilenet_tf.quantize
I [TRAINER]Quantization complete.
[TRAINER]Quantization complete.
I Save net to mobilenet_tf.data
I Clean.
I ----------------Error(0),Warning(0)---------------- #正常执行结束
...

第三个脚本,生成 case 代码

在执行第三个脚本生成case代码之前需要修改下脚本中的 optimize 参数内容,否则在运行时模型加载会出错,对于A311D而言,optimize 参数的值应为 VIPNANOQI_PID0X88

...
$export_ovxlib \
    --model-input ${NAME}.json \
    --data-input ${NAME}.data \
    --model-quantize ${NAME}.quantize \
    --reorder-channel '0 1 2' \
    --channel-mean-value '128 128 128 128' \
    --export-dtype quantized \
    --optimize VIPNANOQI_PID0X88  \      #注意 A311D此处应修改为 VIPNANOQI_PID0X88
    --viv-sdk ${ACUITY_PATH}vcmdtools \ 
    --pack-nbg-unify  \
...

然后执行脚本

$ ./2_export_case_code.sh
...
I Save vx network source file to /home/rpdzkj/acuity-toolkit-binary-5.21.1/demo_nbg_unify/.project
/home/rpdzkj/acuity-toolkit-binary-5.21.1/demo_nbg_unify
customer:input,0,1:output,0,0:
*********************************
/home/rpdzkj/acuity-toolkit-binary-5.21.1/demo
/
/home/rpdzkj/acuity-toolkit-binary-5.21.1/demo_nbg_unify
I ----------------Error(0),Warning(0)----------------  # 正常执行结束
...

模型转换完成后会在demo的当前目录与上层目录分别产生 normal_case_demo 和 demo_nbg_unify 两个目录

  1. normal_case_demo :加载模型时,会有编译时间,耗时较长。

  2. demo_nbg_unify : 在线编译已经由PC端完成,板子上能够直接加载nb文件,模型加载速度快。

转换自己的模型,可以将模型放到该目录下,然后修改 sh 脚本中的参数即可。

case 编译与运行

编译case代码需要使用到 DDK_6.4.6.2_SDK_V0.1 编译环境,开发板上默认有编译环境,DDK所在位置为 ~/DDK_6.4.6.2_SDK_V0.1/buildroot_sdk_646/

将case代码push到该路径下,默认已经有了一个 demo_nbg_unify 目录。复制的时候需要注意重命名。假设重命名为 demo_nbg_unify_convert

$ cp demo_nbg_unify/build_vx.sh  demo_nbg_unify_convert/

然后进入 demo_nbg_unify_convert 目录下进行编译

$ ./build_vx.sh arm64

编译成功后会生成 bin_r 目录。

运行case代码

$ ./bin_r/mobilenettf network_binary.nb attach_input_out0_1_out0_1_224_224_3.tensor
Create Neural Network: 23ms or 23339us
Verify...
Verify Graph: 1ms or 1060us
Start run graph [1] times...
Run the 1 time: 6.00ms or 6033.00us
vxProcessGraph execution time:
Total   6.00ms or 6075.00us
Average 6.07ms or 6075.00us
 --- Top5 ---
813: 0.999023
405: 0.000266
868: 0.000135
896: 0.000108
  0: 0.000000

如果运行提示缺少库文件,可以在 buildroot_sdk/build/so/drivers_64_exportdata/ 目录下找到。将里面的库文件全部复制到 /usr/lib/ 目录下即可。

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

推荐阅读更多精彩内容