Foundry/tt/tests/test_memory_ops.c

1314 lines
60 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file test_memory_ops.c
* @brief OFF-HARDWARE gate for the memory op family — EMBEDDING and KV_APPEND — and for
* the KV cache layout contract (ft_tt_op_memory.h PART 1).
*
* ---------------------------------------------------------------------------
* A COPY OP IS WHERE A TEST LIES MOST EASILY
* ---------------------------------------------------------------------------
* A gather that returns the right BYTES from the WRONG ROW looks right whenever every row
* holds similar data, and a face-order mistake is invisible to any metric computed through
* the same wrong arithmetic (docs/tt_kernel_model.md §9). So this suite does four things
* deliberately, and every one of them was needed to make some mutation go red:
*
* 1. **Every source row holds different content**, from a per-row generator whose values
* are full-mantissa bit patterns — never small integers and never a per-row constant.
* 2. **Every destination is poisoned first**, and the poison is checked to SURVIVE
* everywhere the op was not supposed to write. That half is what proves an APPEND
* preserves the positions already in the cache, and no PCC-shaped check can see it.
* 3. **The comparison is BYTE-FOR-BYTE, never a correlation.** PCC is blind to a
* consistent permutation, which is exactly the failure mode of a layout op (dev notes
* §3). The expected image is built element by element through
* `ft_tt_tile_face_index()` — ft_tt_tilize.c, written for the weight converter and
* verified against tt-metal's own tilize_utils.cpp — so it shares no code with the
* thing under test.
* 4. **The functions under test are THE KERNEL'S**, not a C copy of them. The arithmetic
* half of kernels/resident_memory_protocol.h compiles on the host and is reached
* through tests/ft_tt_memory_device_shim.c. A transcription would have agreed with
* itself.
*
* ---------------------------------------------------------------------------
* WHAT THIS CANNOT PROVE
* ---------------------------------------------------------------------------
* That a 32-byte DRAM write is legal, which is the fact the whole family rests on. Nothing
* off-hardware can — "off-hardware suites cannot cover the copy path" (dev notes §3), and
* here they cannot cover the NoC's alignment rules either. tests/test_phase6_memory.c
* measures it on silicon, against neighbours as well as against the row.
*
* Environment:
* FT_TT_KERNEL_DIR where the device headers live (default ../kernels)
*
* @status GREEN: 95 checks, 0 failures, on the Mac (clang) and on the card host (gcc). TWELVE
* mutations were run against it and ALL TWELVE went red — 14, 11, 10, 2, 6, 9, 15, 3, 6,
* 2, 1 and 1 failures. SIX were in the KERNEL's own header (the face index, a run that
* emits one column half, a run one face row too long, the face pairing of a row-major
* source, a place that reads the wrong source row, and a run split that ignores the
* 16-row face group boundary); TWO in the host layout arithmetic (the cache axis order,
* and a split rule that ignores the source tile row); FOUR in the model's control flow
* (the reader's fast path, the gather's index array, the whole-tiles flag, the
* zero-fill).
* @issues None
* @todo None
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../ft_tt_op_dispatch.h"
#include "../ft_tt_op_l1_layout.h"
#include "../ft_tt_op_memory.h"
#include "../ft_tt_queue.h"
#include "../ft_tt_tilize.h"
#include "ft_tt_test_report.h"
/** Where the device headers live, so the wire-constant pin reads the real file. */
#define KERNEL_DIRECTORY_ENVIRONMENT "FT_TT_KERNEL_DIR"
#define KERNEL_DIRECTORY_DEFAULT "../kernels"
#define PATH_MAX_BYTES 512u
#define TILE_BYTES FT_TT_OP_TILE_BYTES_BF16
#define TILE_DIM FT_TT_OP_TILE_DIM
/** Two patterns, and they answer different questions. POISON is what a destination holds
* before the op, so "the op did not write here" and "the op wrote a zero" are different
* bit patterns. STALE is what a circular-buffer page holds before the reader fills it, so
* a missing zero-fill publishes something recognisable instead of a plausible zero. */
#define POISON_BYTE 0xA5u
#define STALE_BYTE 0xCCu
/* Declared by tests/ft_tt_memory_device_shim.c — the KERNEL's own arithmetic. */
uint32_t ft_tt_device_chunk_bytes(uint32_t tile_bytes);
uint32_t ft_tt_device_face_bytes(uint32_t tile_bytes);
uint32_t ft_tt_device_chunk_offset(uint32_t row, uint32_t half, uint32_t tile_bytes);
uint32_t ft_tt_device_group_row_bytes(uint32_t group_tiles, uint32_t tile_bytes);
void ft_tt_device_place_row_from_row_major(uint32_t *destination_block,
const uint32_t *source_row, uint32_t destination_row,
uint32_t group_tiles, uint32_t tile_bytes);
void ft_tt_device_place_row_from_tiles(uint32_t *destination_block, const uint32_t *source_block,
uint32_t source_row, uint32_t destination_row,
uint32_t group_tiles, uint32_t tile_bytes);
void ft_tt_device_zero_words(uint32_t *destination, uint32_t words);
uint32_t ft_tt_device_destination_runs(uint32_t row0, uint32_t row_count, uint32_t tile_bytes,
uint32_t *offsets, uint32_t *bytes);
uint32_t ft_tt_device_max_runs(void);
int ft_tt_device_length_is_issuable(uint32_t bytes);
static unsigned char g_cb_buffer[FT_TT_NUM_CIRCULAR_BUFFERS * FT_TT_WORDS_PER_LOCAL_CB_CONFIG * 4];
/* ================================================================
* Test data that cannot be confused between rows
* ================================================================ */
/**
* The bf16 bit pattern for logical element (row, column) of source `stream`.
*
* Every (stream, row, column) gets a DIFFERENT value, and the row contributes a large
* multiplier, so "the right bytes from the wrong row" is a mismatch in every element rather
* than in none. The pattern also avoids POISON_BYTE and STALE_BYTE in both halves, so a
* value can never be mistaken for un-written memory.
*/
static uint16_t source_pattern(uint32_t stream, uint32_t row, uint32_t column)
{
uint32_t mixed = (stream * 2654435761u) ^ (row * 40503u) ^ (column * 2246822519u);
mixed ^= mixed >> 13;
mixed = (mixed * 1103515245u) + 12345u;
mixed >>= 9;
mixed &= 0xFFFFu;
if ((mixed & 0xFFu) == POISON_BYTE || (mixed & 0xFFu) == STALE_BYTE)
mixed ^= 0x0001u;
if (((mixed >> 8) & 0xFFu) == POISON_BYTE || ((mixed >> 8) & 0xFFu) == STALE_BYTE)
mixed ^= 0x0100u;
return (uint16_t)mixed;
}
/** Write the bf16 element (row, column) of a face-ordered tile grid `tiles` wide. */
static void put_tiled_element(unsigned char *base, uint32_t tiles_wide, uint32_t row,
uint32_t column, uint16_t value)
{
const uint32_t tile = column / TILE_DIM;
const uint32_t tile_row = row / TILE_DIM;
const size_t offset = ((size_t)tile_row * tiles_wide + tile) * TILE_BYTES +
(size_t)ft_tt_tile_face_index(row % TILE_DIM, column % TILE_DIM) * 2u;
(void)tiles_wide;
memcpy(base + offset, &value, sizeof(value));
}
/* ================================================================
* A model of the two kernels, driven by the KERNEL's own arithmetic
* ================================================================ */
/**
* Run one descriptor the way resident_gather_reader.c and resident_place_writer.c do,
* over simulated device memory.
*
* The control flow is a transcription — that much is unavoidable off-hardware — but every
* address it computes comes from the kernel's own inline functions through the shim, and
* the ANSWER is scored against an expectation built out of ft_tt_tilize.c. So a bug in the
* face arithmetic goes red here; a bug in the control flow goes red on the card.
*/
static void model_run(const uint32_t words[FT_TT_OP_MEMORY_WORDS], const unsigned char *source,
unsigned char *destination, const uint32_t *indices)
{
const uint32_t flags = words[FT_TT_OP_MEMORY_FLAGS];
const uint32_t row_count = words[FT_TT_OP_MEMORY_ROW_COUNT];
const uint32_t group_count = words[FT_TT_OP_MEMORY_GROUP_COUNT];
const uint32_t group_tiles = words[FT_TT_OP_MEMORY_GROUP_TILES];
const uint32_t source_base = words[FT_TT_OP_MEMORY_SRC_BASE];
const uint32_t source_row_stride = words[FT_TT_OP_MEMORY_SRC_ROW_STRIDE];
const uint32_t source_row0 = words[FT_TT_OP_MEMORY_SRC_ROW0];
const uint32_t source_group_stride = words[FT_TT_OP_MEMORY_SRC_GROUP_STRIDE];
const uint32_t destination_base = words[FT_TT_OP_MEMORY_DST_BASE];
const uint32_t destination_group_stride = words[FT_TT_OP_MEMORY_DST_GROUP_STRIDE];
const uint32_t destination_row0 = words[FT_TT_OP_MEMORY_DST_ROW0];
const uint32_t row_in_tile = destination_row0 % TILE_DIM;
const uint32_t tile_row = destination_row0 / TILE_DIM;
const uint32_t group_bytes = group_tiles * TILE_BYTES;
const uint32_t row_bytes = ft_tt_device_group_row_bytes(group_tiles, TILE_BYTES);
const bool whole_tiles = (flags & (uint32_t)FT_TT_OP_MEMORY_FLAG_DST_WHOLE_TILES) != 0u;
const bool tiled_source = (flags & (uint32_t)FT_TT_OP_MEMORY_FLAG_SRC_TILED) != 0u;
const bool gather = (flags & (uint32_t)FT_TT_OP_MEMORY_FLAG_GATHER) != 0u;
unsigned char *block = malloc(group_bytes);
unsigned char *stage = malloc(group_bytes);
uint32_t group;
if (!block || !stage) {
free(block);
free(stage);
return;
}
for (group = 0; group < group_count; group++) {
const uint32_t source_group = source_base + group * source_group_stride;
const uint32_t destination_group = destination_base + group * destination_group_stride +
tile_row * group_bytes;
uint32_t row;
/* A circular-buffer page is REUSED, so it starts as whatever the previous descriptor
* left. Modelling that is what makes a missing zero-fill visible. */
memset(block, (int)STALE_BYTE, group_bytes);
if (whole_tiles && row_count < TILE_DIM)
ft_tt_device_zero_words((uint32_t *)(void *)block, group_bytes / 4u);
if (tiled_source) {
const bool direct =
source_row0 == row_in_tile && !(whole_tiles && row_count < TILE_DIM);
memcpy(direct ? block : stage, source + source_group, group_bytes);
if (!direct) {
for (row = 0; row < row_count; row++) {
ft_tt_device_place_row_from_tiles((uint32_t *)(void *)block,
(const uint32_t *)(const void *)stage,
source_row0 + row, row_in_tile + row,
group_tiles, TILE_BYTES);
}
}
} else {
for (row = 0; row < row_count; row++) {
const uint32_t index = gather ? indices[row] : source_row0 + row;
memcpy(stage + row * row_bytes,
source + source_group + index * source_row_stride, row_bytes);
}
for (row = 0; row < row_count; row++) {
ft_tt_device_place_row_from_row_major(
(uint32_t *)(void *)block,
(const uint32_t *)(const void *)(stage + row * row_bytes),
row_in_tile + row, group_tiles, TILE_BYTES);
}
}
if (whole_tiles || (row_in_tile == 0u && row_count == TILE_DIM)) {
memcpy(destination + destination_group, block, group_bytes);
} else {
uint32_t offsets[8];
uint32_t lengths[8];
const uint32_t run_count =
ft_tt_device_destination_runs(row_in_tile, row_count, TILE_BYTES, offsets,
lengths);
uint32_t tile;
for (tile = 0; tile < group_tiles; tile++) {
uint32_t index;
for (index = 0; index < run_count; index++) {
const uint32_t offset = tile * TILE_BYTES + offsets[index];
memcpy(destination + destination_group + offset, block + offset,
lengths[index]);
}
}
}
}
free(block);
free(stage);
}
/* ================================================================
* Reading a constant out of a device header
* ================================================================ */
static int read_device_constant(const char *path, const char *name, uint32_t *out_value)
{
char line[512];
FILE *file = fopen(path, "r");
int found = -1;
if (!file)
return -1;
while (fgets(line, sizeof(line), file)) {
char parsed_name[128];
char parsed_value[64];
if (sscanf(line, " enum { %127[A-Za-z0-9_] = %63[^}] };", parsed_name,
parsed_value) != 2)
continue;
if (strcmp(parsed_name, name) != 0)
continue;
*out_value = (uint32_t)strtoul(parsed_value, NULL, 0);
found = 0;
break;
}
fclose(file);
return found;
}
static void device_header_path(char *out, size_t out_bytes, const char *file)
{
const char *directory = getenv(KERNEL_DIRECTORY_ENVIRONMENT);
if (!directory || directory[0] == '\0')
directory = KERNEL_DIRECTORY_DEFAULT;
snprintf(out, out_bytes, "%s/%s", directory, file);
}
static void check_device_constant(const char *path, const char *name, uint32_t host_value)
{
uint32_t device_value = 0;
if (read_device_constant(path, name, &device_value) != 0) {
ft_tt_test_check_fmt(name, false, "not declared in %s", path);
return;
}
ft_tt_test_check_fmt(name, device_value == host_value, "host %u, kernel %u", host_value,
device_value);
}
static void test_wire_constants_agree(void)
{
char memory_path[PATH_MAX_BYTES];
char queue_path[PATH_MAX_BYTES];
ft_tt_test_step("wire constants: the host enum against the kernel's own header");
device_header_path(memory_path, sizeof(memory_path), "resident_memory_protocol.h");
device_header_path(queue_path, sizeof(queue_path), "resident_queue_protocol.h");
check_device_constant(memory_path, "kMemFlags", FT_TT_OP_MEMORY_FLAGS);
check_device_constant(memory_path, "kMemRowCount", FT_TT_OP_MEMORY_ROW_COUNT);
check_device_constant(memory_path, "kMemGroupCount", FT_TT_OP_MEMORY_GROUP_COUNT);
check_device_constant(memory_path, "kMemGroupTiles", FT_TT_OP_MEMORY_GROUP_TILES);
check_device_constant(memory_path, "kMemSrcX", FT_TT_OP_MEMORY_SRC_X);
check_device_constant(memory_path, "kMemSrcY", FT_TT_OP_MEMORY_SRC_Y);
check_device_constant(memory_path, "kMemSrcBase", FT_TT_OP_MEMORY_SRC_BASE);
check_device_constant(memory_path, "kMemSrcRowStride", FT_TT_OP_MEMORY_SRC_ROW_STRIDE);
check_device_constant(memory_path, "kMemSrcRow0", FT_TT_OP_MEMORY_SRC_ROW0);
check_device_constant(memory_path, "kMemSrcGroupStride", FT_TT_OP_MEMORY_SRC_GROUP_STRIDE);
check_device_constant(memory_path, "kMemDstX", FT_TT_OP_MEMORY_DST_X);
check_device_constant(memory_path, "kMemDstY", FT_TT_OP_MEMORY_DST_Y);
check_device_constant(memory_path, "kMemDstBase", FT_TT_OP_MEMORY_DST_BASE);
check_device_constant(memory_path, "kMemDstGroupStride", FT_TT_OP_MEMORY_DST_GROUP_STRIDE);
check_device_constant(memory_path, "kMemDstRow0", FT_TT_OP_MEMORY_DST_ROW0);
check_device_constant(memory_path, "kMemWords", FT_TT_OP_MEMORY_WORDS);
check_device_constant(memory_path, "kMemMaxRows", FT_TT_OP_MEMORY_MAX_ROWS);
check_device_constant(memory_path, "kMemFlagSrcTiled", FT_TT_OP_MEMORY_FLAG_SRC_TILED);
check_device_constant(memory_path, "kMemFlagDstWholeTiles",
FT_TT_OP_MEMORY_FLAG_DST_WHOLE_TILES);
check_device_constant(memory_path, "kMemFlagGather", FT_TT_OP_MEMORY_FLAG_GATHER);
check_device_constant(memory_path, "kMemFaceRows", FT_TT_OP_MEMORY_FACE_ROWS);
check_device_constant(queue_path, "kOpEmbedding", FT_TT_QUEUE_OP_EMBEDDING);
check_device_constant(queue_path, "kOpKvAppend", FT_TT_QUEUE_OP_KV_APPEND);
}
/* ================================================================
* The face arithmetic, against an independent implementation
* ================================================================ */
static void test_chunk_offset_is_the_tilize_layout(void)
{
uint32_t row;
unsigned mismatches = 0;
unsigned host_mismatches = 0;
ft_tt_test_step("face arithmetic: the KERNEL's chunk offsets against ft_tt_tilize.c");
for (row = 0; row < TILE_DIM; row++) {
uint32_t half;
for (half = 0; half < 2u; half++) {
const uint32_t expected =
ft_tt_tile_face_index(row, half * 16u) * 2u; /* bf16 */
const uint32_t device = ft_tt_device_chunk_offset(row, half, TILE_BYTES);
const uint32_t host = ft_tt_op_memory_chunk_offset(row, half, TILE_BYTES);
if (device != expected)
mismatches++;
if (host != device)
host_mismatches++;
}
}
ft_tt_test_check_fmt("all 64 face rows land where ft_tt_tilize puts them",
mismatches == 0u, "%u of 64 wrong", mismatches);
ft_tt_test_check_fmt("and the host copy agrees with the kernel's", host_mismatches == 0u,
"%u of 64 differ", host_mismatches);
ft_tt_test_check_fmt("a face row is 32 bytes at bf16",
ft_tt_device_chunk_bytes(TILE_BYTES) == 32u, "%u",
ft_tt_device_chunk_bytes(TILE_BYTES));
ft_tt_test_check_fmt("and 64 at fp32",
ft_tt_device_chunk_bytes(FT_TT_OP_TILE_BYTES_FP32) == 64u, "%u",
ft_tt_device_chunk_bytes(FT_TT_OP_TILE_BYTES_FP32));
ft_tt_test_check_fmt("a face is a quarter of a tile",
ft_tt_device_face_bytes(TILE_BYTES) == 512u, "%u",
ft_tt_device_face_bytes(TILE_BYTES));
/* The property the whole family rests on: every partial-write address and length is a
* multiple of the 16-byte DRAM write alignment. */
ft_tt_test_check("every face row is a multiple of the 16-byte DRAM write alignment",
ft_tt_device_chunk_bytes(TILE_BYTES) % 16u == 0u &&
ft_tt_device_chunk_bytes(FT_TT_OP_TILE_BYTES_FP32) % 16u == 0u,
"32 and 64");
}
/**
* The runs of a row range cover EXACTLY that range's bytes — no byte of another row, and
* no byte of the range missed.
*
* Checked by painting: mark every byte the runs claim, then walk the tile element by
* element through ft_tt_tile_face_index() and demand that a byte is marked if and only if
* its row is in the range. A run that spilled into row 16 would be invisible to any check
* that only looked at the rows it meant to write.
*/
static void test_destination_runs_cover_exactly_the_rows(void)
{
uint32_t row0;
unsigned bad_ranges = 0;
unsigned worst_runs = 0;
ft_tt_test_step("destination runs: exactly the rows asked for, and nothing beside them");
for (row0 = 0; row0 < TILE_DIM; row0++) {
uint32_t count;
for (count = 1u; row0 + count <= TILE_DIM; count++) {
unsigned char marked[FT_TT_OP_TILE_BYTES_BF16];
uint32_t offsets[8];
uint32_t lengths[8];
uint32_t run_count =
ft_tt_device_destination_runs(row0, count, TILE_BYTES, offsets, lengths);
uint32_t index;
uint32_t row;
bool ok = true;
memset(marked, 0, sizeof(marked));
if (run_count == 0u || run_count > ft_tt_device_max_runs()) {
bad_ranges++;
continue;
}
if (run_count > worst_runs)
worst_runs = run_count;
for (index = 0; index < run_count; index++) {
uint32_t byte;
for (byte = 0; byte < lengths[index]; byte++)
marked[offsets[index] + byte] = 1u;
}
for (row = 0; row < TILE_DIM && ok; row++) {
const bool wanted = row >= row0 && row < row0 + count;
uint32_t column;
for (column = 0; column < TILE_DIM; column++) {
const uint32_t at = ft_tt_tile_face_index(row, column) * 2u;
if ((marked[at] != 0u) != wanted || (marked[at + 1u] != 0u) != wanted) {
ok = false;
break;
}
}
}
if (!ok)
bad_ranges++;
}
}
ft_tt_test_check_fmt("every one of the 528 row ranges is covered exactly",
bad_ranges == 0u, "%u wrong", bad_ranges);
ft_tt_test_check_fmt("and never needs more than four runs", worst_runs <= 4u, "worst %u",
worst_runs);
ft_tt_test_check("a zero-length or out-of-range request yields no runs",
ft_tt_device_destination_runs(0u, 0u, TILE_BYTES, NULL, NULL) == 0u &&
ft_tt_device_destination_runs(30u, 4u, TILE_BYTES, NULL, NULL) == 0u,
"");
}
/* ================================================================
* The KV cache layout contract
* ================================================================ */
static void test_kv_cache_layout(void)
{
ft_tt_kv_cache_layout layout;
ft_tt_kv_cache_layout model;
uint64_t offset;
uint32_t bytes;
int rc;
ft_tt_test_step("KV cache layout: the contract as arithmetic");
/* MiniCPM5-1B, read from the GGUF header (docs/tt_phase0_decisions.md §3) and never
* assumed: 2 KV heads, head_dim 128, context 131072. */
rc = ft_tt_kv_cache_layout_init(2u, 128u, 131072u, TILE_BYTES, &model);
ft_tt_test_check_fmt("MiniCPM5-1B's shape derives", rc == FT_TT_OK, "rc=%d", rc);
ft_tt_test_check_fmt("dim_tiles is 4 and capacity_tiles 4096",
model.dim_tiles == 4u && model.capacity_tiles == 4096u, "%u, %u",
model.dim_tiles, model.capacity_tiles);
ft_tt_test_check_fmt("one region is 64 MiB, so K+V over 24 layers is 3 GiB",
model.region_bytes == 64ull * 1024ull * 1024ull, "%llu bytes",
(unsigned long long)model.region_bytes);
rc = ft_tt_kv_cache_layout_init(2u, 128u, 200u, TILE_BYTES, &layout);
ft_tt_test_check_fmt("a capacity is rounded UP to a whole position tile",
rc == FT_TT_OK && layout.capacity_positions == 224u &&
layout.capacity_tiles == 7u,
"rc=%d capacity=%u tiles=%u", rc, layout.capacity_positions,
layout.capacity_tiles);
/* F2: the axis order. Consecutive dim tiles are adjacent; the next position tile
* follows the last dim tile; a head is head_stride away. */
ft_tt_test_check(
"dim tiles are adjacent",
ft_tt_kv_cache_tile_offset(&layout, 0u, 0u, 1u) -
ft_tt_kv_cache_tile_offset(&layout, 0u, 0u, 0u) == TILE_BYTES,
"");
ft_tt_test_check("the next position tile follows the last dim tile",
ft_tt_kv_cache_tile_offset(&layout, 0u, 32u, 0u) -
ft_tt_kv_cache_tile_offset(&layout, 0u, 31u, 3u) == TILE_BYTES,
"");
ft_tt_test_check("a head is head_stride away",
ft_tt_kv_cache_tile_offset(&layout, 1u, 0u, 0u) == layout.head_stride,
"");
ft_tt_test_check("an out-of-range index refuses rather than wrapping",
ft_tt_kv_cache_tile_offset(&layout, 2u, 0u, 0u) == UINT64_MAX &&
ft_tt_kv_cache_tile_offset(&layout, 0u, 224u, 0u) == UINT64_MAX &&
ft_tt_kv_cache_tile_offset(&layout, 0u, 0u, 4u) == UINT64_MAX,
"");
/* §1.2: the property attention is written against. */
rc = ft_tt_kv_cache_run(&layout, 1u, 64u, 96u, &offset, &bytes);
ft_tt_test_check_fmt("a position range is ONE run", rc == FT_TT_OK, "rc=%d", rc);
ft_tt_test_check_fmt("it starts at the first position's tile",
offset == ft_tt_kv_cache_tile_offset(&layout, 1u, 64u, 0u),
"%llu", (unsigned long long)offset);
ft_tt_test_check_fmt("and it is exactly the positions asked for",
bytes == 3u * layout.position_tile_bytes, "%u bytes", bytes);
ft_tt_test_check("the run ends where the next one begins — no gap, no stride",
offset + bytes == ft_tt_kv_cache_tile_offset(&layout, 1u, 160u, 0u), "");
ft_tt_test_check(
"a run that is not a whole number of position tiles is refused, not rounded",
ft_tt_kv_cache_run(&layout, 0u, 16u, 32u, &offset, &bytes) == FT_TT_EINVAL &&
ft_tt_kv_cache_run(&layout, 0u, 0u, 33u, &offset, &bytes) == FT_TT_EINVAL,
"");
ft_tt_test_check("a run past the capacity is refused",
ft_tt_kv_cache_run(&layout, 0u, 192u, 64u, &offset, &bytes) ==
FT_TT_EINVAL,
"");
/* §1.6: block float is refused BY NAME. */
ft_tt_test_check("a Bfp8_b cache is refused, and so is Bfp4_b",
ft_tt_kv_cache_layout_init(2u, 128u, 64u, FT_TT_OP_TILE_BYTES_BFP8_B,
&layout) == FT_TT_EINVAL &&
ft_tt_kv_cache_layout_init(2u, 128u, 64u, FT_TT_OP_TILE_BYTES_BFP4_B,
&layout) == FT_TT_EINVAL,
"");
ft_tt_test_check("a head dimension that is not a whole number of tiles is refused",
ft_tt_kv_cache_layout_init(2u, 100u, 64u, TILE_BYTES, &layout) ==
FT_TT_EINVAL,
"");
}
/* ================================================================
* KV_APPEND, byte for byte
* ================================================================ */
#define APPEND_HEADS 2u
#define APPEND_HEAD_DIM 128u
#define APPEND_DIM_TILES (APPEND_HEAD_DIM / TILE_DIM)
#define APPEND_SOURCE_TILES (APPEND_HEADS * APPEND_DIM_TILES)
#define APPEND_CAPACITY 128u
/**
* One append, scored against an expectation built element by element.
*
* `stream` seeds the source content so no two calls in this file can pass on each other's
* data.
*/
static bool run_append_case(const char *label, uint32_t position, uint32_t source_row0,
uint32_t token_count, uint32_t stream)
{
ft_tt_kv_cache_layout layout;
ft_tt_op_kv_append_request request;
uint32_t words[FT_TT_OP_MEMORY_WORDS];
unsigned char *source = NULL;
unsigned char *cache = NULL;
unsigned char *expected = NULL;
size_t source_bytes;
size_t cache_bytes;
uint32_t row;
uint32_t head;
size_t wrong = 0;
size_t disturbed = 0;
bool ok = false;
int rc;
rc = ft_tt_kv_cache_layout_init(APPEND_HEADS, APPEND_HEAD_DIM, APPEND_CAPACITY, TILE_BYTES,
&layout);
if (rc != FT_TT_OK) {
ft_tt_test_check_fmt(label, false, "layout rc=%d", rc);
return false;
}
source_bytes = (size_t)APPEND_SOURCE_TILES * TILE_BYTES * 2u; /* two source tile rows */
cache_bytes = (size_t)layout.region_bytes;
source = malloc(source_bytes);
cache = malloc(cache_bytes);
expected = malloc(cache_bytes);
if (!source || !cache || !expected) {
ft_tt_test_check(label, false, "out of memory");
goto done;
}
/* Per-ROW distinguishable content: a gather that returned the right bytes from the
* wrong row cannot pass this. */
memset(source, 0, source_bytes);
for (row = 0; row < TILE_DIM * 2u; row++) {
uint32_t column;
for (column = 0; column < APPEND_HEADS * APPEND_HEAD_DIM; column++)
put_tiled_element(source, APPEND_SOURCE_TILES, row, column,
source_pattern(stream, row, column));
}
memset(cache, (int)POISON_BYTE, cache_bytes);
memcpy(expected, cache, cache_bytes);
for (head = 0; head < APPEND_HEADS; head++) {
uint32_t token;
for (token = 0; token < token_count; token++) {
uint32_t dimension;
for (dimension = 0; dimension < APPEND_HEAD_DIM; dimension++) {
const uint64_t tile = ft_tt_kv_cache_tile_offset(&layout, head, position + token,
dimension / TILE_DIM);
const size_t at = (size_t)tile + (size_t)ft_tt_tile_face_index(
(position + token) % TILE_DIM,
dimension % TILE_DIM) *
2u;
const uint16_t value =
source_pattern(stream, source_row0 + token, head * APPEND_HEAD_DIM + dimension);
memcpy(expected + at, &value, sizeof(value));
}
}
}
memset(&request, 0, sizeof(request));
request.cache = &layout;
request.position = position;
request.token_count = token_count;
request.source.x = 1u;
request.source.y = 2u;
request.source.address = 0u;
request.source.tile_bytes = TILE_BYTES;
request.source_row0 = source_row0;
request.source_tiles = APPEND_SOURCE_TILES;
request.region.x = 3u;
request.region.y = 4u;
request.region.address = 0u;
request.region.tile_bytes = TILE_BYTES;
rc = ft_tt_op_kv_append_parameters(&request, words);
if (rc != FT_TT_OK) {
ft_tt_test_check_fmt(label, false, "encode rc=%d", rc);
goto done;
}
model_run(words, source, cache, NULL);
{
size_t index;
for (index = 0; index < cache_bytes; index++) {
if (cache[index] == expected[index])
continue;
if (expected[index] == POISON_BYTE)
disturbed++;
else
wrong++;
}
}
ft_tt_test_check_fmt(label, wrong == 0u && disturbed == 0u,
"%zu bytes wrong, %zu bytes of an untouched position disturbed",
wrong, disturbed);
ok = wrong == 0u && disturbed == 0u;
done:
free(source);
free(cache);
free(expected);
return ok;
}
static void test_kv_append(void)
{
ft_tt_test_step("KV_APPEND: byte for byte, against a poisoned cache");
/* Decode: one token, at four positions that exercise both face row groups and both
* position tiles. */
(void)run_append_case("one token at position 0", 0u, 0u, 1u, 1u);
(void)run_append_case("one token at position 7 (first face group)", 7u, 0u, 1u, 2u);
(void)run_append_case("one token at position 20 (second face group)", 20u, 0u, 1u, 3u);
(void)run_append_case("one token at position 63 (last row of the second tile)", 63u, 0u,
1u, 4u);
/* The source row need not be the destination row — decode reads row 0 of a projection
* whose position is anything. */
(void)run_append_case("source row 5 into position 19", 19u, 5u, 1u, 5u);
/* A range that CROSSES the face row group boundary: the case with four runs. */
(void)run_append_case("six tokens across rows 14..19", 14u, 14u, 6u, 6u);
(void)run_append_case("six tokens from source row 0 into rows 14..19", 14u, 0u, 6u, 7u);
/* Prefill: a whole aligned tile row, which collapses to one transaction per head. */
(void)run_append_case("a whole 32-token block at position 32", 32u, 32u, 32u, 8u);
(void)run_append_case("a whole 32-token block from source row 0", 96u, 0u, 32u, 9u);
/* The last position of the cache. */
(void)run_append_case("the last position of the cache", APPEND_CAPACITY - 1u, 3u, 1u, 10u);
}
/**
* Two appends in a row: the second must not disturb the first.
*
* This is the check that makes "an append PRESERVES" more than a claim about poison — the
* bytes it must preserve are real data at a neighbouring position, in the SAME tile, in the
* same face rows.
*/
static void test_kv_append_preserves_earlier_positions(void)
{
ft_tt_kv_cache_layout layout;
ft_tt_op_kv_append_request request;
uint32_t words[FT_TT_OP_MEMORY_WORDS];
unsigned char *source = NULL;
unsigned char *cache = NULL;
size_t source_bytes;
size_t cache_bytes;
uint32_t position;
uint32_t row;
size_t wrong = 0;
int rc;
ft_tt_test_step("KV_APPEND: appending position p+1 leaves position p alone");
rc = ft_tt_kv_cache_layout_init(APPEND_HEADS, APPEND_HEAD_DIM, APPEND_CAPACITY, TILE_BYTES,
&layout);
source_bytes = (size_t)APPEND_SOURCE_TILES * TILE_BYTES;
cache_bytes = (size_t)layout.region_bytes;
source = malloc(source_bytes);
cache = malloc(cache_bytes);
if (rc != FT_TT_OK || !source || !cache) {
ft_tt_test_check("the case sets up", false, "");
goto done;
}
memset(source, 0, source_bytes);
for (row = 0; row < TILE_DIM; row++) {
uint32_t column;
for (column = 0; column < APPEND_HEADS * APPEND_HEAD_DIM; column++)
put_tiled_element(source, APPEND_SOURCE_TILES, row, column,
source_pattern(99u, row, column));
}
memset(cache, (int)POISON_BYTE, cache_bytes);
/* Append 24 positions, ONE AT A TIME, exactly as decode does. Source row p is what
* position p receives, so every position holds different content. */
for (position = 0; position < 24u; position++) {
memset(&request, 0, sizeof(request));
request.cache = &layout;
request.position = position;
request.token_count = 1u;
request.source.address = 0u;
request.source.tile_bytes = TILE_BYTES;
request.source_row0 = position;
request.source_tiles = APPEND_SOURCE_TILES;
request.region.address = 0u;
request.region.tile_bytes = TILE_BYTES;
if (ft_tt_op_kv_append_parameters(&request, words) != FT_TT_OK) {
ft_tt_test_check_fmt("every step encodes", false, "position %u", position);
goto done;
}
model_run(words, source, cache, NULL);
}
for (position = 0; position < 24u; position++) {
uint32_t head;
for (head = 0; head < APPEND_HEADS; head++) {
uint32_t dimension;
for (dimension = 0; dimension < APPEND_HEAD_DIM; dimension++) {
const uint64_t tile =
ft_tt_kv_cache_tile_offset(&layout, head, position, dimension / TILE_DIM);
const size_t at =
(size_t)tile +
(size_t)ft_tt_tile_face_index(position % TILE_DIM, dimension % TILE_DIM) * 2u;
const uint16_t want =
source_pattern(99u, position, head * APPEND_HEAD_DIM + dimension);
uint16_t got;
memcpy(&got, cache + at, sizeof(got));
if (got != want)
wrong++;
}
}
}
ft_tt_test_check_fmt("all 24 positions survive 24 single-position appends", wrong == 0u,
"%zu of %u elements wrong", wrong,
24u * APPEND_HEADS * APPEND_HEAD_DIM);
done:
free(source);
free(cache);
}
/* ================================================================
* EMBEDDING, byte for byte
* ================================================================ */
#define EMBEDDING_VOCABULARY 64u
#define EMBEDDING_DIM 64u
#define EMBEDDING_TILES (EMBEDDING_DIM / TILE_DIM)
/**
* A gather, with a PERMUTED, repeating id list.
*
* The permutation is the point: a kernel that ignored the index array and read rows in
* order would return the right bytes from the wrong rows, which is the single most likely
* bug in a gather and the one a similar-looking table hides.
*/
static void test_embedding(bool zero_fill)
{
static const uint32_t ids[8] = { 40u, 3u, 17u, 3u, 63u, 0u, 55u, 8u };
ft_tt_op_embedding_request request;
uint32_t words[FT_TT_OP_MEMORY_WORDS];
unsigned char *table = NULL;
unsigned char *output = NULL;
unsigned char *expected = NULL;
const uint32_t row_bytes = EMBEDDING_DIM * 2u;
const uint32_t output_row0 = zero_fill ? 3u : 11u;
size_t table_bytes = (size_t)EMBEDDING_VOCABULARY * row_bytes;
size_t output_bytes = (size_t)EMBEDDING_TILES * TILE_BYTES;
uint32_t token;
uint32_t row;
size_t wrong = 0;
int rc;
ft_tt_test_step("EMBEDDING: a permuted gather with a repeat, %s",
zero_fill ? "zero-filling the rest of the tiles"
: "preserving the rest of the tiles");
table = malloc(table_bytes);
output = malloc(output_bytes);
expected = malloc(output_bytes);
if (!table || !output || !expected) {
ft_tt_test_check("the case sets up", false, "out of memory");
goto done;
}
/* A ROW-MAJOR table: row `id` is one contiguous run, which is the whole reason the
* table is not tilized (a face row is 32 B and a 32-byte DRAM READ is not legal). */
for (row = 0; row < EMBEDDING_VOCABULARY; row++) {
uint32_t column;
for (column = 0; column < EMBEDDING_DIM; column++) {
const uint16_t value = source_pattern(0x1234u, row, column);
memcpy(table + (size_t)row * row_bytes + (size_t)column * 2u, &value, sizeof(value));
}
}
memset(output, (int)POISON_BYTE, output_bytes);
memcpy(expected, output, output_bytes);
if (zero_fill)
memset(expected, 0, output_bytes);
for (token = 0; token < 8u; token++) {
uint32_t column;
for (column = 0; column < EMBEDDING_DIM; column++) {
const size_t at = (size_t)(column / TILE_DIM) * TILE_BYTES +
(size_t)ft_tt_tile_face_index(output_row0 + token,
column % TILE_DIM) *
2u;
const uint16_t value = source_pattern(0x1234u, ids[token], column);
memcpy(expected + at, &value, sizeof(value));
}
}
memset(&request, 0, sizeof(request));
request.token_count = 8u;
request.embedding_dim = EMBEDDING_DIM;
request.table.address = 0u;
request.table.tile_bytes = TILE_BYTES;
request.table_row_stride = row_bytes;
request.table_row_count = EMBEDDING_VOCABULARY;
request.output.address = 0u;
request.output.tile_bytes = TILE_BYTES;
request.output_bytes = (uint32_t)output_bytes;
request.output_row0 = output_row0;
request.zero_fill = zero_fill;
rc = ft_tt_op_embedding_parameters(&request, words);
if (rc != FT_TT_OK) {
ft_tt_test_check_fmt("the descriptor encodes", false, "rc=%d", rc);
goto done;
}
model_run(words, table, output, ids);
{
size_t index;
for (index = 0; index < output_bytes; index++) {
if (output[index] != expected[index])
wrong++;
}
}
ft_tt_test_check_fmt("the gathered rows land byte for byte, and nothing else moves",
wrong == 0u, "%zu of %zu bytes differ", wrong, output_bytes);
done:
free(table);
free(output);
free(expected);
}
/* ================================================================
* The split rule, the encoders' refusals, and the placement
* ================================================================ */
static void test_split_rule(void)
{
uint32_t positions[64];
uint32_t source_rows[64];
uint32_t counts[64];
uint32_t count = 0;
uint32_t index;
uint32_t total = 0;
bool contiguous = true;
int rc;
ft_tt_test_step("the split rule: a long append becomes descriptors that each fit one "
"tile row");
rc = ft_tt_op_kv_append_split(0u, 32u, 0u, positions, source_rows, counts, 64u, &count);
ft_tt_test_check_fmt("an aligned 32-token block is ONE descriptor",
rc == FT_TT_OK && count == 1u && counts[0] == 32u, "rc=%d count=%u",
rc, count);
rc = ft_tt_op_kv_append_split(5u, 100u, 0u, positions, source_rows, counts, 64u, &count);
ft_tt_test_check_fmt("100 tokens from position 5 split", rc == FT_TT_OK, "rc=%d", rc);
for (index = 0; index < count; index++) {
if ((positions[index] % TILE_DIM) + counts[index] > TILE_DIM ||
(source_rows[index] % TILE_DIM) + counts[index] > TILE_DIM)
contiguous = false;
if (index > 0 && (positions[index] != positions[index - 1] + counts[index - 1] ||
source_rows[index] != source_rows[index - 1] + counts[index - 1]))
contiguous = false;
total += counts[index];
}
ft_tt_test_check_fmt("every piece fits one source AND one destination tile row",
contiguous, "%u pieces", count);
ft_tt_test_check_fmt("and they account for every token", total == 100u, "%u of 100",
total);
/* Both ends bind independently: a source row and a position that are out of phase split
* more often than either would alone. */
rc = ft_tt_op_kv_append_split(0u, 64u, 16u, positions, source_rows, counts, 64u, &count);
ft_tt_test_check_fmt("an out-of-phase source splits on the SOURCE boundary too",
rc == FT_TT_OK && count == 4u && counts[0] == 16u, "count=%u first=%u",
count, counts[0]);
/* And keeping the two in phase is worth doing: 64 aligned tokens are 2 descriptors, the
* same 64 out of phase are 4. A projection output whose row 0 is position p0 is in
* phase by construction, so this is a property to preserve rather than a cost to pay. */
rc = ft_tt_op_kv_append_split(0u, 64u, 0u, positions, source_rows, counts, 64u, &count);
ft_tt_test_check_fmt("in phase, the same 64 tokens are half as many descriptors",
rc == FT_TT_OK && count == 2u, "count=%u", count);
rc = ft_tt_op_kv_append_split(5u, 100u, 0u, positions, source_rows, counts, 2u, &count);
ft_tt_test_check_fmt("too small an output array is refused, and still says how many",
rc == FT_TT_ENOMEM && count == 7u, "rc=%d count=%u", rc, count);
}
static void test_encoders_refuse_what_the_kernel_cannot_do(void)
{
ft_tt_kv_cache_layout layout;
ft_tt_op_kv_append_request append;
ft_tt_op_embedding_request embedding;
uint32_t words[FT_TT_OP_MEMORY_WORDS];
ft_tt_test_step("the encoders refuse what the kernel cannot do");
(void)ft_tt_kv_cache_layout_init(APPEND_HEADS, APPEND_HEAD_DIM, APPEND_CAPACITY, TILE_BYTES,
&layout);
memset(&append, 0, sizeof(append));
append.cache = &layout;
append.position = 0u;
append.token_count = 1u;
append.source.tile_bytes = TILE_BYTES;
append.source_tiles = APPEND_SOURCE_TILES;
append.region.tile_bytes = TILE_BYTES;
ft_tt_test_check("the baseline request encodes",
ft_tt_op_kv_append_parameters(&append, words) == FT_TT_OK, "");
append.position = 30u;
append.token_count = 4u;
ft_tt_test_check("a range crossing a destination tile row is refused",
ft_tt_op_kv_append_parameters(&append, words) == FT_TT_EINVAL, "");
append.position = 0u;
append.source_row0 = 30u;
ft_tt_test_check("a range crossing a SOURCE tile row is refused",
ft_tt_op_kv_append_parameters(&append, words) == FT_TT_EINVAL, "");
append.source_row0 = 0u;
append.token_count = 33u;
ft_tt_test_check("more than 32 rows is refused",
ft_tt_op_kv_append_parameters(&append, words) == FT_TT_EINVAL, "");
append.token_count = 1u;
append.position = APPEND_CAPACITY;
ft_tt_test_check("a position past the capacity is refused",
ft_tt_op_kv_append_parameters(&append, words) == FT_TT_EINVAL, "");
append.position = 0u;
append.source_tiles = APPEND_SOURCE_TILES - 1u;
ft_tt_test_check("a source too narrow for every head is refused",
ft_tt_op_kv_append_parameters(&append, words) == FT_TT_EINVAL, "");
append.source_tiles = APPEND_SOURCE_TILES;
append.source.tile_bytes = FT_TT_OP_TILE_BYTES_BFP8_B;
ft_tt_test_check("a source whose tile size disagrees with the cache is refused",
ft_tt_op_kv_append_parameters(&append, words) == FT_TT_EINVAL, "");
memset(&embedding, 0, sizeof(embedding));
embedding.token_count = 4u;
embedding.embedding_dim = 1536u;
embedding.table.tile_bytes = TILE_BYTES;
embedding.table_row_stride = 1536u * 2u;
embedding.table_row_count = 73448u;
embedding.output.tile_bytes = TILE_BYTES;
embedding.output_bytes = 48u * TILE_BYTES;
ft_tt_test_check("MiniCPM5-1B's 1536-wide embedding encodes",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_OK, "");
ft_tt_test_check_fmt("and asks for 48 tiles in one group",
words[FT_TT_OP_MEMORY_GROUP_TILES] == 48u &&
words[FT_TT_OP_MEMORY_GROUP_COUNT] == 1u,
"%u tiles, %u groups", words[FT_TT_OP_MEMORY_GROUP_TILES],
words[FT_TT_OP_MEMORY_GROUP_COUNT]);
ft_tt_test_check("every embedding descriptor is a GATHER",
(words[FT_TT_OP_MEMORY_FLAGS] &
(uint32_t)FT_TT_OP_MEMORY_FLAG_GATHER) != 0u,
"");
embedding.table_row_stride = 1536u * 2u + 32u;
ft_tt_test_check("a table row stride that breaks the 64-byte DRAM read alignment is "
"refused",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_EINVAL, "");
embedding.table_row_stride = 1536u * 2u;
embedding.embedding_dim = 1500u;
ft_tt_test_check("a width that is not a whole number of tiles is refused",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_EINVAL, "");
embedding.embedding_dim = 1536u;
embedding.output_row0 = 30u;
embedding.token_count = 4u;
ft_tt_test_check("a token block crossing a destination tile row is refused",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_EINVAL, "");
/* THE TWO BOUNDS THE KERNEL REFUSES WITHOUT (gaps 4 and 5). Each is asserted against
* FT_TT_EINVAL specifically and re-armed afterwards, so the case that follows it is not
* being carried by the previous one still being broken — the failure mode a check with
* several sufficient grounds always has (DEV_NOTES.md §3). */
embedding.output_row0 = 0u;
embedding.token_count = 4u;
ft_tt_test_check("the request is green again before the bound cases",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_OK, "");
embedding.table_row_count = 0u;
ft_tt_test_check("a table with no declared row count is refused — an unbounded token id "
"is an arbitrary DRAM read",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_EINVAL, "");
embedding.table_row_count = 73448u;
embedding.output_bytes = 0u;
ft_tt_test_check("an output with no declared extent is refused — an absolute row0 is an "
"unbounded byte offset",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_EINVAL, "");
embedding.output_bytes = 48u * TILE_BYTES;
embedding.output_row0 = 32u;
ft_tt_test_check("and a destination tile row past that extent is refused, not clamped",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_EINVAL, "");
embedding.output_bytes = 2u * 48u * TILE_BYTES;
ft_tt_test_check("the same row0 against an extent that HOLDS it encodes",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_OK, "");
ft_tt_test_check_fmt("and both bounds ride the descriptor",
words[FT_TT_OP_MEMORY_SRC_ROW_LIMIT] == 73448u &&
words[FT_TT_OP_MEMORY_DST_GROUP_STRIDE] == 2u * 48u * TILE_BYTES,
"row limit %u, group bytes %u", words[FT_TT_OP_MEMORY_SRC_ROW_LIMIT],
words[FT_TT_OP_MEMORY_DST_GROUP_STRIDE]);
}
static void test_circular_buffers_fit_the_arena(void)
{
ft_tt_launch_cb_entry entries[FT_TT_OP_MEMORY_CB_COUNT];
uint64_t mask = 0;
uint32_t index_address;
int rc;
ft_tt_test_step("circular buffers: ONE OUTPUT CB PER GROUP SIZE, and each is a whole "
"multiple of its own group");
/* MiniCPM5-1B: 48 tiles of hidden width for the embedding, 4 dim-tiles a KV head. */
rc = ft_tt_op_memory_place_cbs(48u, 4u, TILE_BYTES, entries, g_cb_buffer,
sizeof(g_cb_buffer), &mask);
ft_tt_test_check_fmt("a 1536-wide embedding + 4-tile KV residency places", rc == FT_TT_OK,
"rc=%d", rc);
index_address = ft_tt_op_memory_index_address(entries, FT_TT_OP_MEMORY_CB_COUNT);
ft_tt_test_check_fmt("and the index array has an address in the arena",
index_address >= FT_TT_OP_CB_ARENA_BASE &&
index_address < FT_TT_OP_CB_ARENA_END,
"0x%X", index_address);
ft_tt_test_check_fmt("the mask names exactly the four circular buffers this op uses",
mask == ((1ull << FT_TT_OP_MEMORY_CB_STAGE) |
(1ull << FT_TT_OP_MEMORY_CB_INDEX) |
(1ull << FT_TT_OP_MEMORY_CB_OUT_EMBEDDING) |
(1ull << FT_TT_OP_MEMORY_CB_OUT_KV)),
"0x%llX", (unsigned long long)mask);
/* THE INVARIANT THE SPLIT EXISTS FOR, stated as the arithmetic the kernel's guard runs:
* each output CB holds a whole number of ITS OWN blocks, so its pointer is on that grid
* at every descriptor boundary. The two are DIFFERENT buffers — the check that would
* have failed before the split is that they do not overlap. */
{
const ft_tt_launch_cb_entry *embedding_out =
ft_tt_op_cb_find(entries, FT_TT_OP_MEMORY_CB_COUNT, FT_TT_OP_MEMORY_CB_OUT_EMBEDDING);
const ft_tt_launch_cb_entry *kv_out =
ft_tt_op_cb_find(entries, FT_TT_OP_MEMORY_CB_COUNT, FT_TT_OP_MEMORY_CB_OUT_KV);
ft_tt_test_check("both output circular buffers are placed",
embedding_out != NULL && kv_out != NULL, "");
if (embedding_out && kv_out) {
ft_tt_test_check_fmt("the embedding output holds a whole number of 48-tile blocks",
embedding_out->page_count % 48u == 0u, "%u pages",
embedding_out->page_count);
ft_tt_test_check_fmt("the KV output holds a whole number of 4-tile blocks",
kv_out->page_count % 4u == 0u, "%u pages", kv_out->page_count);
ft_tt_test_check_fmt("and they are DISJOINT byte ranges — the whole point of the "
"split, since one buffer cannot carry two group sizes",
embedding_out->address + embedding_out->size_bytes <=
kv_out->address ||
kv_out->address + kv_out->size_bytes <=
embedding_out->address,
"embedding 0x%X+%u kv 0x%X+%u", embedding_out->address,
embedding_out->size_bytes, kv_out->address,
kv_out->size_bytes);
}
}
rc = ft_tt_op_memory_place_cbs(0u, 4u, TILE_BYTES, entries, g_cb_buffer,
sizeof(g_cb_buffer), &mask);
ft_tt_test_check("a zero-width embedding group is refused", rc == FT_TT_EINVAL, "");
rc = ft_tt_op_memory_place_cbs(48u, 0u, TILE_BYTES, entries, g_cb_buffer,
sizeof(g_cb_buffer), &mask);
ft_tt_test_check("a zero-width KV group is refused", rc == FT_TT_EINVAL, "");
rc = ft_tt_op_memory_place_cbs(48u, 4u, FT_TT_OP_TILE_BYTES_BFP8_B, entries, g_cb_buffer,
sizeof(g_cb_buffer), &mask);
ft_tt_test_check("a block-float page size is refused", rc == FT_TT_EINVAL, "");
}
/* ================================================================
* The zero-length guard, and the cross-op row phase
* ================================================================ */
/**
* Nothing this family issues can be zero bytes long.
*
* A zero-length NoC read is suspected of taking the WHOLE CARD down — the RoPE round's
* card-down on 2026-08-12 happened at exactly the first descriptor that computed one
* (INFERRED, and deliberately not re-confirmed, because confirming it costs the card for
* every worker on it). Both ops here are gathers built out of computed lengths, so a zero
* arrives by arithmetic rather than by a typo: a token count of zero, a collapsed range, an
* underflowed subtraction.
*
* So the guard is asserted three ways — the kernel HAS one, every length it can compute is
* non-zero, and the host REFUSES the requests that would produce one.
*/
static void test_no_transfer_can_be_zero_length(void)
{
ft_tt_kv_cache_layout layout;
ft_tt_op_kv_append_request append;
ft_tt_op_embedding_request embedding;
uint32_t words[FT_TT_OP_MEMORY_WORDS];
uint32_t row0;
unsigned zero_runs = 0;
unsigned zero_rows = 0;
uint32_t group_tiles;
ft_tt_test_step("no transfer this family issues can be zero bytes long");
ft_tt_test_check("the kernel has the guard, and it answers",
ft_tt_device_length_is_issuable(0u) == 0 &&
ft_tt_device_length_is_issuable(32u) == 1,
"");
for (row0 = 0; row0 < TILE_DIM; row0++) {
uint32_t count;
for (count = 1u; row0 + count <= TILE_DIM; count++) {
uint32_t offsets[8];
uint32_t lengths[8];
const uint32_t run_count =
ft_tt_device_destination_runs(row0, count, TILE_BYTES, offsets, lengths);
uint32_t index;
for (index = 0; index < run_count; index++) {
if (!ft_tt_device_length_is_issuable(lengths[index]))
zero_runs++;
}
}
}
ft_tt_test_check_fmt("every run of every one of the 528 row ranges is non-empty",
zero_runs == 0u, "%u empty", zero_runs);
for (group_tiles = 1u; group_tiles <= 64u; group_tiles++) {
if (!ft_tt_device_length_is_issuable(
ft_tt_device_group_row_bytes(group_tiles, TILE_BYTES)))
zero_rows++;
}
ft_tt_test_check_fmt("a source row is non-empty at every group width 1..64",
zero_rows == 0u, "%u empty", zero_rows);
/* And the host refuses the requests that would compute one, rather than sending a
* descriptor the kernel has to refuse on the card. */
(void)ft_tt_kv_cache_layout_init(APPEND_HEADS, APPEND_HEAD_DIM, APPEND_CAPACITY, TILE_BYTES,
&layout);
memset(&append, 0, sizeof(append));
append.cache = &layout;
append.token_count = 0u;
append.source.tile_bytes = TILE_BYTES;
append.source_tiles = APPEND_SOURCE_TILES;
append.region.tile_bytes = TILE_BYTES;
ft_tt_test_check("a zero-token append is REFUSED, not sent as a no-op",
ft_tt_op_kv_append_parameters(&append, words) == FT_TT_EINVAL, "");
memset(&embedding, 0, sizeof(embedding));
embedding.token_count = 0u;
embedding.embedding_dim = 1536u;
embedding.table.tile_bytes = TILE_BYTES;
embedding.table_row_stride = 1536u * 2u;
embedding.output.tile_bytes = TILE_BYTES;
ft_tt_test_check("a zero-token gather is REFUSED",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_EINVAL, "");
embedding.token_count = 4u;
embedding.embedding_dim = 0u;
ft_tt_test_check("a zero-width gather is REFUSED",
ft_tt_op_embedding_parameters(&embedding, words) == FT_TT_EINVAL, "");
}
/**
* §1.5b: a token at position p occupies tile row p % 32 — in BOTH ops, and RoPE depends on
* it.
*
* This is the check that keeps a cross-op constraint from being lost between two authors:
* RoPE multiplies activation tile row `r` by sin/cos table row `r`, so a hidden state that
* the embedding op put at the wrong row is rotated by the wrong angle — a plausible wrong
* answer with no error anywhere.
*/
static void test_row_phase_invariant(void)
{
ft_tt_kv_cache_layout layout;
ft_tt_op_kv_append_request append;
ft_tt_op_embedding_request embedding;
uint32_t append_words[FT_TT_OP_MEMORY_WORDS];
uint32_t embedding_words[FT_TT_OP_MEMORY_WORDS];
uint32_t position;
unsigned disagreements = 0;
ft_tt_test_step("the row-phase invariant: position p lands at tile row p %% 32, in BOTH "
"ops");
for (position = 0; position < 100u; position++) {
if (ft_tt_op_memory_position_row(position) != position % TILE_DIM)
disagreements++;
}
ft_tt_test_check_fmt("the helper is position modulo 32", disagreements == 0u, "%u wrong",
disagreements);
(void)ft_tt_kv_cache_layout_init(APPEND_HEADS, APPEND_HEAD_DIM, APPEND_CAPACITY, TILE_BYTES,
&layout);
for (position = 0; position < APPEND_CAPACITY; position++) {
memset(&append, 0, sizeof(append));
append.cache = &layout;
append.position = position;
append.token_count = 1u;
append.source.tile_bytes = TILE_BYTES;
append.source_tiles = APPEND_SOURCE_TILES;
append.region.tile_bytes = TILE_BYTES;
if (ft_tt_op_kv_append_parameters(&append, append_words) != FT_TT_OK) {
disagreements++;
continue;
}
memset(&embedding, 0, sizeof(embedding));
embedding.token_count = 1u;
embedding.embedding_dim = 1536u;
embedding.table.tile_bytes = TILE_BYTES;
embedding.table_row_stride = 1536u * 2u;
embedding.table_row_count = 73448u;
embedding.output.tile_bytes = TILE_BYTES;
embedding.output_bytes = 48u * TILE_BYTES;
embedding.output_row0 = ft_tt_op_memory_position_row(position);
if (ft_tt_op_embedding_parameters(&embedding, embedding_words) != FT_TT_OK) {
disagreements++;
continue;
}
/* The two ops must put the same position on the same tile row — that is the whole
* statement, and it is what RoPE reads. */
if (append_words[FT_TT_OP_MEMORY_DST_ROW0] % TILE_DIM !=
embedding_words[FT_TT_OP_MEMORY_DST_ROW0] % TILE_DIM)
disagreements++;
if (embedding_words[FT_TT_OP_MEMORY_DST_ROW0] % TILE_DIM != position % TILE_DIM)
disagreements++;
}
ft_tt_test_check_fmt("EMBEDDING and KV_APPEND agree on the row for all 128 positions",
disagreements == 0u, "%u disagreements", disagreements);
}
/* ================================================================ */
int main(void)
{
ft_tt_test_step("Phase 6 memory ops — OFF-HARDWARE gate");
test_wire_constants_agree();
test_chunk_offset_is_the_tilize_layout();
test_destination_runs_cover_exactly_the_rows();
test_kv_cache_layout();
test_kv_append();
test_kv_append_preserves_earlier_positions();
test_embedding(true);
test_embedding(false);
test_split_rule();
test_encoders_refuse_what_the_kernel_cannot_do();
test_circular_buffers_fit_the_arena();
test_no_transfer_can_be_zero_length();
test_row_phase_invariant();
return ft_tt_test_finish();
}