5. 模型运行
在 Section 2.3, IPU推理方案架构 中提到,IPU推理方案分为模型编译和模型运行时两个阶段。本章将介绍在用户的模型通过模型转换并编译为 PopEF 之后,如何通过 PopRT 、 Triton Inference Server 或 TensorFlow Serving 部署和运行。
备注
本章示例的命令和代码较长,推荐使用HTML版本的文档进行拷贝
PDF格式下拷贝命令和代码请注意空格、换行以及分页
5.1. 通过PopRT Runtime运行
PopRT Runtime是PopRT中用于加载和运行PopEF的运行时环境。PopRT Runtime提供Python和C++ API,可以用于PopEF的快速验证,或者与机器学习框架以及模型服务框架集成。本节将以 Section 4.1.4, 模型转换和编译 中生成的 executable.popef
为例,讲述如何使用PopRT Runtime API加载和运行PopEF。
5.1.1. 环境准备
将当前目录切换到 Section 4.1.4, 模型转换和编译 中的 executable.popef
所在的目录,并通过 ls
检查目录是否正确。
$ ls `pwd -P` | grep bertsquad-12_fp16_bs_16.onnx
显示如下的信息则证明当前目录是正确的。
bertsquad-12_fp16_bs_16.onnx
使用以下的命令启动容器:
$ gc-docker -- --rm -it \
-v `pwd -P`:/model_runtime_test \
-w /model_runtime_test \
--entrypoint /bin/bash \
graphcorecn/poprt-staging:latest
5.1.2. 通过Python API运行
将 Listing 5.1 中的示例代码保存为 model_runner_quick_start.py
。
# Copyright (c) 2022 Graphcore Ltd. All rights reserved.
import numpy as np
from poprt import runtime
# Load the popef
runner = runtime.ModelRunner('executable.popef')
# Get the input and output information of the model
inputs = runner.get_model_inputs()
outputs = runner.get_model_outputs()
# Create the random inputs and zero outputs
inputs_dict = {
x.name: np.random.randint(2, size=x.shape).astype(x.numpy_data_type())
for x in inputs
}
outputs_dict = {
x.name: np.zeros(x.shape).astype(x.numpy_data_type()) for x in outputs
}
# Execute the inference
runner.execute(inputs_dict, outputs_dict)
# Check the output values
for name, value in outputs_dict.items():
print(f'{name} : {value}')
运行保存的示例代码:
$ python3 model_runner_quick_start.py
成功运行将得到类似下面的输出:
unstack:1 : [[-0.9604 -1.379 -2.01 ... -1.814 -1.78 -1.626 ]
[-1.051 -1.977 -1.913 ... -1.435 -1.681 -1.251 ]
[-3.67 -2.71 -2.78 ... -3.951 -4.027 -3.959 ]
...
[-0.0919 -0.6445 -0.3125 ... -0.384 -0.54 -0.3152]
[-0.69 -1.071 -1.421 ... -1.533 -1.456 -1.389 ]
[-3.56 -2.99 -3.23 ... -4.05 -3.977 -3.955 ]]
unstack:0 : [[-1.437 -1.645 -2.17 ... -2.139 -2.379 -2.281 ]
[-1.259 -1.8545 -1.915 ... -1.804 -1.8955 -1.671 ]
[-2.832 -2.057 -2.104 ... -3.29 -3.34 -3.36 ]
...
[-0.4673 -0.8716 -0.8545 ... -1.253 -1.287 -1.289 ]
[-1.288 -1.481 -1.928 ... -2.158 -2.146 -2.129 ]
[-2.762 -2.43 -2.6 ... -3.418 -3.23 -3.324 ]]
unique_ids:0 : [1 0 1 1 1 0 0 1 1 0 1 1 1 1 0 1]
5.1.3. 通过C++ API运行
PopRT Runtime也提供了C++ API,将 Listing 5.2 中的代码保存为 model_runner_quick_start.cpp
。
// Copyright (c) 2022 Graphcore Ltd. All rights reserved.
#include <iostream>
#include "poprt/runtime/model_runner.hpp"
int main(int argc, char* argv[]) {
// Load the PopEF file
auto runner = poprt::runtime::ModelRunner("executable.popef");
// Get the inputs and outputs information of model
auto inputs = runner.getModelInputs();
auto outputs = runner.getModelOutputs();
// Create the inputs and ouputs
poprt::runtime::InputMemoryView in;
poprt::runtime::OutputMemoryView out;
std::vector<std::shared_ptr<unsigned char[]>> memories;
int i=0;
for(const auto& input : inputs){
memories.push_back(std::shared_ptr<unsigned char[]>(new unsigned char[input.sizeInBytes]));
in.emplace(input.name, poprt::runtime::ConstTensorMemoryView(memories[i++].get(), input.sizeInBytes));
}
for(const auto& output : outputs){
memories.push_back(std::shared_ptr<unsigned char[]>(new unsigned char[output.sizeInBytes]));
out.emplace(output.name, poprt::runtime::TensorMemoryView(memories[i++].get(), output.sizeInBytes));
}
// Execute the inference
runner.execute(in, out);
// Print the result information
std::cout << "Sucessfully executed. The outputs are: " << std::endl;
for(const auto& output: outputs)
std::cout << "name: " << output.name
<< ", dataType: " << output.dataType
<< ", sizeInBytes: " << output.sizeInBytes
<< std::endl;
}
编译代码:
$ apt-get update && \
apt-get install g++ -y && \
g++ model_runner_quick_start.cpp -o model_runner_quick_start \
--std=c++14 -I/usr/local/lib/python3.8/dist-packages/poprt/include \
-L/usr/local/lib/python3.8/dist-packages/poprt/lib \
-lpoprt_runtime -lpoprt_compiler -lpopef
运行编译得到的程序:
$ LD_LIBRARY_PATH=/usr/local/lib/python3.8/dist-packages/poprt/lib:$LD_LIBRARY_PATH ./model_runner_quick_start
成功运行会得到如下的输出:
Sucessfully execute, the outputs:
name: unstack:1, dataType: F16, sizeInBytes: 8192
name: unstack:0, dataType: F16, sizeInBytes: 8192
name: unique_ids:0, dataType: S32, sizeInBytes: 64
备注
完成上述示例后,请退出当前容器,回到主机环境
5.2. 部署到Trition Inference server
Graphcore通过Poplar Model Runtime实现了 libtriton_poplar.so
,它作为Trition Inference Server的plugin包含在Poplar SDK中,负责加载和运行PopEF。
更多关于Poplar Triton Backend的信息,请参考 Poplar Triton Backend: User Guide 。
本节将以 Section 4.1.4, 模型转换和编译 中生成的 executable.popef
文件为例,讲解如何将编译出来的PopEF部署到Trition Inference Server。
5.2.1. 环境准备
首先将当前目录切换到 Section 4.1.4, 模型转换和编译 中生成的 executable.popef
所在的目录,通过以下命令检查目录是否正确。
$ ls `pwd -P` | grep bertsquad-12_fp16_bs_16.onnx
显示如下的信息则证明当前目录是正确的:
bertsquad-12_fp16_bs_16.onnx
创建一个 model_repository
的目录。
$ mkdir -p model_repository/bertsquad-12/1/ && \
cp executable.popef model_repository/bertsquad-12/1/ && \
touch model_repository/bertsquad-12/config.pbtxt && \
cd model_repository
目录结构如下:
$ tree .
.
└── bertsquad-12
├── 1
│ └── executable.popef
└── config.pbtxt
bertsquad-12
是模型的名字1
表示模型的版本executable.popef
是模型编译产生的PopEF文件config.pbtxt
是在 生成模型的配置 中说明的Trition配置文件
备注
当模型的输入输出中包含Triton Inference Server不能处理的特殊时,可以使用Poplar Triton Backend的输入输出名称映射的功能。请参考 Poplar Triton Backend: User Guide 中 Input/Output Name Mapping 的信息。
5.2.2. 生成模型的配置
部署模型到Triton Inference Server需要为这个模型创建一个配置文件 config.pbtxt
,主要包含模型的名称、使用的backend、batching的信息和输入输出等信息,更多关于模型配置的内容请参考 Triton Model Configuration 文档。
本例中使用的 config.pbtxt
配置如下,需要将内容拷贝到上述产生的空的 config.pbtxt
中。
name: "bertsquad-12"
backend: "poplar"
max_batch_size: 16
dynamic_batching {
preferred_batch_size: [16]
max_queue_delay_microseconds: 5000
}
input [
{
name: "input_ids:0"
data_type: TYPE_INT32
dims: [ 256 ]
},
{
name: "input_mask:0"
data_type: TYPE_INT32
dims: [ 256 ]
},
{
name: "segment_ids:0"
data_type: TYPE_INT32
dims: [ 256 ]
},
{
name: "unique_ids_raw_output___9:0"
data_type: TYPE_INT32
dims: [ 1 ]
reshape: { shape: [ ] }
}
]
output [
{
name: "unique_ids:0"
data_type: TYPE_INT32
dims: [ 1 ]
reshape: { shape: [ ] }
},
{
name: "unstack:0"
data_type: TYPE_FP16
dims: [ 256 ]
},
{
name: "unstack:1"
data_type: TYPE_FP16
dims: [ 256 ]
}
]
parameters [
{
key: "synchronous_execution"
value:{string_value: "1"}
},
{
key: "timeout_ns"
value:{string_value: "500000"}
}
]
模型名称
bertsquad-12
,通常与模型所在的目录名一致
Backend
poplar
,指明使用Poplar Triton Backend
Batching
Poplar Triton Backend支持动态batch,建议 max_batch_size
和 preferred_batch_size
的值设置为模型batch size的整数倍。本例中模型的batch size为16,简单起见直接设置两个参数值为batch size的大小。
输入和输出
输入和输出的名字、类型和维度信息,可以通过 popef_dump
命令获取。更多关于 popef_dump
的信息,请参考 PopEF: User Guide 中的 PopEF file analysis 。
$ gc-docker -- --rm \
-v `pwd -P`:/models \
--entrypoint popef_dump \
graphcorecn/toolkit-triton-staging:latest \
/models/bertsquad-12/1/executable.popef
popef_dump
的输出信息节选如下:
PopEF file: executable.popef
Metadata:
...
Anchors:
Inputs (User-provided):
Name: "input_ids:0":
TensorInfo: { dtype: S32, sizeInBytes: 16384, shape [16, 256] }
Programs: [5]
Handle: h2d_input_ids:0
IsPerReplica: **False**
...
Outputs (User-provided):
Name: "unique_ids:0":
TensorInfo: { dtype: S32, sizeInBytes: 64, shape [16] }
Programs: [5]
Handle: anchor_d2h_unique_ids:0
IsPerReplica: **False**
...
从上述节选的 popef_dump
的部分输出,可以看到PopEF的模型输入输出与模型配置文件中输入输出的对应关系。其中 dtype
的数据类型,可以参考 PopEF Tensor and Feed Data 和 Types supported by Poplar Triton Backend 。
在 max_batch_size
的设置不为0的情况下, 模型配置文件中 dims
的维度信息是不包含batch size这一维的,比如 input_ids:0
的维度是 [ 16, 256 ]
,在模型配置文件中配置为 dims: [ 256 ]
。 对于仅有batch size这一个维度的输入输出,如 unique_ids:0
,需要设置 dims: [ 1 ]
并使用 reshape: { shape: [ ] }
将维度转换成一维。更多关于维度设置相关的内容,请参考 Triton Model Configuration 文档。
更多关于 config.pbtxt
配置文件中的字段信息,请参考 Poplar Triton Backend 文档中的 Triton model configuration 。
5.2.3. 启动模型服务
通过 gc-docker
启动容器,如果主机上没有安装Poplar SDK请参考 Section 3.7.2, 使用docker run启动容器。
$ gc-docker -- --rm \
--network=host \
-v `pwd -P`:/models \
graphcorecn/toolkit-triton-staging:latest
备注
如果在IPU-M2000或Bow-2000的环境中测试,运行 gc-docker
时请删除 --network=host
参数
打印出如下信息,说明服务已经启动,可以接受 gRPC
和 HTTP
的请求。
Started GRPCInferenceService at 0.0.0.0:8001
Started HTTPService at 0.0.0.0:8000
通过gRPC验证服务
以下是通过 Triton Client gRPC API 测试部署模型的例子,更详细的API信息请参考其文档和代码示例。
import numpy as np
import tritonclient.grpc as gc
# create the triton client
triton_client = gc.InferenceServerClient(
url = "localhost:8001")
model_name = 'bertsquad-12'
inputs = []
outputs = []
inputs.append(gc.InferInput('input_ids:0', [16,256], "INT32"))
inputs.append(gc.InferInput('input_mask:0', [16,256], "INT32"))
inputs.append(gc.InferInput('segment_ids:0', [16,256], "INT32"))
inputs.append(gc.InferInput('unique_ids_raw_output___9:0', [ 16,1 ], "INT32"))
# create data
input0_data = np.random.randint(0, 1000, size=(16,256)).astype(np.int32)
input1_data = np.random.randint(0, 1, size=(16,1)).astype(np.int32)
for i in range(3):
inputs[i].set_data_from_numpy(input0_data)
inputs[3].set_data_from_numpy(input1_data)
outputs_names = ['unique_ids:0', 'unstack:0', 'unstack:1']
for name in outputs_names:
outputs.append(gc.InferRequestedOutput(name))
results = triton_client.infer(
model_name = model_name,
inputs = inputs,
outputs = outputs)
statistics = triton_client.get_inference_statistics(model_name=model_name)
if len(statistics.model_stats) != 1:
print("FAILED: Inference Statistics")
sys.exit(1)
print(statistics)
for name in outputs_names:
print(f'{name} = {results.as_numpy(name)}')
打开新的终端连接到主机,将以上代码保存到 grpc_test.py
,创建python虚拟环境并测试。
$ virtualenv -p python3 venv && \
source venv/bin/activate && \
pip install tritonclient[all] && \
python grpc_test.py && \
deactivate
正确执行,则返回模型统计信息和推理结果。
model_stats {
name: "bertsquad-12"
version: "1"
last_inference: 1667439772895
inference_count: 64
execution_count: 4
inference_stats {
success {
count: 4
ns: 170377440
}
...
unique_ids:0 = [[0]
...
unstack:0 = [[-0.991 -1.472 -1.571 ... -1.738 -1.77 -1.803]
...
unstack:1 = [[-0.9023 -1.285 -1.325 ... -1.419 -1.441 -1.452 ]
...
通过HTTP验证服务
以下是通过 Triton Client HTTP API 测试部署模型的例子,更详细的API信息请参考其文档和代码示例。
import numpy as np
import tritonclient.http as hc
# create the triton client
triton_client = hc.InferenceServerClient(
url = "localhost:8000")
model_name = 'bertsquad-12'
inputs = []
outputs = []
inputs.append(hc.InferInput('input_ids:0', [16,256], "INT32"))
inputs.append(hc.InferInput('input_mask:0', [16,256], "INT32"))
inputs.append(hc.InferInput('segment_ids:0', [16,256], "INT32"))
inputs.append(hc.InferInput('unique_ids_raw_output___9:0', [ 16,1 ], "INT32"))
# create data
input0_data = np.random.randint(0, 1000, size=(16,256)).astype(np.int32)
input1_data = np.random.randint(0, 1, size=(16,1)).astype(np.int32)
for i in range(3):
inputs[i].set_data_from_numpy(input0_data)
inputs[3].set_data_from_numpy(input1_data)
outputs_names = ['unique_ids:0', 'unstack:0', 'unstack:1']
for name in outputs_names:
outputs.append(hc.InferRequestedOutput(name, binary_data=True))
results = triton_client.infer(
model_name = model_name,
inputs = inputs,
outputs = outputs)
statistics = triton_client.get_inference_statistics(model_name=model_name, headers=None)
if len(statistics['model_stats']) != 1:
print("FAILED: Inference Statistics")
sys.exit(1)
print(statistics)
for name in outputs_names:
print(f'{name} = {results.as_numpy(name)}')
triton_client_http_test.py
(重命名为 http_test.py
)
打开新的终端连接到主机,将以上代码保存到 http_test.py
,创建Python虚拟环境并测试。
# Execute the following virtualenv command if the python virtual environment has not been created
# virtualenv -p python3 venv
$ source venv/bin/activate && \
pip install tritonclient[all] && \
python http_test.py && \
deactivate
正确执行,则返回模型统计信息和推理结果。
{'model_stats': [{'name': 'bertsquad-12', 'version': '1', 'last_inference': 1667440001420, 'inference_count': 80, ... {'count': 5, 'ns': 462978}}]}]}
unique_ids:0 = [[0]
...
unstack:0 = [[-0.753 -1.183 -1.296 ... -1.595 -1.599 -1.65 ]
...
unstack:1 = [[-0.6206 -0.9683 -1.031 ... -1.222 -1.221 -1.241 ]
...
备注
本示例结束,请按<ctrl+C>退出Triton容器,回到主机环境
5.3. 部署到TensorFlow Serving
本节将以 Section 4.2.2, 模型转换和编译 中生成的 executable.popef
文件为例,讲解如何将编译出来的PopEF部署到 TensorFlow Serving 。
5.3.1. 环境准备
通过 gc-docker
启动容器,如果主机上没有安装Poplar SDK请参考 Section 3.7.2, 使用docker run启动容器 。
启动docker前,将 MODEL_PATH
指向包含 resnet_v2_50_optimized.onnx
的路径:
$ export MODEL_PATH=/path/to/your/models
$ ls $MODEL_PATH
如果 MODEL_PATH
指定的正确,将会看到如下信息:
resnet_v2_50_optimized.onnx executable.popef ...
5.3.2. 生成SavedModel模型
TensorFlow Serving的输入模型格式是SavedModel,所以我们需要将PopEF文件封装在一个model_runtime自定义算子中,使得TensorFlow在运行该算子时,可以通过调用 Poplar Model Runtime API来运行PopEF模型文件。
$ gc-docker -- --rm \
-v $MODEL_PATH:/model_path \
graphcorecn/toolkit-tfserving-staging:latest \
/bin/bash -c "python3 -m popef2tf.convert \
--model /model_path/executable.popef \
--name resnet_v2_50_serving \
--version 001 \
--output /model_path"
备注
该镜像使用的TensorFlow为通过官方源代码编译得到的TensorFlow-2.6.5,并非Poplar SDK中提供的TensorFlow。
因此,这里使用 popef2tf
生成TensorFlow的SavedModel模型,其中包含了 model_runtime
自定义算子以执行指定的PopEF模型。其中, popef2tf
工具的输入参数 model
和 output
分别指定了输入PopEF文件的路径和输出SavedModel的路径。
通过 tree
可以观察生成的SavedModel文件目录如下:
$ tree $MODEL_PATH/resnet_v2_50_serving
resnet_v2_50_serving
└── 001
├── assets
│ └── executable.popef
├── saved_model.pb
└── variables
├── variables.data-00000-of-00001
└── variables.index
5.3.3. 启动模型服务
TensorFlow Serving提供了batching功能,而IPU使用静态图,需要确定 batch_size
和 input_shape
。对于该模型,我们使用固定shape为[4,3,224,224]的输入tensor尺寸,其所对应的 batch_size
为4。
因此,我们需要在部署时,通过配置文件设置 allowed_batch_sizes
和 max_batch_size
为4,使客户端可以向服务器发送batch_size为[1-4]间任意整数的数据。更多关于 allowed_batch_sizes
的信息请参考 batching_effect
。
在 MODEL_PATH
目录下创建配置文件并编辑:
$ touch $MODEL_PATH/resnet_v2_50_serving/001/resnet_bs4_3_224_224.conf
$ vim $MODEL_PATH/resnet_v2_50_serving/001/resnet_bs4_3_224_224.conf
在该文件中添加:
allowed_batch_sizes: 4
max_batch_size {value: 4}
开启Serving服务:
$ gc-docker -- --rm \
-v $MODEL_PATH:/model_path \
--network=host \
graphcorecn/toolkit-tfserving-staging:latest \
/bin/bash -c "tensorflow_model_server \
--rest_api_port=8501 \
--model_name=resnet_v2_50 \
--enable_batching \
--batching_parameters_file=/model_path/resnet_v2_50_serving/001/resnet_bs4_3_224_224.conf \
--model_base_path=/model_path/resnet_v2_50_serving"
其中,参数 --enable_batching
用于开启 batching 功能, --batching_parameters_file
指定了配置文件的路径。
备注
如果在IPU-M2000或Bow-2000上测试,运行 gc-docker
时请去掉 --network=host
参数
备注
该镜像使用的 TensorFlow Serving 是通过官方源代码编译得到的TensorFlow Serving-2.6.5 (TensorFlow-2.6.5),并支持 model_runtime
自定义算子。
如果无需开启batching功能,在启动该容器时去除 --enable_batching
和 --batching_parameters_file
2个参数即可。此时,客户端仅能处理 batch_size=4
的输入数据。
容器启动后,输出log如下:
Building single TensorFlow model file config: model_name: resnet_v2_50 model_base_path: /model_path/resnet_v2_50_serving
Adding/updating models.
**(Re-)**\ adding model: resnet_v2_50
Successfully reserved resources to load servable {name: resnet_v2_50 version: 1}
Approving load for servable version {name: resnet_v2_50 version: 1}
Loading servable version {name: resnet_v2_50 version: 1}
Reading SavedModel from: /model_path/resnet_v2_50_serving/001
Reading meta graph with tags { serve }
Reading SavedModel debug info (if present) from: /model_path/resnet_v2_50_serving/001
...
Successfully loaded servable version {name: resnet_v2_50 version: 1}
...
Exporting HTTP/REST API at:localhost:8501 ...
resnet_v2_50
模型服务已发布到8501端口,客户端可以通过RESTful API来调用该服务。
开启或关闭batching功能
在运行TensorFlow Serving可以开启或者关闭batching功能。本节将讨论关于 allowed_batch_sizes
在batching开启和关闭时的作用。
开启batching并设置
allowed_batch_sizes
的值为4客户端可以发送
batch size
大小为[1-4]之间的数据,如果batch size小于4,服务器端将会填充数据到allowed_batch_sizes
或max_batch_size
,即4这个大小。例如,客户端发送的数据大小为[1, 3, 224, 224],服务器端会通过空数据将其填充到[4, 3, 224, 224]的大小。开启batching但不设置
allowed_batch_sizes
服务器端将接受
batch size
为[1-4]的数据,但PopEF只能接受batch size
为4的数据。例如,客户端发送大小为[2, 3, 224, 224]的数据,服务器只检查大小是否超过max_batch_size
,而并不会填充数据。由于数据的大小并没有超过设置的最大阈值,服务器会将其发送给TensorFlow后端,这将引发数据大小不匹配的错误。而如果发送大小为[5, 3, 224, 224]的数据时,因为超出了
max_batch_size
设置的阈值,会引发超出最大batch size的错误。禁用Batching
客户端仅允许发送
batch_size==4
的数据。例如,当客户端发送大小为[5, 3, 224, 224]的请求时,同样会导致数据大小不匹配的错误。
5.3.4. 通过HTTP验证服务
本例会下载一组图片作为运行示例,演示如何通过RESTful API调用模型服务:
import numpy as np
import json, urllib3
import tempfile, wget
from PIL import Image
def read_labels_file(fname):
outs = []
with open(fname, 'r') as fin:
for line in fin.readlines():
outs.append(line.strip())
return outs
def read_images(files, img_h, img_w):
images = []
for file in files:
image = Image.open(file)
image = image.resize((img_h, img_w))
image = (np.array(image) / 255).astype(np.float32)
image = image.transpose((2, 0, 1))
images.append(image[np.newaxis, :])
return images
def main():
# image urls
urls = [
'http://images.cocodataset.org/test-stuff2017/000000024309.jpg',
'http://images.cocodataset.org/test-stuff2017/000000028117.jpg',
'http://images.cocodataset.org/test-stuff2017/000000006149.jpg',
'http://images.cocodataset.org/test-stuff2017/000000004954.jpg',
]
# download images
image_files = []
with tempfile.TemporaryDirectory() as tmpdir:
for url in urls:
image_files.append(wget.download(url, tmpdir))
images = read_images(image_files, 224, 224)
label_file = wget.download(
"https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt", tmpdir)
labels = read_labels_file(label_file)
# get input json
input_data = np.concatenate(images, axis=0)
input_data_list = input_data.tolist()
postData = {'inputs':input_data_list}
jPostData = json.dumps(postData)
http = urllib3.PoolManager()
r = http.request('POST','http://localhost:8501/v1/models/resnet_v2_50:predict',body=jPostData)
return_data = json.loads(r.data)
output = np.array(list(return_data.values()))
# get real images top5
k = 5
idx = output.argsort()[:,:,-1:-k-1:-1]
for b,idx_bs in enumerate(idx[0]):
print(f'\nimage{b}:')
for i in idx_bs:
print(labels[i], output[0, b, i])
if __name__ == "__main__":
main()
打开新的终端连接到主机,将以上代码保存到 restful_http_test.py
,创建Python虚拟环境并测试。
virtualenv -p python3 venv
source venv/bin/activate
pip install pillow numpy urllib3 wget
python restful_http_test.py
deactivate
正确执行,则返回结果示例如下:
image0:
laptop 18.2867374
notebook 15.9842319
desk 13.6191549
web site 11.1951714
mouse 11.1430635
image1:
mashed potato 14.4165163
guacamole 11.8748741
meat loaf 10.7978802
cheeseburger 9.09055805
plate 8.93529892
image2:
knee pad 8.52720547
volleyball 8.52627659
racket 8.31885242
ski 7.84297752
horizontal bar 7.11924124
image3:
hare 17.0646706
fountain 15.7413292
tennis ball 13.2553062
wallaby 12.6554518
wood rabbit 11.5797682