# Python Pipeline 优化指南 - [优化响应时长](#1) - [1.1 分析响应时长](#1.1) - [Pipeline Trace Tool](#1.1.1) - [Pipeline Profile Tool](#1.1.2) - [1.2 优化思路](#1.2) - [优化服务吞吐](#2) - [2.1 分析吞吐瓶颈](#2.1) - [2.2 优化思路](#2.2) - [增加 Op 并发](#2.2.1) - [动态批量](#2.2.2) - [CPU 与 GPU 处理分离](#2.2.3) 通常,服务的性能优化是基于耗时分析,首先要掌握服务运行的各阶段耗时信息,从中找到耗时最长的性能瓶颈再做针对性优化。对于模型推理服务化不仅要关注耗时,由于 GPU 芯片昂贵,更要关注服务吞吐,从而提升 GPU 利用率实现降本增效。因此,模型推理服务化可总结为: - 优化响应时长 - 优化服务吞吐 经过分析和调优后,各个阶段实现整体服务的性能最优。 ## 优化响应时长 首先,优化响应时长的主要思路首先要掌握各阶段耗时,并分析出性能瓶颈或者耗时占比较高的阶段,再针对性能瓶颈做专项优化。 Paddle Serving 提供2种耗时分析工具,`Pipeline Trace Tool` 和 `Pipeline Profile Tool`。2个工具的特点如下: - Pipeline Trace Tool : 统计服务端所有进程各个阶段的平均耗时,包括每个 `Op` 和 `Channel`,用于定量分析。 - Pipeline Profile Tool : 是可视化 Trace View 工具,生成多进程并发效果图,用定性和定量分析执行和并发效果。 **一.耗时分析** 1.Pipeline Trace Tool `Pipeline Trace Tool` 统计每个 `Op` 和 `Channel` 中各阶段的处理耗时, 开启方法在配置文件 `config.yml` 的 `dag` 区段内添加 `tracer` 字段,框架会每隔 `interval_s` 时间生成 Trace 信息。 ``` dag: #op资源类型, True, 为线程模型;False,为进程模型 is_thread_op: True #tracer, 跟踪框架吞吐,每个OP和channel的工作情况。无tracer时不生成数据 tracer: #每次trace的时间间隔,单位秒/s interval_s: 10 ``` 生成的 Trace 信息保存在 `./PipelineServingLogs/pipeline.tracer` 日志中。如下图所示 ``` ==================== TRACER ====================== Op(uci): in[8473.507333333333 ms]: # 等待前置 Channel 中数据放入 Op 的耗时,如长时间无请求,此值会变大 prep[0.6753333333333333 ms] # 推理前处理 preprocess 阶段耗时 midp[26.476333333333333 ms] # 推理 process 阶段耗时 postp[1.8616666666666666 ms] # 推理后处理 postprocess 阶段耗时 out[1.3236666666666668 ms] # 后处理结果放入后置 channel 耗时 idle[0.9965882097324374] # 框架自循环耗时,间隔 1 ms,如此值很大说明系统负载高,调度变慢 DAGExecutor: Query count[30] # interval_s 间隔时间内请求数量 QPS[27.35 q/s] # interval_s 间隔时间内服务 QPS Succ[1.0] # interval_s 间隔时间内请求成功率 Error req[] # 异常请求信息 Latency: ave[36.55233333333334 ms] # 平均延时 .50[8.702 ms] # 50分位延时 .60[8.702 ms] # 60分位延时 .70[92.346 ms] # 70分位延时 .80[92.346 ms] # 70分位延时 .90[92.346 ms] # 90分位延时 .95[92.346 ms] # 95分位延时 .99[92.346 ms] # 99分位延时 Channel (server worker num[1]): chl0(In: ['@DAGExecutor'], Out: ['uci']) size[0/0] # 框架 RequestOp 与 uci Op 之间 Channel 中堆积请求数。此值较大,说明下游 uci Op 消费能力不足。 chl1(In: ['uci'], Out: ['@DAGExecutor']) size[0/0] # uci Op 与 框架 ResponseOp 之间 Channel 中堆积的请求数。此值较大,说明下游 ReponseOp 消费能力不足。 ==================== TRACER ====================== ``` 2.Pipeline Profile Tool ``` dag: #op资源类型, True, 为线程模型;False,为进程模型 is_thread_op: True #使用性能分析, 默认为 False,imeline性能数据,对性能有一定影响 use_profile: True, ``` 开启后,Server 端在预测的过程中会将对应的日志信息打印到`标准输出`,为了更直观地展现各阶段的耗时,因此服务启动要使用如下命令: ``` python3.7 web_service.py > profile.txt 2>&1 ``` 服务接收请求后,输出 Profile 信息到 `profile.txt` 文件中。再粘贴如下代码到 `trace.py`, 使用框架提供 Analyst 模块对日志文件做进一步的分析处理。 ``` from paddle_serving_server.pipeline import Analyst import json import sys if __name__ == "__main__": log_filename = "profile.txt" trace_filename = "trace" analyst = Analyst(log_filename) analyst.save_trace(trace_filename) ``` 运行命令,脚本将日志中的时间打点信息转换成 json 格式保存到 `trace` 文件。 ``` python3.7 trace.py ``` `trace` 文件可以通过 `chrome` 浏览器的 `tracing` 功能进行可视化。 ``` 打开 chrome 浏览器,在地址栏输入 chrome://tracing/ ,跳转至 tracing 页面,点击 load 按钮,打开保存的 trace 文件,即可将预测服务的各阶段时间信息可视化。 ``` 通过图示中并发请求的处理流程可观测到推理阶段的流水线状态,以及多个请求在推理阶段的`间隔`信息,进行优化。 **二.降低响应时长优化思路** 根据 `Pipeline Trace Tool` 输出结果在不同阶段耗时长的问题,常见场景的优化方法如下: - Op 推理阶段(midp) 耗时长: - 增加 Op 并发度 - 开启 auto-batching (前提是多个请求的 shape 一致) - 若批量数据中某条数据的 shape 很大,padding 很大导致推理很慢,可参考 OCR 示例中 mini-batch 方法。 - 开启 TensorRT/MKL-DNN 优化 - 开启低精度推理 - Op 前处理阶段(prep) 或 后处理阶段耗时长: - 增加 OP 并发度 - 优化前后处理逻辑 - in/out 耗时长(channel 堆积>5) - 检查 channel 传递的数据大小,可能为传输的数据大导致延迟大。 - 优化传入数据,不传递数据或压缩后再传入 - 增加 Op 并发度 - 减少上游 Op 并发度 根据 `Pipeline Profile Tool` 输出结果优化流水行并发的效果 - 增加 Op 并发度,或调整不同 Op 的并发度 - 开启 auto-batching 此外,还有一些优化思路,如将 CPU 处理较慢的过程转换到 GPU 上处理等,客户端与服务端传输较大数据时,可使用共享内存方式传递内存或显存地址等。 ## 优化服务吞吐 **一.分析吞吐瓶颈** 服务的吞吐量受到多种多因素条件制约,如 Op 处理时长、传输数据耗时、并发数和 DAG 图结构等,可以将这些因素进一步拆解,当传输数据不是极端庞大的时候,最重要因素是流水线中`最慢 Op 的处理时长和并发数`。 ``` Op 处理时长: op_cost = process(pre + mid + post) 服务吞吐量: service_throughput = 1 / 最慢 op_cost * 并发数 服务平响: service_avg_cost = ∑op_concurrency 【关键路径】 批量预测平均耗时: avg_batch_cost = (N * pre + mid + post) / N ``` **二.优化思路** 优化吞吐的主要方法是 `增大 Op 并发数`、`自动批量` 和 `CPU 与 GPU 处理分离` 1.增加 Op 并发** 调整 Op 的并发数量通过设置 `is_thread_op: False` 进程类型 Op 和 `uci` Op 的 `concurrency` 字段 ``` dag: #op资源类型, True, 为线程模型;False,为进程模型 is_thread_op: False op: uci: #并发数,is_thread_op=True时,为线程并发;否则为进程并发 concurrency: 10 ``` Op 的进程数量不是越大越好,受到机器 CPU 核数、内存和显存大小的限制,推荐设置 Op 的并发数不超过系统 CPU 核数。 2.动态批量 动态批量是增加吞吐的有一种方法,开启方式可参考[Python Pipeline 核心功能](./7-2_Python_Pipeline_Senior_CN.md#批量推理) 3.CPU 与 GPU 处理分离 在 `CV` 模型中,对图片或视频的前后处理成为主要瓶颈时,可考虑此方案,即将前后处理过程独立成一个 Op 并独立设置并发度。 将 CPU 前后处理和 GPU 推理过程比例调整到服务最佳配比。以 OCR 为例,原有流水线设计为 `RequestOp -> DetOp -> RecOp -> ResponseOp`。 根据耗时分析,`DetOp` 和 `RecOp` 的前处理耗时很长,因此,将2个模型前处理分离成独立 Op,最新的流水线设计为: `RequestOp -> PreDetOp -> DetOp -> PreRecOp -> RecOp -> ResponseOp`,并调大 `PreDetOp` 和 `PreRecOp`的并发度,从而获得 20% 的性能提升。 由于增加了2次数据传递,单条请求的处理延时会增加。