| # Custom Vulkan/SPIR-V Dispatch Shaders | |
| See the [custom_dispatch README](/samples/custom_dispatch/README.md) for an | |
| overview of this approach. | |
| This sample demonstrates how to define external device functions that can be | |
| dispatched from within IREE programs when following the IREE Vulkan/SPIR-V ABI. | |
| The user authoring the shaders compiles their GLSL/HLSL/etc code to SPIR-V blobs | |
| and can dispatch functions within those blobs by declaring them in their IR. | |
| ### Work in Progress | |
| Note that currently only entire dispatches can be modeled and this prevents | |
| IREE from performing optimizations it otherwise can. In the future SPIR-V | |
| linking will be implemented such that the external functions are referenced and | |
| linked with the compiler-produced portions such that more information about the | |
| usage of the dispatch can be used to specialize/prune the hand-authored | |
| portions. Since the IREE Vulkan/SPIR-V ABI is not version-stable this entire | |
| shader approach may require updating when taking new IREE versions while | |
| function-level linking would not. | |
| Since today only entire shaders can be provided the user must specify an empty | |
| executable (no `builtin.module` contents) and thus must provide objects for | |
| all targets they are compiling for. When partial function linking is available | |
| it'll be possible to provide fallback code as IR for when objects are not | |
| available. | |
| ## Workflow | |
| ``` | |
| +--------------+ | |
| | example.mlir | | |
| +--------------+ | |
| +-----------------+ +----------------+ v | |
| | simple_mul.glsl | -> glslc -> | simple_mul.spv | ------> iree-compile | |
| +-----------------+ +----------------+ v | |
| +--------------+ | |
| | example.vmfb | | |
| +--------------+ | |
| ``` | |
| 1. The user authors their shaders in a .glsl (.hlsl/etc) file. | |
| ```c | |
| #version 450 | |
| layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; | |
| layout(set = 0, binding = 0) readonly buffer Binding0 { float binding0[]; }; | |
| layout(set = 0, binding = 1) readonly buffer Binding1 { float binding1[]; }; | |
| layout(set = 0, binding = 2) buffer Binding2 { float binding2[]; }; | |
| layout(push_constant) uniform PushConstants { uint dim; }; | |
| void main() { | |
| uint tid = gl_GlobalInvocationID.x; | |
| if (tid < dim) binding2[tid] = binding0[tid] * binding1[tid]; | |
| } | |
| ``` | |
| 2. Source files are compiled to SPIR-V files via glslc. Each architecture or | |
| set of extensions the user is targeting will need its own object file(s). | |
| ```cmake | |
| glslc -fshader-stage=compute simple_mul.glsl -o simple_mul.spv | |
| ``` | |
| 3. The user (or compiler transforms) declare the externally defined shaders and | |
| the target-to-objects map for each configuration. The layout specifies how | |
| the dispatch arguments are mapped to buffers. The region on the export is | |
| used to compute the workgroup count and can query the `%device` if runtime | |
| device information is needed. | |
| ```mlir | |
| hal.executable.source private @executable attributes { | |
| objects = #hal.executable.objects<{ | |
| #spirv_target = [ | |
| #hal.executable.object<{path = "simple_mul.spv"}> | |
| ] | |
| }> | |
| hal.executable.export public @simple_mul ordinal(0) | |
| layout(#hal.pipeline.layout<push_constants = 1, sets = [ | |
| <0, bindings = [ | |
| <0, storage_buffer, ReadOnly>, | |
| <1, storage_buffer, ReadOnly>, | |
| <2, storage_buffer> | |
| ]> | |
| ]>) { | |
| ^bb0(%device: !hal.device, %workload: index): | |
| %x = affine.apply affine_map<()[s0] -> (s0 ceildiv 64)>()[%workload] | |
| %c1 = arith.constant 1 : index | |
| hal.return %x, %c1, %c1 : index, index, index | |
| } | |
| } | |
| ``` | |
| 4. The user (or compiler transforms) dispatches the shader. | |
| ```mlir | |
| %0 = flow.dispatch @executable::@simple_mul[%dim](%dim_i32, %arg0, %arg1) : | |
| (i32, tensor<?xf32>{%dim}, tensor<?xf32>{%dim}) -> tensor<?xf32>{%dim} | |
| ``` | |
| 5. The IREE compiler selects the appropriate object files for the target | |
| configuration and links them into the binaries it produces. Dispatches are | |
| automatically routed to the appropriate variant of those available at | |
| runtime. | |
| ## Instructions | |
| This presumes that `iree-compile` and `iree-run-module` have been installed or | |
| built. [See here](https://iree-org.github.io/iree/building-from-source/getting-started/) | |
| for instructions for CMake setup and building from source. | |
| 0. Ensure that `glslc` is on your PATH (comes with the [Vulkan SDK](https://vulkan.lunarg.com/sdk/home)): | |
| ``` | |
| glslc --version | |
| ``` | |
| 1. Build the `iree-sample-deps` CMake target to compile the GLSL to SPIR-V: | |
| ``` | |
| cmake --build ../iree-build/ --target iree-sample-deps | |
| ``` | |
| In a user application this would be replaced with whatever build | |
| infrastructure the user has for compiling shaders to SPIR-V. No IREE | |
| compiler or runtime changes are required and the normal compiler install can | |
| be used. | |
| 2. Compile the [example module](./example.mlir) to a .vmfb file and pass the | |
| path to the build directory so the .spv files can be found: | |
| ``` | |
| iree-compile \ | |
| --iree-hal-executable-object-search-path=../iree-build/ \ | |
| samples/custom_dispatch/vulkan/shaders/example.mlir \ | |
| -o=/tmp/example.vmfb | |
| ``` | |
| 3. Run the example program using the custom shaders: | |
| ``` | |
| iree-run-module \ | |
| --device=vulkan \ | |
| --function=mixed_invocation \ | |
| --input=8xf32=2 \ | |
| --input=8xf32=4 \ | |
| /tmp/example.vmfb | |
| ``` |