Pantheon/tests/test_graph_capture_retry.c

261 lines
9.7 KiB
C

/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file test_graph_capture_retry.c
* @brief Unit test for the CUDA-graph capture tracker's retry policy and its
* three-state residency gauge (tasks #41/#42). Host-side, no GPU.
*
* WHAT IT GUARDS
* Before this, a capture that FAILED latched the layer's graph off for the
* life of the process — a transient collision cost that layer ~1.8 ms/step
* forever, silently, with correct output. The fix retries on a bounded
* backoff. This test pins the two halves of "bounded":
*
* - a FAILED capture must recover (the whole point), and
* - an INELIGIBLE slot must never attempt a capture at all, so a model
* that legitimately captures nothing (non-Q3_K experts → MoE not
* grouped) does not turn into a per-step retry loop.
*
* RED-FIRST
* `--latch` runs every expectation against a simulated OLD tracker — one
* chance to capture, then off forever, which is exactly what the shipped
* `graph_tried` / `slots_graph_tried` booleans did. It MUST fail. A gate
* nobody has watched go red is not evidence, so the failing arm ships with
* the passing one.
*
* @status Complete
* @issues None
* @todo None
*/
#include <stdio.h>
#include <string.h>
#include "../src/models/gpu_graph_capture_tracker.h"
static int failures = 0;
#define CHECK(cond, msg) do { \
if (cond) { printf(" PASS: %s\n", msg); } \
else { printf(" FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); failures++; } \
} while (0)
/* When set, every capture attempt is answered by the OLD one-shot latch
* instead of the tracker's backoff — the red arm. */
static int latch_mode = 0;
/* One simulated layer: the tracker plus the boolean latch the shipped code
* used, so both policies can be driven by the same step loop. */
typedef struct {
pn_graph_capture_tracker tracker;
int graph_live; /* a captured graph is replaying (the fast path) */
int latch_tried; /* the old `slots_graph_tried` */
int capture_attempts; /* how many times a capture was actually attempted */
} sim_layer;
/* Ask the policy under test whether to attempt a capture this step. */
static int sim_should_attempt(sim_layer *l)
{
if (latch_mode) {
if (l->latch_tried)
return 0;
l->latch_tried = 1;
return 1;
}
return pn_graph_capture_should_attempt(&l->tracker);
}
/* Run one decode step for a layer with no live graph. `capture_succeeds` is
* what the driver would report if a capture is attempted. */
static void sim_step(sim_layer *l, int capture_succeeds)
{
if (l->graph_live)
return; /* fast path: replay, no tracker involvement */
if (!sim_should_attempt(l))
return;
l->capture_attempts++;
if (capture_succeeds) {
l->graph_live = 1;
pn_graph_capture_succeeded(&l->tracker);
} else {
pn_graph_capture_failed(&l->tracker);
}
}
/* A live graph is dropped for a legitimate reason (its capture key changed). */
static void sim_drop(sim_layer *l)
{
l->graph_live = 0;
l->latch_tried = 0;
pn_graph_capture_dropped(&l->tracker);
}
/* GATE 1 — a capture that fails must recover on a later step. */
static void test_failed_capture_recovers(void)
{
printf("test: a failed capture recovers on a later step\n");
sim_layer l;
memset(&l, 0, sizeof(l));
sim_step(&l, 0); /* the collision */
CHECK(l.tracker.state == PN_GRAPH_CAPTURE_RETRYING,
"one failure leaves the slot RETRYING, not latched off");
CHECK(l.tracker.lifetime_failures == 1, "the failure is counted");
/* Every later step succeeds if it is allowed to try at all. */
for (int step = 0; step < 64; step++)
sim_step(&l, 1);
CHECK(l.tracker.state == PN_GRAPH_CAPTURE_LIVE,
"the layer holds a live graph again");
CHECK(l.tracker.lifetime_captures == 1, "exactly one recapture");
CHECK(l.capture_attempts == 2,
"the backoff cost exactly one extra attempt, not one per step");
}
/* GATE 1b — the retry is BOUNDED: a capture that can never work stops. */
static void test_permanent_failure_stops(void)
{
printf("test: a capture that always fails gives up (bounded retry)\n");
sim_layer l;
memset(&l, 0, sizeof(l));
for (int step = 0; step < 100000; step++)
sim_step(&l, 0);
CHECK(l.tracker.state == PN_GRAPH_CAPTURE_GAVE_UP,
"the slot gave up instead of retrying forever");
CHECK(l.capture_attempts == PN_GRAPH_CAPTURE_MAX_FAILURES,
"attempts are capped at the retry budget");
/* And it stays given up. */
int before = l.capture_attempts;
for (int step = 0; step < 100000; step++)
sim_step(&l, 0);
CHECK(l.capture_attempts == before, "no further attempts, ever");
}
/* A recurring-collision serve must not exhaust its budget: a success in
* between resets it, so the layer keeps recovering for the life of the
* process. */
static void test_intermittent_collisions_never_exhaust(void)
{
printf("test: intermittent collisions never exhaust the budget\n");
sim_layer l;
memset(&l, 0, sizeof(l));
for (int round = 0; round < 50; round++) {
if (round)
sim_drop(&l); /* a capture key changed */
sim_step(&l, 0); /* a collision */
for (int step = 0; step < 32; step++)
sim_step(&l, 1); /* it recovers */
if (l.tracker.state != PN_GRAPH_CAPTURE_LIVE)
break;
}
CHECK(l.tracker.state == PN_GRAPH_CAPTURE_LIVE,
"still recovering after 50 collision/recovery rounds");
CHECK(l.tracker.lifetime_captures == 50, "recovered every time");
}
/* GATE 2 — an INELIGIBLE slot attempts NOTHING. This is the case the retry
* must not touch: a Q5_K_M model's 40 layers report "MoE not grouped" and
* that is correct behaviour, not a fault to retry. */
static void test_ineligible_never_attempts(void)
{
printf("test: an ineligible slot never attempts a capture\n");
sim_layer l;
memset(&l, 0, sizeof(l));
/* The forward marks the gate closed and never calls should_attempt. */
for (int step = 0; step < 100000; step++)
pn_graph_capture_ineligible(&l.tracker,
PN_GRAPH_INELIGIBLE_MOE_NOT_GROUPED);
CHECK(l.capture_attempts == 0, "zero capture attempts");
CHECK(l.tracker.lifetime_failures == 0, "zero failures — nothing failed");
CHECK(l.tracker.state == PN_GRAPH_CAPTURE_INELIGIBLE, "state is ineligible");
CHECK(strcmp(pn_graph_ineligible_reason_name(l.tracker.ineligible_reason),
"MoE not grouped") == 0, "the reason is reported");
/* A temporarily-closed gate (sprof, slot_base>0) must reopen cleanly. */
CHECK(pn_graph_capture_should_attempt(&l.tracker) == 1,
"a reopened gate allows a capture again");
}
/* GATE 4 — the gauge tells the three states apart, and does not invent a
* deficit for a code path the model never runs. */
static void test_residency_gauge(void)
{
printf("test: the residency gauge distinguishes live/failed/ineligible\n");
pn_graph_capture_tracker slots[10];
memset(slots, 0, sizeof(slots));
for (int i = 0; i < 6; i++)
pn_graph_capture_succeeded(&slots[i]);
for (int i = 6; i < 8; i++)
pn_graph_capture_failed(&slots[i]);
for (int i = 8; i < 10; i++)
pn_graph_capture_ineligible(&slots[i],
PN_GRAPH_INELIGIBLE_MOE_NOT_GROUPED);
pn_graph_residency r;
memset(&r, 0, sizeof(r));
for (int i = 0; i < 10; i++)
pn_graph_residency_add(&r, &slots[i]);
CHECK(r.live == 6 && r.failed == 2 && r.ineligible == 2 && r.total == 10,
"6 live / 2 failed / 2 ineligible of 10");
CHECK(r.gave_up == 0, "one failure each is not a give-up");
CHECK(r.lifetime_captures == 6 && r.lifetime_failures == 2,
"lifetime counters agree");
/* A model that decodes batched never touches its single-token slots. */
pn_graph_capture_tracker untouched[40];
memset(untouched, 0, sizeof(untouched));
pn_graph_residency u;
memset(&u, 0, sizeof(u));
for (int i = 0; i < 40; i++)
pn_graph_residency_add(&u, &untouched[i]);
CHECK(u.total == 0,
"an unexercised path reports total 0, not 40 missing graphs");
/* A model that captures nothing reports honestly: no fault, all gated. */
pn_graph_capture_tracker q5[40];
memset(q5, 0, sizeof(q5));
pn_graph_residency g;
memset(&g, 0, sizeof(g));
for (int i = 0; i < 40; i++) {
pn_graph_capture_ineligible(&q5[i], PN_GRAPH_INELIGIBLE_MOE_NOT_GROUPED);
pn_graph_residency_add(&g, &q5[i]);
}
CHECK(g.total == 40 && g.ineligible == 40 && g.failed == 0 && g.live == 0,
"a non-Q3_K model reads 40 ineligible / 0 failed");
}
int main(int argc, char **argv)
{
for (int i = 1; i < argc; i++)
if (strcmp(argv[i], "--latch") == 0)
latch_mode = 1;
printf("=== CUDA-graph capture retry + residency gauge ===\n");
if (latch_mode)
printf("MODE: --latch (the OLD one-shot behaviour — this arm MUST fail)\n");
test_failed_capture_recovers();
test_permanent_failure_stops();
test_intermittent_collisions_never_exhaust();
test_ineligible_never_attempts();
test_residency_gauge();
printf("\n%d check(s) failed\n", failures);
if (latch_mode) {
/* Inverted: the red arm proves the gate is capable of failing. */
printf(failures > 0 ? "[PASS] the latch arm failed, as it must\n"
: "[FAIL] the latch arm PASSED — the gate is vacuous\n");
return failures > 0 ? 0 : 1;
}
printf(failures ? "[FAIL] graph capture retry\n" : "[PASS] graph capture retry\n");
return failures ? 1 : 0;
}