ds4fa / tests /test_metal_session_batch.c
julianmb's picture
Upload folder using huggingface_hub (part 4)
8d0b310 verified
Raw
History Blame Contribute Delete
15.8 kB
/* Model-backed correctness oracle for native Metal session batching.
*
* Run with:
* DS4_TEST_MODEL=/path/to/model.gguf make test-metal-session-batch
*/
#include "ds4.h"
#include "ds4_tp.h"
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_SESSION_COUNT 16
#define DECODE_STEPS 6
#define MIXED_SUFFIX_TOKENS 8
#define TEST_CTX 512
static const char *prompts[MAX_SESSION_COUNT] = {
"Write the integers from 1 to 80, separated by commas.",
"Explain a binary search using one compact worked example.",
"Give three concise reasons to test concurrent model sessions.",
"Write a four-line description of merge sort.",
"List five prime numbers and briefly define a prime number.",
"Explain the difference between a stack and a queue in two sentences.",
"Give a compact example of hexadecimal notation.",
"Describe one invariant of a binary search tree.",
};
static void fail(const char *what, int session, int step) {
fprintf(stderr, "FAIL: %s session=%d step=%d\n", what, session, step);
exit(1);
}
static ds4_tp_transport tp_transport_from_env(void) {
const char *value = getenv("DS4_TEST_TP_TRANSPORT");
if (!value || !value[0] || strcmp(value, "auto") == 0) {
return DS4_TP_TRANSPORT_AUTO;
}
if (strcmp(value, "tcp") == 0) return DS4_TP_TRANSPORT_TCP;
if (strcmp(value, "rdma") == 0) return DS4_TP_TRANSPORT_RDMA;
fprintf(stderr, "FAIL: invalid DS4_TEST_TP_TRANSPORT=%s\n", value);
exit(1);
}
static int tp_port_from_env(void) {
const char *value = getenv("DS4_TEST_TP_PORT");
if (!value || !value[0]) return 19452;
char *end = NULL;
long port = strtol(value, &end, 10);
if (end == value || *end != '\0' || port < 1 || port > 65535) {
fprintf(stderr, "FAIL: invalid DS4_TEST_TP_PORT=%s\n", value);
exit(1);
}
return (int)port;
}
static int session_count_from_env(void) {
const char *value = getenv("DS4_TEST_SESSION_COUNT");
if (!value || !value[0]) return 2;
char *end = NULL;
long count = strtol(value, &end, 10);
if (end == value || *end != '\0' || count < 2 ||
count > MAX_SESSION_COUNT) {
fprintf(stderr, "FAIL: invalid DS4_TEST_SESSION_COUNT=%s\n", value);
exit(1);
}
return (int)count;
}
static void archive_logits(ds4_session *session, float *dst, int vocab,
int session_id, int step) {
if (ds4_session_copy_logits(session, dst, vocab) != vocab) {
fail("copy logits", session_id, step);
}
}
static void compare_logits(ds4_session *session, const float *expected,
float *actual, int vocab, int expected_argmax,
int session_id, int step) {
archive_logits(session, actual, vocab, session_id, step);
float max_abs = 0.0f;
int different = 0;
int low_different = 0;
int high_different = 0;
for (int i = 0; i < vocab; i++) {
if (memcmp(&actual[i], &expected[i], sizeof(float)) != 0) {
different++;
if (i < vocab / 2) low_different++;
else high_different++;
}
float d = fabsf(actual[i] - expected[i]);
if (!isfinite(d)) d = FLT_MAX;
if (d > max_abs) max_abs = d;
}
int actual_argmax = ds4_session_argmax(session);
if (different != 0 || actual_argmax != expected_argmax) {
fprintf(stderr,
"FAIL: logits mismatch session=%d step=%d expected_top=%d "
"actual_top=%d differing=%d low=%d high=%d max_abs=%g\n",
session_id, step, expected_argmax, actual_argmax,
different, low_different, high_different, max_abs);
exit(1);
}
}
int main(void) {
const char *model = getenv("DS4_TEST_MODEL");
if (!model || !model[0]) {
fprintf(stderr, "FAIL: DS4_TEST_MODEL is not set\n");
return 1;
}
setenv("DS4_METAL_SESSION_BATCH_LOG", "1", 1);
const int session_count = session_count_from_env();
const char *tp_mode = getenv("DS4_TEST_TP_MODE");
const bool tp_leader = tp_mode && strcmp(tp_mode, "leader") == 0;
const bool tp_worker = tp_mode && strcmp(tp_mode, "worker") == 0;
if (tp_mode && tp_mode[0] && !tp_leader && !tp_worker) {
fprintf(stderr, "FAIL: invalid DS4_TEST_TP_MODE=%s\n", tp_mode);
return 1;
}
const int tp_port = tp_port_from_env();
ds4_engine_options opt = {
.model_path = model,
.backend = DS4_BACKEND_METAL,
.n_threads = 1,
.context_size = TEST_CTX,
};
if (tp_leader) {
opt.tp.role = DS4_TP_LEADER;
opt.tp.listen_host = getenv("DS4_TEST_TP_LISTEN_HOST");
if (!opt.tp.listen_host || !opt.tp.listen_host[0]) {
opt.tp.listen_host = "0.0.0.0";
}
opt.tp.listen_port = tp_port;
opt.tp.transport = tp_transport_from_env();
} else if (tp_worker) {
opt.tp.role = DS4_TP_WORKER;
opt.tp.leader_host = getenv("DS4_TEST_TP_LEADER_HOST");
if (!opt.tp.leader_host || !opt.tp.leader_host[0]) {
fprintf(stderr, "FAIL: DS4_TEST_TP_LEADER_HOST is required for worker mode\n");
return 1;
}
opt.tp.leader_port = tp_port;
opt.tp.transport = tp_transport_from_env();
}
ds4_engine *engine = NULL;
if (ds4_engine_open(&engine, &opt) != 0) fail("engine open", -1, -1);
if (tp_worker) {
const int worker_rc = ds4_tp_worker_run(engine, &opt.tp);
ds4_engine_close(engine);
return worker_rc;
}
ds4_tp *tp = NULL;
if (tp_leader) {
char tp_err[256] = "";
ds4_tp_identity identity = {
.gguf_bytes = ds4_engine_model_bytes(engine),
.model_id = (uint32_t)ds4_engine_model_id(engine),
.n_layer = (uint32_t)ds4_engine_layer_count(engine),
.n_embd = (uint32_t)ds4_engine_embd_dim(engine),
.n_vocab = (uint32_t)ds4_engine_vocab_size(engine),
.quant_bits = (uint32_t)ds4_engine_routed_quant_bits(engine),
.ctx_size = TEST_CTX,
};
ds4_engine_tp_gate_schedule(engine,
&identity.gate_slot_start,
&identity.gate_slot_step,
&identity.gates_per_token);
if (!ds4_tp_create(&tp, &opt.tp, &identity,
tp_err, sizeof(tp_err)) ||
!ds4_engine_tp_bind(engine, tp, tp_err, sizeof(tp_err))) {
fprintf(stderr, "FAIL: TP leader setup: %s\n", tp_err);
return 1;
}
}
ds4_tokens prompt[MAX_SESSION_COUNT] = {0};
ds4_session *batched[MAX_SESSION_COUNT] = {0};
char err[256] = {0};
for (int i = 0; i < session_count; i++) {
ds4_encode_chat_prompt(engine, NULL, prompts[i % 8], DS4_THINK_NONE,
&prompt[i]);
if (ds4_session_create(&batched[i], engine, TEST_CTX) != 0) {
fail("session create", i, -1);
}
if (ds4_session_sync(batched[i], &prompt[i], err, sizeof(err)) != 0) {
fprintf(stderr, "FAIL: prefill session=%d: %s\n", i, err);
return 1;
}
}
if (tp_leader && getenv("DS4_TEST_TP_DISCONNECT")) {
ds4_decode_item items[MAX_SESSION_COUNT];
for (int i = 0; i < session_count; i++) {
items[i].session = batched[i];
items[i].token = ds4_session_argmax(batched[i]);
}
fprintf(stderr, "TP_DISCONNECT_READY\n");
fflush(stderr);
usleep(1000 * 1000);
err[0] = '\0';
if (ds4_sessions_eval_batch(items, session_count,
err, sizeof(err)) == 0) {
fail("disconnect batch unexpectedly succeeded", -1, -1);
}
for (int i = 0; i < session_count; i++) {
if (ds4_session_pos(batched[i]) != 0) {
fail("disconnect did not invalidate checkpoint", i, -1);
}
ds4_session_free(batched[i]);
ds4_tokens_free(&prompt[i]);
}
ds4_engine_close(engine);
ds4_tp_free(tp);
fprintf(stderr,
"test_metal_session_batch DISCONNECT PASS invalidated=%d err=%s\n",
session_count, err[0] ? err : "unknown");
return 0;
}
const int vocab = ds4_engine_vocab_size(engine);
const size_t frontier_count =
(size_t)session_count * (DECODE_STEPS + 1u);
float *expected = malloc(frontier_count * (size_t)vocab * sizeof(float));
float *actual = malloc((size_t)vocab * sizeof(float));
int *argmax = malloc(frontier_count * sizeof(int));
int generated[MAX_SESSION_COUNT][DECODE_STEPS];
if (!expected || !actual || !argmax) fail("oracle allocation", -1, -1);
#define FRONTIER(step_, session_) \
((size_t)(step_) * (size_t)session_count + (size_t)(session_))
for (int i = 0; i < session_count; i++) {
size_t f = FRONTIER(0, i);
archive_logits(batched[i], expected + f * (size_t)vocab,
vocab, i, 0);
argmax[f] = ds4_session_argmax(batched[i]);
}
for (int step = 0; step < DECODE_STEPS; step++) {
ds4_decode_item items[MAX_SESSION_COUNT];
for (int row = 0; row < session_count; row++) {
int i = (step & 1) ? session_count - 1 - row : row;
int token = ds4_session_argmax(batched[i]);
generated[i][step] = token;
items[row].session = batched[i];
items[row].token = token;
}
if (ds4_sessions_eval_batch(items, session_count,
err, sizeof(err)) != 0) {
fprintf(stderr, "FAIL: batch step=%d: %s\n", step, err);
return 1;
}
for (int i = 0; i < session_count; i++) {
size_t f = FRONTIER(step + 1, i);
archive_logits(batched[i], expected + f * (size_t)vocab,
vocab, i, step + 1);
argmax[f] = ds4_session_argmax(batched[i]);
}
}
for (int i = 0; i < session_count; i++) {
ds4_session_free(batched[i]);
}
ds4_tokens mixed_prompt = {0};
ds4_tokens suffix = {0};
ds4_tokens_copy(&mixed_prompt, &prompt[0]);
ds4_tokenize_text(engine,
" Continue with a concise verification example and conclusion.",
&suffix);
if (suffix.len < MIXED_SUFFIX_TOKENS) {
fail("mixed suffix tokenization", -1, -1);
}
for (int i = 0; i < MIXED_SUFFIX_TOKENS; i++) {
ds4_tokens_push(&mixed_prompt, suffix.v[i]);
}
ds4_session *mixed_prefill = NULL;
ds4_session *mixed_decode[MAX_SESSION_COUNT] = {0};
float *mixed_expected = malloc(
(size_t)(session_count + 1) * (size_t)vocab * sizeof(float));
int mixed_argmax[MAX_SESSION_COUNT + 1];
if (!mixed_expected) fail("mixed oracle allocation", -1, -1);
if (ds4_session_create(&mixed_prefill, engine, TEST_CTX) != 0) {
fail("mixed prefill create", -1, -1);
}
if (ds4_session_sync(mixed_prefill, &prompt[0], err, sizeof(err)) != 0) {
fprintf(stderr, "FAIL: mixed base prefill: %s\n", err);
return 1;
}
ds4_decode_item mixed_items[MAX_SESSION_COUNT];
for (int i = 0; i < session_count; i++) {
if (ds4_session_create(&mixed_decode[i], engine, TEST_CTX) != 0) {
fail("mixed decode create", i, -1);
}
if (ds4_session_sync(mixed_decode[i], &prompt[i], err, sizeof(err)) != 0) {
fprintf(stderr, "FAIL: mixed decode prefill session=%d: %s\n", i, err);
return 1;
}
mixed_items[i].session = mixed_decode[i];
mixed_items[i].token = ds4_session_argmax(mixed_decode[i]);
}
if (ds4_sessions_eval_batch_with_prefill(
mixed_items, session_count, mixed_prefill, &mixed_prompt,
err, sizeof(err)) != 0) {
fprintf(stderr, "FAIL: Metal mixed batch: %s\n", err);
return 1;
}
archive_logits(mixed_prefill, mixed_expected, vocab, -1, -1);
mixed_argmax[0] = ds4_session_argmax(mixed_prefill);
for (int i = 0; i < session_count; i++) {
archive_logits(mixed_decode[i],
mixed_expected + (size_t)(i + 1) * (size_t)vocab,
vocab, i, -1);
mixed_argmax[i + 1] = ds4_session_argmax(mixed_decode[i]);
}
ds4_session *mixed_control = NULL;
if (ds4_session_create(&mixed_control, engine, TEST_CTX) != 0) {
fail("mixed prefill control create", -1, -1);
}
if (ds4_session_sync(mixed_control, &prompt[0], err, sizeof(err)) != 0 ||
ds4_session_sync(mixed_control, &mixed_prompt, err, sizeof(err)) != 0) {
fprintf(stderr, "FAIL: mixed prefill control: %s\n", err);
return 1;
}
compare_logits(mixed_control, mixed_expected, actual, vocab,
mixed_argmax[0],
-1, -1);
if (ds4_session_pos(mixed_prefill) != mixed_prompt.len ||
ds4_session_pos(mixed_control) != mixed_prompt.len) {
fail("mixed prefill checkpoint", -1, -1);
}
ds4_session_free(mixed_control);
for (int i = 0; i < session_count; i++) {
mixed_control = NULL;
if (ds4_session_create(&mixed_control, engine, TEST_CTX) != 0) {
fail("mixed decode control create", i, -1);
}
if (ds4_session_sync(mixed_control, &prompt[i], err, sizeof(err)) != 0 ||
ds4_session_eval(mixed_control, mixed_items[i].token,
err, sizeof(err)) != 0) {
fprintf(stderr, "FAIL: mixed decode control session=%d: %s\n", i, err);
return 1;
}
compare_logits(mixed_control,
mixed_expected + (size_t)(i + 1) * (size_t)vocab,
actual, vocab, mixed_argmax[i + 1], i, -1);
if (ds4_session_pos(mixed_decode[i]) != prompt[i].len + 1 ||
ds4_session_pos(mixed_control) != prompt[i].len + 1) {
fail("mixed decode checkpoint", i, -1);
}
ds4_session_free(mixed_control);
ds4_session_free(mixed_decode[i]);
}
ds4_session_free(mixed_prefill);
free(mixed_expected);
ds4_tokens_free(&suffix);
ds4_tokens_free(&mixed_prompt);
for (int i = 0; i < session_count; i++) {
ds4_session *control = NULL;
if (ds4_session_create(&control, engine, TEST_CTX) != 0) {
fail("control create", i, -1);
}
if (ds4_session_sync(control, &prompt[i], err, sizeof(err)) != 0) {
fprintf(stderr, "FAIL: control prefill session=%d: %s\n", i, err);
return 1;
}
for (int step = 0; step <= DECODE_STEPS; step++) {
size_t f = FRONTIER(step, i);
compare_logits(control, expected + f * (size_t)vocab,
actual, vocab, argmax[f], i, step);
if (step < DECODE_STEPS &&
ds4_session_eval(control, generated[i][step],
err, sizeof(err)) != 0) {
fprintf(stderr,
"FAIL: control eval session=%d step=%d: %s\n",
i, step, err);
return 1;
}
}
ds4_session_free(control);
ds4_tokens_free(&prompt[i]);
}
free(argmax);
free(actual);
free(expected);
if (tp) (void)ds4_tp_send_stop(tp);
ds4_engine_close(engine);
ds4_tp_free(tp);
fprintf(stderr,
"test_metal_session_batch PASS sessions=%d steps=%d mixed_suffix=%d exact_logits=1\n",
session_count, DECODE_STEPS, MIXED_SUFFIX_TOKENS);
return 0;
#undef FRONTIER
}