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
设计架构如下,部分功能仍在开发中,建议仔细阅读第一章节使用限制。
1. GraphOptimizationBackend 当前使用限制
1.1 多卡场景需要开启 Custom all-reduce
在 CUDAGraph 多卡推理任务中需要使用 Custom all-reduce 算子进行多卡 all-reduce,
在 2.2 版本之前,CUDAGraph 和 Custom all-reduce 算子都未默认开启,需要在启动命令中添加 --enable-custom-all-reduce
手动开启。
1.2 FLAGS_max_partition_size 相关的 Kernel 的动态执行配置导致 CUDAGraph 执行失败
FLAGS_max_partition_size
环境变量控制了 CascadeAppend Attention 中 Kernel 的gridDim
执行配置 , 而动态的执行配置会导致 CUDAGraph 执行失败。
PR#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 = Falsegraph_optimization_config
: Dict[str, Any]graph_opt_level
: int = 0use_cudagraph
: bool = Falsecudagraph_capture_sizes
: List[int] = None
可以通过设置 --use-cudagraph
或 --graph-optimization-config '{"use_cudagraph":true}'
开启 CudaGrpah。
--graph-optimization-config
中的 graph_opt_level
参数用于配置图优化等级,可选项如下:
0
: 动态图,默认为 01
: 静态图,初始化阶段会使用 Paddle API 将动态图转换为静态图2
: 在静态图的基础上,使用 Paddle 框架编译器(CINN, Compiler Infrastructure for Neural Networks)进行编译优化
一般情况下静态图比动态图的 Kernel Launch 开销更小,推荐使用静态图。 对于已适配的模型,FastDeploy 的 CudaGraph 可同时支持动态图与静态图。
在默认配置下开启 CudaGraph 时,会根据 max_num_seqs
参数自动设置 CudaGraph 需要捕获的 Batch Size 列表,需要捕获的 Batch Size 的列表自动生成逻辑如下:
- 生成一个范围为 [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)]
- 根据用户设置的
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
正确修饰。
# 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 的子类上
...