Foundry/tt/ft_tt_op_rope.c

304 lines
12 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_op_rope.c
* @brief Implementation of the ROPE host driver. See ft_tt_op_rope.h, which carries the
* face-space argument, the table-placement decision and the row-phase contract this file
* assumes.
*
* Everything except ft_tt_op_rope_build_table() is PURE — no device access, no allocation.
* The enqueue itself is ft_tt_queue_write_op_parameters() plus ft_tt_queue_enqueue(), which
* the caller does, because a resident op has no launch of its own to own.
*
* @status None
* @issues None
* @todo None
*/
#include "ft_tt_op_rope.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "convert/ft_float_convert.h"
#include "ft_tt_device.h"
#include "ft_tt_op_l1_layout.h"
#include "ft_tt_tilize.h"
/** Pages per streamed input CB, as a multiple of the block depth. TWO blocks: the reader
* fills one while the compute thread consumes the other. */
#define ROPE_CB_BLOCKS 2u
/** Pages in the CBs produced and consumed a tile at a time. */
#define ROPE_CB_PAGES_SMALL 2u
/** Positions in one table row block — a tile is 32 rows and a row is a position. */
#define ROPE_POSITIONS_PER_BLOCK FT_TT_OP_TILE_DIM
uint32_t ft_tt_op_rope_rotary_width(uint32_t head_dim, uint32_t rope_dim)
{
return (rope_dim > 0u && rope_dim <= head_dim) ? rope_dim : head_dim;
}
bool ft_tt_op_rope_shape_is_supported(uint32_t head_tiles, uint32_t rot_tiles)
{
if (head_tiles == 0u || rot_tiles == 0u || rot_tiles > head_tiles)
return false;
/* rot_tiles even <=> half = rot/2 is a whole number of tiles <=> the partner element is
* the same intra-tile column of a different tile. This one clause IS the face-space
* argument, and the kernel's ftr_shape_is_supported() is the same three lines. */
return (rot_tiles % 2u) == 0u;
}
/** An operand is usable when it names a tile size. The COORDINATE is not checked here: it
* comes from ft_tt_coord and a caller that invented one has a bigger problem than a range
* check (spec §7.3). */
static bool operand_is_set(const ft_tt_op_operand *operand)
{
return operand->tile_bytes != 0u;
}
/** The tile counts a request implies, or false if the request is not expressible. */
static bool resolve_shape(const ft_tt_op_rope_request *request, uint32_t *out_head_tiles,
uint32_t *out_rot_tiles)
{
uint32_t rot;
if (request->row_blocks == 0u || request->heads == 0u || request->head_dim == 0u)
return false;
if ((request->head_dim % FT_TT_OP_TILE_DIM) != 0u)
return false;
rot = ft_tt_op_rope_rotary_width(request->head_dim, request->rope_dim);
if ((rot % FT_TT_OP_TILE_DIM) != 0u)
return false;
*out_head_tiles = request->head_dim / FT_TT_OP_TILE_DIM;
*out_rot_tiles = rot / FT_TT_OP_TILE_DIM;
return ft_tt_op_rope_shape_is_supported(*out_head_tiles, *out_rot_tiles);
}
/** The depth the reader will use: it must divide BOTH runs it blocks over, because cb_in
* advances over head_tiles while the three rotated streams advance over rot_tiles, and a
* block that does not divide its run straddles the CB wrap. */
static uint32_t effective_depth(const ft_tt_op_rope_request *request, uint32_t head_tiles,
uint32_t rot_tiles)
{
uint32_t depth = ft_tt_op_block_depth(head_tiles, request->prefetch_tiles);
return ft_tt_op_block_depth(rot_tiles, depth);
}
int ft_tt_op_rope_parameters(const ft_tt_op_rope_request *request,
uint32_t out_words[FT_TT_OP_ROPE_WORDS])
{
uint32_t head_tiles = 0u;
uint32_t rot_tiles = 0u;
if (!request || !out_words)
return FT_TT_EINVAL;
if (!resolve_shape(request, &head_tiles, &rot_tiles))
return FT_TT_EINVAL;
if (!operand_is_set(&request->input) || !operand_is_set(&request->table) ||
!operand_is_set(&request->output))
return FT_TT_EINVAL;
/* THE ROW-PHASE CONTRACT. A caller with a decode token at position p passes p and puts
* the token in tile row p % 32; the row block is derived below. A first_position that is
* not a multiple of 32 would mean row 0 of the activation is not row 0 of a table block,
* and there is no field that could express the difference — so it is a refusal rather
* than a rounding. */
if ((request->first_position % ROPE_POSITIONS_PER_BLOCK) != 0u &&
request->row_blocks != 1u)
return FT_TT_EINVAL;
memset(out_words, 0, FT_TT_OP_ROPE_WORDS * sizeof(out_words[0]));
out_words[FT_TT_OP_ROPE_TILE_COUNT] = request->row_blocks * request->heads * head_tiles;
out_words[FT_TT_OP_ROPE_HEAD_TILES] = head_tiles;
out_words[FT_TT_OP_ROPE_ROT_TILES] = rot_tiles;
out_words[FT_TT_OP_ROPE_HEADS] = request->heads;
out_words[FT_TT_OP_ROPE_DEPTH] = effective_depth(request, head_tiles, rot_tiles);
out_words[FT_TT_OP_ROPE_A_X] = request->input.x;
out_words[FT_TT_OP_ROPE_A_Y] = request->input.y;
out_words[FT_TT_OP_ROPE_A_BASE] = request->input.address;
out_words[FT_TT_OP_ROPE_TABLE_X] = request->table.x;
out_words[FT_TT_OP_ROPE_TABLE_Y] = request->table.y;
out_words[FT_TT_OP_ROPE_TABLE_BASE] =
request->table.address + ft_tt_op_rope_table_offset(request->first_position, rot_tiles,
request->table.tile_bytes);
out_words[FT_TT_OP_ROPE_C_X] = request->output.x;
out_words[FT_TT_OP_ROPE_C_Y] = request->output.y;
out_words[FT_TT_OP_ROPE_C_BASE] = request->output.address;
return FT_TT_OK;
}
int ft_tt_op_rope_place_cbs(const ft_tt_op_rope_request *request,
ft_tt_launch_cb_entry out_entries[FT_TT_OP_ROPE_CB_COUNT],
void *buffer, size_t buffer_bytes, uint64_t *out_mask)
{
ft_tt_op_cb_request requests[FT_TT_OP_ROPE_CB_COUNT];
uint32_t head_tiles = 0u;
uint32_t rot_tiles = 0u;
uint32_t depth;
if (!request || !out_entries || !buffer || !out_mask)
return FT_TT_EINVAL;
if (!resolve_shape(request, &head_tiles, &rot_tiles))
return FT_TT_EINVAL;
if (!operand_is_set(&request->input) || !operand_is_set(&request->table) ||
!operand_is_set(&request->output))
return FT_TT_EINVAL;
depth = effective_depth(request, head_tiles, rot_tiles);
requests[0].index = FT_TT_OP_ROPE_CB_IN;
requests[0].page_bytes = request->input.tile_bytes;
requests[0].page_count = depth * ROPE_CB_BLOCKS;
requests[1].index = FT_TT_OP_ROPE_CB_ROT;
requests[1].page_bytes = request->input.tile_bytes;
requests[1].page_count = depth * ROPE_CB_BLOCKS;
requests[2].index = FT_TT_OP_ROPE_CB_COS;
requests[2].page_bytes = request->table.tile_bytes;
requests[2].page_count = depth * ROPE_CB_BLOCKS;
requests[3].index = FT_TT_OP_ROPE_CB_SIN;
requests[3].page_bytes = request->table.tile_bytes;
requests[3].page_count = depth * ROPE_CB_BLOCKS;
requests[4].index = FT_TT_OP_ROPE_CB_PROD_COS;
requests[4].page_bytes = request->output.tile_bytes;
requests[4].page_count = ROPE_CB_PAGES_SMALL;
requests[5].index = FT_TT_OP_ROPE_CB_PROD_SIN;
requests[5].page_bytes = request->output.tile_bytes;
requests[5].page_count = ROPE_CB_PAGES_SMALL;
requests[6].index = FT_TT_OP_ROPE_CB_OUT;
requests[6].page_bytes = request->output.tile_bytes;
requests[6].page_count = ROPE_CB_PAGES_SMALL;
return ft_tt_op_cb_place(requests, FT_TT_OP_ROPE_CB_COUNT, out_entries, buffer,
buffer_bytes, out_mask);
}
/* ================================================================
* The table
* ================================================================ */
uint32_t ft_tt_op_rope_table_row_block_bytes(uint32_t rot_tiles, uint32_t tile_bytes)
{
return 2u * rot_tiles * tile_bytes;
}
size_t ft_tt_op_rope_table_bytes(uint32_t position_count, uint32_t rot_tiles,
uint32_t tile_bytes)
{
const size_t blocks =
((size_t)position_count + ROPE_POSITIONS_PER_BLOCK - 1u) / ROPE_POSITIONS_PER_BLOCK;
return blocks * ft_tt_op_rope_table_row_block_bytes(rot_tiles, tile_bytes);
}
uint32_t ft_tt_op_rope_table_offset(uint32_t position, uint32_t rot_tiles,
uint32_t tile_bytes)
{
return (position / ROPE_POSITIONS_PER_BLOCK) *
ft_tt_op_rope_table_row_block_bytes(rot_tiles, tile_bytes);
}
double ft_tt_op_rope_angle(uint32_t position, uint32_t j, uint32_t rot, double theta_base)
{
const uint32_t half = rot / 2u;
double frequency;
if (half == 0u)
return 0.0;
/* pow() in double, and no explicit range reduction: libm's cos/sin reduce correctly for
* any |x| < 2^63 and the largest angle here is 131071 radians. That exactness is the
* whole reason this is not an SFPU pass (ft_tt_op_rope.h, THE TABLE). */
frequency = pow(theta_base, -2.0 * (double)(j % half) / (double)rot);
return (double)position * frequency;
}
/** One row block: 32 positions x `rot` columns of cos, then the same of SIGNED sin. */
static void fill_table_block(uint16_t *cos_plain, uint16_t *sin_plain, uint32_t position,
uint32_t rot, double theta_base)
{
const uint32_t half = rot / 2u;
uint32_t row;
for (row = 0; row < ROPE_POSITIONS_PER_BLOCK; row++) {
uint32_t column;
for (column = 0; column < rot; column++) {
const double angle = ft_tt_op_rope_angle(position + row, column, rot, theta_base);
const double sine = sin(angle);
/* THE SIGN LIVES HERE. Below the half the output is x*cos - partner*sin, above it
* x*cos + partner*sin, so a signed table makes both the same instruction. */
const double signed_sine = (column < half) ? -sine : sine;
cos_plain[row * rot + column] = ft_bf16_from_float((float)cos(angle));
sin_plain[row * rot + column] = ft_bf16_from_float((float)signed_sine);
}
}
}
int ft_tt_op_rope_build_table(uint32_t first_position, uint32_t position_count,
uint32_t head_dim, uint32_t rope_dim, double theta_base,
void *out, size_t out_bytes)
{
unsigned char *cursor = out;
uint16_t *cos_plain = NULL;
uint16_t *sin_plain = NULL;
uint32_t rot;
uint32_t rot_tiles;
uint32_t blocks;
uint32_t block;
size_t strip_bytes;
int rc = FT_TT_EINVAL;
if (!out || position_count == 0u || head_dim == 0u)
return FT_TT_EINVAL;
if ((first_position % ROPE_POSITIONS_PER_BLOCK) != 0u ||
(position_count % ROPE_POSITIONS_PER_BLOCK) != 0u)
return FT_TT_EINVAL;
if ((head_dim % FT_TT_OP_TILE_DIM) != 0u)
return FT_TT_EINVAL;
rot = ft_tt_op_rope_rotary_width(head_dim, rope_dim);
if ((rot % FT_TT_OP_TILE_DIM) != 0u)
return FT_TT_EINVAL;
rot_tiles = rot / FT_TT_OP_TILE_DIM;
if (!ft_tt_op_rope_shape_is_supported(head_dim / FT_TT_OP_TILE_DIM, rot_tiles))
return FT_TT_EINVAL;
if (out_bytes <
ft_tt_op_rope_table_bytes(position_count, rot_tiles, FT_TT_OP_TILE_BYTES_BF16))
return FT_TT_EINVAL;
cos_plain = malloc((size_t)ROPE_POSITIONS_PER_BLOCK * rot * sizeof(*cos_plain));
sin_plain = malloc((size_t)ROPE_POSITIONS_PER_BLOCK * rot * sizeof(*sin_plain));
if (!cos_plain || !sin_plain) {
rc = FT_TT_ENOMEM;
goto cleanup;
}
blocks = position_count / ROPE_POSITIONS_PER_BLOCK;
strip_bytes = (size_t)rot_tiles * FT_TT_OP_TILE_BYTES_BF16;
for (block = 0; block < blocks; block++) {
const uint32_t position = first_position + block * ROPE_POSITIONS_PER_BLOCK;
fill_table_block(cos_plain, sin_plain, position, rot, theta_base);
/* ft_tt_tilize_strip(), i.e. the SAME face map the activations go through. A table
* built flat row-major would hand three quarters of every tile the angle of a
* different (position, frequency) pair — a wrong answer with nothing to cancel it. */
if (ft_tt_tilize_strip(cos_plain, sizeof(*cos_plain), ROPE_POSITIONS_PER_BLOCK, rot,
(size_t)rot * sizeof(*cos_plain), cursor) != 0)
goto cleanup;
if (ft_tt_tilize_strip(sin_plain, sizeof(*sin_plain), ROPE_POSITIONS_PER_BLOCK, rot,
(size_t)rot * sizeof(*sin_plain), cursor + strip_bytes) != 0)
goto cleanup;
cursor += 2u * strip_bytes;
}
rc = FT_TT_OK;
cleanup:
free(cos_plain);
free(sin_plain);
return rc;
}