Foundry/tt/ft_tt_fttw.h

140 lines
6.9 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_fttw.h
* @brief The FTTW loader: mmap a converted weight artifact, find a tensor, put it on the
* card. This is the Phase 3 follow-up "the artifact has no loader yet".
*
* ---------------------------------------------------------------------------
* WHY A TENSOR UPLOAD IS A BYTE COPY
* ---------------------------------------------------------------------------
* The converter already did the work: a 2-D tensor is `ceil(rows/32) x ceil(cols/32)`
* 32x32 tiles in row-major TILE order, face-ordered inside each tile, in the on-card
* format, each payload 64-byte aligned — which is the NoC's DRAM alignment
* (docs/tt_bringup_log.md §12.7). A Bfp8_b tile is the same 1088 bytes the FPU consumes
* and the same 1088 bytes a CB page holds. So the loader neither converts nor re-lays-out
* anything; it streams bytes from an mmap into DRAM. That is Route B's payoff on the host
* side, and it is why this file is short.
*
* The one tensor class that is NOT a byte copy is the 1-D one. Norm weights are stored
* untiled fp32 (they are 6 KiB each and block-floating a normalization scale buys
* nothing), while the RMS-norm kernel wants them as full 32x32 tiles with the row
* replicated 32 times — so ft_tt_fttw_upload_broadcast_rows() does that expansion here,
* in the loader, rather than inflating the artifact 32x or spending a broadcast op in the
* kernel every row block. That settles the open Phase 3 follow-up on 1-D layout.
*
* ---------------------------------------------------------------------------
* ORIENTATION
* ---------------------------------------------------------------------------
* The artifact stores GGUF's order: rows = dims[1] = out_features, cols = dims[0] =
* in_features. The matmul wants its weight operand as K x N = in x out, i.e. transposed.
* THE LOADER DOES NOT TRANSPOSE, and cannot usefully: a Bfp8_b tile's shared exponent
* covers 16 elements along a row, so transposing a packed tile means unpacking and
* repacking it, which is conversion work in the wrong place. The transpose belongs in the
* converter. See ft_tt_op_matmul.h for the decision and its reasons.
*
* @status Reads the production MiniCPM5-1B artifact and uploads real tensors to DRAM.
* @issues None
* @todo None
*/
#ifndef FT_TT_FTTW_H
#define FT_TT_FTTW_H
#include <stddef.h>
#include <stdint.h>
#include "convert/ft_tt_weight_convert.h"
#include "ft_tt_launch.h"
/** An mmapped artifact. Caller-owned storage, like everything else in this backend. */
typedef struct ft_tt_fttw {
const unsigned char *base; /**< the mapping */
size_t bytes;
ft_tt_artifact_header header;
const ft_tt_artifact_entry *directory; /**< into the mapping; little-endian on disk */
int fd;
} ft_tt_fttw;
/**
* Map an artifact and validate its header and directory bounds.
*
* Validation is not a formality: the directory is written LAST by a streaming converter,
* so a truncated artifact has a plausible header and a directory that is not there.
*
* @return FT_TT_OK; FT_TT_EIO if it cannot be opened or mapped; FT_TT_EINVAL for a bad
* magic, an unsupported version, or a directory that does not fit the file
*
* Thread-safe: no Reentrant: no Signal-safe: no
*/
int ft_tt_fttw_open(const char *path, ft_tt_fttw *out_artifact);
/** Unmap and close. Safe on NULL and on a half-opened artifact. */
void ft_tt_fttw_close(ft_tt_fttw *artifact);
/** The directory entry for `name`, or NULL. Linear scan; 219 entries. */
const ft_tt_artifact_entry *ft_tt_fttw_find(const ft_tt_fttw *artifact, const char *name);
/** Bytes of one 32x32 tile in `format` (ft_tt_tensor_format), or 0 if unmodelled. */
uint32_t ft_tt_fttw_tile_bytes(uint32_t format);
/**
* Copy a tensor's payload to `address` on the NoC endpoint `(x, y)` — normally a DRAM
* bank from ft_tt_coord_dram_bank(), never a guessed coordinate (spec §7.3).
*
* @param chunk_bytes how much to move per call into the accessor; 0 picks 64 KiB. The
* accessor re-aims its window as the address moves, so this bounds
* the host-side staging, not the transfer size
* @return FT_TT_OK, or the accessor's failure
*/
int ft_tt_fttw_upload(const ft_tt_fttw *artifact, const ft_tt_artifact_entry *entry,
const ft_tt_noc_access *noc, uint16_t x, uint16_t y, uint32_t address,
uint32_t chunk_bytes);
/**
* Copy PART of a tensor's payload — the byte run `[source_offset, source_offset+source_bytes)`
* — to `address` on `(x, y)`.
*
* This is what makes a load-time BANK SHARD a copy rather than a re-tiling: an N-major
* weight's column slice is an already-contiguous run of the canonical bytes
* (ft_tt_dram_shard.h), so placing member c's slice in its own bank is exactly one call of
* this. Nothing here knows about members or tiles; the caller does that arithmetic and this
* refuses a run that leaves the tensor.
*
* @param source_offset byte offset into THIS TENSOR's payload
* @param source_bytes how many; 0 is refused rather than treated as "the rest"
* @return FT_TT_OK; FT_TT_EINVAL for a NULL argument or a run outside the
* tensor; otherwise the accessor's failure
*
* Thread-safe: yes Reentrant: yes Signal-safe: no (logs on failure)
*/
int ft_tt_fttw_upload_range(const ft_tt_fttw *artifact, const ft_tt_artifact_entry *entry,
uint64_t source_offset, uint64_t source_bytes,
const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
uint32_t address, uint32_t chunk_bytes);
/**
* Upload a 1-D fp32 tensor as `ceil(n/32)` 32x32 tiles with the row replicated down every
* row of every tile, converted to bf16 (Float16_b) — the layout a norm weight needs to be
* a plain `mul_tiles` operand rather than a broadcast op.
*
* The expansion is 32x in bytes and that is the point: 1536 fp32 values become 48 tiles
* of 2048 B, 96 KiB, which is nothing against L1's 1.5 MiB and saves a per-tile broadcast
* reconfiguration in the innermost loop of every norm.
*
* Tiles are face-ordered, via ft_tt_tilize_matrix_strip(), so the same code lays them out
* as the converter uses for everything else.
*
* @param scratch caller-owned, at least 32*32*2 bytes per tile it will build; the
* loader builds one tile at a time, so 2048 bytes is enough
* @param scratch_bytes its size
* @return FT_TT_OK; FT_TT_EINVAL if the entry is not 1-D fp32 or the
* scratch is short
*/
int ft_tt_fttw_upload_broadcast_rows(const ft_tt_fttw *artifact,
const ft_tt_artifact_entry *entry,
const ft_tt_noc_access *noc, uint16_t x, uint16_t y,
uint32_t address, void *scratch, size_t scratch_bytes);
#endif /* FT_TT_FTTW_H */