Conversation
|
@copilot new or modified files should have |
c2d57c2 to
fe66e35
Compare
|
Tick the box to add this pull request to the merge queue (same as
|
The set of distance kernels compiled ahead of time -- extents x ISA levels
-- was written out by hand in every place that needed it: three extern
template blocks, two per-arch translation units, the `supported_dim_list`
array, and 48 near-identical `SPEC struct` lines in the instantiation
macros. Adding an extent meant editing all of them and hoping none was
missed. One had been: `euclidean.h` was missing d=160 for AVX2 (fixed in
the preceding commit), which silently made consumers instantiate that
kernel locally at their own -march.
Declare the surface once, in `cmake/dispatch-surface.cmake`:
set(SVS_SUPPORTED_DIMS 64 96 100 128 160 200 512 768)
set(SVS_ISA_LEVELS
"AVX2|haswell|avx2"
"AVX512|cascadelake|avx512"
)
`cmake/generate-dispatch-surface.cmake` validates it and writes
`include/svs/core/distance/dispatch_surface.h`, which exports
`SVS_FOR_EACH_SUPPORTED_DIM(M)`, `SVS_FOR_EACH_DISPATCH_TARGET(M)` and
`SVS_SUPPORTED_DIM_COUNT`. Everything that used to spell the list out now
loops over one of those. 108 hand-written instantiation lines become 0.
Type pairs stay in C++, in `multi-arch/x86/preprocessor.h`. A pair exists
because an implementation exists for it -- sometimes a hand-written one --
so the list belongs beside those implementations, not in the build system.
`svs::Dynamic` is appended automatically and cannot be listed: it is what
serves every dimensionality without a fixed-extent kernel, and the library
is incorrect without it.
The generated header is committed as well as generated. The build always
compiles against the build-tree copy, placed ahead of the source include
directory, and installs it over the committed one; the committed copy is
refreshed only when the declaration is the default, so overriding the
surface for a one-off build cannot rewrite the tree. Committing it keeps a
bare `-I include` compile working without CMake -- which the downstream
repository relies on, since it compiles `multi-arch/x86/{avx2,avx512}.cpp`
by path with its own CMake.
No behaviour change: the static library exports the same 864 symbols with
the same sizes, and the two arch objects are symbol-identical before and
after, both here and in the downstream build. `[distance]` passes
(134402115 assertions, 13 test cases).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every comment this branch adds now says what the code cannot say for itself and stops there. The block comments that restated the surrounding code, or spent five lines on a hazard that takes two, are gone; the hazards themselves stay, each naming its failure mode. Comment-only. The non-comment diff against the previous tip is empty.
A kernel that is missing its `extern template` declaration does not
produce an error. The consumer instantiates it locally instead, from the
generic primary template -- and in a baseline consumer translation unit
the vectorized partial specializations are not even visible, since they
are guarded on SVS_AVX2 / SVS_AVX512_F. So the consumer silently gets a
scalar loop where the library has a vectorized kernel, compiled at
whatever -march the consumer happens to use. That is the bug that shipped
for L2 at d=160 with AVX2.
Nothing could catch it, because nothing referenced the whole surface at
once. This adds a consumer that does: tests/multi-arch/x86/link_probe.cpp
names every kernel the surface declares -- every (extent, ISA level) pair,
every element-type pair, all three distances -- and nothing else. It is
compiled at -march=x86-64, like an arbitrary consumer of the headers, and
two tests are run against it:
dispatch_surface_probe calls every kernel whose ISA level this host
satisfies, so a kernel compiled beyond what
its level guarantees faults here
dispatch_surface_linkage reads the object's symbol table and requires
the kernels it references to be exactly the
kernels the library defines
The linkage check is host-independent and covers the whole surface
everywhere; the run covers only what the host can reach.
On the default surface the two sets match exactly at 864 kernels, and on
the reduced surface used by the non-default-surface CI job, at 288. All
three failure modes were confirmed to fire: dropping the L2 extern block
reports 288 kernels instantiated by the probe itself, and checking against
an archive missing the AVX-512 translation unit reports its 432 kernels as
declared but never instantiated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"9 extents (8 fixed + svs::Dynamic) x 2 ISA levels" says nothing about which extents, which levels, or what instruction budget each level compiles at, so reading the log gave no way to tell a correct surface from a plausible one. Also name the AVX_AVAILABILITY enumerators that are not in the surface, since that is the question the old count invited and could not answer: NONE is dispatched to but has no translation unit, so every consumer instantiates its kernels itself, at the consumer's own -march. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four checks, each closing a failure mode the link probe cannot see. dispatch_surface_declaration derives what the library must contain from the three hand-written sources -- the extent list and ISA levels, the type-pair lists, and the AVX_AVAILABILITY enumerator order -- and never reads the generated header. The linkage check compares the archive against a probe built from that header, so a generator that dropped an extent would drop it from both and still agree; this one has nowhere to hide. It also checks the entry-point consumer, whose kernels must all come from the archive: one it defines itself is an extern declaration that is missing. dispatch_instructions_<level>, one test per ISA level, disassembles the level's object file and holds it to a budget table keyed by -march. A level guarantees only what its runtime predicate tests, so an instruction outside that budget faults on a host the dispatcher routes there -- and no symbol-table check can see it. dispatch_surface_execution is the only check that observes a kernel run rather than exist: a specialization lost behind an `#if` still links and still counts. It breaks on every level's kernel for one extent and confirms the run enters the level this host satisfies. Weaker levels are covered by hosts that satisfy only those. dispatch_entry_probe reaches the kernels through the entry points rather than by naming the Impl classes, which is what makes the consumer half of the declaration check meaningful. nm, objdump and gdb are each optional: a missing tool skips its tests rather than failing the build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Answer the review on the declaration's maintenance story: cmake/dispatch-surface.cmake now states what to edit for an extent, a level, a type pair or an instruction budget, and why AVX_AVAILABILITY::NONE has no row. Move the four checker scripts to cmake/dispatch-checks/ with a README, and make the preprocessor.h type-pair comment stand without the refactoring for context. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e time
The declaration added in the preceding commit is only worth something if it
is checked rather than trusted, and if the knob that overrides it is
actually turned by something other than a person debugging.
Move the validation out of `generate-dispatch-surface.cmake` into
`validate-dispatch-surface.cmake`, which touches no build-system state and
so runs in script mode:
cmake -DSVS_DISPATCH_SURFACE_FILE=<file> -DSVS_X86_SRC_DIR=<dir> \
-P cmake/validate-dispatch-surface.cmake
`tests/cmake/dispatch-surface/` holds two declarations that must be accepted
and twelve that must be rejected, each carrying the substring its rejection
has to mention. `.github/scripts/check_dispatch_surface.sh` runs the lot --
fifteen cases, counting the default declaration -- in a fraction of a
second, needing no compiler and no build tree. It is a pre-commit hook and a
CI job.
Script mode has no `cmake_minimum_required`, so CMP0007 and CMP0057 default
to OLD there. Both matter: without CMP0007 an empty `|`-field disappears
when the entry is split, and without CMP0057 `IN_LIST` is not an operator.
Set both, scoped with cmake_policy PUSH/POP.
The new `Dispatch Surface` workflow adds what the script cannot check:
- a configure with the default declaration must leave the committed
`dispatch_surface.h` untouched. This catches a declaration changed
without a reconfigure, and a generated header edited by hand.
- a full build and test run against `valid-reduced.cmake`, which shares no
fixed extent with the default declaration -- so a build that quietly
fell back to the committed header would fail to compile rather than pass
by accident. That build's archive holds 288 kernels at extents 32, 384
and svs::Dynamic, against 864 at the default nine.
- that same overridden build must leave the committed header alone.
Correctness does not depend on which extents have a fixed-extent kernel: an
extent without one is served by the svs::Dynamic kernel. `ctest -LE long`
against the reduced surface passes 153 of 154, the one failure being
`Testing Binary Reader Iterator`, which fails identically on the unmodified
default-surface build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…x512
The AVX512 level's translation unit was compiled at -march=cascadelake, which
enables AVX512-VNNI. That level promises AVX-512 F/BW/DQ about the host and
nothing more, so every kernel in that object file was compiled with permission
to use instructions a Skylake-SP does not have. The VNNI kernels that existed
guarded themselves with a runtime check, but the guard only covered the calls
that were written by hand; the compiler was free to emit vpdpwssd anywhere in
the TU on its own initiative.
Adding AVX_AVAILABILITY::AVX512_VNNI as a fourth level moves the check to the
one place a level is chosen -- the entry point -- and lets each TU be compiled
at exactly what its level promises. The int8/int8 and uint8/uint8 kernels move
to the new level; every other pair promotes to float before doing arithmetic,
where VNNI has nothing to offer, so those pairs have no kernel at this level.
That is what keeps a fourth level from costing a fourth of everything: 54 new
instantiations rather than 432.
Two consequences worth naming:
- The pairs that move need an AVX512-level kernel to fall back to, and it has
to live outside `#if SVS_AVX512_VNNI`. Inside, it would be absent from the
AVX512 TU -- which is now compiled where that macro is 0 -- and silently
replaced by the generic template. This is why the two halves of the change
cannot land separately.
- The entry points must not dispatch to a level that has no kernel for the
pair in hand, for the same reason. `svs::distance::has_vnni_kernel` answers
that, generated from the same list the kernels are, and it is `if constexpr`
so it compiles away for the pairs that do not move.
The generated header now also defines SVS_ISA_LEVEL_<enumerator> per level, so
a surface that leaves a level out is visible to the code that dispatches on it.
Dropping the VNNI level degrades correctly -- those pairs stay on AVX512, and
the probe reports 864 kernels instead of 918. Dropping AVX2 or AVX512 is an
`#error` instead, because the entry points reach those two for every type pair.
ISA levels are not configuration the way the extent list is: a level exists
because kernels, a TU and a CPUID check exist for it.
The dispatch checks pick the change up on their own, which is what they were
written for. dispatch_instructions_avx512 now judges avx512.cpp.o at
skylake-avx512 and so forbids VNNI there, and it fails on the old object file;
the cascadelake row gains `vnni` as a requirement, because a VNNI level whose
object file has no VNNI in it is 54 instantiations of dead weight. Three
mechanical follow-ons: the per-level object libraries are named after the level
rather than the -march, since two levels now share neither; the execution check
breaks on int8/int8 rather than float/float, as a float-promoting pair has no
kernel at the top level and would route one lower; and the VNNI predicate joins
the other two in tests/multi-arch/x86/host_levels.h.
Verified on the default surface: 918 kernels declared, instantiated and
reachable with none instantiated by the consumer; all 456 vpdpwssd encodings in
vnni.cpp.o, zero in avx512.cpp.o and avx2.cpp.o, where before all 456 sat in
the AVX512 level's object file; the AVX2 object identical in symbol names and
sizes to before; the new L2Impl<128,int8,int8,AVX512> vectorized 16-wide float,
not scalar. `[distance]` passes with the same 134402115 assertions as before,
all eight dispatch tests pass, and ctest is otherwise unchanged. Also verified
with SVS_NO_AVX512=YES (both TUs compile generic, zero AVX-512 encodings, all
918 still linked) and with the reduced surface (306 kernels).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dd2db8d to
5fafe1d
Compare
There was a problem hiding this comment.
Pull request overview
Centralizes x86 distance-kernel extents and ISA levels into one generated dispatch declaration.
Changes:
- Generates kernel declarations, instantiations, and supported dimensions from one CMake surface.
- Adds linkage, instruction, declaration, and runtime dispatch checks.
- Installs the generated header and documents maintenance workflows.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
CMakeLists.txt |
Installs the generated surface header. |
cmake/AGENTS.md |
Documents dispatch ownership. |
cmake/dispatch-surface.cmake |
Declares extents and ISA levels. |
cmake/generate-dispatch-surface.cmake |
Validates and generates the surface. |
cmake/multi-arch.cmake |
Builds ISA objects from the declaration. |
cmake/templates/dispatch_surface.h.in |
Defines the generated-header template. |
cmake/dispatch-checks/README.md |
Documents dispatch checks. |
cmake/dispatch-checks/check-dispatch-declaration.cmake |
Validates declared kernel counts. |
cmake/dispatch-checks/check-dispatch-execution.cmake |
Verifies runtime routing. |
cmake/dispatch-checks/check-dispatch-instructions.cmake |
Inspects ISA instruction budgets. |
cmake/dispatch-checks/check-dispatch-linkage.cmake |
Verifies kernel linkage. |
include/svs/core/distance/cosine.h |
Generates cosine extern templates. |
include/svs/core/distance/dispatch_surface.h |
Commits the default generated surface. |
include/svs/core/distance/distance_core.h |
Generates supported dimensions. |
include/svs/core/distance/euclidean.h |
Generates L2 extern templates. |
include/svs/core/distance/inner_product.h |
Generates IP extern templates. |
include/svs/multi-arch/x86/avx2.cpp |
Generates AVX2 instantiations. |
include/svs/multi-arch/x86/avx512.cpp |
Generates AVX512 instantiations. |
include/svs/multi-arch/x86/preprocessor.h |
Defines reusable instantiation macros. |
tests/CMakeLists.txt |
Enables multi-architecture tests. |
tests/multi-arch/CMakeLists.txt |
Registers dispatch probes and checks. |
tests/multi-arch/x86/entry_probe.cpp |
Exercises public dispatch entry points. |
tests/multi-arch/x86/host_levels.h |
Models host ISA predicates. |
tests/multi-arch/x86/link_probe.cpp |
References every declared kernel. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
rfsaliev
left a comment
There was a problem hiding this comment.
The change is pretty big with high review/maintenance cost.
It seems like the huge AI generated change to be reviewed by AI.
Scripts should be simplified and well structured.
| @@ -0,0 +1,72 @@ | |||
| /* | |||
There was a problem hiding this comment.
As I understand, this file is autogenerated from dispatch_surface.h.in.
Why do we need to track it in repository?
Suggesting to remove the file from repository but generate it in binary directory during config/build.
There was a problem hiding this comment.
I added a default version for reference. If someone researched the code on GH only, or on a fresh checkout without prior compilation there are no missing files.
| @@ -0,0 +1,66 @@ | |||
| <!-- | |||
There was a problem hiding this comment.
Seems like files in this directory intended for tests.
IMHO it makes sense to move them to /test
| @@ -0,0 +1,250 @@ | |||
| # Copyright 2026 Intel Corporation | |||
There was a problem hiding this comment.
Seems like the cmake code here is pretty complicated.
Writing it in form of straightforward script leads high maintenance costs.
Please, modify the code to make it more structured, e.g. split to functions.
The targets were dispatch_surface_probe and dispatch_entry_probe while the sources are x86/link_probe.cpp and x86/entry_probe.cpp, so neither binary could be found from the name of the file that produced it. The ctest names keep the dispatch_ prefix -- dispatch_link_probe, dispatch_entry_probe -- because `ctest -R dispatch` is how CI and the docs select this group, and renaming the tests to match the binaries would drop two of the eight out of that filter. The object libraries follow from the target name via svs_add_dispatch_probe, so the symbol-table checks pick up link_probe_objects and entry_probe_objects without further change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Copilot reviewed 46 out of 46 changed files in this pull request and generated 4 comments.
Suppressed comments (3)
tests/multi-arch/dispatch-checks/check-dispatch-declaration.cmake:1
- The error message references
${SVS_SURFACE_FILE}, but this script is driven bySVS_MANIFESTand never definesSVS_SURFACE_FILE, so the message will be missing the file context. Use${SVS_MANIFEST}(or reword to “declared in the manifest”) so failures point to the correct input.
tests/multi-arch/CMakeLists.txt:1 - This
else()triggers when eithersvs_nmorsvs_gdbis missing, but the status message only mentions gdb. Make the message reflect the actual condition (e.g., mention missingnmand/orgdb) so skipped-test diagnostics are accurate.
tests/multi-arch/CMakeLists.txt:1 - This
else()triggers when eithersvs_nmorsvs_gdbis missing, but the status message only mentions gdb. Make the message reflect the actual condition (e.g., mention missingnmand/orgdb) so skipped-test diagnostics are accurate.
The probes printed only their accumulated distance sum, which says nothing about how many kernels ran: a macro list that expanded to fewer calls than the surface declares still produced a plausible float. The count makes that visible, and it cross-checks against a figure derived from a different source -- the declaration checker independently computes 918 kernels, which is what link_probe now reports. entry_probe reports 432 rather than 918 because each entry point picks one ISA level at runtime, so it reaches nine extents by sixteen type pairs by three distances, not the whole surface. Its count comes from a named constant next to entry_one, since that function's body is what fixes the calls per expansion. The comments explaining that printing defeats dead-code elimination are gone from both files: the printed message now says it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The flag was only reached through the execution check's script, so a ctest run never showed the three lines it parses and a probe that stopped honouring the flag surfaced as a missing level rather than as itself. The mode returns before the kernel sweep, so this is a second invocation rather than an argument on the existing one, which keeps the kernel count visible too.
…est" This reverts 669358e. The flag is reached again only through the execution check's script, so a ctest run shows the kernel counts but not the three lines that check parses.
|
By the way, here is the verbose output what the new tests are doing. I agree the So what's left are the changes to existing headers and implementations. Those are very small. It's mostly just removing the now redundant instantiations and some updated dispatching logic because of the VNNI change. So, honestly, I wouldn't be too concerned about "large AI generated PR". |
The comment justified naming the object target after the ISA level by asserting that more than one level can share an instruction budget. That stopped being true when validate-dispatch-surface.cmake began rejecting duplicate -march values as a hard error, so the stated reason no longer holds. Record the constraint that does apply: the target name is unique only because duplicate infixes are rejected too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SVS_SURFACE_FILE was never set anywhere in the tree, so the diagnostic rendered as "ISA level 'X' is declared in but is not an AVX_AVAILABILITY enumerator" -- the file context the message exists to give was always blank. The script is driven by SVS_MANIFEST. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The guard requires both tools, but the skip message named only gdb, so a host missing nm reported a cause that was not the one that fired. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
||
| # The build always compiles against the build-tree copy, and it is placed ahead | ||
| # of the source include directory so that it wins. | ||
| set(SVS_GENERATED_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/include") |
There was a problem hiding this comment.
I am still afraid that there is possible include file collision between include/svs/core/distance/dispatch_surface.h and <build>/generated/include/svs/core/distance/dispatch_surface.h
There was a problem hiding this comment.
Would you remove the header instead or do something else entirely?
There was a problem hiding this comment.
The intention is to generate the new header, and overwrite the existing include/svs/core/distance/dispatch_surface.h.
This header will be copied to <build> and used ... there should not be the possibility for confusion. Both headers should have identical content.
|
Below is a very detailed and slightly sloppy benchmarking report. The tl;dr is
Basically, check out the table under "QPS at a glance" and if anything is unclear you'll probably find it in the details below. PR #372 A/B — resultsPR #372,
Host: 2 sockets × 56 physical cores. Search runs are pinned — QPS at a glanceMedian throughput, queries/second, at
The two integer rows come from the 10-replicate deep-dive run, whose 100× larger query set costs ~9 % Each recall figure is the value that arm reported, not a copy; the generator asserts the identity rather Effects are not derived from these medians: the estimates in this report are medians of paired VerdictRecall is bit-identical everywhere. Throughput is unchanged on the ten float cells and
On the float cells the effect and the null are the same size, and that is the result: #372 moves Both pooled estimators are quoted for the float cells because the centre shifts by ~0.3 points Build time is a separate measurement and it resolves nothing, on any cell. An early 2-arm run For the PR this is a positive result, not merely a neutral one. The refactor was expected to be Recall — the control, not the measurementBecause all three arms search one shared saved graph per cell (see Independently, the shared-graph design was gated before any timing run: arm B loading an This is the strongest single statement the experiment makes. But state it precisely: what is proven is With 84 operating points × 5 replicates × ~5,000–20,000 queries each, a rounding difference large Throughput, per cellRobust per-cell estimates, sws ≥ 20, replicate 1 excluded (see "What was corrected" below).
Per (cell, sws) rows: The three cells that look like they moved, and why none of them did
ISA coverage — what this experiment could and could not have detectedThe compiled dispatch surfaces differ as expected. Flags read out of each tree's
Arm B's configure step reports the generated surface explicitly: 9 extents × 3 ISA levels; extents Why a flat result on the ten float cells was mechanistically predicted
For the integer kernels both arms run the same arithmetic, but not the same function. Arm A is template <size_t N> struct L2Impl<N, int8_t, int8_t, AVX_AVAILABILITY::AVX512> { // arm A
SVS_NOINLINE static float compute(...) {
if (__builtin_expect(svs::detail::avx_runtime_flags.is_avx512vnni_supported(), 1)) {
return simd::generic_simd_op(L2VNNIOp<int16_t, 32>(), a, b, length);
}
return simd::generic_simd_op(L2FloatOp<16>{}, a, b, length); // dead on this host
}
};Arm B dispatches to a level-3 specialization whose body is the VNNI call and nothing else. The cost of
Symbol sizes for spacev's kernel are the same story: 575 B at level 2 against 186 B at level 3. So the VNNI arithmetic is bit-for-bit the same work, and arm A additionally loads a global and branches Note also that this makes +1.2 % the narrow case. Arm A benefits from being compiled at Two consequences that bound the strength of this result:
Six of the twelve cells required registrations that do not exist upstream — the shipped list in The final compiled set is VNNI deep-dive — the one real effect in the experimentThe two integer cells were re-measured on their own: 10 replicates instead of 5, three arms, arm Recall is still exactly unchangedΔrecall is 0.0 at all 14 (cell, sws) points across all three arms and all 10 replicates, and equals the Throughput
The effect is ~100× the null. It is also present at every operating point from sws=20 to sws=200 on Unlike the main matrix, replicate 1 shows no cold-cache penalty here (every one of the 42 Three adversarial checks, all passedRun order cannot explain it. With arms ignored, slot position moves QPS by at most +0.13 % — an A sign test needs no distributional assumption. A and A′ are the same binary, so under the null each A permutation test gives the exact null. Exchanging which arm label is treated as "B" within each One caveat kept deliberately: a stricter criterion — requiring the effect's 2σ band to be disjoint Build timingSeparate measurement, unpinned at
Eight float cells: no resolvable difference. Pooled median B/A is −0.15 % with a 95 % CI of roughly The two integer cells appeared to move — this reading is superseded and wrong; the 3-arm follow-up
Integer replicate-pairs: 6/6 B-faster, median −5.06 %. Float replicate-pairs: median −0.04 %, B It looked expected rather than surprising, which is precisely why it needed the control: graph Six replicate-pairs was thin, and the follow-up run withdrew this finding. See below. The integer build-time effect did not survive its own control — withdrawnThe two integer cells were re-measured at 10 replicates × 3 arms = 60 builds, all
Negative means B builds faster. Excluding replicate 1 changes nothing material (pooled −0.52 %, The two cells now disagree in sign, and the mechanism predicts they should agree. They differ only Three further reasons not to rescue this:
Corrected statement: build time is unresolved on all ten cells, integer included. The measurement's Build recall confirms the nondeterminism the design assumesRecall at sws=20 from these per-arm builds differs across replicates of the same arm — spans up to What was corrected during analysisTwo corrections changed the numbers materially. Both are in the analysis script's docstring so the Replicate 1 is a cold-page-cache run and is excluded. The first run of each cell paid a page-cache The central estimate is the median of paired log-ratios, not the mean. Mean-of-logs is dragged by The mechanism for the integer effect was wrong twice before it was right, and only disassembly settled The integer build-time speedup was retracted by its own follow-up. Three replicates per arm with no Reading limits — carry these into any decision
ArtifactsAll under
Appendix — QPS, every cell and operating pointMedian queries/second, same exclusions as the table at the top. Recall gets one column here rather Rows marked
Per-sample values behind these medians: |
| include("${CMAKE_CURRENT_LIST_DIR}/dispatch-levels.cmake") | ||
|
|
||
| set(SVS_DEFAULT_DISPATCH_SURFACE_FILE "${CMAKE_CURRENT_LIST_DIR}/dispatch-surface.cmake") | ||
| set(SVS_DISPATCH_SURFACE_FILE "${SVS_DEFAULT_DISPATCH_SURFACE_FILE}" |
There was a problem hiding this comment.
What is the motivation for this variable?
| # CMake, and a one-off build with an overridden surface must not rewrite it. | ||
| # configure_file only touches the file when the content changes, so this neither | ||
| # dirties the tree nor forces rebuilds. | ||
| if(svs_surface_is_default) |
There was a problem hiding this comment.
Seems like by default, the <root>/include/svs/core/distance/dispatch_surface.h is always overriden.
What is the reason having 2 identical autogenerated headers in different locations?
| @@ -0,0 +1,32 @@ | |||
| /* | |||
There was a problem hiding this comment.
Why this file is named vnni.cpp? Is it intended for avx-vnni or avx512-vnni support?
| #define SVS_DEFINE_FOR_DIM(DIM) SVS_INSTANTIATE_DISTANCES(template, DIM, AVX2) | ||
| SVS_FOR_EACH_SUPPORTED_DIM(SVS_DEFINE_FOR_DIM) |
There was a problem hiding this comment.
SVS_INSTANTIATE_DISTANCES macro is defined in preprocessor.h
SVS_FOR_EACH_SUPPORTED_DIM - in dispatch_surface.h
but these headers are not included.
Seems like it breaks at least readability "... If someone researched the code on GH only,..."
Three human review comments on PR #372 named gaps the Copilot review agent did not catch. Each is generalized here so a similar violation in a later PR is flagged, rather than recorded as a one-off. - copilot-instructions.md: a new file must satisfy the purpose line of the nearest AGENTS.md, in kind as well as in role. - tests/AGENTS.md: the purpose line now admits harnesses written in any language, including build-level checks. Without this the rule above cannot decide where a ctest checker written in CMake belongs, because cmake/AGENTS.md and tests/AGENTS.md both had a claim on it. That ambiguity, not a weak rule, is why the reviewer's objection was derivable from neither. - build-system.instructions.md: a value another module consumes is returned through function()/PARENT_SCOPE or a cache entry. - library-code.instructions.md (new): a macro expanding into a translation unit's own definitions needs its defining header included directly. Two rules were rejected at the wrong altitude before these survived. "Split build logic into functions", the reviewer's own suggested example, would have flagged 12 of 13 cmake modules on main: the established convention rather than a deviation from it. The include rule scoped to include/svs/** would have flagged 53 merged headers that use SVS_UNUSED or SVS_FWD transitively. Both reached a usable form only after re-distilling to a rung at which main is clean and this PR's additions are not. The other six comments needed no rule. Five are answered by code or configuration already on record, and one asks for a rationale that is present at the comparison it serves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The AVX512 runtime predicate does not guarantee its compilation budget, and instruction tests break when AVX512 is disabled.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (6)
Previously missed (6) — in code that hasn't changed since the last review.
cmake/generate-dispatch-surface.cmake:74
- This module publishes
SVS_DISPATCH_TU_SPECStomulti-arch.cmakevia a bare file-scope variable, and similarly publishes the generated-header path to the root install logic. That directly conflicts with the module-interface rule in.github/instructions/build-system.instructions.md:9; wrap generation in a function and return consumed outputs withPARENT_SCOPE(or use cache entries) so unrelated includes cannot silently overwrite them.
include/svs/multi-arch/x86/avx2.cpp:27 - This translation unit expands
SVS_INSTANTIATE_DISTANCESandSVS_FOR_EACH_SUPPORTED_DIMbut obtains their definitions only transitively through the distance headers. Include the defining preprocessor header directly so this code-generating dependency remains explicit and does not break when those headers stop re-exporting it.
include/svs/multi-arch/x86/avx512.cpp:27 - This translation unit expands
SVS_INSTANTIATE_DISTANCESandSVS_FOR_EACH_SUPPORTED_DIMbut obtains their definitions only transitively through the distance headers. Include the defining preprocessor header directly so this code-generating dependency remains explicit and does not break when those headers stop re-exporting it.
include/svs/multi-arch/x86/vnni.cpp:20 - This translation unit expands
SVS_INSTANTIATE_DISTANCESandSVS_FOR_EACH_SUPPORTED_DIMbut obtains their definitions only transitively through the distance headers. Include the defining preprocessor header directly so this code-generating dependency remains explicit and does not break when those headers stop re-exporting it.
tests/cmake/dispatch-surface/valid-minimal.cmake:16 - This comment calls the one-level fixture “still a library,” but
distance_core.hexplicitly rejects any x86 surface without both AVX2 and AVX512. The fixture is only structurally valid to the declaration validator, not buildable; describe that distinction so it is not mistaken for a supported minimal configuration.
tests/multi-arch/x86/entry_probe.cpp:115 - This reference still points to the checker's old location even though this PR moves it under
tests/multi-arch/dispatch-checks/. Update the path so future changes can find the script that consumes this mangling.
- Files reviewed: 50/50 changed files
- Comments generated: 2
- Review effort level: Balanced
| # that the budget and the promise line up. | ||
| set(SVS_ISA_LEVELS | ||
| "AVX2|haswell|avx2" | ||
| "AVX512|skylake-avx512|avx512" |
| foreach(class IN LISTS svs_required) | ||
| svs_count_class(count "${class}") | ||
| if(count EQUAL 0) | ||
| message("${SVS_OBJECT} contains no ${class} instructions.") |
Sweeps every merged pull request below 300 on the public repo and writes the recurring human review catches Copilot missed into the instruction surfaces. 18 principles, each seen in two or more distinct pull requests. New surfaces: library-code (include/svs/**), bindings (bindings/**) and documentation. Widens the testing glob to reach bindings/*/tests/**, which is where two thirds of the fixture-reuse evidence lives, and the build-system glob to reach .github/workflows/**. Renames library-code.instructions.md to multi-arch.instructions.md: its glob is include/svs/multi-arch/** and its title said Multi-Arch, so the name was misleading and the library-code name was needed for the include/svs/** rules. Resolves two contradictions. "Prefer smallest test surface" and "cover every documented variant" pulled opposite ways, and are now ordered — smallest surface per variant, then every variant. "Delete branches a change makes unreachable" and "a conditional-compilation guard is load-bearing" both describe an #ifdef path no CI job defines, so the first now says such a path is not dead merely because no CI job selects it. Five of the 18 could not be validated against their own evidence and land on the strength of recurrence alone. Reuse-the-shared-helper, reuse-the-shared-fixture and check-for-an-existing-flag describe duplication that is invisible inside a single diff, which is all a reviewer sees; expect them to be quiet. Role-based naming and compile-time-over-runtime had only post-fix evidence, so no violation could be demonstrated. Each says so in its own record. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
ISA predicates do not fully match compile budgets, and declaration validation accepts configurations that cannot build.
Review details
Suppressed comments (8)
Previously missed (6) — in code that hasn't changed since the last review.
cmake/validate-dispatch-surface.cmake:115
- Validation never checks that
levelnames anAVX_AVAILABILITYenumerator. For example,BOGUS|haswell|avx2is accepted becauseavx2.cppexists, then the generated C++ fails to compile. Since this PR promises configure-time rejection of malformed declarations, validate levels against the enum (and add a rejection fixture) before accepting the row.
include/svs/multi-arch/x86/avx2.cpp:27 - This translation unit now expands code-generating macros without directly including either defining header. That makes its definitions depend on the distance headers' transitive includes, contrary to the multi-arch requirement and easy to break when those headers are reorganized. Include both macro owners explicitly.
include/svs/multi-arch/x86/avx512.cpp:27 - This translation unit now expands code-generating macros without directly including either defining header. That makes its definitions depend on the distance headers' transitive includes, contrary to the multi-arch requirement and easy to break when those headers are reorganized. Include both macro owners explicitly.
include/svs/multi-arch/x86/vnni.cpp:20 - This new translation unit expands code-generating macros without directly including either defining header. That makes its definitions depend on the distance headers' transitive includes, contrary to the multi-arch requirement and easy to break when those headers are reorganized. Include both macro owners explicitly.
tests/cmake/dispatch-surface/valid-minimal.cmake:19 - This fixture is labeled valid and described as a usable minimal library, but
distance_core.hnow emits an error unless both AVX2 and AVX512 are declared. The checker therefore blesses a surface that cannot build. Keep one extent, but include both mandatory levels (or classify the one-level surface as invalid).
tests/multi-arch/x86/entry_probe.cpp:116 - This comment contradicts the probe:
int8_t/int8_tis one of the two pairs that does have an AVX512_VNNI kernel, and the checker path has moved out ofcmake/. As written, it gives the opposite rationale for whyexpected_level()should match this call.
cmake/dispatch-surface.cmake:118
- These
-marchbudgets still exceed what the runtime predicates guarantee.haswellpermits FMA (used by the AVX2 kernels), while dispatch tests only the AVX2 bit; more critically,skylake-avx512permits BW/DQ/VL, while dispatch tests only AVX512F, and the integer conversion paths use BW/VL intrinsics. An AVX512F-only host such as KNL can therefore be routed into unsupported instructions. Extend the runtime feature detection/predicates to cover every feature enabled by each budget, or lower/split the budgets accordingly.
"AVX2|haswell|avx2"
"AVX512|skylake-avx512|avx512"
"AVX512_VNNI|cascadelake|vnni"
tests/multi-arch/dispatch-checks/check-dispatch-instructions.cmake:108
- The required-class assertion makes the test suite fail for the supported
SVS_NO_AVX512=YESconfiguration: that option deliberately compiles the AVX512 and VNNI translation units without zmm/VNNI instructions, but these rows still require them. Pass the configuration into this checker and validate the generic fallback instead, or omit only these required-instruction assertions when AVX512 is disabled.
- Files reviewed: 54/54 changed files
- Comments generated: 0 new
- Review effort level: Balanced
The set of distance kernels compiled ahead of time is declared once, in
cmake/dispatch-surface.cmake, and the generated header, the per--marchtranslationunits and the ctest checks all derive from it. Configure-time validation rejects a
malformed declaration, and VNNI becomes its own ISA level so that each level's
-marchbudget matches what it promises.
Gotchas:
touched the same files, so redoing that work here rather than folding them in would have
meant resolving the same conflicts twice. Both are closed as merged here. The diff is
large for that reason, not because the change grew.
cmake/dispatch-checks/is nowtests/multi-arch/dispatch-checks/. They are test drivers; only generation stays undercmake/.add_test, not Catch2.link_probe.cppexists to fail to linkwhen a declared kernel is uninstantiated, and
entry_probe.cppgets driven under gdb —neither works from inside the single
testsbinary. Porting the fourcmake -Pcheckersto Catch2 is a follow-up.
include/svs/core/distance/dispatch_surface.his generated but committed, so a bare-I includecompile works without cmake. CI fails if it goes stale, and asserts that abuild with an overridden surface leaves it alone.
-march. Previously accepted, and itshipped a fault: the weaker level got compiled with instructions its runtime predicate does
not guarantee, and the instruction checker missed it because it looks the budget up by
-march. Not a review point; found while probing whether these checks can still fail.declaration checker as redundant, and parallel lists for the ISA table. Reasons are in
the review thread.