Repository navigation
refactor(ISA): generate compute kernels from a single input file #395
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ahuber21
wants to merge
8
commits into
main
Choose a base branch
from
dev/improve-isa-dispatching
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
bad2f64
refactor(ISA): generate compute kernels from a single input file
ahuber21 0815eab
reset test utils.h
ahuber21 6a8eaa8
fix template comments
ahuber21 417761a
cleanup
ahuber21 f8f9500
fixup: add validate surface file which sets some required variables
ahuber21 a786630
fixup: don't change build string
ahuber21 3dc50de
improve comments
ahuber21 64cd9b8
include header where it is used
ahuber21 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # Copyright 2026 Intel Corporation | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| ## | ||
| ## This is a cmake helper to split a spec provided as `a|b|c` | ||
| ## into three fields `[a, b, c]` | ||
| ## | ||
|
|
||
| include_guard(GLOBAL) | ||
|
|
||
| # Scoped this file via PUSH/POP so it does not affect the includer | ||
| cmake_policy(PUSH) | ||
| cmake_policy(SET CMP0007 NEW) | ||
|
|
||
| # Splits `spec` on '|' into `n` fields | ||
| function(svs_split_fields spec n) | ||
| string(REPLACE "|" ";" fields "${spec}") | ||
| list(LENGTH fields n_fields) | ||
| if(NOT n_fields EQUAL n) | ||
| message(FATAL_ERROR "Malformed spec '${spec}': expected ${n} '|'-separated fields, got ${n_fields}.") | ||
| endif() | ||
| math(EXPR last_index "${n} - 1") | ||
| foreach(i RANGE 0 ${last_index}) | ||
| list(GET fields ${i} value) | ||
| list(GET ARGN ${i} out_var) | ||
| set(${out_var} "${value}" PARENT_SCOPE) | ||
| endforeach() | ||
| endfunction() | ||
|
|
||
| # e.g. svs_parse_isa_level("AVX2|haswell|avx2" level arch infix) sets | ||
| # level=AVX2, arch=haswell, infix=avx2 | ||
| function(svs_parse_isa_level spec out_level out_arch out_infix) | ||
| svs_split_fields("${spec}" 3 level arch infix) | ||
| set(${out_level} "${level}" PARENT_SCOPE) | ||
| set(${out_arch} "${arch}" PARENT_SCOPE) | ||
| set(${out_infix} "${infix}" PARENT_SCOPE) | ||
| endfunction() | ||
|
|
||
| # e.g. svs_parse_tu_spec("avx2.cpp|AVX2|haswell|avx2" src level arch infix) sets | ||
| # src=avx2.cpp, level=AVX2, arch=haswell, infix=avx2 | ||
| function(svs_parse_tu_spec spec out_src out_level out_arch out_infix) | ||
| svs_split_fields("${spec}" 4 src level arch infix) | ||
| set(${out_src} "${src}" PARENT_SCOPE) | ||
| set(${out_level} "${level}" PARENT_SCOPE) | ||
| set(${out_arch} "${arch}" PARENT_SCOPE) | ||
| set(${out_infix} "${infix}" PARENT_SCOPE) | ||
| endfunction() | ||
|
|
||
| cmake_policy(POP) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| # Copyright 2026 Intel Corporation | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| ##### | ||
| ##### The dispatch surface for the x86 distance kernels. | ||
| ##### | ||
| ##### This file is the single place the extent list and the ISA levels are | ||
| ##### written down. Everything derived from them is generated: | ||
| ##### | ||
| ##### - svs/core/distance/dispatch_surface.h, which drives every `extern | ||
| ##### template` and explicit instantiation, and `supported_dim_list` | ||
| ##### - the object library each ISA level's translation unit is compiled into, | ||
| ##### and the instruction budget it is compiled at | ||
| ##### | ||
| ##### Edit this file. The header is not in the source tree at all: it is written | ||
| ##### into the build directory on every configure and installed from there. | ||
| ##### | ||
| ##### A build may point somewhere else with -DSVS_DISPATCH_SURFACE_FILE=<file>, | ||
| ##### in which case only that build tree describes the overridden surface. | ||
| ##### | ||
| ##### Bookkeeping | ||
| ##### | ||
| ##### To add or remove a fixed extent: | ||
| ##### Edit `SVS_SUPPORTED_DIMS`. The generated header, every `extern | ||
| ##### template`, and `supported_dim_list` follow automatically. | ||
| ##### | ||
| ##### To add an ISA level: | ||
| ##### 1. Add a row to `SVS_ISA_LEVELS`. | ||
| ##### 2. Add a `SVS_TYPE_PAIRS_<enumerator>` list in | ||
| ##### include/svs/multi-arch/x86/preprocessor.h, listing the element-type | ||
| ##### pairs that level has kernels for. | ||
| ##### 3. Add the level's translation unit at | ||
| ##### include/svs/multi-arch/x86/<infix>.cpp. | ||
| ##### The object library and its compile flags follow from the row here. A | ||
| ##### level without a type-pair list is a compile error, not an empty | ||
| ##### instantiation set. | ||
| ##### | ||
| ##### To add or remove an element-type pair for a level: | ||
| ##### Edit that level's `SVS_TYPE_PAIRS_<enumerator>` list in | ||
| ##### include/svs/multi-arch/x86/preprocessor.h. Not configured here: a type | ||
| ##### pair exists because an implementation exists for it. | ||
| ##### | ||
| ##### To change a level's instruction budget: | ||
| ##### Edit the middle field of its row in `SVS_ISA_LEVELS`, observing the | ||
| ##### constraint recorded there. | ||
| ##### | ||
|
|
||
|
|
||
| ## | ||
| ## List of supported dimensions; kernels will be available for these dimensions | ||
| ## If used with dimensions not in this list, compute will be dispatched to | ||
| ## svs::Dynamic, which is automatically added to the list of supported dims. | ||
| ## | ||
|
|
||
| set(SVS_SUPPORTED_DIMS 64 96 100 128 160 200 512 768) | ||
|
|
||
| ## | ||
| ## ISA mapping | ||
| ## | ||
| ## All compute kernels will be compiled for the architectures below | ||
| ## NONE is not added; it only serves as a fallback and it will compile with | ||
| ## the flags set by the consumer. | ||
| ## Format is "ENUM|arch|infix", where | ||
| ## - ENUM is the AVX_AVAILABILITY value from distance_core.h | ||
| ## - arch is the -march flag passed to the compiler | ||
| ## - infix is a **unique** string used for object and library names | ||
|
|
||
| set(SVS_ISA_LEVELS | ||
| "AVX2|haswell|avx2" | ||
| "AVX512|cascadelake|avx512" | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| # Copyright 2026 Intel Corporation | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| ##### | ||
| ##### Derives the dispatch surface declared in cmake/dispatch-surface.cmake. | ||
| ##### | ||
| ##### Defines svs_generate_dispatch_surface(), which produces: | ||
| ##### <build>/generated/include/svs/core/distance/dispatch_surface.h (build tree only) | ||
| ##### SVS_DISPATCH_TU_SPECS -- "<src>|<level>|<arch>|<infix>", one per ISA level | ||
| ##### | ||
| ##### cmake/multi-arch.cmake calls the function and receives both. | ||
| ##### | ||
|
|
||
| include_guard(GLOBAL) | ||
|
|
||
| include("${CMAKE_CURRENT_LIST_DIR}/dispatch-levels.cmake") | ||
|
|
||
| set(SVS_DISPATCH_GEN_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}") | ||
|
|
||
| set(SVS_DEFAULT_DISPATCH_SURFACE_FILE "${CMAKE_CURRENT_LIST_DIR}/dispatch-surface.cmake") | ||
| set(SVS_DISPATCH_SURFACE_FILE "${SVS_DEFAULT_DISPATCH_SURFACE_FILE}" | ||
| CACHE FILEPATH | ||
| "Declaration of the ahead-of-time distance-kernel dispatch surface" | ||
| ) | ||
|
|
||
| ##### | ||
| ##### Read the declaration | ||
| ##### | ||
|
|
||
| function(svs_dispatch_read_declaration surface_file x86_src_dir | ||
| out_supported_dims out_dim_list out_dim_count out_isa_levels) | ||
| set(SVS_DISPATCH_SURFACE_FILE "${surface_file}") | ||
| set(SVS_X86_SRC_DIR "${x86_src_dir}") | ||
| include("${SVS_DISPATCH_GEN_CMAKE_DIR}/validate-dispatch-surface.cmake") | ||
| set(${out_supported_dims} "${SVS_SUPPORTED_DIMS}" PARENT_SCOPE) | ||
| set(${out_dim_list} "${SVS_DIM_LIST}" PARENT_SCOPE) | ||
| set(${out_dim_count} "${SVS_DIM_COUNT}" PARENT_SCOPE) | ||
| set(${out_isa_levels} "${SVS_ISA_LEVELS}" PARENT_SCOPE) | ||
| endfunction() | ||
|
|
||
| ##### | ||
| ##### Build the macro bodies | ||
| ##### | ||
|
|
||
| function(svs_dispatch_build_expansions dim_list isa_levels | ||
| out_dim_loop out_target_loop out_level_loop out_level_defines) | ||
| set(dim_loop "\\\n") | ||
| foreach(dim IN LISTS dim_list) | ||
| string(APPEND dim_loop " M(${dim}) \\\n") | ||
| endforeach() | ||
| string(APPEND dim_loop " /* end */") | ||
|
|
||
| set(target_loop "\\\n") | ||
| set(level_loop "\\\n") | ||
| set(level_defines "") | ||
| foreach(level_spec IN LISTS isa_levels) | ||
| svs_parse_isa_level("${level_spec}" level _ _) | ||
| string(APPEND level_loop " M(${level}) \\\n") | ||
| string(APPEND level_defines "#define SVS_ISA_LEVEL_${level} 1\n") | ||
| foreach(dim IN LISTS dim_list) | ||
| string(APPEND target_loop " M(${dim}, ${level}) \\\n") | ||
| endforeach() | ||
| endforeach() | ||
| string(APPEND target_loop " /* end */") | ||
| string(APPEND level_loop " /* end */") | ||
| string(STRIP "${level_defines}" level_defines) | ||
|
|
||
| set(${out_dim_loop} "${dim_loop}" PARENT_SCOPE) | ||
| set(${out_target_loop} "${target_loop}" PARENT_SCOPE) | ||
| set(${out_level_loop} "${level_loop}" PARENT_SCOPE) | ||
| set(${out_level_defines} "${level_defines}" PARENT_SCOPE) | ||
| endfunction() | ||
|
|
||
| ##### | ||
| ##### Derive the translation-unit specs | ||
| ##### | ||
|
|
||
| function(svs_dispatch_derive_tu_specs isa_levels x86_src_dir | ||
| out_tu_specs out_levels out_level_report) | ||
| set(tu_specs) | ||
| set(levels) | ||
| set(level_report) | ||
| foreach(level_spec IN LISTS isa_levels) | ||
| svs_parse_isa_level("${level_spec}" level arch infix) | ||
| list(APPEND tu_specs "${x86_src_dir}/${infix}.cpp|${level}|${arch}|${infix}") | ||
| list(APPEND levels "${level}") | ||
| list(APPEND level_report "AVX_AVAILABILITY::${level} -march=${arch} ${infix}.cpp") | ||
| endforeach() | ||
| set(${out_tu_specs} "${tu_specs}" PARENT_SCOPE) | ||
| set(${out_levels} "${levels}" PARENT_SCOPE) | ||
| set(${out_level_report} "${level_report}" PARENT_SCOPE) | ||
| endfunction() | ||
|
|
||
| ##### | ||
| ##### Emit the header | ||
| ##### | ||
|
|
||
| # Writes the build-tree header from the template and the expansion strings above. | ||
| function(svs_dispatch_emit_header template_file dim_count dim_loop target_loop | ||
| level_loop level_defines include_dir out_header) | ||
| set(SVS_GEN_DIM_COUNT "${dim_count}") | ||
| set(SVS_GEN_DIM_LOOP "${dim_loop}") | ||
| set(SVS_GEN_TARGET_LOOP "${target_loop}") | ||
| set(SVS_GEN_LEVEL_LOOP "${level_loop}") | ||
| set(SVS_GEN_LEVEL_DEFINES "${level_defines}") | ||
| set(header "${include_dir}/svs/core/distance/dispatch_surface.h") | ||
| configure_file("${template_file}" "${header}" @ONLY) | ||
| set(${out_header} "${header}" PARENT_SCOPE) | ||
| endfunction() | ||
|
|
||
| ##### | ||
| ##### Emit the manifest | ||
| ##### | ||
|
|
||
| function(svs_dispatch_emit_manifest supported_dims levels manifest_file) | ||
| string(REPLACE ";" " " levels_text "${levels}") | ||
| string(REPLACE ";" " " extents_text "${supported_dims}") | ||
| file(GENERATE | ||
| OUTPUT "${manifest_file}" | ||
| CONTENT "set(SVS_MANIFEST_FIXED_EXTENTS ${extents_text}) | ||
| set(SVS_MANIFEST_LEVELS ${levels_text}) | ||
| " | ||
| ) | ||
| endfunction() | ||
|
|
||
| ##### | ||
| ##### Orchestrate | ||
| ##### | ||
|
|
||
| # Reads the declaration, writes the build-tree header and the ctest manifest, and | ||
| # reports the surface. Returns the TU specs and the generated header path, which is | ||
| # how cmake/multi-arch.cmake receives them; nothing here escapes via a bare | ||
| # file-scope set(). | ||
| function(svs_generate_dispatch_surface out_tu_specs out_header) | ||
| set(x86_src_dir "${PROJECT_SOURCE_DIR}/include/svs/multi-arch/x86") | ||
|
|
||
| svs_dispatch_read_declaration( | ||
| "${SVS_DISPATCH_SURFACE_FILE}" "${x86_src_dir}" | ||
| supported_dims dim_list dim_count isa_levels | ||
| ) | ||
|
|
||
| # Re-run configure when the declaration changes, so the generated header and | ||
| # the translation units cannot go stale. | ||
| set_property( | ||
| DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS | ||
| "${SVS_DISPATCH_SURFACE_FILE}" | ||
| ) | ||
|
|
||
| svs_dispatch_build_expansions( | ||
| "${dim_list}" "${isa_levels}" | ||
| dim_loop target_loop level_loop level_defines | ||
| ) | ||
| svs_dispatch_derive_tu_specs( | ||
| "${isa_levels}" "${x86_src_dir}" | ||
| tu_specs levels level_report | ||
| ) | ||
|
|
||
| # The build always compiles against the build-tree copy, and it is placed | ||
| # ahead of the source include directory so that it wins. | ||
| set(generated_include_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/include") | ||
| svs_dispatch_emit_header( | ||
| "${SVS_DISPATCH_GEN_CMAKE_DIR}/templates/dispatch_surface.h.in" | ||
| "${dim_count}" "${dim_loop}" "${target_loop}" "${level_loop}" "${level_defines}" | ||
| "${generated_include_dir}" | ||
| header | ||
| ) | ||
| target_include_directories( | ||
| ${SVS_LIB} BEFORE INTERFACE $<BUILD_INTERFACE:${generated_include_dir}> | ||
| ) | ||
|
|
||
| svs_dispatch_emit_manifest( | ||
| "${supported_dims}" "${levels}" | ||
| "${CMAKE_BINARY_DIR}/dispatch_surface.manifest.cmake" | ||
| ) | ||
|
|
||
| ##### | ||
| ##### Report the surface | ||
| ##### | ||
|
|
||
| list(LENGTH isa_levels level_count) | ||
| string(REPLACE ";" " " dims_display "${supported_dims}") | ||
| message(STATUS "Dispatch surface: ${dim_count} extents x ${level_count} ISA levels") | ||
| message(STATUS " extents: ${dims_display} svs::Dynamic") | ||
| foreach(entry IN LISTS level_report) | ||
| message(STATUS " level: ${entry}") | ||
| endforeach() | ||
|
|
||
| # Every enumerator without a translation unit is still reachable -- the entry | ||
| # points fall back to it -- so its kernels are built by each consumer instead. | ||
| set(enum_header "${PROJECT_SOURCE_DIR}/include/svs/core/distance/distance_core.h") | ||
| if(EXISTS "${enum_header}") | ||
| file(READ "${enum_header}" enum_text) | ||
| if(enum_text MATCHES "enum class AVX_AVAILABILITY[ \t\r\n]*{([^}]*)}") | ||
| string(REPLACE "," ";" enumerators "${CMAKE_MATCH_1}") | ||
| set(undeclared) | ||
| foreach(enumerator IN LISTS enumerators) | ||
| string(STRIP "${enumerator}" enumerator) | ||
| if(enumerator AND NOT enumerator IN_LIST levels) | ||
| list(APPEND undeclared "${enumerator}") | ||
| endif() | ||
| endforeach() | ||
| if(undeclared) | ||
| string(REPLACE ";" ", " undeclared_display "${undeclared}") | ||
| message(STATUS " not in the surface: ${undeclared_display}") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: on a default configure this always prints |
||
| message(STATUS | ||
| " dispatched to, but compiled by no translation unit, so " | ||
| "every consumer instantiates those kernels itself, at its own -march" | ||
| ) | ||
| endif() | ||
| endif() | ||
| endif() | ||
|
|
||
| set(${out_tu_specs} "${tu_specs}" PARENT_SCOPE) | ||
| set(${out_header} "${header}" PARENT_SCOPE) | ||
| endfunction() | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocker: the header only exists in the build tree, and the install rule in
CMakeLists.txtcopies${PROJECT_SOURCE_DIR}/include/svsonly. Aftercmake --install,euclidean.h/inner_product.h/cosine.hinclude adispatch_surface.hthat isn't there, so anyfind_package(svs)consumer (and the conda package) fails to compile. On a fresh configure of this branch,build/cmake_install.cmakehas no rule for it; CI doesn't catch it because everything builds in-tree.Suggest something like: