228 lines
11 KiB
C
228 lines
11 KiB
C
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
/* Copyright (C) 2026 David (Corey) Herrera */
|
|
/*
|
|
* test_die_scratch_ctx.c — the per-die GPU scratch reserve is priced for the
|
|
* window the model is actually capped to, not for a 32k one.
|
|
*
|
|
* PN_ADMIT_DIE_SCRATCH_BYTES was a single 1.5 GiB constant measured at a 32768
|
|
* window. Most of that is the per-slot on-die KV ring
|
|
* ([nslots][n_head_kv][max_seq][row] per layer), which is proportional to the
|
|
* cap — so charging it flat made a 2048-token model pay a 32k price. That
|
|
* refused a 2-die Qwen3.6-35B-A3B Q4_K_M split whose real per-die peak was
|
|
* 10,570 / 10,879 MiB of 12,206:
|
|
*
|
|
* [admit] REFUSE model load: die ordinal 6 has 11980 MiB free but its split
|
|
* share needs ~11399 MiB (20/40 layers + KV@2048 + scratch)
|
|
*
|
|
* The sibling test (test_admission_reserve.c) covers the OTHER half of the same
|
|
* family of bug: the per-die reserve being charged twice on the mr_load path.
|
|
* Here the reserve is charged exactly once and stays charged — this is a
|
|
* repricing, not a loosening.
|
|
*/
|
|
|
|
#include "../src/core/mem/pn_admission.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
uint64_t pn_die_reserve_bytes(void);
|
|
|
|
static int g_failed = 0;
|
|
|
|
#define CHECK(cond, msg) do { \
|
|
if (!(cond)) { \
|
|
fprintf(stderr, " FAIL: %s (%s:%d)\n", (msg), __FILE__, __LINE__); \
|
|
g_failed++; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define MIB(n) ((uint64_t)(n) << 20)
|
|
|
|
/* The K80 test machine's measured Qwen3.6-35B-A3B Q4_K_M split across two K80 dies. */
|
|
#define K80_DIE_FREE_MIB 11980 /* raw free, after the CUDA context */
|
|
#define K80_DIE_A_WEIGHTS 9857 /* layers [0,20) */
|
|
#define K80_DIE_B_WEIGHTS 10254 /* layers [20,40) + the 397 MiB LM head */
|
|
#define K80_KV_2048 6 /* 20/40 layers of KV at a 2048 cap */
|
|
#define K80_KV_32768 96 /* the same share at a 32768 cap */
|
|
|
|
/* Mirrors PN_QWEN35MOE_MAX_SEQ_CAP (qwen35moe.h) — the widest window any model
|
|
* here can be capped to, and so the widest the reserve is ever asked about. */
|
|
#define ENGINE_MAX_SEQ_CAP 262144
|
|
|
|
int main(void)
|
|
{
|
|
printf("test_die_scratch_ctx\n");
|
|
uint64_t reserve = pn_die_reserve_bytes();
|
|
CHECK(reserve > 0, "there is a per-die reserve to hold back");
|
|
|
|
/* --- The reserve is a curve, not a constant ------------------------- */
|
|
|
|
/* The one point the 1.5 GiB constant was ever measured at must be
|
|
* unchanged: this repricing fixes the extrapolation, it does not move the
|
|
* calibration. */
|
|
CHECK(pn_admit_die_scratch_bytes(32768) == MIB(1536),
|
|
"a 32768 window still costs exactly the measured 1.5 GiB");
|
|
|
|
/* Below it the charge falls, because the per-slot ring does. */
|
|
CHECK(pn_admit_die_scratch_bytes(2048) == MIB(576),
|
|
"a 2048 window costs the base plus its own ring, not a 32k ring");
|
|
CHECK(pn_admit_die_scratch_bytes(2048) <
|
|
pn_admit_die_scratch_bytes(8192),
|
|
"a wider window reserves more");
|
|
CHECK(pn_admit_die_scratch_bytes(8192) <
|
|
pn_admit_die_scratch_bytes(32768),
|
|
"and wider still reserves more again");
|
|
|
|
/* Above the calibration point the charge STOPS growing. The slope is sized
|
|
* for the default 8 sessions; a production serve pinned to --max-sessions 2
|
|
* at a 131072 window allocates a quarter of that ring, so letting the line
|
|
* run would refuse a split that runs today. This repricing must only ever
|
|
* charge less than the flat constant, never more. */
|
|
CHECK(pn_admit_die_scratch_bytes(131072) == MIB(1536),
|
|
"a 131072 window costs no more than the constant it replaces");
|
|
CHECK(pn_admit_die_scratch_bytes(ENGINE_MAX_SEQ_CAP) == MIB(1536),
|
|
"and neither does the widest window the engine allows");
|
|
|
|
int over = 0, backwards = 0;
|
|
uint64_t prev = 0;
|
|
for (int ctx = 0; ctx <= ENGINE_MAX_SEQ_CAP; ctx += 331) {
|
|
uint64_t s = pn_admit_die_scratch_bytes(ctx);
|
|
if (s > MIB(1536)) over++;
|
|
if (s < prev) backwards++;
|
|
prev = s;
|
|
}
|
|
CHECK(over == 0, "no window anywhere is charged more than the old constant");
|
|
CHECK(backwards == 0, "and the charge never falls as the window widens");
|
|
|
|
/* No cap known is the base alone — never zero, and never negative-indexed
|
|
* into the per-token term. */
|
|
CHECK(pn_admit_die_scratch_bytes(0) == PN_ADMIT_DIE_SCRATCH_BASE_BYTES,
|
|
"no window known still reserves the fixed base");
|
|
CHECK(pn_admit_die_scratch_bytes(-1) == PN_ADMIT_DIE_SCRATCH_BASE_BYTES,
|
|
"a garbage window does not underflow the reserve");
|
|
|
|
/* --- The K80 test machine case: it fits, so it must be admitted ----------------- */
|
|
|
|
uint64_t die_free = MIB(K80_DIE_FREE_MIB);
|
|
uint64_t usable = die_free - reserve;
|
|
uint64_t need = 0;
|
|
|
|
/* First reproduce the refusal, so this test is anchored to the real bug:
|
|
* priced at the old flat 1.5 GiB, die A does not fit. */
|
|
CHECK(MIB(K80_DIE_A_WEIGHTS) + MIB(K80_KV_2048) + MIB(1536) > usable,
|
|
"the old flat 32k price refused die A -- the bug, reproduced");
|
|
|
|
CHECK(pn_admit_model_load_die(MIB(K80_DIE_A_WEIGHTS), MIB(K80_KV_2048),
|
|
2048, die_free, &need),
|
|
"die A admits its 20 layers at a 2048 cap");
|
|
CHECK(need == MIB(K80_DIE_A_WEIGHTS) + MIB(K80_KV_2048) + MIB(576),
|
|
"and the projection is weights + KV + the 2048-window scratch");
|
|
CHECK(need <= usable, "the printed numbers agree it fits");
|
|
|
|
/* Die B carries the LM head as well and is the tighter of the two. */
|
|
CHECK(pn_admit_model_load_die(MIB(K80_DIE_B_WEIGHTS), MIB(K80_KV_2048),
|
|
2048, die_free, &need),
|
|
"die B admits its 20 layers plus the LM head");
|
|
|
|
/* The measured peaks stayed inside the reserve floor, which is why
|
|
* admitting this load was correct rather than merely convenient. */
|
|
CHECK(MIB(10879) <= MIB(12206) - reserve,
|
|
"die B's real peak left the per-die reserve intact");
|
|
|
|
/* --- It is a repricing, not a loosening ----------------------------- */
|
|
|
|
/* At the window the constant was measured at, the gate refuses exactly what
|
|
* it refused before: the same load capped to 32768 does not fit. */
|
|
CHECK(!pn_admit_model_load_die(MIB(K80_DIE_A_WEIGHTS), MIB(K80_KV_32768),
|
|
32768, die_free, &need),
|
|
"the same dies still refuse a 32768 cap");
|
|
|
|
/* And the per-die reserve is still held back, exactly once: the admission
|
|
* boundary sits at need + reserve, not at need. */
|
|
uint64_t probe_w = MIB(1000), probe_kv = MIB(10);
|
|
uint64_t probe_need = 0;
|
|
(void)pn_admit_model_load_die(probe_w, probe_kv, 2048, 0, &probe_need);
|
|
CHECK(probe_need == probe_w + probe_kv + MIB(576),
|
|
"the projection does not depend on the free figure");
|
|
CHECK(pn_admit_model_load_die(probe_w, probe_kv, 2048,
|
|
probe_need + reserve, NULL),
|
|
"free == need + reserve is the last figure that admits");
|
|
CHECK(!pn_admit_model_load_die(probe_w, probe_kv, 2048,
|
|
probe_need + reserve - 1, NULL),
|
|
"one byte into the reserve is a refusal");
|
|
|
|
/* --- A die pays the KV of ITS OWN layers ---------------------------- */
|
|
|
|
/* Qwen3.6-35B-A3B reports 6144 B/token whole-model from its real geometry.
|
|
* Split 20/40, one die holds half of that. */
|
|
uint64_t whole_2k = 6144ULL * 2048;
|
|
CHECK(pn_admit_die_kv_bytes(6144, true, 2048, 20, 40) == whole_2k / 2,
|
|
"a 20-of-40-layer die pays half the model's KV");
|
|
CHECK(pn_admit_die_kv_bytes(6144, true, 2048, 40, 40) == whole_2k,
|
|
"a die holding every layer pays all of it");
|
|
|
|
/* An unreadable layout must charge the WHOLE model's KV -- not zero, and
|
|
* not a guessed share. Gated conservatively rather than admitted blind. */
|
|
CHECK(pn_admit_die_kv_bytes(6144, true, 2048, 0, 0) == whole_2k,
|
|
"an unknown layer count charges the whole model, not nothing");
|
|
CHECK(pn_admit_die_kv_bytes(6144, true, 2048, 0, 40) == whole_2k,
|
|
"and so does a zero layer share");
|
|
CHECK(pn_admit_die_kv_bytes(6144, true, 0, 20, 40) == 0,
|
|
"no window means no KV");
|
|
|
|
/* kv_per_token 0 means "not resident yet" -- fall back to the generic
|
|
* constant, never to zero. */
|
|
CHECK(pn_admit_die_kv_bytes(0, true, 2048, 40, 40) ==
|
|
PN_ADMIT_KV_BYTES_PER_TOKEN_Q4 * 2048,
|
|
"a pre-load caller gets the generic constant, not a free pass");
|
|
|
|
/* --- The deployed config's shape, at the cap it actually runs ------------- */
|
|
|
|
/* qwen36-stock: Qwen3.6-35B-A3B Q3_K_M split over two dies at --ctx-cap
|
|
* 131072. Measured per-die weight projection 7425 MiB against a
|
|
* die-manager budget of 11182 MiB (12,206 total less the 1 GiB reserve).
|
|
*
|
|
* This is the load the CPU-path LUT refused. Priced as the GPU load it is:
|
|
*
|
|
* 8192: 7425 + 23 + 768 = 8216 MiB
|
|
* 32768: 7425 + 92 + 1536 = 9053 MiB
|
|
* 131072: 7425 + 369 + 1536 = 9330 MiB
|
|
*
|
|
* against 11182 MiB. Priced through the CPU-resident gate the same load
|
|
* needed 56363 MiB at 131072 -- 48.2 GiB of it host activation LUT. */
|
|
const uint64_t prod_weights = MIB(7425);
|
|
const uint64_t prod_budget = MIB(11182);
|
|
uint64_t kv8k = pn_admit_die_kv_bytes(0, true, 8192, 20, 40);
|
|
uint64_t kv32k = pn_admit_die_kv_bytes(0, true, 32768, 20, 40);
|
|
uint64_t kv128k = pn_admit_die_kv_bytes(0, true, 131072, 20, 40);
|
|
uint64_t n8k = 0, n32k = 0, n128k = 0;
|
|
|
|
CHECK(pn_admit_model_load_die_budgeted(prod_weights, kv8k, 8192,
|
|
prod_budget, &n8k),
|
|
"the deployed config's shape admits at an 8192 cap");
|
|
CHECK(pn_admit_model_load_die_budgeted(prod_weights, kv32k, 32768,
|
|
prod_budget, &n32k),
|
|
"the deployed config's shape admits at a 32768 cap");
|
|
CHECK(pn_admit_model_load_die_budgeted(prod_weights, kv128k, 131072,
|
|
prod_budget, &n128k),
|
|
"the deployed config's shape admits at the 131072 cap it really runs");
|
|
CHECK(n8k < n32k && n32k < n128k,
|
|
"and a wider cap still costs more -- the gate did not go blind");
|
|
CHECK(n128k + MIB(1800) < prod_budget,
|
|
"it admits with real headroom, not by a hair");
|
|
|
|
/* Still a gate: the same cap, a die whose weight share is 3 GiB larger,
|
|
* does not fit and must be refused. */
|
|
uint64_t over_need = 0;
|
|
CHECK(!pn_admit_model_load_die_budgeted(MIB(10500), kv128k, 131072,
|
|
prod_budget, &over_need),
|
|
"a share that genuinely overruns the budget is still refused");
|
|
CHECK(over_need > prod_budget, "and the printed numbers say why");
|
|
|
|
if (g_failed) {
|
|
printf("FAILED: %d check(s)\n", g_failed);
|
|
return 1;
|
|
}
|
|
printf("ALL PASS\n");
|
|
return 0;
|
|
}
|