Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/audit/generated/runtime_abi.csv
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,9 @@ x86,tessera_x86_amx_gemm_bf16,amx_gemm,bf16,src/compiler/codegen/tessera_x86_bac
x86,tessera_x86_amx_gemm_s8s8_s32,amx_gemm_s8s8_s32,,src/compiler/codegen/tessera_x86_backend/include/tessera/x86/target.h
x86,tessera_x86_amx_gemm_s8s8_s32,amx_gemm_s8s8_s32,,src/compiler/codegen/tessera_x86_backend/src/kernels/amx_gemm_int8.cpp
x86,tessera_x86_avx512_gemm_bf16,avx512_gemm,bf16,src/compiler/codegen/tessera_x86_backend/src/kernels/avx512_gemm_bf16.cpp
x86,tessera_x86_avx512_reduce_f32,avx512_reduce,f32,src/compiler/codegen/tessera_x86_backend/src/kernels/avx512_reduce_f32.cpp
x86,tessera_x86_avx512_vnni_gemm_u8s8_s32,avx512_vnni_gemm_u8s8_s32,,src/compiler/codegen/tessera_x86_backend/src/kernels/avx512_vnni_gemm_int8.cpp
x86,tessera_x86_epilogue_bias_fp32,epilogue_bias_fp32,,src/compiler/codegen/tessera_x86_backend/src/kernels/epilogue.cpp
x86,tessera_x86_epilogue_bias_gelu_fp32,epilogue_bias_gelu_fp32,,src/compiler/codegen/tessera_x86_backend/src/kernels/epilogue.cpp
x86,tessera_x86_reference_gemm_bf16,reference_gemm,bf16,src/compiler/codegen/tessera_x86_backend/src/kernels/avx512_gemm_bf16.cpp
x86,tessera_x86_reference_reduce_f32,reference_reduce,f32,src/compiler/codegen/tessera_x86_backend/src/kernels/avx512_reduce_f32.cpp
4 changes: 2 additions & 2 deletions docs/audit/generated/runtime_abi.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Human-readable view. The canonical machine-readable artifact is `runtime_abi.csv

## Headline

- **326** unique `extern "C" tessera_*` C ABI symbols across all backends.
- **328** unique `extern "C" tessera_*` C ABI symbols across all backends.
- **6 / 6** core runtime headers present.
- **134** Apple GPU kernel families with per-dtype variants.

Expand All @@ -26,7 +26,7 @@ Human-readable view. The canonical machine-readable artifact is `runtime_abi.csv
| `apple` | 304 |
| `nvidia` | 4 |
| `rocm` | 10 |
| `x86` | 8 |
| `x86` | 10 |

## Apple GPU kernel families × dtype matrix

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/codegen/tessera_x86_backend/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ add_library(tessera_x86_backend
src/kernels/amx_gemm_bf16.cpp
src/kernels/amx_gemm_int8.cpp
src/kernels/avx512_vnni_gemm_int8.cpp
src/kernels/avx512_reduce_f32.cpp
src/kernels/epilogue.cpp
src/runtime/amx_runtime.cpp
)
Expand All @@ -53,3 +54,6 @@ target_link_libraries(test_gemm PRIVATE tessera_x86_backend)

add_executable(test_gemm_reference_tails tests/test_gemm_reference_tails.cpp)
target_link_libraries(test_gemm_reference_tails PRIVATE tessera_x86_backend)

add_executable(test_reduce tests/test_reduce.cpp)
target_link_libraries(test_reduce PRIVATE tessera_x86_backend)
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// AVX-512 row-wise reduction kernels (f32) for the Tessera x86 backend.
//
// Reduces each row of a [rows, cols] f32 matrix over the last axis, producing
// out[rows]. kind: 0 = sum, 1 = max, 2 = mean (= sum / cols). This is the
// optimized CPU lane for the S-series reduction family (reduce_sum / mean / max
// over the last axis) — the AVX-512 analog of the AMX/AVX-512 GEMM lane, so the
// reduction primitives get a REAL vectorized CPU kernel rather than only the
// numpy reference. A scalar reference is provided alongside for on-device
// validation (the test compares the two + a hand-computed expectation).
//
// 16 f32 lanes per __m512; the column tail (cols % 16) is handled scalar.
// Horizontal reduce via the AVX-512 `_mm512_reduce_{add,max}_ps` intrinsics.

#include <immintrin.h>
#include <cstdint>
#include <limits>

namespace {
constexpr int kSum = 0;
constexpr int kMax = 1;
constexpr int kMean = 2;
// NaN must PROPAGATE in reduce_max to match the reference (numpy `np.amax`):
// a row containing a NaN reduces to NaN. Plain MAXPS / ordered `>` would drop
// it. We track NaN explicitly (the f32 self-inequality test) and force NaN out.
inline bool is_nan_f32(float v) { return v != v; }
const float kQNaN = std::numeric_limits<float>::quiet_NaN();
} // namespace

extern "C" void tessera_x86_reference_reduce_f32(const float* X, int64_t rows,
int64_t cols, float* out,
int kind) {
for (int64_t r = 0; r < rows; ++r) {
const float* row = X + r * cols;
if (kind == kMax) {
float acc = -std::numeric_limits<float>::infinity();
bool nan = false;
for (int64_t c = 0; c < cols; ++c) {
float v = row[c];
if (is_nan_f32(v)) nan = true;
else if (v > acc) acc = v;
}
out[r] = nan ? kQNaN : acc;
} else { // sum / mean — `+` already propagates NaN
float acc = 0.0f;
for (int64_t c = 0; c < cols; ++c) acc += row[c];
out[r] = (kind == kMean && cols > 0) ? acc / (float)cols : acc;
}
}
}

extern "C" void tessera_x86_avx512_reduce_f32(const float* X, int64_t rows,
int64_t cols, float* out,
int kind) {
const int64_t vstep = 16; // f32 lanes per __m512
for (int64_t r = 0; r < rows; ++r) {
const float* row = X + r * cols;
int64_t c = 0;
if (kind == kMax) {
__m512 vacc = _mm512_set1_ps(-std::numeric_limits<float>::infinity());
bool nan = false;
for (; c + vstep <= cols; c += vstep) {
__m512 v = _mm512_loadu_ps(row + c);
// any NaN lane in this chunk? (v != v, unordered with itself)
if (_mm512_cmp_ps_mask(v, v, _CMP_UNORD_Q)) nan = true;
vacc = _mm512_max_ps(vacc, v);
}
float acc = _mm512_reduce_max_ps(vacc);
for (; c < cols; ++c) {
float v = row[c];
if (is_nan_f32(v)) nan = true;
else if (v > acc) acc = v;
}
out[r] = nan ? kQNaN : acc;
} else { // sum / mean — `+` already propagates NaN
__m512 vacc = _mm512_setzero_ps();
for (; c + vstep <= cols; c += vstep)
vacc = _mm512_add_ps(vacc, _mm512_loadu_ps(row + c));
float acc = _mm512_reduce_add_ps(vacc);
for (; c < cols; ++c) acc += row[c];
out[r] = (kind == kMean && cols > 0) ? acc / (float)cols : acc;
}
}
}
107 changes: 107 additions & 0 deletions src/compiler/codegen/tessera_x86_backend/tests/test_reduce.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// On-device test for the AVX-512 row-reduction kernel (f32).
//
// Validates tessera_x86_avx512_reduce_f32 against the scalar reference AND a
// hand-computed expectation, across kinds (sum/max/mean) and shapes incl.
// non-multiple-of-16 column tails. Runs natively on the AVX-512 host — this is
// the "tested + running on the key device" proof for the CPU reduction lane.

#include <cmath>
#include <cstdint>
#include <cstdio>
#include <random>
#include <vector>

extern "C" void tessera_x86_reference_reduce_f32(const float*, int64_t, int64_t,
float*, int);
extern "C" void tessera_x86_avx512_reduce_f32(const float*, int64_t, int64_t,
float*, int);

static int g_fail = 0;

static void check(const char* name, int kind, int64_t rows, int64_t cols) {
std::mt19937 rng(1234 + (unsigned)(rows * 131 + cols * 7 + kind));
std::uniform_real_distribution<float> dist(-3.0f, 3.0f);
std::vector<float> x((size_t)rows * cols);
for (auto& v : x) v = dist(rng);

std::vector<float> ref(rows), avx(rows);
tessera_x86_reference_reduce_f32(x.data(), rows, cols, ref.data(), kind);
tessera_x86_avx512_reduce_f32(x.data(), rows, cols, avx.data(), kind);

for (int64_t r = 0; r < rows; ++r) {
// hand-computed expectation (independent of both kernels). NaN must
// propagate (numpy semantics) — for any kind, a NaN in the row => NaN.
bool row_nan = false;
double want = (kind == 1) ? -1e30 : 0.0;
for (int64_t c = 0; c < cols; ++c) {
double v = x[(size_t)r * cols + c];
if (std::isnan(v)) { row_nan = true; continue; }
if (kind == 1) want = v > want ? v : want;
else want += v;
}
if (kind == 2 && cols > 0) want /= (double)cols;

if (row_nan) {
// every kind must yield NaN from both kernels
if (!std::isnan(avx[r]) || !std::isnan(ref[r])) {
std::printf("FAIL %s kind=%d [%lld,%lld] row %lld: NaN not "
"propagated: avx=%g ref=%g\n", name, kind,
(long long)rows, (long long)cols, (long long)r,
avx[r], ref[r]);
++g_fail;
return;
}
continue;
}

float tol = 1e-3f * (1.0f + std::fabs((float)want));
if (std::fabs(avx[r] - ref[r]) > tol ||
std::fabs(avx[r] - (float)want) > tol) {
std::printf("FAIL %s kind=%d [%lld,%lld] row %lld: avx=%g ref=%g "
"want=%g\n", name, kind, (long long)rows,
(long long)cols, (long long)r, avx[r], ref[r],
(double)want);
++g_fail;
return;
}
}
std::printf("ok %s kind=%d [%lld,%lld]\n", name, kind, (long long)rows,
(long long)cols);
}

// NaN propagation: a row with a NaN must reduce to NaN for every kind, in both
// the vector body and the scalar tail (matches numpy np.amax/np.sum semantics).
static void check_nan(int kind, int64_t cols, int64_t nan_col) {
const int64_t rows = 3;
std::vector<float> x((size_t)rows * cols, 1.5f);
std::vector<float> avx(rows), ref(rows);
for (int64_t r = 0; r < rows; ++r)
x[(size_t)r * cols + nan_col] = std::nanf("");
tessera_x86_avx512_reduce_f32(x.data(), rows, cols, avx.data(), kind);
tessera_x86_reference_reduce_f32(x.data(), rows, cols, ref.data(), kind);
for (int64_t r = 0; r < rows; ++r) {
if (!std::isnan(avx[r]) || !std::isnan(ref[r])) {
std::printf("FAIL nan kind=%d cols=%lld nan_col=%lld row %lld: "
"avx=%g ref=%g\n", kind, (long long)cols,
(long long)nan_col, (long long)r, avx[r], ref[r]);
++g_fail;
return;
}
}
std::printf("ok nan kind=%d cols=%lld nan_col=%lld\n", kind,
(long long)cols, (long long)nan_col);
}

int main() {
for (int kind = 0; kind <= 2; ++kind) {
check("aligned", kind, 4, 64); // cols multiple of 16
check("tail", kind, 8, 70); // cols % 16 != 0
check("small", kind, 3, 5); // cols < 16 (all scalar tail)
check("wide", kind, 2, 1024); // many vector steps
check("onecol", kind, 5, 1); // degenerate
check_nan(kind, 64, 5); // NaN in the vector body
check_nan(kind, 70, 67); // NaN in the scalar tail
}
std::printf(g_fail ? "\n%d FAILED\n" : "\nALL PASSED\n", g_fail);
return g_fail ? 1 : 0;
}
Loading