# FastDeploy 中的图优化技术 FastDeploy 的 `GraphOptimizationBackend` 中集成了多种图优化技术: + **CUDA Graph**:一种通过单个 CPU 操作启动多个 GPU 操作的机制,可以降低开销并提高性能 + **动态图转静态图**:将动态图转换为静态图,利用全局图结构信息优化计算图、提升执行效率 + **CINN 神经网络编译器**:在静态图的基础上执行 IR 转换、Kernel 融合、Kernel 生成等计算图编译优化方法,实现综合优化 任何依赖数据的控制流、Host-Device 同步、地址/形状变化的模型输入、动态的 Kernel 执行配置等动态情况都会导致 CUDAGraph Capture/Replay 失败,而大模型推理中面临场景的是动态的输入长度、动态的 Batch Size,灵活的 Attention 实现和多卡通信,导致 CUDA Graph 难以应用。 开源主流方案基于静态图实现 CUDA Graph,技术栈较深。FastDeploy 不仅支持静态图、神经网络编译器、CUDAGraph 组合优化,还支持直接在动态图中应用 CUDA Graph ,开发成本更低,但面临的动态情况更复杂。 FastDeploy 的 `GraphOptimizationBackend` 设计架构如下,**部分功能仍在开发中,建议仔细阅读第一章节使用限制**。 ![](./images/GraphOptBackendArch.svg) ## 1. GraphOptimizationBackend 当前使用限制 ### 1.1 多卡场景需要开启 Custom all-reduce 在 CUDAGraph 多卡推理任务中需要使用 Custom all-reduce 算子进行多卡 all-reduce, 在 2.2 版本之前,CUDAGraph 未默认开启,Custom all-reduce 算子默认开启。 ### 1.2 FLAGS_max_partition_size 相关的 Kernel 的动态执行配置导致 CUDAGraph 执行失败 `FLAGS_max_partition_size` 环境变量控制了 CascadeAppend Attention 中 Kernel 的`gridDim` 执行配置 , 而动态的执行配置会导致 CUDAGraph 执行失败。 [PR#3223](https://github.com/PaddlePaddle/FastDeploy/pull/3223) 修复了这个问题,但在 2.2 之前的 Release 版本依然存在这个问题。 **问题自查方法:** + 根据`FLAGS_max_partition_size`的值(默认是 32K)和启动参数中的 `max_model_len`计算`div_up(max_model_len, max_partition_size)`,结果大于`1`时无法执行,等于`1`时可以正常运行 **解决方法:** 1. 调整`FLAGS_max_partition_size`和`max_model_len`的值,不触发动态执行配置。 2. 关闭 CUDAGraph ## 2. GraphOptimizationBackend 相关配置参数说明 当前仅支持用户配置以下参数: + `use_cudagraph` : bool = False + `graph_optimization_config` : Dict[str, Any] + `graph_opt_level`: int = 0 + `use_cudagraph`: bool = False + `cudagraph_capture_sizes` : List[int] = None 可以通过设置 `--use-cudagraph` 或 `--graph-optimization-config '{"use_cudagraph":true}'` 开启 CudaGrpah。 `--graph-optimization-config` 中的 `graph_opt_level` 参数用于配置图优化等级,可选项如下: + `0`: 动态图,默认为 0 + `1`: 静态图,初始化阶段会使用 Paddle API 将动态图转换为静态图 + `2`: 在静态图的基础上,使用 Paddle 框架编译器(CINN, Compiler Infrastructure for Neural Networks)进行编译优化 一般情况下静态图比动态图的 Kernel Launch 开销更小,推荐使用静态图。 对于已适配的模型,FastDeploy 的 CudaGraph **可同时支持动态图与静态图**。 在默认配置下开启 CudaGraph 时,会根据 `max_num_seqs` 参数自动设置 CudaGraph 需要捕获的 Batch Size 列表,需要捕获的 Batch Size 的列表自动生成逻辑如下: 1. 生成一个范围为 [1,1024] Batch Size 的候选列表 ``` # Batch Size [1, 2, 4, 8, 16, ... 120, 128] candidate_capture_sizes = [1, 2, 4] + [8 * i for i in range(1, 17)] # Batch Size (128, 144, ... 240, 256] candidate_capture_sizes += [16 * i for i in range(9, 17)] # Batch Size (256, 288, ... 992, 1024] candidate_capture_sizes += [32 * i for i in range(17, 33)] ``` 2. 根据用户设置的 `max_num_seqs` 裁剪候选列表,得到范围为 [1, `max_num_seqs`] 的 CudaGraph 捕获列表。 用户也可以通过 `--graph-optimization-config` 中的 `cudagraph_capture_sizes` 参数自定义需要被 CudaGraph 捕获的 Batch Size 列表: ``` --graph-optimization-config '{"cudagraph_capture_sizes": [1, 3, 5, 7, 9]}' ``` ### 2.1 CudaGraph相关参数说明 使用 CudaGraph 会产生一些额外的显存开销,在FastDeploy中分为下面两类: + 额外的输入 Buffer 开销 + CudaGraph 使用了专用的显存池,因此会持有一部分与主框架隔离的中间激活显存 FastDeploy 的初始化顺序为先使用 `gpu_memory_utilization` 参数计算 `KVCache` 可用的显存,初始化完 `KVCache` 之后才会使用剩余显存初始化 CudaGraph。由于 CudaGraph 目前还不是默认开启的,因此使用默认启动参数可能会遇到 `Out Of Memory` 错误,可以尝试使用下面三种方式解决: + 调低 `gpu_memory_utilization` 的值,多预留一些显存给CudaGraph使用。 + 调低 `max_num_seqs` 的值,降低最大并发数。 + 通过 `graph_optimization_config` 自定义需要 CudaGraph 捕获的 Batch Size 列表 `cudagraph_capture_sizes`,减少捕获的图的数量 使用CudaGraph之前,需要确保加载的模型被装饰器 ``@support_graph_optimization``正确修饰。 ```python # 1. import 装饰器 from fastdeploy.model_executor.graph_optimization.decorator import support_graph_optimization ... # 2. 添加装饰器 @support_graph_optimization class Ernie4_5_Model(nn.Layer): # 注意 decorator 加在 nn.Layer 的子类上 ... # 3. 修改 ModelForCasualLM 子类中 self.model() 的传参方式 class Ernie4_5_MoeForCausalLM(ModelForCasualLM): ... def forward( self, ids_remove_padding: paddle.Tensor, forward_meta: ForwardMeta, ): hidden_states = self.model(ids_remove_padding=ids_remove_padding, # 传参时指定参数名 forward_meta=forward_meta) return hidden_statesfrom fastdeploy.model_executor.graph_optimization.decorator import support_graph_optimization ... @support_graph_optimization class Ernie45TModel(nn.Layer): # 注意 decorator 加在 nn.Layer 的子类上 ... ```