Latest Results
Implement GEMMT with a blocked level-3 driver instead of a GEMV loop
?GEMMT / ?GEMMTR has been a loop over the columns of the triangle with one
?GEMV per column since it was added in 0.3.22. Besides being much slower
than GEMM (issue #4921), every column above the GEMV multithreading
threshold becomes its own thread dispatch, so the routine gains almost
nothing from more cores.
Add a real level-3 driver. No new microkernels are needed: the blocks that
cross the diagonal are handled by the existing SYRK kernel, which already
clips a block against the diagonal at register-block granularity.
- driver/level3/level3_gemmt.c is derived from level3_syrk.c. The
blocking, the diagonal handling and the loop structure are the same;
the packed B buffer is filled from a second matrix, so the buffer
sharing SYRK can do is not available, and op(A) / op(B) are selected by
the NN/NT/.../CC defines that level3.c uses.
- driver/level3/gemmt_k.c holds the triangular beta scaling and the
choice of kernel, and is built once per uplo x op(A) x op(B) just as
syrk_k.c is built per uplo x trans.
- For complex, syrk_kernel.c is also built with CONJA / CONJB; the file
already supported those defines but nothing instantiated them.
interface/gemmt.c dispatches through a [uplo][op(B)][op(A)] table like
interface/gemm.c, and above the existing SMP threshold splits the triangle
into column ranges of equal area with syrk_thread(), so every thread writes
a disjoint set of columns of C. Only the blocks that intersect the
requested triangle are packed and computed, and only the triangle is scaled
by beta.
This also stops complex GEMMT from writing to the caller's B: conjugation
used to be done by conjugating B in place and undoing it afterwards, which
is unsafe for a shared or read-only B.
dgemmt on an 8-core/16-thread i7-11800H, SKYLAKEX kernels, time for one
call, with dsyrk on the same data for reference:
n=1600 k=128, 1 thread: 18.7 ms -> 5.14 ms (dsyrk 5.12 ms)
n=1600 k=128, 4 threads: 6.92 ms -> 1.54 ms (dsyrk 1.53 ms)
n=1600 k=128, 16 threads: 11.7 ms -> 1.01 ms (dsyrk 1.01 ms)
n=400 k=128, 16 threads: 1.31 ms -> 0.10 ms (dsyrk 0.11 ms)
One deliberate deviation from level3_syrk.c: for a row band that lies
entirely below the column block, level3_syrk.c bounds its column loop with
`jjs < min_j`, which mixes an absolute index with a length and packs
nothing when js > 0. The GEMMT driver uses `jjs < js + min_j`. Neither
interface passes a row range, so the branch is only reachable by calling
the driver directly; level3_syrk.c is left alone here.
Add a utest case per precision at a size that is neither a multiple of the
register blocking nor small enough to stay single-threaded, which is where
the packing and the diagonal clipping have to agree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Latest Branches
0%
0%
0%
© 2026 CodSpeed Technology