ember.cpp 0.1.0
C++ inference engine for Llama-family and paperkiln models (CPU, CUDA, WebAssembly)
Loading...
Searching...
No Matches
model.cpp
Go to the documentation of this file.
1#include "model.h"
2
3#ifdef HAS_CUDA
4#include "cuda_kernels.h"
5#endif
6#include <algorithm>
7#include <cmath>
8#include <cstring>
9#include <fstream>
10#include <iomanip>
11#include <limits>
12#include <memory>
13#include <sstream>
14#include <stdexcept>
15#ifdef _WIN32
16#include <windows.h>
17#endif
18#include <cassert>
19#include <cstdint>
20#include <iostream>
21#include <numeric>
22#include <variant>
23
24#include "cpu_attention.h"
25#include "cpu_batch_processor.h"
26#include "gguf_parser.h"
27#include "gpu_initialization.h"
28#include "kv_cache.h"
29#include "logger.h"
30#include "model_config.h"
31#include "model_constants.h"
32#include "model_macros.h"
33#include "model_utils.h"
34#include "quantization.h"
35#include "safetensors_loader.h"
36#include "utils.h"
37#include "graph_ir.h"
38#include "weight_management.h"
40 const GGUFData* gguf) {
41 Logger::info("Initializing model weights...");
45 int vs = config_.vocab_size;
46 layers.resize(nhl);
47
48 if (gguf) {
49 Logger::info("Processing weights from GGUF data source...");
50
51 if (gguf && (gguf->mapped_tensor_data || !gguf->tensor_data.empty())) {
52 map_gguf_weights(*gguf, *this);
53 Logger::info("[INIT_WEIGHTS_GGUF] map_gguf_weights(*gguf, *this) CALLED (using function parameter).");
54 } else if (gguf_data_ && (gguf_data_->mapped_tensor_data || !gguf_data_->tensor_data.empty())) {
56 Logger::info("[INIT_WEIGHTS_GGUF] map_gguf_weights(*gguf_data_, *this) CALLED (using member gguf_data_).");
57 } else {
58 Logger::error("[INIT_WEIGHTS_GGUF] map_gguf_weights failed - tensor data not available. No GGUF weights mapped.");
59 }
60
61 // LAZY DEQUANTIZATION: Only dequantize what's immediately needed
62 Logger::info("[INIT_WEIGHTS_GGUF] Using lazy dequantization to prevent OOM");
63
64 // Only dequantize embed_tokens and final_norm immediately (small and always needed)
65 if (this->embed_tokens_f32.empty()) {
66 ensure_embed_tokens_dequantized(); // every quantised table type, Q5_K included
67 if (!this->embed_tokens_f32.empty()) {
68 Logger::info("[INIT_WEIGHTS_GGUF_DEQUANT] embed_tokens_f32 populated. Size: " + std::to_string(this->embed_tokens_f32.size()));
69 }
70 }
71
72 if (this->final_norm_f32.empty()) {
73 if (!this->final_norm.empty()) {
75 if (!this->final_norm_f32.empty()) {
76 Logger::info("[INIT_WEIGHTS_GGUF_DEQUANT] Successfully converted final_norm (BF16) to final_norm_f32. Size: " + std::to_string(this->final_norm_f32.size()));
77 }
78 }
79 }
80
81 // DEFER lm_head dequantization until actually needed (it's huge)
82 Logger::info("[INIT_WEIGHTS_GGUF] Deferring lm_head dequantization until needed to save memory");
83 // Tied embeddings: a GGUF with no output.weight (llama.cpp's converter omits it when the checkpoint ties the
84 // head to the embedding, e.g. SmolLM2) uses token_embd as the head, as llama.cpp does. Every head kernel then
85 // reads the f32 copy. Files with output.weight are untouched.
86 if (this->lm_head_f32.empty() && this->lm_head_q8_0.empty() && this->lm_head_q6k.empty() &&
87 this->lm_head_q4k.empty() && this->lm_head_q5k.empty() && this->lm_head_q8k.empty() &&
88 this->lm_head.empty() && !this->embed_tokens_f32.empty()) {
89 this->lm_head_f32 = this->embed_tokens_f32;
90 Logger::info("[INIT_WEIGHTS_GGUF] No output.weight: the head is tied to token_embd.weight");
91 }
92
93 // DEFER all layer weight dequantization until the layer is actually used
94 Logger::info("[INIT_WEIGHTS_GGUF] Deferring all layer weight dequantization until layers are used");
95
96 // Only populate layer norms immediately (small and needed for validation)
97 for (int l = 0; l < nhl; ++l) {
98 auto& lw = layers[l];
99
100 if (lw.input_layernorm_f32.empty() && !lw.input_layernorm.empty()) {
101 lw.input_layernorm_f32 = bf16vec_to_float_vec(lw.input_layernorm);
102 if (!lw.input_layernorm_f32.empty()) Logger::info(" L" + std::to_string(l) + " input_layernorm_f32 populated from BF16. Size: " + std::to_string(lw.input_layernorm_f32.size()));
103 }
104 if (lw.post_attention_layernorm_f32.empty() && !lw.post_attention_layernorm.empty()) {
105 lw.post_attention_layernorm_f32 = bf16vec_to_float_vec(lw.post_attention_layernorm);
106 if (!lw.post_attention_layernorm_f32.empty()) Logger::info(" L" + std::to_string(l) + " post_attention_layernorm_f32 populated from BF16. Size: " + std::to_string(lw.post_attention_layernorm_f32.size()));
107 }
108 }
109
110 // Validation checks for layer norms
111 for (int l = 0; l < nhl; ++l) {
112 const auto& lw = layers[l];
113 if (lw.input_layernorm_f32.empty()) {
114 Logger::error("[INIT_WEIGHTS_GGUF_CHECK] Layer " + std::to_string(l) +
115 ": input_layernorm_f32 is EMPTY post-GGUF. This WILL cause GPU init errors if this layer is on GPU.");
116 }
117 if (lw.post_attention_layernorm_f32.empty()) {
118 Logger::error("[INIT_WEIGHTS_GGUF_CHECK] Layer " + std::to_string(l) +
119 ": post_attention_layernorm_f32 is EMPTY post-GGUF. This WILL cause GPU init errors if this layer is on GPU.");
120 }
121 }
122 Logger::info("[INIT_WEIGHTS_GGUF] Finished per-layer NORM F32 vector checks post-GGUF.");
123
124 } else {
125 Logger::fatal("TinyLlamaModel::initialize_weights called with neither GGUF nor SafeTensors loader. Cannot initialize weights.");
126 throw std::runtime_error("Model weights source (GGUF or SafeTensors) not provided to initialize_weights.");
127 }
128
129 Logger::info("Finished initializing model weights logic block.");
130
131 if (this->final_norm_f32.empty()) {
132 Logger::error("[INIT_WEIGHTS_FINAL_CHECK] final_norm_f32 is EMPTY. This WILL cause errors if final normalization is needed in F32.");
133 } else {
134 Logger::info("[INIT_WEIGHTS_FINAL_CHECK] final_norm_f32 is POPULATED. Size: " + std::to_string(this->final_norm_f32.size()));
135 }
136
137 if (this->embed_tokens_f32.empty()) {
138 Logger::error("[INIT_WEIGHTS_FINAL_CHECK] embed_tokens_f32 is EMPTY. This WILL cause errors if token embeddings are needed in F32.");
139 } else {
140 Logger::info("[INIT_WEIGHTS_FINAL_CHECK] embed_tokens_f32 is POPULATED. Size: " + std::to_string(this->embed_tokens_f32.size()));
141 }
142}
145 : config_(config) { // Copies the potentially faulty config first
146 config_.is_gguf_file_loaded = false; // Explicitly set to false for SafeTensors path
147 Logger::info("Constructing TinyLlamaModel from SafeTensorsLoader (is_gguf_file_loaded set to false).");
148 initialize_weights(&loader, nullptr);
150 Logger::info("TinyLlamaModel construction from SafeTensorsLoader complete.");
151}
152
154 const std::string& model_path)
155 : model_path_(model_path)
158#endif
159{
160 Logger::info("TinyLlamaModel constructor entered. Model path (from string): " + model_path);
161 int cli_gpu_layer_request = initial_config.num_cpu_offload_layers;
162 bool cli_mmap_preference = initial_config.use_mmap_for_gguf;
163 this->config_ = initial_config;
164 if (this->model_path_.empty() && !model_path.empty()) {
165 this->model_path_ = model_path;
166 }
167 std::unique_ptr<SafeTensorsLoader> loader = nullptr;
168 if (!this->model_path_.empty() && this->model_path_.size() > 5 &&
169 this->model_path_.substr(this->model_path_.size() - 5) == ".gguf") {
170 Logger::info("GGUF file detected by path in Model Constructor: " + this->model_path_);
171 try {
173 Logger::info("TinyLlamaModel GGUF path: Using mmap setting " + std::string(force_mmap_for_gguf_load ? "true" : "false") +
174 " for gguf_meta/weight loading based on CLI mmap preference: " +
175 std::string(cli_mmap_preference ? "true" : "false"));
176
177 this->gguf_data_ = std::make_unique<GGUFData>(load_gguf_meta(this->model_path_, force_mmap_for_gguf_load));
178
180 // A paperkiln IR that is not the graph the tuned llama path implements
181 // (ember_ir::tuned_llama_match: a structural match, family_hint only
182 // its pre-filter; or EMBER_IR_ONLY=1): the interpreter serves it, so
183 // the dimensions come from the IR and the llama loader is skipped below.
184 {
185 const std::string ir = ember_ir::find_ir(*this->gguf_data_);
186 const char* only = std::getenv("EMBER_IR_ONLY");
187 if (!ir.empty()) {
188 const auto j = nlohmann::json::parse(ir);
189 std::string why;
190 const bool tuned = ember_ir::tuned_llama_match(ir, *this->gguf_data_, &why);
191 if (!tuned && j.value("family_hint", "") == "llama")
192 Logger::info("[IR] family_hint llama, but not the tuned llama path's graph (" + why +
193 "): the interpreter runs it");
194 if (!tuned || (only && std::string(only) == "1")) {
195 this->ir_only_ = true;
196 const auto& m = j.at("model");
197 config_from_gguf.hidden_size = m.at("d").get<int>();
198 config_from_gguf.num_hidden_layers = j.at("block").at("repeat").get<int>();
199 config_from_gguf.vocab_size = m.at("vocab").get<int>();
200 config_from_gguf.max_position_embeddings = m.value("ctx", config_from_gguf.max_position_embeddings);
201 // EMBER_IR_CTX caps it as it caps the interpreter's kv state (graph_ir.h).
202 if (const char* c = std::getenv("EMBER_IR_CTX"))
203 if (std::atoi(c) > 0)
204 config_from_gguf.max_position_embeddings = std::min(config_from_gguf.max_position_embeddings, std::atoi(c));
205 // Head counts from the kv state; without one (recurrent-only
206 // families), from linear attention's [heads, head_dim] state, else
207 // one head of width d (the llama-side KV cache is then unused).
208 bool kv_heads_set = false;
209 for (const auto& s : j.at("block").value("state", nlohmann::json::array()))
210 if (s.value("kind", "") == "kv") {
211 config_from_gguf.num_attention_heads = s.at("heads").get<int>();
212 config_from_gguf.num_key_value_heads = s.at("kv_heads").get<int>();
213 kv_heads_set = true;
214 }
215 if (!kv_heads_set) {
216 int heads = 1;
217 for (const auto& s : j.at("block").value("state", nlohmann::json::array()))
218 if (s.value("kind", "") == "recurrent" && s.value("op", "") == "linear_attention" &&
219 !s.value("tensors", nlohmann::json::array()).empty())
220 heads = s.at("tensors").at(0).at("shape").at(0).get<int>();
221 config_from_gguf.num_attention_heads = heads;
222 config_from_gguf.num_key_value_heads = heads;
223 }
224 config_from_gguf.num_experts = 0;
225 Logger::info("GGUF served by the paperkiln IR interpreter only (family_hint '" +
226 j.value("family_hint", "") + "'): llama weights are not loaded.");
227 }
228 }
229 }
230 this->config_ = config_from_gguf;
232 this->config_.is_gguf_file_loaded = true;
233 if (cli_gpu_layer_request < 0) {
235 Logger::info("TinyLlamaModel GGUF Ctor CALC: CLI hint < 0 (all GPU). num_cpu_offload_layers set to 0.");
236 } else if (cli_gpu_layer_request == 0) {
238 Logger::info("TinyLlamaModel GGUF Ctor CALC: CLI hint == 0 (all CPU). num_cpu_offload_layers set to num_hidden_layers (" + std::to_string(this->config_.num_cpu_offload_layers) + ").");
239 } else { // CLI hint > 0, meaning cli_gpu_layer_request is the number of desired GPU layers
240 if (this->config_.num_hidden_layers > 0) {
241 if (cli_gpu_layer_request >= this->config_.num_hidden_layers) {
242 this->config_.num_cpu_offload_layers = 0; // More GPU layers requested than available -> all on GPU
243 Logger::info("TinyLlamaModel GGUF Ctor CALC: CLI GPU layer request ("+ std::to_string(cli_gpu_layer_request) +") >= total layers. num_cpu_offload_layers set to 0.");
244 } else {
246 Logger::info("TinyLlamaModel GGUF Ctor CALC: Partial GPU. CLI GPU req: " + std::to_string(cli_gpu_layer_request) + ". num_cpu_offload_layers set to " + std::to_string(this->config_.num_cpu_offload_layers));
247 }
248 } else { // num_hidden_layers is 0 or negative, something is wrong with GGUF. Default to all CPU.
250 Logger::warning("TinyLlamaModel GGUF Ctor CALC: num_hidden_layers from GGUF is <= 0. Defaulting num_cpu_offload_layers to 0. CLI GPU req: " + std::to_string(cli_gpu_layer_request));
251 }
252 }
253 Logger::info("TinyLlamaModel GGUF Ctor: POST-CALC (within GGUF block) final num_cpu_offload_layers = " + std::to_string(this->config_.num_cpu_offload_layers));
254 Logger::info("[CTOR_GGUF_DEBUG_L1860] After CLI hint logic: this->config_.num_cpu_offload_layers = " + std::to_string(this->config_.num_cpu_offload_layers) +
255 ", this->config_.num_hidden_layers = " + std::to_string(this->config_.num_hidden_layers));
256 } catch (const std::exception& e) {
257 Logger::error("Failed to load or parse GGUF file: " + std::string(e.what()));
258 throw;
259 }
260 } else if (model_path.size() > 12 &&
261 model_path.substr(model_path.size() - 12) == ".safetensors") {
262 Logger::info("SafeTensors file detected: " + model_path);
265
266 // For SafeTensors, start with JSON config, then layer CLI preferences.
268 Logger::info("Successfully loaded and parsed config.json for SafeTensors model.");
269 this->config_ = config_from_json; // Base is from JSON
270 } else {
271 Logger::warning("Failed to load config.json or it was not found for SafeTensors model. Proceeding with initial_config defaults and CLI overrides.");
272 }
273 this->config_.is_gguf_file_loaded = false;
274 this->config_.use_mmap_for_gguf = cli_mmap_preference; // This field is GGUF specific, but store CLI pref anyway.
275
276 if (cli_gpu_layer_request < 0) {
278 } else if (cli_gpu_layer_request == 0) {
280 } else {
281 if (this->config_.num_hidden_layers > 0) {
282 if (cli_gpu_layer_request >= this->config_.num_hidden_layers) {
284 } else {
286 }
287 } else {
288 this->config_.num_cpu_offload_layers = 0; // Fallback if num_hidden_layers not known
289 Logger::warning("SafeTensors path: num_hidden_layers is 0 from JSON/default. Defaulting num_cpu_offload_layers to 0 despite CLI GPU request: " + std::to_string(cli_gpu_layer_request));
290 }
291 }
292 Logger::info("SafeTensors path: Calculated num_cpu_offload_layers = " + std::to_string(this->config_.num_cpu_offload_layers));
293
294 try {
295 loader = std::make_unique<SafeTensorsLoader>(model_path);
296 Logger::info("SafeTensorsLoader initialized for: " + model_path);
297 } catch (const std::exception& e) {
298 Logger::error("Failed to initialize SafeTensorsLoader: " + std::string(e.what()));
299 throw;
300 }
301 } else {
302 throw std::runtime_error(
303 "Unsupported model file type. Please use .gguf or .safetensors");
304 }
305
306 Logger::info("TinyLlamaModel constructor: After specific loader block. Current config_.num_cpu_offload_layers = " + std::to_string(this->config_.num_cpu_offload_layers) +
307 ", config_.num_hidden_layers = " + std::to_string(this->config_.num_hidden_layers));
308 Logger::info("TinyLlamaModel constructor: Current config_.use_mmap_for_gguf = " + std::string(this->config_.use_mmap_for_gguf ? "true" : "false"));
309
310 if (this->config_.num_cpu_offload_layers < 0) { // Should not happen if logic above is correct for -1 CLI hint
312 Logger::warning("Clamping num_cpu_offload_layers: was < 0, set to 0.");
313 }
314 if (this->config_.num_hidden_layers > 0 && this->config_.num_cpu_offload_layers > this->config_.num_hidden_layers) {
315 Logger::warning("Clamping num_cpu_offload_layers: Requested CPU offload layers (" + std::to_string(this->config_.num_cpu_offload_layers) +
316 ") exceeds total hidden layers (" + std::to_string(this->config_.num_hidden_layers) +
317 "). Clamping to " + std::to_string(this->config_.num_hidden_layers) + " (all CPU).");
319 }
320 Logger::info("TinyLlamaModel constructor: Final clamped num_cpu_offload_layers = " + std::to_string(this->config_.num_cpu_offload_layers));
321 Logger::info("[CTOR_DEBUG_L1921] End of Model Ctor (before initialize_weights/rope call): this->config_.num_cpu_offload_layers = " + std::to_string(this->config_.num_cpu_offload_layers) +
322 ", this->config_.num_hidden_layers = " + std::to_string(this->config_.num_hidden_layers));
323 Logger::info("Final ModelConfig (before initialize_weights/rope):");
324 Logger::info(" hidden_size: " + std::to_string(config_.hidden_size));
325 Logger::info(" intermediate_size: " + std::to_string(config_.intermediate_size));
326 Logger::info(" num_attention_heads: " + std::to_string(config_.num_attention_heads));
327 Logger::info(" num_key_value_heads: " + std::to_string(config_.num_key_value_heads));
328 Logger::info(" num_hidden_layers: " + std::to_string(config_.num_hidden_layers));
329 Logger::info(" vocab_size: " + std::to_string(config_.vocab_size));
330 Logger::info(" max_position_embeddings: " + std::to_string(config_.max_position_embeddings));
331 Logger::info(" architecture: " + config_.architecture);
332 Logger::info(" is_gguf_file_loaded: " + std::string(config_.is_gguf_file_loaded ? "true" : "false"));
333 Logger::info(" use_mmap_for_gguf: " + std::string(config_.use_mmap_for_gguf ? "true" : "false"));
334 // --- BEGIN GGUFData Integrity Check ---
335 if (this->config_.is_gguf_file_loaded && this->gguf_data_) {
336 if (this->gguf_data_->tensor_infos_map.empty()) {
337 Logger::error("[CTOR_GGUF_PRE_INIT_W] CRITICAL: gguf_data_->tensor_infos_map is EMPTY. Weights will not be loaded by map_gguf_weights.");
338 }
339 } else if (this->config_.is_gguf_file_loaded && !this->gguf_data_) {
340 Logger::error("[CTOR_GGUF_PRE_INIT_W] CRITICAL: config_.is_gguf_file_loaded is TRUE, but gguf_data_ pointer IS NULL. Weights cannot be loaded.");
341 } else if (!this->config_.is_gguf_file_loaded) {
342 Logger::info("[CTOR_GGUF_PRE_INIT_W] Not a GGUF file load context (e.g., SafeTensors). Skipping gguf_data_ check here.");
343 }
344 // --- END GGUFData Integrity Check ---
345 if (this->ir_only_) {
346 // No llama layers to place: the interpreter owns the weights and picks its
347 // own device from the CLI request (api.cpp).
349 Logger::info("TinyLlamaModel: IR-only GGUF, weights left to the interpreter.");
350 return;
351 }
352 initialize_weights(loader.get(), this->gguf_data_.get());
354
355 Logger::info("TinyLlamaModel (from path string) constructed and initialized successfully.");
356}
358 std::unique_ptr<GGUFData> gguf_data_from_session)
359 : config_(config_from_session),
360 gguf_data_(std::move(gguf_data_from_session)),
361 model_path_("loaded_from_gguf_data_memory")
363 // Initialize all CUDA pointers to nullptr as in the other constructor
365#endif
366{
367 Logger::info("TinyLlamaModel constructor entered (with pre-loaded GGUFData). Model path placeholder: " + model_path_);
368 this->config_.is_gguf_file_loaded = true; // Ensure this is set
369
370 if (this->config_.num_cpu_offload_layers < 0) {
372 }
373 if (this->config_.num_hidden_layers > 0 && this->config_.num_cpu_offload_layers > this->config_.num_hidden_layers) {
374 Logger::warning("Requested CPU offload layers (" + std::to_string(this->config_.num_cpu_offload_layers) +
375 ") exceeds total hidden layers (" + std::to_string(this->config_.num_hidden_layers) +
376 "). Clamping to " + std::to_string(this->config_.num_hidden_layers) + " layers on CPU (all CPU).");
378 }
379 Logger::info("TinyLlamaModel (pre-loaded GGUF): Final clamped num_cpu_offload_layers = " + std::to_string(this->config_.num_cpu_offload_layers));
380
381 initialize_weights(nullptr, gguf_data_.get()); // Pass raw GGUFData pointer
383 Logger::info("TinyLlamaModel (with pre-loaded GGUFData) constructed and initialized successfully.");
384}
385
387#ifdef HAS_CUDA
388 // Only perform GPU cleanup if GPU layers were actually used
390 if (active_num_gpu_layers > 0) {
391 Logger::info("Freeing TinyLlamaModel CUDA resources...");
392 if (cublas_handle_) {
395 Logger::error("cuBLAS handle destruction failed with error code: " +
396 std::to_string(cublas_status));
397 }
398 cublas_handle_ = nullptr;
399 Logger::info("cuBLAS handle destroyed.");
400 }
401 } else {
402 // CPU-only mode: just clean up cuBLAS handle if it exists
403 if (cublas_handle_) {
406 Logger::error("cuBLAS handle destruction failed with error code: " +
407 std::to_string(cublas_status));
408 }
409 cublas_handle_ = nullptr;
410 }
411 }
412 // Continue GPU cleanup only if GPU layers were active
413 if (active_num_gpu_layers > 0) {
414 if (final_norm_dev) {
416 final_norm_dev = nullptr;
417 }
418
419 for (auto& layer : layers) {
420 if (layer.input_layernorm_dev) {
421 gpuErrchk(cudaFree(layer.input_layernorm_dev));
422 layer.input_layernorm_dev = nullptr;
423 }
424 if (layer.post_attention_layernorm_dev) {
425 gpuErrchk(cudaFree(layer.post_attention_layernorm_dev));
426 layer.post_attention_layernorm_dev = nullptr;
427 }
428 }
429
430 if (all_freqs_cis_dev) {
432 all_freqs_cis_dev = nullptr;
433 }
437 }
438 if (lm_head_dev_) {
440 lm_head_dev_ = nullptr;
441 }
442 if (w_q_dev_) {
444 w_q_dev_ = nullptr;
445 }
446 if (w_k_dev_) {
448 w_k_dev_ = nullptr;
449 }
450 if (w_v_dev_) {
452 w_v_dev_ = nullptr;
453 }
454 if (w_o_dev_) {
456 w_o_dev_ = nullptr;
457 }
458 if (w_gate_dev_) {
460 w_gate_dev_ = nullptr;
461 }
462 if (w_up_dev_) {
464 w_up_dev_ = nullptr;
465 }
466 if (w_down_dev_) {
468 w_down_dev_ = nullptr;
469 }
473 }
474 if (lm_head_f32_dev_) {
476 lm_head_f32_dev_ = nullptr;
477 }
478 if (w_q_f32_dev_) {
480 w_q_f32_dev_ = nullptr;
481 }
482 if (w_k_f32_dev_) {
484 w_k_f32_dev_ = nullptr;
485 }
486 if (w_v_f32_dev_) {
488 w_v_f32_dev_ = nullptr;
489 }
490 if (w_o_f32_dev_) {
492 w_o_f32_dev_ = nullptr;
493 }
494 if (w_gate_f32_dev_) {
496 w_gate_f32_dev_ = nullptr;
497 }
498 if (w_up_f32_dev_) {
500 w_up_f32_dev_ = nullptr;
501 }
502 if (w_down_f32_dev_) {
504 w_down_f32_dev_ = nullptr;
505 }
508 if (*p) { cudaFree(*p); *p = nullptr; }
509
510 if (x_dev_) {
512 x_dev_ = nullptr;
513 }
514 if (x_norm_dev_) {
516 x_norm_dev_ = nullptr;
517 }
518 if (x_resid1_dev_) {
520 x_resid1_dev_ = nullptr;
521 }
522 if (x_resid2_dev_) {
524 x_resid2_dev_ = nullptr;
525 }
526 if (q_dev_) {
528 q_dev_ = nullptr;
529 }
530 if (k_dev_) {
532 k_dev_ = nullptr;
533 }
534 if (v_dev_) {
536 v_dev_ = nullptr;
537 }
538 if (attn_out_dev_) {
540 attn_out_dev_ = nullptr;
541 }
542 if (attn_proj_dev_) {
544 attn_proj_dev_ = nullptr;
545 }
546 if (gate_vec_dev_) {
548 gate_vec_dev_ = nullptr;
549 }
550 if (up_vec_dev_) {
552 up_vec_dev_ = nullptr;
553 }
554 if (swiglu_vec_dev_) {
556 swiglu_vec_dev_ = nullptr;
557 }
558 if (mlp_down_dev_) {
560 mlp_down_dev_ = nullptr;
561 }
562 if (logits_dev_) {
564 logits_dev_ = nullptr;
565 }
566 // Free KVCache dequantization buffers
570 }
574 }
575 // Free selective KVCache dequantization buffers
579 }
583 }
584
585 // Free persistent batch processing buffers
587
588 Logger::info("Freed persistent GPU workspace buffers.");
589 Logger::info("Finished freeing TinyLlamaModel CUDA weight memory.");
590 } else {
591 Logger::info("CPU-only mode: No GPU resources to free.");
592 }
593#endif
594}
595std::vector<float> TinyLlamaModel::forward(
596 std::vector<float>& input,
598 const std::vector<int>* attention_mask) {
599 Logger::info("[CPU_FWD] Entered. Processing up to layer " + std::to_string(config_.num_cpu_offload_layers -1) + ". Input n_tokens: " + std::to_string(n_tokens));
600
601 int hs = config_.hidden_size;
602 int vs = config_.vocab_size;
606 int head_dim = hs / n_heads;
607 float eps = config_.rms_norm_eps;
609
610 bool log_first_gen_step = (n_tokens == 0);
611 bool log_this_step = log_first_gen_step || (n_tokens == 12) || (n_tokens == 13);
612
613 // Layer processing loop - ONLY for CPU-offloaded layers
614 for (int l = 0; l < config_.num_cpu_offload_layers; ++l) {
615 Logger::info("[CPU_FWD_MEM] Starting layer " + std::to_string(l) + " processing");
616
617 bool log_this_layer = log_this_step && (l == 0); // Log details only for layer 0 on specific steps
618 if (log_this_layer) {
619 Logger::info("[CPU_FWD] ------ START Layer " + std::to_string(l) +
620 " (pos=" + std::to_string(n_tokens) + ") ------");
621 log_vector_summary("Layer " + std::to_string(l) + " Input (input)", input);
622 }
623
624 const auto& lw = layers[l];
625 std::vector<float> x_norm_vec1(hs);
626 const std::vector<float>& w_input_norm_vec =
627 lw.input_layernorm_f32.empty()
628 ? bf16vec_to_float_vec(lw.input_layernorm)
629 : lw.input_layernorm_f32;
631 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": Allocating QKV vectors");
632 std::vector<float> q_vec(hs), k_vec(n_kv_heads * head_dim), v_vec(n_kv_heads * head_dim);
633 bool enable_debug_logging = (l == 0);
634 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": About to ensure_q_proj_dequantized");
636 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": ensure_q_proj_dequantized completed");
637 if (!lw.q_proj_f32.empty()) matvec_f32_f32_vector_cpu(lw.q_proj_f32, x_norm_vec1, q_vec, hs, hs);
638 else if (!lw.q_proj_q8k.empty() && config_.is_gguf_file_loaded) matvec_q8k_f32_vector_cpu(lw.q_proj_q8k, x_norm_vec1, q_vec, hs, hs, enable_debug_logging);
639 else if (!lw.q_proj_q8_0.empty() && config_.is_gguf_file_loaded) matvec_q8_0_f32_vector_cpu(lw.q_proj_q8_0, x_norm_vec1, q_vec, hs, hs, enable_debug_logging);
640 else if (!lw.q_proj_q4k.empty() && config_.is_gguf_file_loaded) matvec_q4k_f32_vector_cpu(lw.q_proj_q4k, x_norm_vec1, q_vec, hs, hs, enable_debug_logging);
641 else if (!lw.q_proj_q6k.empty() && config_.is_gguf_file_loaded) matvec_q6k_f32_vector_cpu(lw.q_proj_q6k, x_norm_vec1, q_vec, hs, hs, enable_debug_logging);
642 else if (!lw.q_proj.empty()) matvec_bf16_f32_vector_cpu(lw.q_proj, x_norm_vec1, q_vec, hs, hs); // BF16 from SafeTensors
643 else throw std::runtime_error("Layer " + std::to_string(l) + ": No Q proj weights (f32, q8k, q8, q4k, q6k, bf16) for CPU");
644
645 // ... K, V projections ...
646 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": About to ensure_k_proj_dequantized");
648 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": ensure_k_proj_dequantized completed");
649 if (!lw.k_proj_f32.empty()) matvec_f32_f32_vector_cpu(lw.k_proj_f32, x_norm_vec1, k_vec, n_kv_heads * head_dim, hs);
654 else if (!lw.k_proj.empty()) matvec_bf16_f32_vector_cpu(lw.k_proj, x_norm_vec1, k_vec, n_kv_heads * head_dim, hs);
655 else throw std::runtime_error("Layer " + std::to_string(l) + ": No K proj weights (f32, q8k, q8, q4k, q6k, bf16) for CPU");
656
657 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": About to ensure_v_proj_dequantized");
659 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": ensure_v_proj_dequantized completed");
660 if (!lw.v_proj_f32.empty()) matvec_f32_f32_vector_cpu(lw.v_proj_f32, x_norm_vec1, v_vec, n_kv_heads * head_dim, hs);
665 else if (!lw.v_proj.empty()) matvec_bf16_f32_vector_cpu(lw.v_proj, x_norm_vec1, v_vec, n_kv_heads * head_dim, hs);
666 else throw std::runtime_error("Layer " + std::to_string(l) + ": No V proj weights (f32, q8k, q8, q4k, q6k, bf16) for CPU");
667
670 if (kv_cache) {
671 if (static_cast<size_t>(l) < kv_cache->layers.size()) {
672 KVCacheLayer& kv_layer = kv_cache->layers[l];
673 size_t layer_max_seq_len = static_cast<size_t>(kv_cache->max_seq_len_config_);
674 if (static_cast<size_t>(n_tokens) >= layer_max_seq_len && layer_max_seq_len > 0) {
675 Logger::error("KV Cache access out of bounds in CPU forward. Layer " + std::to_string(l) +
676 ", n_tokens: " + std::to_string(n_tokens) +
677 ", configured layer_max_seq_len: " + std::to_string(layer_max_seq_len) + ". Skipping KV update.");
678 } else if (layer_max_seq_len == 0 && n_tokens > 0) {
679 Logger::error("KV Cache layer_max_seq_len is 0, but n_tokens > 0. Layer " + std::to_string(l) + ". Skipping KV update.");
680 } else {
681 for(int h=0; h < n_kv_heads; ++h) {
682 std::copy(k_vec.begin() + h * head_dim, k_vec.begin() + (h+1) * head_dim, kv_layer.k.begin() + n_tokens * (n_kv_heads * head_dim) + h * head_dim);
683 std::copy(v_vec.begin() + h * head_dim, v_vec.begin() + (h+1) * head_dim, kv_layer.v.begin() + n_tokens * (n_kv_heads * head_dim) + h * head_dim);
684 }
685 }
686 } else {
687 Logger::error("KV Cache layer index " + std::to_string(l) + " out of bounds for kv_cache->layers.size() = " + std::to_string(kv_cache->layers.size()));
688 }
689 }
690
691 std::vector<float> attn_out_vec(hs);
692 std::vector<float> x_resid1_vec = input; // Store residual
693 float att_scale = 1.0f / std::sqrt(static_cast<float>(head_dim));
694 std::fill(attn_out_vec.begin(), attn_out_vec.end(), 0.0f);
695 for (int h = 0; h < n_heads; ++h) {
696 std::vector<float> q_head(head_dim);
697 std::copy(q_vec.begin() + h * head_dim, q_vec.begin() + (h + 1) * head_dim, q_head.begin());
698 std::vector<float> current_multihead_attn_out(head_dim, 0.0f);
699 int kv_cache_num_kv_heads = n_kv_heads; // from KVCache struct if available
701 int kv_head_idx = h / kv_group;
702
703 if (kv_cache && static_cast<size_t>(l) < kv_cache->layers.size()) {
704 const KVCacheLayer& kv_layer = kv_cache->layers[l];
705 int current_seq_len = n_tokens + 1;
706 std::vector<float> scores(current_seq_len);
707 for (int t = 0; t < current_seq_len; ++t) {
708 float score = 0.0f;
709 for (int d = 0; d < head_dim; ++d) {
710 score += q_head[d] * kv_layer.k[t * (n_kv_heads * head_dim) + kv_head_idx * head_dim + d];
711 }
712 scores[t] = score * att_scale;
713 }
714 softmax_vector_cpu(scores, scores); // In-place softmax
715 for (int t = 0; t < current_seq_len; ++t) {
716 for (int d = 0; d < head_dim; ++d) {
718 }
719 }
720 }
721 std::copy(current_multihead_attn_out.begin(), current_multihead_attn_out.end(), attn_out_vec.begin() + h * head_dim);
722 }
723
724
725 std::vector<float> attn_proj_vec(hs);
726 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": About to ensure_o_proj_dequantized");
728 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": ensure_o_proj_dequantized completed");
729 if(!lw.o_proj_f32.empty()) matvec_f32_f32_vector_cpu(lw.o_proj_f32, attn_out_vec, attn_proj_vec, hs, hs);
734 else if(!lw.o_proj.empty()) matvec_bf16_f32_vector_cpu(lw.o_proj, attn_out_vec, attn_proj_vec, hs, hs);
735 else throw std::runtime_error("Layer " + std::to_string(l) + ": No O proj weights (f32, q8k, q8, q4k, q6k, bf16) for CPU");
736
737 for(size_t i=0; i<input.size(); ++i) input[i] = x_resid1_vec[i] + attn_proj_vec[i]; // Update input by reference
738
739 // MLP part
740 std::vector<float> x_norm_vec2(hs);
741 std::vector<float> x_resid2_vec = input; // Store residual for MLP
742 const std::vector<float>& w_post_attn_norm_vec =
743 lw.post_attention_layernorm_f32.empty()
744 ? bf16vec_to_float_vec(lw.post_attention_layernorm)
745 : lw.post_attention_layernorm_f32;
747
748 std::vector<float> mlp_out_vec(hs);
749 if (config_.num_experts > 0 && !lw.moe_gate_exps_f32.empty()) {
750 // Mixture-of-experts FFN (utils.cpp moe_ffn_f32_cpu).
752 lw.moe_router_f32.data(), lw.moe_gate_exps_f32.data(), lw.moe_up_exps_f32.data(),
753 lw.moe_down_exps_f32.data(), mlp_out_vec.data());
754 } else {
755 std::vector<float> gate_vec(is), up_vec(is);
756 // Gate-projection
757 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": About to ensure_gate_proj_dequantized");
759 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": ensure_gate_proj_dequantized completed");
760 if(!lw.gate_proj_f32.empty()) matvec_f32_f32_vector_cpu(lw.gate_proj_f32, x_norm_vec2, gate_vec, is, hs);
761 else if (!lw.gate_proj_q8k.empty() && config_.is_gguf_file_loaded) matvec_q8k_f32_vector_cpu(lw.gate_proj_q8k, x_norm_vec2, gate_vec, is, hs, enable_debug_logging);
762 else if (!lw.gate_proj_q8_0.empty() && config_.is_gguf_file_loaded) matvec_q8_0_f32_vector_cpu(lw.gate_proj_q8_0, x_norm_vec2, gate_vec, is, hs, enable_debug_logging);
763 else if (!lw.gate_proj_q4k.empty() && config_.is_gguf_file_loaded) matvec_q4k_f32_vector_cpu(lw.gate_proj_q4k, x_norm_vec2, gate_vec, is, hs, enable_debug_logging);
764 else if (!lw.gate_proj_q6k.empty() && config_.is_gguf_file_loaded) matvec_q6k_f32_vector_cpu(lw.gate_proj_q6k, x_norm_vec2, gate_vec, is, hs, enable_debug_logging);
765 else if(!lw.gate_proj.empty()) matvec_bf16_f32_vector_cpu(lw.gate_proj, x_norm_vec2, gate_vec, is, hs);
766 else throw std::runtime_error("Layer " + std::to_string(l) + ": No Gate proj weights (f32, q8k, q8, q4k, q6k, bf16) for CPU");
767
768 // Up-projection
769 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": About to ensure_up_proj_dequantized");
771 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": ensure_up_proj_dequantized completed");
772 if(!lw.up_proj_f32.empty()) matvec_f32_f32_vector_cpu(lw.up_proj_f32, x_norm_vec2, up_vec, is, hs);
773 else if (!lw.up_proj_q8k.empty() && config_.is_gguf_file_loaded) matvec_q8k_f32_vector_cpu(lw.up_proj_q8k, x_norm_vec2, up_vec, is, hs, enable_debug_logging);
774 else if (!lw.up_proj_q8_0.empty() && config_.is_gguf_file_loaded) matvec_q8_0_f32_vector_cpu(lw.up_proj_q8_0, x_norm_vec2, up_vec, is, hs, enable_debug_logging);
775 else if (!lw.up_proj_q4k.empty() && config_.is_gguf_file_loaded) matvec_q4k_f32_vector_cpu(lw.up_proj_q4k, x_norm_vec2, up_vec, is, hs, enable_debug_logging);
776 else if (!lw.up_proj_q6k.empty() && config_.is_gguf_file_loaded) matvec_q6k_f32_vector_cpu(lw.up_proj_q6k, x_norm_vec2, up_vec, is, hs, enable_debug_logging);
777 else if(!lw.up_proj.empty()) matvec_bf16_f32_vector_cpu(lw.up_proj, x_norm_vec2, up_vec, is, hs);
778 else throw std::runtime_error("Layer " + std::to_string(l) + ": No Up proj weights (f32, q8k, q8, q4k, q6k, bf16) for CPU");
779
780 // Activation dispatch based on config_.hidden_act
781 std::vector<float> activation_out_vec(is);
782 std::vector<float> mlp_intermediate_vec(is);
783
784 if (config_.hidden_act == "gelu" || config_.hidden_act == "gelu_new" ||
785 config_.hidden_act == "gelu_fast" || config_.hidden_act == "gelu_pytorch_tanh") {
786 // GELU-based activation (Phi models) - no gate projection, just up_proj with GELU
787 // Note: For true Phi support, we'd need to handle missing gate_proj differently
788 // For now, treat as GeGLU (GELU + gate) if gate_proj exists
789 if (!lw.gate_proj_f32.empty() || !lw.gate_proj_q8_0.empty() || !lw.gate_proj_q4k.empty() ||
790 !lw.gate_proj_q6k.empty() || !lw.gate_proj_q8k.empty() || !lw.gate_proj.empty()) {
791 // GeGLU: GELU(gate) * up
793 for(size_t i=0; i<is; ++i) mlp_intermediate_vec[i] = activation_out_vec[i] * up_vec[i];
794 } else {
795 // Standard GELU without gating (true Phi-style)
797 }
798 } else {
799 // Default: SiLU/SwiGLU activation (Llama/Mistral models)
801 for(size_t i=0; i<is; ++i) mlp_intermediate_vec[i] = activation_out_vec[i] * up_vec[i];
802 }
803
804 // Down-projection
805 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": About to ensure_down_proj_dequantized");
807 Logger::info("[CPU_FWD_MEM] Layer " + std::to_string(l) + ": ensure_down_proj_dequantized completed");
808 if(!lw.down_proj_f32.empty()) matvec_f32_f32_vector_cpu(lw.down_proj_f32, mlp_intermediate_vec, mlp_out_vec, hs, is);
813 else if(!lw.down_proj.empty()) matvec_bf16_f32_vector_cpu(lw.down_proj, mlp_intermediate_vec, mlp_out_vec, hs, is);
814 else throw std::runtime_error("Layer " + std::to_string(l) + ": No Down proj weights (f32, q8k, q8, q4k, q6k, bf16) for CPU");
815 } // dense FFN
816
817 for(size_t i=0; i<input.size(); ++i) input[i] = x_resid2_vec[i] + mlp_out_vec[i]; // Update input by reference
818
819 // TINYLLAMA_LAYER_TRACE=1: per-sub-op L2s, format-matched to the
820 // trainer's TCPP_LAYER_TRACE [SUBOP] lines — diff the two on identical
821 // tokens to localize CPU-forward divergence (golden-batch suite).
822 if (std::getenv("TINYLLAMA_LAYER_TRACE") != nullptr) {
823 auto l2 = [](const std::vector<float>& v) {
824 double s = 0.0;
825 for (float x : v) s += double(x) * x;
826 return std::sqrt(s);
827 };
828 fprintf(stderr, "[ENG_SUBOP] L%d pos=%d ln1=%g Vraw=%g attnout=%g attn=%g res1=%g ln2=%g ffn=%g out=%g\n",
830 l2(x_norm_vec2), l2(mlp_out_vec), l2(input));
831 }
832
833 if (log_this_layer) {
834 Logger::info("[CPU_FWD] ------ END Layer " + std::to_string(l) +
835 " (pos=" + std::to_string(n_tokens) + ") ------");
836 }
838 int layer_to_clear = l - 2;
842 }
843 }
844 }
845
847 Logger::info("[CPU_FWD] All layers processed on CPU. Performing final RMSNorm and Logits.");
848 const std::vector<float>& w_final_norm_vec =
851 std::vector<float> x_final_norm_vec(hs);
853
854 std::vector<float> logits(vs);
856 bool enable_lm_head_debug_logging = true; // Always log LM head for debugging
862 else if (!lm_head.empty()) matvec_bf16_f32_vector_cpu(lm_head, x_final_norm_vec, logits, vs, hs); // Fallback for BF16 SafeTensors
863 else throw std::runtime_error("No valid LM Head weights (f32, q8k, q8, q4k, q6k, bf16) found for CPU final stage.");
864
866 log_vector_summary("[CPU_FWD] Final Logits (all CPU, pos=" + std::to_string(n_tokens) + ")", logits, 15);
867 }
868 return logits; // Return final logits if all layers were CPU
869 }
870
871 Logger::info("[CPU_FWD] Finished processing " + std::to_string(config_.num_cpu_offload_layers) + " CPU layers. Output is intermediate activation.");
872 return input; // Return the intermediate activations if not all layers were processed here.
873}
874
875#ifdef HAS_CUDA
876std::vector<float> TinyLlamaModel::forward_device(
877 float* x_input_dev,
878 int pos, KVCache* kv_cache,
879 const std::vector<int>* attention_mask, cudaStream_t stream) {
880
881 int hs = config_.hidden_size;
882 int vs = config_.vocab_size;
885 if (n_heads == 0) {
886 Logger::fatal("Number of attention heads is zero during forward_device.");
887 throw std::runtime_error("Division by zero: n_heads is zero.");
888 }
889 int head_dim = hs / n_heads;
893
894 if (num_gpu_layers <= 0) {
895 Logger::warning("forward_device called with no GPU layers to process (num_gpu_layers = " + std::to_string(num_gpu_layers) + "). Returning empty.");
896 return {};
897 }
898 if (!x_input_dev) {
899 Logger::error("forward_device called with null x_input_dev. This should be model_->x_dev_.");
900 return {};
901 }
902 if (!kv_cache) {
903 Logger::error("forward_device called with null KVCache.");
904 return {};
905 }
906
908 float eps = config_.rms_norm_eps;
909
910 // EMBER_DECODE_TIMING=1: sync and charge the elapsed wall time to a phase.
911 static const bool decode_timing = std::getenv("EMBER_DECODE_TIMING") != nullptr;
912 auto t_mark = std::chrono::steady_clock::now();
913 auto phase = [&](const char* name) {
914 if (!decode_timing) return;
916 const auto now = std::chrono::steady_clock::now();
917 decode_phase_ms_[name] += std::chrono::duration<double, std::milli>(now - t_mark).count();
918 t_mark = now;
919 };
920
921 // (A debug copy of the input back to the host, plus a stream sync, used
922 // to run here on every decode step; nothing read it.)
924 phase("entry");
925 // EMBER_DECODE_OPT=0 selects the standard rmsnorm/attention kernels for
926 // decode (measurement knob; the optimized ones are the default).
927 static const bool use_opt_kernels = [&] {
928 const char* v = std::getenv("EMBER_DECODE_OPT");
929 return config_.use_optimized_cuda_kernels && !(v && std::string(v) == "0");
930 }();
931
933 Logger::error("cublasSetStream failed in forward_device");
934 return {};
935 }
936 float* current_x_dev = x_input_dev;
937 for (int l_gpu_idx = 0; l_gpu_idx < num_gpu_layers; ++l_gpu_idx) {
939
940 // Layer-specific norm weights are indexed by the model layer index (l_model_idx)
941 const float* lw_in_norm_dev = layers[l_model_idx].input_layernorm_dev;
942 const float* lw_post_norm_dev = layers[l_model_idx].post_attention_layernorm_dev;
943
946
947 if (!lw_in_norm_dev) {
948 throw std::runtime_error("[TM::fw_dev pos=" + std::to_string(pos) + " L" + std::to_string(l_model_idx) + "] Error: input_layernorm_dev is nullptr. GPU layer cannot proceed.");
949 }
950
951 // Use optimized kernels if enabled, fallback to standard if needed
952 if (use_opt_kernels) {
954 } else {
956 }
957 phase("layer.norm1");
958 // Use concatenated weights for optimal performance
960 phase("layer.ensure_weights");
961
963 const float* w_q_layer_ptr = w_q_f32_dev_ + (size_t)l_gpu_idx * hs * hs;
966
968 q_dev_, hs, hs, stream);
973 } else {
974 Logger::error("GPU L" + std::to_string(l_model_idx) + " (gpu_idx " + std::to_string(l_gpu_idx) + "): No valid concatenated QKV weights."); return {};
975 }
976 phase("layer.qkv");
979
980 // K/V Cache Update Logic
981 if (static_cast<size_t>(l_model_idx) < kv_cache->layers.size()) {
984 for (int kvh = 0; kvh < n_kv_heads; ++kvh) {
985 const float* current_k_head_ptr_fp32 = k_dev_ + kvh * head_dim;
986 const float* current_v_head_ptr_fp32 = v_dev_ + kvh * head_dim;
987
988 size_t token_head_offset_quant = (static_cast<size_t>(pos) * n_kv_heads + kvh) * head_dim;
991
992 size_t scale_offset = static_cast<size_t>(pos) * n_kv_heads + kvh;
993 float* k_scale_target_ptr = current_kv_layer.k_dev_scales + scale_offset;
994 float* v_scale_target_ptr = current_kv_layer.v_dev_scales + scale_offset;
995
1000 }
1001 } else if (kv_cache->allocated_num_kv_heads == n_kv_heads &&
1002 kv_cache->allocated_head_dim == head_dim && pos < kv_cache->allocated_max_seq_len) {
1003 // The cache is [pos][kv_head][dim], so position pos's heads are one
1004 // contiguous run laid out exactly as k_dev_ / v_dev_: one copy each
1005 // instead of a kernel launch per head per tensor.
1006 const size_t row = static_cast<size_t>(n_kv_heads) * head_dim;
1007 gpuErrchk(cudaMemcpyAsync(current_kv_layer.k_dev_fp32 + static_cast<size_t>(pos) * row, k_dev_,
1008 row * sizeof(float), cudaMemcpyDeviceToDevice, stream));
1009 gpuErrchk(cudaMemcpyAsync(current_kv_layer.v_dev_fp32 + static_cast<size_t>(pos) * row, v_dev_,
1010 row * sizeof(float), cudaMemcpyDeviceToDevice, stream));
1011 } else {
1012 for (int kvh = 0; kvh < n_kv_heads; ++kvh) {
1013 const float* current_k_head_ptr = k_dev_ + kvh * head_dim;
1014 const float* current_v_head_ptr = v_dev_ + kvh * head_dim;
1015
1017 kvh, kv_cache->allocated_max_seq_len,
1018 kv_cache->allocated_num_kv_heads,
1019 kv_cache->allocated_head_dim, stream);
1020
1022 kvh, kv_cache->allocated_max_seq_len,
1023 kv_cache->allocated_num_kv_heads,
1024 kv_cache->allocated_head_dim, stream);
1025 }
1026 }
1027
1028 } else {
1029 Logger::error("KVCache layer index " + std::to_string(l_model_idx) + " out of bounds for kv_cache->layers access in forward_device.");
1030 return {};
1031 }
1032
1033 phase("layer.rope_kv");
1034 float scale = 1.0f / SAFE_SQRT(static_cast<float>(head_dim));
1035 const float* attention_k_cache_ptr_dev = nullptr;
1036 const float* attention_v_cache_ptr_dev = nullptr;
1038
1040 Logger::info("[GPU L" + std::to_string(l_model_idx) + "] Using SELECTIVE KVCache dequantization");
1041 } else {
1044 }
1045
1046 float current_attention_scale = 1.0f / sqrtf((float)head_dim);
1047
1051 q_dev_,
1052 attention_kv_layer.k_dev_quantized,
1053 attention_kv_layer.v_dev_quantized,
1054 attention_kv_layer.k_dev_scales,
1055 attention_kv_layer.v_dev_scales,
1060 pos + 1,
1061 head_dim,
1063 kv_cache->allocated_max_seq_len,
1065 stream
1066 );
1067 } else {
1068 // Use optimized kernels if enabled, fallback to standard if needed
1069 if (use_opt_kernels) {
1071 q_dev_,
1076 pos + 1,
1077 head_dim,
1079 kv_cache->allocated_max_seq_len,
1081 stream
1082 );
1083 } else {
1085 q_dev_,
1090 pos + 1,
1091 head_dim,
1093 kv_cache->allocated_max_seq_len,
1095 stream
1096 );
1097 }
1098 }
1099 phase("layer.attention");
1100
1101 if (w_o_f32_dev_) {
1102 const float* lw_o_proj_f32_dev = w_o_f32_dev_ + (size_t)l_gpu_idx * hs * hs;
1104 } else {
1105 Logger::error("GPU L" + std::to_string(l_model_idx) + " (gpu_idx " + std::to_string(l_gpu_idx) + "): No valid O proj weights (FP32/BF16)."); return {};
1106 }
1107
1109
1111
1112 if (!lw_post_norm_dev) { Logger::error("Missing post_attention_layernorm_dev for GPU layer model_idx=" + std::to_string(l_model_idx)); return {}; }
1113
1114 // Use optimized kernels if enabled, fallback to standard if needed
1115 if (use_opt_kernels) {
1117 } else {
1119 }
1120 // (This O-projection / residual / copy / post-norm sequence used to run a
1121 // second time here, recomputing identical values from unchanged inputs.)
1122 phase("layer.o_proj_resid_norm2");
1123
1124 if (config_.num_experts > 0) {
1126 } else {
1128 const float* w_gate_layer_ptr = w_gate_f32_dev_ + (size_t)l_gpu_idx * is * hs;
1129 const float* w_up_layer_ptr = w_up_f32_dev_ + (size_t)l_gpu_idx * is * hs;
1130
1134 up_vec_dev_, is, hs, stream);
1135 } else {
1136 Logger::error("GPU L" + std::to_string(l_model_idx) + ": No valid Gate/Up projection weights.");
1137 return {};
1138 }
1139
1141
1142 if (w_down_f32_dev_) {
1143 const float* w_down_layer_ptr = w_down_f32_dev_ + (size_t)l_gpu_idx * hs * is;
1146 } else {
1147 Logger::error("GPU L" + std::to_string(l_model_idx) + ": No valid Down projection weights.");
1148 return {};
1149 }
1150 } // dense FFN
1152 phase("layer.mlp");
1153
1154 }
1155 // Use optimized kernels if enabled, fallback to standard if needed
1156 if (use_opt_kernels) {
1158 } else {
1160 }
1161 phase("final_norm");
1163 // EMBER_DECODE_LMHEAD=bf16 restores the old BF16 path (measurement knob).
1164 static const bool lmhead_bf16 = [] {
1165 const char* v = std::getenv("EMBER_DECODE_LMHEAD");
1166 return v && std::string(v) == "bf16";
1167 }();
1169 // FP32 LM head, as the batched prefill uses. The BF16 path below
1170 // allocated, converted and freed a full FP32 copy of the head on every
1171 // decode step (a 12.6 MB cudaMalloc/cudaFree pair at vocab 8192, d384),
1172 // and its logits came from BF16-rounded weights.
1174 } else if (lm_head_dev_) {
1176 vs, hs, this->use_bf16_tensor_cores_, stream);
1177 } else {
1178 Logger::error("LM head (lm_head_dev_ for BF16) is null. Cannot calculate logits on GPU.");
1179 return {};
1180 }
1181 phase("lm_head");
1182
1184 std::vector<float> logits(vs);
1185 gpuErrchk(cudaMemcpy(logits.data(), logits_dev_, vs * sizeof(float),
1187 phase("logits_d2h");
1188 return logits;
1189}
1190
1191// Graph decode (19 Oct 2026). forward_device() issues ~90 kernel launches,
1192// cuBLAS calls and syncs per token for a 6-layer model, so a decode step on
1193// a small model is bound by host launch overhead, not by the GPU (the
1194// weights are ~58 MB, a fraction of a millisecond of bandwidth). Here the
1195// same step is captured once as a CUDA graph and replayed per token; the
1196// token id and position live in device memory (decode_step_dev_), read by
1197// the device-position kernels. The kernels and their order are
1198// forward_device()'s optimized path, so the arithmetic is the same.
1199std::vector<float> TinyLlamaModel::forward_device_graph(int token_id, int pos, KVCache* kv_cache) {
1200 static const bool enabled = [] {
1201 const char* v = std::getenv("EMBER_DECODE_GRAPH");
1202 const char* o = std::getenv("EMBER_DECODE_OPT"); // the graph uses the optimized kernels
1203 return !(v && std::string(v) == "0") && !(o && std::string(o) == "0");
1204 }();
1205 if (!enabled || decode_graph_disabled_) return {};
1208 const int L = config_.num_hidden_layers;
1209 if (n_heads == 0) return {};
1210 const int head_dim = hs / n_heads;
1211 const float eps = config_.rms_norm_eps;
1215 return {};
1216 }
1217 if (!kv_cache || static_cast<int>(kv_cache->layers.size()) < L || kv_cache->allocated_num_kv_heads != n_kv ||
1218 kv_cache->allocated_head_dim != head_dim || pos < 0 || pos >= kv_cache->allocated_max_seq_len)
1219 return {};
1222 if (config_.num_experts > 0) moe_scratch(1); // allocated before capture
1227 return {};
1228 }
1229
1230 if (!decode_stream_) {
1231 // Blocking stream: ordered against the legacy default stream the batched
1232 // prefill runs on, so the graph sees the prefill's KV writes.
1234 gpuErrchk(cudaMalloc(&decode_step_dev_, 2 * sizeof(int)));
1235 gpuErrchk(cudaHostAlloc(reinterpret_cast<void**>(&decode_step_host_), 2 * sizeof(int), cudaHostAllocDefault));
1236 gpuErrchk(cudaHostAlloc(reinterpret_cast<void**>(&decode_logits_host_), static_cast<size_t>(vs) * sizeof(float),
1238 // FP32 embedding rows exactly as the per-launch path reads them
1239 // (lookup_embedding on the host; the device table is BF16).
1240 std::vector<float> table(static_cast<size_t>(vs) * hs);
1241 for (int t = 0; t < vs; ++t) {
1242 const std::vector<float> e = lookup_embedding(t);
1243 if (static_cast<int>(e.size()) != hs) {
1245 return {};
1246 }
1247 std::copy(e.begin(), e.end(), table.begin() + static_cast<size_t>(t) * hs);
1248 }
1249 gpuErrchk(cudaMalloc(&decode_embed_f32_dev_, table.size() * sizeof(float)));
1251 }
1252
1253 const void* key = kv_cache->layers[0].k_dev_fp32;
1254 if (decode_graph_exec_ && decode_graph_key_ != key) {
1256 decode_graph_exec_ = nullptr;
1257 }
1258 if (!decode_graph_exec_) {
1265 Logger::warning("[decode graph] stream capture unavailable; using per-launch decode.");
1266 return {};
1267 }
1268 const float scale = 1.0f / sqrtf(static_cast<float>(head_dim));
1269 const int kv_row = n_kv * head_dim;
1271 for (int l = 0; l < L; ++l) {
1272 KVCacheLayer& kvl = kv_cache->layers[l];
1275 decode_matvec(w_q_f32_dev_ + (size_t)l * hs * hs, x_norm_dev_, q_dev_, hs, hs, st);
1280 kv_write_devpos_cuda(kvl.k_dev_fp32, kvl.v_dev_fp32, k_dev_, v_dev_, decode_step_dev_, kv_row, st);
1282 kv_cache->allocated_max_seq_len, n_kv, decode_step_dev_, st);
1287 if (config_.num_experts > 0) {
1289 } else {
1294 }
1296 }
1299 cudaGraph_t graph = nullptr;
1304 if (e != cudaSuccess) {
1306 decode_graph_exec_ = nullptr;
1308 Logger::warning(std::string("[decode graph] capture failed (") + cudaGetErrorString(e) +
1309 "); using per-launch decode.");
1310 return {};
1311 }
1312 decode_graph_key_ = key;
1313 Logger::info("[decode graph] captured the decode step (" + std::to_string(L) + " layers).");
1314 }
1315
1321 gpuErrchk(cudaMemcpyAsync(decode_logits_host_, logits_dev_, static_cast<size_t>(vs) * sizeof(float),
1324 return std::vector<float>(decode_logits_host_, decode_logits_host_ + vs);
1325}
1326
1327#endif // HAS_CUDA
1328
1330 const std::vector<float>& final_batch_activations, // [num_tokens, hidden_size]
1331 int num_tokens_in_batch) {
1332
1334 Logger::error("[CPU_LOGITS_BATCH] final_batch_activations size mismatch. Expected: " +
1335 std::to_string((size_t)num_tokens_in_batch * config_.hidden_size) + " Got: " +
1336 std::to_string(final_batch_activations.size()));
1337 return {};
1338 }
1339
1340 int hs = config_.hidden_size;
1341 int vs = config_.vocab_size;
1342 float eps = config_.rms_norm_eps;
1343
1344 // 1. Final RMSNorm
1345 std::vector<float> final_batch_norm_out(num_tokens_in_batch * hs);
1346 const std::vector<float>& w_final_norm_vec =
1349 if (w_final_norm_vec.empty()) {
1350 Logger::error("[CPU_LOGITS_BATCH] Final RMSNorm weights are empty (neither f32 nor bf16 available).");
1351 return {};
1352 }
1353
1356
1357 // 2. Batched LM Head multiplication: from the f32 copy, as forward() does (a Q5_K head has no
1358 // kernel of its own, and every quantised head then gives the decode step's values).
1360 std::vector<float> batch_logits_out(num_tokens_in_batch * vs);
1361
1362 if (!lm_head_f32.empty()) {
1363 Logger::info("[CPU_LOGITS_BATCH] Using F32 LM Head weights.");
1366 } else if (!lm_head_q8_0.empty() && config_.is_gguf_file_loaded) {
1367 Logger::info("[CPU_LOGITS_BATCH] Using Q8_0 LM Head weights.");
1370 } else if (!lm_head_q6k.empty() && config_.is_gguf_file_loaded) {
1371 Logger::info("[CPU_LOGITS_BATCH] Using Q6_K LM Head weights.");
1374 } else if (!lm_head_q4k.empty() && config_.is_gguf_file_loaded) {
1375 Logger::info("[CPU_LOGITS_BATCH] Using Q4_K LM Head weights.");
1378 } else if (!lm_head.empty()) { // BF16 SafeTensors weights
1379 Logger::info("[CPU_LOGITS_BATCH] Using BF16 LM Head weights (converting to F32 for matmul).");
1380 std::vector<float> lm_head_f32_temp = bf16vec_to_float_vec(lm_head);
1381 if (lm_head_f32_temp.empty()) {
1382 Logger::error("[CPU_LOGITS_BATCH] Failed to convert BF16 LM Head to F32.");
1383 return {};
1384 }
1387 } else {
1388 Logger::error("[CPU_LOGITS_BATCH] No valid LM Head weights found (F32, Q8_0, Q6_K, Q4_K, BF16).");
1389 return {};
1390 }
1391
1392 return batch_logits_out;
1393}
1394
1395std::vector<std::vector<float>> TinyLlamaModel::forward_cpu_batch_generation(
1396 const std::vector<float>& batch_input_activations, // [num_tokens, hidden_size]
1397 const std::vector<int>& token_positions, // Position of each token in its respective sequence
1398 const std::vector<int>& original_sequence_indices, // Original sequence index for each token
1400 KVCache* kv_cache) {
1401
1402 Logger::info("[CPU_BATCH_GEN] Entry: num_tokens=" + std::to_string(num_tokens_in_batch));
1403 std::string pos_str = "token_positions=[";
1404 for (int i = 0; i < std::min(num_tokens_in_batch, 3); ++i) {
1405 pos_str += std::to_string(token_positions[i]) + " ";
1406 }
1407 pos_str += "]";
1408 std::string seq_str = "original_sequence_indices=[";
1409 for (int i = 0; i < std::min(num_tokens_in_batch, 3); ++i) {
1410 seq_str += std::to_string(original_sequence_indices[i]) + " ";
1411 }
1412 seq_str += "]";
1413 Logger::info("[CPU_BATCH_GEN] " + pos_str + ", " + seq_str);
1415 Logger::error("[CPU_BATCH_GENERATION] batch_input_activations size mismatch. Expected: " +
1416 std::to_string((size_t)num_tokens_in_batch * config_.hidden_size) + " Got: " +
1417 std::to_string(batch_input_activations.size()));
1418 return {};
1419 }
1420
1421 if (token_positions.size() != static_cast<size_t>(num_tokens_in_batch)) {
1422 Logger::error("[CPU_BATCH_GENERATION] token_positions size mismatch. Expected: " +
1423 std::to_string(num_tokens_in_batch) + " Got: " + std::to_string(token_positions.size()));
1424 return {};
1425 }
1426
1427 int hs = config_.hidden_size;
1431 if (n_heads == 0) {
1432 Logger::error("[CPU_BATCH_GENERATION] Error: num_attention_heads is zero.");
1433 return {};
1434 }
1435 int head_dim = hs / n_heads;
1436 float eps = config_.rms_norm_eps;
1439 float attention_scale = 1.0f / SAFE_SQRT(static_cast<float>(head_dim));
1440 int vs = config_.vocab_size;
1441 int kv_group = n_heads / n_kv_heads; // Pre-calculate GQA grouping
1442
1444 for (int l = 0; l < config_.num_cpu_offload_layers; ++l) {
1445 const auto& lw = layers[l];
1446
1447 // Batch RMSNorm for attention
1448 std::vector<float> batch_x_norm1(current_batch_activations.size());
1449 const std::vector<float>& w_input_norm_vec =
1450 lw.input_layernorm_f32.empty()
1451 ? bf16vec_to_float_vec(lw.input_layernorm)
1452 : lw.input_layernorm_f32;
1454
1456
1457 // Batch Q, K, V projections
1458 std::vector<float> q_batch((size_t)num_tokens_in_batch * hs);
1459 std::vector<float> k_batch((size_t)num_tokens_in_batch * n_kv_heads * head_dim);
1460 std::vector<float> v_batch((size_t)num_tokens_in_batch * n_kv_heads * head_dim);
1461
1462 // Q Projection (batched)
1463 if (!lw.q_proj_f32.empty()) {
1465 } else if (!lw.q_proj_q8_0.empty()) {
1467 } else if (!lw.q_proj_q6k.empty()) {
1469 } else if (!lw.q_proj_q4k.empty()) {
1471 } else {
1472 Logger::error("[CPU_BATCH_GENERATION] Layer " + std::to_string(l) + ": No Q proj weights found for CPU (batched)");
1473 return {};
1474 }
1475
1476 // K Projection (batched)
1477 if (!lw.k_proj_f32.empty()) {
1479 } else if (!lw.k_proj_q8_0.empty()) {
1481 } else if (!lw.k_proj_q6k.empty()) {
1483 } else if (!lw.k_proj_q4k.empty()) {
1485 } else {
1486 Logger::error("[CPU_BATCH_GENERATION] Layer " + std::to_string(l) + ": No K proj weights found for CPU (batched)");
1487 return {};
1488 }
1489
1490 // V Projection (batched)
1491 if (!lw.v_proj_f32.empty()) {
1493 } else if (!lw.v_proj_q8_0.empty()) {
1495 } else if (!lw.v_proj_q6k.empty()) {
1497 } else if (!lw.v_proj_q4k.empty()) {
1499 } else {
1500 Logger::error("[CPU_BATCH_GENERATION] Layer " + std::to_string(l) + ": No V proj weights found for CPU (batched)");
1501 return {};
1502 }
1503
1504 // Optimized RoPE, KV cache update, and attention with OpenMP and SIMD
1505 std::vector<float> batch_attn_output((size_t)num_tokens_in_batch * hs);
1506
1507 // Ensure weights are dequantized before parallel processing
1515
1516 #pragma omp parallel if(num_tokens_in_batch > 1)
1517 {
1518 // Thread-local buffers to avoid allocations in loop
1519 std::vector<float> q_token(hs);
1520 std::vector<float> k_token(n_kv_heads * head_dim);
1521 std::vector<float> v_token(n_kv_heads * head_dim);
1522 std::vector<float> scores_buffer;
1523
1524 #pragma omp for
1525 for (int token_idx = 0; token_idx < num_tokens_in_batch; ++token_idx) {
1527
1528 // Extract Q, K, V for this token (reuse thread-local buffers)
1529 std::copy(q_batch.begin() + (size_t)token_idx * hs,
1530 q_batch.begin() + (size_t)(token_idx + 1) * hs,
1531 q_token.begin());
1532 std::copy(k_batch.begin() + (size_t)token_idx * n_kv_heads * head_dim,
1533 k_batch.begin() + (size_t)(token_idx + 1) * n_kv_heads * head_dim,
1534 k_token.begin());
1535 std::copy(v_batch.begin() + (size_t)token_idx * n_kv_heads * head_dim,
1536 v_batch.begin() + (size_t)(token_idx + 1) * n_kv_heads * head_dim,
1537 v_token.begin());
1538
1539 // Apply RoPE individually
1542
1543 // Update KV cache at specific position - Sequence-Major Layout
1544 if (kv_cache && static_cast<size_t>(l) < kv_cache->layers.size()) {
1545 auto& layer_cache = kv_cache->layers[l];
1546 // Use sequence-major layout to match prefill behavior
1548 int sequence_base_offset = seq_idx * kv_cache->max_seq_len_config_;
1550 #pragma omp critical
1551 {
1552 if (kv_offset + n_kv_heads * head_dim <= static_cast<int>(layer_cache.k.size())) {
1553 std::copy(k_token.begin(), k_token.end(), layer_cache.k.begin() + kv_offset);
1554 std::copy(v_token.begin(), v_token.end(), layer_cache.v.begin() + kv_offset);
1555 }
1556 }
1557 }
1558
1559 // Copy back RoPE'd Q values to batch
1560 std::copy(q_token.begin(), q_token.end(), q_batch.begin() + (size_t)token_idx * hs);
1561
1562 // SIMD-optimized attention computation for this token
1564 int history_len = (seq_idx < kv_cache->current_batch_size) ? kv_cache->batch_seq_lens[seq_idx] : pos + 1;
1565 scores_buffer.resize(history_len);
1566
1567 const float* q_token_ptr = q_batch.data() + (size_t)token_idx * hs;
1569
1570 if (kv_cache && static_cast<size_t>(l) < kv_cache->layers.size()) {
1571 const auto& layer_cache = kv_cache->layers[l];
1572
1573 // Process all heads for this token efficiently with SIMD
1574 for (int h = 0; h < n_heads; ++h) {
1575 int kv_head_idx = h / kv_group; // Use pre-calculated kv_group
1576 const float* q_head_ptr = q_token_ptr + h * head_dim;
1578
1579 // SIMD-optimized attention score computation
1580 for (int t = 0; t < history_len; ++t) {
1581 // sequence-major layout: each sequence has contiguous region
1583 int sequence_base_offset = seq_idx * kv_cache->max_seq_len_config_;
1584 const float* k_ptr = layer_cache.k.data() + (sequence_base_offset + t) * n_kv_heads * head_dim + kv_head_idx * head_dim;
1585
1586 // Use SIMD dot product for Q·K computation
1587#if defined(__AVX2__) || defined(__SSE2__) || defined(__ARM_NEON)
1588 float score = simd_dot_product(q_head_ptr, k_ptr, head_dim);
1589#else
1590 float score = 0.0f;
1591 for (int d = 0; d < head_dim; ++d) {
1592 score += q_head_ptr[d] * k_ptr[d];
1593 }
1594#endif
1595 scores_buffer[t] = score * attention_scale;
1596 }
1597
1598 // Softmax
1600
1601 // SIMD-optimized weighted sum with V
1602 std::fill(head_output_ptr, head_output_ptr + head_dim, 0.0f);
1603 for (int t = 0; t < history_len; ++t) {
1604 // Sequence-major layout: each sequence has contiguous region
1606 int sequence_base_offset = seq_idx * kv_cache->max_seq_len_config_;
1607 const float* v_ptr = layer_cache.v.data() + (sequence_base_offset + t) * n_kv_heads * head_dim + kv_head_idx * head_dim;
1608 float score = scores_buffer[t];
1609
1610 // Use SIMD scaled vector addition for score * V accumulation
1611#if defined(__AVX2__) || defined(__SSE2__) || defined(__ARM_NEON)
1613#else
1614 for (int d = 0; d < head_dim; ++d) {
1615 head_output_ptr[d] += score * v_ptr[d];
1616 }
1617#endif
1618 }
1619 }
1620 } else {
1621 std::fill(attn_output_ptr, attn_output_ptr + hs, 0.0f);
1622 }
1623 }
1624 }
1625 // O-Projection (batched)
1626 std::vector<float> batch_attn_proj_out((size_t)num_tokens_in_batch * hs);
1627 if(!lw.o_proj_f32.empty()) {
1629 } else if (!lw.o_proj_q8_0.empty()) {
1631 } else if (!lw.o_proj_q6k.empty()) {
1633 } else if (!lw.o_proj_q4k.empty()) {
1635 } else {
1636 Logger::error("[CPU_BATCH_GENERATION] Layer " + std::to_string(l) + ": No O proj weights found for CPU");
1637 return {};
1638 }
1639
1640 // First Residual Connection (batched)
1641 for(size_t i=0; i < current_batch_activations.size(); ++i) {
1643 }
1644
1645 // MLP processing (batched where possible)
1647 std::vector<float> batch_x_norm2(current_batch_activations.size());
1648
1649 const std::vector<float>& w_post_attn_norm_vec =
1650 lw.post_attention_layernorm_f32.empty()
1651 ? bf16vec_to_float_vec(lw.post_attention_layernorm)
1652 : lw.post_attention_layernorm_f32;
1654
1655 std::vector<float> batch_gate_proj_out((size_t)num_tokens_in_batch * is);
1656 std::vector<float> batch_up_proj_out((size_t)num_tokens_in_batch * is);
1657
1658 // Gate and Up projections (batched)
1659 if (!lw.gate_proj_f32.empty()) {
1661 } else if (!lw.gate_proj_q8_0.empty()) {
1663 } else if (!lw.gate_proj_q6k.empty()) {
1665 } else if (!lw.gate_proj_q4k.empty()) {
1667 } else {
1668 Logger::error("[CPU_BATCH_GENERATION] Layer " + std::to_string(l) + ": No gate_proj weights found for CPU");
1669 return {};
1670 }
1671
1672 if (!lw.up_proj_f32.empty()) {
1674 } else if (!lw.up_proj_q8_0.empty()) {
1676 } else if (!lw.up_proj_q6k.empty()) {
1678 } else if (!lw.up_proj_q4k.empty()) {
1680 } else {
1681 Logger::error("[CPU_BATCH_GENERATION] Layer " + std::to_string(l) + ": No up_proj weights found for CPU");
1682 return {};
1683 }
1684
1685 // SwiGLU (batched)
1686 std::vector<float> batch_swiglu_out((size_t)num_tokens_in_batch * is);
1687 for (size_t i = 0; i < batch_gate_proj_out.size(); ++i) {
1689 float silu_gate_val = gate_val / (1.0f + std::exp(-gate_val));
1691 }
1692
1693 // Down Projection (batched)
1694 std::vector<float> batch_mlp_down_proj_out((size_t)num_tokens_in_batch * hs);
1695 if (!lw.down_proj_f32.empty()) {
1697 } else if (!lw.down_proj_q8_0.empty()) {
1699 } else if (!lw.down_proj_q6k.empty()) {
1701 } else if (!lw.down_proj_q4k.empty()) {
1703 } else {
1704 Logger::error("[CPU_BATCH_GENERATION] Layer " + std::to_string(l) + ": No down_proj weights found for CPU");
1705 return {};
1706 }
1707
1708 // Second Residual Connection (batched)
1709 for(size_t i = 0; i < current_batch_activations.size(); ++i) {
1711 }
1712 }
1713
1714 // Update KV cache sequence length
1715 if (kv_cache && num_tokens_in_batch > 0) {
1716 // For batch mode, track positions per sequence
1717 if (kv_cache->current_batch_size > 0) {
1718 // Update batch_seq_lens based on the highest position seen for each sequence
1719 std::vector<int> max_positions_per_seq(kv_cache->current_batch_size, -1);
1720
1721 for (int i = 0; i < num_tokens_in_batch; ++i) {
1723 int pos = token_positions[i];
1724
1725 if (seq_idx >= 0 && seq_idx < kv_cache->current_batch_size) {
1727 }
1728 }
1729
1730 // Update batch_seq_lens to reflect new positions (pos + 1 since pos is 0-indexed)
1731 for (int seq_idx = 0; seq_idx < kv_cache->current_batch_size; ++seq_idx) {
1732 if (max_positions_per_seq[seq_idx] >= 0) {
1733 kv_cache->batch_seq_lens[seq_idx] = max_positions_per_seq[seq_idx] + 1;
1734 Logger::info("[CPU_BATCH_GEN] KV Length Max Update: seq_idx=" + std::to_string(seq_idx) +
1735 ", old_batch_seq_len=" + std::to_string(kv_cache->batch_seq_lens[seq_idx]) +
1736 ", new_batch_seq_len=" + std::to_string(max_positions_per_seq[seq_idx] + 1));
1737 }
1738 }
1739 // For single-sequence compatibility, update seq_len to the max
1740 kv_cache->seq_len = *std::max_element(kv_cache->batch_seq_lens.begin(),
1741 kv_cache->batch_seq_lens.begin() + kv_cache->current_batch_size);
1742 } else {
1743 // Fallback for single sequence mode
1744 int max_pos = *std::max_element(token_positions.begin(), token_positions.end());
1745 kv_cache->seq_len = std::max(kv_cache->seq_len, max_pos + 1);
1746 }
1747 }
1748
1749 // Final normalization and logits calculation for ALL tokens
1751
1752 // Convert flat logits to per-token vectors
1753 std::vector<std::vector<float>> all_logits(num_tokens_in_batch, std::vector<float>(vs));
1754 for (int token_idx = 0; token_idx < num_tokens_in_batch; ++token_idx) {
1755 std::copy(batch_logits.begin() + (size_t)token_idx * vs,
1756 batch_logits.begin() + (size_t)(token_idx + 1) * vs,
1757 all_logits[token_idx].begin());
1758 }
1759 return all_logits;
1760}
1761
1762#ifdef HAS_CUDA
1763// EMBER_PREFILL_DEBUG=1 turns the prefill forward's debug device-to-host
1764// dumps back on (first-layer Q/K/V samples, last-token hidden state, and a
1765// per-token x per-KV-head K/V cache dump: two synchronous copies each, i.e.
1766// 2 x tokens x kv_heads blocking copies per prefill, whose strings were never
1767// even logged). Off by default since docs/TTFT.md measured their cost.
1768static bool prefill_debug_enabled() {
1769 static const bool on = std::getenv("EMBER_PREFILL_DEBUG") != nullptr;
1770 return on;
1771}
1772
1773// EMBER_OP_TIMING=1 (docs/TTFT.md): where the prefill forward's GPU time
1774// goes, without a profiler (nsys captured no CUDA activity on Colab).
1775//
1776// A CUDA event is recorded on the stream immediately before and after each
1777// operation. The interval between an op's two events is the GPU time from
1778// its first piece of GPU work to its last; summed per op name over all
1779// layers, it is that op's share. Whatever falls BETWEEN one op's end event
1780// and the next op's begin event is time the GPU waited on the host
1781// (synchronising debug copies, logging, allocation), reported as
1782// "gpu_idle_between_ops". Caveat: host stalls INSIDE an op call (e.g. the
1783// failed BF16 attempt inside each batched GEMM) count toward that op.
1784// Off by default: the events and the end-of-call synchronisation cost a
1785// little, so timed runs never mix with TTFT records.
1786namespace {
1787class PrefillOpTimer {
1788 public:
1790 : stream_(stream), on_(std::getenv("EMBER_OP_TIMING") != nullptr) {}
1791 ~PrefillOpTimer() {
1792 for (auto& op : ops_) {
1793 cudaEventDestroy(op.begin);
1794 cudaEventDestroy(op.end);
1795 }
1796 }
1797 void begin(const char* name) {
1798 if (!on_) return;
1799 Op op{name, nullptr, nullptr};
1800 cudaEventCreate(&op.begin);
1801 cudaEventCreate(&op.end);
1802 cudaEventRecord(op.begin, stream_);
1803 ops_.push_back(op);
1804 }
1805 void end() {
1806 if (on_ && !ops_.empty()) cudaEventRecord(ops_.back().end, stream_);
1807 }
1808 // Per-op totals in ms, plus the idle gaps and the whole span.
1809 std::map<std::string, double> collect() {
1810 std::map<std::string, double> out;
1811 if (!on_ || ops_.empty()) return out;
1812 cudaEventSynchronize(ops_.back().end);
1813 double busy = 0.0, idle = 0.0;
1814 for (size_t i = 0; i < ops_.size(); ++i) {
1815 float ms = 0.0f;
1817 out[ops_[i].name] += ms;
1818 busy += ms;
1819 if (i > 0) {
1820 float gap = 0.0f;
1822 idle += gap;
1823 }
1824 }
1825 float span = 0.0f;
1826 cudaEventElapsedTime(&span, ops_.front().begin, ops_.back().end);
1827 out["gpu_busy_in_ops"] = busy;
1828 out["gpu_idle_between_ops"] = idle;
1829 out["gpu_span"] = span;
1830 return out;
1831 }
1832
1833 private:
1834 struct Op {
1835 const char* name;
1837 };
1838 cudaStream_t stream_;
1839 bool on_;
1840 std::vector<Op> ops_;
1841};
1842} // namespace
1843
1844std::vector<float> TinyLlamaModel::forward_device_batch_prefill(
1845 float* d_batch_input_embeddings, // This is now assumed to be activations *after* CPU layers if any
1847 int current_model_pos, // This should be the starting position *within the KV cache* for the batch
1850 bool all_logits) {
1851
1852 Logger::info("[FWD_DEV_BATCH_PREFILL_ENTRY] num_tokens_in_batch: " + std::to_string(num_tokens_in_batch) +
1853 ", current_model_pos: " + std::to_string(current_model_pos) +
1854 ", d_batch_input_embeddings: " + Logger::ptrToString(d_batch_input_embeddings));
1855
1856 const int hidden_size = config_.hidden_size;
1860 const int vocab_size = config_.vocab_size;
1861
1862 Logger::debug("[FWD_DEV_BATCH_PREFILL_PARAMS] hidden_size: " + std::to_string(hidden_size) +
1863 ", head_dim: " + std::to_string(head_dim) +
1864 ", ffn_intermediate_dim: " + std::to_string(ffn_intermediate_dim) +
1865 ", n_kv_dim: " + std::to_string(n_kv_dim) +
1866 ", vocab_size: " + std::to_string(vocab_size) +
1867 ", num_attention_heads: " + std::to_string(config_.num_attention_heads) +
1868 ", num_key_value_heads: " + std::to_string(config_.num_key_value_heads));
1869
1870 float* d_batch_x_ptr = d_batch_input_embeddings; // Input to the first GPU layer
1872 float* d_batch_q_proj_out;
1873 float* d_batch_k_proj_out;
1874 float* d_batch_v_proj_out;
1884 float* d_batch_layer_output = nullptr;
1885 size_t batch_hidden_size_elems = (size_t)num_tokens_in_batch * hidden_size;
1892
1893 // Assign persistent buffers instead of allocating per forward pass
1901 d_batch_residual_ffn_in = d_persistent_batch_residual_ + num_tokens_in_batch * hidden_size; // Offset for second residual
1902 d_batch_x_norm_out_ffn = d_persistent_batch_norm_out_; // Can reuse norm buffer
1907
1909 d_batch_layer_output = d_persistent_batch_input_; // Can reuse input buffer for layer output
1910 }
1911 const float alpha = 1.0f;
1912 const float beta = 0.0f;
1913
1916 Logger::fatal("cublasSetStream failed in forward_device_batch_prefill");
1917 throw std::runtime_error("cublasSetStream failed");
1918 }
1919 PrefillOpTimer op_timer(stream); // EMBER_OP_TIMING=1 only
1920
1921 Logger::info("[FWD_DEV_BATCH_PREFILL_MAIN_LOOP_ENTRY] num_cpu_offload_layers: " + std::to_string(config_.num_cpu_offload_layers) +
1922 ", total_hidden_layers: " + std::to_string(config_.num_hidden_layers));
1923
1926 Logger::info("[FWD_DEV_BATCH_PREFILL_LAYER_START] Processing Layer: model_idx=" + std::to_string(l_model_idx) + ", gpu_idx=" + std::to_string(l_gpu_idx) +
1927 ". Current d_batch_x_ptr: " + Logger::ptrToString(d_batch_x_ptr));
1928 // Weight dequantization handled by ensure_f32_concatenated_weights_loaded() below
1929 op_timer.begin("residual_copy");
1931 op_timer.end();
1932
1933 op_timer.begin("rmsnorm");
1937 op_timer.end();
1938
1940
1941 const float* w_q_layer_ptr = w_q_f32_dev_ + (size_t)l_gpu_idx * hidden_size * hidden_size;
1942 op_timer.begin("q_gemm");
1943 smart_gemm_batch_cuda(false, true, num_tokens_in_batch, hidden_size, hidden_size, &alpha,
1944 d_batch_x_norm_out_attn, hidden_size, w_q_layer_ptr, hidden_size, &beta,
1945 d_batch_q_proj_out, hidden_size, stream, "Q_PROJ_GEN");
1946 op_timer.end();
1947 Logger::info("[GPU_Q_PROJ] Layer=" + std::to_string(l_model_idx) +
1949 ", weight_ptr=" + Logger::ptrToString(w_q_layer_ptr) +
1950 ", output_ptr=" + Logger::ptrToString(d_batch_q_proj_out));
1951 // Q_PROJ_OUT LOGGING (Unchanged)
1952 if (prefill_debug_enabled() && l_model_idx == config_.num_cpu_offload_layers && num_tokens_in_batch > 0 && hidden_size > 0) { // Existing logging condition
1953 int log_elements_common = std::min(static_cast<int>(head_dim), 3);
1954 if (log_elements_common <= 0 && hidden_size > 0) log_elements_common = std::min(static_cast<int>(hidden_size), 3);
1955 if (log_elements_common > 0) {
1956 std::vector<float> h_sample_t0(log_elements_common);
1959 std::string str_t0 = ""; for(float val : h_sample_t0) { str_t0 += std::to_string(val) + " "; }
1960 Logger::debug("[FWD_DEV_BATCH_PREFILL_LAYER_L" + std::to_string(l_model_idx) + "] Q_PROJ_OUT (T0, H0, first " + std::to_string(log_elements_common) + "): " + str_t0);
1961 if (num_tokens_in_batch > 1) {
1962 std::vector<float> h_sample_t1(log_elements_common);
1965 std::string str_t1 = ""; for(float val : h_sample_t1) { str_t1 += std::to_string(val) + " "; }
1966 Logger::debug("[FWD_DEV_BATCH_PREFILL_LAYER_L" + std::to_string(l_model_idx) + "] Q_PROJ_OUT (T1, H0, first " + std::to_string(log_elements_common) + "): " + str_t1);
1967 }
1968 }
1969 }
1970
1971 const float* w_k_layer_ptr = w_k_f32_dev_ + (size_t)l_gpu_idx * n_kv_dim * hidden_size;
1972 op_timer.begin("kv_gemm");
1973 smart_gemm_batch_cuda(false, true, num_tokens_in_batch, n_kv_dim, hidden_size, &alpha,
1975 d_batch_k_proj_out, n_kv_dim, stream, "K_PROJ_GEN");
1976 op_timer.end();
1977
1978 const float* w_v_layer_ptr = w_v_f32_dev_ + (size_t)l_gpu_idx * n_kv_dim * hidden_size;
1979 op_timer.begin("kv_gemm");
1980 smart_gemm_batch_cuda(false, true, num_tokens_in_batch, n_kv_dim, hidden_size, &alpha,
1982 d_batch_v_proj_out, n_kv_dim, stream, "V_PROJ_GEN");
1983 op_timer.end();
1984
1985 if (prefill_debug_enabled() && l_model_idx == config_.num_cpu_offload_layers && num_tokens_in_batch > 0 && head_dim > 0) { // Existing logging condition
1986 int log_elements_rope = std::min(3, head_dim);
1989 std::vector<float> h_q_pre_rope(log_elements_rope);
1993 std::string str_q_pre_rope = ""; for(float val : h_q_pre_rope) { str_q_pre_rope += std::to_string(val) + " "; }
1994 }
1996 std::vector<float> h_k_pre_rope(log_elements_rope);
2000 std::string str_k_pre_rope = ""; for(float val : h_k_pre_rope) { str_k_pre_rope += std::to_string(val) + " "; }
2001 }
2002 }
2003 }
2004
2005 op_timer.begin("rope");
2009 op_timer.end();
2010
2011 if (prefill_debug_enabled() && l_model_idx == config_.num_cpu_offload_layers && num_tokens_in_batch > 0 && head_dim > 0) { // Existing logging condition
2012 int log_elements_rope = std::min(3, head_dim);
2015 std::vector<float> h_q_post_rope(log_elements_rope);
2018 std::string str_q_post_rope = ""; for(float val : h_q_post_rope) { str_q_post_rope += std::to_string(val) + " "; }
2019 }
2021 std::vector<float> h_k_post_rope(log_elements_rope);
2024 std::string str_k_post_rope = ""; for(float val : h_k_post_rope) { str_k_post_rope += std::to_string(val) + " "; }
2025 }
2026 }
2027 }
2028
2030 int log_elements = std::min(3, head_dim);
2031 if (d_batch_k_proj_out) {
2032 std::vector<float> h_k_log_token0(log_elements);
2034 std::vector<float> h_k_log_token1(log_elements);
2037 }
2038 if (d_batch_v_proj_out) {
2039 std::vector<float> h_v_log_token0(log_elements);
2041 std::vector<float> h_v_log_token1(log_elements);
2044 }
2045 }
2046
2047 float* d_layer_k_cache_ptr = kv_cache->layers[l_model_idx].k_dev_fp32;
2048 float* d_layer_v_cache_ptr = kv_cache->layers[l_model_idx].v_dev_fp32;
2049 op_timer.begin("kv_update");
2051 config_.num_key_value_heads, head_dim, kv_cache->max_seq_len_config_, stream);
2052 op_timer.end();
2053 op_timer.begin("kv_update");
2055 config_.num_key_value_heads, head_dim, kv_cache->max_seq_len_config_, stream);
2056 op_timer.end();
2057
2058 float current_attention_scale = 1.0f / sqrtf((float)head_dim);
2059 op_timer.begin("attention");
2063 kv_cache->max_seq_len_config_, config_.num_attention_heads,
2065 op_timer.end();
2066 const float* w_o_layer_ptr = w_o_f32_dev_ + (size_t)l_gpu_idx * hidden_size * hidden_size;
2067 op_timer.begin("o_gemm");
2068 smart_gemm_batch_cuda(false, true, num_tokens_in_batch, hidden_size, hidden_size, &alpha,
2069 d_batch_attn_heads_concat_out, hidden_size, w_o_layer_ptr, hidden_size, &beta,
2070 d_batch_attn_final_proj_out, hidden_size, stream, "O_PROJ_GEN");
2071 op_timer.end();
2072
2073 if (l_model_idx == config_.num_cpu_offload_layers && num_tokens_in_batch > 1 && hidden_size > 0) { /* ... */ }
2074 op_timer.begin("residual");
2076 num_tokens_in_batch, hidden_size, stream);
2077 op_timer.end();
2078 if (l_model_idx == config_.num_cpu_offload_layers && num_tokens_in_batch > 1 && hidden_size > 0) { /* ... */ }
2079
2080
2081 op_timer.begin("rmsnorm");
2085 op_timer.end();
2086 // RMSNORM_FFN_OUT LOGGING (Unchanged) - Logging Point 3
2087 if (l_model_idx == config_.num_cpu_offload_layers && num_tokens_in_batch > 1 && hidden_size > 0) { /* ... */ }
2088
2089
2090 if (config_.num_experts > 0) {
2091 op_timer.begin("moe_ffn");
2093 op_timer.end();
2094 } else {
2095 const float* w1_layer_ptr = w_gate_f32_dev_ + (size_t)l_gpu_idx * hidden_size * ffn_intermediate_dim;
2096 op_timer.begin("ffn_gemm");
2100 op_timer.end();
2101 // FFN_GATE_PROJ_OUT LOGGING (Unchanged) - Logging Point 4
2103
2104
2105 const float* w3_layer_ptr = w_up_f32_dev_ + (size_t)l_gpu_idx * hidden_size * ffn_intermediate_dim;
2106 op_timer.begin("ffn_gemm");
2110 op_timer.end();
2111 // FFN_UP_PROJ_OUT LOGGING (Unchanged) - Logging Point 5
2113
2114
2115 op_timer.begin("swiglu");
2118 op_timer.end();
2119 // FFN_SWIGLU_OUT LOGGING (Unchanged) - Logging Point 6
2121
2122
2123 const float* w2_layer_ptr = w_down_f32_dev_ + (size_t)l_gpu_idx * ffn_intermediate_dim * hidden_size;
2124 op_timer.begin("ffn_gemm");
2127 d_batch_ffn_down_proj_out, hidden_size, stream, "FFN_DOWN_PROJ_GEN");
2128 op_timer.end();
2129 // FFN_DOWN_PROJ_OUT LOGGING (Unchanged) - Logging Point 7
2130 if (l_model_idx == config_.num_cpu_offload_layers && num_tokens_in_batch > 1 && hidden_size > 0) { /* ... */ }
2131
2132
2133 } // dense FFN
2134 op_timer.begin("residual");
2136 num_tokens_in_batch, hidden_size, stream);
2137 op_timer.end();
2138 // POST_RESIDUAL_FFN LOGGING (Unchanged) - Logging Point 8
2139 if (l_model_idx == config_.num_cpu_offload_layers && num_tokens_in_batch > 1 && hidden_size > 0) { /* ... */ }
2140
2141
2143 Logger::info("[FWD_DEV_BATCH_PREFILL_LAYER_END] Layer " + std::to_string(l_model_idx) + " finished. Next d_batch_x_ptr: " + Logger::ptrToString(d_batch_x_ptr));
2144 }
2145
2147 std::vector<float> h_last_token_hidden_state(config_.hidden_size);
2149
2152 config_.hidden_size * sizeof(float),
2155 Logger::log_vector_stats("[FWD_DEV_BATCH_PREFILL_LAST_TOKEN_HIDDEN_STATE_PRE_FINAL_RMSNORM]", h_last_token_hidden_state, 20);
2156 }
2157 op_timer.begin("rmsnorm_final");
2161 op_timer.end();
2162
2163 if (config_.num_cpu_offload_layers < config_.num_hidden_layers && num_tokens_in_batch > 0 && hidden_size > 0) { /* ... */ }
2164 if (all_logits) {
2165 // Speculative verification: the LM head on every row, with the same
2166 // FP32 matvec as decode's LM head.
2167 float* d_rows_logits;
2168 gpuErrchk(cudaMalloc(&d_rows_logits, (size_t)num_tokens_in_batch * vocab_size * sizeof(float)));
2169 for (int t = 0; t < num_tokens_in_batch; ++t)
2171 d_rows_logits + (size_t)t * vocab_size, vocab_size, hidden_size, stream);
2172 std::vector<float> h_rows((size_t)num_tokens_in_batch * vocab_size);
2174 stream));
2177 last_prefill_op_ms_ = op_timer.collect();
2178 return h_rows;
2179 }
2180 float* d_logits_last_token;
2181 gpuErrchk(cudaMalloc(&d_logits_last_token, (size_t)vocab_size * sizeof(float)));
2182
2183 // Only calculate logits for the last token in the batch for prefill output
2185
2186 op_timer.begin("lm_head");
2188 d_logits_last_token, vocab_size, hidden_size, stream);
2189 op_timer.end();
2190
2191 std::vector<float> h_logits(vocab_size);
2192 gpuErrchk(cudaMemcpyAsync(h_logits.data(), d_logits_last_token, (size_t)vocab_size * sizeof(float),
2195
2196 Logger::log_vector_stats("[FWD_DEV_BATCH_PREFILL_FINAL_LOGITS]", h_logits, 20);
2197
2200 if (static_cast<size_t>(first_gpu_layer_model_idx) < kv_cache->layers.size()) {
2202 const float* d_k_cache_ptr = cache_layer_to_log.k_dev_fp32;
2203 const float* d_v_cache_ptr = cache_layer_to_log.v_dev_fp32;
2206 const int log_elems_kv = std::min(3, head_dim);
2207
2209 for (int tk_idx = 0; tk_idx < num_tokens_in_batch; ++tk_idx) {
2211 if (cache_pos_for_token >= kv_cache->max_seq_len_config_) {
2212 Logger::warning("[KVDUMP_POST_BATCH_PREFILL] L" + std::to_string(first_gpu_layer_model_idx) +
2213 " Token " + std::to_string(tk_idx) + " (CachePos " + std::to_string(cache_pos_for_token) +
2214 ") would be out of bounds (" + std::to_string(kv_cache->max_seq_len_config_) + "). Skipping.");
2215 continue;
2216 }
2217 for (int kvh_idx = 0; kvh_idx < num_kv_h; ++kvh_idx) {
2219
2220 std::vector<float> h_k_dump(log_elems_kv);
2222 std::string str_k_dump = ""; for(float val : h_k_dump) { str_k_dump += std::to_string(val) + " "; }
2223 std::vector<float> h_v_dump(log_elems_kv);
2225 std::string str_v_dump = ""; for(float val : h_v_dump) { str_v_dump += std::to_string(val) + " "; }
2226 }
2227 }
2228 } else {
2229 Logger::warning("[KVDUMP_POST_BATCH_PREFILL] L" + std::to_string(first_gpu_layer_model_idx) +
2230 " cannot log K/V cache: null pointers, log_elems_kv <= 0, or local_n_kv_dim_for_log <=0.");
2231 }
2232 } else {
2233 Logger::warning("[KVDUMP_POST_BATCH_PREFILL] First GPU layer index " + std::to_string(first_gpu_layer_model_idx) +
2234 " out of bounds for kv_cache->layers (size " + std::to_string(kv_cache->layers.size()) + ")");
2235 }
2236 }
2238 last_prefill_op_ms_ = op_timer.collect();
2239 Logger::info("[FWD_DEV_BATCH_PREFILL_EXIT] Function finished.");
2240 return h_logits;
2241}
2242std::vector<std::vector<float>> TinyLlamaModel::forward_device_batch_generation(
2243 float* d_batch_input_embeddings, // Device pointer to [num_tokens_in_batch, config_.hidden_size]
2244 const std::vector<int>& token_positions, // Position of each token in its respective sequence
2245 const std::vector<int>& original_sequence_indices, // Original sequence index for each token
2249 if (config_.num_experts > 0)
2250 throw std::runtime_error("forward_device_batch_generation: mixture-of-experts models are not supported");
2251 Logger::info("[FWD_DEV_BATCH_GENERATION_ENTRY] num_tokens_in_batch: " + std::to_string(num_tokens_in_batch) +
2252 ", d_batch_input_embeddings: " + Logger::ptrToString(d_batch_input_embeddings));
2253
2254 if (token_positions.size() != static_cast<size_t>(num_tokens_in_batch)) {
2255 Logger::error("[FWD_DEV_BATCH_GENERATION] token_positions size mismatch. Expected: " +
2256 std::to_string(num_tokens_in_batch) + " Got: " + std::to_string(token_positions.size()));
2257 return {};
2258 }
2259 if (original_sequence_indices.size() != static_cast<size_t>(num_tokens_in_batch)) {
2260 Logger::error("[CPU_BATCH_GENERATION] original_sequence_indices size mismatch. Expected: " +
2261 std::to_string(num_tokens_in_batch) + " Got: " + std::to_string(original_sequence_indices.size()));
2262 return {};
2263 }
2264 const int hidden_size = config_.hidden_size;
2268 const int vocab_size = config_.vocab_size;
2269
2270 const size_t batch_hidden_size_bytes = (size_t)num_tokens_in_batch * hidden_size * sizeof(float);
2272 const size_t batch_q_size_bytes = (size_t)num_tokens_in_batch * hidden_size * sizeof(float);
2273 const size_t batch_kv_size_bytes = (size_t)num_tokens_in_batch * n_kv_dim * sizeof(float);
2274
2276 float* d_batch_q_proj_out;
2277 float* d_batch_k_proj_out;
2278 float* d_batch_v_proj_out;
2288 float* d_batch_layer_output = nullptr;
2289
2291
2306
2307 const float alpha = 1.0f, beta = 0.0f;
2308
2311 Logger::fatal("cublasSetStream failed in forward_device_batch_generation");
2312 throw std::runtime_error("cublasSetStream failed");
2313 }
2314
2316
2317 Logger::info("[FWD_DEV_BATCH_GENERATION_MAIN_LOOP_ENTRY] num_cpu_offload_layers: " + std::to_string(config_.num_cpu_offload_layers) +
2318 ", total_hidden_layers: " + std::to_string(config_.num_hidden_layers));
2319
2322 Logger::info("[FWD_DEV_BATCH_GENERATION_LAYER_START] Processing Layer: model_idx=" + std::to_string(l_model_idx) + ", gpu_idx=" + std::to_string(l_gpu_idx) +
2323 ". Current d_batch_x_ptr: " + Logger::ptrToString(d_batch_x_ptr));
2324
2326
2331
2332 const float* w_q_layer_ptr = w_q_f32_dev_ + (size_t)l_gpu_idx * hidden_size * hidden_size;
2333 smart_gemm_batch_cuda(false, true, num_tokens_in_batch, hidden_size, hidden_size, &alpha,
2334 d_batch_x_norm_out_attn, hidden_size, w_q_layer_ptr, hidden_size, &beta,
2335 d_batch_q_proj_out, hidden_size, stream, "Q_PROJ_GEN");
2336
2337 const float* w_k_layer_ptr = w_k_f32_dev_ + (size_t)l_gpu_idx * n_kv_dim * hidden_size;
2338 smart_gemm_batch_cuda(false, true, num_tokens_in_batch, n_kv_dim, hidden_size, &alpha,
2340 d_batch_k_proj_out, n_kv_dim, stream, "K_PROJ_GEN");
2341
2342 const float* w_v_layer_ptr = w_v_f32_dev_ + (size_t)l_gpu_idx * n_kv_dim * hidden_size;
2343 smart_gemm_batch_cuda(false, true, num_tokens_in_batch, n_kv_dim, hidden_size, &alpha,
2345 d_batch_v_proj_out, n_kv_dim, stream, "V_PROJ_GEN");
2346
2347 for (int token_idx = 0; token_idx < num_tokens_in_batch; ++token_idx) {
2349
2352
2357 }
2358// Update KV cache for each token at its specific position with sequence-aware offsets
2359float* d_layer_k_cache_ptr = kv_cache->layers[l_model_idx].k_dev_fp32;
2360float* d_layer_v_cache_ptr = kv_cache->layers[l_model_idx].v_dev_fp32;
2361
2365
2366 // Calculate sequence-specific offset in the cache
2367 int sequence_cache_offset = sequence_idx * kv_cache->max_seq_len_config_;
2369
2372
2373 // Update individual positions in the KV cache with sequence-specific offsets
2374 for (int kvh = 0; kvh < config_.num_key_value_heads; ++kvh) {
2375 const float* current_k_head_ptr = k_token_ptr + kvh * head_dim;
2376 const float* current_v_head_ptr = v_token_ptr + kvh * head_dim;
2377
2379 kvh, kv_cache->allocated_max_seq_len * kv_cache->max_batch_size,
2380 kv_cache->allocated_num_kv_heads,
2381 kv_cache->allocated_head_dim, stream);
2382
2384 kvh, kv_cache->allocated_max_seq_len * kv_cache->max_batch_size,
2385 kv_cache->allocated_num_kv_heads,
2386 kv_cache->allocated_head_dim, stream);
2387 }
2388}
2389 for (int token_idx = 0; token_idx < num_tokens_in_batch; ++token_idx) {
2391
2394
2395 float scale = 1.0f / SAFE_SQRT(static_cast<float>(head_dim));
2396
2402 batch_kv_layer.k_dev_quantized,
2403 batch_kv_layer.v_dev_quantized,
2404 batch_kv_layer.k_dev_scales,
2405 batch_kv_layer.v_dev_scales,
2410 current_pos + 1,
2411 head_dim,
2412 scale,
2413 kv_cache->allocated_max_seq_len,
2414 kv_cache->allocated_num_kv_heads,
2415 stream
2416 );
2417 } else {
2418 // Use optimized kernels if enabled, fallback to standard if needed
2422 scale, kv_cache->allocated_max_seq_len, kv_cache->allocated_num_kv_heads, stream);
2423 } else {
2426 scale, kv_cache->allocated_max_seq_len, kv_cache->allocated_num_kv_heads, stream);
2427 }
2428 }
2429 }
2430
2431 const float* w_o_layer_ptr = w_o_f32_dev_ + (size_t)l_gpu_idx * hidden_size * hidden_size;
2432 smart_gemm_batch_cuda(false, true, num_tokens_in_batch, hidden_size, hidden_size, &alpha,
2433 d_batch_attn_heads_concat_out, hidden_size, w_o_layer_ptr, hidden_size, &beta,
2434 d_batch_attn_final_proj_out, hidden_size, stream, "O_PROJ");
2435
2437 num_tokens_in_batch, hidden_size, stream);
2438
2442
2443 const float* w1_layer_ptr = w_gate_f32_dev_ + (size_t)l_gpu_idx * hidden_size * ffn_intermediate_dim;
2447
2448 const float* w3_layer_ptr = w_up_f32_dev_ + (size_t)l_gpu_idx * hidden_size * ffn_intermediate_dim;
2452
2455
2456 const float* w2_layer_ptr = w_down_f32_dev_ + (size_t)l_gpu_idx * ffn_intermediate_dim * hidden_size;
2459 d_batch_ffn_down_proj_out, hidden_size, stream, "FFN_DOWN_PROJ_GEN");
2460
2462 num_tokens_in_batch, hidden_size, stream);
2463
2465 Logger::info("[FWD_DEV_BATCH_GENERATION_LAYER_END] Layer " + std::to_string(l_model_idx) + " finished. Next d_batch_x_ptr: " + Logger::ptrToString(d_batch_x_ptr));
2466 }
2467
2471
2472 // Calculate logits for ALL tokens in the batch (not just the last one)
2473 float* d_logits_batch;
2474 gpuErrchk(cudaMalloc(&d_logits_batch, (size_t)num_tokens_in_batch * vocab_size * sizeof(float)));
2475
2476 // Use GEMM instead of individual MatVec calls for efficiency
2477 smart_gemm_batch_cuda(false, true, num_tokens_in_batch, vocab_size, hidden_size, &alpha,
2478 d_batch_x_norm_out_attn, hidden_size, lm_head_f32_dev_, vocab_size, &beta,
2479 d_logits_batch, vocab_size, stream, "LM_HEAD_GEN");
2480
2481 // Copy logits back to host for all tokens
2482 std::vector<std::vector<float>> all_logits(num_tokens_in_batch, std::vector<float>(vocab_size));
2483 for (int token_idx = 0; token_idx < num_tokens_in_batch; ++token_idx) {
2485 d_logits_batch + (size_t)token_idx * vocab_size,
2486 vocab_size * sizeof(float),
2488 }
2490
2491 Logger::info("[FWD_DEV_BATCH_GENERATION_FINAL_LOGITS] Calculated logits for " + std::to_string(num_tokens_in_batch) + " tokens");
2493 Logger::info("[FWD_DEV_BATCH_GENERATION_EXIT] Function finished.");
2494 return all_logits;
2495}
2496#endif // HAS_CUDA
2497
2499 const std::vector<float>& batch_input_activations, // [num_tokens, hidden_size]
2502 int start_pos_in_sequence, // Starting position of this batch in the overall sequence (for KVCache)
2504 const std::vector<int>& prompt_lengths) {
2505
2506 if (!cpu_batch_processor_) {
2507 cpu_batch_processor_ = std::make_unique<CPUBatchProcessor>(this);
2508 }
2509
2510 return cpu_batch_processor_->forward_cpu_batch(
2515 kv_cache,
2517 );
2518}
2519
2520#ifdef HAS_CUDA
2521// Smart GEMM wrapper that chooses between BF16 Tensor Cores and FP32 based on batch size
2522void TinyLlamaModel::smart_gemm_batch_cuda(bool transa_user, bool transb_user,
2523 int m_user, int n_user, int k_user,
2524 const float* alpha_user,
2525 const float* A_f32_user, int lda_user,
2526 const float* B_f32_user, int ldb_user,
2527 const float* beta_user,
2528 float* C_f32_user, int ldc_user,
2530 const char* operation_name) {
2531
2532 // Tensor Cores are most beneficial for larger batch sizes
2533 const int tensor_core_threshold = 4; // Use BF16 Tensor Cores when batch size >= 4
2536
2537 // Determine which BF16 weight to use based on operation name and weight pointer
2538 uint16_t* bf16_weight_ptr = nullptr;
2539
2540 if (use_tensor_cores) {
2541 Logger::info("[SMART_GEMM] Using BF16 Tensor Cores for " + std::string(operation_name) +
2542 " (batch_size=" + std::to_string(m_user) + " >= " + std::to_string(tensor_core_threshold) + ")");
2543
2544 // Ensure BF16 weights are loaded
2546
2547 // Map F32 weight pointer to corresponding BF16 weight pointer
2548 if (B_f32_user == w_q_f32_dev_) {
2550 } else if (B_f32_user == w_k_f32_dev_) {
2552 } else if (B_f32_user == w_v_f32_dev_) {
2554 } else if (B_f32_user == w_o_f32_dev_) {
2556 } else if (B_f32_user == w_gate_f32_dev_) {
2558 } else if (B_f32_user == w_up_f32_dev_) {
2560 } else if (B_f32_user == w_down_f32_dev_) {
2562 } else {
2563 // Map layer-specific pointers by calculating offset from base pointer
2564 // offset_bytes is measured in the F32 buffer; the BF16 buffer
2565 // holds the same element count, so the element offset is
2566 // offset_bytes / sizeof(float). (Dividing by sizeof(uint16_t)
2567 // doubled it: layer l read layer 2l, and past the buffer end
2568 // for l >= num_layers / 2. Found by the TTFT parity check.)
2569 size_t offset_bytes = 0;
2570 bool found = false;
2571
2572 // Check if it's a layer-specific pointer within the concatenated weights
2574 offset_bytes = (B_f32_user - w_q_f32_dev_) * sizeof(float);
2576 found = true;
2578 offset_bytes = (B_f32_user - w_k_f32_dev_) * sizeof(float);
2580 found = true;
2582 offset_bytes = (B_f32_user - w_v_f32_dev_) * sizeof(float);
2584 found = true;
2586 offset_bytes = (B_f32_user - w_o_f32_dev_) * sizeof(float);
2588 found = true;
2590 offset_bytes = (B_f32_user - w_gate_f32_dev_) * sizeof(float);
2592 found = true;
2594 offset_bytes = (B_f32_user - w_up_f32_dev_) * sizeof(float);
2596 found = true;
2598 offset_bytes = (B_f32_user - w_down_f32_dev_) * sizeof(float);
2600 found = true;
2601 }
2602
2603 if (!found) {
2604 // If we can't identify the weight, fall back to FP32
2605 Logger::warning("[SMART_GEMM] Unknown weight pointer for " + std::string(operation_name) +
2606 ", falling back to FP32");
2607 use_tensor_cores = false;
2608 }
2609 }
2610
2612 try {
2613 // Use mixed precision: FP32 input x BF16 weights = FP32 output
2618 Logger::info("[SMART_GEMM] Successfully used BF16 Tensor Cores for " + std::string(operation_name));
2619 return;
2620 } catch (const std::exception& e) {
2621 // Latch this batched-GEMM path off for the rest of the
2622 // session. A failure here is a property of the GPU/CUDA/
2623 // cuBLAS stack, not of this call: on L4 + CUDA 13 every
2624 // attempt fails, and each retry cost two cudaMalloc, a
2625 // conversion, two synchronising cudaFree and logging, 154
2626 // times per prefill (15-46 ms of TTFT; docs/TTFT.md,
2627 // flash_v1_fp32). Outputs are unchanged: the FP32 path below
2628 // is what ran anyway. A separate flag, so the decode LM-head
2629 // matvec's own BF16 choice (use_bf16_tensor_cores_) is
2630 // untouched.
2631 Logger::warning("[SMART_GEMM] BF16 Tensor Cores failed for " + std::string(operation_name) +
2632 ": " + e.what() + ". Falling back to FP32 and disabling batched BF16 GEMMs for this session.");
2634 use_tensor_cores = false;
2635 }
2636 }
2637 }
2638
2639 // Fallback to standard FP32 GEMM
2640 Logger::info("[SMART_GEMM] Using FP32 GEMM for " + std::string(operation_name) +
2641 " (batch_size=" + std::to_string(m_user) + " < " + std::to_string(tensor_core_threshold) +
2642 " or Tensor Cores unavailable)");
2643 // TF32 tensor-core math for this FP32 GEMM (FP32 inputs, outputs and
2644 // accumulation; products rounded to a 10-bit mantissa). Scoped to this
2645 // call: the math mode is restored right after, so decode matvecs and the
2646 // LM head keep FP32 math. ON by default since docs/TTFT.md measured it
2647 // (lane flash_v2_tf32): 31-61% lower TTFT on the timing model, first-
2648 // token logits within 2.9e-3 relative of the baseline kernel, argmax
2649 // unchanged on every gated prompt. EMBER_GEMM_TF32=0 restores FP32 math.
2650 static const bool tf32 = [] {
2651 const char* v = std::getenv("EMBER_GEMM_TF32");
2652 return v == nullptr || std::string(v) != "0";
2653 }();
2654 const bool tf32_here = tf32 && !gemm_fp32_only_; // POST /score: FP32 math only
2661}
2662#endif // HAS_CUDA
2663
2664#ifdef HAS_CUDA
2665float* TinyLlamaModel::moe_scratch(int T) {
2667 if (T == 1) {
2671 }
2672 const size_t need = moe_ffn_scratch_floats(T, d, d_ff, E, config_.num_experts_used);
2675 gpuErrchk(cudaMalloc(&moe_scratch_batch_dev_, need * sizeof(float)));
2677 }
2679}
2680
2681void TinyLlamaModel::moe_ffn_layer_cuda(int l_gpu_idx, const float* x, int T, float* out, cudaStream_t stream) {
2684 moe_ffn_cuda(cublas_handle_, x, T, d, d_ff, E, k, /*renorm=*/true, w_moe_router_dev_ + (size_t)l_gpu_idx * E * d,
2685 w_moe_gate_dev_ + (size_t)l_gpu_idx * E * d_ff * d, w_moe_up_dev_ + (size_t)l_gpu_idx * E * d_ff * d,
2686 w_moe_down_dev_ + (size_t)l_gpu_idx * E * d * d_ff, moe_scratch(T), out, stream);
2687}
2688#endif
2689
2690#ifdef HAS_CUDA
2691std::vector<float> TinyLlamaModel::forward_device_verify(const std::vector<int>& tokens, int pos, KVCache* kv_cache) {
2692 const int n = static_cast<int>(tokens.size());
2693 constexpr int kMaxRows = 33;
2696 const int head_dim = hs / n_heads, kv_row = n_kv * head_dim;
2697 const float eps = config_.rms_norm_eps, scale = 1.0f / std::sqrt(static_cast<float>(head_dim));
2699 pos + n > kv_cache->allocated_max_seq_len)
2700 return {};
2706 return {};
2707 // Rows: x, xn, resid, q, attn, o, down [hs]; k, v [kv_row]; gate, up [is]; logits [vs].
2708 const size_t row = (size_t)7 * hs + 2 * kv_row + 2 * is + vs;
2709 if (!verify_buf_dev_) gpuErrchk(cudaMalloc(&verify_buf_dev_, row * kMaxRows * sizeof(float)));
2710 float* x = verify_buf_dev_;
2711 float* xn = x + (size_t)kMaxRows * hs;
2712 float* resid = xn + (size_t)kMaxRows * hs;
2713 float* q = resid + (size_t)kMaxRows * hs;
2714 float* attn = q + (size_t)kMaxRows * hs;
2715 float* o = attn + (size_t)kMaxRows * hs;
2716 float* down = o + (size_t)kMaxRows * hs;
2717 float* k = down + (size_t)kMaxRows * hs;
2718 float* v = k + (size_t)kMaxRows * kv_row;
2719 float* gate = v + (size_t)kMaxRows * kv_row;
2720 float* up = gate + (size_t)kMaxRows * is;
2721 float* logits = up + (size_t)kMaxRows * is;
2722 cudaStream_t st = 0;
2723 std::vector<float> emb((size_t)n * hs);
2724 for (int i = 0; i < n; ++i) {
2725 const std::vector<float> e = lookup_embedding(tokens[i]);
2726 if (e.size() != static_cast<size_t>(hs)) return {};
2727 std::copy(e.begin(), e.end(), emb.begin() + (size_t)i * hs);
2728 }
2729 gpuErrchk(cudaMemcpyAsync(x, emb.data(), emb.size() * sizeof(float), cudaMemcpyHostToDevice, st));
2730 for (int l = 0; l < L; ++l) {
2731 KVCacheLayer& kvl = kv_cache->layers[l];
2732 gpuErrchk(cudaMemcpyAsync(resid, x, (size_t)n * hs * sizeof(float), cudaMemcpyDeviceToDevice, st));
2734 gemm_rm_f32_cuda(cublas_handle_, xn, w_q_f32_dev_ + (size_t)l * hs * hs, q, n, hs, hs, st);
2738 update_kv_cache_batch_cuda(kvl.k_dev_fp32, k, pos, n, n_kv, head_dim, kv_cache->max_seq_len_config_, st);
2739 update_kv_cache_batch_cuda(kvl.v_dev_fp32, v, pos, n, n_kv, head_dim, kv_cache->max_seq_len_config_, st);
2740 for (int i = 0; i < n; ++i)
2741 attention_decode_at_cuda(q + (size_t)i * hs, kvl.k_dev_fp32, kvl.v_dev_fp32, attn + (size_t)i * hs, n_heads,
2742 pos + i + 1, head_dim, scale, kv_cache->allocated_max_seq_len, n_kv, st);
2743 gemm_rm_f32_cuda(cublas_handle_, attn, w_o_f32_dev_ + (size_t)l * hs * hs, o, n, hs, hs, st);
2745 gpuErrchk(cudaMemcpyAsync(resid, x, (size_t)n * hs * sizeof(float), cudaMemcpyDeviceToDevice, st));
2747 if (config_.num_experts > 0) {
2749 } else {
2751 gemm_rm_f32_cuda(cublas_handle_, xn, w_up_f32_dev_ + (size_t)l * is * hs, up, n, is, hs, st);
2754 }
2756 }
2759 std::vector<float> out((size_t)n * vs);
2760 gpuErrchk(cudaMemcpyAsync(out.data(), logits, out.size() * sizeof(float), cudaMemcpyDeviceToHost, st));
2763 return out;
2764}
2765#endif
#define SAFE_SQRT(x)
static void log_vector_stats(const std::string &name, const std::vector< float > &v, int n_show=5)
Definition logger.cpp:160
static void debug(const std::string &message)
Definition logger.cpp:131
static void warning(const std::string &message)
Definition logger.cpp:139
static std::string ptrToString(const void *ptr)
Definition logger.cpp:225
static void info(const std::string &message)
Definition logger.cpp:135
static void error(const std::string &message)
Definition logger.cpp:143
static void fatal(const std::string &message)
Definition logger.cpp:151
Main class for loading tensors from SafeTensors format files (single or sharded)
static bool load_model_config_from_json(const std::string &model_path_or_dir, ModelConfig &config_to_populate)
Loads model configuration from a JSON file corresponding to a .safetensors model path.
bool use_bf16_tensor_cores_
Definition model.h:574
~TinyLlamaModel()
Destructor. Cleans up all allocated resources.
Definition model.cpp:386
std::vector< float > lookup_embedding(int token_id)
Lookup the embedding vector for a given token ID.
void ensure_up_proj_dequantized(int layer_idx)
std::vector< float > final_norm_f32
Definition model.h:581
std::vector< uint16_t > final_norm
Definition model.h:580
bool ir_only_
Definition model.h:697
void ensure_v_proj_dequantized(int layer_idx)
std::vector< block_q4_K > lm_head_q4k
Definition model.h:582
std::vector< float > forward_cpu_logits_batch(const std::vector< float > &final_batch_activations, int num_tokens_in_batch)
Definition model.cpp:1329
friend void map_gguf_weights(const GGUFData &gguf, TinyLlamaModel &model)
std::vector< block_q6_K > lm_head_q6k
Definition model.h:584
std::vector< std::pair< float, float > > precomputed_freqs_cis_
Definition model.h:694
bool bf16_batch_gemm_failed_
Definition model.h:575
bool gemm_fp32_only_
Definition model.h:576
TinyLlamaModel(const ModelConfig &config, const SafeTensorsLoader &loader)
Construct a TinyLlamaModel from a SafeTensorsLoader.
Definition model.cpp:143
std::string model_path_
Definition model.h:698
void ensure_embed_tokens_dequantized()
std::vector< LayerWeights > layers
Definition model.h:587
ModelConfig config_
Definition model.h:573
void ensure_o_proj_dequantized(int layer_idx)
void clear_layer_dequantized_weights(int layer_idx)
void ensure_k_proj_dequantized(int layer_idx)
std::unique_ptr< class CPUBatchProcessor > cpu_batch_processor_
Definition model.h:701
std::vector< block_q8_0 > lm_head_q8_0
Definition model.h:585
std::vector< uint16_t > lm_head
Definition model.h:579
void ensure_f32_concatenated_weights_loaded()
std::vector< std::vector< float > > forward_cpu_batch_generation(const std::vector< float > &batch_input_activations, const std::vector< int > &token_positions, const std::vector< int > &original_sequence_indices, int num_tokens_in_batch, KVCache *kv_cache)
Definition model.cpp:1395
void ensure_bf16_concatenated_weights_loaded()
void ensure_q_proj_dequantized(int layer_idx)
void initialize_weights(const SafeTensorsLoader *loader, const GGUFData *gguf)
Definition model.cpp:39
std::vector< float > forward_cpu_batch(const std::vector< float > &batch_input_activations, int num_tokens_in_batch, int num_cpu_layers_to_process, int start_pos_in_sequence, KVCache *kv_cache, const std::vector< int > &prompt_lengths={})
Definition model.cpp:2498
void ensure_down_proj_dequantized(int layer_idx)
void ensure_gate_proj_dequantized(int layer_idx)
std::vector< float > embed_tokens_f32
Definition model.h:581
std::vector< float > forward(std::vector< float > &input, int n_tokens, KVCache *kv_cache, const std::vector< int > *attention_mask)
Run the forward pass for the model on CPU layers.
Definition model.cpp:595
void ensure_lm_head_dequantized()
std::unique_ptr< GGUFData > gguf_data_
Definition model.h:696
std::vector< float > lm_head_f32
Definition model.h:581
std::vector< block_q8_K > lm_head_q8k
Definition model.h:586
void gemm_rm_f32_cuda(cublasHandle_t handle, const float *X, const float *W, float *Y, int T, int O, int I, cudaStream_t stream)
void moe_ffn_cuda(cublasHandle_t handle, const float *x, int T, int d, int d_ff, int E, int k, bool renorm, const float *router, const float *gate_exps, const float *up_exps, const float *down_exps, float *scratch, float *out, cudaStream_t stream)
void decode_embed_cuda(const float *table_dev, float *out_dev, const int *step_dev, int hidden_size, int vocab_size, cudaStream_t stream)
void add_residual_batch_cuda(float *d_output_batch, const float *d_input_a_batch, const float *d_input_b_batch, int num_tokens, int hidden_size, cudaStream_t stream)
void matvec_f32_f32_cuda(cublasHandle_t handle, const float *mat_f32_dev, const float *vec_f32_dev, float *out_f32_dev, int rows, int cols, cudaStream_t stream)
Implementation of matrix-vector multiplication with FP32.
size_t moe_ffn_scratch_floats(int T, int d, int d_ff, int E, int k)
void kv_write_devpos_cuda(float *k_cache_dev, float *v_cache_dev, const float *k_dev, const float *v_dev, const int *step_dev, int row, cudaStream_t stream)
void rope_cuda(float *x_dev, int num_heads, int head_dim, const float *all_freqs_cis_dev_base, int pos, bool use_adjacent_pairing, cudaStream_t stream)
void attention_cuda_selective_dequant(const float *Q_current_dev, const int8_t *K_quantized_cache_base, const int8_t *V_quantized_cache_base, const float *K_scales_cache_base, const float *V_scales_cache_base, float *selective_k_dequant_buffer, float *selective_v_dequant_buffer, float *out_dev, int num_heads, int current_seq_len, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, cudaStream_t stream)
void rope_batch_cuda(float *d_q_batch, float *d_k_batch, const float *d_all_freqs_cis_base, int num_tokens, int num_q_heads, int num_kv_heads, int head_dim, int start_pos_in_kv_cache, bool use_adjacent_pairing, cudaStream_t stream)
void add_residual_cuda(const float *matvec_out_dev, const float *residual_dev, float *result_dev, int n, cudaStream_t stream)
void matvec_bf16_f32_cuda(cublasHandle_t handle, const uint16_t *mat_bf16_dev, const float *vec_f32_dev, float *out_f32_dev, int rows, int cols, bool use_tensor_cores, cudaStream_t stream)
Matrix-vector multiplication with BF16 matrix and FP32 vector/output on GPU.
void swiglu_cuda(const float *gate_dev, const float *up_dev, float *out_dev, int n, cudaStream_t stream)
void rmsnorm_vector_cuda_optimized(const float *x_dev, const float *weight_dev, float *out_dev, int n, float eps, cudaStream_t stream)
Optimized wrappers using the new high-performance kernels.
void attention_devpos_cuda(const float *Q_current_dev, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out_dev, int num_heads, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, const int *step_dev, cudaStream_t stream)
void gemm_f32_to_bf16_f32_cuda(cublasHandle_t handle, bool transa_user, bool transb_user, int m_user, int n_user, int k_user, const float *alpha_user, const float *A_f32_user, int lda_user, const uint16_t *B_bf16_user, int ldb_user, const float *beta_user, float *C_f32_user, int ldc_user, cudaStream_t stream)
void rope_devpos_cuda(float *x_dev, int num_heads, int head_dim, const float *all_freqs_cis_dev_base, const int *step_dev, bool use_adjacent_pairing, cudaStream_t stream)
void swiglu_batch_cuda(float *d_out_batch, const float *d_gate_act_batch, const float *d_up_act_batch, int num_tokens, int intermediate_size, cudaStream_t stream)
void attention_cuda(const float *Q_current_dev, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out_dev, int num_heads, int current_seq_len, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, cudaStream_t stream)
void update_kv_cache_cuda(float *cache_base_ptr, const float *current_kv_head_vector, int pos, int kv_head_idx, int max_seq_len, int num_kv_heads, int head_dim, cudaStream_t stream)
void attention_decode_at_cuda(const float *Q_current_dev, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out_dev, int num_heads, int seq_len, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, cudaStream_t stream)
void rmsnorm_vector_cuda(const float *x_dev, const float *weight_dev, float *out_dev, int n, float eps, cudaStream_t stream)
Implementation of RMS normalization with device pointers.
void attention_cuda_optimized(const float *Q_current_dev, const float *K_layer_cache_base, const float *V_layer_cache_base, float *out_dev, int num_heads, int current_seq_len, int head_dim, float scale, int cache_max_seq_len, int cache_num_kv_heads, cudaStream_t stream)
GGUFData load_gguf_meta(const std::string &filename, bool use_mmap)
Loads GGUF metadata and optionally memory-maps tensor data.
Parser for GGUF (GPT-Generated Unified Format) files.
std::string why
Definition graph_ir.cpp:280
const float * p[kIrDepthSourcesMax]
int n
std::vector< float > v
const char * name
float c
node out
node scale
size_t j
Logging utilities for the TinyLlama implementation.
ModelConfig parse_model_config_from_gguf(const GGUFData &gguf)
void log_vector_summary(const std::string &name, const std::vector< float > &v, int head_count=5)
Definition utils.cpp:208
Constants used throughout the TinyLlama model implementation.
bool tuned_llama_match(const std::string &ir_json, const GGUFData &gguf, std::string *why)
Definition graph_ir.cpp:440
std::string find_ir(const GGUFData &gguf)
Definition graph_ir.cpp:525
int token_id(const std::vector< std::string > &tokens, const std::string &text)
Weight quantization structures and functions for model compression.
SafeTensors format loader for efficient tensor loading, supporting single and sharded models.
Complete representation of a GGUF file's contents.
Key-Value cache for a single transformer layer.
Definition model.h:136
Complete Key-Value cache for all transformer layers.
Definition model.h:157
Model configuration structure holding architecture and hyperparameters.
Definition model.h:82
int hidden_size
Definition model.h:83
int vocab_size
Definition model.h:88
std::string architecture
Definition model.h:98
float rms_norm_eps
Definition model.h:90
int num_attention_heads
Definition model.h:85
bool use_mmap_for_gguf
Definition model.h:104
int intermediate_size
Definition model.h:84
int num_cpu_offload_layers
Definition model.h:106
bool enable_memory_efficient_layers
Definition model.h:111
bool is_gguf_file_loaded
Definition model.h:103
bool use_kvcache_quantization
Definition model.h:105
int num_experts_used
Definition model.h:108
int num_hidden_layers
Definition model.h:87
bool use_optimized_cuda_kernels
Definition model.h:114
int num_key_value_heads
Definition model.h:86
int num_experts
Definition model.h:107
std::string hidden_act
Definition model.h:92
int max_position_embeddings
Definition model.h:89
void apply_rope_vector(std::vector< float > &x, int num_heads, int head_dim, int current_token_pos, const std::vector< std::pair< float, float > > &all_freqs_cis, int max_pos_embeddings, bool use_adjacent_pairing)
Definition utils.cpp:428
void matvec_q8k_f32_vector_cpu(const std::vector< block_q8_K > &mat_q8k, const std::vector< float > &vec_f32, std::vector< float > &out_f32, int rows, int cols, bool log_first_block)
Definition utils.cpp:399
void matvec_q6k_f32_vector_cpu(const std::vector< block_q6_K > &mat_q6k, const std::vector< float > &vec_f32, std::vector< float > &out_f32, int rows, int cols, bool log_first_block)
Definition utils.cpp:930
void matvec_bf16_f32_vector_cpu(const std::vector< uint16_t > &mat_bf16, const std::vector< float > &vec_f32, std::vector< float > &out_f32, int rows, int cols)
Definition utils.cpp:1192
void matvec_f32_f32_vector_cpu(const std::vector< float > &mat_f32, const std::vector< float > &vec_f32, std::vector< float > &out_f32, int rows, int cols)
Definition utils.cpp:365
void simd_scaled_add(float *dst, const float *src, float scale, int n)
Definition utils.cpp:93
void matmul_q4k_f32_batch_cpu(const std::vector< block_q4_K > &mat_q4k, const std::vector< float > &batch_input_activations, std::vector< float > &batch_output_activations, int num_tokens, int output_dim, int input_dim)
Definition utils.cpp:1155
std::vector< float > bf16vec_to_float_vec(const std::vector< uint16_t > &v_bf16)
Definition utils.cpp:199
void softmax_vector_cpu(const std::vector< float > &x, std::vector< float > &out)
Definition utils.cpp:675
float simd_dot_product(const float *a, const float *b, int n)
Definition utils.cpp:36
void matvec_q4k_f32_vector_cpu(const std::vector< block_q4_K > &mat_q4k, const std::vector< float > &vec_f32, std::vector< float > &out_f32, int rows, int cols, bool log_first_block)
Definition utils.cpp:983
void matmul_q8_0_f32_batch_cpu(const std::vector< block_q8_0 > &mat_q8_0, const std::vector< float > &batch_input_activations, std::vector< float > &batch_output_activations, int num_tokens, int output_dim, int input_dim)
Definition utils.cpp:1036
void matmul_f32_f32_batch_cpu(const std::vector< float > &mat_weights, const std::vector< float > &batch_input_activations, std::vector< float > &batch_output_activations, int num_tokens, int output_dim, int input_dim)
Definition utils.cpp:872
void gelu_new_cpu(const std::vector< float > &x, std::vector< float > &out)
Definition utils.cpp:860
void silu_cpu(const std::vector< float > &x, std::vector< float > &out)
Definition utils.cpp:842
void matmul_q6k_f32_batch_cpu(const std::vector< block_q6_K > &mat_q6k, const std::vector< float > &batch_input_activations, std::vector< float > &batch_output_activations, int num_tokens, int output_dim, int input_dim)
Definition utils.cpp:1117
void moe_ffn_f32_cpu(const float *x, int d, int d_ff, int E, int k, const float *router, const float *gate_exps, const float *up_exps, const float *down_exps, float *out, bool renorm)
Definition utils.cpp:709
void rmsnorm_batch_cpu(const std::vector< float > &x_batch, const std::vector< float > &weight, std::vector< float > &out_batch, int num_tokens, int hidden_size, float eps)
Definition utils.cpp:613
void rmsnorm_vector_cpu(const std::vector< float > &x, const std::vector< float > &weight, std::vector< float > &out, float eps)
Definition utils.cpp:648
void matvec_q8_0_f32_vector_cpu(const std::vector< block_q8_0 > &mat_q8_0, const std::vector< float > &vec_f32, std::vector< float > &out_f32, int rows, int cols, bool log_first_block)
Definition utils.cpp:294