3.2.3. Python 代码解析¶
3.2.3.1. Case 0: 基础示例程序¶
在该示例程序中,我们封装了一个函数,完成图像分类的过程,如下:
def inference(bmodel_path, input_path, loops, tpu_id, compare_path): """ Do inference of a model in a thread. Args: bmodel_path: Path to bmodel input_path: Path to input image. loops: Number of loops to run compare_path: Path to correct result file status: Status of comparison Returns: True for success and False for failure. """在该函数中,我们通过循环处理同一张图像来模拟真实的图像分类业务。
# pipeline of inference for i in range(loops): # read image and preprocess image = preprocess(input_path).astype(np.float32) # inference with fp32 input and output # data scale(input: fp32 to int8, output: int8 to fp32) is done inside # for int8 model output = engine.process(graph_name, {input_name:image}) # postprocess result = postprocess(output[output_name]) # print result # ...如上代码所示,在每次循环中,我们首先使用opencv 的”imread“函数解码图片。 然后对图像进行预处理,获取bmodel 的输入张量。 接着驱动TPU 进行推理。最后对TPU 输出的张量进行后处理,获得最终的结果(top-5)。
在该示例程序中,图片解码和预处理的实现中直接调用了opencv 的相关函数, 同时后处理比较简单,只计算了top-5 分类结果。 因此你可以直接参考相关代码,下面我们主要介绍模型推理部分。
bmodel_path 是本例中用到的resnet50 bmodel 的路径,fp32 或int8 皆可。 我们使用该 bmodel 初始化了一个sail.Engine 的实例, 该实例中保存了模型的基础信息,将作为我们后续模型推理的载体。 如果你的机器上有多个TPU,那么可以通过tpu_id 指定使用哪个TPU进行推理,编号从0 开始。
engine = sail.Engine(bmodel_path, tpu_id, sail.SYSIO)我们可以通过该实例提供的方法获取模型的属性,代码如下:
graph_name = engine.get_graph_names()[0] input_name = engine.get_input_names(graph_name)[0] input_shape = engine.get_input_shape(graph_name, input_name) output_name = engine.get_output_names(graph_name)[0] output_shape = engine.get_output_shape(graph_name, output_name) out_dtype = engine.get_output_dtype(graph_name, output_name);graph_name 代表bmodel 中我们要使用的具体模型的名称,在该示例程序中,bmodel 中只包含一个模型。 input_name 代表我们使用的具体模型中的输入张量名称,在该模型中,只有一个输入张量。 output_name 代表我们使用的具体模型中的输出张量名称,在该模型中,只有一个输出张量。 input_shape 和output_shape 代表指定张量的尺寸。in_dtype 和 out_dtype 代表指定张量的数据类型。
3.2.3.2. Case 1: 单模型多线程¶
在case 0 中,我们使用了bmodel 初始化了一个sail.Engine 的实例, 模型推理的过程是在主线程中进行的。 在case 1 中,我们将展示如果在多个线程中使用同一个sail.Engine 实例做模型推理。
我们定义了一个”thread_infer“函数来实现子线程中的模型推理逻辑,如下:
def thread_infer(thread_id, engine, input_path, loops, compare_path, status): """ Do inference of a model in a thread. Args: thread_id: ID of the thread engine: An sail.Engine instance input_path: Path to input image file loops: Number of loops to run compare_path: Path to correct result file status: Status of comparison Returns: None. """在该函数中,我们也通过循环处理一张图片来模拟真实的图像分类业务, 整体的代码逻辑与 case 0 类似,在这里不再重复介绍。
case 1 与case 0 主要的区别在于对sail.Engine 实例的处理上,下面简写为engine。
首先,我们在主线程中创建 engine,使用了与 case 0 不同构造函数并使用 engine 的 ”load“ 函数加载 bmodel:
engine = sail.Engine(ARGS.tpu_id) engine.load(ARGS.bmodel)不同于case 0,使用该构造函数创建engine 时, engine 中不会为bmodel 的输入与输入张量创建内存, 而是需要用户额外提供输入与输出张量,即sail.Tensor 的实例。
因此,在子线程中,我们根据从 engine 实例中获取的模型信息创建对应张量:
# get handle to create input and output tensors handle = engine.get_handle() input = sail.Tensor(handle, input_shape, in_dtype, True, True) output = sail.Tensor(handle, output_shape, out_dtype, True, True) input_tensors = {input_name:input} ouptut_tensors = {output_name:output}而在模型推理时,也需要指定对应的张量,选择下面的重载函数即可:
engine.process(graph_name, input_tensors, ouptut_tensors)
3.2.3.3. Case 2: 多线程多模型¶
case 2 是 case 1 在模型数量上的扩展。 在case 1 中,我们在每个线程中都是用同一个在主线程中加载的 bmodel 做推理。 而在case 2 中,我们将会在 engine 中加载多个bmodel,如下是加载模型的子线程函数:
def thread_load(thread_id, engine, bmodel_path): """ Load a model in a thread. Args: thread_id: ID of the thread. engine: An sail.Engine instance. bmodel_path: Path to bmodel. Returns: None. """ ret = engine.load(bmodel_path) if ret == 0: graph_name = engine.get_graph_names()[-1] print("Thread {} load {} successfully.".format(thread_id, graph_name))其它代码与 case 1 基本一致.
3.2.3.4. Case 3: 多线程多 TPU 模式¶
case 3 是 case 1 在 TPU 数量上的扩展。由于 engine 是与TPU 一一对应的。 因此我们将对每个线程创建一个指定 tpu_id 的 engine,如下:
# init Engine to load bmodel and allocate input and output tensors # one engine for one TPU engines = list() thread_num = len(ARGS.tpu_id) for i in range(thread_num): engines.append(sail.Engine(ARGS.bmodel, ARGS.tpu_id[i], sail.SYSIO))其它代码与 case 1 基本一致.