223 float temperature,
int top_k,
224 float top_p, std::mt19937& rng) {
225 if (logits.empty()) {
226 throw std::runtime_error(
"Cannot sample from empty logits.");
230 if (temperature < 0.05f) {
231 return std::distance(logits.begin(), std::max_element(logits.begin(), logits.end()));
234 int vocab_size = logits.size();
236 top_k = (std::min)(top_k, vocab_size);
237 if (top_k <= 0) top_k = vocab_size;
239 std::vector<float> scaled_logits(vocab_size);
240 float max_logit = -std::numeric_limits<float>::infinity();
241 for (
float logit : logits) max_logit = (std::max)(max_logit, logit);
244 const float scale = 1.0f / temperature;
245 for (
int i = 0; i < vocab_size; ++i) {
246 scaled_logits[i] = (logits[i] - max_logit) *
scale;
249 std::vector<double> probs_double(vocab_size);
250 double sum_exp = 0.0;
251 for (
int i = 0; i < vocab_size; ++i) {
252 probs_double[i] = std::exp(
static_cast<double>(scaled_logits[i]));
253 sum_exp += probs_double[i];
260 if (top_k >= vocab_size && top_p >= 1.0f && sum_exp > 0.0) {
261 std::discrete_distribution<int> pure(probs_double.begin(), probs_double.end());
267 for (
int i = 0; i < vocab_size; ++i) {
268 probs_double[i] /= sum_exp;
272 for (
int i = 0; i < vocab_size; ++i) {
273 probs_double[i] = 1.0 / vocab_size;
277 std::vector<std::pair<float, int>> prob_idx(vocab_size);
278 for (
int i = 0; i < vocab_size; ++i) {
279 prob_idx[i] = {
static_cast<float>(probs_double[i]), i};
285 if (top_k < vocab_size) {
286 std::partial_sort(prob_idx.begin(), prob_idx.begin() + top_k, prob_idx.end(),
287 std::greater<std::pair<float, int>>());
288 prob_idx.resize(top_k);
290 std::sort(prob_idx.begin(), prob_idx.end(), std::greater<std::pair<float, int>>());
293 float cumulative_prob = 0.0f;
295 for (
int i = 0; i < prob_idx.size(); ++i) {
296 cumulative_prob += prob_idx[i].first;
298 if (cumulative_prob >= top_p) {
302 prob_idx.resize(last_idx + 1);
304 float final_sum = 0.0f;
305 for (
const auto& pi : prob_idx) {
306 final_sum += pi.first;
310 std::vector<float> final_probs(prob_idx.size());
311 if (final_sum > 0.0f) {
312 for (
size_t i = 0; i < prob_idx.size(); ++i) {
313 final_probs[i] = prob_idx[i].first / final_sum;
317 float uniform_prob = 1.0f / prob_idx.size();
318 std::fill(final_probs.begin(), final_probs.end(), uniform_prob);
321 std::discrete_distribution<int> dist(final_probs.begin(), final_probs.end());
322 int sampled_idx_in_filtered = dist(rng);
324 return prob_idx[sampled_idx_in_filtered].second;
329 const std::string& tokenizer_path_arg,
331 int num_gpu_layers_from_cli,
334 bool use_batch_generation,
336 : threads_(threads), use_batch_generation_(use_batch_generation),
337 max_batch_size_(max_batch_size), rng_(std::random_device{}()) {
338 Logger::info(
"TinyLlamaSession constructor entered. Model path: " + model_path_arg +
339 ", Tokenizer path: " + tokenizer_path_arg +
340 ", Threads: " + std::to_string(threads) +
341 ", Num GPU Layers (CLI): " + std::to_string(num_gpu_layers_from_cli) +
342 ", Use MMAP (CLI): " + (cli_use_mmap ?
"true" :
"false") +
343 ", Use KV Quant (CLI): " + (use_kv_quant ?
"true" :
"false"));
347 if (num_gpu_layers_from_cli != 0) {
348 Logger::warning(
"This build has no CUDA: " + std::to_string(num_gpu_layers_from_cli) +
349 " GPU layer(s) requested; every layer runs on the CPU.");
350 num_gpu_layers_from_cli = 0;
354 std::string effective_model_file_path = model_path_arg;
355 std::string path_for_config_json = model_path_arg;
360 if (num_gpu_layers_from_cli < 0) {
366 std::filesystem::path fs_model_path(model_path_arg);
367 bool is_dir = std::filesystem::is_directory(fs_model_path);
370 Logger::info(
"Model path is a directory. Assuming SafeTensors model directory: " + model_path_arg);
371 effective_model_file_path = (fs_model_path /
"model.safetensors").
string();
372 std::string config_json_path_in_dir = (fs_model_path /
"config.json").
string();
374 Logger::info(
"Derived SafeTensors model file path: " + effective_model_file_path);
375 Logger::info(
"Path for loading config.json: " + config_json_path_in_dir);
381 if (st_config_loaded) {
382 Logger::info(
"Successfully loaded config.json directly into initial_model_config_for_model_ctor.");
384 std::string family_after_json_load =
"UNKNOWN_POST_JSON_LOAD_DIR_CASE";
387 Logger::info(
"[API_CPP_POST_JSON_LOAD_DIR_CASE] Tokenizer family in initial_model_config_for_model_ctor: " + family_after_json_load);
391 Logger::warning(
"Failed to load config.json for SafeTensors. initial_model_config_for_model_ctor will have defaults/CLI overrides for some fields, tokenizer_family likely UNKNOWN.");
399 int total_layers_from_config = initial_model_config_for_model_ctor.
num_hidden_layers;
400 if (total_layers_from_config <= 0 && st_config_loaded) {
401 Logger::warning(
"config.json loaded but num_hidden_layers is <= 0. GPU offload logic might be incorrect.");
402 }
else if (total_layers_from_config <= 0 && !st_config_loaded) {
403 Logger::warning(
"config.json NOT loaded and num_hidden_layers is <= 0 (default). GPU offload may not work as expected. Model load will likely fail.");
408 if (num_gpu_layers_from_cli < 0) {
410 }
else if (num_gpu_layers_from_cli == 0) {
413 if (total_layers_from_config > 0) {
414 initial_model_config_for_model_ctor.
num_cpu_offload_layers = total_layers_from_config - num_gpu_layers_from_cli;
419 Logger::warning(
"Total hidden layers unknown from config.json before model load; passing num_gpu_layers_from_cli as num_cpu_offload_layers hint.");
423 if (total_layers_from_config > 0) {
430 model_ = std::make_unique<TinyLlamaModel>(initial_model_config_for_model_ctor, st_loader);
436 Logger::info(
"TinyLlamaSession: Finalizing ModelConfig for KVCache initialization. use_kvcache_quantization set to: " +
447 std::string extension = fs_model_path.extension().string();
448 std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
450 if (extension ==
".gguf") {
451 Logger::info(
"GGUF model type detected by extension for Session constructor: " + model_path_arg);
457 model_ = std::make_unique<TinyLlamaModel>(initial_model_config_for_model_ctor, model_path_arg);
459 }
else if (extension ==
".safetensors") {
460 Logger::info(
"SafeTensors model type detected by extension for Session constructor (file case): " + model_path_arg);
461 effective_model_file_path = model_path_arg;
464 if (st_config_loaded) {
465 Logger::info(
"Successfully loaded config.json for SafeTensors in Session ctor (file case).");
467 std::string family_after_json_load_file_case =
"UNKNOWN_POST_JSON_LOAD_FILE_CASE";
470 Logger::info(
"[API_CPP_POST_JSON_LOAD_FILE_CASE] Tokenizer family in initial_model_config_for_model_ctor: " + family_after_json_load_file_case);
473 Logger::warning(
"Failed to load config.json for SafeTensors in Session ctor (file case). Model will use defaults or CLI overrides.");
478 int total_layers_from_config_file_case = initial_model_config_for_model_ctor.
num_hidden_layers;
479 if (num_gpu_layers_from_cli < 0) {
481 }
else if (num_gpu_layers_from_cli == 0) {
484 if (total_layers_from_config_file_case > 0) {
485 initial_model_config_for_model_ctor.
num_cpu_offload_layers = total_layers_from_config_file_case - num_gpu_layers_from_cli;
488 Logger::warning(
"Total hidden layers unknown from config.json (file case); passing num_gpu_layers_from_cli as num_cpu_offload_layers hint.");
491 if (total_layers_from_config_file_case > 0) {
498 model_ = std::make_unique<TinyLlamaModel>(initial_model_config_for_model_ctor, st_loader);
504 Logger::info(
"TinyLlamaSession: Finalizing ModelConfig for KVCache initialization. use_kvcache_quantization set to: " +
516 throw std::runtime_error(
"Unsupported model file type or extension in Session constructor: " + model_path_arg +
517 ". Please provide a directory for SafeTensors, a .gguf file, or a .safetensors file.");
522 throw std::runtime_error(
"Model pointer is null after instantiation attempt in Session constructor.");
529 throw std::runtime_error(
"GGUF model loaded but GGUFData is null in Session constructor.");
532 Logger::info(
"Tokenizer initialized from GGUF metadata.");
534 std::filesystem::path p_tokenizer_arg(tokenizer_path_arg);
535 std::string tokenizer_dir = p_tokenizer_arg.parent_path().string();
536 if (tokenizer_dir.empty()) {
540 std::string vocab_json_path = (std::filesystem::path(tokenizer_dir) /
"tokenizer.json").
string();
543 std::string sp_model_path = tokenizer_path_arg;
545 Logger::info(
"Initializing Tokenizer for SafeTensors. Vocab JSON path: " + vocab_json_path +
", SP Model path: " + sp_model_path);
547 std::string family_to_log =
"UNKNOWN_IN_API_CPP";
550 Logger::info(
"[API_CPP_TOKENIZER_INIT] Tokenizer family from session config for SafeTensors: " + family_to_log);
552 tokenizer_ = std::make_unique<Tokenizer>(vocab_json_path, sp_model_path,
config_);
553 Logger::info(
"Tokenizer initialized from external files for SafeTensors model.");
555 }
catch (
const std::exception& e) {
556 throw std::runtime_error(std::string(
"Failed to initialize Tokenizer: ") + e.what());
560 throw std::runtime_error(
"Tokenizer pointer is null after instantiation attempt.");
571 const char* force = std::getenv(
"EMBER_INTERPRETER");
572 if (
model_->ir_only() && ir.empty())
throw std::runtime_error(
"IR-only model but no IR found");
573 if (
model_->ir_only() && force && std::string(force) ==
"0")
574 throw std::runtime_error(
"EMBER_INTERPRETER=0, but this GGUF can only run through the IR interpreter");
575 if (!ir.empty() && !(force && std::string(force) ==
"0")) {
576 const std::string family = nlohmann::json::parse(ir).value(
"family_hint",
"");
577 if ((force && std::string(force) ==
"1") || std::getenv(
"EMBER_IR") ||
model_->ir_only() ||
587 const char* dev = std::getenv(
"EMBER_IR_DEVICE");
588 ir_gpu = num_gpu_layers_from_cli != 0 && !(dev && std::string(dev) ==
"cpu") &&
591 ir_ = std::make_unique<ember_ir::Interpreter>(ir, *gd, ir_gpu);
593 Logger::warning(
"Running the model through the paperkiln IR interpreter (" +
594 std::string(
ir_->on_gpu() ?
"CUDA" :
"CPU") +
"; family_hint '" + family +
595 "'). The llama path's weights stay loaded but unused.");
603 const char* dq = std::getenv(
"EMBER_DEQUANT_AT_LOAD");
604 if (!(dq && std::string(dq) ==
"0") &&
model_->dequantize_all_if_small(64u * 1000 * 1000))
605 Logger::info(
"Dequantised all weights to f32 at load (small model).");
614 std::string ext = std::filesystem::path(model_path_arg).extension().string();
615 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
616 if (ext ==
".gguf") {
623 const size_t n_merges =
v.g.merges.size();
629 Logger::info(std::string(
"Chat tokenizer (") +
bpe_->kind() +
"): " + std::to_string(
bpe_->size()) +
630 " tokens, " + std::to_string(n_merges) +
" merges (chat mode)");
634 Logger::info(
"Chat template " + cs.
tmpl.
id() +
" (" + cs.
why +
"), served by ember's own tokenizer");
644 auto it = gd->metadata.find(
"paperkiln.state_tokens.k");
646 if (it != gd->metadata.end())
649 using V = std::decay_t<
decltype(
v)>;
650 if constexpr (std::is_arithmetic_v<V> && !std::is_same_v<V, bool>)
k =
static_cast<int>(
v);
654 if (!
ir_ ||
ir_->state_k() !=
k)
655 throw std::runtime_error(
"paperkiln.state_tokens.k is " + std::to_string(
k) +
656 ": the model must run through the IR interpreter with a state_weight of k rows");
659 if (!
bpe_ || !
bpe_->format().v1_markers())
660 throw std::runtime_error(
"paperkiln.state_tokens.k > 0 needs the paperkiln/1 chat template (state blocks are "
661 "defined over its markers); this file's template is " +
666 std::to_string(
state_vocab_ +
k - 1) +
") after every <|endoftext|> that ends a reply" +
667 (
ir_->evicts() ?
"; kv eviction on" :
"; every kv row kept"));
675 int gpu_layers_for_kvcache = total_model_layers - effective_cpu_offload_layers;
676 if (gpu_layers_for_kvcache < 0) gpu_layers_for_kvcache = 0;
677 if (gpu_layers_for_kvcache > total_model_layers) gpu_layers_for_kvcache = total_model_layers;
679 Logger::info(
"[Session KVCache Init] Total Layers: " + std::to_string(total_model_layers) +
680 ", Effective CPU Offload by Model: " + std::to_string(effective_cpu_offload_layers) +
681 ", GPU Layers for KVCache: " + std::to_string(gpu_layers_for_kvcache));
683 if (total_model_layers <= 0) {
684 throw std::runtime_error(
"Model config has zero or negative num_hidden_layers before KVCache init.");
687 throw std::runtime_error(
"Model config has zero or negative num_attention_heads before KVCache init.");
694 gpu_layers_for_kvcache,
699 Logger::info(
"TinyLlamaSession initialization complete (after KVCache init).");
708 int top_k,
float top_p,
709 const std::string& system_prompt_arg,
710 bool apply_q_a_format_cli_hint) {
711 auto t_start = std::chrono::high_resolution_clock::now();
714 using ttft_clock = std::chrono::steady_clock;
715 auto ms_between = [](ttft_clock::time_point
a, ttft_clock::time_point b) {
716 return std::chrono::duration<double, std::milli>(b -
a).count();
725 Logger::info(
"[Generate API] User prompt: \"" + user_prompt +
"\", System prompt: \"" + system_prompt_arg +
"\", Steps: " + std::to_string(steps));
728 throw std::runtime_error(
"Model or tokenizer not loaded.");
731 std::string final_prompt_for_encoding;
732 bool used_chat_template =
false;
736 bool gguf_template_empty =
tokenizer_->get_gguf_chat_template().empty();
737 Logger::info(
"[Generate API] GGUF chat template from tokenizer is empty: " + std::string(gguf_template_empty ?
"true" :
"false"));
738 if (!gguf_template_empty) {
739 Logger::info(
"[Generate API] GGUF Template Content (first 100 chars): " +
tokenizer_->get_gguf_chat_template().substr(0, 100));
742 Logger::warning(
"[Generate API] Tokenizer is null before checking chat template!");
744 std::string family_log_str =
"UNKNOWN";
747 Logger::info(
"[Generate API] Configured tokenizer_family: " + family_log_str);
756 final_prompt_for_encoding = user_prompt;
757 used_chat_template =
false;
759 Logger::info(
"[Generate API] WORD_LEVEL tokenizer: using raw prompt (no Q/A or chat template).");
760 final_prompt_for_encoding = user_prompt;
761 used_chat_template =
false;
764 else if (apply_q_a_format_cli_hint) {
765 Logger::info(
"[Generate API] Using legacy Q/A formatting (CLI Hint is true - Priority 1).");
766 std::string temp_prompt = user_prompt;
767 if (!system_prompt_arg.empty()) {
768 temp_prompt = system_prompt_arg +
"\\n\\nQ: " + user_prompt +
"\\nA:";
770 temp_prompt =
"Q: " + user_prompt +
"\\nA:";
772 final_prompt_for_encoding = temp_prompt;
773 used_chat_template =
false;
777 std::string gguf_template_content =
tokenizer_->get_gguf_chat_template();
779 bool looks_like_jinja = (gguf_template_content.find(
"{%") != std::string::npos);
781 if (is_llama_sentencepiece_family && looks_like_jinja) {
782 Logger::info(
"[Generate API] Detected LLAMA_SENTENCEPIECE model with a Jinja-like GGUF template. Forcing Q/A format to avoid C++ Jinja processing issues (Priority 2 Override).");
783 std::string temp_prompt = user_prompt;
784 if (!system_prompt_arg.empty()) {
785 temp_prompt = system_prompt_arg +
"\\\\n\\\\nQ: " + user_prompt +
"\\\\nA:";
787 temp_prompt =
"Q: " + user_prompt +
"\\\\nA:";
789 final_prompt_for_encoding = temp_prompt;
790 used_chat_template =
false;
792 Logger::info(
"[Generate API] Using GGUF chat template from tokenizer (Q/A Hint false - Priority 2).");
793 final_prompt_for_encoding =
tokenizer_->apply_chat_template(user_prompt, system_prompt_arg,
config_);
794 used_chat_template =
true;
799 Logger::info(
"[Generate API] Llama 3 tokenizer family detected, using apply_chat_template (Q/A Hint false, No GGUF template - Priority 3).");
800 final_prompt_for_encoding =
tokenizer_->apply_chat_template(user_prompt, system_prompt_arg,
config_);
801 used_chat_template =
true;
805 Logger::info(
"[Generate API] No applicable template/hint. Using user prompt as is (prepending system prompt if available - Priority 4).");
806 if (!system_prompt_arg.empty()) {
807 final_prompt_for_encoding = system_prompt_arg +
"\\n\\n" + user_prompt;
809 final_prompt_for_encoding = user_prompt;
811 used_chat_template =
false;
814 Logger::debug(
"[Generate API] Final prompt for encoding (first 100 chars): \\\"" + final_prompt_for_encoding.substr(0, 100) +
"\\\"");
824 auto is_stop = [&](
int id) {
827 auto decode_one = [&](
int id) {
832 auto emit_token = [&](
int id) {
835 bool stopped_at_first =
false;
842 if (tokens.empty()) {
843 Logger::warning(
"Tokenization resulted in empty ID list for prompt: " +
844 final_prompt_for_encoding);
848 int num_prompt_tokens = tokens.size();
849 Logger::info(
"[Generate API] Number of prompt tokens: " + std::to_string(num_prompt_tokens));
861 static_cast<int>(std::min(lcp, tokens.size() - 1));
870 static const bool prefix_reuse_on = [] {
871 const char*
v = std::getenv(
"EMBER_PREFIX_REUSE");
872 return !(
v && std::string(
v) ==
"0");
880 if (
ir_ && (
ir_->has_recurrent() ||
ir_->evicts())) reuse =
ir_->resume_point(reuse);
888 std::vector<int>
out;
889 for (
int id : script) {
890 if (
static_cast<int>(
out.size()) >= steps)
break;
893 if (is_stop(
id))
break;
895 if (!
out.empty() && is_stop(
out.back())) {
903 const char* ws =
" \t\n\v\f\r";
904 const auto first =
text.find_first_not_of(ws);
905 const auto last =
text.find_last_not_of(ws);
906 return first == std::string::npos ? std::string() :
text.substr(first, last - first + 1);
909 int total_steps = num_prompt_tokens + steps -1;
910 int generated_count = 0;
911 int next_token_id = -1;
913 std::vector<float> logits;
914 std::vector<int> generated_token_ids;
919 std::vector<char> repeat_seen;
920 auto apply_repeat_penalty = [&](std::vector<float>& lg) {
925 auto t_kv_reset = ttft_clock::now();
936 std::vector<float> current_data_host;
937 int start_pos_for_loop = 0;
944 static const int prefill_min = [] {
945 const char*
v = std::getenv(
"EMBER_PREFILL_MIN");
946 return v && *
v ? std::max(1, std::atoi(
v)) : 1;
948 bool prefill_enabled = num_prompt_tokens >= prefill_min;
950 const int prefill_from = prefill_enabled ? reuse : 0;
951 const int prefill_count = num_prompt_tokens - prefill_from;
954if (prefill_enabled) {
955 Logger::info(
"[Generate API] Prefill enabled. num_prompt_tokens: " + std::to_string(num_prompt_tokens) +
959 auto t_embed = ttft_clock::now();
962 for (
int i = 0; i < (
ir_ ? 0 : prefill_count); ++i) {
963 std::vector<float> token_embedding =
model_->lookup_embedding(tokens[prefill_from + i]);
964 if (token_embedding.empty()) {
965 Logger::error(
"Prefill: Embedding lookup returned empty vector for token ID: " + std::to_string(tokens[i]) +
" at prompt pos " + std::to_string(i));
968 std::copy(token_embedding.begin(), token_embedding.end(), batch_initial_embeddings.begin() + i *
config_.
hidden_size);
970 auto t_prefill = ttft_clock::now();
974 const std::vector<int> slice(tokens.begin() + prefill_from, tokens.begin() + prefill_from + prefill_count);
975 logits =
ir_->forward(slice, prefill_from);
978 std::vector<float> cpu_processed_embeddings;
982 if (cpu_processed_embeddings.empty()) {
983 Logger::error(
"Prefill: forward_cpu_batch returned empty or failed.");
987 cpu_processed_embeddings = batch_initial_embeddings;
992 Logger::info(
"[Generate API] Prefill: All layers are on CPU. Getting logits from final CPU layer output.");
993 std::vector<float> batch_logits =
model_->forward_cpu_logits_batch(cpu_processed_embeddings, prefill_count);
995 Logger::error(
"Prefill: forward_cpu_logits_batch returned invalid logits.");
1003 Logger::info(
"[Generate API] Prefill: Processing GPU layers for the batch.");
1005 float* d_temp_batch_embeddings =
nullptr;
1006 size_t batch_embeddings_size_bytes = cpu_processed_embeddings.size() *
sizeof(float);
1008 if (batch_embeddings_size_bytes == 0) {
1009 Logger::error(
"Prefill: cpu_processed_embeddings is empty, cannot proceed with GPU batch prefill.");
1013 gpuErrchk(cudaMalloc(&d_temp_batch_embeddings, batch_embeddings_size_bytes));
1014 if (!d_temp_batch_embeddings) {
1015 Logger::error(
"Prefill: cudaMalloc failed for d_temp_batch_embeddings.");
1019 gpuErrchk(cudaMemcpy(d_temp_batch_embeddings, cpu_processed_embeddings.data(),
1020 batch_embeddings_size_bytes, cudaMemcpyHostToDevice));
1023 cudaEvent_t ev_fwd_start, ev_fwd_end;
1024 gpuErrchk(cudaEventCreate(&ev_fwd_start));
1025 gpuErrchk(cudaEventCreate(&ev_fwd_end));
1026 gpuErrchk(cudaEventRecord(ev_fwd_start, 0));
1027 nvtxRangePushA(
"ttft_prefill_forward");
1028 logits =
model_->forward_device_batch_prefill(d_temp_batch_embeddings, prefill_count, prefill_from, &
kv_cache_, 0);
1030 gpuErrchk(cudaEventRecord(ev_fwd_end, 0));
1031 gpuErrchk(cudaEventSynchronize(ev_fwd_end));
1032 float fwd_span_ms = 0.0f;
1033 gpuErrchk(cudaEventElapsedTime(&fwd_span_ms, ev_fwd_start, ev_fwd_end));
1036 gpuErrchk(cudaEventDestroy(ev_fwd_start));
1037 gpuErrchk(cudaEventDestroy(ev_fwd_end));
1039 if (d_temp_batch_embeddings) {
1040 gpuErrchk(cudaFree(d_temp_batch_embeddings));
1044 throw std::runtime_error(
"[Generate API] GPU layers requested but CUDA not available");
1049 if (logits.empty()) {
1050 Logger::error(
"Prefill: Logits are empty after prefill processing.");
1053 auto t_sample = ttft_clock::now();
1056 apply_repeat_penalty(logits);
1059 generated_token_ids.push_back(next_token_id);
1061 emit_token(next_token_id);
1071 start_pos_for_loop = num_prompt_tokens;
1072 if (
bpe_ && is_stop(next_token_id)) {
1073 stopped_at_first =
true;
1078 Logger::info(
"[Generate API] Prefill completed. next_token_id: " + std::to_string(next_token_id) +
1079 ", Decoded: \"" + decode_one(next_token_id) +
"\"" +
1080 ", start_pos_for_loop set to: " + std::to_string(start_pos_for_loop));
1086 static const bool decode_timing = std::getenv(
"EMBER_DECODE_TIMING") !=
nullptr;
1087 std::map<std::string, double> host_phase_ms;
1088 int decode_steps = 0;
1089 auto t_phase = ttft_clock::now();
1090 auto host_phase = [&](
const char*
name) {
1091 if (!decode_timing)
return;
1092 const auto now = ttft_clock::now();
1093 host_phase_ms[
name] += ms_between(t_phase, now);
1097 if (decode_timing)
model_->decode_phase_ms().clear();
1099 const auto t_decode_start = ttft_clock::now();
1103 static const int spec_k = [] {
1104 const char* e = std::getenv(
"EMBER_SPEC");
1105 return e ? std::max(0, std::atoi(e)) : 0;
1107 const bool spec_on = spec_k > 0 && top_k == 1 && !repeat_on &&
1114 static const bool draft_table_mode = [] {
1115 const char* e = std::getenv(
"EMBER_DRAFT");
1116 const std::string
v = e ? e :
"";
1117 if (!
v.empty() &&
v !=
"pld" &&
v !=
"table")
1118 Logger::warning(
"EMBER_DRAFT=" +
v +
" is not a drafter (pld, table); prompt-lookup drafts only.");
1119 return v ==
"table";
1121 static const double draft_theta = [] {
1122 const char* e = std::getenv(
"EMBER_DRAFT_THETA");
1123 return e && *e ? std::atof(e) : 0.02;
1128 for (
int pos = start_pos_for_loop;
pos < total_steps; ++
pos) {
1129 t_phase = ttft_clock::now();
1140 if (
pos == num_prompt_tokens && start_pos_for_loop == num_prompt_tokens) {
1144 input_token_id = next_token_id;
1145 Logger::debug(
"[Generate Loop] First token post-prefill. Using prefill's next_token_id: " + std::to_string(input_token_id) +
" for pos " + std::to_string(
pos));
1153 input_token_id = (
pos < num_prompt_tokens && start_pos_for_loop == 0) ? tokens[
pos] : next_token_id;
1154 if (start_pos_for_loop == 0 &&
pos < num_prompt_tokens) {
1155 Logger::debug(
"[Generate Loop] No prefill, prompt token. Using tokens[" + std::to_string(
pos) +
"]: " + std::to_string(input_token_id) +
" for pos " + std::to_string(
pos));
1157 Logger::debug(
"[Generate Loop] Standard generation. Using previously sampled next_token_id: " + std::to_string(input_token_id) +
" for pos " + std::to_string(
pos));
1168 if (spec_on &&
pos >= num_prompt_tokens) {
1169 const int room = std::min({spec_k, steps - generated_count, total_steps -
pos - 1,
1171 const auto t_draft = ttft_clock::now();
1172 std::vector<int> hist(tokens.begin(), tokens.begin() + num_prompt_tokens);
1173 hist.insert(hist.end(), generated_token_ids.begin(), generated_token_ids.end());
1175 if (draft.empty() && draft_table && room > 0) draft = draft_table->
draft(hist, room, draft_theta);
1179 for (
size_t i = 0; i < draft.size(); ++i)
1184 if (!draft.empty()) {
1185 std::vector<int> batch = {input_token_id};
1186 batch.insert(batch.end(), draft.begin(), draft.end());
1187 const int n =
static_cast<int>(batch.size());
1189 std::vector<float> emb(
ir_ ? 0 : (size_t)
n * hs);
1190 for (
int i = 0; i < (
ir_ ? 0 :
n); ++i) {
1191 const std::vector<float> e =
model_->lookup_embedding(batch[i]);
1192 std::copy(e.begin(), e.end(), emb.begin() + (
size_t)i * hs);
1194 std::vector<float> all;
1199 static const bool verify_legacy = [] {
1200 const char*
v = std::getenv(
"EMBER_VERIFY");
1201 return v && std::string(
v) ==
"legacy";
1203 std::vector<ember_verify::RowPick> picks;
1207 const bool have_picks =
false;
1212 all =
ir_->forward(batch,
pos,
true);
1214 all =
model_->forward_cpu_logits_batch(
1222 float* d_emb =
nullptr;
1223 gpuErrchk(cudaMalloc(&d_emb, emb.size() *
sizeof(
float)));
1224 gpuErrchk(cudaMemcpy(d_emb, emb.data(), emb.size() *
sizeof(
float), cudaMemcpyHostToDevice));
1226 gpuErrchk(cudaFree(d_emb));
1230 if (!have_picks && all.size() != (
size_t)
n * vs) {
1231 Logger::error(
"Speculative verification returned " + std::to_string(all.size()) +
" logits; stopping.");
1236 for (
int i = 0; i <
n; ++i) {
1243 std::vector<float> lg =
model_->verify_row_logits(i);
1244 if (lg.size() !=
static_cast<size_t>(vs)) {
1245 Logger::error(
"Speculative verification: row " + std::to_string(i) +
" unavailable; stopping.");
1255 std::vector<float> lg(all.begin() + (
size_t)i * vs, all.begin() + (
size_t)(i + 1) * vs);
1260 generated_token_ids.push_back(t);
1266 if (is_stop(t) || generated_count >= steps) { stop =
true;
break; }
1267 if (i + 1 <
n && t == batch[i + 1]) { ++accepted;
continue; }
1274 host_phase(
"host.spec_step");
1281 host_phase(
"host.loop_head");
1282 if (!
ir_) current_data_host =
model_->lookup_embedding(input_token_id);
1283 if (
pos == 14 ||
pos == 15 ||
pos == 16) {
1284 log_vector_summary_detailed(
"[API_CPP GenLoop] current_data_host after lookup_embedding for input_token_id=" + std::to_string(input_token_id),
1285 current_data_host,
pos, -100, 8);
1287 host_phase(
"host.embed_lookup");
1289 if (!
ir_ && current_data_host.empty()) {
1290 Logger::error(
"Embedding lookup returned empty vector for token ID: " + std::to_string(input_token_id) +
" at pos " + std::to_string(
pos));
1296 logits =
ir_->forward({input_token_id},
pos);
1301 std::vector<float> intermediate_activations =
model_->forward(current_data_host,
pos, &
kv_cache_,
nullptr);
1303 Logger::debug(
"[Mixed Mode] CPU layers complete, transferring to GPU for remaining layers");
1304 gpuErrchk(cudaMemcpy(
model_->get_x_dev(), intermediate_activations.data(), intermediate_activations.size() *
sizeof(
float), cudaMemcpyHostToDevice));
1307 Logger::error(
"[Mixed Mode] Mixed CPU/GPU mode requested but CUDA not available. Cannot proceed.");
1315 host_phase(
"host.forward_device_graph");
1316 if (logits.empty()) {
1317 gpuErrchk(cudaMemcpy(
model_->get_x_dev(), current_data_host.data(), current_data_host.size() *
sizeof(
float), cudaMemcpyHostToDevice));
1318 host_phase(
"host.embed_h2d");
1320 host_phase(
"host.forward_device(total)");
1323 Logger::error(
"[GPU-only Mode] GPU-only mode requested but CUDA not available. Cannot proceed.");
1332 if (
pos == num_prompt_tokens - 1 ||
pos >= num_prompt_tokens) {
1334 apply_repeat_penalty(logits);
1337 host_phase(
"host.sample");
1340 if (
pos >= num_prompt_tokens) {
1341 generated_token_ids.push_back(next_token_id);
1343 emit_token(next_token_id);
1350 generated_token_ids.push_back(next_token_id);
1352 emit_token(next_token_id);
1362 Logger::info(
"[Generate API] First token sampled from prompt: " + std::to_string(next_token_id) +
1363 ", Decoded: \"" + decode_one(next_token_id) +
"\"");
1367 if (is_stop(next_token_id) &&
pos >= (
bpe_ ? num_prompt_tokens - 1 : num_prompt_tokens)) {
1369 ") sampled at pos " + std::to_string(
pos) +
". Stopping.");
1373 if (generated_count >= steps) {
1374 Logger::info(
"Reached max generation steps (" + std::to_string(steps) +
"). Stopping.");
1382 if (!prefill_enabled ||
pos >= num_prompt_tokens) {
1386 host_phase(
"host.decode_text_and_rest");
1388 if (decode_timing && decode_steps > 0) {
1389 const double total = ms_between(t_decode_start, ttft_clock::now());
1390 std::fprintf(stderr,
"[decode timing] %d steps, %.3f ms/step wall (timed run, phases sync)\n", decode_steps,
1391 total / decode_steps);
1392 for (
const auto& kv : host_phase_ms)
1393 std::fprintf(stderr,
" %-34s %8.3f ms/step\n", kv.first.c_str(), kv.second / decode_steps);
1395 for (
const auto& kv :
model_->decode_phase_ms())
1396 std::fprintf(stderr,
" gpu.%-30s %8.3f ms/step\n", kv.first.c_str(), kv.second / decode_steps);
1401 std::string generated_ids_str =
"[Generated IDs Pre-Decode] ";
1402 for(
int gen_id : generated_token_ids) {
1403 generated_ids_str += std::to_string(gen_id) +
" ";
1409 std::vector<int>
out = generated_token_ids;
1410 if (!
out.empty() && is_stop(
out.back())) {
1419 const char* ws =
" \t\n\v\f\r";
1420 const auto first = result.find_first_not_of(ws);
1421 const auto last = result.find_last_not_of(ws);
1422 result = first == std::string::npos ? std::string() : result.substr(first, last - first + 1);
1423 (void)stopped_at_first;
1425 const bool stopped = !generated_token_ids.empty() && is_stop(generated_token_ids.back());
1428 result =
tokenizer_->decode(generated_token_ids,
true);
1435 if (!generated_token_ids.empty()) {
1437 generated_token_ids.end() - 1);
1443 if (
ir_ &&
state_k_ > 0 && !generated_token_ids.empty() &&
bpe_->format().is_eos(generated_token_ids.back())) {
1446 std::vector<int> close = {generated_token_ids.back()};
1448 ir_->forward(close, at);
1453 auto t_end = std::chrono::high_resolution_clock::now();
1454 double time_taken_ms = std::chrono::duration<double, std::milli>(t_end - t_start).count();
1457 std::ostringstream time_ss;
1458 time_ss << std::fixed << std::setprecision(4) << time_taken_ms;
1459 Logger::info(
"[INFO] Total generation processing time: " + time_ss.str() +
" ms");
1476 int top_k,
float top_p,
1477 const std::string& system_prompt_arg,
1478 bool apply_q_a_format_cli_hint) {
1479 auto t_start = std::chrono::high_resolution_clock::now();
1481 if (prompts.empty()) {
1482 throw std::runtime_error(
"Cannot process empty prompts vector for batch generation.");
1486 throw std::runtime_error(
"Batch size " + std::to_string(prompts.size()) +
1490 Logger::info(
"[Batch Generate API] Processing " + std::to_string(prompts.size()) +
1491 " prompts in batch. Steps: " + std::to_string(steps));
1494 throw std::runtime_error(
"Model or tokenizer not loaded for batch generation.");
1498 std::vector<std::string> final_prompts(prompts.size());
1499 std::vector<std::vector<int>> all_tokens(prompts.size());
1500 std::vector<int> prompt_lengths(prompts.size());
1501 int max_prompt_length = 0;
1503 for (
size_t i = 0; i < prompts.size(); ++i) {
1505 std::string final_prompt_for_encoding;
1506 bool used_chat_template =
false;
1509 if (apply_q_a_format_cli_hint) {
1510 Logger::info(
"[Batch Generate API] Using legacy Q/A formatting for prompt " + std::to_string(i));
1511 if (!system_prompt_arg.empty()) {
1512 final_prompt_for_encoding = system_prompt_arg +
"\\n\\nQ: " + prompts[i] +
"\\nA:";
1514 final_prompt_for_encoding =
"Q: " + prompts[i] +
"\\nA:";
1517 std::string gguf_template_content =
tokenizer_->get_gguf_chat_template();
1519 bool looks_like_jinja = (gguf_template_content.find(
"{%") != std::string::npos);
1521 if (is_llama_sentencepiece_family && looks_like_jinja) {
1522 Logger::info(
"[Batch Generate API] Using Q/A format override for prompt " + std::to_string(i));
1523 if (!system_prompt_arg.empty()) {
1524 final_prompt_for_encoding = system_prompt_arg +
"\\\\n\\\\nQ: " + prompts[i] +
"\\\\nA:";
1526 final_prompt_for_encoding =
"Q: " + prompts[i] +
"\\\\nA:";
1529 Logger::info(
"[Batch Generate API] Using GGUF chat template for prompt " + std::to_string(i));
1530 final_prompt_for_encoding =
tokenizer_->apply_chat_template(prompts[i], system_prompt_arg,
config_);
1531 used_chat_template =
true;
1534 Logger::info(
"[Batch Generate API] Using Llama 3 chat template for prompt " + std::to_string(i));
1535 final_prompt_for_encoding =
tokenizer_->apply_chat_template(prompts[i], system_prompt_arg,
config_);
1536 used_chat_template =
true;
1538 Logger::info(
"[Batch Generate API] Using raw prompt for prompt " + std::to_string(i));
1539 if (!system_prompt_arg.empty()) {
1540 final_prompt_for_encoding = system_prompt_arg +
"\\n\\n" + prompts[i];
1542 final_prompt_for_encoding = prompts[i];
1546 final_prompts[i] = final_prompt_for_encoding;
1549 if (all_tokens[i].empty()) {
1550 Logger::warning(
"Batch tokenization resulted in empty ID list for prompt " + std::to_string(i));
1551 all_tokens[i].push_back(
tokenizer_->bos_token_id());
1554 prompt_lengths[i] = all_tokens[i].size();
1555 max_prompt_length = std::max(max_prompt_length, prompt_lengths[i]);
1557 Logger::info(
"[Batch Generate API] Prompt " + std::to_string(i) +
": " +
1558 std::to_string(prompt_lengths[i]) +
" tokens");
1566 std::vector<std::string> results(prompts.size());
1570 Logger::info(
"[Batch Generate API] Using parallel batch processing");
1572 Logger::info(
"[DEBUG] Initializing KV cache for batch mode");
1574 Logger::info(
"[DEBUG] KV cache batch initialization completed");
1582 Logger::info(
"[DEBUG] About to call batch_prefill_parallel");
1583 Logger::info(
"[DEBUG] all_tokens.size()=" + std::to_string(all_tokens.size()) +
", prompt_lengths.size()=" + std::to_string(prompt_lengths.size()));
1585 std::vector<std::vector<float>> batch_final_logits;
1589 if (prefill_success && batch_final_logits.size() == prompts.size()) {
1590 Logger::info(
"[Batch Generate API] Batch prefill successful, starting parallel generation");
1593 Logger::info(
"[DEBUG] Checking batch_final_logits integrity after prefill");
1594 for (
size_t i = 0; i < batch_final_logits.size(); ++i) {
1595 if (batch_final_logits[i].empty()) {
1596 Logger::error(
"[DEBUG] batch_final_logits[" + std::to_string(i) +
"] is empty!");
1597 goto fallback_sequential;
1600 Logger::error(
"[DEBUG] batch_final_logits[" + std::to_string(i) +
"] has wrong size: " +
1601 std::to_string(batch_final_logits[i].size()) +
" vs expected " + std::to_string(
config_.
vocab_size));
1602 goto fallback_sequential;
1612 Logger::info(
"[DEBUG] batch_final_logits integrity check passed");
1620 Logger::info(
"[DEBUG] Starting token sampling for " + std::to_string(
prompts.size()) +
" sequences");
1622 for (
size_t i = 0;
i <
prompts.size(); ++
i) {
1623 Logger::info(
"[DEBUG] Sampling token for sequence " + std::to_string(
i));
1634 }
catch (
const std::exception&
e) {
1635 Logger::error(
"[DEBUG] Exception during sampling for sequence " + std::to_string(
i) +
": " + std::string(
e.what()));
1645 Logger::info(
"[DEBUG] Sequence " + std::to_string(
i) +
" finished with EOS token");
1649 Logger::info(
"[DEBUG] Token sampling completed, starting generation loop");
1652 for (
int step = 1; step <
steps; ++step) {
1653 Logger::info(
"[DEBUG] Starting generation step " + std::to_string(step));
1664 Logger::info(
"[Batch Generate API] All sequences finished at step " + std::to_string(step));
1668 Logger::info(
"[DEBUG] Collecting active sequences for step " + std::to_string(step));
1676 for (
size_t i = 0;
i <
prompts.size(); ++
i) {
1682 Logger::info(
"[DEBUG] Active sequence " + std::to_string(
i) +
" mapped to batch index " + std::to_string(
active_tokens.size() - 1) +
1688 Logger::info(
"[DEBUG] No active tokens, breaking from generation loop");
1692 Logger::info(
"[DEBUG] About to call batch_generation_parallel with " + std::to_string(
active_tokens.size()) +
" active sequences");
1700 Logger::warning(
"[Batch Generate API] Parallel generation failed at step " + std::to_string(step) +
1701 ", falling back to sequential processing");
1705 Logger::info(
"[DEBUG] Starting token sampling for step " + std::to_string(step));
1735 }
catch (
const std::exception&
e) {
1736 Logger::error(
"[DEBUG] Exception during sampling at step " + std::to_string(step) +
" for original_seq_idx " + std::to_string(
original_seq_idx) +
": " + std::string(
e.what()));
1741 Logger::info(
"[DEBUG] Completed generation step " + std::to_string(step));
1745 for (
size_t i = 0;
i <
prompts.size(); ++
i) {
1749 Logger::info(
"[Batch Generate API] Parallel batch processing completed successfully");
1751 Logger::warning(
"[Batch Generate API] Batch prefill failed, falling back to sequential processing");
1756 Logger::info(
"[Batch Generate API] Using sequential processing");
1758 for (
size_t i = 0;
i <
prompts.size(); ++
i) {
1759 Logger::info(
"[Batch Generate API] Processing prompt " + std::to_string(
i + 1) +
1760 "/" + std::to_string(
prompts.size()));
1773 auto t_end = std::chrono::high_resolution_clock::now();
1778 Logger::info(
"[Batch Generate API] Total batch processing time: " +
time_ss.str() +
" ms for " +
1779 std::to_string(
prompts.size()) +
" prompts");