230 lines
11 KiB
C
230 lines
11 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/**
|
|
* @file resident_attention_transpose_probe.c
|
|
* @brief THE ONE EXPERIMENT flash-decode attention could not be written without, and it is
|
|
* deliberately the smallest program that can answer it. Despite the name it is a TRISC
|
|
* COMPUTE image, not a data-movement one.
|
|
*
|
|
* ft_tt_op_memory.h PART 1 1.3 records ONE inferred assumption behind the KV cache layout:
|
|
* that `matmul_init(in0, in1, transpose)` -- documented as "perform transpose operation on B",
|
|
* reaching the unpacker's matmul init -- actually transposes the in1 tile on its way to SrcA
|
|
* ON BLACKHOLE. Nothing in this repo had executed it. The layout survives either answer (1.3
|
|
* states the fallback: transpose Q, 4 tiles per head per token, instead of K), but ATTENTION'S
|
|
* INNER LOOP DOES NOT -- the scores come out row-oriented or column-oriented depending on it,
|
|
* and the softmax reduces along a different axis in each case.
|
|
*
|
|
* So this file is resident_matmul_compute.c's loop with ONE change: the transpose flag is read
|
|
* from the descriptor's parameter block (word 14, which the matmul block leaves unused) and
|
|
* passed to the matmul init. That makes the A/B a DESCRIPTOR difference and not a build
|
|
* difference -- the same binary, the same core, the same operands, one word apart -- which is
|
|
* the standard this repo already holds a layout A/B to (docs/tt_bringup_log.md 18.4).
|
|
*
|
|
* THE RUNTIME FLAG IS WHY THIS KERNEL'S ADDR_MOD BLOCK HAS BRANCHES. Slots 1 and 4 have
|
|
* transpose-dependent SrcA increments, so both arms are emitted here where every other matmul
|
|
* folds to one; slot 2 has no transpose branch at all in the full-tile case, which is
|
|
* upstream's control flow and not an omission.
|
|
*
|
|
* It is driven by the UNMODIFIED resident_matmul_{reader,writer}.c, so the only thing in the
|
|
* program that is not already green on silicon is the flag.
|
|
*
|
|
* Compile-time args (positional), identical to resident_matmul_compute.c:
|
|
* 0: cb_in0 index 1: cb_in1 index 2: cb_out index
|
|
* 3: wait mode -- 0 upstream, 1 stoppable (ships), 2 bound-only
|
|
* Runtime args: the ten of resident_queue_protocol.h, identical on all five processors
|
|
*
|
|
* Build:
|
|
* CTARGS=0,1,16,1 CB_TABLE=kernels/cb_tables/matmul_float16b.cbtable \
|
|
* bash foundry/tt/build_tensix_kernel.sh \
|
|
* foundry/tt/kernels/resident_attention_transpose_probe.c /tmp/ttk_probe
|
|
*
|
|
* @status ANSWERED on Tensix 70; C99 CONVERSION MEASURED 2026-08-13 (plan Phase 6.6), with
|
|
* the .cpp retained as the baseline until all three images are byte-identical. Against the
|
|
* C++ build all three Tensix instruction streams are IDENTICAL (trisc0 45/45, trisc1 89/89
|
|
* -- including BOTH arms of the transpose-branched ADDR_MOD block -- trisc2 37/37); trisc1
|
|
* and trisc2 hold the same 0x7c0 / 0xa00 with an identical immediate multiset and differ
|
|
* only in register allocation and commutative operand order, and trisc0 is 0xb30 vs 0xbd0.
|
|
* The original finding follows. THE TRANSPOSE FLAG
|
|
* WORKS ON BLACKHOLE. transpose=1 reproduces A*B^T (PCC 0.999998668, 942/1024 elements
|
|
* bf16-exact) and transpose=0 reproduces A*B (PCC 0.999998688, 932/1024) against a
|
|
* deliberately non-symmetric operand pair, with each arm scoring ~0 (PCC -0.046) against the
|
|
* other candidate. So ft_tt_op_memory.h 1.3's inferred assumption is discharged and its
|
|
* fallback -- transpose Q instead of K -- is not needed.
|
|
* @issues None
|
|
* @todo None
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "ft_tensix_cb_formats.h"
|
|
#include "ft_tensix_compute.h"
|
|
#include "ft_tensix_compute_cb.h"
|
|
#include "ft_tensix_compute_matmul.h"
|
|
#include "ft_tensix_kernel_args.h"
|
|
#ifdef FT_TRISC_MATH
|
|
#include "ft_tensix_compute_math.h"
|
|
#endif
|
|
#ifdef FT_TRISC_UNPACK
|
|
#include "ft_tensix_compute_unpack.h"
|
|
#endif
|
|
#ifdef FT_TRISC_PACK
|
|
#include "ft_tensix_compute_pack.h"
|
|
#endif
|
|
|
|
#include "resident_queue_protocol.h"
|
|
|
|
#define DST_REGISTER 0u
|
|
|
|
/** Word 14 of the matmul parameter block: 0 = plain C = A*B, 1 = C = A*B^T. The matmul family
|
|
* writes 0 into it and reads it nowhere, so borrowing it here cannot change what a matmul
|
|
* descriptor means to any other program. */
|
|
#define PROBE_TRANSPOSE_WORD 14u
|
|
|
|
#ifdef FT_TRISC_MATH
|
|
#define MATMUL_HIGH_FIDELITY (FT_CB_MATH_FIDELITY != 0)
|
|
#define MATMUL_INNER_LOOPS (MATMUL_HIGH_FIDELITY ? (uint32_t)FT_CB_MATH_FIDELITY : 1u)
|
|
#define MATMUL_FIDELITY_INCREMENT (MATMUL_HIGH_FIDELITY ? 1u : 0u)
|
|
#endif
|
|
|
|
/**
|
|
* resident_matmul_compute.c's compute_one_matmul(), with the transpose flag applied.
|
|
*
|
|
* The matmul init is re-run INSIDE the descriptor loop here and not once at boot, because the
|
|
* flag is a per-descriptor word -- which is the whole point of the probe. On the real
|
|
* attention kernel it is re-run once per phase for the same reason (the QK matmul wants the
|
|
* flag and the PV matmul does not).
|
|
*/
|
|
static void compute_one_matmul(ftq_context *ctx, volatile FTQ_L1_PTR uint32_t *parameters,
|
|
uint32_t cb_in0, uint32_t cb_in1, uint32_t cb_out,
|
|
uint32_t wait_mode)
|
|
{
|
|
const uint32_t tile_rows = parameters[kMatmulTileRows];
|
|
const uint32_t tile_inner = parameters[kMatmulTileInner];
|
|
const uint32_t tile_cols = parameters[kMatmulTileCols];
|
|
const uint32_t transpose = parameters[PROBE_TRANSPOSE_WORD];
|
|
uint32_t mt;
|
|
uint32_t nt;
|
|
uint32_t kt;
|
|
|
|
FT_TENSIX_MATH(ft_tensix_math_matmul_init(transpose, MATMUL_FIDELITY_INCREMENT,
|
|
MATMUL_INNER_LOOPS, MATMUL_HIGH_FIDELITY));
|
|
FT_TENSIX_UNPACK(ft_tensix_unpack_ab_matmul_init(
|
|
transpose,
|
|
(uint32_t)ft_cb_unpack_tile_num_faces[cb_in1] * ft_cb_unpack_tile_face_rows[cb_in1] *
|
|
FT_TENSIX_FACE_C_DIM - 1u,
|
|
(uint32_t)ft_cb_unpack_tile_num_faces[cb_in0] * ft_cb_unpack_tile_face_rows[cb_in0] *
|
|
FT_TENSIX_FACE_C_DIM - 1u,
|
|
1u /*kt_dim*/));
|
|
|
|
for (mt = 0; mt < tile_rows; mt++) {
|
|
for (nt = 0; nt < tile_cols; nt++) {
|
|
FT_TENSIX_MATH(ft_tensix_math_wait_for_dest_available());
|
|
|
|
for (kt = 0; kt < tile_inner; kt++) {
|
|
(void)ftq_cb_wait_front_stoppable(ctx, (int32_t)cb_in0, 1, wait_mode);
|
|
(void)ftq_cb_wait_front_stoppable(ctx, (int32_t)cb_in1, 1, wait_mode);
|
|
|
|
/* Issued even while aborting: skipping the unpack stops SrcA/SrcB dvalid and
|
|
* MATH stalls in the BACKEND, where no flag reaches (resident_queue_protocol.h
|
|
* rule 3 -- it wedged a core until reboot). */
|
|
FT_TENSIX_UNPACK(ft_tensix_unpack_ab_matmul(cb_in0, cb_in1, 0, 0, 1 /*ct_dim*/));
|
|
FT_TENSIX_MATH(ft_tensix_math_matmul(DST_REGISTER));
|
|
|
|
FT_TENSIX_UNPACK(ft_tensix_compute_cb_pop_front(cb_in0, 1));
|
|
FT_TENSIX_UNPACK(ft_tensix_compute_cb_pop_front(cb_in1, 1));
|
|
}
|
|
|
|
FT_TENSIX_MATH(ft_tensix_math_dest_section_done()); /* tile_regs_commit() */
|
|
|
|
(void)ftq_cb_reserve_back_stoppable(ctx, (int32_t)cb_out, 1, wait_mode);
|
|
|
|
FT_TENSIX_PACK(ft_tensix_packer_wait_for_math_done()); /* tile_regs_wait() */
|
|
FT_TENSIX_PACK(ft_tensix_pack_tile(DST_REGISTER, cb_out));
|
|
FT_TENSIX_PACK(ft_tensix_pack_dest_section_done(FT_CB_DST_ACCUM_FP32));
|
|
FT_TENSIX_PACK(ft_tensix_compute_cb_push_back(cb_out, 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void kernel_main(void)
|
|
{
|
|
const uint32_t cb_in0 = ft_tensix_compile_time_arg(0);
|
|
const uint32_t cb_in1 = ft_tensix_compile_time_arg(1);
|
|
const uint32_t cb_out = ft_tensix_compile_time_arg(2);
|
|
const uint32_t wait_mode = ft_tensix_compile_time_arg(3);
|
|
|
|
ftq_context ctx;
|
|
bool running = true;
|
|
|
|
ftq_context_init(&ctx);
|
|
ftq_status_set(&ctx, kStateBoot);
|
|
|
|
if (!ftq_check_control(&ctx)) {
|
|
ftq_status_set(&ctx, kStateExit);
|
|
return;
|
|
}
|
|
|
|
/* compute_kernel_hw_startup<SrcOrder::Reverse>(cb_in0, cb_in1, cb_out): in0 -> SrcB,
|
|
* in1 -> SrcA, which is also why the transpose flag lands on in1. */
|
|
FT_TENSIX_UNPACK(ft_tensix_unpack_hw_configure_ab(
|
|
ft_cb_unpack_src_format[cb_in1], ft_cb_unpack_src_format[cb_in0], ft_cb_unpack_dst_format[cb_in1],
|
|
ft_cb_unpack_dst_format[cb_in0], ft_cb_unpack_tile_face_rows[cb_in1],
|
|
ft_cb_unpack_tile_face_rows[cb_in0], ft_cb_unpack_tile_num_faces[cb_in1],
|
|
ft_cb_unpack_tile_num_faces[cb_in0], ft_tensix_cb(cb_in1)->fifo_page_size,
|
|
ft_tensix_cb(cb_in0)->fifo_page_size));
|
|
|
|
FT_TENSIX_MATH(ft_tensix_math_pack_sync_init());
|
|
FT_TENSIX_MATH(ft_tensix_math_hw_configure(ft_cb_unpack_dst_format[cb_in1],
|
|
ft_cb_unpack_dst_format[cb_in0], FT_CB_DST_ACCUM_FP32));
|
|
|
|
FT_TENSIX_PACK(ft_tensix_pack_hw_configure(
|
|
ft_cb_pack_src_format[cb_out], ft_cb_pack_dst_format[cb_out], ft_tensix_cb(cb_out)->fifo_page_size,
|
|
ft_cb_pack_tile_num_faces[cb_out], ft_cb_pack_partial_face[cb_out], 0 /*relu_config*/,
|
|
FT_CB_DST_ACCUM_FP32));
|
|
FT_TENSIX_PACK(ft_tensix_pack_init(ft_cb_pack_src_format[cb_out], ft_cb_pack_tile_face_rows[cb_out],
|
|
ft_cb_pack_tile_num_faces[cb_out], 1 /*num_tiles*/));
|
|
FT_TENSIX_PACK(ft_tensix_pack_dest_init());
|
|
|
|
FT_TENSIX_MATH(ft_tensix_math_matmul_init(0 /*transpose*/, MATMUL_FIDELITY_INCREMENT,
|
|
MATMUL_INNER_LOOPS, MATMUL_HIGH_FIDELITY));
|
|
FT_TENSIX_UNPACK(ft_tensix_unpack_ab_matmul_init(
|
|
0 /*transpose*/,
|
|
(uint32_t)ft_cb_unpack_tile_num_faces[cb_in1] * ft_cb_unpack_tile_face_rows[cb_in1] *
|
|
FT_TENSIX_FACE_C_DIM - 1u,
|
|
(uint32_t)ft_cb_unpack_tile_num_faces[cb_in0] * ft_cb_unpack_tile_face_rows[cb_in0] *
|
|
FT_TENSIX_FACE_C_DIM - 1u,
|
|
1u /*kt_dim*/));
|
|
|
|
ctx.cursor = ctx.control[kFieldCompletedCount];
|
|
|
|
while (running) {
|
|
volatile FTQ_L1_PTR uint32_t *descriptor;
|
|
uint32_t opcode;
|
|
|
|
if (!ftq_wait_for_work(&ctx, kFieldHead))
|
|
break;
|
|
|
|
ctx.aborting = false;
|
|
ftq_status_set(&ctx, kStateExec);
|
|
|
|
descriptor = ftq_descriptor(&ctx);
|
|
opcode = descriptor[kDescOpcode];
|
|
|
|
ftq_status_opcode(&ctx, opcode);
|
|
if (opcode == kOpMatmul) {
|
|
ftq_status_set(&ctx, kStateCbWaitFront);
|
|
compute_one_matmul(&ctx, (volatile FTQ_L1_PTR uint32_t *)(uintptr_t)descriptor[kDescA0],
|
|
cb_in0, cb_in1, cb_out, wait_mode);
|
|
ftq_status_set(&ctx, kStateExec);
|
|
if (ctx.aborting)
|
|
running = false;
|
|
} else if (opcode == kOpStop) {
|
|
running = false;
|
|
}
|
|
|
|
if (!ctx.aborting)
|
|
ctx.cursor++;
|
|
}
|
|
|
|
ftq_status_set(&ctx, kStateExit);
|
|
}
|