多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

TVM 测试框架指南:使用 pytest Target 参数化在多个运行时上运行单元测试

TVM 测试框架指南:使用 pytest Target 参数化在多个运行时上运行单元测试 编译器深度学习模型优化【免费下载链接】tvmOpen deep learning compiler stack for cpu, gpu and specialized accelerators项目地址https://gitcode.com/gh_mirrors/tvm7/tvm点击查看免费下载导读TVM 是一套面向 CPU、GPU 及各类专用加速器的开源深度学习编译器栈。对于任意受支持的运行时TVM 都应产生数值正确的结果因此任何校验数值输出的单元测试都应覆盖所有受支持的运行时。本文围绕 docs/dev/how_to/pytest_target_parametrization.rst 展开系统讲解tvm.testing提供的 target 参数化辅助机制如何让一个测试函数自动展开为针对多个目标设备的多个参数化用例、如何在本地/Docker/CI 中运行这些用例以及底层 pytest 插件与标记mark体系的工作原理。读完本文你将掌握 TVM 官方推荐的单元测试编写范式并能在自己的开发环境中复现 CI 的测试选择逻辑。背景为什么需要 Target 参数化TVM 的核心承诺是同一份代码、同一套算子语义在不同硬件上获得正确的数值结果。为了守住这一承诺回归测试必须同时覆盖 llvmCPU、cuda、opencl、vulkan、metal、rocm、hexagon 等运行时。如果手写循环逐一执行会带来两个问题静默跳过通过循环遍历目标时被config.cmake禁用或缺少对应硬件的运行时会被静默跳过难以在测试报告中察觉失败定位模糊循环式测试会在第一个失败目标处中断无法判断错误是发生在某个特定目标上还是所有目标共有。因此TVM 在tvm.testing中提供了基于 pytest 参数化parametrization的辅助函数一个 Python 测试函数可以展开为多个参数化单元测试每个用例针对单一目标设备每个目标的通过/失败/跳过分别独立上报。一个测试要被真正执行必须同时满足以下全部条件源码逻辑见 python/tvm/testing/utils.py 与 python/tvm/testing/plugin.py该测试所在文件或目录已被传给pytest函数上应用显式或通过 target 参数化隐式应用的 pytest 标记必须与pytest -m表达式的过滤要求兼容使用targetfixture 的参数化测试其目标必须出现在环境变量TVM_TEST_TARGETS中使用targetfixture 的参数化测试其构建配置config.cmake必须启用了对应运行时。单元测试文件的写法显式参数化tvm.testing.parametrize_targets推荐的多目标测试方法是参数化。对于固定的一组目标用tvm.testing.parametrize_targets(target_1, target_2, ...)装饰函数并让函数接收target或dev参数# 显式列出要使用的目标 tvm.testing.parametrize_targets(llvm, cuda) def test_function(target, dev): # 测试代码 pass该函数会为列出的每个目标各运行一次且每个目标的成败独立上报。如果某个目标因在config.cmake中被禁用、或当前机器缺少相应硬件而无法运行则该目标对应的用例会显示为skipped而不是失败。从源码看parametrize_targets的实现非常轻量python/tvm/testing/utils.py#L1287-L1330当带参数使用时它等价于pytest.mark.parametrize(target, list(args), scopesession)即以session作用域对整个目标列表做参数化。隐式参数化接受target/dev参数即可对于应该在所有目标上运行的测试装饰器可以省略。任何接受target或dev参数的测试都会自动按照TVM_TEST_TARGETS中指定的全部目标进行参数化# 隐式按 TVM_TEST_TARGETS 环境变量中的全部目标参数化 def test_function(target, dev): # 测试代码 pass这一自动参数化由 pytest 插件在收集阶段完成。plugin.py中的pytest_generate_tests钩子会依次调用三个处理函数python/tvm/testing/plugin.py#L79-L89_parametrize_correlated_parameters处理tvm.testing.parameter/tvm.testing.parameters定义的相关参数_auto_parametrize_target如果测试函数使用了targetfixture 但没有任何显式parametrize标记则自动为其添加对target的参数化python/tvm/testing/plugin.py#L113-L148_add_target_specific_marks为每个目标用例补充对应的tvm.testing.requires_*标记。其中_auto_parametrize_target在收集时检查metafunc.fixturenames中是否包含target若无显式参数化则通过utils._get_targets()取得目标列表并为每个目标生成一个pytest.mark.parametrize标记。这也解释了为什么只要写上target/dev参数就自动多目标——这是插件层面的约定行为而非parametrize_targets装饰器本身的功能。裸装饰器形式显式强调参数化tvm.testing.parametrize_targets也可以不带参数、作为裸装饰器使用用于在代码中显式标注本测试按全部目标参数化。该形式本身没有额外效果自动参数化已经生效仅用于提高可读性并保持向后兼容# 显式表明按 TVM_TEST_TARGETS 中的全部目标参数化 tvm.testing.parametrize_targets def test_function(target, dev): # 测试代码 passparametrize_targets对无参调用的处理在 python/tvm/testing/utils.py#L1323-L1330当唯一的参数可调用即被装饰的函数本身时直接原样返回该函数。排除与已知失败exclude_targets/known_failing_targets在大多数应运行于全部目标、但个别目标存在特殊情况的场景中应使用以下两个装饰器更详细的使用场景说明见其 docstring# 从参数化目标中排除特定目标 tvm.testing.exclude_targets(cuda) def test_function(target, dev): # 测试代码 pass # 将特定目标标记为已知失败xfail tvm.testing.known_failing_targets(cuda) def test_function(target, dev): # 测试代码 pass实现上这两个装饰器并不直接添加 pytest 标记而是在被装饰函数对象上记录属性tvm_excluded_targets/tvm_known_failing_targets见 python/tvm/testing/utils.py#L1333-L1411exclude_targets在_auto_parametrize_target中被消费自动参数化时会把被排除的target_kind从目标列表中过滤掉known_failing_targets在_add_target_specific_marks中被消费当某个参数化目标的target_kind命中已知失败列表时为该用例追加pytest.mark.xfail(reasonKnown failing test for target ...)python/tvm/testing/plugin.py#L186-L194。多参数关联参数化有些场景需要在多个参数上同时参数化例如某个目标存在多个实现、需要分别测试时。可以显式地对元组参数列表做参数化此时只有显式列出的目标会运行但每个目标仍会被自动打上对应的tvm.testing.requires_RUNTIME标记pytest.mark.parametrize(target,impl, [ (llvm, cpu_implementation), (cuda, gpu_implementation_small_batch), (cuda, gpu_implementation_large_batch), ]) def test_function(target, dev, impl): # 测试代码 pass_add_target_specific_marks中的update_parametrize_target_arg会遍历所有显式parametrize标记只要参数名列表包含target就会把该参数化标记中与 target 对应的值替换为pytest.param(*values, marksrequires_*)形式从而为每个目标用例挂上正确的运行时要求标记python/tvm/testing/plugin.py#L151-L221。这里还隐含一个易错点参数值必须是以列表形式给出的参数集如果误用元组plugin.py会抛出带文件名与行号的TypeError提示。pytest 标记体系参数化功能构建在 pytest marks 之上。每个测试函数都可以用 pytest 标记附加元数据其中最常用的标记如下标记作用pytest.mark.gpu将函数标记为使用 GPU 能力。单独使用无效果可与命令行参数-m gpu或-m not gpu组合限制 pytest 只执行或不执行GPU 测试。通常作为其他标记的组成部分不单独直接使用。tvm.testing.uses_gpu应用pytest.mark.gpu。用于标记可能使用 GPU如果存在的测试。只有显式循环tvm.testing.enabled_targets()的旧式测试才需要显式加此装饰器使用tvm.testing.parametrize_targets()时GPU 目标会自动带上该标记无需显式应用。tvm.testing.requires_gpu应用tvm.testing.uses_gpu并额外通过pytest.mark.skipif在无 GPU 时整体跳过测试。tvm.testing.requires_RUNTIME一系列装饰器如tvm.testing.requires_cuda若指定运行时不可用则跳过测试。运行时不可用包括两种情形在config.cmake中被禁用或缺少兼容设备。对使用 GPU 的运行时该系列包含tvm.testing.requires_gpu。使用参数化 target 时每个测试用例都会自动挂上与其目标对应的tvm.testing.requires_RUNTIME标记。因此如果目标在config.cmake中被禁用或缺少硬件该用例会被明确列为 skipped。这套标记体系的底层是utils.py中的Feature类python/tvm/testing/utils.py#L529-L821。每个特性feature可以声明以下检查维度由Feature.marks()组合成实际生效的 pytest 标记序列cmake_flag构建时必须在config.cmake中启用的 CMake 开关如USE_CUDA、USE_LLVM通过tvm.support.libinfo()读取构建信息禁用则追加skipiftarget_kind_enabled目标种类必须出现在TVM_TEST_TARGETS或默认目标列表中target_kind_hardware必须存在对应设备检查tvm.device(kind).existcompile_time_check/run_time_check编译期如 nvcc 版本与运行期如 GPU 算力自定义检查返回字符串时直接作为跳过原因parent_features特性继承如 cuDNN 依赖 CUDA被依赖特性除target_kind_enabled外的检查会一并继承。标记支持三种support_required模式compile-and-run默认编译与运行条件都要满足、compile-only仅编译期检查、optional只打标记、不跳过用于兼容旧式enabled_targets()风格。仓库中已预置大量特性例如requires_llvm、requires_cuda、requires_rocm、requires_vulkan、requires_metal、requires_opencl、requires_hexagon、requires_cudnn、requires_cublas、requires_nvptx、requires_micro等python/tvm/testing/utils.py#L844-L1045。plugin.py的pytest_configure会在收集前把这些特性的标记名全部注册到 pytestpython/tvm/testing/plugin.py#L64-L72。旧式写法不推荐enabled_targets()tvm.testing.enabled_targets()会返回当前机器上已启用且可运行的全部目标以(target, device)对的形式其判定综合了TVM_TEST_TARGETS环境变量、构建配置与物理硬件。现有测试大多仍显式循环该函数返回值但新测试不应再使用这种风格原因有二循环式测试会静默跳过config.cmake中禁用或无设备可运行的运行时报告中看不出跳过循环会在第一个失败目标处中断无法判断错误是特定目标独有还是所有目标共有。# 旧式写法请勿在新测试中使用 def test_function(): for target, dev in tvm.testing.enabled_targets(): # 测试代码 pass从实现看enabled_targets()基于_get_targets()python/tvm/testing/utils.py#L405-L451对每个候选目标计算is_enabledtvm.runtime.enabled(kind)或libinfo()中的开关与is_runnableis_enabled and tvm.device(kind).exist仅返回is_runnable的目标。若全部目标都不可运行会回退到仅运行llvm若连llvm也未启用则抛出TVMError。本地运行在${TVM_HOME}目录下直接执行pytest即可运行 Python 单元测试。环境变量TVM_TEST_TARGETS以分号分隔的目标列表决定参数化测试覆盖哪些目标。未设置时默认使用tvm.testing.DEFAULT_TEST_TARGETS。仓库中的默认目标列表python/tvm/testing/utils.py#L454-L465DEFAULT_TEST_TARGETS [ llvm, cuda, nvptx, vulkan -from_device0, opencl, opencl -devicemali,aocl_sw_emu, opencl -deviceintel_graphics, metal, rocm, hexagon, ]注意两点目标串可以携带设备相关参数如opencl -deviceintel_graphics、vulkan -from_device0目标种类为第一个空格前的部分如果TVM_TEST_TARGETS中没有任何既已启用、又有可用设备的目标测试会自动回退到仅运行llvm目标。该回退逻辑与对应的警告日志位于_get_targets()中若连llvm也未启用则会抛出TVMError提示尝试设置TVM_TEST_TARGETS为受支持目标。_tvm_test_targets()python/tvm/testing/utils.py#L1117-L1124在解析时会去重并保持用户指定的顺序。TVM_LIBRARY_PATH指向libtvm.so库文件的路径。例如可用它指向 debug 构建的库来运行测试。未设置时会相对于 TVM 源码目录搜索libtvm.so。命令行参数传入文件或目录路径只运行该文件/目录中的单元测试。例如在未安装特定前端frontend的机器上可以通过只传tests/python/frontend之外的路径来避免执行该目录下的测试。-m参数仅运行带指定 pytest 标记的测试。最常见的是-m gpu只运行标记了pytest.mark.gpu、需要 GPU 的测试或-m not gpu只运行不使用 GPU 的测试。一个容易踩坑的细节-m过滤发生在基于TVM_TEST_TARGETS的目标选择之后。即使指定了-m gpu如果TVM_TEST_TARGETS中没有包含 GPU 目标也不会运行任何 GPU 测试。这一过滤顺序在 python/tvm/testing/plugin.py 的pytest_sessionfinish中也有体现当-m表达式过滤后没有收集到任何测试时pytest 不会以未收集到测试的错误状态退出。在本地 Docker 容器中运行docker/bash.sh脚本仓库根目录下的 docker/bash.sh可以在与 CI 相同的 docker 镜像中运行单元测试# 第一个参数指定要使用的镜像如 ci_gpu docker/bash.sh ci_gpu允许的镜像名定义在 TVM 源码目录中 Jenkinsfile 的顶部并映射到 tlcpack 组织发布的 docker 镜像相关镜像构建文件见 docker/Dockerfile.ci_cpu、docker/Dockerfile.ci_gpu 等。如果不传额外参数会进入镜像内的交互式 bash 会话如果传入脚本作为可选参数则该脚本会在镜像内执行例如docker/bash.sh ci_gpu tests/scripts/task_python_unittest.sh需要特别注意的是docker 镜像包含全部系统依赖但并未内置build/config.cmake配置文件。TVM 源码目录被挂载为镜像的家目录因此默认会使用本地已有的 config/build 目录。一个常见做法是分别维护build_local和build_docker两个目录进入/退出 docker 时通过符号链接将build指向对应目录。在 CI 中运行CI 中所有环节都从 Jenkinsfile 中的任务定义出发包括使用哪个 docker 镜像、编译期配置是什么、哪些测试进入哪些阶段。相关定义位于 ci/jenkins/generated生成的 Jenkinsfile如cpu_jenkinsfile.groovy、gpu_jenkinsfile.groovy与 ci/jenkins/templates生成模板如 ci/jenkins/templates/cpu_jenkinsfile.groovy.j2。Docker 镜像Jenkinsfile 中的每个任务如BUILD: CPU都会调用docker/bash.sh。与本地用法一致docker/bash.sh后面的参数定义了 CI 中使用的镜像。例如 ci/jenkins/templates/utils/Build.groovy.j2 中Python 单测任务会以指定镜像执行./tests/scripts/task_python_unittest.sh。编译期配置docker 镜像中没有内置config.cmake因此它是每个BUILD任务的第一步工作通过tests/scripts/task_config_build_*.sh脚本完成例如task_config_build_gpu.sh、task_config_build_cpu.sh等见 tests/scripts 目录。具体使用哪个脚本取决于被测试的构建类型由 Jenkinsfile 指定。每个BUILD任务最后会把编译好的库打包供后续测试阶段使用。测试运行Jenkinsfile 的Unit Test与Integration Test阶段决定pytest的调用方式。每个任务先解包BUILD阶段编译好的库然后运行测试脚本如tests/scripts/task_python_unittest.sh。这些脚本设置了传给pytest的文件/目录与命令行选项。CI 脚本对TVM_TEST_TARGETS与-m gpu的组合使用是理解整个体系的最佳范例见 tests/scripts 目录tests/scripts/task_python_unittest_gpuonly.sh 中export TVM_TEST_TARGETScuda;opencl;metal;rocm;nvptx;opencl -devicemali,aocl_sw_emu并设置PYTEST_ADDOPTS-m gpu只运行 GPU 标记的测试后续还用TVM_TEST_TARGETSvulkan -from_device0单独覆盖 Vulkan 设备tests/scripts/task_python_integration_gpuonly.sh 中TVM_TEST_TARGETScuda;opencl;metal;rocm;nvptx;opencl -devicemali,aocl_sw_emu,adreno配合-m gputests/scripts/task_python_frontend.sh 使用TVM_TEST_TARGETSllvm;cuda而纯 CPU 的前端任务 tests/scripts/task_python_frontend_cpu.sh 使用TVM_TEST_TARGETSllvmtests/scripts/task_python_adreno.sh 针对 Adreno GPU 将TVM_TEST_TARGETS设为opencl。多个 CI 脚本都包含-m gpu选项用于将测试范围限制为带pytest.mark.gpu标记的用例。其他测试工具速览tvm.testing包python/tvm/testing/init.py除 target 参数化外还提供一组与 pytest 深度集成的辅助设施写作单元测试时经常会用到tvm.testing.parameter/tvm.testing.parameters定义参数化 fixture。前者是多个参数全组合后者是多个参数按组对齐每组值只运行一次。二者均以session作用域运行适合无设置成本的参数字符串、整数、元组等tvm.testing.fixture定义带设置成本的 fixture可通过cache_return_valueTrue在测试间缓存返回值可用环境变量TVM_TEST_DISABLE_CACHE强制关闭缓存tvm.testing.assert_allclose带默认atol/rtol的数值比较工具tvm.testing.CompareBeforeAfter编写 TIR 变换测试的基类通过定义before/transform/expected成员即可自动生成测试pytest_plugins [tvm.testing.plugin]在conftest.py中声明该行即可在 TVM 测试目录之外复用这套参数化与标记体系python/tvm/testing/plugin.py#L19-L33。总结TVM 的 target 参数化机制把多运行时覆盖从手动循环变成了 pytest 原生参数化一个测试函数只需声明target/dev参数pytest 插件就会自动按TVM_TEST_TARGETS展开用例并为每个用例挂上对应的requires_*标记使禁用或缺失硬件的目标被显式跳过。撰写新测试时优先采用target/dev参数配合exclude_targets、known_failing_targets的表达方式避免使用enabled_targets()循环的旧式写法运行测试时则通过TVM_TEST_TARGETS、-m与config.cmake三者的配合精确控制覆盖范围——这正是 TVM 官方 CI 在 CPU、GPU 与各类专用硬件节点上复用同一套测试套件的核心机制。赞分享编译器深度学习模型优化【免费下载链接】tvmOpen deep learning compiler stack for cpu, gpu and specialized accelerators项目地址https://gitcode.com/gh_mirrors/tvm7/tvm点击查看免费下载相关推荐TVM 测试指南Python 单元测试的 Target 参数化、本地运行与 CI 集成实战TVM 测试指南Python 单元测试的 Target 参数化、本地运行与 CI 集成实战 导读 本篇指南围绕 Apache TVMOpen Machine模型编译深度学习推理引擎Bash-it 单元测试指南使用 Bats 框架运行与编写测试Bash it 单元测试指南使用 Bats 框架运行与编写测试 本指南以 Bash it 仓库中的测试文档 docs/test.rst https://liCLIngxtop函数测试自动化使用pytest-xdist并行运行测试ngxtop函数测试自动化使用pytest xdist并行运行测试 在现代软件开发中测试自动化是保障代码质量的关键环节。ngxtop作为一款实时监控Ngin运维可观测性CLI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表