Download tests/test_engine_correctness.c from julianmb/ds4fa: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/julianmb/ds4fa/resolve/main/tests/test_engine_correctness.c
- Command line
-
hf download hf://julianmb/ds4fa/tests/test_engine_correctness.c
-
curl -L -o test_engine_correctness.c https://huggingface.co/julianmb/ds4fa/resolve/main/tests/test_engine_correctness.c
10.9 kB
| /* Deterministic correctness check: greedy-decode a known prompt for N | |
| * tokens and assert the decoded token-ID sequence matches between | |
| * single-tier and GPU-only multi-tier execution. | |
| * | |
| * Uses small ctx (1024) and short generation (16 tokens) so it fits in | |
| * tight VRAM environments. Sister test to test_engine_mgpu_runtime, | |
| * which compares logit deltas at a tighter tolerance (1e-4) — this | |
| * one asserts equivalence at the user-visible token level, which is | |
| * the actual correctness contract. | |
| * | |
| * Requires DS4_TEST_MODEL. Skips with PASS if < 2 CUDA devices visible. | |
| * | |
| * The test prints both decoded sequences as human-readable text so a | |
| * reviewer can eyeball "this is English on-topic" regardless of the | |
| * automated assert. */ | |
| /* Build a short prompt using the actual chat template | |
| * (BOS + user_id + <text> + assistant_id + think_end so the model skips | |
| * any reasoning trace and goes straight to the answer). With DS4_THINK_NONE | |
| * the model produces a clean short reply, which makes the token-ID compare | |
| * easy to read at a glance. | |
| * | |
| * Why not ds4_tokenize_rendered_chat: that function parses text that | |
| * ALREADY contains special tokens (it just looks for them inline). It adds | |
| * no scaffolding on its own, so the model sees raw text and continues it | |
| * like a completion ("What is the capital of France?...What is the capital | |
| * of France?<EOS>"). ds4_encode_chat_prompt adds the actual BOS / user / | |
| * assistant / think markers DeepSeek-V4-Flash is trained on. */ | |
| static void build_prompt(ds4_engine *e, ds4_tokens *out) { | |
| const char *prompt = "What is the capital of France?"; | |
| ds4_encode_chat_prompt(e, /*system=*/NULL, prompt, DS4_THINK_NONE, out); | |
| } | |
| /* Decode an array of token IDs to UTF-8 (writes into a heap buffer the | |
| * caller must free). Returns NULL on failure. */ | |
| static char *decode_tokens(ds4_engine *e, const int *tokens, int n) { | |
| size_t cap = 256; | |
| size_t len = 0; | |
| char *buf = (char *)malloc(cap); | |
| if (!buf) return NULL; | |
| buf[0] = '\0'; | |
| for (int i = 0; i < n; i++) { | |
| size_t tlen = 0; | |
| char *txt = ds4_token_text(e, tokens[i], &tlen); | |
| if (!txt) continue; | |
| if (len + tlen + 1 > cap) { | |
| cap = (len + tlen + 1) * 2; | |
| char *nbuf = (char *)realloc(buf, cap); | |
| if (!nbuf) { free(buf); free(txt); return NULL; } | |
| buf = nbuf; | |
| } | |
| memcpy(buf + len, txt, tlen); | |
| len += tlen; | |
| buf[len] = '\0'; | |
| free(txt); | |
| } | |
| return buf; | |
| } | |
| /* Greedy-decode up to N_GEN_TOKENS tokens from a session, stopping at | |
| * EOS (which is what real inference does). Stores generated token IDs | |
| * into `out_tokens` and returns the actual count via `*out_count` | |
| * (always <= N_GEN_TOKENS). On error returns 1. | |
| * | |
| * Continuing past EOS pushes the engine into post-EOS states whose | |
| * deterministic behavior across single-tier and multi-tier is harder | |
| * to reason about; stopping mirrors production inference. */ | |
| static int greedy_decode(ds4_engine *e, ds4_session *s, int *out_tokens, | |
| int *out_count) { | |
| char err[256] = {0}; | |
| const int eos = ds4_token_eos(e); | |
| *out_count = 0; | |
| for (int i = 0; i < N_GEN_TOKENS; i++) { | |
| int t = ds4_session_argmax(s); | |
| if (t < 0) { | |
| fprintf(stderr, "argmax returned %d at step %d\n", t, i); | |
| return 1; | |
| } | |
| out_tokens[i] = t; | |
| (*out_count)++; | |
| if (t == eos) break; | |
| if (ds4_session_eval(s, t, err, sizeof(err)) != 0) { | |
| fprintf(stderr, "eval failed at step %d: %s\n", i, err); | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| int main(void) { | |
| int dev_count = 0; | |
| (void)cudaGetDeviceCount(&dev_count); | |
| fprintf(stderr, "test_engine_correctness: %d CUDA devices visible\n", dev_count); | |
| if (dev_count < 1) { | |
| fprintf(stderr, " skipping (no CUDA 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 math mode for the tightest deterministic equivalence between | |
| * single-tier and multi-tier. */ | |
| setenv("DS4_CUDA_NO_TF32", "1", 1); | |
| 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; | |
| /* ---- Single-tier baseline ---- */ | |
| fprintf(stderr, "test_engine_correctness: single-tier baseline\n"); | |
| ds4_engine *e1 = NULL; | |
| int rc = ds4_engine_open(&e1, &opt); | |
| CHECKF(rc == 0 && e1 != NULL, "single-tier engine_open rc=%d", rc); | |
| ds4_tokens prompt1 = {0}; | |
| build_prompt(e1, &prompt1); | |
| CHECK(prompt1.len > 0 && prompt1.len < 256, "tokenize prompt within bounds"); | |
| ds4_session *s1 = NULL; | |
| rc = ds4_session_create(&s1, e1, 1024); | |
| CHECKF(rc == 0 && s1 != NULL, "single-tier session_create rc=%d", rc); | |
| char err[256] = {0}; | |
| rc = ds4_session_sync(s1, &prompt1, err, sizeof(err)); | |
| CHECKF(rc == 0, "single-tier session_sync rc=%d err=%s", rc, err); | |
| int tokens_a[N_GEN_TOKENS]; | |
| int n_a = 0; | |
| CHECK(greedy_decode(e1, s1, tokens_a, &n_a) == 0, "single-tier greedy decode"); | |
| char *text_a = decode_tokens(e1, tokens_a, n_a); | |
| CHECK(text_a != NULL, "decode single-tier tokens to text"); | |
| fprintf(stderr, " single-tier output (%d tokens): \"%s\"\n", n_a, text_a); | |
| fprintf(stderr, " single-tier token IDs:"); | |
| for (int i = 0; i < n_a; i++) fprintf(stderr, " %d", tokens_a[i]); | |
| fprintf(stderr, "\n"); | |
| /* Stash prompt length before freeing — ds4_tokens_free memsets the | |
| * struct to zero, so reading prompt1.len after free returns 0 | |
| * (which broke the cross-engine prompt-length sanity check below). */ | |
| const int prompt1_len = prompt1.len; | |
| ds4_session_free(s1); | |
| ds4_tokens_free(&prompt1); | |
| ds4_engine_close(e1); | |
| /* ---- Multi-tier (GPU-only, when 2+ devices visible) ---- */ | |
| if (dev_count < 2) { | |
| fprintf(stderr, " skipping multi-tier compare (only 1 CUDA device)\n"); | |
| fprintf(stderr, "test_engine_correctness SKIP_PASS (single-tier only)\n"); | |
| free(text_a); | |
| return 0; | |
| } | |
| fprintf(stderr, "test_engine_correctness: 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; | |
| /* Budgets sized to clear the per-tier graph overhead pre-subtract | |
| * (~3.93 GiB at default ctx) plus the user-supplied safety margin | |
| * plus cuBLAS workspace, then still fit the 80 GiB IQ2XXS model with | |
| * room for ctx=1024 KV. If actual free VRAM is lower (other workloads | |
| * on the box), the engine refuses upfront and this test SKIP_PASSes. */ | |
| cfg.vram_bytes[0] = (size_t)47ull * 1024ull * 1024ull * 1024ull; | |
| cfg.vram_bytes[1] = (size_t)47ull * 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) { | |
| fprintf(stderr, "test_engine_correctness: multi-tier engine_create rc=%d (env-constrained; SKIP_PASS)\n", rc); | |
| fprintf(stderr, "test_engine_correctness SKIP_PASS\n"); | |
| free(text_a); | |
| return 0; | |
| } | |
| ds4_tokens prompt2 = {0}; | |
| build_prompt(e2, &prompt2); | |
| CHECKF(prompt2.len == prompt1_len, | |
| "prompt tokenization length differs across engines (single=%d multi=%d)", | |
| prompt1_len, prompt2.len); | |
| ds4_session *s2 = NULL; | |
| rc = ds4_session_create(&s2, e2, 1024); | |
| if (rc != 0 || s2 == NULL) { | |
| fprintf(stderr, "test_engine_correctness: multi-tier session_create rc=%d (likely the open KV-alloc OOM bug; SKIP_PASS)\n", rc); | |
| fprintf(stderr, "test_engine_correctness SKIP_PASS\n"); | |
| ds4_tokens_free(&prompt2); | |
| ds4_engine_close(e2); | |
| free(text_a); | |
| return 0; | |
| } | |
| rc = ds4_session_sync(s2, &prompt2, err, sizeof(err)); | |
| CHECKF(rc == 0, "multi-tier session_sync rc=%d err=%s", rc, err); | |
| int tokens_b[N_GEN_TOKENS]; | |
| int n_b = 0; | |
| CHECK(greedy_decode(e2, s2, tokens_b, &n_b) == 0, "multi-tier greedy decode"); | |
| char *text_b = decode_tokens(e2, tokens_b, n_b); | |
| CHECK(text_b != NULL, "decode multi-tier tokens to text"); | |
| fprintf(stderr, " multi-tier output (%d tokens): \"%s\"\n", n_b, text_b); | |
| fprintf(stderr, " multi-tier token IDs:"); | |
| for (int i = 0; i < n_b; i++) fprintf(stderr, " %d", tokens_b[i]); | |
| fprintf(stderr, "\n"); | |
| /* The actual correctness assertion: same number of tokens AND every | |
| * greedy token ID matches. A length mismatch (e.g., multi-tier hits | |
| * EOS earlier or later) is itself a divergence. */ | |
| int mismatch_at = -1; | |
| if (n_a != n_b) { | |
| mismatch_at = (n_a < n_b) ? n_a : n_b; | |
| } else { | |
| for (int i = 0; i < n_a; i++) { | |
| if (tokens_a[i] != tokens_b[i]) { mismatch_at = i; break; } | |
| } | |
| } | |
| ds4_session_free(s2); | |
| ds4_tokens_free(&prompt2); | |
| ds4_engine_close(e2); | |
| free(text_a); | |
| free(text_b); | |
| if (mismatch_at >= 0) { | |
| if (n_a != n_b) { | |
| fprintf(stderr, | |
| "FAIL: gen length differs (single=%d multi=%d)\n", | |
| n_a, n_b); | |
| } else { | |
| fprintf(stderr, | |
| "FAIL: token sequences diverge at index %d (single=%d multi=%d)\n", | |
| mismatch_at, tokens_a[mismatch_at], tokens_b[mismatch_at]); | |
| } | |
| return 1; | |
| } | |
| fprintf(stderr, "test_engine_correctness PASS (%d tokens identical incl EOS)\n", n_a); | |
| return 0; | |
| } | |