#include "ggml-backend.h" #include "llama.h" #include #include #include #include #include #include #include #include #include #include #include static void die(const char *msg) { std::fprintf(stderr, "%s\n", msg); std::exit(1); } static std::string read_file(const char *path) { std::ifstream in(path, std::ios::binary); if (!in) { std::fprintf(stderr, "open %s: %s\n", path, std::strerror(errno)); std::exit(1); } std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } static void strip_newline(std::string &s) { while (!s.empty() && (s.back() == '\n' || s.back() == '\r')) { s.pop_back(); } } static std::vector split_tab(const std::string &line) { std::vector out; size_t start = 0; for (;;) { size_t tab = line.find('\t', start); if (tab == std::string::npos) { out.push_back(line.substr(start)); return out; } out.push_back(line.substr(start, tab - start)); start = tab + 1; } } static std::vector tokenize( const llama_vocab *vocab, const std::string &text, bool add_special, bool parse_special) { int n = llama_tokenize(vocab, text.data(), (int32_t)text.size(), nullptr, 0, add_special, parse_special); if (n < 0) n = -n; if (n == 0) return {}; std::vector tokens((size_t)n); int got = llama_tokenize(vocab, text.data(), (int32_t)text.size(), tokens.data(), n, add_special, parse_special); if (got < 0) die("llama_tokenize failed"); tokens.resize((size_t)got); return tokens; } static std::string render_glm_ds4_prompt(const std::string &prompt) { return std::string("[gMASK]<|user|>") + prompt + "<|assistant|>"; } static std::string render_template_prompt( const char *tmpl, const std::string &prompt, bool *ok) { llama_chat_message msg = {"user", prompt.c_str()}; int n = llama_chat_apply_template(tmpl, &msg, 1, true, nullptr, 0); if (n < 0) { *ok = false; return {}; } std::vector buf((size_t)n + 1); int got = llama_chat_apply_template(tmpl, &msg, 1, true, buf.data(), (int32_t)buf.size()); if (got < 0) { *ok = false; return {}; } *ok = true; return std::string(buf.data(), (size_t)got); } static bool decode_chunk( llama_context *ctx, llama_batch &batch, const llama_token *tokens, int n_tokens, int pos, bool logits_last) { batch.n_tokens = n_tokens; for (int i = 0; i < n_tokens; i++) { batch.token[i] = tokens[i]; batch.pos[i] = pos + i; batch.n_seq_id[i] = 1; batch.seq_id[i][0] = 0; batch.logits[i] = (logits_last && i == n_tokens - 1) ? 1 : 0; } return llama_decode(ctx, batch) == 0; } static bool decode_tokens( llama_context *ctx, llama_batch &batch, const std::vector &tokens, int start_pos, int n_batch, bool logits_last) { int off = 0; while (off < (int)tokens.size()) { int n = std::min(n_batch, (int)tokens.size() - off); bool want_logits = logits_last && off + n == (int)tokens.size(); if (!decode_chunk(ctx, batch, tokens.data() + off, n, start_pos + off, want_logits)) { return false; } off += n; } return true; } static double token_logprob( const float *logits, int n_vocab, llama_token token, llama_token *greedy_out) { float max_logit = -std::numeric_limits::infinity(); llama_token greedy = 0; for (int i = 0; i < n_vocab; i++) { if (logits[i] > max_logit) { max_logit = logits[i]; greedy = (llama_token)i; } } double sum = 0.0; for (int i = 0; i < n_vocab; i++) { sum += std::exp((double)logits[i] - (double)max_logit); } *greedy_out = greedy; return (double)logits[token] - ((double)max_logit + std::log(sum)); } int main(int argc, char **argv) { if (argc != 4 && argc != 5 && argc != 6) { std::fprintf(stderr, "usage: %s MODEL manifest.tsv OUT.tsv [ctx] [auto|glm-ds4]\n", argv[0]); return 2; } const char *model_path = argv[1]; const char *manifest_path = argv[2]; const char *out_path = argv[3]; int ctx_size = argc >= 5 ? std::atoi(argv[4]) : 4096; if (ctx_size < 1024) ctx_size = 1024; const std::string template_mode = argc == 6 ? argv[5] : "auto"; if (template_mode != "auto" && template_mode != "glm-ds4") { die("template mode must be auto or glm-ds4"); } ggml_backend_load_all(); llama_backend_init(); llama_model_params model_params = llama_model_default_params(); model_params.n_gpu_layers = -1; model_params.use_mmap = true; llama_model *model = llama_model_load_from_file(model_path, model_params); if (!model) die("failed to open model"); const llama_vocab *vocab = llama_model_get_vocab(model); const int n_vocab = llama_vocab_n_tokens(vocab); const char *tmpl = llama_model_chat_template(model, nullptr); llama_context_params ctx_params = llama_context_default_params(); ctx_params.n_ctx = (uint32_t)ctx_size; ctx_params.n_batch = 2048; ctx_params.n_ubatch = 512; ctx_params.n_seq_max = 1; ctx_params.no_perf = true; llama_context *ctx = llama_init_from_model(model, ctx_params); if (!ctx) die("failed to create context"); const int n_batch = std::min((int)llama_n_batch(ctx), 2048); llama_batch batch = llama_batch_init(n_batch, 0, 1); std::ifstream mf(manifest_path, std::ios::binary); if (!mf) { std::fprintf(stderr, "open %s: %s\n", manifest_path, std::strerror(errno)); return 1; } std::ofstream out(out_path, std::ios::binary); if (!out) { std::fprintf(stderr, "open %s: %s\n", out_path, std::strerror(errno)); return 1; } out << "id\tprompt_tokens\ttarget_tokens\tnll\tavg_nll\tfirst_match\tgreedy_lcp\n"; std::string line; int case_n = 0; double total_nll = 0.0; long total_tokens = 0; long total_lcp = 0; long first_matches = 0; bool warned_template_fallback = false; while (std::getline(mf, line)) { strip_newline(line); if (line.empty() || line[0] == '#') continue; std::vector cols = split_tab(line); if (cols.size() < 3) die("bad manifest row"); const std::string &id = cols[0]; const std::string &prompt_path = cols[1]; const std::string &cont_path = cols[2]; std::string prompt_text = read_file(prompt_path.c_str()); std::string cont_text = read_file(cont_path.c_str()); std::string rendered; bool used_template = false; if (template_mode == "auto" && tmpl) { rendered = render_template_prompt(tmpl, prompt_text, &used_template); } if (!used_template) { if (template_mode == "auto" && !warned_template_fallback) { std::fprintf(stderr, "score_llama: llama.cpp chat template unavailable; " "using DS4 GLM prompt fallback\n"); warned_template_fallback = true; } rendered = render_glm_ds4_prompt(prompt_text); } std::vector prompt = tokenize(vocab, rendered, false, true); std::vector target = tokenize(vocab, cont_text, false, false); if (prompt.empty()) die("empty prompt tokenization"); if (target.empty()) die("empty continuation tokenization"); if ((int)prompt.size() + (int)target.size() + 1 >= ctx_size) { std::fprintf(stderr, "%s exceeds ctx=%d\n", id.c_str(), ctx_size); return 1; } llama_memory_clear(llama_get_memory(ctx), true); if (!decode_tokens(ctx, batch, prompt, 0, n_batch, true)) { std::fprintf(stderr, "%s prompt decode failed\n", id.c_str()); return 1; } double nll = 0.0; int lcp = 0; bool still_matching = true; bool first_match = false; for (int i = 0; i < (int)target.size(); i++) { const float *logits = llama_get_logits_ith(ctx, -1); if (!logits) { std::fprintf(stderr, "%s logits unavailable at target token %d\n", id.c_str(), i); return 1; } llama_token greedy = 0; double lp = token_logprob(logits, n_vocab, target[(size_t)i], &greedy); if (i == 0) first_match = (greedy == target[(size_t)i]); if (still_matching && greedy == target[(size_t)i]) lcp++; else still_matching = false; nll += -lp; if (!decode_chunk(ctx, batch, &target[(size_t)i], 1, (int)prompt.size() + i, true)) { std::fprintf(stderr, "%s target decode failed at token %d\n", id.c_str(), i); return 1; } } const double avg = nll / (double)target.size(); out << id << '\t' << prompt.size() << '\t' << target.size() << '\t' << nll << '\t' << avg << '\t' << (first_match ? 1 : 0) << '\t' << lcp << '\n'; out.flush(); case_n++; total_nll += nll; total_tokens += (long)target.size(); total_lcp += lcp; first_matches += first_match ? 1 : 0; std::fprintf(stderr, "%s cases=%d prompt=%zu target=%zu avg_nll=%.6f lcp=%d\n", id.c_str(), case_n, prompt.size(), target.size(), avg, lcp); } std::fprintf(stderr, "summary cases=%d tokens=%ld avg_nll=%.9f first_match=%ld avg_lcp=%.3f\n", case_n, total_tokens, total_tokens ? total_nll / (double)total_tokens : 0.0, first_matches, case_n ? (double)total_lcp / (double)case_n : 0.0); llama_batch_free(batch); llama_free(ctx); llama_model_free(model); llama_backend_free(); return 0; }