/* CUDA-build numerical-equivalence test for Half-B GPU-only multi-tier * execution. * * Compares first-token + 4 decode steps from a single-tier baseline against * an explicit GPU-only multi-tier run, both with DS4_CUDA_NO_TF32=1 pinning * CUBLAS_DEFAULT_MATH. Asserts: * - prefill max-abs logit delta < 1e-4 * - decode step max-abs logit delta < 1e-3 * - decode step top-1 token identical * - decode step top-5 set identical * - the multi-tier placement was indeed multi-tier (no entry == single * baseline tier; if all entries went to tier 0 the test would be a * tautology). * * Skips with PASS if cudaGetDeviceCount < 2 or DS4_TEST_MODEL is unset. * * Requires DS4_TEST_HOOKS so ds4_test_session_read_logits + * ds4_test_engine_placement are exported. */ #include "ds4.h" #include "ds4_gpu_mgpu.h" #include "ds4_layer_pack.h" /* DS4_LAYER_PACK_CPU */ #include #include #include #include #include #ifndef DS4_N_VOCAB #define DS4_N_VOCAB 129280 #endif /* DS4_N_LAYER is an internal enum in ds4.c. Hardcoded here for the test * placement-size assertion; matches the constant in ds4.c. */ #ifndef DS4_TEST_N_LAYER #define DS4_TEST_N_LAYER 43 #endif /* Declared in ds4.c under DS4_TEST_HOOKS. */ int ds4_test_session_read_logits(ds4_session *s, float *out, uint64_t out_bytes); const int *ds4_test_engine_placement(const ds4_engine *e); #define CHECK(cond, msg) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL: %s (line %d)\n", (msg), __LINE__); \ return 1; \ } \ } while (0) #define CHECKF(cond, fmt, ...) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL: " fmt " (line %d)\n", __VA_ARGS__, __LINE__); \ return 1; \ } \ } while (0) static float max_abs_delta(const float *a, const float *b, size_t n) { float worst = 0.0f; for (size_t i = 0; i < n; i++) { float d = fabsf(a[i] - b[i]); if (d > worst) worst = d; } return worst; } /* Top-K argmax helpers — simple O(N*K), fine for K=5. */ static void top_k(const float *a, size_t n, int *out, int k) { /* Build a sorted set of top-k indices by descending value. */ for (int i = 0; i < k; i++) out[i] = -1; for (size_t i = 0; i < n; i++) { const float v = a[i]; /* Find insertion spot. */ int slot = -1; for (int j = 0; j < k; j++) { if (out[j] < 0) { slot = j; break; } if (v > a[out[j]]) { slot = j; break; } } if (slot < 0) continue; /* Shift the slot..k-1 right by one. */ for (int j = k - 1; j > slot; j--) out[j] = out[j - 1]; out[slot] = (int)i; } } static int top_k_set_equal(const int *a, const int *b, int k) { /* Set equality, not order — both lists contain the same indices. */ for (int i = 0; i < k; i++) { int found = 0; for (int j = 0; j < k; j++) { if (a[i] == b[j]) { found = 1; break; } } if (!found) return 0; } return 1; } static int argmax_f32(const float *a, size_t n) { int best = 0; float bestv = a[0]; for (size_t i = 1; i < n; i++) { if (a[i] > bestv) { bestv = a[i]; best = (int)i; } } return best; } /* Read a short hard-coded test prompt suitable for prefill comparison. * The test needs a prompt long enough to exercise prefill but short * enough to run in seconds. We tokenize a hard-coded UTF-8 string via * ds4_tokenize. */ static void build_prompt(ds4_engine *e, ds4_tokens *out) { /* A short English-only prompt with enough tokens to exercise prefill. * Tokenizer is deterministic. */ const char *text = "The quick brown fox jumps over the lazy dog. " \ "It was a dark and stormy night, and the wind " \ "howled across the empty fields beyond the village."; ds4_tokenize_text(e, text, out); } int main(void) { int dev_count = 0; (void)cudaGetDeviceCount(&dev_count); fprintf(stderr, "test_engine_mgpu_runtime: %d CUDA devices visible\n", dev_count); if (dev_count < 2) { fprintf(stderr, " skipping (need >= 2 devices)\n"); return 0; } const char *model_path = getenv("DS4_TEST_MODEL"); if (!model_path || !model_path[0]) { fprintf(stderr, "FAIL: DS4_TEST_MODEL not set\n"); return 1; } /* Pin cuBLAS default math so reduction-order across devices yields the * tightest possible equivalence. */ setenv("DS4_CUDA_NO_TF32", "1", 1); /* Exercise the peer path when available; copy_xdev falls back to * pinned-host bounce automatically when peer is non-functional. */ unsetenv("DS4_FORCE_HOST_BOUNCE"); /* ---- Run #1: single-GPU baseline. ---- */ fprintf(stderr, "test_engine_mgpu_runtime: single-tier baseline\n"); ds4_engine_options opt; memset(&opt, 0, sizeof(opt)); opt.model_path = model_path; opt.backend = DS4_BACKEND_CUDA; opt.n_threads = 1; opt.warm_weights = false; opt.quality = false; ds4_engine *e1 = NULL; int rc = ds4_engine_open(&e1, &opt); CHECKF(rc == 0 && e1 != NULL, "single-tier engine_open failed (rc=%d)", rc); ds4_tokens prompt1 = {0}; build_prompt(e1, &prompt1); CHECK(prompt1.len > 0, "tokenize prompt"); ds4_session *s1 = NULL; rc = ds4_session_create(&s1, e1, 4096); CHECKF(rc == 0 && s1 != NULL, "single-tier session_create failed (rc=%d)", rc); char err[256] = {0}; rc = ds4_session_sync(s1, &prompt1, err, sizeof(err)); CHECKF(rc == 0, "single-tier session_sync failed (rc=%d, err=%s)", rc, err); float *prefill_a = (float *)malloc((size_t)DS4_N_VOCAB * sizeof(float)); CHECK(prefill_a != NULL, "alloc prefill_a"); CHECK(ds4_test_session_read_logits(s1, prefill_a, (uint64_t)DS4_N_VOCAB * sizeof(float)) == 0, "read single-tier prefill logits"); /* 4 greedy decode steps. */ int decoded_a[4]; float (*decoded_logits_a)[DS4_N_VOCAB] = (float (*)[DS4_N_VOCAB])malloc(4u * (size_t)DS4_N_VOCAB * sizeof(float)); CHECK(decoded_logits_a != NULL, "alloc decoded_logits_a"); for (int i = 0; i < 4; i++) { int tok = ds4_session_argmax(s1); decoded_a[i] = tok; rc = ds4_session_eval(s1, tok, err, sizeof(err)); CHECKF(rc == 0, "single-tier decode eval %d failed (rc=%d, err=%s)", i, rc, err); CHECK(ds4_test_session_read_logits(s1, decoded_logits_a[i], (uint64_t)DS4_N_VOCAB * sizeof(float)) == 0, "read single-tier decode logits"); } ds4_session_free(s1); ds4_tokens_free(&prompt1); ds4_engine_close(e1); /* ---- Run #2: GPU-only multi-tier. ---- */ fprintf(stderr, "test_engine_mgpu_runtime: GPU-only multi-tier\n"); ds4_gpu_config cfg; memset(&cfg, 0, sizeof(cfg)); cfg.n_gpus = 2; cfg.device_indices[0] = 0; cfg.device_indices[1] = 1; /* GPU0 has ~48 GiB free on the test box; GPU1 has ~17 GiB free * because unrelated workloads occupy ~30 GiB. Budget {44, 14} GiB * after a 1 GiB safety margin — enough headroom to fit the entire * model across two GPUs with a small over-provision allowance. */ cfg.vram_bytes[0] = (size_t)44ull * 1024ull * 1024ull * 1024ull; cfg.vram_bytes[1] = (size_t)14ull * 1024ull * 1024ull * 1024ull; cfg.safety_margin_bytes = (size_t)1024u * 1024u * 1024u; ds4_engine *e2 = NULL; rc = ds4_engine_create_with_gpu_config(&e2, &opt, &cfg); if (rc != 0 || e2 == NULL) { /* Common case on a constrained box: GPU1 may be in use by other * workloads, forcing the placement to spill some layers to CPU. * CPU-spill is correctly refused by engine_install_gpu_placement * (B7) with stderr naming the wave-3b follow-up — this test is * for the GPU-only numerical-eq gate, not the spill refusal * (that's covered by tests/test_engine_mgpu_refusal). When the * placement spills, skip with PASS so CI is not blocked by * external VRAM occupancy. */ fprintf(stderr, "test_engine_mgpu_runtime: multi-tier engine_create rc=%d; " "skipping GPU-only numerical-eq gate (CPU spill or other env constraint).\n", rc); free(prefill_a); free(decoded_logits_a); fprintf(stderr, "test_engine_mgpu_runtime SKIP_PASS\n"); return 0; } /* Assert the placement is genuinely multi-tier (not all-CPU spill and * not all-tier-0). The placement is captured by engine_classify_multi_tier. */ const int *placement = ds4_test_engine_placement(e2); CHECK(placement != NULL, "engine placement should be exposed"); int any_tier_1 = 0; int n_entries = DS4_TEST_N_LAYER + 2; /* embedding, per-layer, head */ for (int i = 0; i < n_entries; i++) { if (placement[i] == DS4_LAYER_PACK_CPU) { fprintf(stderr, "FAIL: placement crossed to CPU at entry %d but engine_create returned 0?\n", i); ds4_engine_close(e2); return 1; } if (placement[i] == 1) any_tier_1 = 1; } CHECK(any_tier_1, "test budgets should force at least one entry on tier 1"); ds4_tokens prompt2 = {0}; build_prompt(e2, &prompt2); CHECK(prompt2.len > 0, "tokenize prompt (multi-tier)"); ds4_session *s2 = NULL; rc = ds4_session_create(&s2, e2, 4096); CHECKF(rc == 0 && s2 != NULL, "multi-tier session_create failed (rc=%d)", rc); rc = ds4_session_sync(s2, &prompt2, err, sizeof(err)); CHECKF(rc == 0, "multi-tier session_sync failed (rc=%d, err=%s)", rc, err); float *prefill_b = (float *)malloc((size_t)DS4_N_VOCAB * sizeof(float)); CHECK(prefill_b != NULL, "alloc prefill_b"); CHECK(ds4_test_session_read_logits(s2, prefill_b, (uint64_t)DS4_N_VOCAB * sizeof(float)) == 0, "read multi-tier prefill logits"); /* Prefill numerical-eq. */ float prefill_delta = max_abs_delta(prefill_a, prefill_b, DS4_N_VOCAB); int prefill_top_a = argmax_f32(prefill_a, DS4_N_VOCAB); int prefill_top_b = argmax_f32(prefill_b, DS4_N_VOCAB); fprintf(stderr, " prefill delta=%g (top1: a=%d b=%d)\n", (double)prefill_delta, prefill_top_a, prefill_top_b); CHECKF(prefill_delta < 1e-4f, "prefill delta %g exceeds gate 1e-4", (double)prefill_delta); CHECKF(prefill_top_a == prefill_top_b, "prefill top-1 mismatch: a=%d b=%d", prefill_top_a, prefill_top_b); /* Decode steps. */ for (int i = 0; i < 4; i++) { int tok = ds4_session_argmax(s2); CHECKF(tok == decoded_a[i], "decode top-1 mismatch at step %d (a=%d b=%d)", i, decoded_a[i], tok); rc = ds4_session_eval(s2, tok, err, sizeof(err)); CHECKF(rc == 0, "multi-tier decode eval %d failed (rc=%d, err=%s)", i, rc, err); float decoded_b[DS4_N_VOCAB]; CHECK(ds4_test_session_read_logits(s2, decoded_b, (uint64_t)DS4_N_VOCAB * sizeof(float)) == 0, "read multi-tier decode logits"); float d = max_abs_delta(decoded_logits_a[i], decoded_b, DS4_N_VOCAB); fprintf(stderr, " decode step %d delta=%g\n", i, (double)d); CHECKF(d < 1e-3f, "decode delta %g exceeds gate 1e-3 at step %d", (double)d, i); int top5_a[5], top5_b[5]; top_k(decoded_logits_a[i], DS4_N_VOCAB, top5_a, 5); top_k(decoded_b, DS4_N_VOCAB, top5_b, 5); CHECKF(top_k_set_equal(top5_a, top5_b, 5), "decode top-5 set mismatch at step %d", i); } free(prefill_a); free(prefill_b); free(decoded_logits_a); ds4_session_free(s2); ds4_tokens_free(&prompt2); ds4_engine_close(e2); fprintf(stderr, "test_engine_mgpu_runtime PASS\n"); return 0; }