feat(gemm): the annex is a matrix, not a list — scored by the same GEMM

The per-film annex was folded in after the gallery multiply by a host-side
cosine loop over a vector of {embedding, actor} structs, justified in-comment
by "tens of embeddings". AR-018/AR-019 retired that assumption: every owned
track promotes, so the annex grows with cast size and film length.

TrackGallery now holds it as a contiguous row-major matrix with a parallel
actor index — the flat_emb_/flat_actor_ shape the baked gallery already uses —
and hands newly promoted rows to the matcher once per frame. The matcher pushes
them into the similarity engine's resident matrix through a new
ISimilarityEngine::append_rows, so one SGEMM covers baked and promoted
references alike and best-of-N is a single pass over one similarity column.
Capacity doubles on overflow, and the GPU backends grow device-to-device, so a
promotion never re-uploads the gallery across the bus.

Absorbing promotions runs once per frame, after every face has been scored.
Appending mid-frame would invalidate the similarity pointer the chunk loop is
still reading, and it also removes an incidental dependence on face order
within a frame — a promotion helps subsequent frames, never the one that
produced it, which is the semantics the expansion store already documented.

OpenBLAS becomes a requirement of the CPU GEMM backend rather than an
opportunistic upgrade. That path is what CI and the cpu builder image run, so
falling back to the scalar loop in silence meant AR-027 could be measured — or
believed — on a kernel no release uses. The loop survives as the correctness
oracle the BLAS backends are diffed against, behind SAE_ALLOW_SCALAR_GEMM.

Call site 3, the deferred TBI pass, is untouched: it does not exist until
AR-020, so AR-026 stays In Progress.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

TRACES: AR-026 | UT-004, UT-005 | SR-001
This commit is contained in:
2026-08-04 21:20:31 +02:00
co-authored by Claude Opus 5
parent f33403fff8
commit c1155cb607
11 changed files with 461 additions and 81 deletions
+26 -6
View File
@@ -55,6 +55,13 @@ set_property(CACHE SAE_GEMM_BACKEND PROPERTY STRINGS ROCM CUDA CPU)
# default so ROCm/CPU builds don't reference unavailable EPs.
option(SAE_ORT_TRT_EP "ORT backend: enable TensorRT/CUDA execution providers" OFF)
# AR-026/AR-027: the CPU GEMM path is backed by OpenBLAS, and its absence is a
# configure error rather than a silent downgrade to the scalar loop. Declared at
# top level because the unit-test target compiles the CPU kernel regardless of
# which backend the main build selected, and both must make the same choice.
option(SAE_ALLOW_SCALAR_GEMM
"Permit the scalar-loop GEMM fallback when OpenBLAS is absent" OFF)
# Back-compat: a legacy -DSAE_WITH_TRT=ON/OFF seeds the new vars (ON⇒TRT+CUDA,
# OFF⇒ORT+ROCM) unless the user set them explicitly.
if(DEFINED SAE_WITH_TRT)
@@ -153,10 +160,16 @@ if(SAE_GEMM_BACKEND STREQUAL "CPU")
target_include_directories(gemm_backend PRIVATE src)
target_compile_definitions(gemm_backend PRIVATE SAE_GEMM_CPU)
# AR-026/AR-027: back the CPU path with OpenBLAS when present. Optional, so
# the build gains no hard dependency — but without it the fallback is a
# scalar loop, which does not hold up against a library-scale gallery, and
# the CPU path is exactly what CI (no GPU) and the cpu builder image use.
# AR-026/AR-027: the CPU path is backed by OpenBLAS, and that is REQUIRED
# rather than opportunistic. The CPU backend is what CI (no GPU) and the cpu
# builder image actually run, so a silent fall back to the scalar loop means
# AR-027 is measured — or worse, believed — on a path no release uses. A
# missing dependency should stop the build and name itself, not degrade into
# a slower answer nobody notices.
#
# The scalar loop survives as the correctness oracle the two backends are
# diffed against; -DSAE_ALLOW_SCALAR_GEMM=ON is how you ask for it, which
# keeps that an explicit, visible choice.
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(OPENBLAS QUIET openblas)
@@ -166,9 +179,16 @@ if(SAE_GEMM_BACKEND STREQUAL "CPU")
target_compile_definitions(gemm_backend PRIVATE SAE_GEMM_CBLAS)
target_include_directories(gemm_backend PRIVATE ${OPENBLAS_INCLUDE_DIRS})
target_link_libraries(gemm_backend PRIVATE ${OPENBLAS_LINK_LIBRARIES})
elseif(SAE_ALLOW_SCALAR_GEMM)
message(WARNING "GEMM backend: CPU scalar fallback (SAE_ALLOW_SCALAR_GEMM=ON) — "
"correct, but slow on a large gallery. Do not measure AR-027 here.")
else()
message(WARNING "GEMM backend: CPU scalar fallback — OpenBLAS not found. "
"Correct, but slow on a large gallery (AR-027).")
message(FATAL_ERROR
"OpenBLAS not found, and the CPU GEMM backend requires it (AR-026/AR-027).\n"
" Install it: Fedora dnf install openblas-devel\n"
" Arch pacman -S openblas\n"
" Debian apt install libopenblas-dev\n"
" Or build the scalar fallback deliberately: -DSAE_ALLOW_SCALAR_GEMM=ON")
endif()
elseif(SAE_GEMM_BACKEND STREQUAL "CUDA")
find_library(CUBLAS_LIB cublas