Foundry/tt/tests/test_gdn_ops.c

1865 lines
98 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file test_gdn_ops.c
* @brief Off-hardware acceptance for the GATED-DELTA-NET op family: the wire contract
* between `ft_tt_op_gdn.h` and `kernels/resident_gdn_protocol.h`, and the host driver's
* parameter encoding, shape refusals and CB placement.
*
* ---------------------------------------------------------------------------
* WHY THE WIRE PIN PARSES THE HEADER INSTEAD OF INCLUDING IT
* ---------------------------------------------------------------------------
* The two halves of the contract are declared TWICE — once as a C enum the host compiles
* and once as the `enum { name = value };` form the device kernels compile — and nothing in
* either build makes them agree. Including the device header here would make the test
* compile against the same declaration the kernel does, which is the right thing to want
* and the wrong thing to test: the failure this suite exists to catch is one of the two
* being edited alone. So it READS the file, exactly as `test_rope_ops.c`, `test_vector_ops.c`
* and `test_topk_ops.c` do for their own families, and a constant that is MISSING is a
* failure rather than a default — a default passes against a header that no longer declares
* the field.
*
* The opcode itself is pinned the same way, across the two files that carry it: the host's
* `FT_TT_QUEUE_OP_GDN` in `ft_tt_queue.h` against the device's `kOpGdn` in
* `kernels/resident_queue_protocol.h`.
*
* ---------------------------------------------------------------------------
* AND `FT_TT_OP_GDN_WORDS == FT_TT_QUEUE_PARAM_WORDS` IS NOT PEDANTRY
* ---------------------------------------------------------------------------
* Every family's parameter block is 16 words because `ft_tt_queue_write_op_parameters()`
* writes exactly `FT_TT_QUEUE_PARAM_WORDS` of them into a slot whose STRIDE is shared with
* every other family. A seventeenth word does not overflow a buffer here; it silently moves
* the next family's slot on the card. This op is the one most tempted to want a
* seventeenth — it has five DRAM endpoints where rope has three — which is exactly why its
* coordinates are packed two to a word, and why that pack is asserted rather than trusted.
*
* ---------------------------------------------------------------------------
* AND THE LAYOUT ARMS INCLUDE THE DEVICE HEADER, WHICH IS NOT A CONTRADICTION
* ---------------------------------------------------------------------------
* The layout accessors (`ftg_delta_stream_base`, `ftg_state_tile`, `ftg_conv_plane_tile`,
* `ftg_y_tile`) are declared ONCE, in the device header, and both dataflow kernels call
* them — there is no second declaration for them to drift from. The drift that matters
* there is between the accessor and whatever a future host stager writes into DRAM, and the
* only way to test THAT off-hardware is to compute the expected tile index independently
* from the oracle and compare. So those arms include the header and check its arithmetic;
* the wire-constant arms parse it and check its text. Different failure, different method.
*
* ---------------------------------------------------------------------------
* THE DELTA GEOMETRY THIS SUITE NOW PINS: HEAD-MAJOR ROW-0 STRIPS
* ---------------------------------------------------------------------------
* `kGdnActivationIn` (PHASE DELTA's q | k | v) and `kGdnActivationOut` (y) are ROW-0 STRIPS,
* head-major: a head's S values are `state_tiles` CONSECUTIVE tiles, all of the data in ROW 0
* (`kGdnStripRow`) of each. Head `h` starts at `h * state_tiles` inside its block, which is
* what `ftg_delta_head_tile()` and `ftg_y_tile()` return.
*
* They used to be three tilized `[heads, S]` matrices with ONE ROW PER HEAD — 12 input tiles
* and 4 y tiles at Ornith, against 256 and 128 now. That packing was 32x denser and cost
* nothing to read, but it was a DIFFERENT GEOMETRY from the strip every projection, reduce and
* elementwise op in this backend produces, so the host had to de-tilize conv_out, L2-normalise
* on the CPU and re-tilize between the phases. The strip is what lets the block run WHOLE on
* the card, and the literals below are the new contract.
*
* The distinguishing property, and the one the arms below assert directly, is INJECTIVITY:
* under the old `ftg_y_tile()` all 32 v-heads collapsed onto the same four tiles and were told
* apart by their ROW, so a check that (head, tile_row) -> tile is one-to-one is red against it.
*
* @status Off-hardware only, by construction — no device is opened. The triad is written,
* built and blob-embedded but has never executed, so every claim here is host arithmetic
* and header text.
* @issues None
* @todo An on-card arm gating the delta rule against pn_qwen35moe_ssm_recurrence at 1e-3
* relative, once the op has a caller.
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../ft_tt_op_gdn.h"
#include "../ft_tt_op_l1_layout.h"
#include "../ft_tt_queue.h"
#include "ft_tt_test_report.h"
/* The device's L1 pointer attribute is a property of the SFPI GCC fork, not of the contract.
* The header guards it, so naming it as nothing lets a HOST compiler read the file. */
#define FTQ_L1_PTR
#include "../kernels/resident_gdn_protocol.h"
#define KERNEL_DIRECTORY_ENVIRONMENT "FT_TT_KERNEL_DIR"
#define KERNEL_DIRECTORY_DEFAULT "../kernels"
#define PATH_MAX_BYTES 512u
static unsigned char g_cb_buffer[FT_TT_NUM_CIRCULAR_BUFFERS * FT_TT_WORDS_PER_LOCAL_CB_CONFIG *
4];
/* ================================================================
* Reading a constant out of a device header
* ================================================================ */
/** The value of `enum { <name> = <value> };` in `path`. A missing constant is a FAILURE. */
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 *file, const char *name, uint32_t expected)
{
char path[PATH_MAX_BYTES];
uint32_t value = 0xFFFFFFFFu;
char label[160];
int rc;
device_header_path(path, sizeof(path), file);
rc = read_device_constant(path, name, &value);
snprintf(label, sizeof(label), "%s::%s is the host's value", file, name);
ft_tt_test_check_fmt(label, rc == 0 && value == expected, "device %u, host %u (rc=%d)",
value, expected, rc);
}
/**
* The `position`'th comma-separated value (0-based) after `key` on any line of `path`, e.g.
* `read_ctarg_value(path, "READER_CTARGS=", 7, &v)` against `... READER_CTARGS=0,1,2,3,4,
* 4096,1,1,8 ...` reads `1`. A missing key or a list shorter than `position` is a FAILURE,
* the same discipline as `read_device_constant` above and for the same reason: a build line
* that no longer says what this test expects must be a red, not a value nobody checked.
*/
static int read_ctarg_value(const char *path, const char *key, unsigned position,
uint32_t *out_value)
{
char line[512];
FILE *file = fopen(path, "r");
int found = -1;
if (!file)
return -1;
while (found != 0 && fgets(line, sizeof(line), file)) {
char *cursor = strstr(line, key);
unsigned index;
if (!cursor)
continue;
cursor += strlen(key);
for (index = 0; index <= position; index++) {
char *end;
unsigned long value = strtoul(cursor, &end, 10);
if (end == cursor)
break; /* not a digit where the next list value was expected */
if (index == position) {
*out_value = (uint32_t)value;
found = 0;
break;
}
if (*end != ',')
break; /* the list ended before `position` */
cursor = end + 1;
}
}
fclose(file);
return found;
}
static void check_ctarg(const char *file, const char *key, unsigned position, const char *what,
uint32_t expected)
{
char path[PATH_MAX_BYTES];
uint32_t value = 0xFFFFFFFFu;
int rc;
device_header_path(path, sizeof(path), file);
rc = read_ctarg_value(path, key, position, &value);
ft_tt_test_check_fmt(what, rc == 0 && value == expected,
"%s[%u] in %s: device %u, host %u (rc=%d)", key, position, file,
value, expected, rc);
}
static void test_wire_constants(void)
{
ft_tt_test_step("the host and the KERNEL agree on every wire word — PARSED, not claimed");
check_device_constant("resident_gdn_protocol.h", "kGdnShape",
(uint32_t)FT_TT_OP_GDN_SHAPE);
check_device_constant("resident_gdn_protocol.h", "kGdnConvTileCols",
(uint32_t)FT_TT_OP_GDN_CONV_TILE_COLS);
check_device_constant("resident_gdn_protocol.h", "kGdnConvTaps",
(uint32_t)FT_TT_OP_GDN_CONV_TAPS);
check_device_constant("resident_gdn_protocol.h", "kGdnActivationXy",
(uint32_t)FT_TT_OP_GDN_ACTIVATION_XY);
check_device_constant("resident_gdn_protocol.h", "kGdnActivationIn",
(uint32_t)FT_TT_OP_GDN_ACTIVATION_IN);
check_device_constant("resident_gdn_protocol.h", "kGdnActivationOut",
(uint32_t)FT_TT_OP_GDN_ACTIVATION_OUT);
check_device_constant("resident_gdn_protocol.h", "kGdnConstantXy",
(uint32_t)FT_TT_OP_GDN_CONSTANT_XY);
check_device_constant("resident_gdn_protocol.h", "kGdnConstantWeight",
(uint32_t)FT_TT_OP_GDN_CONSTANT_WEIGHT);
check_device_constant("resident_gdn_protocol.h", "kGdnConstantGate",
(uint32_t)FT_TT_OP_GDN_CONSTANT_GATE);
check_device_constant("resident_gdn_protocol.h", "kGdnStateXy",
(uint32_t)FT_TT_OP_GDN_STATE_XY);
check_device_constant("resident_gdn_protocol.h", "kGdnStateConv",
(uint32_t)FT_TT_OP_GDN_STATE_CONV);
check_device_constant("resident_gdn_protocol.h", "kGdnStateRecurrent",
(uint32_t)FT_TT_OP_GDN_STATE_RECURRENT);
check_device_constant("resident_gdn_protocol.h", "kGdnScaleBits",
(uint32_t)FT_TT_OP_GDN_SCALE_BITS);
check_device_constant("resident_gdn_protocol.h", "kGdnTileBytes",
(uint32_t)FT_TT_OP_GDN_TILE_BYTES);
check_device_constant("resident_gdn_protocol.h", "kGdnFlags",
(uint32_t)FT_TT_OP_GDN_FLAGS);
check_device_constant("resident_gdn_protocol.h", "kGdnTileCount",
(uint32_t)FT_TT_OP_GDN_TILE_COUNT);
check_device_constant("resident_gdn_protocol.h", "kGdnWords",
(uint32_t)FT_TT_OP_GDN_WORDS);
/* The two phase ids, because a descriptor that names the wrong half of the block would
* run the conv over the recurrent state's address. */
check_device_constant("resident_gdn_protocol.h", "kGdnPhaseConv", FT_TT_OP_GDN_PHASE_CONV);
check_device_constant("resident_gdn_protocol.h", "kGdnPhaseDelta", FT_TT_OP_GDN_PHASE_DELTA);
/* The opcode, across the two files that carry it. */
check_device_constant("resident_queue_protocol.h", "kOpGdn",
(uint32_t)FT_TT_QUEUE_OP_GDN);
ft_tt_test_check_fmt("the parameter block is FT_TT_QUEUE_PARAM_WORDS wide",
(uint32_t)FT_TT_OP_GDN_WORDS == FT_TT_QUEUE_PARAM_WORDS,
"gdn %u, queue %u", (unsigned)FT_TT_OP_GDN_WORDS,
(unsigned)FT_TT_QUEUE_PARAM_WORDS);
/* 0x12 was the next free opcode. Pinned so that a family added later cannot silently
* take it: two families on one opcode is a descriptor executed by the wrong kernel. */
ft_tt_test_check_fmt("kOpGdn sits above every opcode that existed before it",
FT_TT_QUEUE_OP_GDN > FT_TT_QUEUE_OP_ATTENTION, "0x%02X > 0x%02X",
(unsigned)FT_TT_QUEUE_OP_GDN, (unsigned)FT_TT_QUEUE_OP_ATTENTION);
}
/**
* FT_TT_OP_GDN_CONV_DEPTH and FT_TT_OP_GDN_CB_STAGE are not read back off any device
* structure the way the wire words above are. ft_tt_op_gdn.h says outright that raising the
* depth is "a measured change to this constant, `place_cbs` and both kernel build lines
* together" -- three sides, and nothing enforces that they move together. A header edit
* alone changes `resolve_cb_pages()` and this file's own `placement_satisfies_device()` in
* lockstep, since both read the same macro, so re-deriving the expectation from the header
* cannot catch a mutation of the header: it has to be pinned against a text the header does
* not own. That text is the CTARGS the kernel SOURCE COMMENTS document as the actual build
* line -- what `programs/gdn` on the card host was built with -- and it does not move just
* because the header does.
*/
static void test_compile_time_arg_contract(void)
{
ft_tt_test_step("FT_TT_OP_GDN_CONV_DEPTH and FT_TT_OP_GDN_CB_STAGE, pinned against the "
"kernel build-line comments and not re-derived from the header they pin");
ft_tt_test_check_fmt("FT_TT_OP_GDN_CONV_DEPTH is the depth every kernel build line "
"compiles in",
FT_TT_OP_GDN_CONV_DEPTH == 2u, "%u",
(unsigned)FT_TT_OP_GDN_CONV_DEPTH);
ft_tt_test_check_fmt("FT_TT_OP_GDN_CB_STAGE is the CB index the host and the reader "
"both use for the private staging buffer",
FT_TT_OP_GDN_CB_STAGE == 4u, "%u", (unsigned)FT_TT_OP_GDN_CB_STAGE);
/* THE THIRD AND FOURTH SIDES: the reader's own compile-time arg 7 and the compute
* kernel's own compile-time arg 9, read out of the build lines their own file headers
* document (resident_gdn_reader.c:121, resident_gdn_compute.c:141). */
check_ctarg("resident_gdn_reader.c", "READER_CTARGS=", 7u,
"resident_gdn_reader.c's own READER_CTARGS[7] is FT_TT_OP_GDN_CONV_DEPTH",
(uint32_t)FT_TT_OP_GDN_CONV_DEPTH);
check_ctarg("resident_gdn_compute.c", "COMPUTE_CTARGS=", 9u,
"resident_gdn_compute.c's own COMPUTE_CTARGS[9] is FT_TT_OP_GDN_CONV_DEPTH",
(uint32_t)FT_TT_OP_GDN_CONV_DEPTH);
check_ctarg("resident_gdn_reader.c", "READER_CTARGS=", 4u,
"resident_gdn_reader.c's own READER_CTARGS[4] is FT_TT_OP_GDN_CB_STAGE",
(uint32_t)FT_TT_OP_GDN_CB_STAGE);
/* THE DST BATCH IS THE ONE CONTRACT WHOSE BREAKAGE IS SILENT AND SLOW. A short build line
* folds its arg to a literal 0, `ftg_dst_batch()` maps 0 to 1, and the op then runs the
* per-tile sequence at the shipped 3,368 µs a DELTA descriptor while every host-side
* number says 4 — no refusal, no wrong answer, nothing on the card to look at. The arg
* COUNT beside each build line is the only guard, so it is pinned on BOTH kernels here
* and CB_PROD's index with it, since the reader's refusal is what stops a placement the
* batched indexed read would walk off. */
ft_tt_test_check_fmt("FT_TT_OP_GDN_DST_BATCH is the batch every kernel build line "
"compiles in",
FT_TT_OP_GDN_DST_BATCH == 4u, "%u",
(unsigned)FT_TT_OP_GDN_DST_BATCH);
check_ctarg("resident_gdn_reader.c", "READER_CTARGS=", 9u,
"resident_gdn_reader.c's own READER_CTARGS[9] is FT_TT_OP_GDN_CB_PROD",
(uint32_t)FT_TT_OP_GDN_CB_PROD);
check_ctarg("resident_gdn_reader.c", "READER_CTARGS=", 10u,
"resident_gdn_reader.c's own READER_CTARGS[10] is FT_TT_OP_GDN_DST_BATCH",
(uint32_t)FT_TT_OP_GDN_DST_BATCH);
check_ctarg("resident_gdn_compute.c", "COMPUTE_CTARGS=", 11u,
"resident_gdn_compute.c's own COMPUTE_CTARGS[11] is FT_TT_OP_GDN_DST_BATCH",
(uint32_t)FT_TT_OP_GDN_DST_BATCH);
/* THE TWO RESTRUCTURING MASKS, and they are the ones that carry this op's 1.76x. They
* fail the same silent way the batch does — a short build line folds the arg to 0 and the
* kernel quietly runs the slow shape — except that here the slow shape is 3,351 µs
* against 1,905, so the cost of the silence is 43% of PHASE DELTA rather than 1%. */
ft_tt_test_check_fmt("FT_TT_OP_GDN_READER_EXTRAS is the mask every reader build line "
"compiles in",
FT_TT_OP_GDN_READER_EXTRAS == 1u, "%u",
(unsigned)FT_TT_OP_GDN_READER_EXTRAS);
ft_tt_test_check_fmt("FT_TT_OP_GDN_DELTA_EXTRAS is the mask every compute build line "
"compiles in, and it does NOT carry the push-batch bit that refused "
"on silicon",
FT_TT_OP_GDN_DELTA_EXTRAS == 1u, "%u",
(unsigned)FT_TT_OP_GDN_DELTA_EXTRAS);
check_ctarg("resident_gdn_reader.c", "READER_CTARGS=", 11u,
"resident_gdn_reader.c's own READER_CTARGS[11] is FT_TT_OP_GDN_READER_EXTRAS",
(uint32_t)FT_TT_OP_GDN_READER_EXTRAS);
check_ctarg("resident_gdn_compute.c", "COMPUTE_CTARGS=", 12u,
"resident_gdn_compute.c's own COMPUTE_CTARGS[12] is FT_TT_OP_GDN_DELTA_EXTRAS",
(uint32_t)FT_TT_OP_GDN_DELTA_EXTRAS);
}
/**
* `ftg_dst_batch()` is the ONE implementation of the clamp, called by the reader, by the
* compute kernel and by `resolve_cb_pages()` — so what it returns is a three-sided fact and
* the arms below are the only thing that says what it should be.
*
* The CEILING is the part that cannot be inferred from anything else in the tree. DST is 16
* tiles of 32x32, DST_SYNC half hands MATH one half, and `dst_accum_mode 1` — mandatory for
* the one op carrying state across tokens — halves it again, so four is the largest index the
* hardware has. A batch above it does not fail: it ALIASES DST tiles onto each other and
* produces a smoothly wrong recurrence, which is the failure this family has already been
* bitten by twice under other names.
*/
static void test_dst_batch_clamp(void)
{
uint32_t state_tiles;
ft_tt_test_step("ftg_dst_batch() clamps to the fp32 half-DST ceiling, to the tile-row, "
"and onto the divisor grid — at every shape the family admits");
ft_tt_test_check_fmt("the ceiling is four — DST is 16 tiles, half sync halves it and "
"dst_accum_mode 1 halves it again",
(uint32_t)kGdnDstBatchCeiling == 4u, "%u",
(unsigned)kGdnDstBatchCeiling);
ft_tt_test_check_fmt("a zero max — what a SHORT build line folds to — is the identity, "
"not a division by zero",
ftg_dst_batch(4u, 0u) == 1u, "%u", ftg_dst_batch(4u, 0u));
ft_tt_test_check_fmt("a max above the ceiling is clamped to it, not honoured",
ftg_dst_batch(8u, 64u) == (uint32_t)kGdnDstBatchCeiling, "%u",
ftg_dst_batch(8u, 64u));
ft_tt_test_check_fmt("Ornith's own shape batches WHOLE — state_tiles 4, batch 4, one DST "
"section a tile-row loop",
ftg_dst_batch(FT_TT_OP_GDN_QWEN35_STATE / FT_TT_OP_TILE_DIM,
FT_TT_OP_GDN_DST_BATCH) == 4u,
"%u", ftg_dst_batch(FT_TT_OP_GDN_QWEN35_STATE / FT_TT_OP_TILE_DIM,
FT_TT_OP_GDN_DST_BATCH));
for (state_tiles = 1u; state_tiles <= 8u; state_tiles++) {
const uint32_t batch = ftg_dst_batch(state_tiles, FT_TT_OP_GDN_DST_BATCH);
char label[160];
snprintf(label, sizeof(label),
"S=%u (%u state tiles): batch %u is >=1, <= the ceiling, <= the tile-row and "
"DIVIDES it",
state_tiles * 32u, state_tiles, batch);
ft_tt_test_check_fmt(label,
batch >= 1u && batch <= (uint32_t)kGdnDstBatchCeiling &&
batch <= state_tiles && (state_tiles % batch) == 0u,
"batch=%u tiles=%u", batch, state_tiles);
}
}
/**
* `ftg_writer_block()` is the writer's barrier group and the host's CB_OUT size, from ONE
* implementation — the same two-sided shape `ftg_dst_batch()` has and for the same reason.
*
* WHAT ONLY THIS TEST SAYS is the BARRIER COUNT each rung buys, because nothing on the card
* reports it: 640 / 128 / 32 a DELTA descriptor at Ornith's 32 v-heads. A rung that quietly
* returned the wrong group would be a slower kernel and a correct answer, which is the one
* failure class this family has no on-card signal for.
*/
static void test_writer_block_rungs(void)
{
const uint32_t tiles = FT_TT_OP_GDN_QWEN35_STATE / FT_TT_OP_TILE_DIM;
const uint32_t heads = FT_TT_OP_GDN_QWEN35_V_HEADS;
const uint32_t pages = heads * tiles * (tiles + 1u);
uint32_t rung;
ft_tt_test_step("ftg_writer_block() — the rungs, the arena ceiling, and the barrier count "
"each one leaves in a DELTA descriptor");
ft_tt_test_check_fmt("rung 0 is the IDENTITY: one barrier per tile, the sequence that "
"shipped and what a short build line folds to",
ftg_writer_block(tiles, 0u) == 1u, "%u", ftg_writer_block(tiles, 0u));
ft_tt_test_check_fmt("rung 1 is one TILE-ROW — `state_tiles` state pages then one y",
ftg_writer_block(tiles, 1u) == tiles + 1u, "%u",
ftg_writer_block(tiles, 1u));
ft_tt_test_check_fmt("rung 2 is one HEAD — every tile-row of it",
ftg_writer_block(tiles, 2u) == tiles * (tiles + 1u), "%u",
ftg_writer_block(tiles, 2u));
ft_tt_test_check_fmt("a state size of zero cannot divide, so it is the identity",
ftg_writer_block(0u, 2u) == 1u, "%u", ftg_writer_block(0u, 2u));
ft_tt_test_check_fmt("the ceiling binds at S=256, where an unclamped rung 2 would ask for "
"144 CB_OUT pages and put the placement over the arena",
ftg_writer_block(8u, 2u) == (uint32_t)kGdnWriterBlockCeiling, "%u",
ftg_writer_block(8u, 2u));
for (rung = 0u; rung <= 2u; rung++) {
const uint32_t block = ftg_writer_block(tiles, rung);
const uint32_t barriers = pages / block;
char label[176];
snprintf(label, sizeof(label),
"rung %u: %u CB_OUT pages a group BEFORE the placement clamp, so a 32-head "
"DELTA descriptor takes %u write barriers, not %u",
rung, block, barriers, pages);
ft_tt_test_check_fmt(label,
block >= 1u && (pages % block) == 0u &&
barriers == (rung == 0u ? 640u : (rung == 1u ? 128u : 32u)),
"block=%u barriers=%u", block, barriers);
}
}
/* ================================================================
* THE WRITER'S PAGE WALK — the half of the barrier group that silicon cannot check
* ================================================================
*
* Grouping the barrier moves the POPS behind it, and a batched consumer then has to name
* pages by INDEX off its front. `read_pointer + index * page` is what every indexed CB read
* in this backend does, and a FIFO pointer returns to its base on an EXACT HIT of the limit
* and on nothing else — so the indexed form reads straight past the buffer whenever a group
* straddles the end. `page_address()` in resident_gdn_writer.c spells the wrap out for that
* reason, and this is the model that says the wrap is REACHED rather than defensive.
*
* IT IS REACHED BECAUSE OF PHASE CONV, and that is not obvious from the DELTA side alone.
* A DELTA descriptor moves `head_count * state_tiles * (state_tiles + 1)` pages — always a
* multiple of 20, which divides a 40-page CB_OUT, so DELTA after DELTA always starts on the
* base. But CONV moves `conv_tile_cols * taps` = 1,024 pages, and 1,024 mod 40 is 24, so
* EVERY DELTA descriptor in a real layer stack starts 24 pages in and every group after the
* fourth straddles the end.
*
* WHAT THE MODEL ASSERTS is the only thing that matters: the writer reads page `k` of the
* stream from the SAME L1 address the producer pushed page `k` into. The producer advances
* one page at a time with an exact-hit wrap, so that address is `base + (k mod pages)*page`
* and nothing about the rung may change it. Measured on silicon 2026-08-19 from the other
* side: the same writer with the wrap patched out returned 56 bytes and `cmp` DIFFERS.
*/
typedef struct wb_fifo {
uint32_t base;
uint32_t page;
uint32_t pages;
uint32_t size;
uint32_t limit; /* one past the end, as ft_tensix_cb_pop_front() compares it */
uint32_t read;
} wb_fifo;
static void wb_fifo_init(wb_fifo *fifo, uint32_t base, uint32_t page, uint32_t pages)
{
fifo->base = base;
fifo->page = page;
fifo->pages = pages;
fifo->size = page * pages;
fifo->limit = base + fifo->size;
fifo->read = base;
}
/** Where the PRODUCER put stream page `k`: it pushes one page at a time from the base. */
static uint32_t wb_producer_address(const wb_fifo *fifo, uint32_t k)
{
return fifo->base + (k % fifo->pages) * fifo->page;
}
/** resident_gdn_writer.c `page_address()`, transliterated. `wrap` 0 is the RED arm. */
static uint32_t wb_page_address(const wb_fifo *fifo, uint32_t front, uint32_t index, int wrap)
{
uint32_t address = front + index * fifo->page;
if (wrap && address >= fifo->limit)
address -= fifo->size;
return address;
}
/**
* `ft_tensix_cb_pop_front(cb, pages)` — advance, wrap on an EXACT hit and NOTHING ELSE.
*
* The exact-hit comparison is upstream's and is the whole reason the writer pops ONE PAGE AT
* A TIME instead of popping its group. At `pages == 1` the pointer visits every page boundary
* and therefore always hits the limit exactly; at `pages == group` it steps over the limit
* whenever the group is not aligned to it, the wrap never fires, and the pointer walks out of
* the buffer and never comes back. `wb_pop_group_would_escape()` below is the model of that.
*/
static void wb_pop(wb_fifo *fifo, uint32_t pages)
{
fifo->read += pages * fifo->page;
if (fifo->read == fifo->limit)
fifo->read -= fifo->size;
}
static void wb_pop_one(wb_fifo *fifo)
{
wb_pop(fifo, 1u);
}
/**
* `writer_block()` in resident_gdn_writer.c: the rung's group, CLAMPED to half the placed
* pages. The clamp is what stops a kernel built at a rung the host did not size for from
* waiting on more pages than the buffer can ever hold — and the model has to apply it too,
* or it tests a state the card cannot reach. Proven on silicon 2026-08-19: the rung-1 kernel
* under a two-page CB_OUT ran `cmp` IDENTICAL at rung-0 speed (1910.7 us against 1908.3)
* rather than refusing the descriptor.
*/
static uint32_t wb_clamped_block(uint32_t state_tiles, uint32_t rung, uint32_t cb_pages)
{
uint32_t block = ftg_writer_block(state_tiles, rung);
if (block > cb_pages / 2u)
block = cb_pages / 2u;
if (block == 0u)
block = 1u;
return block;
}
/**
* Drain one descriptor's `pages` CB_OUT pages in groups of `group`, as write_one_delta() and
* write_one_conv() do. Returns the barrier count; advances `*k`, the stream page counter.
*
* `out_bad` counts pages whose source address is NOT where the producer put them — which is
* either a read past the buffer or a read of the wrong page, and both are wrong answers.
*/
static uint32_t wb_drain(wb_fifo *fifo, uint32_t pages, uint32_t group, int wrap, uint32_t *k,
uint32_t *out_bad, uint32_t *out_off_buffer)
{
uint32_t emitted = 0u;
uint32_t barriers = 0u;
while (emitted < pages) {
uint32_t n = pages - emitted;
uint32_t front;
uint32_t index;
if (n > group)
n = group;
front = fifo->read;
for (index = 0; index < n; index++) {
const uint32_t source = wb_page_address(fifo, front, index, wrap);
if (source < fifo->base || source >= fifo->limit)
(*out_off_buffer)++;
if (source != wb_producer_address(fifo, *k + index))
(*out_bad)++;
}
barriers++;
for (index = 0; index < n; index++)
wb_pop_one(fifo);
emitted += n;
*k += n;
}
return barriers;
}
/** The DELTA destination for stream page `slot_page` of head `head`, as the writer decodes it.
* Encoded so a whole descriptor's destinations can be compared between rungs. */
static uint32_t wb_delta_destination(const ftg_shape *shape, uint32_t head, uint32_t page)
{
const uint32_t row_pages = shape->state_tiles + 1u;
const uint32_t tile_row = page / row_pages;
const uint32_t slot = page % row_pages;
if (slot < shape->state_tiles)
return ftg_state_tile(shape, head, tile_row, slot);
return 0x40000000u | ftg_y_tile(shape, head, tile_row);
}
static void test_writer_page_walk(void)
{
const uint32_t tiles = FT_TT_OP_GDN_QWEN35_STATE / FT_TT_OP_TILE_DIM;
const uint32_t heads = FT_TT_OP_GDN_QWEN35_V_HEADS;
const uint32_t conv_pages =
(FT_TT_OP_GDN_QWEN35_CONV_DIM / FT_TT_OP_TILE_DIM) * FT_TT_OP_GDN_QWEN35_CONV_TAPS;
const uint32_t delta_pages = heads * tiles * (tiles + 1u);
const uint32_t cb_pages = 2u * ftg_writer_block(tiles, FT_TT_OP_GDN_WRITER_RUNG);
ftg_shape shape;
uint32_t rung;
uint32_t reference[1024];
uint32_t page;
ft_tt_test_step("the WRITER's page walk — the barrier group changes WHEN it drains, never "
"WHICH page it reads or where that page goes");
memset(&shape, 0, sizeof(shape));
shape.state_tiles = tiles;
shape.v_heads = heads;
/* The destinations, decoded from a page's position in the stream. Rung-independent by
* construction, so recording them once is enough — what the loop below proves is that
* the SOURCES stay matched to them however the groups fall. */
for (page = 0; page < tiles * (tiles + 1u); page++)
reference[page] = wb_delta_destination(&shape, 0u, page);
ft_tt_test_check_fmt("a head's 20 pages are 4 state tiles then one y, four times over",
reference[0] == ftg_state_tile(&shape, 0u, 0u, 0u) &&
reference[4] == (0x40000000u | 0u) &&
reference[19] == (0x40000000u | 3u),
"%08x %08x %08x", reference[0], reference[4], reference[19]);
/* AND THE LAST HEAD'S y PAGES ARE NOT THE FIRST HEAD'S. y is a HEAD-MAJOR ROW-0 STRIP, so
* head 31's four y destinations are tiles 124..127 and they share nothing with head 0's
* 0..3. Under the old one-row-per-head y every head drained into the SAME four tiles and
* this pair compared equal, which is why the arm is spelled as a disagreement. */
{
uint32_t last[32];
int disjoint = 1;
uint32_t index;
for (page = 0; page < tiles * (tiles + 1u); page++)
last[page] = wb_delta_destination(&shape, heads - 1u, page);
for (page = 0; page < tiles * (tiles + 1u); page++)
for (index = 0; index < tiles * (tiles + 1u); index++)
if ((last[page] & 0x40000000u) != 0u && last[page] == reference[index])
disjoint = 0;
ft_tt_test_check_fmt("head 31's y pages are tiles 124..127 and share none of head 0's",
disjoint && last[4] == (0x40000000u | 124u) &&
last[19] == (0x40000000u | 127u),
"%08x %08x disjoint=%d", last[4], last[19], disjoint);
}
ft_tt_test_check_fmt("CONV leaves the read pointer OFF the base, and that is what makes "
"the wrap reachable at all",
(conv_pages % cb_pages) != 0u, "conv_pages=%u cb_pages=%u rem=%u",
conv_pages, cb_pages, conv_pages % cb_pages);
for (rung = 0u; rung <= 2u; rung++) {
const uint32_t group = wb_clamped_block(tiles, rung, cb_pages);
uint32_t bad = 0u;
uint32_t off = 0u;
uint32_t k = 0u;
uint32_t delta_barriers = 0u;
uint32_t layer;
wb_fifo fifo;
char label[208];
wb_fifo_init(&fifo, 0x30000u, FT_TT_OP_GDN_FP32_TILE_BYTES, cb_pages);
/* Twenty-four SSM layers of a real token: CONV then DELTA, CONV then DELTA. */
for (layer = 0; layer < 24u; layer++) {
(void)wb_drain(&fifo, conv_pages, 1u, 1, &k, &bad, &off);
delta_barriers = wb_drain(&fifo, delta_pages, group, 1, &k, &bad, &off);
}
snprintf(label, sizeof(label),
"rung %u (group %u after the clamp): 24 layers of CONV+DELTA — every page "
"read where the producer put it, none off the buffer, %u barriers a DELTA "
"descriptor",
rung, group, delta_barriers);
ft_tt_test_check_fmt(label,
bad == 0u && off == 0u && group >= 1u &&
delta_barriers == (delta_pages + group - 1u) / group,
"bad=%u off_buffer=%u barriers=%u", bad, off, delta_barriers);
}
/* THE OTHER HALF OF THE SAME RULE: popping the GROUP instead of one page at a time.
* `ft_tensix_cb_pop_front()` wraps on an EXACT hit of the limit and on nothing else, so a
* pointer that STEPS OVER the limit never comes back. CONV leaves the front 24 pages in
* and a group of 5 from there lands on 29, 34, 39, 44 — 44 is past the 40-page limit and
* is not equal to it, so the wrap does not fire. This is why the writer's pops are
* spelled one at a time and it is invisible in any DELTA-only test. */
for (rung = 1u; rung <= 2u; rung++) {
const uint32_t group = wb_clamped_block(tiles, rung, cb_pages);
wb_fifo fifo;
uint32_t emitted;
int escaped = 0;
char label[192];
wb_fifo_init(&fifo, 0x30000u, FT_TT_OP_GDN_FP32_TILE_BYTES, cb_pages);
for (emitted = 0; emitted < conv_pages; emitted++)
wb_pop_one(&fifo);
for (emitted = 0; emitted < delta_pages; emitted += group) {
wb_pop(&fifo, group);
if (fifo.read < fifo.base || fifo.read >= fifo.limit)
escaped = 1;
}
snprintf(label, sizeof(label),
"rung %u: popping the whole %u-page group instead of one page at a time walks "
"the read pointer OUT of the buffer — the exact-hit wrap never fires",
rung, group);
ft_tt_test_check_fmt(label, escaped, "read=%08x base=%08x limit=%08x", fifo.read,
fifo.base, fifo.limit);
}
/* THE RED ARM, off hardware. The same walk with page_address()'s wrap removed — the build
* that returned 56 bytes and `cmp` DIFFERS on silicon. Rung 0 must be UNAFFECTED, because
* a group of one never straddles anything; that is what says the model discriminates the
* wrap rather than the rung. */
for (rung = 0u; rung <= 2u; rung++) {
uint32_t bad = 0u;
uint32_t off = 0u;
uint32_t k = 0u;
uint32_t layer;
wb_fifo fifo;
char label[192];
wb_fifo_init(&fifo, 0x30000u, FT_TT_OP_GDN_FP32_TILE_BYTES, cb_pages);
for (layer = 0; layer < 24u; layer++) {
(void)wb_drain(&fifo, conv_pages, 1u, 0, &k, &bad, &off);
(void)wb_drain(&fifo, delta_pages, wb_clamped_block(tiles, rung, cb_pages), 0,
&k, &bad, &off);
}
snprintf(label, sizeof(label),
"rung %u WITHOUT the wrap: %s",
rung,
rung == 0u ? "a group of one cannot straddle, so nothing moves — the model "
"discriminates the wrap, not the rung"
: "reads land off the buffer, which is the 56-byte `cmp` DIFFERS "
"arm reproduced off hardware");
ft_tt_test_check_fmt(label, rung == 0u ? (bad == 0u && off == 0u) : (bad > 0u && off > 0u),
"bad=%u off_buffer=%u", bad, off);
}
}
/* ================================================================
* A request at Ornith's real shape
* ================================================================ */
static ft_tt_op_operand operand(uint16_t x, uint16_t y, uint32_t address, uint32_t tile_bytes)
{
ft_tt_op_operand out;
out.x = x;
out.y = y;
out.address = address;
out.tile_bytes = tile_bytes;
return out;
}
static void fill_delta_request(ft_tt_op_gdn_request *request)
{
memset(request, 0, sizeof(*request));
request->phase = FT_TT_OP_GDN_PHASE_DELTA;
request->state_size = FT_TT_OP_GDN_QWEN35_STATE;
request->v_heads = FT_TT_OP_GDN_QWEN35_V_HEADS;
request->k_heads = FT_TT_OP_GDN_QWEN35_K_HEADS;
request->conv_dim = FT_TT_OP_GDN_QWEN35_CONV_DIM;
request->conv_taps = FT_TT_OP_GDN_QWEN35_CONV_TAPS;
/* EVERY operand is fp32. The whole family's CB table is Float32 and both dataflow
* kernels are built with TILE_BYTES = 4096; this fixture used to hand three of the four
* operands the bf16 size, which is exactly the request that placed 2048-byte pages under
* a reader issuing 4096-byte tile reads. */
request->activation_in = operand(1u, 2u, 0x00100000u, FT_TT_OP_TILE_BYTES_FP32);
request->activation_out = operand(1u, 2u, 0x00200000u, FT_TT_OP_TILE_BYTES_FP32);
request->gate = operand(3u, 4u, 0x00300000u, FT_TT_OP_TILE_BYTES_FP32);
request->state_recurrent = operand(5u, 6u, 0x00400000u, FT_TT_OP_TILE_BYTES_FP32);
}
static void fill_conv_request(ft_tt_op_gdn_request *request)
{
fill_delta_request(request);
request->phase = FT_TT_OP_GDN_PHASE_CONV;
request->gate = operand(0u, 0u, 0u, 0u);
request->state_recurrent = operand(0u, 0u, 0u, 0u);
request->conv_weight = operand(3u, 4u, 0x00500000u, FT_TT_OP_TILE_BYTES_FP32);
request->state_conv = operand(5u, 6u, 0x00600000u, FT_TT_OP_TILE_BYTES_FP32);
}
/**
* THE CARD-SIDE HALF OF THE CONV-DEPTH CONTRACT — the one the checks above cannot reach.
*
* `check_ctarg` above pins the build-line COMMENTS, which is a statement about text in this
* checkout. It says nothing about the images a program directory on the card host was actually
* built from, and a depth sweep runs several directories against one host binary on purpose.
* The drift that matters is the SILENT direction: a host that placed for a LARGER depth than
* the kernels carry passes every capacity and grid check this family has, runs at the
* kernels' depth, and returns the right answer at the CONTROL'S SPEED. The arm reads green
* and reports a number that belongs to a different build.
*
* `kGdnByteConvDepth` closes it by putting the host's own constant on the wire, and
* `ftg_conv_depth_agrees()` is the single comparison both kernels make. This step proves the
* encoder writes it, that a decode recovers it, and — the part that makes the gate non-vacuous
* — that the comparison can FAIL, on both a wrong depth and the zero a pre-declaration encoder
* would leave.
*/
static void test_declared_conv_depth(void)
{
ft_tt_op_gdn_request request;
uint32_t words[FT_TT_OP_GDN_WORDS];
ftg_shape shape;
int rc;
ft_tt_test_step("the CONV block depth is DECLARED on the wire and the kernels' comparison "
"is proven able to fail");
/* THE COMPARISON ITSELF, BEFORE ANY ENCODER. Both kernels call this and nothing else. */
ft_tt_test_check_fmt("ftg_conv_depth_agrees() is true only on equality",
ftg_conv_depth_agrees(1u, 1u) && ftg_conv_depth_agrees(4u, 4u),
"1/1 %d 4/4 %d", (int)ftg_conv_depth_agrees(1u, 1u),
(int)ftg_conv_depth_agrees(4u, 4u));
/* RED: the silent direction. Host placed for 4, kernels built at 1. Every capacity and
* grid check in conv_is_executable() passes this pair; only the declared depth sees it. */
ft_tt_test_check_fmt("RED — a host at depth 4 against kernels at depth 1 DISAGREES",
!ftg_conv_depth_agrees(4u, 1u), "%d",
(int)ftg_conv_depth_agrees(4u, 1u));
/* RED: the loud direction, which conv_block_depth() already caught. Both now refuse. */
ft_tt_test_check_fmt("RED — a host at depth 1 against kernels at depth 4 DISAGREES",
!ftg_conv_depth_agrees(1u, 4u), "%d",
(int)ftg_conv_depth_agrees(1u, 4u));
/* RED: there is deliberately NO "0 means unset" escape, which is the opposite of the head
* slice's compatibility hinge. An encoder that forgets to declare must refuse, not pass. */
ft_tt_test_check_fmt("RED — an UNDECLARED depth (byte 3 zero) DISAGREES with every real "
"build, so a forgetful encoder refuses instead of passing",
!ftg_conv_depth_agrees(0u, 1u) && !ftg_conv_depth_agrees(0u, 4u), "%d %d",
(int)ftg_conv_depth_agrees(0u, 1u),
(int)ftg_conv_depth_agrees(0u, 4u));
/* THE ENCODER. A CONV descriptor carries this host's constant in byte 3 of the taps word,
* and `ftg_decode()` — the kernels' own decode, not a respelling of it — recovers it. */
fill_conv_request(&request);
rc = ft_tt_op_gdn_parameters(&request, words);
ft_tt_test_check_fmt("a CONV request encodes", rc == FT_TT_OK, "rc=%d", rc);
if (rc != FT_TT_OK)
return;
ft_tt_test_check_fmt("byte 3 of the CONV-taps word is this host's own CONV block depth",
((words[FT_TT_OP_GDN_CONV_TAPS] >> 24) & 0xFFu) ==
(ft_tt_op_gdn_conv_depth() & 0xFFu),
"0x%08X vs depth %u", words[FT_TT_OP_GDN_CONV_TAPS],
ft_tt_op_gdn_conv_depth());
shape = ftg_decode(words);
ft_tt_test_check_fmt("the kernels' own ftg_decode() recovers it, and it AGREES with a "
"matching build",
shape.declared_conv_depth == ft_tt_op_gdn_conv_depth() &&
ftg_conv_depth_agrees(shape.declared_conv_depth,
ft_tt_op_gdn_conv_depth()),
"%u", shape.declared_conv_depth);
/* AND THE DEPTH DID NOT LAND ON A FIELD THAT WAS ALREADY THERE. Bytes 0-2 are the taps and
* the head slice; a shift that overlapped one of them would be caught here and nowhere
* else, because every other arm reads them through the same accessor that wrote them. */
ft_tt_test_check_fmt("declaring the depth left the taps and the head slice alone",
shape.conv_taps == FT_TT_OP_GDN_QWEN35_CONV_TAPS &&
shape.head_first == 0u && shape.head_count == request.v_heads,
"taps %u first %u count %u", shape.conv_taps, shape.head_first,
shape.head_count);
/* PHASE DELTA DOES NOT READ THE DEPTH, so its taps word stays zero and its descriptor is
* byte-for-byte the one that shipped. A declaration on DELTA would have refused every
* DELTA descriptor the moment the kernels started comparing. */
fill_delta_request(&request);
rc = ft_tt_op_gdn_parameters(&request, words);
ft_tt_test_check_fmt("a DELTA descriptor declares NOTHING and its taps word is still zero",
rc == FT_TT_OK && words[FT_TT_OP_GDN_CONV_TAPS] == 0u, "rc=%d 0x%08X",
rc, words[FT_TT_OP_GDN_CONV_TAPS]);
/* THE DEPTH HAS TO FIT THE BYTE IT RIDES IN. Nothing else in the tree says so. */
ft_tt_test_check_fmt("FT_TT_OP_GDN_CONV_DEPTH fits byte 3 and is not zero",
FT_TT_OP_GDN_CONV_DEPTH >= 1u && FT_TT_OP_GDN_CONV_DEPTH <= 255u, "%u",
(unsigned)FT_TT_OP_GDN_CONV_DEPTH);
/* THE KNOB THAT LETS ONE BINARY RUN THE WHOLE SWEEP, tested through the PARSER rather
* than the cached accessor: the accessor answers once per process and a sweep needs more
* than one answer per test run. */
ft_tt_test_check_fmt("an unset knob is the compile-time default",
ft_tt_op_gdn_conv_depth_parse(NULL) == FT_TT_OP_GDN_CONV_DEPTH &&
ft_tt_op_gdn_conv_depth_parse("") == FT_TT_OP_GDN_CONV_DEPTH,
"%u %u", ft_tt_op_gdn_conv_depth_parse(NULL),
ft_tt_op_gdn_conv_depth_parse(""));
ft_tt_test_check_fmt("every rung of the sweep parses to itself",
ft_tt_op_gdn_conv_depth_parse("1") == 1u &&
ft_tt_op_gdn_conv_depth_parse("2") == 2u &&
ft_tt_op_gdn_conv_depth_parse("4") == 4u &&
ft_tt_op_gdn_conv_depth_parse("255") == 255u,
"%u %u %u %u", ft_tt_op_gdn_conv_depth_parse("1"),
ft_tt_op_gdn_conv_depth_parse("2"), ft_tt_op_gdn_conv_depth_parse("4"),
ft_tt_op_gdn_conv_depth_parse("255"));
/* RED, and it is the clause that matters: a knob that quietly falls back to the default
* on a value it cannot read measures the CONTROL once per rung and prints a flat ladder
* that reads exactly like a refuted lever. */
ft_tt_test_check_fmt("RED — 0, 256, a negative, a trailing character and pure text ALL "
"return 0, never the default",
ft_tt_op_gdn_conv_depth_parse("0") == 0u &&
ft_tt_op_gdn_conv_depth_parse("256") == 0u &&
ft_tt_op_gdn_conv_depth_parse("-1") == 0u &&
ft_tt_op_gdn_conv_depth_parse("4x") == 0u &&
ft_tt_op_gdn_conv_depth_parse("eight") == 0u,
"%u %u %u %u %u", ft_tt_op_gdn_conv_depth_parse("0"),
ft_tt_op_gdn_conv_depth_parse("256"),
ft_tt_op_gdn_conv_depth_parse("-1"),
ft_tt_op_gdn_conv_depth_parse("4x"),
ft_tt_op_gdn_conv_depth_parse("eight"));
/* AND WHAT THE ACCESSOR ANSWERS IS WHAT THE DESCRIPTOR CARRIED, on this process's own
* value — the two halves of the knob checked against each other rather than separately. */
ft_tt_test_check_fmt("the descriptor declared the accessor's answer, not the macro's",
shape.declared_conv_depth == ft_tt_op_gdn_conv_depth(), "%u vs %u",
shape.declared_conv_depth, ft_tt_op_gdn_conv_depth());
}
static void test_parameters(void)
{
ft_tt_op_gdn_request request;
uint32_t words[FT_TT_OP_GDN_WORDS];
float scale;
int rc;
ft_tt_test_step("the parameter encoding, at Ornith's real shape (S=128, 32 v-heads, "
"16 k-heads, conv_dim 8192, 4 taps)");
fill_delta_request(&request);
rc = ft_tt_op_gdn_parameters(&request, words);
ft_tt_test_check_fmt("a DELTA request encodes", rc == FT_TT_OK, "rc=%d", rc);
if (rc != FT_TT_OK)
return;
ft_tt_test_check_fmt("the shape word packs phase, S/32, v-heads and k-heads",
(words[FT_TT_OP_GDN_SHAPE] & 0xFFu) == FT_TT_OP_GDN_PHASE_DELTA &&
((words[FT_TT_OP_GDN_SHAPE] >> 8) & 0xFFu) == 4u &&
((words[FT_TT_OP_GDN_SHAPE] >> 16) & 0xFFu) == 32u &&
((words[FT_TT_OP_GDN_SHAPE] >> 24) & 0xFFu) == 16u,
"0x%08X", words[FT_TT_OP_GDN_SHAPE]);
ft_tt_test_check_fmt("a coordinate pair round-trips through one word",
(words[FT_TT_OP_GDN_STATE_XY] & 0xFFFFu) == 5u &&
((words[FT_TT_OP_GDN_STATE_XY] >> 16) & 0xFFFFu) == 6u,
"0x%08X", words[FT_TT_OP_GDN_STATE_XY]);
memcpy(&scale, &words[FT_TT_OP_GDN_SCALE_BITS], sizeof(scale));
/* 1/sqrt(128) = 0.088388347... The bits are compared against the value the CPU oracle
* multiplies y by, not against a rounded decimal. */
ft_tt_test_check_fmt("the scale word is the fp32 bits of 1/sqrt(S)",
scale > 0.0883883f && scale < 0.0883884f, "%.9f", (double)scale);
/* The writer publishes the CARRIED STATE as well as y, because the updated state is a
* RESULT and rule 1 gives results to the writer. This counts CB_OUT PAGES:
* 32 heads x 4 tile-rows x (4 state tiles + 1 y column) = 640.
*
* NOT 4 — that would leave the updated state in L1 and let the next token read a stale
* recurrence, which a single-step comparison passes and the 48-token gate does not.
* NOT 516 — y is 4 tiles in DRAM but crosses the buffer as 128 per-head COLUMN tiles,
* because the (e) reduce produces one column per head and the writer scatters it into a
* row. Counting the DRAM figure starves the writer by 124 pages and hangs the triad on
* its bound. */
ft_tt_test_check_fmt("a DELTA descriptor passes 640 CB_OUT pages, state and y interleaved",
words[FT_TT_OP_GDN_TILE_COUNT] == 640u, "%u",
words[FT_TT_OP_GDN_TILE_COUNT]);
/* The words a DELTA descriptor does not use are ZERO, which is what
* resident_vector_protocol.h requires of every family. */
ft_tt_test_check_fmt("a DELTA descriptor zeroes the CONV-only words",
words[FT_TT_OP_GDN_CONV_TILE_COLS] == 0u &&
words[FT_TT_OP_GDN_CONV_TAPS] == 0u &&
words[FT_TT_OP_GDN_CONSTANT_WEIGHT] == 0u &&
words[FT_TT_OP_GDN_STATE_CONV] == 0u,
"%u %u %u %u", words[FT_TT_OP_GDN_CONV_TILE_COLS],
words[FT_TT_OP_GDN_CONV_TAPS], words[FT_TT_OP_GDN_CONSTANT_WEIGHT],
words[FT_TT_OP_GDN_STATE_CONV]);
ft_tt_test_check_fmt("the reserved word is 0", words[FT_TT_OP_GDN_FLAGS] == 0u, "%u",
words[FT_TT_OP_GDN_FLAGS]);
fill_conv_request(&request);
rc = ft_tt_op_gdn_parameters(&request, words);
ft_tt_test_check_fmt("a CONV request encodes", rc == FT_TT_OK, "rc=%d", rc);
if (rc != FT_TT_OK)
return;
ft_tt_test_check_fmt("conv_dim/32 needs a whole word and gets one",
words[FT_TT_OP_GDN_CONV_TILE_COLS] == 256u, "%u",
words[FT_TT_OP_GDN_CONV_TILE_COLS]);
/* The convolved stream, then the ROLLED WINDOW — taps-1 deep over the same 256 columns,
* and carried state for the same reason the recurrent matrix is. 256 + 256*3 = 1024. */
ft_tt_test_check_fmt("a CONV descriptor writes the convolved stream THEN the rolled window",
words[FT_TT_OP_GDN_TILE_COUNT] == 1024u, "%u",
words[FT_TT_OP_GDN_TILE_COUNT]);
ft_tt_test_check_fmt("a CONV descriptor zeroes the DELTA-only words",
words[FT_TT_OP_GDN_CONSTANT_GATE] == 0u &&
words[FT_TT_OP_GDN_STATE_RECURRENT] == 0u,
"%u %u", words[FT_TT_OP_GDN_CONSTANT_GATE],
words[FT_TT_OP_GDN_STATE_RECURRENT]);
}
static void test_refusals(void)
{
ft_tt_op_gdn_request request;
uint32_t words[FT_TT_OP_GDN_WORDS];
ft_tt_test_step("the shapes this op REFUSES, one mutation at a time from a request that "
"encodes");
fill_delta_request(&request);
request.phase = 0u;
ft_tt_test_check("phase 0 — a zeroed block is invalid, not plausible",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
/* THE ONE THAT MATTERS MOST. The GQA repeat is `kh = vh % k_heads`, which is a repeat
* only when k_heads divides v_heads. Pantheon's own docs name a real gibberish bug from
* getting this mapping backwards, so a shape where the modulo is not a repeat has to be
* refused rather than executed into nonsense. */
fill_delta_request(&request);
request.k_heads = 12u;
ft_tt_test_check("k_heads that does not divide v_heads — the GQA repeat is not a repeat",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
fill_delta_request(&request);
request.state_size = 100u;
ft_tt_test_check("a state size that is not a multiple of the tile dimension",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
fill_delta_request(&request);
request.state_size = 32u * 9u;
ft_tt_test_check("a state size whose tile count does not fit the shape byte",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
fill_delta_request(&request);
request.state_recurrent = operand(0u, 0u, 0u, 0u);
ft_tt_test_check("a DELTA request with no recurrent state operand",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
fill_conv_request(&request);
request.conv_taps = 1u;
ft_tt_test_check("a one-tap conv — the carried window would be write-only",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
fill_conv_request(&request);
request.state_conv = operand(0u, 0u, 0u, 0u);
ft_tt_test_check("a CONV request with no window operand",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
ft_tt_test_check("a null request", ft_tt_op_gdn_parameters(NULL, words) == FT_TT_EINVAL,
NULL);
fill_delta_request(&request);
ft_tt_test_check("a null output", ft_tt_op_gdn_parameters(&request, NULL) == FT_TT_EINVAL,
NULL);
}
static void test_cb_placement(void)
{
ft_tt_op_gdn_request request;
ft_tt_launch_cb_entry entries[FT_TT_OP_GDN_CB_COUNT];
uint64_t mask = 0u;
unsigned index;
int rc;
ft_tt_test_step("circular-buffer placement fits the arena and names every buffer once");
fill_delta_request(&request);
rc = ft_tt_op_gdn_place_cbs(&request, entries, g_cb_buffer, sizeof(g_cb_buffer), &mask);
ft_tt_test_check_fmt("a DELTA request places its CBs", rc == FT_TT_OK, "rc=%d", rc);
if (rc != FT_TT_OK)
return;
for (index = 0; index < FT_TT_OP_GDN_CB_COUNT; index++) {
char label[96];
snprintf(label, sizeof(label), "CB %u sits inside the arena", entries[index].index);
ft_tt_test_check_fmt(label,
entries[index].address >= FT_TT_OP_CB_ARENA_BASE &&
entries[index].address < FT_TT_OP_CB_ARENA_END,
"0x%08X", entries[index].address);
}
ft_tt_test_check_fmt("the mask names exactly the eleven buffers placed",
mask == ((1ull << FT_TT_OP_GDN_CB_IN) | (1ull << FT_TT_OP_GDN_CB_STATE) |
(1ull << FT_TT_OP_GDN_CB_SCALER) |
(1ull << FT_TT_OP_GDN_CB_VECTOR) |
(1ull << FT_TT_OP_GDN_CB_STAGE) |
(1ull << FT_TT_OP_GDN_CB_PRED) |
(1ull << FT_TT_OP_GDN_CB_DELTA) |
(1ull << FT_TT_OP_GDN_CB_PROD) |
(1ull << FT_TT_OP_GDN_CB_WORK) |
(1ull << FT_TT_OP_GDN_CB_OUT) |
(1ull << FT_TT_OP_GDN_CB_CARRY)),
"0x%016llX", (unsigned long long)mask);
/* THE ONE-PRODUCER RULE, AS AN INDEX TEST. PHASE CONV's qkv stream is pushed by the
* READER and PHASE DELTA's working buffer by the COMPUTE thread, and the two push
* primitives keep incompatible books: the dataflow one INCREMENTS the tiles_received
* stream register, the compute one STORES a local counter over it. Sharing one index
* therefore makes `cb_wait_front` on that buffer stop blocking after the first DELTA
* descriptor — measured on silicon 2026-08-18 as PHASE CONV's second pass copying stale
* pages, `s = t (mod 8)` at all 256 tiles. Nothing else in this suite would notice the
* two constants being equal again. */
ft_tt_test_check_fmt("CB_IN and CB_WORK are DIFFERENT indices — one producer per buffer",
FT_TT_OP_GDN_CB_IN != FT_TT_OP_GDN_CB_WORK, "in=%u work=%u",
FT_TT_OP_GDN_CB_IN, FT_TT_OP_GDN_CB_WORK);
fill_conv_request(&request);
rc = ft_tt_op_gdn_place_cbs(&request, entries, g_cb_buffer, sizeof(g_cb_buffer), &mask);
ft_tt_test_check_fmt("a CONV request places the SAME eleven buffers — one binary, one "
"layout, two phases",
rc == FT_TT_OK, "rc=%d", rc);
}
/* ================================================================
* The CB placement, against the refusals the DEVICE makes
* ================================================================ */
/**
* The device's own guards, transliterated.
*
* `delta_is_executable()` and `conv_is_executable()` in `kernels/resident_gdn_reader.c` refuse
* a descriptor the placed buffers cannot serve, and `conv_block_depth()` refuses a block depth
* they cannot hold. NOTHING made the host's placement satisfy them — the two were written a
* commit apart and agreed only at Ornith's shape, by arithmetic coincidence
* (2 * state_tiles = 8 = 2 * conv_depth * taps, and state_tiles + 3 = 7 <= 8). This is the
* check that would have said so.
*
* `ft_tensix_cb_push_grid_is_safe()` is reduced here to its page-count half — "chunk <= pages
* and pages % chunk == 0". Its third clause tests a live FIFO pointer, which is a runtime fact
* no host placement can decide; the two static clauses are the ones a placement owns.
*/
static int placement_satisfies_device(const ft_tt_launch_cb_entry *entries, uint32_t state_tiles,
uint32_t taps, const char **out_reason)
{
const uint32_t depth = FT_TT_OP_GDN_CONV_DEPTH;
const ft_tt_launch_cb_entry *in =
ft_tt_op_cb_find(entries, FT_TT_OP_GDN_CB_COUNT, FT_TT_OP_GDN_CB_IN);
const ft_tt_launch_cb_entry *state =
ft_tt_op_cb_find(entries, FT_TT_OP_GDN_CB_COUNT, FT_TT_OP_GDN_CB_STATE);
const ft_tt_launch_cb_entry *scaler =
ft_tt_op_cb_find(entries, FT_TT_OP_GDN_CB_COUNT, FT_TT_OP_GDN_CB_SCALER);
const ft_tt_launch_cb_entry *vector =
ft_tt_op_cb_find(entries, FT_TT_OP_GDN_CB_COUNT, FT_TT_OP_GDN_CB_VECTOR);
const ft_tt_launch_cb_entry *stage =
ft_tt_op_cb_find(entries, FT_TT_OP_GDN_CB_COUNT, FT_TT_OP_GDN_CB_STAGE);
const ft_tt_launch_cb_entry *work =
ft_tt_op_cb_find(entries, FT_TT_OP_GDN_CB_COUNT, FT_TT_OP_GDN_CB_WORK);
unsigned index;
*out_reason = "";
if (!in || !state || !scaler || !vector || !stage || !work) {
*out_reason = "a buffer the kernels name was not placed";
return 0;
}
/* Every page is an fp32 tile — the CB table says so for all ten. */
for (index = 0; index < FT_TT_OP_GDN_CB_COUNT; index++) {
if (entries[index].page_size_bytes != FT_TT_OP_GDN_FP32_TILE_BYTES) {
*out_reason = "a page is not an fp32 tile";
return 0;
}
}
/* delta_is_executable() */
if (scaler->page_count < 2u * state_tiles) {
*out_reason = "CB_SCALER cannot hold this head's k and q replicas";
return 0;
}
if (vector->page_count < state_tiles + FT_TT_OP_GDN_VECTOR_CONSTANTS) {
*out_reason = "CB_VECTOR cannot hold the v columns plus decay/beta/scale";
return 0;
}
if (stage->page_count < state_tiles + 1u) {
*out_reason = "CB_STAGE cannot hold a row block plus the gate page";
return 0;
}
if (work->page_count < 2u * state_tiles) {
*out_reason = "CB_WORK cannot hold the decayed tile-row AND the updated one";
return 0;
}
if (state->page_count < state_tiles || (state->page_count % state_tiles) != 0u) {
*out_reason = "CB_STATE is off the DELTA tile-row push grid";
return 0;
}
/* conv_block_depth() and conv_is_executable() */
if (in->page_count < 2u * depth || (in->page_count % depth) != 0u) {
*out_reason = "CB_IN is off the CONV block grid";
return 0;
}
if (vector->page_count < 2u * depth * taps || (vector->page_count % (depth * taps)) != 0u) {
*out_reason = "CB_VECTOR is off the CONV weight-plane grid";
return 0;
}
if (state->page_count < 2u * depth * (taps - 1u) || (state->page_count % depth) != 0u) {
*out_reason = "CB_STATE cannot hold the CONV window";
return 0;
}
/* ---------------------------------------------------------------------------
* THE CONSUMER'S INDEXED-READ GRID — the half of the rule this suite did not have.
*
* `ft_tensix_unpack_ab(cb, ..., tile)` resolves to `read_pointer + tile * page_size`, and
* a FIFO pointer wraps on an EXACT HIT of the limit and on nothing else. So a compute
* thread that waits for `n` pages and then names tiles `0..n-1` needs the front low
* enough that the whole window still fits, and the front is a multiple of `n` only when
* `n` divides the buffer. Everything above tests CAPACITY and the PRODUCER's push grid;
* neither of those sees this.
*
* MEASURED ON SILICON 2026-08-18, and both were wrong at ORNITH'S OWN SHAPE. CB_VECTOR
* held 8 pages against a DELTA window of `state_tiles + 3` = 7, so the front walked
* 0, 7, 6, 5, ... and 24 of 32 heads read their v / decay / beta / scale out of the
* buffer and into CB_STAGE — exactly the 8 heads whose front was 0 or 1 scored 4e-3
* against the closed form and the rest scored 1.0. CB_STATE held 8 against a CONV window
* of `depth*(taps-1)` = 3, so 2 blocks in 8 read past the end; that one is invisible at
* layer 0 of token 0 only, because the L1 above CB_STATE is CB_SCALER and it is still
* zero until the first DELTA descriptor writes k/q replicas into it.
* --------------------------------------------------------------------------- */
if ((work->page_count % state_tiles) != 0u) {
*out_reason = "CB_WORK is off the DELTA indexed-read grid (a whole state tile-row)";
return 0;
}
/* THE BATCHED (d)'s OWN GRID. It stages `dst_batch` rank-1 products in CB_PROD and then
* indexes every one of them off the front, and it rewrites CB_WORK a block at a time.
* CB_PROD had NO grid before the batch — every access to it named tile 0 — so nothing
* above this line looks at it, and `resolve_cb_pages()` handed it a flat 2 pages whatever
* the shape. `delta_is_executable()` in resident_gdn_reader.c refuses the same two
* clauses on the device; this is the host side of that pair. */
{
const uint32_t dst_batch = ftg_dst_batch(state_tiles, FT_TT_OP_GDN_DST_BATCH);
const ft_tt_launch_cb_entry *prod =
ft_tt_op_cb_find(entries, FT_TT_OP_GDN_CB_COUNT, FT_TT_OP_GDN_CB_PROD);
if (!prod) {
*out_reason = "CB_PROD was not placed";
return 0;
}
if (prod->page_count < 2u * dst_batch) {
*out_reason = "CB_PROD cannot hold two DST batches of rank-1 products";
return 0;
}
if ((prod->page_count % dst_batch) != 0u) {
*out_reason = "CB_PROD is off the DELTA DST-batch indexed-read grid";
return 0;
}
if ((work->page_count % dst_batch) != 0u) {
*out_reason = "CB_WORK is off the DELTA DST-batch rotation grid";
return 0;
}
}
if ((state->page_count % state_tiles) != 0u) {
*out_reason = "CB_STATE is off the DELTA indexed-read grid";
return 0;
}
if ((scaler->page_count % (2u * state_tiles)) != 0u) {
*out_reason = "CB_SCALER is off the DELTA indexed-read grid (k AND q replicas)";
return 0;
}
if ((vector->page_count % (state_tiles + FT_TT_OP_GDN_VECTOR_CONSTANTS)) != 0u) {
*out_reason = "CB_VECTOR is off the DELTA indexed-read grid (the v columns plus the "
"three per-head constants)";
return 0;
}
if ((state->page_count % (depth * (taps - 1u))) != 0u) {
*out_reason = "CB_STATE is off the CONV indexed-read grid (every tap of a column)";
return 0;
}
if ((vector->page_count % (depth * taps)) != 0u) {
*out_reason = "CB_VECTOR is off the CONV indexed-read grid (every weight plane)";
return 0;
}
/* THE WRITER'S BARRIER GROUP. CB_OUT has no grid on either side — the producer pushes a
* page at a time, and the writer addresses its sources with the wrap SPELLED OUT and pops
* one page at a time — so this clause is purely CAPACITY, and it is the capacity the
* writer's own clamp reads back off the placed buffer. Two groups: one draining behind a
* barrier, one filling. A placement short of it does not fail on the card; it silently
* demotes the writer to the per-tile sequence, which is a slow build nothing refuses. */
{
const ft_tt_launch_cb_entry *out =
ft_tt_op_cb_find(entries, FT_TT_OP_GDN_CB_COUNT, FT_TT_OP_GDN_CB_OUT);
if (!out) {
*out_reason = "CB_OUT was not placed";
return 0;
}
if (out->page_count < 2u * ftg_writer_block(state_tiles, FT_TT_OP_GDN_WRITER_RUNG)) {
*out_reason = "CB_OUT cannot hold two of the WRITER's barrier groups";
return 0;
}
}
return 1;
}
static void test_cb_placement_serves_the_device(void)
{
ft_tt_op_gdn_request request;
ft_tt_launch_cb_entry entries[FT_TT_OP_GDN_CB_COUNT];
uint64_t mask = 0u;
uint32_t state_tiles;
uint32_t taps;
ft_tt_test_step("the placement satisfies every refusal the READER makes, at EVERY shape "
"ft_tt_op_gdn_shape_is_supported() admits");
for (state_tiles = 1u; state_tiles <= 8u; state_tiles++) {
for (taps = 2u; taps <= 8u; taps++) {
const char *reason = "";
char label[160];
int rc;
fill_delta_request(&request);
request.state_size = state_tiles * FT_TT_OP_TILE_DIM;
request.conv_taps = taps;
rc = ft_tt_op_gdn_place_cbs(&request, entries, g_cb_buffer, sizeof(g_cb_buffer),
&mask);
snprintf(label, sizeof(label), "S=%u (%u state tiles), %u taps — placed and "
"executable",
state_tiles * 32u, state_tiles, taps);
ft_tt_test_check_fmt(
label,
rc == FT_TT_OK &&
placement_satisfies_device(entries, state_tiles, taps, &reason),
"rc=%d %s", rc, reason);
}
}
}
static void test_tile_format(void)
{
ft_tt_op_gdn_request request;
ft_tt_launch_cb_entry entries[FT_TT_OP_GDN_CB_COUNT];
uint32_t words[FT_TT_OP_GDN_WORDS];
uint64_t mask = 0u;
unsigned index;
ft_tt_test_step("every operand and every circular-buffer page is an fp32 tile — the family "
"has exactly one format and one tile size");
fill_delta_request(&request);
(void)ft_tt_op_gdn_parameters(&request, words);
ft_tt_test_check_fmt("a DELTA descriptor publishes 4096 as its tile size",
words[FT_TT_OP_GDN_TILE_BYTES] == FT_TT_OP_GDN_FP32_TILE_BYTES, "%u",
words[FT_TT_OP_GDN_TILE_BYTES]);
fill_conv_request(&request);
(void)ft_tt_op_gdn_parameters(&request, words);
/* CONV took the word from the ACTIVATION and DELTA from the STATE, so the two phases of
* one binary could publish different page sizes for the same ten buffers. */
ft_tt_test_check_fmt("a CONV descriptor publishes the SAME tile size, not its own "
"operand's",
words[FT_TT_OP_GDN_TILE_BYTES] == FT_TT_OP_GDN_FP32_TILE_BYTES, "%u",
words[FT_TT_OP_GDN_TILE_BYTES]);
/* THE ONE THAT WOULD HAVE CORRUPTED L1 ON THE FIRST RUN. The kernels compare
* kGdnTileBytes against their compiled-in 4096, but that word comes from ONE of five
* endpoints, so a bf16 activation beside an fp32 state passed the device's check and then
* had 2048-byte pages placed under 4096-byte tile reads. */
fill_delta_request(&request);
request.activation_in.tile_bytes = FT_TT_OP_TILE_BYTES_BF16;
ft_tt_test_check("a bf16 activation beside an fp32 state — the device's one tile-size word "
"cannot see it, so the host refuses it",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
fill_conv_request(&request);
request.conv_weight.tile_bytes = FT_TT_OP_TILE_BYTES_BFP8_B;
ft_tt_test_check("a Bfp8_b conv weight — a block-float page cannot hold an fp32 tile",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
/* THE ONE THAT DID CORRUPT DRAM ON THE FIRST SILICON RUN, and the device could not see it
* either: `kGdnActivationXy` is ONE packed coordinate for the input AND the output, so a
* stager that puts them on different DRAM endpoints makes the writer land the answer at
* the output's offset INSIDE THE INPUT'S BANK. The descriptor retires clean, the counters
* are right, and the output buffer is never written. Both phases carry the pair. */
fill_conv_request(&request);
request.activation_out.x = (uint16_t)(request.activation_in.x + 1u);
ft_tt_test_check("a CONV output on a different DRAM x from its input — one packed xy "
"cannot address two banks",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
fill_delta_request(&request);
request.activation_out.y = (uint16_t)(request.activation_in.y + 1u);
ft_tt_test_check("a DELTA output on a different DRAM y from its input",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_EINVAL, NULL);
fill_delta_request(&request);
ft_tt_test_check("the activation pair on ONE endpoint is still accepted",
ft_tt_op_gdn_parameters(&request, words) == FT_TT_OK, NULL);
fill_delta_request(&request);
(void)ft_tt_op_gdn_place_cbs(&request, entries, g_cb_buffer, sizeof(g_cb_buffer), &mask);
for (index = 0; index < FT_TT_OP_GDN_CB_COUNT; index++) {
char label[96];
snprintf(label, sizeof(label), "CB %u holds fp32 pages", entries[index].index);
ft_tt_test_check_fmt(label, entries[index].page_size_bytes == FT_TT_OP_GDN_FP32_TILE_BYTES,
"%u B", entries[index].page_size_bytes);
}
}
/* ================================================================
* The DRAM layouts the reader and the writer address
* ================================================================ */
/**
* The layout arithmetic, against literals worked out from the ORACLE rather than from the
* accessors.
*
* ---------------------------------------------------------------------------
* WHY THIS ONE INCLUDES THE DEVICE HEADER INSTEAD OF PARSING IT
* ---------------------------------------------------------------------------
* The wire CONSTANTS are declared twice — once for the host and once for the kernels — so a
* suite that included the device header would compile against the same declaration the kernel
* does and could not see the two diverge. That is why `test_wire_constants()` parses.
*
* The layout ACCESSORS are declared ONCE, in `resident_gdn_protocol.h`, and both dataflow
* kernels call them. There is no second declaration to drift from — the drift risk is between
* the accessor and the layout a future stager writes, and the only way to test that is to
* compute the expected tile index INDEPENDENTLY here and compare. Every literal below is
* derived from `pn_qwen35moe_ssm_recurrence()` and `ft_tt_tilize_strip()`, not from the
* function it checks.
*/
static ftg_shape ornith_shape(uint32_t phase)
{
ftg_shape shape;
shape.phase = phase;
shape.state_tiles = FT_TT_OP_GDN_QWEN35_STATE / FT_TT_OP_TILE_DIM; /* 4 */
shape.v_heads = FT_TT_OP_GDN_QWEN35_V_HEADS; /* 32 */
shape.k_heads = FT_TT_OP_GDN_QWEN35_K_HEADS; /* 16 */
shape.conv_tile_cols = FT_TT_OP_GDN_QWEN35_CONV_DIM / FT_TT_OP_TILE_DIM; /* 256 */
shape.conv_taps = FT_TT_OP_GDN_QWEN35_CONV_TAPS; /* 4 */
shape.tile_bytes = FT_TT_OP_GDN_FP32_TILE_BYTES;
shape.tile_count = 640u;
return shape;
}
static void test_delta_input_layout(void)
{
ftg_shape shape = ornith_shape(FT_TT_OP_GDN_PHASE_DELTA);
ftg_shape wide = shape;
uint32_t head;
int strided = 1;
int block_starts = 1;
ft_tt_test_step("the DELTA q|k|v layout at kGdnActivationIn — ONE head-major ROW-0 STRIP, "
"q and k per K-HEAD");
/* q and k carry 16 k-heads of `state_tiles` CONSECUTIVE row-0 tiles each — 64 tiles. v
* carries 32 v-heads the same way, 128. 256 tiles in all, which is the tilized
* `[1, (2*k_heads + v_heads)*S]` strip PHASE CONV's output already is. */
ft_tt_test_check_fmt("q is k_heads x state_tiles = 64 row-0 tiles",
ftg_delta_stream_tiles(&shape, kGdnStreamQ) == 64u &&
ftg_delta_stream_tiles(&shape, kGdnStreamK) == 64u,
"%u %u", ftg_delta_stream_tiles(&shape, kGdnStreamQ),
ftg_delta_stream_tiles(&shape, kGdnStreamK));
ft_tt_test_check_fmt("v is v_heads x state_tiles = 128 row-0 tiles",
ftg_delta_stream_tiles(&shape, kGdnStreamV) == 128u, "%u",
ftg_delta_stream_tiles(&shape, kGdnStreamV));
ft_tt_test_check_fmt("q | k | v sit at tiles 0, 64 and 128",
ftg_delta_stream_base(&shape, kGdnStreamQ) == 0u &&
ftg_delta_stream_base(&shape, kGdnStreamK) == 64u &&
ftg_delta_stream_base(&shape, kGdnStreamV) == 128u,
"%u %u %u", ftg_delta_stream_base(&shape, kGdnStreamQ),
ftg_delta_stream_base(&shape, kGdnStreamK),
ftg_delta_stream_base(&shape, kGdnStreamV));
ft_tt_test_check_fmt("the whole staged input is 256 tiles",
ftg_delta_input_tiles(&shape) == 256u, "%u",
ftg_delta_input_tiles(&shape));
/* THE STRIDE IS THE GEOMETRY. Consecutive heads are exactly `state_tiles` apart in every
* block, head 0 of a block IS the block's base, and head 31's v block ENDS on the last
* tile of the whole input — so the three blocks tile the buffer with no hole and no
* overlap. Under the old one-row-per-head packing every head of a row block returned the
* SAME tile and this stride was 0, so the arm is red against it at head 1. */
for (head = 0; head + 1u < shape.v_heads; head++) {
if (ftg_delta_head_tile(&shape, kGdnStreamV, head + 1u) -
ftg_delta_head_tile(&shape, kGdnStreamV, head) !=
shape.state_tiles)
strided = 0;
}
for (head = 0; head < shape.k_heads; head++) {
if (ftg_delta_head_tile(&shape, kGdnStreamQ, head) != head * shape.state_tiles ||
ftg_delta_head_tile(&shape, kGdnStreamK, head) != 64u + head * shape.state_tiles)
strided = 0;
}
ft_tt_test_check_fmt("consecutive heads are state_tiles apart in every block — the stride "
"the old one-row-per-head packing made 0",
strided && ftg_delta_head_tile(&shape, kGdnStreamV, 1u) -
ftg_delta_head_tile(&shape, kGdnStreamV, 0u) ==
4u,
"%u %u", ftg_delta_head_tile(&shape, kGdnStreamV, 0u),
ftg_delta_head_tile(&shape, kGdnStreamV, 1u));
ft_tt_test_check_fmt("head 0 of q, k and v IS that stream's base",
ftg_delta_head_tile(&shape, kGdnStreamQ, 0u) ==
ftg_delta_stream_base(&shape, kGdnStreamQ) &&
ftg_delta_head_tile(&shape, kGdnStreamK, 0u) ==
ftg_delta_stream_base(&shape, kGdnStreamK) &&
ftg_delta_head_tile(&shape, kGdnStreamV, 0u) ==
ftg_delta_stream_base(&shape, kGdnStreamV),
"%u %u %u", ftg_delta_head_tile(&shape, kGdnStreamQ, 0u),
ftg_delta_head_tile(&shape, kGdnStreamK, 0u),
ftg_delta_head_tile(&shape, kGdnStreamV, 0u));
ft_tt_test_check_fmt("v-head 31's block ENDS exactly at the input's last tile — no hole, "
"no overlap, nothing off the end",
ftg_delta_head_tile(&shape, kGdnStreamV, shape.v_heads - 1u) +
shape.state_tiles ==
ftg_delta_input_tiles(&shape),
"%u vs %u",
ftg_delta_head_tile(&shape, kGdnStreamV, shape.v_heads - 1u) +
shape.state_tiles,
ftg_delta_input_tiles(&shape));
/* And the k block's last head ends exactly where the v block begins. */
ft_tt_test_check_fmt("k-head 15's block ends exactly where v begins",
ftg_delta_head_tile(&shape, kGdnStreamK, shape.k_heads - 1u) +
shape.state_tiles ==
ftg_delta_stream_base(&shape, kGdnStreamV),
"%u vs %u",
ftg_delta_head_tile(&shape, kGdnStreamK, shape.k_heads - 1u) +
shape.state_tiles,
ftg_delta_stream_base(&shape, kGdnStreamV));
/* The row inside every one of those tiles is 0 and nothing reads rows 1-31. */
ft_tt_test_check_fmt("a strip's data is in ROW 0 of every tile — it used to be head % 32",
(uint32_t)kGdnStripRow == 0u, "%u", (uint32_t)kGdnStripRow);
/* q and k are per K-HEAD and v per V-HEAD, so the three blocks are NOT the same size
* whenever the GQA group is not 1. 64 v-heads over 16 k-heads: q and k are 64 tiles each,
* v is 256, and the input is 384. The bases do not move, because they are the k blocks. */
wide.v_heads = 64u;
ft_tt_test_check_fmt("64 v-heads over 16 k-heads — v is 64 x 4 = 256 tiles",
ftg_delta_stream_tiles(&wide, kGdnStreamV) == 256u, "%u",
ftg_delta_stream_tiles(&wide, kGdnStreamV));
ft_tt_test_check_fmt("and the whole input is 384 tiles, not 256",
ftg_delta_input_tiles(&wide) == 384u &&
ftg_delta_stream_base(&wide, kGdnStreamV) == 128u,
"%u %u", ftg_delta_input_tiles(&wide),
ftg_delta_stream_base(&wide, kGdnStreamV));
/* And the mirror case: more k-heads than v-heads cannot happen (the GQA repeat needs
* k_heads to divide v_heads), but 64 of each must still give three equal blocks of 256. */
wide.k_heads = 64u;
ft_tt_test_check_fmt("64 of each — three equal blocks of 256, bases 0, 256, 512",
ftg_delta_stream_base(&wide, kGdnStreamQ) == 0u &&
ftg_delta_stream_base(&wide, kGdnStreamK) == 256u &&
ftg_delta_stream_base(&wide, kGdnStreamV) == 512u &&
ftg_delta_input_tiles(&wide) == 768u,
"%u %u %u total=%u", ftg_delta_stream_base(&wide, kGdnStreamQ),
ftg_delta_stream_base(&wide, kGdnStreamK),
ftg_delta_stream_base(&wide, kGdnStreamV),
ftg_delta_input_tiles(&wide));
for (head = 0; head < wide.v_heads; head++) {
if (ftg_delta_head_tile(&wide, kGdnStreamV, head) !=
ftg_delta_stream_base(&wide, kGdnStreamV) + head * wide.state_tiles)
block_starts = 0;
}
ft_tt_test_check_fmt("every one of the 64 v-heads starts at its base + h*state_tiles, "
"including the ones past 32 that a row block could not hold",
block_starts && ftg_delta_head_tile(&wide, kGdnStreamV, 32u) == 640u,
"%u", ftg_delta_head_tile(&wide, kGdnStreamV, 32u));
/* The GQA repeat is INTERLEAVED. v-head 17 reads k-head 1, not k-head 8. */
ft_tt_test_check_fmt("the GQA repeat is vh mod k_heads, not vh / kv_groups",
ftg_k_head_for(17u, 16u) == 1u && ftg_k_head_for(31u, 16u) == 15u,
"%u %u", ftg_k_head_for(17u, 16u), ftg_k_head_for(31u, 16u));
/* And the repeat is now an ADDRESS in the k strip: v-head 17 reads tiles [4, 8). */
ft_tt_test_check_fmt("v-head 17's k tiles are k-head 1's four, at 64 + 4 = 68",
ftg_delta_head_tile(&shape, kGdnStreamK,
ftg_k_head_for(17u, shape.k_heads)) == 68u,
"%u",
ftg_delta_head_tile(&shape, kGdnStreamK,
ftg_k_head_for(17u, shape.k_heads)));
}
static void test_state_and_output_layout(void)
{
ftg_shape shape = ornith_shape(FT_TT_OP_GDN_PHASE_DELTA);
ft_tt_test_step("the carried state, y and the CONV planes — the layouts the WRITER "
"addresses");
/* [v_heads, S, S] fp32, head-major, each head tilized into 4x4 tiles row-major.
* Head 1's tile-row 2 therefore starts at 1*16 + 2*4 = 24. */
ft_tt_test_check_fmt("head 1, tile-row 2, tile-column 0 is state tile 24",
ftg_state_tile(&shape, 1u, 2u, 0u) == 24u, "%u",
ftg_state_tile(&shape, 1u, 2u, 0u));
ft_tt_test_check_fmt("a head owns state_tiles^2 = 16 tiles",
ftg_state_tiles_per_head(&shape) == 16u, "%u",
ftg_state_tiles_per_head(&shape));
/* y is a HEAD-MAJOR ROW-0 STRIP: v-head h's S values are ROW 0 of tiles
* [h*state_tiles, +state_tiles), so Ornith's y is 32*4 = 128 tiles and head 31 owns
* 124..127 outright. The gated RMS norm that follows is then a `row_blocks = v_heads,
* tile_cols = state_tiles` reduce over the strip — the same shape of reduce, over a strip
* instead of a matrix. */
ft_tt_test_check_fmt("y is 128 tiles at Ornith, and head 31 owns 124..127 alone",
ftg_y_tile(&shape, 0u, 0u) == 0u && ftg_y_tile(&shape, 0u, 3u) == 3u &&
ftg_y_tile(&shape, 31u, 0u) == 124u &&
ftg_y_tile(&shape, 31u, 3u) == 127u,
"%u %u %u %u", ftg_y_tile(&shape, 0u, 0u), ftg_y_tile(&shape, 0u, 3u),
ftg_y_tile(&shape, 31u, 0u), ftg_y_tile(&shape, 31u, 3u));
/* AND IT IS INJECTIVE. This is the arm that is RED against the geometry that shipped: the
* old `ftg_y_tile()` collapsed every head of a row block onto the same `state_tiles`
* tiles and told them apart by ROW, so (head, tile_row) -> tile was 32-to-1 and the very
* first collision below fires at heads 0 and 1. A strip has no row to hide in, so a
* non-injective y is two heads writing the same DRAM tile. */
{
unsigned char seen[128 * 4];
uint32_t head, row;
uint32_t collisions = 0u;
uint32_t highest = 0u;
memset(seen, 0, sizeof(seen));
for (head = 0; head < shape.v_heads; head++) {
for (row = 0; row < shape.state_tiles; row++) {
const uint32_t tile = ftg_y_tile(&shape, head, row);
if (tile >= sizeof(seen)) {
collisions++;
continue;
}
if (seen[tile])
collisions++;
seen[tile] = 1u;
if (tile + 1u > highest)
highest = tile + 1u;
}
}
ft_tt_test_check_fmt("(head, tile_row) -> y tile is INJECTIVE over all 32 x 4, and the "
"128 tiles it reaches are exactly v_heads * state_tiles",
collisions == 0u && highest == shape.v_heads * shape.state_tiles,
"collisions=%u highest=%u", collisions, highest);
}
/* A 64-head model simply keeps going: head 32 is at 128, not back at the top of a second
* row block, and the buffer is twice as long. */
{
ftg_shape wide = shape;
wide.v_heads = 64u;
ft_tt_test_check_fmt("head 32 of a 64-head model is at y tile 128, and head 63 ends "
"the 256-tile strip",
ftg_y_tile(&wide, 32u, 0u) == 128u &&
ftg_y_tile(&wide, 63u, 3u) + 1u == 256u,
"%u %u", ftg_y_tile(&wide, 32u, 0u), ftg_y_tile(&wide, 63u, 3u));
}
/* y and the DELTA input are the SAME geometry, which is the whole reason for the change:
* head h sits at h*state_tiles in both, so PHASE CONV's output strip IS PHASE DELTA's
* input with no copy and no host round trip. */
ft_tt_test_check_fmt("y's head stride and the v stream's head stride are the same "
"arithmetic — conv_out feeds DELTA with no re-tilization",
ftg_y_tile(&shape, 7u, 2u) ==
ftg_delta_head_tile(&shape, kGdnStreamV, 7u) + 2u -
ftg_delta_stream_base(&shape, kGdnStreamV),
"%u %u", ftg_y_tile(&shape, 7u, 2u),
ftg_delta_head_tile(&shape, kGdnStreamV, 7u) + 2u -
ftg_delta_stream_base(&shape, kGdnStreamV));
/* The CONV weight and window are TAP-MAJOR: plane t is `conv_tile_cols` tiles. Tap 3,
* column 5 is 3*256 + 5. The oracle's own weight is row-major [conv_dim, taps], so a
* stager that reshapes rather than transposes lands here. */
ft_tt_test_check_fmt("conv tap 3, column 5 is plane tile 773",
ftg_conv_plane_tile(&shape, 3u, 5u) == 3u * 256u + 5u, "%u",
ftg_conv_plane_tile(&shape, 3u, 5u));
ft_tt_test_check_fmt("tap 0 is the first plane, so column 5 of it is tile 5",
ftg_conv_plane_tile(&shape, 0u, 5u) == 5u, "%u",
ftg_conv_plane_tile(&shape, 0u, 5u));
}
/**
* What ONE layer costs in card DRAM — the six operand sizes a host stager allocates.
*
* ---------------------------------------------------------------------------
* THE 50.25 MiB THIS ARM USED TO ASSERT WAS A CAPACITY FIGURE READ AS A BUFFER SIZE
* ---------------------------------------------------------------------------
* It decomposed as 48 MiB of recurrent state plus 2.25 MiB of conv window, and the second
* term is `conv_dim * (taps-1) * 4` — the ELEMENT count. The window the reader addresses is
* `(taps-1)` TAP-MAJOR PLANES of `conv_dim/32` fp32 TILES, and a plane is a ONE-ROW tilized
* strip whose other 31 rows are the tile quantum. So the DRAM figure is 32x the element one:
* 3 MiB a layer, not 96 KiB. Allocating the old number would not have been 2.25 MiB of
* under-reserve, it would have been every window read past plane 0 landing in whatever
* `ft_tt_buffer_alloc` put above it.
*
* Every literal below is worked out from the layout section of `resident_gdn_protocol.h`
* and Ornith's shape, not from the functions being checked.
*/
static void test_capacity(void)
{
ft_tt_op_gdn_request request;
const size_t mib = 1024u * 1024u;
size_t state, window, weight, stream, input, output, gate;
ft_tt_test_step("what one layer costs in card DRAM — every operand a stager allocates");
fill_conv_request(&request);
request.v_heads = FT_TT_OP_GDN_QWEN35_V_HEADS;
request.k_heads = FT_TT_OP_GDN_QWEN35_K_HEADS;
state = ft_tt_op_gdn_state_bytes(&request);
window = ft_tt_op_gdn_conv_state_bytes(&request);
weight = ft_tt_op_gdn_conv_weight_bytes(&request);
stream = ft_tt_op_gdn_conv_stream_bytes(&request);
input = ft_tt_op_gdn_delta_input_bytes(&request);
output = ft_tt_op_gdn_delta_output_bytes(&request);
gate = ft_tt_op_gdn_gate_bytes(&request);
/* 32 heads x (128/32)^2 = 512 tiles x 4096 B = 2 MiB — and 32*128*128*4 is the same
* number, because [S,S] is a whole number of tiles with no padding. */
ft_tt_test_check_fmt("a layer's recurrent state is 2 MiB", state == 2u * mib, "%zu bytes",
state);
/* 3 window planes x 256 tiles x 4096 B. The element count, 8192*3*4, is 96 KiB — the
* trap this arm exists to pin. */
ft_tt_test_check_fmt("a layer's conv WINDOW is 3 MiB of tiles, not 96 KiB of elements",
window == 3u * mib, "%zu bytes", window);
/* 4 weight planes x 256 tiles x 4096 B. */
ft_tt_test_check_fmt("a layer's conv WEIGHT is 4 MiB", weight == 4u * mib, "%zu bytes",
weight);
/* One row block of 256 tiles: qkv in, conv_out out. */
ft_tt_test_check_fmt("one conv stream is 1 MiB", stream == mib, "%zu bytes", stream);
/* q|k|v is a ROW-0 STRIP now: 16*4 + 16*4 + 32*4 = 256 tiles, 1 MiB — the same size as the
* conv stream it IS, since q, k and v are channels of conv_out. It was 12 tiles / 48 KiB
* as three one-row-per-head matrices; the strip is 32x bigger in DRAM and that is the
* price of not de-tilizing and re-tilizing 49,408 B a layer over PCIe. */
ft_tt_test_check_fmt("the staged q|k|v is 256 row-0 tiles = 1 MiB, the same buffer "
"conv_out already is",
input == mib && input == stream, "%zu bytes", input);
/* y is v_heads * state_tiles = 128 tiles, 512 KiB — each v-head owns four outright. */
ft_tt_test_check_fmt("y is 128 tiles = 512 KiB", output == 512u * 1024u, "%zu bytes",
output);
/* 2 x 32 fp32 = 256 B, already a multiple of the 64 B read granule. */
ft_tt_test_check_fmt("the gate block is 256 B", gate == 256u, "%zu bytes", gate);
/* THE PER-LAYER TOTAL, AND THE 24-LAYER ONE THE CAPACITY PLAN HAS TO CARRY. Carried
* state is 5 MiB a layer (2 recurrent + 3 window) = 120 MiB over 24, against the 50.25
* the plan reserves; the conv weight adds 96 MiB of constant on top. Stated as one
* expression so the plan's number and the allocation cannot drift apart again. */
ft_tt_test_check_fmt("carried state over 24 SSM layers is 120 MiB, not 50.25",
(state + window) * 24u == 120u * mib, "%.2f MiB",
(double)((state + window) * 24u) / (double)mib);
ft_tt_test_check_fmt("with the conv weights the family owns 216 MiB of card DRAM",
(state + window + weight) * 24u == 216u * mib, "%.2f MiB",
(double)((state + window + weight) * 24u) / (double)mib);
/* An odd head count is the shape the 64 B rounding exists for: 2*31*4 = 248 -> 256. */
request.v_heads = 31u;
request.k_heads = 31u;
ft_tt_test_check_fmt("31 v-heads: 248 B of gate rounds UP to the 64 B read granule",
ft_tt_op_gdn_gate_bytes(&request) == 256u, "%zu bytes",
ft_tt_op_gdn_gate_bytes(&request));
request.v_heads = 33u;
request.k_heads = 33u;
ft_tt_test_check_fmt("33 v-heads: 264 B rounds to 320, so the read has somewhere to land",
ft_tt_op_gdn_gate_bytes(&request) == 320u, "%zu bytes",
ft_tt_op_gdn_gate_bytes(&request));
/* Doubling the v-heads doubles y and the v block; q and k do not move, because they are
* per K-HEAD. 384 input tiles (64 + 64 + 256) and 256 of y. */
request.v_heads = 64u;
request.k_heads = 16u;
ft_tt_test_check_fmt("64 v-heads over 16 k-heads: q|k|v is 384 tiles, y is 256",
ft_tt_op_gdn_delta_input_bytes(&request) == 384u * 4096u &&
ft_tt_op_gdn_delta_output_bytes(&request) == 256u * 4096u,
"%zu %zu", ft_tt_op_gdn_delta_input_bytes(&request),
ft_tt_op_gdn_delta_output_bytes(&request));
/* A request this family will not execute sizes NOTHING, so an unchecked caller allocates
* nothing rather than something wrong. */
request.k_heads = 24u; /* does not divide 64 — the GQA repeat is not a repeat */
ft_tt_test_check_fmt("a shape the family refuses sizes to 0 everywhere",
ft_tt_op_gdn_state_bytes(&request) == 0u &&
ft_tt_op_gdn_conv_state_bytes(&request) == 0u &&
ft_tt_op_gdn_conv_weight_bytes(&request) == 0u &&
ft_tt_op_gdn_delta_input_bytes(&request) == 0u &&
ft_tt_op_gdn_gate_bytes(&request) == 0u,
"%zu", ft_tt_op_gdn_state_bytes(&request));
}
/**
* THE CONV COLUMN SLICE, encoded by the host and decoded by the kernels' own ftg_decode().
*
* The same two bytes are the DELTA head slice, so each phase must read only its own meaning,
* an unsliced CONV must decode to the whole stream with every head, and every malformed slice
* must be refused by the ENCODER rather than reinterpreted by a kernel.
*/
static void test_conv_column_slice(void)
{
ft_tt_op_gdn_request request;
uint32_t words[FT_TT_OP_GDN_WORDS];
ftg_shape shape;
int rc;
ft_tt_test_step("PHASE CONV column slice");
fill_conv_request(&request);
rc = ft_tt_op_gdn_parameters(&request, words);
shape = ftg_decode(words);
ft_tt_test_check_fmt("an unsliced CONV decodes to the whole stream, first 0",
rc == FT_TT_OK && shape.col_first == 0u &&
shape.col_count == shape.conv_tile_cols && shape.conv_tile_cols > 0u,
"rc %d, cols %u+%u of %u", rc, shape.col_first, shape.col_count,
shape.conv_tile_cols);
ft_tt_test_check_fmt("and owns every head (the head slice is not read in CONV)",
shape.head_first == 0u && shape.head_count == shape.v_heads,
"heads %u+%u of %u", shape.head_first, shape.head_count, shape.v_heads);
request.col_first = 64u;
request.col_count = 64u;
rc = ft_tt_op_gdn_parameters(&request, words);
shape = ftg_decode(words);
ft_tt_test_check_fmt("a slice [64, 128) round-trips through the kernels' decode",
rc == FT_TT_OK && shape.col_first == 64u && shape.col_count == 64u &&
ftg_shape_is_supported(&shape),
"rc %d, cols %u+%u", rc, shape.col_first, shape.col_count);
ft_tt_test_check_fmt("and a sliced CONV still owns every head",
shape.head_first == 0u && shape.head_count == shape.v_heads,
"heads %u+%u", shape.head_first, shape.head_count);
ft_tt_test_check_fmt("the plane stride stays the FULL stream: tap 1 column 64 is tile "
"conv_tile_cols + 64",
ftg_conv_plane_tile(&shape, 1u, 64u) == shape.conv_tile_cols + 64u,
"%u", ftg_conv_plane_tile(&shape, 1u, 64u));
request.col_first = shape.conv_tile_cols - 8u;
request.col_count = 16u;
ft_tt_test_check_fmt("a slice that runs off the stream is REFUSED by the encoder",
ft_tt_op_gdn_parameters(&request, words) != FT_TT_OK, "%s", "");
request.col_first = 8u;
request.col_count = 0u;
ft_tt_test_check_fmt("a first column with no count is REFUSED",
ft_tt_op_gdn_parameters(&request, words) != FT_TT_OK, "%s", "");
fill_conv_request(&request);
request.head_count = 4u;
ft_tt_test_check_fmt("a HEAD slice on CONV is still REFUSED",
ft_tt_op_gdn_parameters(&request, words) != FT_TT_OK, "%s", "");
fill_delta_request(&request);
request.col_count = 32u;
ft_tt_test_check_fmt("a COLUMN slice on DELTA is REFUSED, not read as heads",
ft_tt_op_gdn_parameters(&request, words) != FT_TT_OK, "%s", "");
fill_delta_request(&request);
request.head_first = 8u;
request.head_count = 8u;
rc = ft_tt_op_gdn_parameters(&request, words);
shape = ftg_decode(words);
ft_tt_test_check_fmt("a DELTA head slice still decodes as heads, and its CONV range is "
"the whole (unused) stream",
rc == FT_TT_OK && shape.head_first == 8u && shape.head_count == 8u &&
shape.col_first == 0u,
"heads %u+%u col_first %u", shape.head_first, shape.head_count,
shape.col_first);
}
int main(void)
{
ft_tt_test_step("Gated-Delta-Net op family — off-hardware acceptance (no device is "
"opened; the triad does not exist yet)");
test_wire_constants();
test_compile_time_arg_contract();
test_declared_conv_depth();
test_dst_batch_clamp();
test_writer_block_rungs();
test_writer_page_walk();
test_parameters();
test_refusals();
test_conv_column_slice();
test_cb_placement();
test_cb_placement_serves_the_device();
test_tile_format();
test_delta_input_layout();
test_state_and_output_layout();
test_capacity();
return ft_tt_test_finish();
}