# PyLite

Lite的python接口提供更加方便灵活的使用Lite进行模型Inference，支持各种平台上运行，X86-CUDA，X86-CPU，Arm-CPU，Arm-CUDA平台。

## 安装
### whl包安装
Lite python接口的whl包会随着megbrain的发版发布，版本号和megbrain保持一致，目前发布的Lite的whl包，包括Linux、windows、macos平台，这些平台可以直接通过pip3安装。
```shell
    python3 -m pip install --upgrade pip
    python3 -m pip install megenginelite -i  https://pypi.megvii-inc.com/simple
```
### develop 安装
开发模式下，可以使用Cmake编译出lite动态库liblite.so/liblite.dll/liblite_shared.dylib，并使用这个动态库进行开发和debug。该方式安装的pylite只能在本地机器上使用，不能copy到其他机器上使用。
* 编译liblite.so。使用cmake编译出liblite.so
    * clone megbrain工程到本地
    ```shell
    git clone git@git-core.megvii-inc.com:brain-sdk/MegBrain.git
    ```
    * 进行Cmake编译，这里的cmake编译同megbrain的cmake编译，使用参数和宏也完全一样
    * 编译准备
    ```shell
    cd MegBrain
    sh ./third_party/prepare.sh
    mkdir build
    cd build 
    ```
    * 编译X86-CUDA版本
    ```shell
    cmake .. -DMGE_WITH_CUDA=ON -DMGE_WITH_TEST=ON -DCMAKE_BUILD_TYPE=Release &&  make -j$(nproc)
    ```
    * 编译X86 CPU Only版本
    ```shell
    cmake .. -DMGE_WITH_CUDA=OFF -DMGE_WITH_TEST=ON -DCMAKE_BUILD_TYPE=Release &&  make -j$(nproc)
    ```
    * 编译完成之后，liblite.so 保存在build目录中的lite文件下
    * 将liblite.so copy到megenginelite的python源文件目录下，就可以使用megenginelite了。
    ```shell
    MegBrain的工程目录为 ${mgb_hone}
    cp ${mgb_hone}/build/lite/liblite.so ${mgb_home}/lite/pylite/megenginelite/
    cd ${mgb_home}/lite/pylite
    python3 -m "import megenginelite"
    ```
    这样就可以在${mgb_home}/lite/pylite 目录下面开发和debug lite的python接口了

## python3中使用megenginelite
Lite的python接口是对其C/C++接口的一层封装，他们使用的模型都是相同的模型格式。megenginelite提供两种数据接口，分别是LiteTensor和LiteNetwork。

### LiteTensor
LiteTensor提供了用户对数据的操作接口，提供了接口包括:
* fill_zero: 将tensor的内存设置为全0
* share_memory_with: 可以和其他LiteTensor的共享内存
* copy_from: 从其他LiteTensor中copy数据到自身内存中
* reshape: 改变该LiteTensor的shape，内存数据保持不变
* slice: 对该LiteTensor中的数据进行切片，需要分别指定每一维切片的start，end，和step。
* set_data_by_share: 调用之后使得该LiteTensor中的内存共享自输入的array的内存，输入的array必须是numpy的ndarray，并且tensor在CPU上
* set_data_by_copy: 该LiteTensor将会从输入的data中copy数据，data可以是list和numpy的ndarray，需要保证data的数据量不超过tensor的容量，tensor在CPU上
* to_numpy: 将该LiteTensor中数据copy到numpy的array中，返回给用户，如果是非连续的LiteTensor，如slice出来的，将copy到连续的numpy array中，该接口主要数为了debug，有性能问题。

#### 使用example
* LiteTensor 设置数据example
```
def test_tensor_set_data():
    layout = LiteLayout([2, 16], "int8")
    tensor = LiteTensor(layout)
    assert tensor.nbytes == 2 * 16

    data = [i for i in range(32)]
    tensor.set_data_by_copy(data)
    real_data = tensor.to_numpy()
    for i in range(32):
        assert real_data[i // 16][i % 16] == i

    arr = np.ones([2, 16], "int8")
    tensor.set_data_by_copy(arr)
    real_data = tensor.to_numpy()
    for i in range(32):
        assert real_data[i // 16][i % 16] == 1

    for i in range(32):
        arr[i // 16][i % 16] = i
    tensor.set_data_by_share(arr)
    real_data = tensor.to_numpy()
    for i in range(32):
        assert real_data[i // 16][i % 16] == i

    arr[0][8] = 100
    arr[1][3] = 20
    real_data = tensor.to_numpy()
    assert real_data[0][8] == 100
    assert real_data[1][3] == 20
```
* tensor 共享内存example
```python
def test_tensor_share_memory_with():
    layout = LiteLayout([4, 32], "int16")
    tensor = LiteTensor(layout)
    assert tensor.nbytes == 4 * 32 * 2

    arr = np.ones([4, 32], "int16")
    for i in range(128):
        arr[i // 32][i % 32] = i
    tensor.set_data_by_share(arr)
    real_data = tensor.to_numpy()
    for i in range(128):
        assert real_data[i // 32][i % 32] == i

    tensor2 = LiteTensor(layout)
    tensor2.share_memory_with(tensor)
    real_data = tensor.to_numpy()
    real_data2 = tensor2.to_numpy()
    for i in range(128):
        assert real_data[i // 32][i % 32] == i
        assert real_data2[i // 32][i % 32] == i

    arr[1][18] = 5
    arr[3][7] = 345
    real_data = tensor2.to_numpy()
    assert real_data[1][18] == 5
    assert real_data[3][7] == 345
```
更多的使用可以参考pylite中test/test_tensor.py中的使用
### LiteNetwork
LiteNetwork主要为用户提供模型载入，运行等功能。使用的模型见lite的readme中关于模型的部分
* CPU基本模型载入运行的example
```
def test_network_basic():
    source_dir = os.getenv("LITE_TEST_RESOUCE")
    input_data_path = os.path.join(source_dir, "input_data.npy")
    # read input to input_data
    input_data = np.load(input_data_path)
    model_path = os.path.join(source_dir, "shufflenet.mge")

    network = LiteNetwork()
    network.load(model_path)

    input_name = network.get_input_name(0)
    input_tensor = network.get_io_tensor(input_name)
    output_name = network.get_output_name(0)
    output_tensor = network.get_io_tensor(output_name)

    assert input_tensor.layout.shapes[0] == 1
    assert input_tensor.layout.shapes[1] == 3
    assert input_tensor.layout.shapes[2] == 224
    assert input_tensor.layout.shapes[3] == 224
    assert input_tensor.layout.data_type == LiteDataType.LITE_FLOAT
    assert input_tensor.layout.ndim == 4

    # copy input data to input_tensor of the network
    input_tensor.set_data_by_copy(input_data)
    for i in range(3):
        network.forward()
        network.wait()

    output_data = output_tensor.to_numpy()
    print('shufflenet output max={}, sum={}'.format(output_data.max(), output_data.sum()))
```
* CUDA上使用device内存作为模型输入，需要在构造network候配置config和IO信息
```
def test_network_device_IO():
    source_dir = os.getenv("LITE_TEST_RESOUCE")
    input_data_path = os.path.join(source_dir, "input_data.npy")
    model_path = os.path.join(source_dir, "shufflenet.mge")
    # read input to input_data
    input_data = np.load(input_data_path)
    input_layout = LiteLayout([1, 3, 224, 224])
    host_input_data = LiteTensor(layout=input_layout)
    host_input_data.set_data_by_share(input_data)
    dev_input_data = LiteTensor(layout=input_layout, device_type=LiteDeviceType.LITE_CUDA)
    dev_input_data.copy_from(host_input_data)

    # construct LiteOption
    options = LiteOptions()
    options.weight_preprocess = 1
    options.var_sanity_check_first_run = 0
    net_config = LiteConfig(device_type=LiteDeviceType.LITE_CUDA, option=options)

    # constuct LiteIO, is_host=False means the input tensor will use device memory
    input_io = LiteIO("data", is_host=False)
    ios = LiteNetworkIO()
    ios.add_input(input_io)

    network = LiteNetwork(config=net_config, io=ios)
    network.load(model_path)

    input_name = network.get_input_name(0)
    dev_input_tensor = network.get_io_tensor(input_name)
    output_name = network.get_output_name(0)
    output_tensor = network.get_io_tensor(output_name)

    # copy input data to input_tensor of the network
    dev_input_tensor.share_memory_with(dev_input_data)
    for i in range(3):
        network.forward()
        network.wait()

    output_data = output_tensor.to_numpy()
    print('shufflenet output max={}, sum={}'.format(output_data.max(), output_data.sum()))
```
更多的使用可以参考pylite中test/test_network.py和test/test_network_cuda.py中的使用
