Download src/ds4_bench.c from julianmb/ds4fa: direct link, hf CLI and curl.
- Browser
- Download file 31 kB
-
https://huggingface.co/julianmb/ds4fa/resolve/main/src/ds4_bench.c
- Command line
-
hf download hf://julianmb/ds4fa/src/ds4_bench.c
-
curl -L -o ds4_bench.c https://huggingface.co/julianmb/ds4fa/resolve/main/src/ds4_bench.c
31 kB
| /* Purpose-built throughput benchmark. | |
| * | |
| * The benchmark walks one fixed token sequence to configurable context | |
| * frontiers, measuring only the newest prefill interval at each frontier. It | |
| * then snapshots the live session in memory when the payload is small enough, | |
| * performs a fixed greedy decode run without allowing EOS, restores the | |
| * snapshot or replays the prefix, and continues to the next frontier. Snapshot | |
| * save/restore time is intentionally outside both timing windows. | |
| */ | |
| typedef struct { | |
| const char *model_path; | |
| const char *prompt_path; | |
| const char *chat_prompt_path; | |
| const char *system; | |
| const char *csv_path; | |
| const char *expert_profile_path; | |
| const char *gpu_vram_arg; | |
| const char *gpu_devices_arg; | |
| ds4_backend backend; | |
| int threads; | |
| int ctx_start; | |
| int ctx_max; | |
| int ctx_alloc; | |
| int step_incr; | |
| int gen_tokens; | |
| int power_percent; | |
| uint32_t prefill_chunk; | |
| uint32_t ssd_streaming_cache_experts; | |
| uint64_t ssd_streaming_cache_bytes; | |
| uint32_t ssd_streaming_full_layers; | |
| uint32_t ssd_streaming_preload_experts; | |
| uint64_t simulate_used_memory_bytes; | |
| double step_mul; | |
| const char *dump_frontier_logits_dir; | |
| ds4_dist_options dist; | |
| bool warm_weights; | |
| bool quality; | |
| bool ssd_streaming; | |
| bool ssd_streaming_cold; | |
| bool ssd_streaming_full_layers_set; | |
| bool cuda_tensor_parallel; | |
| bool show_output; | |
| } bench_config; | |
| static double bench_now_sec(void) { | |
| struct timespec ts; | |
| clock_gettime(CLOCK_MONOTONIC, &ts); | |
| return (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0; | |
| } | |
| static uint64_t bench_snapshot_max_bytes(void) { | |
| const char *env = getenv("DS4_BENCH_SNAPSHOT_MAX_BYTES"); | |
| if (!env || env[0] == '\0') return DS4_BENCH_DEFAULT_SNAPSHOT_MAX_BYTES; | |
| if (!strcmp(env, "unlimited") || !strcmp(env, "UNLIMITED") || | |
| !strcmp(env, "inf") || !strcmp(env, "INF")) { | |
| return UINT64_MAX; | |
| } | |
| char *end = NULL; | |
| unsigned long long v = strtoull(env, &end, 10); | |
| if (env[0] == '\0' || !end || *end != '\0') { | |
| fprintf(stderr, | |
| "ds4-bench: invalid DS4_BENCH_SNAPSHOT_MAX_BYTES=%s; using default %llu\n", | |
| env, | |
| (unsigned long long)DS4_BENCH_DEFAULT_SNAPSHOT_MAX_BYTES); | |
| return DS4_BENCH_DEFAULT_SNAPSHOT_MAX_BYTES; | |
| } | |
| return (uint64_t)v; | |
| } | |
| static double bytes_to_gib(uint64_t bytes) { | |
| return (double)bytes / (1024.0 * 1024.0 * 1024.0); | |
| } | |
| static void usage(FILE *fp, const char *topic) { | |
| ds4_help_print(fp, DS4_HELP_BENCH, topic); | |
| } | |
| static int parse_int(const char *s, const char *opt) { | |
| char *end = NULL; | |
| long v = strtol(s, &end, 10); | |
| if (s[0] == '\0' || *end != '\0' || v <= 0 || v > INT_MAX) { | |
| fprintf(stderr, "ds4-bench: invalid value for %s: %s\n", opt, s); | |
| exit(2); | |
| } | |
| return (int)v; | |
| } | |
| static int parse_nonnegative_int(const char *s, const char *opt) { | |
| char *end = NULL; | |
| long v = strtol(s, &end, 10); | |
| if (s[0] == '\0' || *end != '\0' || v < 0 || v > INT_MAX) { | |
| fprintf(stderr, "ds4-bench: invalid value for %s: %s\n", opt, s); | |
| exit(2); | |
| } | |
| return (int)v; | |
| } | |
| static double parse_double_arg(const char *s, const char *opt) { | |
| char *end = NULL; | |
| double v = strtod(s, &end); | |
| if (s[0] == '\0' || *end != '\0' || !isfinite(v)) { | |
| fprintf(stderr, "ds4-bench: invalid value for %s: %s\n", opt, s); | |
| exit(2); | |
| } | |
| return v; | |
| } | |
| static const char *need_arg(int *i, int argc, char **argv, const char *opt) { | |
| if (*i + 1 >= argc) { | |
| fprintf(stderr, "ds4-bench: %s requires an argument\n", opt); | |
| exit(2); | |
| } | |
| return argv[++*i]; | |
| } | |
| static ds4_backend parse_backend(const char *s, const char *opt) { | |
| if (!strcmp(s, "metal")) return DS4_BACKEND_METAL; | |
| if (!strcmp(s, "rocm")) return DS4_BACKEND_CUDA; | |
| if (!strcmp(s, "cuda")) return DS4_BACKEND_CUDA; | |
| if (!strcmp(s, "cpu")) return DS4_BACKEND_CPU; | |
| fprintf(stderr, "ds4-bench: invalid value for %s: %s\n", opt, s); | |
| fprintf(stderr, "ds4-bench: valid backends are: metal, rocm, cpu\n"); | |
| fprintf(stderr, "ds4-bench: valid backends are: metal, cuda, cpu\n"); | |
| exit(2); | |
| } | |
| static ds4_backend default_backend(void) { | |
| return DS4_BACKEND_CPU; | |
| return DS4_BACKEND_METAL; | |
| return DS4_BACKEND_CUDA; | |
| } | |
| static char *read_file(const char *path) { | |
| FILE *fp = fopen(path, "rb"); | |
| if (!fp) { | |
| fprintf(stderr, "ds4-bench: failed to open %s: %s\n", path, strerror(errno)); | |
| exit(1); | |
| } | |
| if (fseek(fp, 0, SEEK_END) != 0) { | |
| fprintf(stderr, "ds4-bench: failed to seek %s\n", path); | |
| fclose(fp); | |
| exit(1); | |
| } | |
| long n = ftell(fp); | |
| if (n < 0) { | |
| fprintf(stderr, "ds4-bench: failed to tell %s\n", path); | |
| fclose(fp); | |
| exit(1); | |
| } | |
| if (fseek(fp, 0, SEEK_SET) != 0) { | |
| fprintf(stderr, "ds4-bench: failed to rewind %s\n", path); | |
| fclose(fp); | |
| exit(1); | |
| } | |
| char *buf = malloc((size_t)n + 1); | |
| if (!buf) { | |
| fprintf(stderr, "ds4-bench: out of memory reading %s\n", path); | |
| fclose(fp); | |
| exit(1); | |
| } | |
| if (fread(buf, 1, (size_t)n, fp) != (size_t)n) { | |
| fprintf(stderr, "ds4-bench: failed to read %s\n", path); | |
| free(buf); | |
| fclose(fp); | |
| exit(1); | |
| } | |
| fclose(fp); | |
| buf[n] = '\0'; | |
| return buf; | |
| } | |
| static bench_config parse_options(int argc, char **argv) { | |
| bench_config c = { | |
| .model_path = "ds4flash.gguf", | |
| .system = "You are a helpful assistant.", | |
| .backend = default_backend(), | |
| .ctx_start = 2048, | |
| .ctx_max = 32768, | |
| .step_incr = 2048, | |
| .gen_tokens = 128, | |
| .step_mul = 1.0, | |
| }; | |
| for (int i = 1; i < argc; i++) { | |
| const char *arg = argv[i]; | |
| if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) { | |
| const char *topic = (i + 1 < argc && argv[i + 1][0] != '-') ? | |
| argv[i + 1] : NULL; | |
| usage(stdout, topic); | |
| exit(0); | |
| } | |
| char dist_parse_err[256] = {0}; | |
| ds4_dist_cli_parse_result dist_parse = | |
| ds4_dist_parse_cli_arg(arg, | |
| &i, | |
| argc, | |
| argv, | |
| &c.dist, | |
| dist_parse_err, | |
| sizeof(dist_parse_err)); | |
| if (dist_parse == DS4_DIST_CLI_ERROR) { | |
| fprintf(stderr, | |
| "ds4-bench: %s\n", | |
| dist_parse_err[0] ? dist_parse_err : "invalid distributed option"); | |
| exit(2); | |
| } | |
| if (dist_parse == DS4_DIST_CLI_MATCHED) continue; | |
| if (!strcmp(arg, "-m") || !strcmp(arg, "--model")) { | |
| c.model_path = need_arg(&i, argc, argv, arg); | |
| } else if (!strcmp(arg, "--prompt-file")) { | |
| c.prompt_path = need_arg(&i, argc, argv, arg); | |
| } else if (!strcmp(arg, "--chat-prompt-file")) { | |
| c.chat_prompt_path = need_arg(&i, argc, argv, arg); | |
| } else if (!strcmp(arg, "-sys") || !strcmp(arg, "--system")) { | |
| c.system = need_arg(&i, argc, argv, arg); | |
| } else if (!strcmp(arg, "--ctx-start")) { | |
| c.ctx_start = parse_int(need_arg(&i, argc, argv, arg), arg); | |
| } else if (!strcmp(arg, "--ctx-max")) { | |
| c.ctx_max = parse_int(need_arg(&i, argc, argv, arg), arg); | |
| } else if (!strcmp(arg, "--ctx-alloc")) { | |
| c.ctx_alloc = parse_int(need_arg(&i, argc, argv, arg), arg); | |
| } else if (!strcmp(arg, "--step-incr")) { | |
| c.step_incr = parse_int(need_arg(&i, argc, argv, arg), arg); | |
| } else if (!strcmp(arg, "--step-mul")) { | |
| c.step_mul = parse_double_arg(need_arg(&i, argc, argv, arg), arg); | |
| } else if (!strcmp(arg, "--gen-tokens") || !strcmp(arg, "--tokens") || !strcmp(arg, "-n")) { | |
| c.gen_tokens = parse_nonnegative_int(need_arg(&i, argc, argv, arg), arg); | |
| } else if (!strcmp(arg, "--csv")) { | |
| c.csv_path = need_arg(&i, argc, argv, arg); | |
| } else if (!strcmp(arg, "--dump-frontier-logits-dir")) { | |
| c.dump_frontier_logits_dir = need_arg(&i, argc, argv, arg); | |
| } else if (!strcmp(arg, "--expert-profile")) { | |
| c.expert_profile_path = need_arg(&i, argc, argv, arg); | |
| } else if (!strcmp(arg, "-t") || !strcmp(arg, "--threads")) { | |
| c.threads = parse_int(need_arg(&i, argc, argv, arg), arg); | |
| } else if (!strcmp(arg, "--backend")) { | |
| c.backend = parse_backend(need_arg(&i, argc, argv, arg), arg); | |
| } else if (!strcmp(arg, "--metal")) { | |
| c.backend = DS4_BACKEND_METAL; | |
| } else if (!strcmp(arg, "--rocm")) { | |
| c.backend = DS4_BACKEND_CUDA; | |
| } else if (!strcmp(arg, "--cuda")) { | |
| c.backend = DS4_BACKEND_CUDA; | |
| } else if (!strcmp(arg, "--gpu-vram")) { | |
| c.gpu_vram_arg = need_arg(&i, argc, argv, arg); | |
| } else if (!strcmp(arg, "--gpu-devices")) { | |
| c.gpu_devices_arg = need_arg(&i, argc, argv, arg); | |
| } else if (!strcmp(arg, "--cuda-tensor-parallel")) { | |
| c.cuda_tensor_parallel = true; | |
| } else if (!strcmp(arg, "--cpu")) { | |
| c.backend = DS4_BACKEND_CPU; | |
| } else if (!strcmp(arg, "--quality")) { | |
| c.quality = true; | |
| } else if (!strcmp(arg, "--ssd-streaming")) { | |
| c.ssd_streaming = true; | |
| } else if (!strcmp(arg, "--ssd-streaming-cold")) { | |
| c.ssd_streaming_cold = true; | |
| } else if (!strcmp(arg, "--ssd-streaming-cache-experts")) { | |
| uint32_t experts = 0; | |
| uint64_t bytes = 0; | |
| if (!ds4_parse_streaming_cache_experts_arg( | |
| need_arg(&i, argc, argv, arg), &experts, &bytes)) { | |
| fprintf(stderr, | |
| "ds4-bench: --ssd-streaming-cache-experts must be a positive count or <number>GB\n"); | |
| exit(2); | |
| } | |
| c.ssd_streaming_cache_experts = experts; | |
| c.ssd_streaming_cache_bytes = bytes; | |
| } else if (!strcmp(arg, "--ssd-streaming-full-layers")) { | |
| int v = parse_nonnegative_int(need_arg(&i, argc, argv, arg), arg); | |
| c.ssd_streaming_full_layers = (uint32_t)v; | |
| c.ssd_streaming_full_layers_set = true; | |
| } else if (!strcmp(arg, "--ssd-streaming-preload-experts")) { | |
| int v = parse_int(need_arg(&i, argc, argv, arg), arg); | |
| if (v <= 0) { | |
| fprintf(stderr, "ds4-bench: --ssd-streaming-preload-experts must be positive\n"); | |
| exit(2); | |
| } | |
| c.ssd_streaming_preload_experts = (uint32_t)v; | |
| } else if (!strcmp(arg, "--simulate-used-memory")) { | |
| if (!ds4_parse_gib_arg(need_arg(&i, argc, argv, arg), | |
| &c.simulate_used_memory_bytes)) { | |
| fprintf(stderr, | |
| "ds4-bench: --simulate-used-memory must be a positive GiB value, e.g. 64GB\n"); | |
| exit(2); | |
| } | |
| } else if (!strcmp(arg, "--prefill-chunk")) { | |
| c.prefill_chunk = (uint32_t)parse_int(need_arg(&i, argc, argv, arg), arg); | |
| } else if (!strcmp(arg, "--power")) { | |
| c.power_percent = parse_int(need_arg(&i, argc, argv, arg), arg); | |
| if (c.power_percent < 1 || c.power_percent > 100) { | |
| fprintf(stderr, "ds4-bench: --power must be between 1 and 100\n"); | |
| exit(2); | |
| } | |
| } else if (!strcmp(arg, "--warm-weights")) { | |
| c.warm_weights = true; | |
| } else if (!strcmp(arg, "--show-output")) { | |
| c.show_output = true; | |
| } else { | |
| fprintf(stderr, "ds4-bench: unknown option: %s\n", arg); | |
| usage(stderr, NULL); | |
| exit(2); | |
| } | |
| } | |
| if (!!c.prompt_path == !!c.chat_prompt_path) { | |
| fprintf(stderr, "ds4-bench: specify exactly one of --prompt-file or --chat-prompt-file\n"); | |
| exit(2); | |
| } | |
| if (c.ctx_start > c.ctx_max) { | |
| fprintf(stderr, "ds4-bench: --ctx-start must be <= --ctx-max\n"); | |
| exit(2); | |
| } | |
| if (c.step_mul < 1.0) { | |
| fprintf(stderr, "ds4-bench: --step-mul must be >= 1\n"); | |
| exit(2); | |
| } | |
| if (c.step_mul == 1.0 && c.step_incr <= 0) { | |
| fprintf(stderr, "ds4-bench: --step-incr must be positive when --step-mul is 1\n"); | |
| exit(2); | |
| } | |
| if (c.ctx_max > INT_MAX - c.gen_tokens - 1) { | |
| fprintf(stderr, "ds4-bench: requested context is too large\n"); | |
| exit(2); | |
| } | |
| if (c.ctx_alloc == 0) c.ctx_alloc = c.ctx_max + c.gen_tokens + 1; | |
| if (c.ctx_alloc <= c.ctx_max + c.gen_tokens) { | |
| fprintf(stderr, "ds4-bench: --ctx-alloc must be greater than ctx-max + gen-tokens\n"); | |
| exit(2); | |
| } | |
| char dist_err[256]; | |
| if (ds4_dist_prepare_engine_options(&c.dist, NULL, dist_err, sizeof(dist_err)) != 0) { | |
| fprintf(stderr, "ds4-bench: %s\n", dist_err); | |
| exit(2); | |
| } | |
| if (c.dist.role == DS4_DISTRIBUTED_WORKER) { | |
| fprintf(stderr, "ds4-bench: --role worker is a serving mode; start workers with ./ds4\n"); | |
| exit(2); | |
| } | |
| return c; | |
| } | |
| static void json_write_string(FILE *fp, const char *s) { | |
| fputc('"', fp); | |
| if (s) { | |
| for (const unsigned char *p = (const unsigned char *)s; *p; p++) { | |
| switch (*p) { | |
| case '"': fputs("\\\"", fp); break; | |
| case '\\': fputs("\\\\", fp); break; | |
| case '\b': fputs("\\b", fp); break; | |
| case '\f': fputs("\\f", fp); break; | |
| case '\n': fputs("\\n", fp); break; | |
| case '\r': fputs("\\r", fp); break; | |
| case '\t': fputs("\\t", fp); break; | |
| default: | |
| if (*p < 0x20) fprintf(fp, "\\u%04x", (unsigned)*p); | |
| else fputc((char)*p, fp); | |
| break; | |
| } | |
| } | |
| } | |
| fputc('"', fp); | |
| } | |
| static int write_frontier_logits_json( | |
| const bench_config *cfg, | |
| ds4_engine *engine, | |
| ds4_session *session, | |
| int frontier, | |
| int previous) { | |
| if (!cfg->dump_frontier_logits_dir) return 0; | |
| const int vocab = ds4_engine_vocab_size(engine); | |
| float *logits = malloc((size_t)vocab * sizeof(logits[0])); | |
| if (!logits) { | |
| fprintf(stderr, "ds4-bench: out of memory copying frontier logits\n"); | |
| return 1; | |
| } | |
| if (ds4_session_copy_logits(session, logits, vocab) != vocab) { | |
| fprintf(stderr, "ds4-bench: failed to copy frontier logits at %d\n", frontier); | |
| free(logits); | |
| return 1; | |
| } | |
| char path[PATH_MAX]; | |
| const int n = snprintf(path, | |
| sizeof(path), | |
| "%s/frontier_%06d.logits.json", | |
| cfg->dump_frontier_logits_dir, | |
| frontier); | |
| if (n <= 0 || (size_t)n >= sizeof(path)) { | |
| fprintf(stderr, "ds4-bench: frontier logits path is too long\n"); | |
| free(logits); | |
| return 1; | |
| } | |
| FILE *fp = fopen(path, "wb"); | |
| if (!fp) { | |
| fprintf(stderr, "ds4-bench: failed to open %s: %s\n", path, strerror(errno)); | |
| free(logits); | |
| return 1; | |
| } | |
| const int argmax = ds4_session_argmax(session); | |
| fprintf(fp, "{\n \"source\":\"ds4-bench\",\n \"model\":"); | |
| json_write_string(fp, cfg->model_path); | |
| fprintf(fp, | |
| ",\n \"backend\":\"%s\",\n \"quality\":%s,\n" | |
| " \"quant_bits\":%d,\n \"prompt_tokens\":%d,\n" | |
| " \"frontier_tokens\":%d,\n \"prefill_tokens\":%d,\n" | |
| " \"ctx\":%d,\n \"vocab\":%d,\n" | |
| " \"argmax_id\":%d,\n \"argmax_logit\":%.9g,\n \"logits\":[", | |
| ds4_backend_name(cfg->backend), | |
| cfg->quality ? "true" : "false", | |
| ds4_engine_routed_quant_bits(engine), | |
| frontier, | |
| frontier, | |
| frontier - previous, | |
| cfg->ctx_alloc, | |
| vocab, | |
| argmax, | |
| logits[argmax]); | |
| for (int i = 0; i < vocab; i++) { | |
| if (i) fputc(',', fp); | |
| if ((i % 8) == 0) fputs("\n ", fp); | |
| if (isfinite(logits[i])) fprintf(fp, "%.9g", logits[i]); | |
| else fputs("null", fp); | |
| } | |
| fputs("\n ]\n}\n", fp); | |
| if (fclose(fp) != 0) { | |
| fprintf(stderr, "ds4-bench: failed to close %s\n", path); | |
| free(logits); | |
| return 1; | |
| } | |
| free(logits); | |
| return 0; | |
| } | |
| static int next_frontier(const bench_config *c, int cur) { | |
| if (cur >= c->ctx_max) return c->ctx_max; | |
| int next; | |
| if (c->step_mul == 1.0) { | |
| if (cur > INT_MAX - c->step_incr) next = c->ctx_max; | |
| else next = cur + c->step_incr; | |
| } else { | |
| const double v = ceil((double)cur * c->step_mul); | |
| next = v > (double)INT_MAX ? c->ctx_max : (int)v; | |
| if (next <= cur) next = cur + 1; | |
| } | |
| if (next > c->ctx_max) next = c->ctx_max; | |
| return next; | |
| } | |
| static void log_context_memory(ds4_backend backend, | |
| int ctx_size, | |
| uint32_t prefill_chunk, | |
| bool ssd_streaming) { | |
| ds4_context_memory m = | |
| ds4_context_memory_estimate_with_prefill_mode(backend, | |
| ctx_size, | |
| prefill_chunk, | |
| ssd_streaming); | |
| fprintf(stderr, | |
| "ds4-bench: context buffers %.2f MiB (ctx=%d, backend=%s, prefill_chunk=%u, raw_kv_rows=%u, compressed_kv_rows=%u)\n", | |
| (double)m.total_bytes / (1024.0 * 1024.0), | |
| ctx_size, | |
| ds4_backend_name(backend), | |
| m.prefill_cap, | |
| m.raw_cap, | |
| m.comp_cap); | |
| } | |
| static int wait_distributed_route(ds4_session *session) { | |
| char err[256] = {0}; | |
| char last[256] = {0}; | |
| unsigned ticks = 0; | |
| const struct timespec delay = {0, 250000000L}; | |
| for (;;) { | |
| int ready = ds4_session_distributed_route_ready(session, err, sizeof(err)); | |
| if (ready > 0) { | |
| if (ticks) fprintf(stderr, "ds4-bench: distributed route ready\n"); | |
| return 0; | |
| } | |
| if (ready < 0) { | |
| fprintf(stderr, | |
| "ds4-bench: distributed route readiness failed: %s\n", | |
| err[0] ? err : "unknown error"); | |
| return 1; | |
| } | |
| const char *why = err[0] ? err : "route incomplete"; | |
| if (strcmp(last, why) != 0 || (ticks % 20u) == 0) { | |
| fprintf(stderr, "ds4-bench: waiting for distributed route: %s\n", why); | |
| snprintf(last, sizeof(last), "%s", why); | |
| } | |
| nanosleep(&delay, NULL); | |
| ticks++; | |
| } | |
| } | |
| static void maybe_warn_distributed_step_shape(const bench_config *cfg, ds4_session *session) { | |
| if (!cfg || !session || cfg->dist.role != DS4_DISTRIBUTED_COORDINATOR) return; | |
| uint32_t chunk = cfg->dist.prefill_chunk; | |
| if (chunk == 0) { | |
| const int cap = ds4_session_prefill_cap(session); | |
| if (cap > 0) chunk = (uint32_t)cap; | |
| } | |
| if (chunk == 0) return; | |
| if (cfg->step_mul == 1.0 && | |
| cfg->step_incr > 0 && | |
| (uint32_t)cfg->step_incr < chunk && | |
| cfg->ctx_start < cfg->ctx_max) | |
| { | |
| fprintf(stderr, | |
| "ds4-bench: note: --step-incr=%d is smaller than distributed prefill chunk %u; " | |
| "suffix rows will not show multi-chunk pipeline overlap\n", | |
| cfg->step_incr, | |
| chunk); | |
| } | |
| } | |
| int main(int argc, char **argv) { | |
| bench_config cfg = parse_options(argc, argv); | |
| /* Hint the packer at the largest ctx this bench run will exercise | |
| * so per-layer KV bytes are priced for the real session size, not | |
| * a stale 4096 default. Single-tier and CPU paths ignore this. */ | |
| int placement_ctx_hint = cfg.ctx_max; | |
| if (cfg.ctx_alloc > placement_ctx_hint) placement_ctx_hint = cfg.ctx_alloc; | |
| ds4_gpu_config gpu_cfg = {0}; | |
| bool skip_cuda = false; | |
| const bool have_gpu_config = cfg.gpu_vram_arg || cfg.gpu_devices_arg; | |
| if (have_gpu_config) { | |
| char gpu_err[256]; | |
| if (parse_gpu_vram_arg(cfg.gpu_vram_arg, cfg.gpu_devices_arg, | |
| &gpu_cfg, &skip_cuda, | |
| gpu_err, sizeof(gpu_err)) != 0) { | |
| fprintf(stderr, "ds4-bench: %s\n", gpu_err); | |
| return 2; | |
| } | |
| cfg.backend = skip_cuda ? DS4_BACKEND_CPU : DS4_BACKEND_CUDA; | |
| } | |
| ds4_engine_options opt = { | |
| .model_path = cfg.model_path, | |
| .backend = cfg.backend, | |
| .n_threads = cfg.threads, | |
| .context_size = cfg.ctx_alloc, | |
| .prefill_chunk = cfg.prefill_chunk, | |
| .ssd_streaming_cache_experts = cfg.ssd_streaming_cache_experts, | |
| .ssd_streaming_cache_bytes = cfg.ssd_streaming_cache_bytes, | |
| .ssd_streaming_full_layers = cfg.ssd_streaming_full_layers, | |
| .ssd_streaming_preload_experts = cfg.ssd_streaming_preload_experts, | |
| .simulate_used_memory_bytes = cfg.simulate_used_memory_bytes, | |
| .power_percent = cfg.power_percent, | |
| .warm_weights = cfg.warm_weights, | |
| .quality = cfg.quality, | |
| .cuda_tensor_parallel = cfg.cuda_tensor_parallel, | |
| .ssd_streaming = cfg.ssd_streaming, | |
| .ssd_streaming_cold = cfg.ssd_streaming_cold, | |
| .ssd_streaming_full_layers_set = cfg.ssd_streaming_full_layers_set, | |
| .expert_profile_path = cfg.expert_profile_path, | |
| .distributed = cfg.dist, | |
| }; | |
| char dist_err[256]; | |
| if (ds4_dist_prepare_engine_options(&cfg.dist, &opt, dist_err, sizeof(dist_err)) != 0) { | |
| fprintf(stderr, "ds4-bench: %s\n", dist_err); | |
| return 2; | |
| } | |
| ds4_engine *engine = NULL; | |
| if (have_gpu_config && !skip_cuda) { | |
| const bool was_auto = | |
| (cfg.gpu_vram_arg && !strcmp(cfg.gpu_vram_arg, "auto")) || | |
| (!cfg.gpu_vram_arg && cfg.gpu_devices_arg); | |
| char layout[256]; | |
| if (format_gpu_layout_line(&gpu_cfg, was_auto, | |
| layout, sizeof(layout)) > 0) { | |
| fprintf(stdout, "%s\n", layout); | |
| fflush(stdout); | |
| } | |
| if (ds4_engine_create_with_gpu_config( | |
| &engine, &opt, &gpu_cfg) != 0) return 1; | |
| } else if (ds4_engine_open(&engine, &opt) != 0) { | |
| return 1; | |
| } | |
| log_context_memory(opt.backend, | |
| cfg.ctx_alloc, | |
| ds4_engine_prefill_chunk(engine), | |
| cfg.ssd_streaming); | |
| char *text = read_file(cfg.prompt_path ? cfg.prompt_path : cfg.chat_prompt_path); | |
| ds4_tokens prompt = {0}; | |
| if (cfg.chat_prompt_path) { | |
| ds4_encode_chat_prompt(engine, cfg.system, text, DS4_THINK_NONE, &prompt); | |
| } else { | |
| ds4_tokenize_text(engine, text, &prompt); | |
| } | |
| free(text); | |
| if (prompt.len < cfg.ctx_max) { | |
| fprintf(stderr, | |
| "ds4-bench: prompt has %d tokens, need at least --ctx-max=%d\n", | |
| prompt.len, | |
| cfg.ctx_max); | |
| ds4_tokens_free(&prompt); | |
| ds4_engine_close(engine); | |
| return 1; | |
| } | |
| ds4_session *session = NULL; | |
| if (ds4_session_create(&session, engine, cfg.ctx_alloc) != 0) { | |
| fprintf(stderr, "ds4-bench: failed to create session\n"); | |
| ds4_tokens_free(&prompt); | |
| ds4_engine_close(engine); | |
| return 1; | |
| } | |
| if (cfg.dist.role == DS4_DISTRIBUTED_COORDINATOR && | |
| wait_distributed_route(session) != 0) | |
| { | |
| ds4_session_free(session); | |
| ds4_tokens_free(&prompt); | |
| ds4_engine_close(engine); | |
| return 1; | |
| } | |
| maybe_warn_distributed_step_shape(&cfg, session); | |
| FILE *out = stdout; | |
| if (cfg.csv_path) { | |
| out = fopen(cfg.csv_path, "wb"); | |
| if (!out) { | |
| fprintf(stderr, "ds4-bench: failed to open %s: %s\n", cfg.csv_path, strerror(errno)); | |
| ds4_session_free(session); | |
| ds4_tokens_free(&prompt); | |
| ds4_engine_close(engine); | |
| return 1; | |
| } | |
| } | |
| fprintf(out, "ctx_tokens,prefill_tokens,prefill_tps,gen_tokens,gen_tps,gen_first_ms,gen_steady_tokens,gen_steady_tps,kvcache_bytes\n"); | |
| fflush(out); | |
| const int eos = ds4_token_eos(engine); | |
| const bool distributed = cfg.dist.role == DS4_DISTRIBUTED_COORDINATOR; | |
| ds4_session_snapshot snap = {0}; | |
| const uint64_t snapshot_max_bytes = bench_snapshot_max_bytes(); | |
| bool warned_large_snapshot = false; | |
| char err[256]; | |
| int previous = 0; | |
| int rc = 0; | |
| for (int frontier = cfg.ctx_start; ; frontier = next_frontier(&cfg, frontier)) { | |
| ds4_tokens prefix = { | |
| .v = prompt.v, | |
| .len = frontier, | |
| .cap = frontier, | |
| }; | |
| const double prefill_t0 = bench_now_sec(); | |
| if (ds4_session_sync(session, &prefix, err, sizeof(err)) != 0) { | |
| fprintf(stderr, "ds4-bench: prefill to %d failed: %s\n", frontier, err); | |
| rc = 1; | |
| break; | |
| } | |
| const double prefill_t1 = bench_now_sec(); | |
| const double prefill_sec = prefill_t1 - prefill_t0; | |
| const int prefill_tokens = frontier - previous; | |
| if (write_frontier_logits_json(&cfg, engine, session, frontier, previous) != 0) { | |
| rc = 1; | |
| break; | |
| } | |
| const bool need_restore_after_generation = | |
| cfg.gen_tokens > 0 && frontier < cfg.ctx_max; | |
| bool have_snapshot = false; | |
| if (need_restore_after_generation && !distributed && | |
| getenv("DS4_BENCH_DISABLE_SNAPSHOT") == NULL) { | |
| const uint64_t payload_bytes = ds4_session_payload_bytes(session); | |
| const bool large_snapshot_forced = | |
| getenv("DS4_BENCH_FORCE_SNAPSHOT") != NULL; | |
| if (payload_bytes > snapshot_max_bytes && !large_snapshot_forced) { | |
| if (!warned_large_snapshot) { | |
| fprintf(stderr, | |
| "ds4-bench: session payload snapshot is %.2f GiB, above the %.2f GiB benchmark limit; " | |
| "replaying prefixes instead (set DS4_BENCH_FORCE_SNAPSHOT=1 to force snapshots)\n", | |
| bytes_to_gib(payload_bytes), | |
| bytes_to_gib(snapshot_max_bytes)); | |
| warned_large_snapshot = true; | |
| } | |
| } else if (payload_bytes > 0) { | |
| if (ds4_session_save_snapshot(session, &snap, err, sizeof(err)) != 0) { | |
| fprintf(stderr, "ds4-bench: snapshot at %d failed: %s\n", frontier, err); | |
| rc = 1; | |
| break; | |
| } | |
| have_snapshot = true; | |
| } | |
| } | |
| const double gen_t0 = bench_now_sec(); | |
| double gen_first_sec = 0.0; | |
| double gen_steady_sec = 0.0; | |
| int gen_done = 0; | |
| int *gen_token_buf = cfg.show_output && cfg.gen_tokens > 0 | |
| ? malloc((size_t)cfg.gen_tokens * sizeof(gen_token_buf[0])) | |
| : NULL; | |
| int gen_token_count = 0; | |
| for (int i = 0; i < cfg.gen_tokens; i++) { | |
| if (ds4_session_pos(session) + 1 >= ds4_session_ctx(session)) { | |
| fprintf(stderr, "ds4-bench: generation would exceed allocated context at frontier %d\n", frontier); | |
| rc = 1; | |
| break; | |
| } | |
| const int token = ds4_session_argmax_excluding(session, eos); | |
| if (token < 0) { | |
| fprintf(stderr, "ds4-bench: failed to choose non-EOS token at frontier %d\n", frontier); | |
| rc = 1; | |
| break; | |
| } | |
| const double token_t0 = bench_now_sec(); | |
| if (ds4_session_eval(session, token, err, sizeof(err)) != 0) { | |
| fprintf(stderr, "ds4-bench: decode at frontier %d failed: %s\n", frontier, err); | |
| rc = 1; | |
| break; | |
| } | |
| const double token_t1 = bench_now_sec(); | |
| if (i == 0) gen_first_sec = token_t1 - token_t0; | |
| else gen_steady_sec += token_t1 - token_t0; | |
| if (gen_token_buf) gen_token_buf[gen_token_count++] = token; | |
| gen_done++; | |
| } | |
| const double gen_t1 = bench_now_sec(); | |
| if (cfg.show_output && gen_token_buf && gen_token_count > 0) { | |
| fprintf(stderr, "ds4-bench: gen[ctx=%d] decoded text: \"", frontier); | |
| for (int i = 0; i < gen_token_count; i++) { | |
| size_t tlen = 0; | |
| char *txt = ds4_token_text(engine, gen_token_buf[i], &tlen); | |
| if (txt) { | |
| fwrite(txt, 1, tlen, stderr); | |
| free(txt); | |
| } | |
| } | |
| fprintf(stderr, "\"\n"); | |
| fflush(stderr); | |
| } | |
| free(gen_token_buf); | |
| if (rc != 0) break; | |
| if (!need_restore_after_generation) { | |
| /* Nothing later depends on the frontier state. */ | |
| } else if (distributed || !have_snapshot) { | |
| if (ds4_session_sync(session, &prefix, err, sizeof(err)) != 0) { | |
| fprintf(stderr, "ds4-bench: replay restore at %d failed: %s\n", frontier, err); | |
| rc = 1; | |
| break; | |
| } | |
| } else { | |
| if (ds4_session_load_snapshot(session, &snap, err, sizeof(err)) != 0) { | |
| fprintf(stderr, "ds4-bench: restore at %d failed: %s\n", frontier, err); | |
| rc = 1; | |
| break; | |
| } | |
| } | |
| const double gen_sec = gen_t1 - gen_t0; | |
| const int gen_steady_tokens = gen_done > 1 ? gen_done - 1 : 0; | |
| fprintf(out, | |
| "%d,%d,%.2f,%d,%.2f,%.3f,%d,%.2f,%llu\n", | |
| frontier, | |
| prefill_tokens, | |
| prefill_sec > 0.0 ? (double)prefill_tokens / prefill_sec : 0.0, | |
| gen_done, | |
| gen_sec > 0.0 ? (double)gen_done / gen_sec : 0.0, | |
| gen_first_sec * 1000.0, | |
| gen_steady_tokens, | |
| gen_steady_sec > 0.0 ? (double)gen_steady_tokens / gen_steady_sec : 0.0, | |
| (unsigned long long)(have_snapshot ? snap.len : 0)); | |
| fflush(out); | |
| previous = frontier; | |
| if (frontier >= cfg.ctx_max) break; | |
| } | |
| if (out != stdout) fclose(out); | |
| ds4_session_snapshot_free(&snap); | |
| ds4_session_free(session); | |
| ds4_tokens_free(&prompt); | |
| ds4_engine_close(engine); | |
| return rc; | |
| } | |