#include "llama.h" #include #include #include #include #include #include #include #include #include #include namespace { struct Score { int index; int input_index; int target; int top1; double logprob; }; bool read_tokens(const char * path, std::vector & tokens) { std::ifstream in(path); if (!in) { std::cerr << "cannot open token file: " << path << "\n"; return false; } long long value = 0; while (in >> value) { if (value < std::numeric_limits::min() || value > std::numeric_limits::max()) { std::cerr << "token out of int32 range\n"; return false; } tokens.push_back(static_cast(value)); } if (!in.eof()) { std::cerr << "malformed token file\n"; return false; } return true; } bool score_chunk(llama_context * ctx, const std::vector & tokens, int start, int n_vocab, std::vector & scores, std::string & error) { constexpr int chunk = 512; llama_batch batch = llama_batch_init(chunk, 0, 1); if (!batch.token || !batch.pos || !batch.n_seq_id || !batch.seq_id || !batch.logits) { llama_batch_free(batch); error = "llama_batch_init returned incomplete batch"; return false; } for (int i = 0; i < chunk; ++i) { batch.token[i] = tokens[start + i]; batch.pos[i] = i; batch.n_seq_id[i] = 1; batch.seq_id[i][0] = 0; batch.logits[i] = 1; } batch.n_tokens = chunk; const int rc = llama_decode(ctx, batch); if (rc != 0) { error = "llama_decode returned " + std::to_string(rc); llama_batch_free(batch); return false; } for (int i = 0; i < chunk; ++i) { const float * logits = llama_get_logits_ith(ctx, i); if (!logits) { error = "llama_get_logits_ith returned null at row " + std::to_string(i); llama_batch_free(batch); return false; } float max_logit = -std::numeric_limits::infinity(); int top1 = -1; for (int v = 0; v < n_vocab; ++v) { if (logits[v] > max_logit) { max_logit = logits[v]; top1 = v; } } double sum_exp = 0.0; for (int v = 0; v < n_vocab; ++v) { sum_exp += std::exp(static_cast(logits[v]) - static_cast(max_logit)); } const int target = tokens[start + i + 1]; if (target < 0 || target >= n_vocab || !(sum_exp > 0.0)) { error = "invalid target or logit normalization at row " + std::to_string(i); llama_batch_free(batch); return false; } const double logprob = static_cast(logits[target]) - static_cast(max_logit) - std::log(sum_exp); scores.push_back({start + i + 1, start + i, target, top1, logprob}); } llama_batch_free(batch); return true; } } // namespace int main(int argc, char ** argv) { if (argc != 4) { std::cerr << "usage: llama-teacher-score MODEL TOKEN_IDS OUTPUT_JSONL\n"; return 2; } std::vector tokens; if (!read_tokens(argv[2], tokens) || tokens.size() < 1025) { std::cerr << "need at least 1025 token ids, got " << tokens.size() << "\n"; return 2; } llama_backend_init(); llama_model_params model_params = llama_model_default_params(); model_params.n_gpu_layers = -1; llama_model * model = llama_model_load_from_file(argv[1], model_params); if (!model) { std::cerr << "llama_model_load_from_file failed\n"; llama_backend_free(); return 3; } llama_context_params context_params = llama_context_default_params(); context_params.n_ctx = 512; context_params.n_batch = 512; context_params.n_ubatch = 512; context_params.n_seq_max = 1; context_params.n_outputs_max = 512; context_params.n_outputs_max_per_seq = 512; context_params.n_threads = 16; context_params.n_threads_batch = 48; context_params.flash_attn_type = LLAMA_FLASH_ATTN_TYPE_AUTO; context_params.op_offload = true; context_params.kv_unified = false; llama_context * ctx = llama_init_from_model(model, context_params); if (!ctx) { std::cerr << "llama_init_from_model failed\n"; llama_model_free(model); llama_backend_free(); return 4; } const llama_vocab * vocab = llama_model_get_vocab(model); const int n_vocab = llama_vocab_n_tokens(vocab); std::vector scores; scores.reserve(1024); std::string error; const auto started = std::chrono::steady_clock::now(); for (int start : {0, 512}) { if (start != 0) { llama_memory_clear(llama_get_memory(ctx), true); } if (!score_chunk(ctx, tokens, start, n_vocab, scores, error)) { std::cerr << error << "\n"; llama_free(ctx); llama_model_free(model); llama_backend_free(); return 5; } } llama_synchronize(ctx); const auto elapsed = std::chrono::duration(std::chrono::steady_clock::now() - started).count(); double nll = 0.0; int top1_correct = 0; for (const Score & s : scores) { nll -= s.logprob; top1_correct += s.top1 == s.target; } const double mean_nll = nll / static_cast(scores.size()); const double ppl = std::exp(mean_nll); std::ofstream out(argv[3]); if (!out) { std::cerr << "cannot open output: " << argv[3] << "\n"; llama_free(ctx); llama_model_free(model); llama_backend_free(); return 6; } out << std::setprecision(12); out << "{\"type\":\"meta\",\"commit\":\"9a9394a895b96003ca842a6041cb28ac49a108f7\"," "\"vocab\":" << n_vocab << ",\"token_count\":" << tokens.size() << ",\"chunk_size\":512,\"chunk_starts\":[0,512],\"bos_injected\":false," "\"score_count\":" << scores.size() << ",\"elapsed_seconds\":" << elapsed << "}\n"; for (const Score & s : scores) { out << "{\"type\":\"score\",\"target_index\":" << s.index << ",\"input_index\":" << s.input_index << ",\"target\":" << s.target << ",\"correct_logprob\":" << s.logprob << ",\"top1\":" << s.top1 << "}\n"; } out << "{\"type\":\"summary\",\"score_count\":" << scores.size() << ",\"mean_nll\":" << mean_nll << ",\"ppl\":" << ppl << ",\"top1_correct\":" << top1_correct << ",\"top1_accuracy\":" << (static_cast(top1_correct) / scores.size()) << ",\"elapsed_seconds\":" << elapsed << "}\n"; out.close(); llama_free(ctx); llama_model_free(model); llama_backend_free(); return 0; }