ember.cpp 0.1.0
C++ inference engine for Llama-family and paperkiln models (CPU, CUDA, WebAssembly)
Loading...
Searching...
No Matches
score.cpp
Go to the documentation of this file.
1// POST /score's forward (api.h score_rows; the request handling and mtstudio's arithmetic are in score.h
2// and server.cpp).
3#include <algorithm>
4#include <cstdlib>
5#include <stdexcept>
6#include <string>
7#include <vector>
8
9#include "api.h"
10#include "graph_ir.h"
11#include "model.h"
12#ifdef HAS_CUDA
13#include "cuda_kernels.h"
14#endif
15
16namespace tinyllama {
17
18namespace {
19// EMBER_SCORE_FULL=1: every request a fresh prefill that computes every row's logits, the rows then
20// picked (the reference the default path is checked against; mtstudio score's own computation).
21bool score_full() {
22 static const bool on = [] {
23 const char* v = std::getenv("EMBER_SCORE_FULL");
24 return v && std::string(v) == "1";
25 }();
26 return on;
27}
28} // namespace
29
30int TinyLlamaSession::score_vocab() const { return ir_ ? ir_->vocab() : config_.vocab_size; }
31
34 if (ir_ && ir_->max_len() > 0) ctx = std::min(ctx, ir_->max_len());
35 return ctx;
36}
37
38const std::vector<std::string>& TinyLlamaSession::gguf_tokens() const {
39 static const std::vector<std::string> none;
40 const GGUFData* g = model_ ? model_->get_gguf_data() : nullptr;
41 return g ? g->tokenizer_tokens : none;
42}
43
45 static const bool off = [] {
46 const char* v = std::getenv("EMBER_SCORE_REUSE");
47 return v && std::string(v) == "0";
48 }();
49 if (off || score_full()) return false;
50 if (ir_)
51 return !ir_->on_gpu() && !ir_->has_recurrent() && !ir_->evicts() && !ir_->has_depth_route() &&
52 ir_->state_k() == 0 && !ir_->turn_state();
53 return config_.num_cpu_offload_layers == config_.num_hidden_layers; // every layer on the CPU
54}
55
56std::vector<float> TinyLlamaSession::score_rows(const std::vector<int>& ids, int first_row, bool reuse_prefix,
57 int* reused) {
58 const int n = static_cast<int>(ids.size());
59 if (n == 0 || first_row < 0 || first_row >= n) throw std::invalid_argument("score_rows: first_row out of range");
60 if (n > score_context())
61 throw std::invalid_argument("score_rows: " + std::to_string(n) + " positions exceed the context of " +
62 std::to_string(score_context()));
63 const int V = score_vocab();
64 kv_token_history_.clear(); // generate() must not reuse what this leaves in the cache
65 int reuse = 0;
67 size_t lcp = 0;
68 while (lcp < score_ids_.size() && lcp < ids.size() && score_ids_[lcp] == ids[lcp]) ++lcp;
69 reuse = static_cast<int>(std::min(lcp, static_cast<size_t>(first_row))); // rows >= first_row are computed
70 }
71 score_ids_.clear(); // set again only when this forward completes
72 if (reused) *reused = reuse;
73 const int m = n - reuse; // positions computed
74 const int rows = n - first_row; // rows returned: the last `rows` of them
75 // The default path computes the head on the returned rows only, where that is row-wise by
76 // construction (the CPU paths); EMBER_SCORE_FULL=1 and the device compute every row.
77 std::vector<float> out;
78 if (ir_) {
79 const std::vector<int> tail(ids.begin() + reuse, ids.end());
80 if (!score_full() && !ir_->on_gpu()) {
81 out = ir_->forward(tail, reuse, /*all_rows=*/false, rows);
82 } else {
83 const std::vector<float> all = ir_->forward(tail, reuse, /*all_rows=*/true);
84 if (all.size() != static_cast<size_t>(m) * V)
85 throw std::runtime_error("score: the interpreter returned " + std::to_string(all.size()) + " logits");
86 out.assign(all.end() - static_cast<long>(rows) * V, all.end());
87 }
88 } else {
89 // As generate()'s prefill: positions [0, reuse) kept, every later one rewritten before it is read.
90 if (reuse > 0) {
92 } else {
95 }
96 const int hs = config_.hidden_size;
97 std::vector<float> h(static_cast<size_t>(m) * hs);
98 for (int i = 0; i < m; ++i) {
99 const std::vector<float> e = model_->lookup_embedding(ids[reuse + i]);
100 if (e.size() != static_cast<size_t>(hs))
101 throw std::runtime_error("score: no embedding for token id " + std::to_string(ids[reuse + i]));
102 std::copy(e.begin(), e.end(), h.begin() + static_cast<long>(i) * hs);
103 }
105 if (cpu_layers > 0) h = model_->forward_cpu_batch(h, m, cpu_layers, reuse, &kv_cache_);
106 if (h.size() != static_cast<size_t>(m) * hs) throw std::runtime_error("score: the CPU layers failed");
107 if (cpu_layers == L) {
108 // The final norm and the LM head are row-wise (rmsnorm_batch_cpu; matmul_f32_f32_batch_cpu's
109 // one dot product per row and output), so the returned rows alone give the bits all rows give.
110 std::vector<float> keep = h;
111 if (!score_full()) keep.assign(h.end() - static_cast<long>(rows) * hs, h.end());
112 const int k_rows = score_full() ? m : rows;
113 std::vector<float> lg = model_->forward_cpu_logits_batch(keep, k_rows);
114 if (lg.size() != static_cast<size_t>(k_rows) * V) throw std::runtime_error("score: the LM head failed");
115 out.assign(lg.end() - static_cast<long>(rows) * V, lg.end());
116 } else {
117#ifdef HAS_CUDA
118 float* d = nullptr;
119 gpuErrchk(cudaMalloc(&d, h.size() * sizeof(float)));
120 gpuErrchk(cudaMemcpy(d, h.data(), h.size() * sizeof(float), cudaMemcpyHostToDevice));
121 // FP32 GEMMs only (no BF16 tensor cores or TF32, which /chat's prefill may use: they move
122 // log-probabilities by ~1e-3), and every row's logits (the same FP32 matvec per row as
123 // decode's LM head).
124 struct Fp32Only {
125 TinyLlamaModel& model;
126 explicit Fp32Only(TinyLlamaModel& mdl) : model(mdl) { model.set_gemm_fp32_only(true); }
127 ~Fp32Only() { model.set_gemm_fp32_only(false); }
128 } fp32_only(*model_);
129 const std::vector<float> all =
130 model_->forward_device_batch_prefill(d, m, reuse, &kv_cache_, 0, /*all_logits=*/true);
132 if (all.size() != static_cast<size_t>(m) * V)
133 throw std::runtime_error("score: the device prefill returned " + std::to_string(all.size()) + " logits");
134 out.assign(all.end() - static_cast<long>(rows) * V, all.end());
135#else
136 throw std::runtime_error("score: GPU layers requested but CUDA not available");
137#endif
138 }
140 }
141 if (out.size() != static_cast<size_t>(rows) * V)
142 throw std::runtime_error("score: " + std::to_string(out.size()) + " logits for " + std::to_string(rows) + " rows");
143 score_ids_ = ids;
144 return out;
145}
146
147} // namespace tinyllama
Main transformer model class for TinyLlama.
Definition model.h:309
void set_gemm_fp32_only(bool on)
Definition model.h:385
ModelConfig config_
Definition api.h:294
std::vector< float > score_rows(const std::vector< int > &ids, int first_row, bool reuse_prefix, int *reused=nullptr)
Definition score.cpp:56
int score_context() const
Definition score.cpp:32
bool score_reuse_ok() const
Definition score.cpp:44
std::unique_ptr< TinyLlamaModel > model_
Definition api.h:288
std::unique_ptr< ember_ir::Interpreter > ir_
Definition api.h:291
std::vector< int > score_ids_
Definition api.h:317
std::vector< int > kv_token_history_
Definition api.h:306
const std::vector< std::string > & gguf_tokens() const
Definition score.cpp:38
int n
std::vector< float > v
node out
size_t j
Complete representation of a GGUF file's contents.
std::vector< std::string > tokenizer_tokens
void clear_data()
Definition model.h:186
int seq_len
Definition model.h:161
int hidden_size
Definition model.h:83
int vocab_size
Definition model.h:88
int num_cpu_offload_layers
Definition model.h:106
int num_hidden_layers
Definition model.h:87
int max_position_embeddings
Definition model.h:89