Foundry/tt/convert/ft_tt_artifact_cache.c

501 lines
20 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file ft_tt_artifact_cache.c
* @brief Implementation of the convert-on-load artifact cache.
*
* @status None
* @issues None
* @todo None
*/
#define _DEFAULT_SOURCE
#include "ft_tt_artifact_cache.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
/* ------------------------------------------------------------------------- */
/* Paths */
/* ------------------------------------------------------------------------- */
/** The suffix that names a rung in an artifact filename. */
static const char *cache_rung_suffix(ft_tt_ledger_mode mode)
{
const char *name = ft_tt_ledger_mode_name(mode);
return name ? name : "unknown";
}
/** The last path separator, or NULL. Only '/' — this tree is POSIX everywhere. */
static const char *cache_basename(const char *path)
{
const char *slash = strrchr(path, '/');
return slash ? slash + 1 : path;
}
int ft_tt_artifact_cache_path(const char *gguf_path, const char *cache_dir,
ft_tt_ledger_mode mode, char *out, size_t out_bytes)
{
const char *stem_start;
size_t stem_length;
int written;
if (!gguf_path || !out || out_bytes == 0u)
return FT_TT_ARTIFACT_CACHE_EINVAL;
stem_start = (cache_dir && *cache_dir) ? cache_basename(gguf_path) : gguf_path;
stem_length = strlen(stem_start);
if (stem_length > 5u && strcmp(stem_start + stem_length - 5u, ".gguf") == 0)
stem_length -= 5u;
if (cache_dir && *cache_dir)
written = snprintf(out, out_bytes, "%s/%.*s.%s.fttw", cache_dir, (int)stem_length,
stem_start, cache_rung_suffix(mode));
else
written = snprintf(out, out_bytes, "%.*s.%s.fttw", (int)stem_length, stem_start,
cache_rung_suffix(mode));
if (written < 0 || (size_t)written >= out_bytes)
return FT_TT_ARTIFACT_CACHE_EINVAL;
return FT_TT_ARTIFACT_CACHE_OK;
}
/** Total bytes of the checkpoint, every part of a split set included. */
static uint64_t cache_source_bytes(const ft_gguf_map *map)
{
uint32_t parts = ft_gguf_map_part_count(map);
uint64_t total = 0;
uint32_t i;
for (i = 0; i < parts; i++) {
const char *path = ft_gguf_map_part_path(map, i);
struct stat info;
if (path && stat(path, &info) == 0)
total += (uint64_t)info.st_size;
}
return total;
}
/* ------------------------------------------------------------------------- */
/* Rotary pairing */
/* ------------------------------------------------------------------------- */
/** Does the checkpoint declare all four keys the split-half permutation needs? */
static int cache_declares_rope_geometry(const ft_gguf_map *map, const char *architecture)
{
static const char *const SUFFIXES[] = {"attention.head_count", "attention.head_count_kv",
"attention.key_length", "rope.dimension_count"};
size_t i;
for (i = 0; i < sizeof(SUFFIXES) / sizeof(SUFFIXES[0]); i++) {
char key[128];
uint64_t value;
if (snprintf(key, sizeof(key), "%s.%s", architecture, SUFFIXES[i]) >= (int)sizeof(key))
return 0;
if (ft_gguf_map_metadata_u64(map, key, &value) != FT_GGUF_OK)
return 0;
}
return 1;
}
ft_tt_rope_pairing ft_tt_artifact_cache_rope_pairing(const ft_gguf_map *map,
ft_tt_model_family family,
const char *architecture,
const char **out_reason)
{
const char *reason;
ft_tt_rope_pairing pairing;
if (family == FT_TT_FAMILY_QWEN) {
pairing = FT_TT_ROPE_PAIRING_SOURCE;
reason = "this family already stores q/k in split-half order";
} else if (map && architecture && *architecture &&
cache_declares_rope_geometry(map, architecture)) {
pairing = FT_TT_ROPE_PAIRING_SPLIT_HALF;
reason = "a llama-family checkpoint stores q/k interleaved, which costs 22% "
"perplexity on chat-templated prompts";
} else {
/* Announced, not silent. The alternative is refusing a checkpoint for lacking
* metadata the user has never heard of, which is exactly the "your file is the
* wrong format" answer this whole phase exists to remove. */
pairing = FT_TT_ROPE_PAIRING_SOURCE;
reason = "the checkpoint does not declare its head geometry, so the split-half "
"permutation is undefined for it";
}
if (out_reason)
*out_reason = reason;
return pairing;
}
/* ------------------------------------------------------------------------- */
/* The built-in progress report */
/* ------------------------------------------------------------------------- */
/**
* A first load of a large model is a PROGRESS BAR, NOT A QUESTION.
*
* On a terminal that is a redrawn line; in a log it is one line per decile, because a log
* full of carriage returns is a log nobody reads. Both carry the same three numbers, so a
* run recorded from a pipe is as auditable as one watched live.
*/
typedef struct {
uint64_t total_source_bytes;
uint64_t done_source_bytes;
uint32_t tensors_done;
uint32_t tensors_total;
int last_decile;
int interactive;
double started_at;
double last_drawn_at;
const char *model_label;
} cache_progress_state;
static double cache_monotonic_seconds(void)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0)
return 0.0;
return (double)now.tv_sec + (double)now.tv_nsec * 1e-9;
}
static void cache_progress_draw(cache_progress_state *state, int final)
{
double elapsed = cache_monotonic_seconds() - state->started_at;
double fraction = state->total_source_bytes > 0u
? (double)state->done_source_bytes / (double)state->total_source_bytes
: 0.0;
double rate = elapsed > 0.01 ? (double)state->done_source_bytes / elapsed : 0.0;
double remaining = rate > 1.0 ? ((double)state->total_source_bytes -
(double)state->done_source_bytes) / rate
: 0.0;
fprintf(stderr, "%s%s: preparing for the card %3d%% %.2f/%.2f GiB %.0f MiB/s eta %.0fs%s",
state->interactive ? "\r" : "", state->model_label, (int)(fraction * 100.0 + 0.5),
(double)state->done_source_bytes / (1024.0 * 1024.0 * 1024.0),
(double)state->total_source_bytes / (1024.0 * 1024.0 * 1024.0),
rate / (1024.0 * 1024.0), remaining,
(state->interactive && !final) ? "" : "\n");
fflush(stderr);
}
static int cache_progress_report(const ft_tt_convert_tensor_report *report, void *user)
{
cache_progress_state *state = (cache_progress_state *)user;
double now;
if (!state || !report)
return 0;
state->done_source_bytes += report->source_bytes;
state->tensors_done++;
if (state->interactive) {
/* Redrawing per tensor is 300+ writes for a small model and thousands for a large
* one; ten a second is smooth and costs nothing. */
now = cache_monotonic_seconds();
if (now - state->last_drawn_at < 0.1 && state->tensors_done != state->tensors_total)
return 0;
state->last_drawn_at = now;
cache_progress_draw(state, state->tensors_done == state->tensors_total);
return 0;
}
{
int decile = state->total_source_bytes > 0u
? (int)((state->done_source_bytes * 10u) / state->total_source_bytes)
: 10;
if (decile > state->last_decile) {
state->last_decile = decile;
cache_progress_draw(state, decile >= 10);
}
}
return 0;
}
/* ------------------------------------------------------------------------- */
/* The cache */
/* ------------------------------------------------------------------------- */
/** Fill the record a fresh conversion of THIS checkpoint at THESE settings would carry. */
static void cache_expected_provenance(ft_tt_artifact_provenance *want, const ft_gguf_map *map,
uint64_t source_bytes, ft_tt_ledger_mode mode,
ft_tt_rope_pairing rope_pairing)
{
memset(want, 0, sizeof(*want));
want->source_bytes = source_bytes;
want->source_digest = ft_tt_artifact_source_digest(map);
want->provenance_version = FT_TT_PROVENANCE_VERSION;
want->artifact_version = FT_TT_ARTIFACT_VERSION;
want->ledger_mode = (uint32_t)mode;
want->rope_pairing = (uint32_t)rope_pairing;
want->artifact_flags =
(rope_pairing == FT_TT_ROPE_PAIRING_SPLIT_HALF ||
rope_pairing == FT_TT_ROPE_PAIRING_SPLIT_HALF_REVERSED)
? FT_TT_ARTIFACT_FLAG_ROPE_SPLIT_HALF
: 0u;
if (rope_pairing == FT_TT_ROPE_PAIRING_SPLIT_HALF_REVERSED ||
rope_pairing == FT_TT_ROPE_PAIRING_INTERLEAVED_REVERSED)
want->artifact_flags |= FT_TT_ARTIFACT_FLAG_ROPE_PAIRS_REVERSED;
want->tensor_count = ft_gguf_map_tensor_count(map);
want->behaviour_stamp = ft_tt_artifact_behaviour_stamp();
want->magic = FT_TT_PROVENANCE_MAGIC;
}
/**
* Is the artifact at `path` the one this checkpoint needs?
*
* Every answer is derived from the artifact's OWN BYTES — its trailer, its FTTW header and
* its length. Nothing here trusts the filename, the mtime, or the fact that a file exists.
*
* @return 1 when it is usable; 0 with `note` naming what disagreed
*/
static int cache_artifact_is_current(const char *path, const ft_tt_artifact_provenance *want,
ft_tt_artifact_provenance *got, char *note,
size_t note_bytes)
{
int rc = ft_tt_artifact_provenance_read(path, got);
if (rc == FT_TT_PROVENANCE_EIO) {
snprintf(note, note_bytes, "no cached artifact");
return 0;
}
if (rc == FT_TT_PROVENANCE_EABSENT) {
snprintf(note, note_bytes,
"the cached artifact carries no provenance trailer — it predates the cache "
"or was produced by hand");
return 0;
}
if (rc != FT_TT_PROVENANCE_OK) {
snprintf(note, note_bytes, "the cached artifact's provenance trailer is malformed");
return 0;
}
if (ft_tt_artifact_provenance_verify_file(path, got, note, note_bytes) != FT_TT_PROVENANCE_OK)
return 0;
if (ft_tt_artifact_provenance_compare(want, got, note, note_bytes) != FT_TT_PROVENANCE_OK)
return 0;
note[0] = '\0';
return 1;
}
/** Convert into a temp file beside the target and rename it into place. */
static int cache_produce(const char *gguf_path, const char *artifact_path,
const ft_tt_artifact_cache_request *request, ft_tt_ledger_mode mode,
ft_tt_rope_pairing rope_pairing, const ft_gguf_map *map,
uint64_t source_bytes, char *reason, size_t reason_bytes)
{
char temp_path[FT_TT_ARTIFACT_CACHE_PATH_BYTES];
cache_progress_state progress_state;
ft_tt_convert_options options;
int written;
int rc;
written = snprintf(temp_path, sizeof(temp_path), "%s.tmp.%ld", artifact_path,
(long)getpid());
if (written < 0 || (size_t)written >= sizeof(temp_path)) {
snprintf(reason, reason_bytes, "the artifact path is too long to write beside");
return FT_TT_ARTIFACT_CACHE_EINVAL;
}
memset(&options, 0, sizeof(options));
options.mode = mode;
options.rope_pairing = rope_pairing;
memset(&progress_state, 0, sizeof(progress_state));
if (request->progress) {
options.progress = request->progress;
options.progress_user = request->progress_user;
} else if (request->progress_style == FT_TT_CACHE_PROGRESS_STDERR) {
progress_state.total_source_bytes = source_bytes;
progress_state.tensors_total = ft_gguf_map_tensor_count(map);
progress_state.interactive = isatty(2) ? 1 : 0;
progress_state.started_at = cache_monotonic_seconds();
progress_state.model_label = cache_basename(gguf_path);
progress_state.last_decile = -1;
options.progress = cache_progress_report;
options.progress_user = &progress_state;
}
rc = ft_tt_convert_gguf(gguf_path, temp_path, &options);
if (rc != FT_TT_CONVERT_OK) {
remove(temp_path);
snprintf(reason, reason_bytes,
"converting %.400s at %s failed (ft_tt_convert_gguf rc %d)", gguf_path,
ft_tt_ledger_mode_name(mode), rc);
return FT_TT_ARTIFACT_CACHE_ECONVERT;
}
rc = ft_tt_artifact_provenance_append(temp_path, source_bytes,
ft_tt_artifact_source_digest(map), (uint32_t)mode,
(uint32_t)rope_pairing);
if (rc != FT_TT_PROVENANCE_OK) {
remove(temp_path);
snprintf(reason, reason_bytes,
"the artifact converted but its provenance trailer could not be written; "
"an artifact with no trailer would be reconverted on every launch, so it "
"was discarded rather than kept");
return FT_TT_ARTIFACT_CACHE_EIO;
}
/* rename() is the atomic step: until it runs there is no artifact at the cache path at
* all, so a converter killed mid-write can never leave a plausible short one. */
if (rename(temp_path, artifact_path) != 0) {
remove(temp_path);
snprintf(reason, reason_bytes, "could not move the converted artifact into %.400s",
artifact_path);
return FT_TT_ARTIFACT_CACHE_EIO;
}
return FT_TT_ARTIFACT_CACHE_OK;
}
int ft_tt_artifact_cache_ensure(const ft_tt_artifact_cache_request *request,
ft_tt_artifact_cache_result *result)
{
ft_tt_artifact_provenance want;
ft_tt_rope_pairing rope_pairing;
ft_tt_model_family family;
ft_tt_ledger_mode mode;
ft_gguf_map *map = NULL;
char architecture[64];
int rc;
if (!request || !result || !request->gguf_path)
return FT_TT_ARTIFACT_CACHE_EINVAL;
memset(result, 0, sizeof(*result));
if (ft_gguf_map_open(request->gguf_path, &map) != FT_GGUF_OK || !map) {
snprintf(result->reason, sizeof(result->reason),
"%.400s could not be read as a GGUF checkpoint", request->gguf_path);
return FT_TT_ARTIFACT_CACHE_ESOURCE;
}
architecture[0] = '\0';
ft_gguf_map_metadata_string(map, "general.architecture", architecture, sizeof(architecture));
family = ft_tt_ledger_family_from_architecture(architecture);
result->source_bytes = cache_source_bytes(map);
if (request->rope_pairing >= 0) {
rope_pairing = (ft_tt_rope_pairing)request->rope_pairing;
result->rope_reason = "forced by the caller";
} else {
rope_pairing = ft_tt_artifact_cache_rope_pairing(map, family, architecture,
&result->rope_reason);
}
result->rope_pairing = rope_pairing;
rc = ft_tt_capacity_select(map, family, &request->budget, &result->selection);
if (rc == FT_TT_CAPACITY_EUNCLASSIFIED) {
snprintf(result->reason, sizeof(result->reason), "%s", result->selection.reason);
ft_gguf_map_close(map);
return FT_TT_ARTIFACT_CACHE_EUNCLASSIFIED;
}
if (rc == FT_TT_CAPACITY_ENOFIT) {
snprintf(result->reason, sizeof(result->reason), "%s", result->selection.reason);
ft_gguf_map_close(map);
return FT_TT_ARTIFACT_CACHE_ENOFIT;
}
if (rc != FT_TT_CAPACITY_OK) {
snprintf(result->reason, sizeof(result->reason),
"the capacity planner refused this checkpoint (rc %d)", rc);
ft_gguf_map_close(map);
return FT_TT_ARTIFACT_CACHE_ERROR;
}
/* Forcing a rung is an instrument. It still goes through the fit check above, so a
* forced rung that does not fit is refused with the same arithmetic as an automatic
* one — an override may pick a rung, it may not overrule the card. */
mode = request->forced_mode >= 0 ? (ft_tt_ledger_mode)request->forced_mode
: result->selection.mode;
if (request->forced_mode >= 0) {
uint32_t i;
for (i = 0; i < result->selection.rung_count; i++) {
if (result->selection.rungs[i].mode != mode)
continue;
if (result->selection.rungs[i].artifact_bytes + result->selection.reserve_bytes >
request->budget.card_bytes) {
snprintf(result->reason, sizeof(result->reason),
"%s was forced but does not fit: %.2f GiB of weights plus %.2f GiB "
"reserved against a %.3f GiB card",
ft_tt_ledger_mode_name(mode),
(double)result->selection.rungs[i].artifact_bytes /
(1024.0 * 1024.0 * 1024.0),
(double)result->selection.reserve_bytes / (1024.0 * 1024.0 * 1024.0),
(double)request->budget.card_bytes / (1024.0 * 1024.0 * 1024.0));
ft_gguf_map_close(map);
return FT_TT_ARTIFACT_CACHE_ENOFIT;
}
result->selection.chosen_index = i;
result->selection.mode = mode;
break;
}
}
if (ft_tt_artifact_cache_path(request->gguf_path, request->cache_dir, mode,
result->artifact_path,
sizeof(result->artifact_path)) != FT_TT_ARTIFACT_CACHE_OK) {
snprintf(result->reason, sizeof(result->reason),
"the cached artifact's path would be longer than %u bytes",
(unsigned)FT_TT_ARTIFACT_CACHE_PATH_BYTES);
ft_gguf_map_close(map);
return FT_TT_ARTIFACT_CACHE_EINVAL;
}
cache_expected_provenance(&want, map, result->source_bytes, mode, rope_pairing);
{
struct stat existing;
/* Whether a file was THERE is a different fact from whether it was USABLE, and the
* caller needs both: "reconverting because the cached artifact is stale" and "there
* was nothing to reconvert from" are different sentences to print. */
result->had_artifact = stat(result->artifact_path, &existing) == 0 ? 1 : 0;
}
if (cache_artifact_is_current(result->artifact_path, &want, &result->provenance,
result->note, sizeof(result->note))) {
ft_gguf_map_close(map);
return FT_TT_ARTIFACT_CACHE_OK;
}
rc = cache_produce(request->gguf_path, result->artifact_path, request, mode, rope_pairing,
map, result->source_bytes, result->reason, sizeof(result->reason));
if (rc != FT_TT_ARTIFACT_CACHE_OK) {
ft_gguf_map_close(map);
return rc;
}
result->produced = 1;
/* Read the trailer BACK off the file we just wrote and check it against `want` — the
* same check a cache hit passes. A conversion whose output would not satisfy the next
* launch is a cache that reconverts forever, and it is better to learn that now, from
* the bytes, than to wonder later why nothing is ever cached. */
{
char recheck_note[FT_TT_ARTIFACT_CACHE_NOTE_BYTES];
if (!cache_artifact_is_current(result->artifact_path, &want, &result->provenance,
recheck_note, sizeof(recheck_note))) {
snprintf(result->reason, sizeof(result->reason),
"the artifact was converted but does not validate against the checkpoint "
"it came from: %.300s", recheck_note);
ft_gguf_map_close(map);
return FT_TT_ARTIFACT_CACHE_ECONVERT;
}
}
ft_gguf_map_close(map);
return FT_TT_ARTIFACT_CACHE_OK;
}