/* 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 #include #include #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 { = };` 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(); }