运行 来自 cmake 的具有最佳编译器标志和配置的 Halide 生成器
Running Halide generators from cmake with the most optimal compiler flags and configurations
好的,所以:我已经成功集成了第一个工作Halide generator into the cmake build system for my little image-processing project。
生成器基于 Halide 代码库中的示例代码实现了图像大小调整和重采样算法 – Halide/apps/resize/resize.cpp
– 我调整了样本以利用生成器参数,并绑定了生成器的编译并使用 HalideGenerator.cmake
中定义的函数调用我的 cmake 脚本,就像 Halide 项目在其自己的构建脚本中所做的那样。
到目前为止,所有这一切都很好——但我在代码生成细微差别领域缺乏专业知识。例如,我调整了调度方法以在我的笔记本电脑上获得最佳观察到的经验速度——但是尽管进行了许多长时间的修补会话和代码阅读逗留在 Halide 的许多与生成器相关的深度 tools and scripts,我只有对代码生成过程最肤浅的理解。
具体来说,我不知道该如何处理。最好是使用默认值还是尝试为我的目标平台打开特定选项——如果是后者,我是否必须在某处有条件代码,或者二进制文件是否可以包含回退?
这就是我所说的:在 Halide 教程第 15 课的源代码中,有一个复杂的脚本可以调用具有各种选项的生成器。这是此脚本中代码注释的片段:
# If you're compiling and linking multiple Halide pipelines, then the
# multiple copies of the runtime should combine into a single copy
# (via weak linkage). If you're compiling and linking for multiple
# different targets (e.g. avx and non-avx), then the runtimes might be
# different, and you can't control which copy of the runtime the
# linker selects.
# You can control this behavior explicitly by compiling your pipelines
# with the no_runtime target flag. Let's generate and link several
# different versions of the first pipeline for different x86 variants: [snip]
…由此很难区分什么必须完成,什么应该完成,或者什么可以酌情完成。相比之下,在设置 C++ 或 Objective-C 项目(甚至更多拜占庭示例)时不必处理这些问题,因为编译器和链接器会为您做出大部分这些决定,最多需要一两个标志。
我的问题是:如何将 Halide 生成器的输出库二进制文件集成到我现有的项目中——这样生成器输出尽可能快(例如使用 GPU、SSE2/3、AVX2 等)没有进一步限制可移植性(例如,它不会在稍微不同的机器上神秘地发生段错误)?
具体来说,我的流程应该是什么——例如,我是否应该首先只针对最低公分母,然后逐步利用更多奇特的处理器功能?
编辑:正如我在下面的评论中提到的,这是我的 GenGen 二进制输出到 stdout
在没有选项的情况下调用的内容:
对于预生成二进制文件 (AOT) 的情况,听起来您需要运行时分派。您的程序将在启动时检查 CPU/GPU 环境并决定应使用哪些功能(AVX、OpenCL 等)。这不是卤化物特有的。
- Select 一组作为最佳案例的高级功能(高性能桌面 GPU)和一组适用于每台机器的最小功能(仅限 SSE2)。
- 为每个包含每个需要性能的功能的功能集构建一个 DLL/dylib/so。这些可以以不同的方式安排,甚至可以使用完全不同的
Func
定义来构建。您可以将两个集合放在同一个源代码文件中,并在生成时测试 Target
对象以在它们之间进行选择。
- 在程序启动时,查看您的最佳案例功能是否存在,如果存在,加载该库并使用它。如果缺少任何功能,默认为最兼容的版本。
您可以自由选择要支持的功能集和库的数量。
另一种方法是在程序启动 (JIT) 时编译 Halide 代码。我建议使用 get_jit_target_from_environment()
返回的 Target
对象,它使用环境变量 HL_JIT_TARGET
或 "host" 的内容(如果未设置该变量)。 "host" 目标字符串与 get_host_target()
相同,意味着 Halide 将检查 CPU/GPU 环境并设置它找到的任何功能。然后,您可以动态测试 Target
对象并使用 GPU 或 CPU 调度。
最近,AOT 编译现在支持通过运行时检测为多个 CPU 功能生成自定义。只需使用带有逗号分隔的目标列表的 GenGen 和 static_library 作为输出,例如
GenGen -e static_library,h target=x86-64-linux-sse41-avx,x86-64-linux-sse41,x86-64-linux
这将生成一个 .a 文件,其中包含:
- 3 个版本的代码,使用 AVX+SSE4.1、SSE4.1 和 plain-old-x86-64 的特化编译
- 一个使用 halide_can_use_target_features() 运行时调用为实际目标机器选择正确包装的瘦包装器
有关详细信息,请参阅 Func::compile_to_multitarget_static_library() 和 multitarget_generator.cpp/multitarget_aottest.cpp。
好的,所以:我已经成功集成了第一个工作Halide generator into the cmake build system for my little image-processing project。
生成器基于 Halide 代码库中的示例代码实现了图像大小调整和重采样算法 – Halide/apps/resize/resize.cpp
– 我调整了样本以利用生成器参数,并绑定了生成器的编译并使用 HalideGenerator.cmake
中定义的函数调用我的 cmake 脚本,就像 Halide 项目在其自己的构建脚本中所做的那样。
到目前为止,所有这一切都很好——但我在代码生成细微差别领域缺乏专业知识。例如,我调整了调度方法以在我的笔记本电脑上获得最佳观察到的经验速度——但是尽管进行了许多长时间的修补会话和代码阅读逗留在 Halide 的许多与生成器相关的深度 tools and scripts,我只有对代码生成过程最肤浅的理解。
具体来说,我不知道该如何处理。最好是使用默认值还是尝试为我的目标平台打开特定选项——如果是后者,我是否必须在某处有条件代码,或者二进制文件是否可以包含回退?
这就是我所说的:在 Halide 教程第 15 课的源代码中,有一个复杂的脚本可以调用具有各种选项的生成器。这是此脚本中代码注释的片段:
# If you're compiling and linking multiple Halide pipelines, then the
# multiple copies of the runtime should combine into a single copy
# (via weak linkage). If you're compiling and linking for multiple
# different targets (e.g. avx and non-avx), then the runtimes might be
# different, and you can't control which copy of the runtime the
# linker selects.
# You can control this behavior explicitly by compiling your pipelines
# with the no_runtime target flag. Let's generate and link several
# different versions of the first pipeline for different x86 variants: [snip]
…由此很难区分什么必须完成,什么应该完成,或者什么可以酌情完成。相比之下,在设置 C++ 或 Objective-C 项目(甚至更多拜占庭示例)时不必处理这些问题,因为编译器和链接器会为您做出大部分这些决定,最多需要一两个标志。
我的问题是:如何将 Halide 生成器的输出库二进制文件集成到我现有的项目中——这样生成器输出尽可能快(例如使用 GPU、SSE2/3、AVX2 等)没有进一步限制可移植性(例如,它不会在稍微不同的机器上神秘地发生段错误)?
具体来说,我的流程应该是什么——例如,我是否应该首先只针对最低公分母,然后逐步利用更多奇特的处理器功能?
编辑:正如我在下面的评论中提到的,这是我的 GenGen 二进制输出到 stdout
在没有选项的情况下调用的内容:
对于预生成二进制文件 (AOT) 的情况,听起来您需要运行时分派。您的程序将在启动时检查 CPU/GPU 环境并决定应使用哪些功能(AVX、OpenCL 等)。这不是卤化物特有的。
- Select 一组作为最佳案例的高级功能(高性能桌面 GPU)和一组适用于每台机器的最小功能(仅限 SSE2)。
- 为每个包含每个需要性能的功能的功能集构建一个 DLL/dylib/so。这些可以以不同的方式安排,甚至可以使用完全不同的
Func
定义来构建。您可以将两个集合放在同一个源代码文件中,并在生成时测试Target
对象以在它们之间进行选择。 - 在程序启动时,查看您的最佳案例功能是否存在,如果存在,加载该库并使用它。如果缺少任何功能,默认为最兼容的版本。
您可以自由选择要支持的功能集和库的数量。
另一种方法是在程序启动 (JIT) 时编译 Halide 代码。我建议使用 get_jit_target_from_environment()
返回的 Target
对象,它使用环境变量 HL_JIT_TARGET
或 "host" 的内容(如果未设置该变量)。 "host" 目标字符串与 get_host_target()
相同,意味着 Halide 将检查 CPU/GPU 环境并设置它找到的任何功能。然后,您可以动态测试 Target
对象并使用 GPU 或 CPU 调度。
最近,AOT 编译现在支持通过运行时检测为多个 CPU 功能生成自定义。只需使用带有逗号分隔的目标列表的 GenGen 和 static_library 作为输出,例如
GenGen -e static_library,h target=x86-64-linux-sse41-avx,x86-64-linux-sse41,x86-64-linux
这将生成一个 .a 文件,其中包含:
- 3 个版本的代码,使用 AVX+SSE4.1、SSE4.1 和 plain-old-x86-64 的特化编译
- 一个使用 halide_can_use_target_features() 运行时调用为实际目标机器选择正确包装的瘦包装器
有关详细信息,请参阅 Func::compile_to_multitarget_static_library() 和 multitarget_generator.cpp/multitarget_aottest.cpp。