ds4fa / tests /test_engine_mgpu_runtime.c
julianmb's picture
Upload folder using huggingface_hub (part 4)
8d0b310 verified
Raw
History Blame Contribute Delete
12.4 kB
/* 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 <cuda_runtime.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}