1. MindSpore静态图网络编译性能优化的前因
静态图模式又被称为Graph模式,在Graph模式下,基于图优化、计算图整图下沉等技术,编译器可以针对图进行全局的优化,获得较好的性能,因此比较适合网络固定且需要高性能的场景。静态图的特点是将计算图的构建和实际计算分开, 也就是编译和运行分开。
网络执行过程的时间主要由编译耗时与运行耗时两个组成,在推理的时候,编译耗时远大于运行耗时, 因此减少编译耗时势在必得,且优化编译性能对于提升网络在实际应用时的部署效果有着极为重要的意义。
2. MindSpore静态图网络编译性能优化的方法
- 使用HyperMap优化编译性能
- 使用Select算子优化编译性能
- 使用编译缓存优化编译性能
- 使用vmap优化编译性能
3. 使用编译缓存优化编译性能
编译缓存的本质是存储了网络模型的编译中间过程文件,当网络模型不变时,
中间过程文件可以复用。
复用方法就是在代码中用enable_compile_cache进行控制
4. enable_compile_cache介绍
enable_compile_cache支持三个后端:Ascend GPU CPU
使用方法是:
from mindspore import context
context.set_context(enable_compile_cache=True, compile_cache_path="./cache.ms")
enable_compile_cache (bool) #表示是否加载或者保存前端编译的图。
当 enable_compile_cache 被设置为True时,在第一次执行的过程中,一个硬件无关的编译缓存会被生成并且导出为一个MINDIR文件。
当该网络被再次执行时,如果 enable_compile_cache 仍然为True并且网络脚本没有被更改,那么这个编译缓存会被加载
。注意目前只支持有限的Python脚本更改的自动检测,这意味着可能有正确性风险。默认值: False 。这是一个实验特性,可能会被更改或者删除。
5. 示例代码
import time
from mindspore import set_context
from mindspore import dtype
import mindspore as ms
@ms.jit
def func(input_x, input_y):
output = input_x
for _ in range(200):
output = input_x + input_x * input_y + output
return output
set_context(enable_compile_cache=True, compile_cache_path="my_compile_cache")
x = ms.Tensor([1], dtype.float32)
y = ms.Tensor([2], dtype.float32)
start_time = time.time()
out = func(x, y)
end_time = time.time()
print("Enable comile_cache cost time:", end_time - start_time)
第一遍:
[WARNING] PIPELINE(54491,7f1a1ae7a740,python):2023-08-31-19:37:37.880.474 [mindspore/ccsrc/pipeline/jit/ps/compile_cache_manager.cc:353] CheckDepFilesHashConsistency] Open the hash file /home/wys/MindSporeTest/parallel/my_compile_cache/rank_0/graph_cache/compile_dependency.hash failed. The file may not exist. Errno: 2, ErrInfo: No such file or directory
[WARNING] PIPELINE(54491,7f1a1ae7a740,python):2023-08-31-19:37:37.880.500 [mindspore/ccsrc/pipeline/jit/ps/resource.cc:699] GetCompileCacheResource] Check the consistency of dependency files hash failed. Execute all the compilation actions.
Enable comile_cache cost time: 10.460726261138916
第二遍:
[WARNING] PIPELINE(54690,7f93d9603740,python):2023-08-31-19:38:15.971.356 [mindspore/ccsrc/pipeline/jit/ps/compile_cache_manager.cc:391] GetCachedFuncGraph] Use the compilation cache and execute the backend actions only. Be aware of correctness risks.
Enable comile_cache cost time: 10.13181447982788
第三遍:
[WARNING] PIPELINE(54885,7f237d723740,python):2023-08-31-19:39:25.295.035 [mindspore/ccsrc/pipeline/jit/ps/compile_cache_manager.cc:391] GetCachedFuncGraph] Use the compilation cache and execute the backend actions only. Be aware of correctness risks.
Enable comile_cache cost time: 9.852082967758179
以上代码执行两次, 可以看到,第一遍执行时间最长
6. 使用vmap优化编译性能
vmap不仅能优化编译性能,也能优化运行性能, vmap可以替代for循环来优化性能
7. vamp用法介绍
mindspore.vmap(fn, in_axes=0, out_axes=0)
自动向量化(Vectorizing Map,vmap),是一种用于沿参数轴映射函数 fn 的高阶函数。
Vmap由Jax率先提出,它消除了算子对batch维度的限制,并提供更加方便、统一的运算符表达。
同时,用户还可以与 mindspore.grad() 等其它功能模块组合使用,提高开发效率。
此外,由于自动向量化并不在函数外部执行循环,而是将循环逻辑下沉至函数的各个原语操作中,
以获得更好的性能。当与图算融合特性相结合时,执行效率将进一步提高。
参数:
fn (Union[Cell, Function, CellList]) - 待沿参数轴映射的函数,该函数至少拥有一个输入参数并且返回值为一个或多个Tensor或Tensor支持的数据类型。当 fn 的类型是CellList时,为模型集成场景,需要确保每个单元的结构相同,并且单元数量与映射轴索引对应的size( axis_size )一致。
in_axes (Union[int, list, tuple]) - 指定输入参数映射的轴索引。如果 in_axes 是一个整数,则 fn 的所有输入参数都将根据此轴索引进行映射。 如果 in_axes 是一个tuple或list,仅支持由整数或None组成,则其长度应与 fn 的输入参数的个数一致,分别表示相应位置参数的映射轴索引。 请注意,每个参数对应的整数轴索引的取值范围必须在 [−ndim,ndim) 中,其中 ndim 是参数的维度。None表示不沿任何轴映射。并且 in_axes 中必须至少有一个位置参数的映射轴索引不为None。所有参数的映射轴索引对应的size( axis_size )必须相等。默认值: 0 。
out_axes (Union[int, list, tuple]) - 指定映射轴呈现在输出中的索引位置。如果 out_axes 是一个整数,则 fn 的所有输出都根据此axis指定。 如果 out_axes 是一个tuple或list,仅支持由整数或None组成,其长度应与 fn 的输出个数相等。 请注意,每个输出对应的整数轴索引的取值范围必须在 [−ndim,ndim)中,其中 ndim 是 vmap 映射后的函数的输出的维度。 所有具有非None映射轴的输出只能指定非None的 out_axes ,如果具有None映射轴的输出指定非None的 out_axes ,结果将沿映射轴进行广播。默认值: 0 。
返回:
Function,返回 fn 的自动向量化后的函数。此函数的输入参数和输出与 fn 的相对应,但它在 in_axes 和 out_axes 指定的位置新增了额外的批处理维度。
它支持的后端有:Ascend GPU CPU
8. 示例代码
import numpy as np
import time
from mindspore import ops, vmap
import mindspore as ms
def hswish_func(x):
return ops.HSwish()(x)
@ms.jit
def manually_batched(xs):
output = []
for i in range(xs.shape[0]):
output.append(hswish_func(xs[i]))
return ops.stack(output)
shape = (100, 2)
prop = 100
x_np = (np.random.randn(*shape) * prop).astype(np.float32)
x = ms.Tensor(x_np)
x = ops.sub(x, 0)
start_time = time.time()
output_vmap = vmap(hswish_func, in_axes=(0,))(x)
end_time = time.time()
print("vmap cost time:", end_time - start_time)
start_time = time.time()
output_manually = manually_batched(x)
end_time = time.time()
print("for loop cost time:", end_time - start_time)
用GPU跑结果:
与预期不太一致,vmap甚至用了更长时间,是不是这个也只适合CPU
用CPU跑:
用CPU跑的时候, vmap的时间比for用的时间短了非常多
综上所述, vmap提升性能更适用于CPU机器,其他机器,反而效果更差