/* SPDX-License-Identifier: AGPL-3.0-or-later */ /* Copyright (C) 2026 David (Corey) Herrera */ /** * @file ft_tt_capacity_plan.h * @brief How big a GGUF checkpoint's artifact would be at each rung, and WHICH RUNG to * convert it at, from the tensor directory alone. * * --------------------------------------------------------------------------- * WHY THIS IS A LIBRARY AND NOT ARITHMETIC IN A TOOL * --------------------------------------------------------------------------- * The size of a converted tensor is `tiles(rows) * tiles(cols) * tile_bytes(format)`, * aligned up — it depends on the SHAPE and the FORMAT and on nothing about the values. So * the whole fit question is answerable from the GGUF directory in milliseconds, without * touching a tensor byte, and `ft_tt_gguf_plan` has answered it that way since Phase 3. * * It stopped being a tool's business when the product had to answer it: `pantheon serve` * gets a `.gguf` and has to pick a rung with nobody to ask. One implementation of the * arithmetic, used by both, or the tool and the product can disagree about whether a model * fits — and the one that matters would be the unreviewed one. * * --------------------------------------------------------------------------- * THE LADDER IS TWO RUNGS, AND THAT IS A HARDWARE FACT * --------------------------------------------------------------------------- * `pn_tt_program_for()` holds a matmul program for a 1088-byte (Bfp8_b) and a 576-byte * (Bfp4_b) weight tile and for nothing else — a 2048-byte bf16 tile is refused. So the * candidate rungs are FT_TT_LEDGER_BFP8 and FT_TT_LEDGER_BFP4, in that order, and the * PRODUCTION ledger (which puts a Qwen model's attention at bf16) is NOT among them. * Blackhole's own ladder is 8 / 4 / 2 with no 6-bit rung, so there is nothing between them. * * A selection is the FIRST candidate that fits. Highest precision that the card can hold, * never a cheaper one for its own sake. * * @status None * @issues None * @todo None */ #ifndef FT_TT_CAPACITY_PLAN_H #define FT_TT_CAPACITY_PLAN_H #include #include #include "ft_gguf_map.h" #include "ft_tt_precision_ledger.h" #ifdef __cplusplus extern "C" { #endif /** Candidate rungs the card can actually load, best first. */ #define FT_TT_CAPACITY_RUNG_COUNT 2u /** Room for the refusal arithmetic, which is a whole paragraph on purpose. */ #define FT_TT_CAPACITY_REASON_BYTES 512u /** Status codes. Distinct values, because the CALLER's response differs for each. */ enum { FT_TT_CAPACITY_OK = 0, FT_TT_CAPACITY_EINVAL = -3, /** No candidate rung fits the budget. `reason` carries the arithmetic. */ FT_TT_CAPACITY_ENOFIT = -11, /** The converter would refuse this checkpoint whatever the budget: it holds 2-D * tensors whose role the classifier does not recognise. A different problem with a * different fix, so it is a different code and never reported as "too big". */ FT_TT_CAPACITY_EUNCLASSIFIED = -9 }; /** What one rung would cost, measured over the whole tensor directory. */ typedef struct { ft_tt_ledger_mode mode; uint64_t artifact_bytes; /**< payload the converter would write, alignment included */ uint32_t tensor_count; uint32_t matmul_tensor_count; /**< tensors stored MATMUL_NMAJOR — the ones a program serves */ uint32_t matmul_tile_bytes; /**< the single matmul tile size, or 0 when the rung mixes two */ uint32_t unclassified_2d; /**< 2-D tensors the converter would refuse outright */ int loadable; /**< every matmul tile is a size pn_tt_program_for() holds */ } ft_tt_capacity_rung; /** * The run-time footprint a selection has to leave room for, in the caller's terms. * * These are the CALLER's numbers because they are the runtime's, not the checkpoint's: the * KV geometry comes from the model config and the scratch from what the forward allocates. * They are carried into every message this library prints so a fit is never read without * the assumptions that produced it. */ typedef struct { uint64_t card_bytes; /**< total card DRAM the allocator hands out */ uint64_t runtime_state_bytes;/**< scratch, activations, rotary tables, recurrent state */ uint64_t kv_bytes_per_token; /**< 0 means "do not reserve context" */ uint32_t context_tokens; /**< context the run must be able to serve */ } ft_tt_capacity_budget; /** The answer: which rung, why, and the arithmetic behind it. */ typedef struct { int chosen; /**< 1 when a rung was selected, 0 when none fits */ ft_tt_ledger_mode mode; /**< meaningful only when `chosen` */ uint32_t chosen_index; /**< index into `rungs` */ uint32_t rung_count; ft_tt_capacity_rung rungs[FT_TT_CAPACITY_RUNG_COUNT]; uint64_t reserve_bytes; /**< runtime_state + kv_bytes_per_token * context_tokens */ uint64_t spare_bytes; /**< card - artifact - reserve, when chosen */ uint64_t max_context_tokens; /**< what the spare would buy on top of `context_tokens` */ char reason[FT_TT_CAPACITY_REASON_BYTES]; /**< the refusal, when !chosen; else "" */ } ft_tt_capacity_selection; /** * Stored bytes for one tensor at one format, INCLUDING the artifact's alignment pad. * * This is the one implementation of the size arithmetic. 1-D tensors are stored untiled as * fp32 whatever the rung, which is why the format argument does not reach them. * * @param tensor a directory entry; no tensor byte is read * @param format the format the ledger assigned * @return bytes the artifact would spend, or 0 for a format with no tile size * * Thread-safe: yes Reentrant: yes Signal-safe: yes */ uint64_t ft_tt_capacity_tensor_bytes(const ft_gguf_tensor *tensor, ft_tt_tensor_format format); /** * Measure one rung over a whole checkpoint. * * @param map an open GGUF map; only its directory is walked * @param family from ft_tt_ledger_family_from_architecture() * @param mode the rung * @param out zeroed and filled * @return FT_TT_CAPACITY_OK, or FT_TT_CAPACITY_EINVAL * * Thread-safe: yes Reentrant: yes Signal-safe: no (touches the mmap) */ int ft_tt_capacity_rung_measure(const ft_gguf_map *map, ft_tt_model_family family, ft_tt_ledger_mode mode, ft_tt_capacity_rung *out); /** * Choose the rung to convert this checkpoint at. * * Walks FT_TT_LEDGER_BFP8 then FT_TT_LEDGER_BFP4 and takes the first whose artifact plus * the caller's reserve fits `card_bytes`. Every rung is measured either way, so the caller * can print the table it rejected as well as the row it took. * * @param map an open GGUF map * @param family from ft_tt_ledger_family_from_architecture() * @param budget the run-time footprint to leave room for * @param out zeroed and filled; `reason` carries the arithmetic on a refusal * @return FT_TT_CAPACITY_OK when a rung was chosen; * FT_TT_CAPACITY_EUNCLASSIFIED when the converter would refuse the file; * FT_TT_CAPACITY_ENOFIT when no rung fits; * FT_TT_CAPACITY_EINVAL on a bad argument * * Thread-safe: yes Reentrant: yes Signal-safe: no */ int ft_tt_capacity_select(const ft_gguf_map *map, ft_tt_model_family family, const ft_tt_capacity_budget *budget, ft_tt_capacity_selection *out); /** * The one line a serve prints about its own rung choice. * * Names the rung, the artifact size, what it was measured against and what it left — so the * choice is auditable from the log without re-running the planner. * * @param selection a chosen selection; a refusal writes its `reason` instead * @param budget the same budget the selection was made against * @param out destination * @param out_bytes its size; the line is always NUL-terminated * @return `out` * * Thread-safe: yes Reentrant: yes Signal-safe: no */ const char *ft_tt_capacity_selection_line(const ft_tt_capacity_selection *selection, const ft_tt_capacity_budget *budget, char *out, size_t out_bytes); #ifdef __cplusplus } #endif #endif /* FT_TT_CAPACITY_PLAN_H */