ember.cpp 0.1.0
C++ inference engine for Llama-family and paperkiln models (CPU, CUDA, WebAssembly)
Loading...
Searching...
No Matches
tinyllama::TinyLlamaSession Class Reference

Represents an active TinyLlama session holding the loaded model and tokenizer. More...

#include <api.h>

Collaboration diagram for tinyllama::TinyLlamaSession:
Collaboration graph

Public Member Functions

 TinyLlamaSession (const std::string &model_path, const std::string &tokenizer_path, int threads=1, int num_gpu_layers_from_cli=0, bool cli_use_mmap=true, bool use_kv_quant=false, bool use_batch_generation=false, int max_batch_size=1)
 Loads the model, config, and tokenizer from the specified directory or GGUF file.
 
 ~TinyLlamaSession ()
 Destructor to ensure proper cleanup (e.g., KVCache CUDA memory).
 
std::string generate (const std::string &prompt, int steps=128, float temperature=0.1f, int top_k=40, float top_p=0.9f, const std::string &system_prompt="", bool apply_q_a_format=false)
 Generates text based on a given prompt.
 
std::vector< std::string > generate_batch (const std::vector< std::string > &prompts, int steps=128, float temperature=0.1f, int top_k=40, float top_p=0.9f, const std::string &system_prompt="", bool apply_q_a_format=false)
 Generates text for multiple prompts in a single batch (parallel processing).
 
const Tokenizer * get_tokenizer () const
 
const ModelConfig & get_config () const
 
KVCache & get_kv_cache ()
 
void set_repetition_penalty (float p)
 
float get_repetition_penalty () const
 
void set_repeat_penalty (float p, int last_n)
 
const TtftBreakdown & last_ttft () const
 
bool is_bpe () const
 
const ember::ChatFormat * chat_format () const
 
const microtorch::chat::Template * chat_template () const
 
const std::string & chat_template_why () const
 
const std::string & ir_document () const
 
bool is_chat_stop (int id) const
 
bool is_chat_eos (int id) const
 
int bpe_token_count (const std::string &text) const
 
std::vector< int > bpe_prompt_ids (const std::string &text) const
 
int state_k () const
 
const std::string & last_stop_reason () const
 
int last_generated_tokens () const
 
const std::vector< int > & last_prompt_ids () const
 
const std::vector< int > & last_generated_ids () const
 
int last_stop_id () const
 
void set_seed (unsigned seed)
 
void set_token_callback (std::function< void(int)> cb)
 
void set_scripted_replies (std::vector< std::vector< int > > r)
 
std::string bpe_decode (const std::vector< int > &ids) const
 
std::vector< int > bpe_encode (const std::string &text) const
 
int sample_step (std::vector< float > &logits, const std::vector< int > &context, float temperature, int top_k, float top_p)
 
std::vector< float > score_rows (const std::vector< int > &ids, int first_row, bool reuse_prefix, int *reused=nullptr)
 
int score_vocab () const
 
int score_context () const
 
bool score_reuse_ok () const
 
const std::vector< std::string > & gguf_tokens () const
 

Private Member Functions

 TinyLlamaSession (const TinyLlamaSession &)=delete
 
TinyLlamaSession & operator= (const TinyLlamaSession &)=delete
 
bool batch_prefill_parallel (const std::vector< std::vector< int > > &all_tokens, const std::vector< int > &prompt_lengths, std::vector< std::vector< float > > &batch_final_logits)
 
bool batch_generation_parallel (const std::vector< int > &current_tokens, const std::vector< int > &token_positions, const std::vector< int > &original_sequence_indices, std::vector< std::vector< float > > &batch_logits)
 

Private Attributes

std::unique_ptr< TinyLlamaModel > model_
 
std::unique_ptr< ember_ir::Interpreter > ir_
 
std::string ir_doc_
 
std::unique_ptr< Tokenizer > tokenizer_
 
ModelConfig config_
 
KVCache kv_cache_
 
int eos_token_id_
 
std::mt19937 rng_ {std::random_device{}()}
 
float repetition_penalty_ = 1.3f
 
float repeat_penalty_ = 1.0f
 
int repeat_last_n_ = 64
 
int threads_
 
bool use_batch_generation_
 
std::stringstream generated_stream_
 
std::string generated_text_for_api_return_
 
TtftBreakdown last_ttft_
 
std::vector< int > kv_token_history_
 
std::unique_ptr< ember::ChatTokenizer > bpe_
 
std::unique_ptr< microtorch::chat::Template > word_template_
 
std::string chat_template_why_
 
std::string last_stop_reason_
 
int last_generated_tokens_ = 0
 
std::vector< int > last_prompt_ids_
 
std::vector< int > last_generated_ids_
 
int last_stop_id_ = -1
 
std::function< void(int)> token_callback_
 
std::vector< std::vector< int > > scripted_replies_
 
int state_k_ = 0
 
int state_vocab_ = 0
 
std::vector< int > score_ids_
 
int max_batch_size_ = 1
 

Detailed Description

Represents an active TinyLlama session holding the loaded model and tokenizer.

This class provides a high-level interface for text generation using TinyLlama models. It supports both GGUF and SafeTensors model formats, and handles model loading, tokenization, and text generation with various sampling strategies.

Definition at line 85 of file api.h.

Constructor & Destructor Documentation

◆ TinyLlamaSession() [1/2]

tinyllama::TinyLlamaSession::TinyLlamaSession ( const std::string &  model_path,
const std::string &  tokenizer_path,
int  threads = 1,
int  num_gpu_layers_from_cli = 0,
bool  cli_use_mmap = true,
bool  use_kv_quant = false,
bool  use_batch_generation = false,
int  max_batch_size = 1 
)

Loads the model, config, and tokenizer from the specified directory or GGUF file.

Parameters
model_pathPath to the directory containing model files OR a .gguf file.
tokenizer_pathPath to the tokenizer file.
threadsNumber of threads to use for model loading.
num_gpu_layers_from_cliNumber of GPU layers to use from command-line arguments.
cli_use_mmapWhether to use mmap for loading the model.
use_kv_quantWhether to use INT8 quantization for the KVCache on GPU.
use_batch_generationWhether to enable single-token batch generation.
max_batch_sizeMaximum number of sequences for multi-prompt batch processing (default: 1).
Exceptions
std::runtime_errorif loading fails.

Definition at line 328 of file api.cpp.

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"));
344#ifndef HAS_CUDA
345 // A CPU build runs every layer on the CPU whatever the CLI asks: a nonzero
346 // count here otherwise reaches generate(), which cannot run GPU layers.
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.");
351 }
352#endif
353
356
359 initial_model_config_for_model_ctor.use_kvcache_quantization = use_kv_quant;
360 if (num_gpu_layers_from_cli < 0) {
361 initial_model_config_for_model_ctor.num_cpu_offload_layers = 0;
362 } else {
364 }
365
366 std::filesystem::path fs_model_path(model_path_arg);
367 bool is_dir = std::filesystem::is_directory(fs_model_path);
368
369 if (is_dir) {
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();
373
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);
376
377 // Directly populate initial_model_config_for_model_ctor
378 // load_model_config_from_json returns bool and populates the passed ModelConfig&
380
381 if (st_config_loaded) {
382 Logger::info("Successfully loaded config.json directly into initial_model_config_for_model_ctor.");
383 // Log tokenizer_family IMMEDIATELY after loading from config.json
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);
388 // tokenizer_family should now be set in initial_model_config_for_model_ctor
389 // num_hidden_layers is also now set from config.json
390 } else {
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.");
392 // If config.json fails, num_hidden_layers might be 0 or default, which will affect cpu offload calculation.
393 // It's crucial config.json loads for correct layer counts.
394 }
395
396 // Apply CLI overrides for mmap and GPU layers. GPU layer logic depends on total_hidden_layers from config.
398
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.");
404 // If config.json didn't load, total_layers_from_config is likely 0 from default ModelConfig.
405 // The TinyLlamaModel constructor will ultimately use its own parsed config, but this intermediate step needs care.
406 }
407
408 if (num_gpu_layers_from_cli < 0) { // -1 signifies all layers on GPU
409 initial_model_config_for_model_ctor.num_cpu_offload_layers = 0;
410 } else if (num_gpu_layers_from_cli == 0) { // 0 signifies all layers on CPU
411 initial_model_config_for_model_ctor.num_cpu_offload_layers = total_layers_from_config; // All layers offloaded to CPU
412 } else { // N > 0 signifies N layers on GPU
413 if (total_layers_from_config > 0) {
415 } else {
416 // Cannot determine actual GPU layer count if total_layers_from_config is unknown.
417 // Pass the CLI hint, TinyLlamaModel ctor will deal with it against its own parsed config.
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.");
420 }
421 }
422 // Clamp num_cpu_offload_layers
423 if (total_layers_from_config > 0) {
424 initial_model_config_for_model_ctor.num_cpu_offload_layers = std::max(0, std::min(initial_model_config_for_model_ctor.num_cpu_offload_layers, total_layers_from_config));
425 }
426
427 initial_model_config_for_model_ctor.is_gguf_file_loaded = false;
428
430 model_ = std::make_unique<TinyLlamaModel>(initial_model_config_for_model_ctor, st_loader);
431 config_ = model_->get_config();
432 config_.is_gguf_file_loaded = false; // Ensure this is set for session's copy too
433
434 config_.use_kvcache_quantization = use_kv_quant; // Re-apply CLI/constructor preference
435
436 Logger::info("TinyLlamaSession: Finalizing ModelConfig for KVCache initialization. use_kvcache_quantization set to: " +
437 std::string(config_.use_kvcache_quantization ? "true" : "false"));
438
445
446 } else { // Not a directory, assume it's a file path and check extension
447 std::string extension = fs_model_path.extension().string();
448 std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
449
450 if (extension == ".gguf") {
451 Logger::info("GGUF model type detected by extension for Session constructor: " + model_path_arg);
452 // The GGUF constructor decodes num_cpu_offload_layers as the RAW
453 // GPU-layer hint (<0 all GPU, 0 all CPU, >0 that many GPU layers).
454 // The translation above turned -1 into 0, which that decoder reads as
455 // "all CPU": every default GGUF run was CPU-only (docs/TTFT.md).
457 model_ = std::make_unique<TinyLlamaModel>(initial_model_config_for_model_ctor, model_path_arg);
458 config_ = model_->get_config();
459 } else if (extension == ".safetensors") {
460 Logger::info("SafeTensors model type detected by extension for Session constructor (file case): " + model_path_arg);
462
464 if (st_config_loaded) {
465 Logger::info("Successfully loaded config.json for SafeTensors in Session ctor (file case).");
466 // Log tokenizer_family IMMEDIATELY after loading from config.json (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);
471 // tokenizer_family and num_hidden_layers are now set in initial_model_config_for_model_ctor
472 } else {
473 Logger::warning("Failed to load config.json for SafeTensors in Session ctor (file case). Model will use defaults or CLI overrides.");
474 }
475
477 // Correctly calculate num_cpu_offload_layers based on total_layers_from_config
479 if (num_gpu_layers_from_cli < 0) {
480 initial_model_config_for_model_ctor.num_cpu_offload_layers = 0;
481 } else if (num_gpu_layers_from_cli == 0) {
483 } else {
486 } else {
488 Logger::warning("Total hidden layers unknown from config.json (file case); passing num_gpu_layers_from_cli as num_cpu_offload_layers hint.");
489 }
490 }
492 initial_model_config_for_model_ctor.num_cpu_offload_layers = std::max(0, std::min(initial_model_config_for_model_ctor.num_cpu_offload_layers, total_layers_from_config_file_case));
493 }
494
495 initial_model_config_for_model_ctor.is_gguf_file_loaded = false;
496
498 model_ = std::make_unique<TinyLlamaModel>(initial_model_config_for_model_ctor, st_loader);
499 config_ = model_->get_config();
500 config_.is_gguf_file_loaded = false; // Ensure this is set for session's copy too
501
502 config_.use_kvcache_quantization = use_kv_quant; // Re-apply CLI/constructor preference
503
504 Logger::info("TinyLlamaSession: Finalizing ModelConfig for KVCache initialization. use_kvcache_quantization set to: " +
505 std::string(config_.use_kvcache_quantization ? "true" : "false"));
506
507 // Initialize KVCache with potentially updated config_ (from model load)
508 // and the now-set use_kvcache_quantization flag.
515 } else {
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.");
518 }
519 }
520
521 if (!model_) {
522 throw std::runtime_error("Model pointer is null after instantiation attempt in Session constructor.");
523 }
524
525 try {
527 const GGUFData* gguf_data = model_->get_gguf_data();
528 if (!gguf_data) {
529 throw std::runtime_error("GGUF model loaded but GGUFData is null in Session constructor.");
530 }
531 tokenizer_ = std::make_unique<Tokenizer>(*gguf_data, config_);
532 Logger::info("Tokenizer initialized from GGUF metadata.");
533 } else { // SafeTensors (either from directory or direct .safetensors file)
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()) {
537 tokenizer_dir = ".";
538 }
539
540 std::string vocab_json_path = (std::filesystem::path(tokenizer_dir) / "tokenizer.json").string();
541 // The model_path for the tokenizer constructor should be the actual sentencepiece model file, e.g. data/tokenizer.model
542 // tokenizer_path_arg already holds this (e.g. data/tokenizer.model)
543 std::string sp_model_path = tokenizer_path_arg;
544
545 Logger::info("Initializing Tokenizer for SafeTensors. Vocab JSON path: " + vocab_json_path + ", SP Model path: " + sp_model_path);
546 // Log the tokenizer_family from the config_ that will be passed to the Tokenizer
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);
551
552 tokenizer_ = std::make_unique<Tokenizer>(vocab_json_path, sp_model_path, config_);
553 Logger::info("Tokenizer initialized from external files for SafeTensors model.");
554 }
555 } catch (const std::exception& e) {
556 throw std::runtime_error(std::string("Failed to initialize Tokenizer: ") + e.what());
557 }
558
559 if (!tokenizer_) {
560 throw std::runtime_error("Tokenizer pointer is null after instantiation attempt.");
561 }
562
563 eos_token_id_ = config_.eos_token_id; // Use the session's config_ which is now model's config
564 // paperkiln IR (LOCAL_LLM_STACK.md section 4): EMBER_IR=path runs the
565 // interpreter; an IR embedded in the GGUF (`paperkiln.ir`) runs it unless it
566 // is the graph the tuned llama path implements (ember_ir::tuned_llama_match:
567 // a structural match against the file's llama metadata; family_hint "llama"
568 // is only its pre-filter, IR_SPEC 9.1). EMBER_INTERPRETER=1/0 forces it on or off.
569 if (const GGUFData* gd = model_->get_gguf_data()) {
570 const std::string ir = ember_ir::find_ir(*gd);
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() ||
579 // CUDA builds run the interpreter on the device when the CLI asked for
580 // GPU layers (any nonzero count: the interpreter does not split
581 // layers); EMBER_IR_DEVICE=cpu keeps it on the CPU. Every op the
582 // interpreter has (recurrent state, gating and attention-residual ops
583 // and sliding-window attention included) runs on either; an IR past
584 // the device kernels' limits stays on the CPU (ember_ir::device_supported).
585 bool ir_gpu = false;
586#ifdef HAS_CUDA
587 const char* dev = std::getenv("EMBER_IR_DEVICE");
588 ir_gpu = num_gpu_layers_from_cli != 0 && !(dev && std::string(dev) == "cpu") &&
590#endif
591 ir_ = std::make_unique<ember_ir::Interpreter>(ir, *gd, ir_gpu);
592 ir_doc_ = ir;
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.");
596 }
597 }
598 }
599 // Small quantised models (a Q8_0 download for the browser): dequantise
600 // once, so the CPU path runs at f32 speed on the quantised values.
601 // EMBER_DEQUANT_AT_LOAD=0 keeps the per-call quantised kernels.
602 {
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).");
606 }
607
608 // Chat mode (chat_tokenizer.h): the GGUF's chat template is resolved by chat::resolve (declared
609 // paperkiln.chat_template, paperkiln's specials, a word-level vocabulary, a third-party record by
610 // sha256 or markers and architecture), and the vocabulary goes through the vendored paperkiln
611 // tokenizers ("bpe", "gpt2" with "llama-bpe" or "qwen2") or the SentencePiece port ("llama",
612 // Gemma 3). A GGUF with no template keeps ember's own tokenizers and serves raw completion.
613 {
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") {
618 if (const GGUFData* gd = model_->get_gguf_data()) {
620 } else {
622 }
623 const size_t n_merges = v.g.merges.size();
626 bpe_ = std::move(cs.tok);
627 if (bpe_) {
628 repetition_penalty_ = 1.0f; // mtstudio chat applies none
629 Logger::info(std::string("Chat tokenizer (") + bpe_->kind() + "): " + std::to_string(bpe_->size()) +
630 " tokens, " + std::to_string(n_merges) + " merges (chat mode)");
631 Logger::info("Chat template " + bpe_->format().name + " (" + cs.why + ")");
632 } else if (cs.resolved) {
633 word_template_ = std::make_unique<microtorch::chat::Template>(cs.tmpl);
634 Logger::info("Chat template " + cs.tmpl.id() + " (" + cs.why + "), served by ember's own tokenizer");
635 } else {
636 Logger::info("No chat template: " + cs.why);
637 }
638 }
639 }
640 // Per-turn state tokens (state_tokens.h): k from the GGUF's paperkiln.state_tokens.k (absent =
641 // 0); the interpreter has already checked it against its state_weight. The template contract
642 // needs paperkiln's chat format and the interpreter.
643 if (const GGUFData* gd = model_->get_gguf_data()) {
644 auto it = gd->metadata.find("paperkiln.state_tokens.k");
645 int k = 0;
646 if (it != gd->metadata.end())
647 std::visit(
648 [&](const auto& v) {
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);
651 },
652 it->second);
653 if (k > 0) {
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");
657 // The insertion rule is defined over paperkiln/1's markers (state_tokens.h; ir.hpp names
658 // no other template yet).
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 " +
662 std::string(bpe_ ? bpe_->format().name : "none") + " (" + chat_template_why_ + ")");
663 state_k_ = k;
664 state_vocab_ = ir_->vocab();
665 Logger::info("Per-turn state tokens: k " + std::to_string(k) + " (ids " + std::to_string(state_vocab_) + " .. " +
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"));
668 }
669 }
670
671 const ModelConfig& final_model_config = model_->get_config(); // Explicitly use model's config
673
674 int effective_cpu_offload_layers = final_model_config.num_cpu_offload_layers;
676 if (gpu_layers_for_kvcache < 0) gpu_layers_for_kvcache = 0; // Sanity check, should not happen if model ctor is correct
678
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));
682
683 if (total_model_layers <= 0) {
684 throw std::runtime_error("Model config has zero or negative num_hidden_layers before KVCache init.");
685 }
686 if (final_model_config.num_attention_heads <= 0) {
687 throw std::runtime_error("Model config has zero or negative num_attention_heads before KVCache init.");
688 }
689
690 int head_dim = final_model_config.hidden_size / final_model_config.num_attention_heads;
691
692 kv_cache_.initialize(final_model_config, // Total layers for CPU part of KVCache
694 gpu_layers_for_kvcache, // Actual GPU layers for device memory
695 final_model_config.max_position_embeddings,
696 final_model_config.num_key_value_heads,
697 head_dim,
699 Logger::info("TinyLlamaSession initialization complete (after KVCache init).");
700}
static void warning(const std::string &message)
Definition logger.cpp:139
static void info(const std::string &message)
Definition logger.cpp:135
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.
ModelConfig config_
Definition api.h:294
std::string chat_template_why_
Definition api.h:309
std::unique_ptr< ember::ChatTokenizer > bpe_
Definition api.h:307
std::mt19937 rng_
Definition api.h:297
std::unique_ptr< TinyLlamaModel > model_
Definition api.h:288
std::unique_ptr< ember_ir::Interpreter > ir_
Definition api.h:291
std::unique_ptr< Tokenizer > tokenizer_
Definition api.h:293
std::string ir_doc_
Definition api.h:292
std::unique_ptr< microtorch::chat::Template > word_template_
Definition api.h:308
std::vector< float > v
size_t j
bool tuned_llama_match(const std::string &ir_json, const GGUFData &gguf, std::string *why)
Definition graph_ir.cpp:440
bool device_supported(const std::string &ir_json)
Definition graph_ir.cpp:463
std::string find_ir(const GGUFData &gguf)
Definition graph_ir.cpp:525
ChatVocab chat_vocab_from_gguf(const GGUFData &gd, const std::string &path)
Definition chat_gguf.h:13
ChatSetup make_chat_setup(ChatVocab v)
GgufTokenizer read_gguf_tokenizer(const std::string &path)
Complete representation of a GGUF file's contents.
void initialize(const ModelConfig &config, int total_num_model_layers, int num_gpu_layers_to_allocate, int max_seq_len_arg, int num_kv_heads, int head_dim, int max_batch_size_arg=1)
Initializes the KV cache with given dimensions.
Definition kv_cache.cpp:10
Model configuration structure holding architecture and hyperparameters.
Definition model.h:82
int hidden_size
Definition model.h:83
int num_attention_heads
Definition model.h:85
bool use_mmap_for_gguf
Definition model.h:104
int eos_token_id
Definition model.h:95
int num_cpu_offload_layers
Definition model.h:106
bool is_gguf_file_loaded
Definition model.h:103
bool use_kvcache_quantization
Definition model.h:105
int num_hidden_layers
Definition model.h:87
int num_key_value_heads
Definition model.h:86
TokenizerFamily tokenizer_family
Definition model.h:123
int max_position_embeddings
Definition model.h:89

References bpe_, chat_template_why_, ember::chat_vocab_from_gguf(), config_, ember_ir::device_supported(), ModelConfig::eos_token_id, eos_token_id_, ember_ir::find_ir(), ModelConfig::hidden_size, microtorch::chat::Template::id(), Logger::info(), KVCache::initialize(), ir_, ir_doc_, ModelConfig::is_gguf_file_loaded, k, kv_cache_, ModelConfig::LLAMA3_TIKTOKEN, ModelConfig::LLAMA_SENTENCEPIECE, SafeTensorsLoader::load_model_config_from_json(), ember::make_chat_setup(), max_batch_size_, ModelConfig::max_position_embeddings, model_, ModelConfig::num_attention_heads, ModelConfig::num_cpu_offload_layers, ModelConfig::num_hidden_layers, ModelConfig::num_key_value_heads, microtorch::bpe::read_gguf_tokenizer(), repetition_penalty_, ember::ChatSetup::resolved, state_k_, state_vocab_, ember::ChatSetup::tmpl, ember::ChatSetup::tok, tokenizer_, ModelConfig::tokenizer_family, ember_ir::tuned_llama_match(), ModelConfig::use_kvcache_quantization, ModelConfig::use_mmap_for_gguf, v, Logger::warning(), ember::ChatSetup::why, and word_template_.

◆ ~TinyLlamaSession()

tinyllama::TinyLlamaSession::~TinyLlamaSession ( )

Destructor to ensure proper cleanup (e.g., KVCache CUDA memory).

Definition at line 702 of file api.cpp.

702 {
703 Logger::info("TinyLlamaSession: Destroyed.");
704}

References Logger::info().

◆ TinyLlamaSession() [2/2]

tinyllama::TinyLlamaSession::TinyLlamaSession ( const TinyLlamaSession &  )
privatedelete

Member Function Documentation

◆ batch_generation_parallel()

bool tinyllama::TinyLlamaSession::batch_generation_parallel ( const std::vector< int > &  current_tokens,
const std::vector< int > &  token_positions,
const std::vector< int > &  original_sequence_indices,
std::vector< std::vector< float > > &  batch_logits 
)
private

Definition at line 2063 of file api.cpp.

2066 {
2067 Logger::info("[DEBUG] Entering batch_generation_parallel");
2068
2069 int num_sequences = current_tokens.size();
2070
2071 if (num_sequences == 0 || token_positions.size() != current_tokens.size()) {
2072 Logger::error("[Batch Generation] Invalid input sizes");
2073 return false;
2074 }
2075
2076 Logger::info("[Batch Generation] Processing " + std::to_string(num_sequences) +
2077 " sequences in parallel generation step");
2078
2079 // Create batch embeddings for all sequences
2080 std::vector<float> batch_embeddings;
2082
2083 for (int i = 0; i < num_sequences; ++i) {
2084 std::vector<float> token_embedding = model_->lookup_embedding(current_tokens[i]);
2085 if (token_embedding.empty()) {
2086 Logger::error("[Batch Generation] Embedding lookup failed for token " + std::to_string(current_tokens[i]));
2087 return false;
2088 }
2090 }
2091
2092 // Process through CPU layers if any
2094 Logger::info("[Batch Generation] Processing " + std::to_string(config_.num_cpu_offload_layers) +
2095 " CPU layers for batch generation");
2096
2097 std::vector<std::vector<float>> cpu_batch_logits = model_->forward_cpu_batch_generation(
2099
2100 if (cpu_batch_logits.size() != static_cast<size_t>(num_sequences)) {
2101 Logger::error("[Batch Generation] CPU batch generation returned wrong number of results");
2102 return false;
2103 }
2104
2105 // If all layers are on CPU, we're done
2108 Logger::info("[Batch Generation] All CPU layers processed, returning logits");
2109 return true;
2110 }
2111
2112 // Convert CPU results back to batch embeddings for GPU processing
2113 batch_embeddings.clear();
2115 // Note: This would need the CPU layer output activations, not logits
2116 // For now, fall back to sequential processing if mixed CPU/GPU
2117 Logger::warning("[Batch Generation] Mixed CPU/GPU not yet implemented for batch generation");
2118 return false;
2119 }
2120
2121 // GPU-only processing
2123#ifdef HAS_CUDA
2124 Logger::info("[Batch Generation] Processing GPU layers for batch generation");
2125
2126 float* d_batch_embeddings = nullptr;
2127 size_t batch_size_bytes = batch_embeddings.size() * sizeof(float);
2128
2132
2133 std::vector<std::vector<float>> gpu_batch_logits = model_->forward_device_batch_generation(
2135
2137
2138 if (gpu_batch_logits.size() != static_cast<size_t>(num_sequences)) {
2139 Logger::error("[Batch Generation] GPU batch generation returned wrong number of results");
2140 return false;
2141 }
2142
2144 Logger::info("[Batch Generation] GPU batch generation completed successfully");
2145 return true;
2146#else
2147 Logger::error("[Batch Generation] GPU processing requested but CUDA not available.");
2148 return false;
2149#endif
2150 }
2151
2152 Logger::error("[Batch Generation] No valid processing path found");
2153 return false;
2154}
static void error(const std::string &message)
Definition logger.cpp:143

References config_, Logger::error(), ModelConfig::hidden_size, Logger::info(), j, kv_cache_, model_, ModelConfig::num_cpu_offload_layers, ModelConfig::num_hidden_layers, and Logger::warning().

Referenced by generate_batch().

◆ batch_prefill_parallel()

bool tinyllama::TinyLlamaSession::batch_prefill_parallel ( const std::vector< std::vector< int > > &  all_tokens,
const std::vector< int > &  prompt_lengths,
std::vector< std::vector< float > > &  batch_final_logits 
)
private

Definition at line 1784 of file api.cpp.

1786 {
1787 Logger::info("[EMERGENCY_DEBUG] batch_prefill_parallel function entry - FIRST LINE");
1788 Logger::info("[DEBUG] Entering batch_prefill_parallel");
1789 // Calculate total tokens across all prompts for batch prefill
1791 for (int len : prompt_lengths) {
1793 }
1794
1796 Logger::error("[Batch Prefill] No tokens to process in batch prefill.");
1797 return false;
1798 }
1799
1800 Logger::info("[Batch Prefill] Processing " + std::to_string(all_tokens.size()) +
1801 " sequences with total " + std::to_string(total_tokens_across_all_prompts) + " tokens");
1802
1803 // Process all tokens for all sequences in batch
1804 Logger::info("[Batch Prefill] Preparing batch embeddings for " +
1805 std::to_string(total_tokens_across_all_prompts) + " tokens");
1806
1807 // Calculate required memory
1808 size_t required_memory_bytes = static_cast<size_t>(total_tokens_across_all_prompts) * config_.hidden_size * sizeof(float);
1809 Logger::info("[DEBUG] About to allocate " + std::to_string(required_memory_bytes) + " bytes (" +
1810 std::to_string(required_memory_bytes / (1024*1024)) + " MB) for batch embeddings");
1811
1813 Logger::info("[DEBUG] batch_embeddings allocation completed successfully");
1814
1815 int token_offset = 0;
1816
1817 // Add detailed logging for token processing
1818 Logger::info("[DEBUG] Starting token embedding processing for " + std::to_string(all_tokens.size()) + " sequences");
1819
1820 for (size_t seq_idx = 0; seq_idx < all_tokens.size(); ++seq_idx) {
1821 Logger::info("[DEBUG] Processing sequence " + std::to_string(seq_idx) + " with " + std::to_string(prompt_lengths[seq_idx]) + " tokens");
1822
1823 // Log first few tokens of this sequence
1824 std::string token_ids_str = "Token IDs: ";
1825 for (int i = 0; i < std::min(5, prompt_lengths[seq_idx]); ++i) {
1826 token_ids_str += std::to_string(all_tokens[seq_idx][i]) + " ";
1827 }
1828 if (prompt_lengths[seq_idx] > 5) token_ids_str += "...";
1829 Logger::info("[DEBUG] Sequence " + std::to_string(seq_idx) + " " + token_ids_str);
1830
1831 for (int token_idx = 0; token_idx < prompt_lengths[seq_idx]; ++token_idx) {
1833
1834 // Log token placement in batch
1835 if (seq_idx < 2 && token_idx < 3) { // Only log first few tokens of first two sequences
1836 Logger::info("[DEBUG] Placing token " + std::to_string(current_token_id) +
1837 " from seq " + std::to_string(seq_idx) + " pos " + std::to_string(token_idx) +
1838 " at batch offset " + std::to_string(token_offset));
1839 }
1840
1841 std::vector<float> token_embedding = model_->lookup_embedding(current_token_id);
1842 if (token_embedding.empty() || token_embedding.size() != static_cast<size_t>(config_.hidden_size)) {
1843 Logger::error("[Batch Prefill] Embedding lookup failed for token " +
1844 std::to_string(current_token_id) +
1845 " in sequence " + std::to_string(seq_idx));
1846 return false;
1847 }
1848
1849 // Ensure we don't write beyond bounds
1852 Logger::error("[Batch Prefill] Buffer overflow detected at token offset " + std::to_string(token_offset));
1853 return false;
1854 }
1855
1856 std::copy(token_embedding.begin(), token_embedding.end(),
1858 token_offset++;
1859 }
1860
1861 Logger::info("[DEBUG] Sequence " + std::to_string(seq_idx) + " complete. Next token_offset: " + std::to_string(token_offset));
1862 }
1863
1864 // Process CPU layers if any
1865 std::vector<float> cpu_processed_embeddings;
1867 Logger::info("[Batch Prefill] Processing " + std::to_string(config_.num_cpu_offload_layers) +
1868 " CPU layers for batch prefill");
1869 cpu_processed_embeddings = model_->forward_cpu_batch(batch_embeddings,
1872 0, &kv_cache_,
1874
1875 if (cpu_processed_embeddings.empty()) {
1876 Logger::error("[Batch Prefill] CPU batch processing failed.");
1877 return false;
1878 }
1879 } else {
1881 }
1882
1883 // Process GPU layers if any
1884 std::vector<float> final_batch_logits;
1885
1887 // All CPU - get logits from CPU
1888 Logger::info("[Batch Prefill] All layers on CPU, computing logits");
1889 final_batch_logits = model_->forward_cpu_logits_batch(cpu_processed_embeddings,
1891 } else {
1892 // GPU layers exist - transfer to GPU and process
1893#ifdef HAS_CUDA
1894 Logger::info("[Batch Prefill] Processing GPU layers for batch prefill");
1895
1896 Logger::info("[DEBUG] About to allocate GPU memory for batch prefill");
1897 float* d_batch_embeddings = nullptr;
1898 size_t batch_size_bytes = cpu_processed_embeddings.size() * sizeof(float);
1899 Logger::info("[DEBUG] GPU allocation size: " + std::to_string(batch_size_bytes) + " bytes (" +
1900 std::to_string(batch_size_bytes / (1024*1024)) + " MB)");
1901
1902 Logger::info("[DEBUG] Calling cudaMalloc...");
1904 Logger::info("[DEBUG] cudaMalloc completed successfully");
1905
1906 Logger::info("[DEBUG] Calling cudaMemcpy host to device...");
1909 Logger::info("[DEBUG] cudaMemcpy completed successfully");
1910
1911 // Call forward_device_batch_prefill ONCE with all the batch data
1912 Logger::info("[DEBUG] Calling forward_device_batch_prefill with " + std::to_string(total_tokens_across_all_prompts) + " total tokens");
1913 std::vector<float> all_batch_logits = model_->forward_device_batch_prefill(
1915
1916 Logger::info("[DEBUG] forward_device_batch_prefill completed, returned " + std::to_string(all_batch_logits.size()) + " total logits");
1917
1919
1921#else
1922 Logger::error("[Batch Prefill] GPU processing requested but CUDA not available.");
1923 return false;
1924#endif
1925 }
1926
1927 Logger::info("[Batch Prefill] Successfully processed batch prefill for " +
1928 std::to_string(all_tokens.size()) + " sequences");
1929
1930 Logger::info("[DEBUG] About to return from batch_prefill_parallel");
1931 Logger::info("[DEBUG] batch_final_logits.size()=" + std::to_string(batch_final_logits.size()));
1932 for (size_t i = 0; i < batch_final_logits.size() && i < 3; ++i) {
1933 Logger::info("[DEBUG] batch_final_logits[" + std::to_string(i) + "].size()=" + std::to_string(batch_final_logits[i].size()));
1934 }
1935
1936 // Extract logits for the last token of each sequence
1937 batch_final_logits.clear();
1938 batch_final_logits.resize(all_tokens.size());
1939
1941 // For CPU-only, extract last token logits from the flat array
1942 if (final_batch_logits.size() != static_cast<size_t>(total_tokens_across_all_prompts * config_.vocab_size)) {
1943 Logger::error("[Batch Prefill] CPU logits size mismatch. Expected: " +
1945 ", got: " + std::to_string(final_batch_logits.size()));
1946 return false;
1947 }
1948
1949 int token_offset = 0;
1950 for (size_t seq_idx = 0; seq_idx < all_tokens.size(); ++seq_idx) {
1952
1953 Logger::info("[DEBUG] Extracting logits for sequence " + std::to_string(seq_idx) +
1954 ": token_offset=" + std::to_string(token_offset) +
1955 ", prompt_length=" + std::to_string(prompt_lengths[seq_idx]) +
1956 ", last_token_pos=" + std::to_string(last_token_pos));
1957
1959
1960 // Bounds check before copying
1963 if (src_end > final_batch_logits.size()) {
1964 Logger::error("[Batch Prefill] CPU logits bounds check failed for sequence " + std::to_string(seq_idx));
1965 return false;
1966 }
1967
1968 std::copy(final_batch_logits.begin() + src_start,
1969 final_batch_logits.begin() + src_end,
1970 batch_final_logits[seq_idx].begin());
1971
1972 // Log a few logit values for debugging
1973 if (seq_idx < 2) { // Only for first two sequences
1974 std::string logit_sample = "First 5 logits: ";
1975 for (int i = 0; i < 5 && i < config_.vocab_size; ++i) {
1976 logit_sample += std::to_string(batch_final_logits[seq_idx][i]) + " ";
1977 }
1978 Logger::info("[DEBUG] Sequence " + std::to_string(seq_idx) + " " + logit_sample);
1979 }
1980
1982 }
1983 } else {
1984 // For GPU, check if logits are for all tokens or just last tokens
1985 Logger::info("[DEBUG] GPU batch logits size: " + std::to_string(final_batch_logits.size()) +
1986 ", expected for all tokens: " + std::to_string(total_tokens_across_all_prompts * config_.vocab_size) +
1987 ", expected for last tokens only: " + std::to_string(all_tokens.size() * config_.vocab_size));
1988
1989 if (final_batch_logits.size() == static_cast<size_t>(total_tokens_across_all_prompts * config_.vocab_size)) {
1990 // GPU returned logits for all tokens, extract last token for each sequence
1991 Logger::info("[DEBUG] GPU returned logits for all tokens, extracting last token logits");
1992 int token_offset = 0;
1993 for (size_t seq_idx = 0; seq_idx < all_tokens.size(); ++seq_idx) {
1995
1996 Logger::info("[DEBUG] GPU: Extracting logits for sequence " + std::to_string(seq_idx) +
1997 ": token_offset=" + std::to_string(token_offset) +
1998 ", prompt_length=" + std::to_string(prompt_lengths[seq_idx]) +
1999 ", last_token_pos=" + std::to_string(last_token_pos));
2000
2002
2005 if (src_end > final_batch_logits.size()) {
2006 Logger::error("[Batch Prefill] GPU logits bounds check failed for sequence " + std::to_string(seq_idx));
2007 return false;
2008 }
2009
2010 std::copy(final_batch_logits.begin() + src_start,
2011 final_batch_logits.begin() + src_end,
2012 batch_final_logits[seq_idx].begin());
2013
2014 // Log a few logit values for debugging
2015 if (seq_idx < 2) { // Only for first two sequences
2016 std::string logit_sample = "First 5 logits: ";
2017 for (int i = 0; i < 5 && i < config_.vocab_size; ++i) {
2018 logit_sample += std::to_string(batch_final_logits[seq_idx][i]) + " ";
2019 }
2020 Logger::info("[DEBUG] GPU Sequence " + std::to_string(seq_idx) + " " + logit_sample);
2021 }
2022
2024 }
2025 } else if (final_batch_logits.size() == static_cast<size_t>(all_tokens.size() * config_.vocab_size)) {
2026 // GPU returned logits for last tokens only
2027 Logger::info("[DEBUG] GPU returned logits for last tokens only");
2028 for (size_t seq_idx = 0; seq_idx < all_tokens.size(); ++seq_idx) {
2029 Logger::info("[DEBUG] GPU Last-Token-Only: Processing sequence " + std::to_string(seq_idx) +
2030 " at logit offset " + std::to_string(seq_idx * config_.vocab_size));
2031
2033
2036 if (src_end > final_batch_logits.size()) {
2037 Logger::error("[Batch Prefill] GPU logits bounds check failed for sequence " + std::to_string(seq_idx));
2038 return false;
2039 }
2040
2041 std::copy(final_batch_logits.begin() + src_start,
2042 final_batch_logits.begin() + src_end,
2043 batch_final_logits[seq_idx].begin());
2044
2045 // Log a few logit values for debugging
2046 if (seq_idx < 2) { // Only for first two sequences
2047 std::string logit_sample = "First 5 logits: ";
2048 for (int i = 0; i < 5 && i < config_.vocab_size; ++i) {
2049 logit_sample += std::to_string(batch_final_logits[seq_idx][i]) + " ";
2050 }
2051 Logger::info("[DEBUG] GPU Last-Token Sequence " + std::to_string(seq_idx) + " " + logit_sample);
2052 }
2053 }
2054 } else {
2055 Logger::error("[Batch Prefill] GPU logits size doesn't match expected patterns");
2056 return false;
2057 }
2058 }
2059
2060 return true;
2061}
int vocab_size
Definition model.h:88

References config_, Logger::error(), ModelConfig::hidden_size, Logger::info(), j, kv_cache_, model_, ModelConfig::num_cpu_offload_layers, ModelConfig::num_hidden_layers, and ModelConfig::vocab_size.

Referenced by generate_batch().

◆ bpe_decode()

std::string tinyllama::TinyLlamaSession::bpe_decode ( const std::vector< int > &  ids) const
inline

Definition at line 249 of file api.h.

249{ return bpe_ ? bpe_->decode(ids) : std::string(); }

References bpe_.

◆ bpe_encode()

std::vector< int > tinyllama::TinyLlamaSession::bpe_encode ( const std::string &  text) const
inline

Definition at line 250 of file api.h.

250{ return bpe_ ? bpe_->encode(text) : std::vector<int>(); }
std::string text

References bpe_, and text.

◆ bpe_prompt_ids()

std::vector< int > tinyllama::TinyLlamaSession::bpe_prompt_ids ( const std::string &  text) const
inline

Definition at line 213 of file api.h.

213 {
214 if (!bpe_) return {};
215 std::vector<int> ids = bpe_->encode(text);
216 if (state_k_ > 0) {
217 const ember::ChatFormat& f = bpe_->format();
218 ids = ember::insert_state_blocks(ids, state_vocab_, state_k_, f.eos_ids.at(0), f.turn_ids.at(0),
219 f.turn_ids.at(1));
220 }
221 return ids;
222 }
std::vector< int > insert_state_blocks(const std::vector< int > &ids, int vocab, int k, int eot, int user, int assistant)

References bpe_, ember::insert_state_blocks(), j, state_k_, state_vocab_, and text.

Referenced by bpe_token_count(), and generate().

◆ bpe_token_count()

int tinyllama::TinyLlamaSession::bpe_token_count ( const std::string &  text) const
inline

Definition at line 208 of file api.h.

208 {
209 return bpe_ ? static_cast<int>(bpe_prompt_ids(text).size()) : 0;
210 }
std::vector< int > bpe_prompt_ids(const std::string &text) const
Definition api.h:213

References bpe_, bpe_prompt_ids(), and text.

◆ chat_format()

const ember::ChatFormat * tinyllama::TinyLlamaSession::chat_format ( ) const
inline

Definition at line 193 of file api.h.

193{ return bpe_ ? &bpe_->format() : nullptr; }

References bpe_.

◆ chat_template()

const microtorch::chat::Template * tinyllama::TinyLlamaSession::chat_template ( ) const
inline

Definition at line 198 of file api.h.

198 {
199 return bpe_ ? &bpe_->format().tmpl : word_template_ ? word_template_.get() : nullptr;
200 }

References bpe_, and word_template_.

◆ chat_template_why()

const std::string & tinyllama::TinyLlamaSession::chat_template_why ( ) const
inline

Definition at line 201 of file api.h.

201{ return chat_template_why_; }

References chat_template_why_.

◆ generate()

std::string tinyllama::TinyLlamaSession::generate ( const std::string &  prompt,
int  steps = 128,
float  temperature = 0.1f,
int  top_k = 40,
float  top_p = 0.9f,
const std::string &  system_prompt = "",
bool  apply_q_a_format = false 
)

Generates text based on a given prompt.

This method supports various text generation strategies through its sampling parameters. For temperatures close to 0, it approaches deterministic/greedy sampling. For higher temperatures with top-k and top-p, it produces more diverse outputs.

The method can also apply Q:A formatting to the prompt, which is recommended for both GGUF and SafeTensors models when used via the command-line interface.

Parameters
promptThe input prompt string.
stepsThe number of tokens to generate.
temperatureSampling temperature. Lower values (e.g., 0.1) make the output more focused and deterministic.
top_kTop-K sampling parameter. Limits sampling to K most likely tokens. Set to 0 or vocab_size to disable.
top_pNucleus sampling parameter. Limits sampling to tokens comprising top P probability mass (0.0 to 1.0).
system_promptOptional system prompt to guide the generation.
apply_q_a_formatWhether to apply Q:A format ("Q: [prompt]\nA:"). Recommended true for command-line use.
Returns
The generated text string (excluding the prompt).
Exceptions
std::runtime_errorif generation fails.

Definition at line 706 of file api.cpp.

710 { // Renamed for clarity
711 auto t_start = std::chrono::high_resolution_clock::now(); // Start timing
712
713 // TTFT instrumentation (docs/TTFT.md): each phase up to the first token.
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();
717 };
718 last_ttft_ = TtftBreakdown{};
719 last_ttft_.t_entry = ttft_clock::now();
720
721 generated_text_for_api_return_.clear(); // Clear for new generation
722 generated_stream_.str(""); // Clear for new generation
723 generated_stream_.clear(); // Clear error flags
724
725 Logger::info("[Generate API] User prompt: \"" + user_prompt + "\", System prompt: \"" + system_prompt_arg + "\", Steps: " + std::to_string(steps));
726
727 if (!model_ || !tokenizer_) {
728 throw std::runtime_error("Model or tokenizer not loaded.");
729 }
730
731 std::string final_prompt_for_encoding;
732 bool used_chat_template = false;
733
734 // Log conditions for chat template application
735 if (tokenizer_) {
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));
740 }
741 } else {
742 Logger::warning("[Generate API] Tokenizer is null before checking chat template!");
743 }
744 std::string family_log_str = "UNKNOWN";
747 Logger::info("[Generate API] Configured tokenizer_family: " + family_log_str);
748
749 // New Priority Logic:
750 // Priority 0: WORD_LEVEL models (transformer_cpp exports) are raw word-level
751 // LMs: "Q:"/"A:" wrappers are OOV noise that shift every position, and any
752 // chat formatting ("user: ... assistant:") is the caller's contract.
753 // BPE chat models: the caller (server.cpp) already built the exact SFT
754 // template with its <|user|>/<|assistant|> markers; nothing is added.
755 if (bpe_) {
757 used_chat_template = false;
759 Logger::info("[Generate API] WORD_LEVEL tokenizer: using raw prompt (no Q/A or chat template).");
761 used_chat_template = false;
762 }
763 // Priority 1 (NEW): Legacy Q/A formatting if CLI hint is true
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:";
769 } else {
770 temp_prompt = "Q: " + user_prompt + "\\nA:";
771 }
773 used_chat_template = false; // Q/A is not a 'chat template' in the GGUF sense
774 }
775 // Priority 2 (WAS 1): GGUF Chat Template from Tokenizer (only if Q/A hint is false)
776 else if (tokenizer_ && !tokenizer_->get_gguf_chat_template().empty()) {
777 std::string gguf_template_content = tokenizer_->get_gguf_chat_template();
779 bool looks_like_jinja = (gguf_template_content.find("{%") != std::string::npos);
780
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:";
786 } else {
787 temp_prompt = "Q: " + user_prompt + "\\\\nA:";
788 }
790 used_chat_template = false;
791 } else {
792 Logger::info("[Generate API] Using GGUF chat template from tokenizer (Q/A Hint false - Priority 2).");
794 used_chat_template = true;
795 }
796 }
797 // Priority 3 (WAS 2): Llama 3 family specific template (only if Q/A hint false and no GGUF template)
799 Logger::info("[Generate API] Llama 3 tokenizer family detected, using apply_chat_template (Q/A Hint false, No GGUF template - Priority 3).");
801 used_chat_template = true;
802 }
803 // Priority 4 (WAS 3/4 depending on apply_q_a_format_cli_hint): Raw prompt (if all above are false)
804 else {
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()) {
808 } else {
810 }
811 used_chat_template = false;
812 }
813
814 Logger::debug("[Generate API] Final prompt for encoding (first 100 chars): \\\"" + final_prompt_for_encoding.substr(0, 100) + "\\\"");
815
816 // BPE chat models take no BOS (the SFT template starts at <|user|>).
817 // Per-turn state tokens: the k state ids after every <|endoftext|> that ends a reply, and
818 // <|endoftext|> + the state ids for a reply that stopped early (bpe_prompt_ids, state_tokens.h).
819 std::vector<int> tokens = bpe_ ? bpe_prompt_ids(final_prompt_for_encoding)
820 : tokenizer_->encode(final_prompt_for_encoding, true, false, Tokenizer::PreTokenizeMethod::DEFAULT); // add_bos=true, add_eos=false (EOS handled by loop)
821 // Stop tokens: in chat mode the chat format's (paperkiln: <|endoftext|>, or a
822 // generated <|user|> or <|assistant|>, the model starting the next turn;
823 // ChatML: <|im_end|> or <|endoftext|>), else the GGUF's eos.
824 auto is_stop = [&](int id) {
825 return bpe_ ? bpe_->format().is_stop(id) : id == eos_token_id_;
826 };
827 auto decode_one = [&](int id) {
828 return bpe_ ? bpe_->decode({id}) : tokenizer_->decode({id}, false);
829 };
830 // Streaming: the per-token callback (set_token_callback) sees every
831 // generated token except stop markers, in order.
832 auto emit_token = [&](int id) {
833 if (token_callback_ && !is_stop(id)) token_callback_(id);
834 };
835 bool stopped_at_first = false;
836 last_stop_reason_ = "length";
838 last_prompt_ids_ = tokens;
839 last_generated_ids_.clear();
840 last_stop_id_ = -1;
841
842 if (tokens.empty()) {
843 Logger::warning("Tokenization resulted in empty ID list for prompt: " +
845 return "";
846 }
847
848 int num_prompt_tokens = tokens.size();
849 Logger::info("[Generate API] Number of prompt tokens: " + std::to_string(num_prompt_tokens));
850 last_ttft_.num_prompt_tokens = num_prompt_tokens;
851 last_ttft_.tokenize_ms = ms_between(last_ttft_.t_entry, ttft_clock::now());
852 {
853 // Prefix-cache opportunity against what the previous request cached.
854 size_t lcp = 0;
855 while (lcp < kv_token_history_.size() && lcp < tokens.size() &&
856 kv_token_history_[lcp] == tokens[lcp]) {
857 ++lcp;
858 }
859 last_ttft_.prefix_prev_cached_tokens = static_cast<int>(kv_token_history_.size());
861 static_cast<int>(std::min(lcp, tokens.size() - 1));
863 static_cast<double>(last_ttft_.prefix_reusable_tokens) / num_prompt_tokens;
864 }
865 // Prefix reuse (EMBER_PREFIX_REUSE, on by default): the KV cache still
866 // holds the previous request's prompt and reply, so the shared leading
867 // tokens are kept and only the rest is prefilled, from that offset. At
868 // least the last prompt token is always recomputed, for its logits. Only
869 // the batched prefill path reuses; the token-by-token path starts over.
870 static const bool prefix_reuse_on = [] {
871 const char* v = std::getenv("EMBER_PREFIX_REUSE");
872 return !(v && std::string(v) == "0");
873 }();
875 // Recurrent IR state (linear attention, state-space scans) cannot drop
876 // tokens: resume where the interpreter holds the state after the prefix
877 // (the live state or a snapshot), else nearer the start or from 0.
878 // Under turn_state eviction, a prefix ending inside a turn whose text rows were dropped (an
879 // edited earlier message) resumes from that turn's start, which is re-prefilled.
880 if (ir_ && (ir_->has_recurrent() || ir_->evicts())) reuse = ir_->resume_point(reuse);
881 kv_token_history_.clear(); // refilled only if this request completes
882
883 // Test hook (set_scripted_replies): the next scripted ids stand in for sampling, through the same
884 // callback, stop rule and reply decoding as sampled ids; kv_token_history_ stays empty.
885 if (bpe_ && !scripted_replies_.empty()) {
886 const std::vector<int> script = scripted_replies_.front();
888 std::vector<int> out;
889 for (int id : script) {
890 if (static_cast<int>(out.size()) >= steps) break;
891 out.push_back(id);
892 emit_token(id);
893 if (is_stop(id)) break;
894 }
895 if (!out.empty() && is_stop(out.back())) {
896 last_stop_reason_ = bpe_->format().is_eos(out.back()) ? "eos" : "turn";
897 last_stop_id_ = out.back();
898 out.pop_back();
899 }
900 last_generated_tokens_ = static_cast<int>(out.size());
902 const std::string text = bpe_->decode(out);
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);
907 }
908
909 int total_steps = num_prompt_tokens + steps -1; // Max total tokens including prompt
910 int generated_count = 0;
911 int next_token_id = -1;
912
913 std::vector<float> logits; // Declare logits here, to be populated by prefill or loop
914 std::vector<int> generated_token_ids; // Track generated tokens separately
915
916 // The /chat contract's repetition penalty (set_repeat_penalty) over prompt
917 // + reply so far, the token being sampled not included.
918 const bool repeat_on = repeat_penalty_ != 1.0f && repeat_last_n_ > 0;
919 std::vector<char> repeat_seen;
920 auto apply_repeat_penalty = [&](std::vector<float>& lg) {
921 if (repeat_on)
923 };
924
925 auto t_kv_reset = ttft_clock::now();
926 if (reuse > 0) {
927 // Keep positions [0, reuse); every later position is rewritten before
928 // attention reads it (attention reads only positions <= the current one).
930 } else {
931 kv_cache_.clear_data(); // Clear K/V vector data for all layers
932 kv_cache_.seq_len = 0; // Reset KVCache logical sequence length for new sequence
933 }
934 last_ttft_.kv_reset_ms = ms_between(t_kv_reset, ttft_clock::now());
935
936 std::vector<float> current_data_host; // To hold embedding or output of CPU layers
937 int start_pos_for_loop = 0;
938
939 // Batch prefill for every prompt length. The old 32-token threshold cost
940 // short (chat-sized) prompts: on chat_sft_bpe, 20 prompts of 10-30 tokens,
941 // p50 TTFT 294-338 ms -> 53-55 ms on CUDA and 983-1251 -> 592-650 ms on CPU,
942 // with identical greedy replies (experiments/chat_2026-10-18/).
943 // EMBER_PREFILL_MIN restores a threshold (measurement knob).
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;
947 }();
948 bool prefill_enabled = num_prompt_tokens >= prefill_min;
949 // Reused positions: [0, prefill_from) stay in the cache untouched.
950 const int prefill_from = prefill_enabled ? reuse : 0;
951 const int prefill_count = num_prompt_tokens - prefill_from;
953
954if (prefill_enabled) {
955 Logger::info("[Generate API] Prefill enabled. num_prompt_tokens: " + std::to_string(num_prompt_tokens) +
956 ", num_cpu_offload_layers: " + std::to_string(config_.num_cpu_offload_layers) +
957 ", total_hidden_layers: " + std::to_string(config_.num_hidden_layers));
959 auto t_embed = ttft_clock::now();
960
961 std::vector<float> batch_initial_embeddings(ir_ ? 0 : prefill_count * config_.hidden_size);
962 for (int i = 0; i < (ir_ ? 0 : prefill_count); ++i) { // the interpreter embeds tokens itself
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));
966 return ""; // Or handle error appropriately
967 }
968 std::copy(token_embedding.begin(), token_embedding.end(), batch_initial_embeddings.begin() + i * config_.hidden_size);
969 }
970 auto t_prefill = ttft_clock::now();
972
973 if (ir_) {
974 const std::vector<int> slice(tokens.begin() + prefill_from, tokens.begin() + prefill_from + prefill_count);
975 logits = ir_->forward(slice, prefill_from);
976 } else {
977 // If there are CPU layers to process for prefill
978 std::vector<float> cpu_processed_embeddings;
980 Logger::info("[Generate API] Prefill: Processing " + std::to_string(config_.num_cpu_offload_layers) + " CPU layers for the batch.");
982 if (cpu_processed_embeddings.empty()) {
983 Logger::error("Prefill: forward_cpu_batch returned empty or failed.");
984 return "";
985 }
986 } else {
987 cpu_processed_embeddings = batch_initial_embeddings; // No CPU layers, pass embeddings directly
988 }
989
990 // If all layers are CPU layers (i.e., num_gpu_layers == 0)
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);
994 if (batch_logits.empty() || batch_logits.size() % config_.vocab_size != 0) {
995 Logger::error("Prefill: forward_cpu_logits_batch returned invalid logits.");
996 return "";
997 }
998 // Extract logits for the last token of the prompt
999 logits.assign(batch_logits.begin() + (prefill_count - 1) * config_.vocab_size,
1001 } else { // GPU layers exist and need to be processed
1002#ifdef HAS_CUDA
1003 Logger::info("[Generate API] Prefill: Processing GPU layers for the batch.");
1004 // Copy the (potentially CPU-processed) embeddings to the device
1005 float* d_temp_batch_embeddings = nullptr;
1007
1008 if (batch_embeddings_size_bytes == 0) {
1009 Logger::error("Prefill: cpu_processed_embeddings is empty, cannot proceed with GPU batch prefill.");
1010 return ""; // Or handle error appropriately
1011 }
1012
1015 Logger::error("Prefill: cudaMalloc failed for d_temp_batch_embeddings.");
1016 return ""; // Or handle error appropriately
1017 }
1018
1021
1022 // Events on the default stream (the forward runs on stream 0).
1027 nvtxRangePushA("ttft_prefill_forward");
1028 logits = model_->forward_device_batch_prefill(d_temp_batch_embeddings, prefill_count, prefill_from, &kv_cache_, 0);
1029 nvtxRangePop();
1032 float fwd_span_ms = 0.0f;
1035 last_ttft_.op_ms = model_->last_prefill_op_ms();
1038
1041 }
1042#else
1043 // An error, not an empty reply: a caller must not read it as a finished generation.
1044 throw std::runtime_error("[Generate API] GPU layers requested but CUDA not available");
1045#endif
1046 }
1047 } // llama path
1048
1049 if (logits.empty()) {
1050 Logger::error("Prefill: Logits are empty after prefill processing.");
1051 return ""; // Critical error
1052 }
1053 auto t_sample = ttft_clock::now();
1058 next_token_id = sample_top_k_top_p_temperature(logits, temperature, top_k, top_p, rng_);
1059 generated_token_ids.push_back(next_token_id); // Track generated token
1062
1063 // Stream the first generated token from prefill
1066 last_ttft_.t_first_token = ttft_clock::now();
1067 last_ttft_.first_token = true;
1070
1071 start_pos_for_loop = num_prompt_tokens; // Next token will be at num_prompt_tokens
1072 if (bpe_ && is_stop(next_token_id)) {
1073 stopped_at_first = true; // the reply is empty; skip the decode loop
1074 total_steps = 0;
1075 }
1076 kv_cache_.seq_len = num_prompt_tokens; // KVCache is now filled up to num_prompt_tokens
1077
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));
1081 }
1082
1083 // EMBER_DECODE_TIMING=1: wall time per decode phase, host side here and
1084 // GPU-forward side in TinyLlamaModel::forward_device, summarised on stderr
1085 // after the loop (per decode step).
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();
1094 t_phase = now;
1095 };
1096#ifdef HAS_CUDA
1097 if (decode_timing) model_->decode_phase_ms().clear();
1098#endif
1099 const auto t_decode_start = ttft_clock::now();
1100 // EMBER_SPEC=k: speculative decoding with prompt-lookup drafts of up to k
1101 // tokens, greedy (top_k == 1) only, all-GPU or all-CPU layers, and off
1102 // under the contract repetition penalty (its window moves with each row).
1103 static const int spec_k = [] {
1104 const char* e = std::getenv("EMBER_SPEC");
1105 return e ? std::max(0, std::atoi(e)) : 0;
1106 }();
1107 const bool spec_on = spec_k > 0 && top_k == 1 && !repeat_on &&
1110 // EMBER_DRAFT selects the drafter (experiments/draft_2026-10-27). Unset or "pld": prompt lookup alone, as
1111 // before. "table": prompt lookup first; where it finds nothing, the corpus table EMBER_DRAFT_TABLE
1112 // (draft_table.h), its draft cut where the product of the entries' shares falls below EMBER_DRAFT_THETA.
1113 // Verification decides every token either way, so the replies are plain decode's.
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";
1120 }();
1121 static const double draft_theta = [] {
1122 const char* e = std::getenv("EMBER_DRAFT_THETA");
1123 return e && *e ? std::atof(e) : 0.02;
1124 }();
1127
1128 for (int pos = start_pos_for_loop; pos < total_steps; ++pos) {
1129 t_phase = ttft_clock::now();
1130 ++decode_steps;
1132 Logger::warning("Reached max sequence length (" +
1133 std::to_string(config_.max_position_embeddings) +
1134 "). Stopping.");
1135 break;
1136 }
1137
1138 int input_token_id;
1139
1140 if (pos == num_prompt_tokens && start_pos_for_loop == num_prompt_tokens) {
1141 // This is the first token *after* a successful prefill.
1142 // `next_token_id` was already sampled using prefill's logits from the *last prompt token*.
1143 // This `next_token_id` is the actual input for the current position `pos`.
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));
1146 } else {
1147 // Standard iterative logic:
1148 // If prefill didn't run (start_pos_for_loop == 0):
1149 // For pos < num_prompt_tokens: use prompt token.
1150 // For pos >= num_prompt_tokens: use previously sampled next_token_id.
1151 // If prefill did run (start_pos_for_loop == num_prompt_tokens):
1152 // This 'else' block is for pos > num_prompt_tokens, so use previously sampled next_token_id.
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));
1156 } else {
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));
1158 }
1159 }
1160
1161 // Speculative step: one batched forward over [input, draft...] from pos
1162 // gives the logits at every position; tokens are taken greedily while
1163 // they equal the draft, plus the first one that does not (or a stop).
1164 // KV entries written for rejected draft positions lie past the valid
1165 // length and are overwritten by the next steps. Recurrent IR state has
1166 // absorbed them all: the next forward, from pos + accepted + 1, restores
1167 // the interpreter's snapshot after that row (all_rows keeps every row).
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());
1174 std::vector<int> draft = room > 0 ? prompt_lookup_draft(hist, room) : std::vector<int>{};
1175 if (draft.empty() && draft_table && room > 0) draft = draft_table->draft(hist, room, draft_theta);
1176 last_ttft_.spec_draft_ms += ms_between(t_draft, ttft_clock::now());
1178 // A draft never proposes a state id (the head has no logits for them).
1179 for (size_t i = 0; i < draft.size(); ++i)
1180 if (state_k_ > 0 && draft[i] >= state_vocab_) {
1181 draft.resize(i);
1182 break;
1183 }
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());
1188 const int hs = config_.hidden_size, vs = config_.vocab_size;
1189 std::vector<float> emb(ir_ ? 0 : (size_t)n * hs);
1190 for (int i = 0; i < (ir_ ? 0 : n); ++i) { // the interpreter embeds tokens itself
1191 const std::vector<float> e = model_->lookup_embedding(batch[i]);
1192 std::copy(e.begin(), e.end(), emb.begin() + (size_t)i * hs);
1193 }
1194 std::vector<float> all;
1195#ifdef HAS_CUDA
1196 // The verification pass as a captured CUDA graph with a greedy pick per
1197 // row on the device (speculative_verify.cpp); EMBER_VERIFY=legacy, or a
1198 // shape it does not cover, takes the passes below.
1199 static const bool verify_legacy = [] {
1200 const char* v = std::getenv("EMBER_VERIFY");
1201 return v && std::string(v) == "legacy";
1202 }();
1203 std::vector<ember_verify::RowPick> picks;
1204 const bool have_picks = !ir_ && !verify_legacy && config_.num_cpu_offload_layers == 0 &&
1205 model_->forward_device_verify_graph(batch, pos, &kv_cache_, &picks);
1206#else
1207 const bool have_picks = false;
1208#endif
1209 if (have_picks) {
1210 // every row's pick is in `picks`; its logits stay on the device
1211 } else if (ir_) {
1212 all = ir_->forward(batch, pos, /*all_rows=*/true);
1214 all = model_->forward_cpu_logits_batch(
1215 model_->forward_cpu_batch(emb, n, config_.num_hidden_layers, pos, &kv_cache_), n);
1216 } else {
1217#ifdef HAS_CUDA
1218 // The per-launch verification path; the batched prefill when it does
1219 // not apply.
1220 if (config_.num_cpu_offload_layers == 0) all = model_->forward_device_verify(batch, pos, &kv_cache_);
1221 if (all.empty()) {
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));
1225 all = model_->forward_device_batch_prefill(d_emb, n, pos, &kv_cache_, 0, /*all_logits=*/true);
1227 }
1228#endif
1229 }
1230 if (!have_picks && all.size() != (size_t)n * vs) {
1231 Logger::error("Speculative verification returned " + std::to_string(all.size()) + " logits; stopping.");
1232 break;
1233 }
1234 int accepted = 0;
1235 bool stop = false;
1236 for (int i = 0; i < n; ++i) {
1237 int t = -1;
1238#ifdef HAS_CUDA
1239 if (have_picks && repetition_penalty_ <= 1.0f) t = greedy_from_pick(picks[i], temperature, rng_);
1240 // The record cannot decide, or a debug dump wants the row: the row itself
1241 // (with the record's pick kept when it decided).
1242 if (have_picks && (t < 0 || logits_debug_on())) {
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.");
1246 stop = true;
1247 break;
1248 }
1251 if (t < 0) t = sample_top_k_top_p_temperature(lg, temperature, top_k, top_p, rng_);
1252 }
1253#endif
1254 if (!have_picks) {
1255 std::vector<float> lg(all.begin() + (size_t)i * vs, all.begin() + (size_t)(i + 1) * vs);
1258 t = sample_top_k_top_p_temperature(lg, temperature, top_k, top_p, rng_);
1259 }
1260 generated_token_ids.push_back(t);
1262 emit_token(t);
1265 next_token_id = t;
1266 if (is_stop(t) || generated_count >= steps) { stop = true; break; }
1267 if (i + 1 < n && t == batch[i + 1]) { ++accepted; continue; }
1268 break;
1269 }
1270 last_ttft_.spec_drafted += static_cast<int>(draft.size());
1273 kv_cache_.seq_len = pos + accepted + 1; // input + accepted drafts are now cached
1274 host_phase("host.spec_step");
1275 if (stop) break;
1276 pos += accepted; // the loop's ++pos then moves to the next input position
1277 continue;
1278 }
1279 }
1280
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);
1286 }
1287 host_phase("host.embed_lookup");
1288
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));
1291 break;
1292 }
1293
1294 // Mixed-mode forward pass logic
1295 if (ir_) {
1296 logits = ir_->forward({input_token_id}, pos);
1298#ifdef HAS_CUDA
1299 // Mixed CPU/GPU mode: First process CPU layers, then GPU layers
1300 Logger::debug("[Mixed Mode] Processing " + std::to_string(config_.num_cpu_offload_layers) + " CPU layers first");
1301 std::vector<float> intermediate_activations = model_->forward(current_data_host, pos, &kv_cache_, nullptr);
1302
1303 Logger::debug("[Mixed Mode] CPU layers complete, transferring to GPU for remaining layers");
1305 logits = model_->forward_device(model_->get_x_dev(), pos, &kv_cache_, nullptr);
1306#else
1307 Logger::error("[Mixed Mode] Mixed CPU/GPU mode requested but CUDA not available. Cannot proceed.");
1308 break;
1309#endif
1310 } else if (config_.num_cpu_offload_layers == 0) {
1311#ifdef HAS_CUDA
1312 // GPU-only mode: the captured decode graph when it applies, else the
1313 // per-launch forward.
1314 logits = model_->forward_device_graph(input_token_id, pos, &kv_cache_);
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");
1319 logits = model_->forward_device(model_->get_x_dev(), pos, &kv_cache_, nullptr);
1320 host_phase("host.forward_device(total)");
1321 }
1322#else
1323 Logger::error("[GPU-only Mode] GPU-only mode requested but CUDA not available. Cannot proceed.");
1324 break;
1325#endif
1326 } else {
1327 // CPU-only mode
1328 logits = model_->forward(current_data_host, pos, &kv_cache_, nullptr);
1329 }
1330
1331 // Sampling logic: Only sample if we're at the last prompt token or generating
1332 if (pos == num_prompt_tokens - 1 || pos >= num_prompt_tokens) {
1336 next_token_id = sample_top_k_top_p_temperature(logits, temperature, top_k, top_p, rng_);
1337 host_phase("host.sample");
1338
1339 // Only add to generated tokens if we're actually generating (not just finishing prompt)
1340 if (pos >= num_prompt_tokens) {
1344
1345 // Stream the generated token
1348 } else {
1349 // This is the first token sampled from the last prompt position
1353
1354 // Stream the first generated token
1357 // Token-by-token path: only the total is recorded (batch_prefill=false).
1358 last_ttft_.t_first_token = ttft_clock::now();
1359 last_ttft_.first_token = true;
1361
1362 Logger::info("[Generate API] First token sampled from prompt: " + std::to_string(next_token_id) +
1363 ", Decoded: \"" + decode_one(next_token_id) + "\"");
1364 }
1365 }
1366
1367 if (is_stop(next_token_id) && pos >= (bpe_ ? num_prompt_tokens - 1 : num_prompt_tokens)) { // EOS only if we are generating
1368 Logger::info("EOS token (" + std::to_string(eos_token_id_) +
1369 ") sampled at pos " + std::to_string(pos) + ". Stopping.");
1370 break;
1371 }
1372
1373 if (generated_count >= steps) { // steps is max new tokens
1374 Logger::info("Reached max generation steps (" + std::to_string(steps) + "). Stopping.");
1375 break;
1376 }
1377
1378 // KVCache seq_len update for single token pass (already handled by batch prefill for its tokens)
1379 // This needs to happen *after* the forward pass for the current 'pos' has updated the cache for 'pos'.
1380 // So, after processing 'pos', the cache now contains information up to and including 'pos'.
1381 // The length of the sequence in the cache is pos + 1.
1382 if (!prefill_enabled || pos >= num_prompt_tokens) { // only update if not prefill, or if we are past prompt token processing in prefill case
1383 kv_cache_.seq_len = pos + 1;
1384 // Logger::debug("[Generate Loop] KVCache seq_len updated to: " + std::to_string(kv_cache_.seq_len) + " after processing pos " + std::to_string(pos));
1385 }
1386 host_phase("host.decode_text_and_rest");
1387 }
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,
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);
1394#ifdef HAS_CUDA
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);
1397#endif
1398 }
1399
1400 // Log all generated IDs before decoding
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) + " ";
1404 }
1406
1407 std::string result;
1408 if (bpe_) {
1409 std::vector<int> out = generated_token_ids;
1410 if (!out.empty() && is_stop(out.back())) {
1411 last_stop_reason_ = bpe_->format().is_eos(out.back()) ? "eos" : "turn";
1412 last_stop_id_ = out.back();
1413 out.pop_back();
1414 }
1415 last_generated_tokens_ = static_cast<int>(out.size());
1417 result = bpe_->decode(out);
1418 // Strip leading and trailing whitespace (the C isspace set), as mtstudio does.
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);
1424 } else {
1425 const bool stopped = !generated_token_ids.empty() && is_stop(generated_token_ids.back());
1426 if (stopped) last_stop_reason_ = "eos";
1427 last_generated_tokens_ = static_cast<int>(generated_token_ids.size()) - (stopped ? 1 : 0);
1428 result = tokenizer_->decode(generated_token_ids, true);
1429 }
1430 Logger::info("Generated response: " + ember::utf8_valid(result));
1431
1432 // What the KV cache now holds: the prompt plus every generated token that
1433 // was fed back through the model (all but the last one sampled).
1434 kv_token_history_ = tokens;
1435 if (!generated_token_ids.empty()) {
1437 generated_token_ids.end() - 1);
1438 }
1439 // Per-turn state tokens: a reply that ended with a generated <|endoftext|> is followed by the k
1440 // state ids, prefilled with it (no sampling), so the cache holds the finished exchange as the
1441 // next request's prompt writes it. A reply that stopped early gets <|endoftext|> and the state
1442 // ids from the next prompt instead (state_tokens.h).
1443 if (ir_ && state_k_ > 0 && !generated_token_ids.empty() && bpe_->format().is_eos(generated_token_ids.back())) {
1444 const int at = static_cast<int>(kv_token_history_.size());
1446 std::vector<int> close = {generated_token_ids.back()};
1447 for (int j = 0; j < state_k_; ++j) close.push_back(state_vocab_ + j);
1448 ir_->forward(close, at);
1449 kv_token_history_.insert(kv_token_history_.end(), close.begin(), close.end());
1450 }
1451 }
1452
1453 auto t_end = std::chrono::high_resolution_clock::now(); // End timing
1454 double time_taken_ms = std::chrono::duration<double, std::milli>(t_end - t_start).count();
1455
1456 // Create a string with the desired precision for the time
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");
1460
1461 return result;
1462}
static void debug(const std::string &message)
Definition logger.cpp:131
A lightweight tokenizer implementation for text processing.
Definition tokenizer.h:61
std::stringstream generated_stream_
Definition api.h:303
std::vector< int > last_prompt_ids_
Definition api.h:312
std::string last_stop_reason_
Definition api.h:310
std::vector< std::vector< int > > scripted_replies_
Definition api.h:315
TtftBreakdown last_ttft_
Definition api.h:305
std::function< void(int)> token_callback_
Definition api.h:314
std::vector< int > kv_token_history_
Definition api.h:306
std::vector< int > last_generated_ids_
Definition api.h:312
std::string generated_text_for_api_return_
Definition api.h:304
int n
const char * name
std::vector< E > a
node out
std::string utf8_valid(const std::string &s)
Definition utf8_text.h:74
static std::vector< int > prompt_lookup_draft(const std::vector< int > &h, int k)
Definition api.cpp:96
static void debug_dump_topk(const std::vector< float > &logits)
Definition api.cpp:188
static const ember_draft::DraftTable * corpus_draft_table(int vocab_size)
Definition api.cpp:109
static bool logits_debug_on()
Definition api.cpp:216
static int sample_top_k_top_p_temperature(const std::vector< float > &logits, float temperature, int top_k, float top_p, std::mt19937 &rng)
Definition api.cpp:222
static void log_vector_summary_detailed(const std::string &name, const std::vector< float > &v, int current_pos, int current_layer, int N)
Definition api.cpp:43
static void apply_repetition_penalty(std::vector< float > &logits, const std::vector< int > &generated, float penalty, size_t window=64)
Definition api.cpp:152
static void apply_contract_penalty(std::vector< float > &lg, const std::vector< int > &a, const std::vector< int > &b, float p, int last_n, IsStop is_stop, std::vector< char > &seen)
Definition api.cpp:170
void clear_data()
Definition model.h:186
int seq_len
Definition model.h:161
std::chrono::steady_clock::time_point t_first_token
Definition api.h:39
double prefix_hit_rate
Definition api.h:59
int prefix_prev_cached_tokens
Definition api.h:57
std::map< std::string, double > op_ms
Definition api.h:74
std::chrono::steady_clock::time_point t_entry
Definition api.h:38
double prefill_gpu_span_ms
Definition api.h:48
double prefill_wall_ms
Definition api.h:46

References a, tinyllama::apply_contract_penalty(), tinyllama::apply_repetition_penalty(), tinyllama::TtftBreakdown::batch_prefill, bpe_, bpe_prompt_ids(), KVCache::clear_data(), config_, tinyllama::corpus_draft_table(), Logger::debug(), tinyllama::debug_dump_topk(), Tokenizer::DEFAULT, ember_draft::DraftTable::draft(), tinyllama::TtftBreakdown::embed_ms, eos_token_id_, Logger::error(), tinyllama::TtftBreakdown::first_token, generated_stream_, generated_text_for_api_return_, ModelConfig::hidden_size, id, Logger::info(), ir_, j, kv_cache_, tinyllama::TtftBreakdown::kv_reset_ms, kv_token_history_, last_generated_ids_, last_generated_tokens_, last_prompt_ids_, last_stop_id_, last_stop_reason_, last_ttft_, ModelConfig::LLAMA3_TIKTOKEN, ModelConfig::LLAMA_SENTENCEPIECE, tinyllama::log_vector_summary_detailed(), tinyllama::logits_debug_on(), ModelConfig::max_position_embeddings, model_, n, name, ModelConfig::num_cpu_offload_layers, ModelConfig::num_hidden_layers, tinyllama::TtftBreakdown::num_prompt_tokens, tinyllama::TtftBreakdown::op_ms, out, pos, tinyllama::TtftBreakdown::prefill_gpu_span_ms, tinyllama::TtftBreakdown::prefill_wall_ms, tinyllama::TtftBreakdown::prefix_hit_rate, tinyllama::TtftBreakdown::prefix_prev_cached_tokens, tinyllama::TtftBreakdown::prefix_reusable_tokens, tinyllama::TtftBreakdown::prefix_reused_tokens, tinyllama::prompt_lookup_draft(), repeat_last_n_, repeat_penalty_, repetition_penalty_, rng_, tinyllama::TtftBreakdown::sample_ms, tinyllama::sample_top_k_top_p_temperature(), scripted_replies_, KVCache::seq_len, tinyllama::TtftBreakdown::spec_accepted, tinyllama::TtftBreakdown::spec_draft_calls, tinyllama::TtftBreakdown::spec_draft_ms, tinyllama::TtftBreakdown::spec_drafted, tinyllama::TtftBreakdown::spec_passes, state_k_, state_vocab_, tinyllama::TtftBreakdown::t_entry, tinyllama::TtftBreakdown::t_first_token, text, token_callback_, tinyllama::TtftBreakdown::tokenize_ms, tokenizer_, ModelConfig::tokenizer_family, tinyllama::TtftBreakdown::ttft_ms, ember::utf8_valid(), v, ModelConfig::vocab_size, Logger::warning(), and ModelConfig::WORD_LEVEL.

Referenced by generate_batch().

◆ generate_batch()

std::vector< std::string > tinyllama::TinyLlamaSession::generate_batch ( const std::vector< std::string > &  prompts,
int  steps = 128,
float  temperature = 0.1f,
int  top_k = 40,
float  top_p = 0.9f,
const std::string &  system_prompt = "",
bool  apply_q_a_format = false 
)

Generates text for multiple prompts in a single batch (parallel processing).

This method processes multiple independent prompts simultaneously, providing significant efficiency gains compared to sequential generate() calls. Each prompt is processed independently with its own KV cache state.

Parameters
promptsVector of input prompt strings to process in batch.
stepsThe number of tokens to generate for each prompt.
temperatureSampling temperature. Lower values make output more focused and deterministic.
top_kTop-K sampling parameter. Limits sampling to K most likely tokens.
top_pNucleus sampling parameter. Limits sampling to tokens comprising top P probability mass.
system_promptOptional system prompt applied to all prompts in the batch.
apply_q_a_formatWhether to apply Q:A format to all prompts in the batch.
Returns
Vector of generated text strings, one for each input prompt.
Exceptions
std::runtime_errorif batch generation fails or if prompts vector is empty.

Definition at line 1473 of file api.cpp.

1478 {
1479 auto t_start = std::chrono::high_resolution_clock::now();
1480
1481 if (prompts.empty()) {
1482 throw std::runtime_error("Cannot process empty prompts vector for batch generation.");
1483 }
1484
1485 if (static_cast<int>(prompts.size()) > max_batch_size_) {
1486 throw std::runtime_error("Batch size " + std::to_string(prompts.size()) +
1487 " exceeds maximum batch size " + std::to_string(max_batch_size_));
1488 }
1489
1490 Logger::info("[Batch Generate API] Processing " + std::to_string(prompts.size()) +
1491 " prompts in batch. Steps: " + std::to_string(steps));
1492
1493 if (!model_ || !tokenizer_) {
1494 throw std::runtime_error("Model or tokenizer not loaded for batch generation.");
1495 }
1496
1497 // Process each prompt to create final prompts and tokenize them
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;
1502
1503 for (size_t i = 0; i < prompts.size(); ++i) {
1504 // Apply same prompt processing logic as single generate()
1505 std::string final_prompt_for_encoding;
1506 bool used_chat_template = false;
1507
1508 // Same priority logic as single generate()
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:";
1513 } else {
1514 final_prompt_for_encoding = "Q: " + prompts[i] + "\\nA:";
1515 }
1516 } else if (tokenizer_ && !tokenizer_->get_gguf_chat_template().empty()) {
1517 std::string gguf_template_content = tokenizer_->get_gguf_chat_template();
1519 bool looks_like_jinja = (gguf_template_content.find("{%") != std::string::npos);
1520
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:";
1525 } else {
1526 final_prompt_for_encoding = "Q: " + prompts[i] + "\\\\nA:";
1527 }
1528 } else {
1529 Logger::info("[Batch Generate API] Using GGUF chat template for prompt " + std::to_string(i));
1531 used_chat_template = true;
1532 }
1534 Logger::info("[Batch Generate API] Using Llama 3 chat template for prompt " + std::to_string(i));
1536 used_chat_template = true;
1537 } else {
1538 Logger::info("[Batch Generate API] Using raw prompt for prompt " + std::to_string(i));
1539 if (!system_prompt_arg.empty()) {
1541 } else {
1543 }
1544 }
1545
1548
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()); // Fallback to BOS token
1552 }
1553
1554 prompt_lengths[i] = all_tokens[i].size();
1556
1557 Logger::info("[Batch Generate API] Prompt " + std::to_string(i) + ": " +
1558 std::to_string(prompt_lengths[i]) + " tokens");
1559 }
1560
1561 // Initialize batch mode
1562 kv_cache_.initialize_batch(static_cast<int>(prompts.size()));
1564 kv_token_history_.clear(); // the cache no longer holds the last generate()'s tokens
1565
1566 std::vector<std::string> results(prompts.size());
1567
1568 // Try parallel batch processing if enabled
1570 Logger::info("[Batch Generate API] Using parallel batch processing");
1571
1572 Logger::info("[DEBUG] Initializing KV cache for batch mode");
1573 kv_cache_.initialize_batch(static_cast<int>(prompts.size()));
1574 Logger::info("[DEBUG] KV cache batch initialization completed");
1575
1576 Logger::info("[DEBUG] Clearing KV cache data");
1578 kv_token_history_.clear(); // the cache no longer holds the last generate()'s tokens
1579 Logger::info("[DEBUG] KV cache clear completed");
1580
1581 // Phase 1: Parallel Batch Prefill
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()));
1584
1585 std::vector<std::vector<float>> batch_final_logits;
1586
1588
1589 if (prefill_success && batch_final_logits.size() == prompts.size()) {
1590 Logger::info("[Batch Generate API] Batch prefill successful, starting parallel generation");
1591
1592 // Add safety check for batch_final_logits
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!");
1598 }
1599 if (batch_final_logits[i].size() != static_cast<size_t>(config_.vocab_size)) {
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));
1603 }
1604 // Check for NaN/Inf values
1605 for (size_t j = 0; j < std::min(static_cast<size_t>(10UL), batch_final_logits[i].size()); ++j) {
1606 if (!std::isfinite(batch_final_logits[i][j])) {
1607 Logger::error("[DEBUG] batch_final_logits[" + std::to_string(i) + "][" + std::to_string(j) + "] is not finite: " + std::to_string(batch_final_logits[i][j]));
1609 }
1610 }
1611 }
1612 Logger::info("[DEBUG] batch_final_logits integrity check passed");
1613
1614 // Sample first tokens for all sequences
1615 std::vector<int> current_tokens(prompts.size());
1616 std::vector<std::vector<int>> all_generated_tokens(prompts.size());
1617 std::vector<int> sequence_positions(prompts.size());
1618 std::vector<bool> sequence_finished(prompts.size(), false);
1619
1620 Logger::info("[DEBUG] Starting token sampling for " + std::to_string(prompts.size()) + " sequences");
1621
1622 for (size_t i = 0; i < prompts.size(); ++i) {
1623 Logger::info("[DEBUG] Sampling token for sequence " + std::to_string(i));
1624
1625 // Safety check before sampling
1626 if (i >= batch_final_logits.size()) {
1627 Logger::error("[DEBUG] Index " + std::to_string(i) + " out of bounds for batch_final_logits (size: " + std::to_string(batch_final_logits.size()) + ")");
1629 }
1630
1631 try {
1633 Logger::info("[DEBUG] Sampled token " + std::to_string(current_tokens[i]) + " 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()));
1637 }
1638
1640 sequence_positions[i] = prompt_lengths[i]; // Position for next token
1641
1642 // Check for EOS
1643 if (current_tokens[i] == eos_token_id_) {
1644 sequence_finished[i] = true;
1645 Logger::info("[DEBUG] Sequence " + std::to_string(i) + " finished with EOS token");
1646 }
1647 }
1648
1649 Logger::info("[DEBUG] Token sampling completed, starting generation loop");
1650
1651 // Phase 2: Parallel Batch Generation
1652 for (int step = 1; step < steps; ++step) {
1653 Logger::info("[DEBUG] Starting generation step " + std::to_string(step));
1654
1655 // Check if all sequences are finished
1656 bool all_finished = true;
1657 for (bool finished : sequence_finished) {
1658 if (!finished) {
1659 all_finished = false;
1660 break;
1661 }
1662 }
1663 if (all_finished) {
1664 Logger::info("[Batch Generate API] All sequences finished at step " + std::to_string(step));
1665 break;
1666 }
1667
1668 Logger::info("[DEBUG] Collecting active sequences for step " + std::to_string(step));
1669
1670 // Collect active sequences
1671 std::vector<int> active_tokens;
1672 std::vector<int> active_positions;
1673 std::vector<int> active_sequence_indices;
1674 std::vector<int> batch_to_original_seq_mapping; // Map batch index to original sequence index
1675
1676 for (size_t i = 0; i < prompts.size(); ++i) {
1677 if (!sequence_finished[i]) {
1678 active_tokens.push_back(current_tokens[i]);
1680 active_sequence_indices.push_back(active_tokens.size() - 1); // Use contiguous 0-based index
1681 batch_to_original_seq_mapping.push_back(i); // Remember original sequence index
1682 Logger::info("[DEBUG] Active sequence " + std::to_string(i) + " mapped to batch index " + std::to_string(active_tokens.size() - 1) +
1683 ": token=" + std::to_string(current_tokens[i]) + ", pos=" + std::to_string(sequence_positions[i]));
1684 }
1685 }
1686
1687 if (active_tokens.empty()) {
1688 Logger::info("[DEBUG] No active tokens, breaking from generation loop");
1689 break;
1690 }
1691
1692 Logger::info("[DEBUG] About to call batch_generation_parallel with " + std::to_string(active_tokens.size()) + " active sequences");
1693
1694 // Process active sequences in parallel
1695 std::vector<std::vector<float>> step_logits;
1697 Logger::info("[DEBUG] batch_generation_parallel returned: " + std::string(generation_success ? "success" : "failure"));
1698
1699 if (!generation_success || step_logits.size() != active_tokens.size()) {
1700 Logger::warning("[Batch Generate API] Parallel generation failed at step " + std::to_string(step) +
1701 ", falling back to sequential processing");
1703 }
1704
1705 Logger::info("[DEBUG] Starting token sampling for step " + std::to_string(step));
1706
1707 // Sample next tokens for active sequences
1708 for (size_t active_idx = 0; active_idx < active_tokens.size(); ++active_idx) {
1709 size_t original_seq_idx = batch_to_original_seq_mapping[active_idx]; // Use mapping to get original sequence index
1710 Logger::info("[DEBUG] Sampling for active_idx=" + std::to_string(active_idx) + ", original_seq_idx=" + std::to_string(original_seq_idx));
1711
1712 // Safety checks
1713 if (active_idx >= step_logits.size()) {
1714 Logger::error("[DEBUG] active_idx " + std::to_string(active_idx) + " out of bounds for step_logits (size: " + std::to_string(step_logits.size()) + ")");
1716 }
1717 if (original_seq_idx >= prompts.size()) {
1718 Logger::error("[DEBUG] original_seq_idx " + std::to_string(original_seq_idx) + " out of bounds for prompts (size: " + std::to_string(prompts.size()) + ")");
1720 }
1721
1722 try {
1723 int next_token = sample_top_k_top_p_temperature(step_logits[active_idx], temperature, top_k, top_p, rng_);
1724 Logger::info("[DEBUG] Sampled next token " + std::to_string(next_token) + " for original_seq_idx " + std::to_string(original_seq_idx));
1725
1729
1730 // Check for EOS
1731 if (next_token == eos_token_id_) {
1733 Logger::info("[DEBUG] Sequence " + std::to_string(original_seq_idx) + " finished with EOS at step " + std::to_string(step));
1734 }
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()));
1738 }
1739 }
1740
1741 Logger::info("[DEBUG] Completed generation step " + std::to_string(step));
1742 }
1743
1744 // Decode results for all sequences
1745 for (size_t i = 0; i < prompts.size(); ++i) {
1746 results[i] = tokenizer_->decode(all_generated_tokens[i], true);
1747 }
1748
1749 Logger::info("[Batch Generate API] Parallel batch processing completed successfully");
1750 } else {
1751 Logger::warning("[Batch Generate API] Batch prefill failed, falling back to sequential processing");
1753 }
1754 } else {
1756 Logger::info("[Batch Generate API] Using sequential processing");
1757
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()));
1761
1762 // Reset to single-sequence mode for this prompt
1763 kv_cache_.seq_len = 0;
1764 kv_token_history_.clear(); // the cache no longer holds the last generate()'s tokens
1765
1766 // Use existing single-sequence generation logic
1767 std::string result = generate(prompts[i], steps, temperature, top_k, top_p,
1769 results[i] = result;
1770 }
1771 }
1772
1773 auto t_end = std::chrono::high_resolution_clock::now();
1774 double time_taken_ms = std::chrono::duration<double, std::milli>(t_end - t_start).count();
1775
1776 std::ostringstream time_ss;
1777 time_ss << std::fixed << std::setprecision(4) << time_taken_ms;
1778 Logger::info("[Batch Generate API] Total batch processing time: " + time_ss.str() + " ms for " +
1779 std::to_string(prompts.size()) + " prompts");
1780
1781 return results;
1782}
bool batch_generation_parallel(const std::vector< int > &current_tokens, const std::vector< int > &token_positions, const std::vector< int > &original_sequence_indices, std::vector< std::vector< float > > &batch_logits)
Definition api.cpp:2063
bool batch_prefill_parallel(const std::vector< std::vector< int > > &all_tokens, const std::vector< int > &prompt_lengths, std::vector< std::vector< float > > &batch_final_logits)
Definition api.cpp:1784
std::string generate(const std::string &prompt, int steps=128, float temperature=0.1f, int top_k=40, float top_p=0.9f, const std::string &system_prompt="", bool apply_q_a_format=false)
Generates text based on a given prompt.
Definition api.cpp:706
void initialize_batch(int batch_size)
Initialize batch mode with specified number of sequences.
Definition model.h:216

References batch_generation_parallel(), batch_prefill_parallel(), KVCache::clear_data(), config_, Tokenizer::DEFAULT, eos_token_id_, Logger::error(), generate(), Logger::info(), KVCache::initialize_batch(), j, kv_cache_, kv_token_history_, ModelConfig::LLAMA3_TIKTOKEN, ModelConfig::LLAMA_SENTENCEPIECE, max_batch_size_, model_, rng_, tinyllama::sample_top_k_top_p_temperature(), KVCache::seq_len, tokenizer_, ModelConfig::tokenizer_family, use_batch_generation_, ModelConfig::vocab_size, and Logger::warning().

◆ get_config()

const ModelConfig & tinyllama::TinyLlamaSession::get_config ( ) const
inline

Definition at line 165 of file api.h.

165{ return config_; }

References config_.

Referenced by PYBIND11_MODULE().

◆ get_kv_cache()

KVCache & tinyllama::TinyLlamaSession::get_kv_cache ( )
inline

Definition at line 166 of file api.h.

166{ return kv_cache_; }

References kv_cache_.

◆ get_repetition_penalty()

float tinyllama::TinyLlamaSession::get_repetition_penalty ( ) const
inline

Definition at line 172 of file api.h.

172{ return repetition_penalty_; }

References repetition_penalty_.

◆ get_tokenizer()

const Tokenizer * tinyllama::TinyLlamaSession::get_tokenizer ( ) const
inline

Definition at line 164 of file api.h.

164{ return tokenizer_.get(); }

References tokenizer_.

◆ gguf_tokens()

const std::vector< std::string > & tinyllama::TinyLlamaSession::gguf_tokens ( ) const

Definition at line 38 of file score.cpp.

38 {
39 static const std::vector<std::string> none;
40 const GGUFData* g = model_ ? model_->get_gguf_data() : nullptr;
41 return g ? g->tokenizer_tokens : none;
42}
std::vector< std::string > tokenizer_tokens

References j, model_, and GGUFData::tokenizer_tokens.

◆ ir_document()

const std::string & tinyllama::TinyLlamaSession::ir_document ( ) const
inline

Definition at line 204 of file api.h.

204{ return ir_doc_; }

References ir_doc_.

◆ is_bpe()

bool tinyllama::TinyLlamaSession::is_bpe ( ) const
inline

Definition at line 191 of file api.h.

191{ return bpe_ != nullptr; }

References bpe_.

◆ is_chat_eos()

bool tinyllama::TinyLlamaSession::is_chat_eos ( int  id) const
inline

Definition at line 207 of file api.h.

207{ return bpe_ && bpe_->format().is_eos(id); }

References bpe_.

◆ is_chat_stop()

bool tinyllama::TinyLlamaSession::is_chat_stop ( int  id) const
inline

Definition at line 206 of file api.h.

206{ return bpe_ && bpe_->format().is_stop(id); }

References bpe_.

◆ last_generated_ids()

const std::vector< int > & tinyllama::TinyLlamaSession::last_generated_ids ( ) const
inline

Definition at line 234 of file api.h.

234{ return last_generated_ids_; }

References last_generated_ids_.

◆ last_generated_tokens()

int tinyllama::TinyLlamaSession::last_generated_tokens ( ) const
inline

Definition at line 228 of file api.h.

228{ return last_generated_tokens_; }

References last_generated_tokens_.

◆ last_prompt_ids()

const std::vector< int > & tinyllama::TinyLlamaSession::last_prompt_ids ( ) const
inline

Definition at line 233 of file api.h.

233{ return last_prompt_ids_; }

References last_prompt_ids_.

◆ last_stop_id()

int tinyllama::TinyLlamaSession::last_stop_id ( ) const
inline

Definition at line 235 of file api.h.

235{ return last_stop_id_; }

References last_stop_id_.

◆ last_stop_reason()

const std::string & tinyllama::TinyLlamaSession::last_stop_reason ( ) const
inline

Definition at line 227 of file api.h.

227{ return last_stop_reason_; }

References last_stop_reason_.

◆ last_ttft()

const TtftBreakdown & tinyllama::TinyLlamaSession::last_ttft ( ) const
inline

Definition at line 182 of file api.h.

182{ return last_ttft_; }

References last_ttft_.

◆ operator=()

TinyLlamaSession & tinyllama::TinyLlamaSession::operator= ( const TinyLlamaSession &  )
privatedelete

◆ sample_step()

int tinyllama::TinyLlamaSession::sample_step ( std::vector< float > &  logits,
const std::vector< int > &  context,
float  temperature,
int  top_k,
float  top_p 
)

Definition at line 1464 of file api.cpp.

1465 {
1466 if (!bpe_) throw std::runtime_error("sample_step: chat models only");
1467 std::vector<char> seen;
1469 [this](int id) { return bpe_->format().is_stop(id); }, seen);
1470 return sample_top_k_top_p_temperature(logits, temperature, top_k, top_p, rng_);
1471}

References tinyllama::apply_contract_penalty(), bpe_, repeat_last_n_, repeat_penalty_, rng_, and tinyllama::sample_top_k_top_p_temperature().

◆ score_context()

int tinyllama::TinyLlamaSession::score_context ( ) const

Definition at line 32 of file score.cpp.

32 {
34 if (ir_ && ir_->max_len() > 0) ctx = std::min(ctx, ir_->max_len());
35 return ctx;
36}

References config_, ir_, and ModelConfig::max_position_embeddings.

Referenced by score_rows().

◆ score_reuse_ok()

bool tinyllama::TinyLlamaSession::score_reuse_ok ( ) const

Definition at line 44 of file score.cpp.

44 {
45 static const bool off = [] {
46 const char* v = std::getenv("EMBER_SCORE_REUSE");
47 return v && std::string(v) == "0";
48 }();
49 if (off || score_full()) return false;
50 if (ir_)
51 return !ir_->on_gpu() && !ir_->has_recurrent() && !ir_->evicts() && !ir_->has_depth_route() &&
52 ir_->state_k() == 0 && !ir_->turn_state();
53 return config_.num_cpu_offload_layers == config_.num_hidden_layers; // every layer on the CPU
54}

References config_, ir_, j, ModelConfig::num_cpu_offload_layers, ModelConfig::num_hidden_layers, off, and v.

Referenced by score_rows().

◆ score_rows()

std::vector< float > tinyllama::TinyLlamaSession::score_rows ( const std::vector< int > &  ids,
int  first_row,
bool  reuse_prefix,
int *  reused = nullptr 
)

Definition at line 56 of file score.cpp.

57 {
58 const int n = static_cast<int>(ids.size());
59 if (n == 0 || first_row < 0 || first_row >= n) throw std::invalid_argument("score_rows: first_row out of range");
60 if (n > score_context())
61 throw std::invalid_argument("score_rows: " + std::to_string(n) + " positions exceed the context of " +
62 std::to_string(score_context()));
63 const int V = score_vocab();
64 kv_token_history_.clear(); // generate() must not reuse what this leaves in the cache
65 int reuse = 0;
67 size_t lcp = 0;
68 while (lcp < score_ids_.size() && lcp < ids.size() && score_ids_[lcp] == ids[lcp]) ++lcp;
69 reuse = static_cast<int>(std::min(lcp, static_cast<size_t>(first_row))); // rows >= first_row are computed
70 }
71 score_ids_.clear(); // set again only when this forward completes
72 if (reused) *reused = reuse;
73 const int m = n - reuse; // positions computed
74 const int rows = n - first_row; // rows returned: the last `rows` of them
75 // The default path computes the head on the returned rows only, where that is row-wise by
76 // construction (the CPU paths); EMBER_SCORE_FULL=1 and the device compute every row.
77 std::vector<float> out;
78 if (ir_) {
79 const std::vector<int> tail(ids.begin() + reuse, ids.end());
80 if (!score_full() && !ir_->on_gpu()) {
81 out = ir_->forward(tail, reuse, /*all_rows=*/false, rows);
82 } else {
83 const std::vector<float> all = ir_->forward(tail, reuse, /*all_rows=*/true);
84 if (all.size() != static_cast<size_t>(m) * V)
85 throw std::runtime_error("score: the interpreter returned " + std::to_string(all.size()) + " logits");
86 out.assign(all.end() - static_cast<long>(rows) * V, all.end());
87 }
88 } else {
89 // As generate()'s prefill: positions [0, reuse) kept, every later one rewritten before it is read.
90 if (reuse > 0) {
92 } else {
95 }
96 const int hs = config_.hidden_size;
97 std::vector<float> h(static_cast<size_t>(m) * hs);
98 for (int i = 0; i < m; ++i) {
99 const std::vector<float> e = model_->lookup_embedding(ids[reuse + i]);
100 if (e.size() != static_cast<size_t>(hs))
101 throw std::runtime_error("score: no embedding for token id " + std::to_string(ids[reuse + i]));
102 std::copy(e.begin(), e.end(), h.begin() + static_cast<long>(i) * hs);
103 }
105 if (cpu_layers > 0) h = model_->forward_cpu_batch(h, m, cpu_layers, reuse, &kv_cache_);
106 if (h.size() != static_cast<size_t>(m) * hs) throw std::runtime_error("score: the CPU layers failed");
107 if (cpu_layers == L) {
108 // The final norm and the LM head are row-wise (rmsnorm_batch_cpu; matmul_f32_f32_batch_cpu's
109 // one dot product per row and output), so the returned rows alone give the bits all rows give.
110 std::vector<float> keep = h;
111 if (!score_full()) keep.assign(h.end() - static_cast<long>(rows) * hs, h.end());
112 const int k_rows = score_full() ? m : rows;
113 std::vector<float> lg = model_->forward_cpu_logits_batch(keep, k_rows);
114 if (lg.size() != static_cast<size_t>(k_rows) * V) throw std::runtime_error("score: the LM head failed");
115 out.assign(lg.end() - static_cast<long>(rows) * V, lg.end());
116 } else {
117#ifdef HAS_CUDA
118 float* d = nullptr;
119 gpuErrchk(cudaMalloc(&d, h.size() * sizeof(float)));
120 gpuErrchk(cudaMemcpy(d, h.data(), h.size() * sizeof(float), cudaMemcpyHostToDevice));
121 // FP32 GEMMs only (no BF16 tensor cores or TF32, which /chat's prefill may use: they move
122 // log-probabilities by ~1e-3), and every row's logits (the same FP32 matvec per row as
123 // decode's LM head).
124 struct Fp32Only {
125 TinyLlamaModel& model;
126 explicit Fp32Only(TinyLlamaModel& mdl) : model(mdl) { model.set_gemm_fp32_only(true); }
127 ~Fp32Only() { model.set_gemm_fp32_only(false); }
128 } fp32_only(*model_);
129 const std::vector<float> all =
130 model_->forward_device_batch_prefill(d, m, reuse, &kv_cache_, 0, /*all_logits=*/true);
132 if (all.size() != static_cast<size_t>(m) * V)
133 throw std::runtime_error("score: the device prefill returned " + std::to_string(all.size()) + " logits");
134 out.assign(all.end() - static_cast<long>(rows) * V, all.end());
135#else
136 throw std::runtime_error("score: GPU layers requested but CUDA not available");
137#endif
138 }
140 }
141 if (out.size() != static_cast<size_t>(rows) * V)
142 throw std::runtime_error("score: " + std::to_string(out.size()) + " logits for " + std::to_string(rows) + " rows");
143 score_ids_ = ids;
144 return out;
145}
Main transformer model class for TinyLlama.
Definition model.h:309
void set_gemm_fp32_only(bool on)
Definition model.h:385
int score_context() const
Definition score.cpp:32
bool score_reuse_ok() const
Definition score.cpp:44
std::vector< int > score_ids_
Definition api.h:317

References KVCache::clear_data(), config_, ModelConfig::hidden_size, ir_, j, kv_cache_, kv_token_history_, model_, n, ModelConfig::num_cpu_offload_layers, ModelConfig::num_hidden_layers, out, score_context(), score_ids_, score_reuse_ok(), score_vocab(), KVCache::seq_len, and TinyLlamaModel::set_gemm_fp32_only().

◆ score_vocab()

int tinyllama::TinyLlamaSession::score_vocab ( ) const

Definition at line 30 of file score.cpp.

30{ return ir_ ? ir_->vocab() : config_.vocab_size; }

References config_, ir_, and ModelConfig::vocab_size.

Referenced by score_rows().

◆ set_repeat_penalty()

void tinyllama::TinyLlamaSession::set_repeat_penalty ( float  p,
int  last_n 
)
inline

Definition at line 179 of file api.h.

const float * p[kIrDepthSourcesMax]

References j, p, repeat_last_n_, and repeat_penalty_.

◆ set_repetition_penalty()

void tinyllama::TinyLlamaSession::set_repetition_penalty ( float  p)
inline

Definition at line 171 of file api.h.

References p, and repetition_penalty_.

◆ set_scripted_replies()

void tinyllama::TinyLlamaSession::set_scripted_replies ( std::vector< std::vector< int > >  r)
inline

Definition at line 247 of file api.h.

247{ scripted_replies_ = std::move(r); }

References j, and scripted_replies_.

◆ set_seed()

void tinyllama::TinyLlamaSession::set_seed ( unsigned  seed)
inline

Definition at line 237 of file api.h.

237{ rng_.seed(seed); }

References rng_.

◆ set_token_callback()

void tinyllama::TinyLlamaSession::set_token_callback ( std::function< void(int)>  cb)
inline

Definition at line 240 of file api.h.

240{ token_callback_ = std::move(cb); }

References j, and token_callback_.

◆ state_k()

int tinyllama::TinyLlamaSession::state_k ( ) const
inline

Definition at line 224 of file api.h.

224{ return state_k_; }

References state_k_.

Member Data Documentation

◆ bpe_

std::unique_ptr<ember::ChatTokenizer> tinyllama::TinyLlamaSession::bpe_
private

◆ chat_template_why_

std::string tinyllama::TinyLlamaSession::chat_template_why_
private

Definition at line 309 of file api.h.

Referenced by chat_template_why(), and TinyLlamaSession().

◆ config_

◆ eos_token_id_

int tinyllama::TinyLlamaSession::eos_token_id_
private

Definition at line 296 of file api.h.

Referenced by generate(), generate_batch(), and TinyLlamaSession().

◆ generated_stream_

std::stringstream tinyllama::TinyLlamaSession::generated_stream_
private

Definition at line 303 of file api.h.

Referenced by generate().

◆ generated_text_for_api_return_

std::string tinyllama::TinyLlamaSession::generated_text_for_api_return_
private

Definition at line 304 of file api.h.

Referenced by generate().

◆ ir_

std::unique_ptr<ember_ir::Interpreter> tinyllama::TinyLlamaSession::ir_
private

◆ ir_doc_

std::string tinyllama::TinyLlamaSession::ir_doc_
private

Definition at line 292 of file api.h.

Referenced by ir_document(), and TinyLlamaSession().

◆ kv_cache_

KVCache tinyllama::TinyLlamaSession::kv_cache_
private

◆ kv_token_history_

std::vector<int> tinyllama::TinyLlamaSession::kv_token_history_
private

Definition at line 306 of file api.h.

Referenced by generate(), generate_batch(), and score_rows().

◆ last_generated_ids_

std::vector<int> tinyllama::TinyLlamaSession::last_generated_ids_
private

Definition at line 312 of file api.h.

Referenced by generate(), and last_generated_ids().

◆ last_generated_tokens_

int tinyllama::TinyLlamaSession::last_generated_tokens_ = 0
private

Definition at line 311 of file api.h.

Referenced by generate(), and last_generated_tokens().

◆ last_prompt_ids_

std::vector<int> tinyllama::TinyLlamaSession::last_prompt_ids_
private

Definition at line 312 of file api.h.

Referenced by generate(), and last_prompt_ids().

◆ last_stop_id_

int tinyllama::TinyLlamaSession::last_stop_id_ = -1
private

Definition at line 313 of file api.h.

Referenced by generate(), and last_stop_id().

◆ last_stop_reason_

std::string tinyllama::TinyLlamaSession::last_stop_reason_
private

Definition at line 310 of file api.h.

Referenced by generate(), and last_stop_reason().

◆ last_ttft_

TtftBreakdown tinyllama::TinyLlamaSession::last_ttft_
private

Definition at line 305 of file api.h.

Referenced by generate(), and last_ttft().

◆ max_batch_size_

int tinyllama::TinyLlamaSession::max_batch_size_ = 1
private

Definition at line 320 of file api.h.

Referenced by generate_batch(), and TinyLlamaSession().

◆ model_

std::unique_ptr<TinyLlamaModel> tinyllama::TinyLlamaSession::model_
private

◆ repeat_last_n_

int tinyllama::TinyLlamaSession::repeat_last_n_ = 64
private

Definition at line 300 of file api.h.

Referenced by generate(), sample_step(), and set_repeat_penalty().

◆ repeat_penalty_

float tinyllama::TinyLlamaSession::repeat_penalty_ = 1.0f
private

Definition at line 299 of file api.h.

Referenced by generate(), sample_step(), and set_repeat_penalty().

◆ repetition_penalty_

float tinyllama::TinyLlamaSession::repetition_penalty_ = 1.3f
private

Definition at line 298 of file api.h.

Referenced by generate(), get_repetition_penalty(), set_repetition_penalty(), and TinyLlamaSession().

◆ rng_

std::mt19937 tinyllama::TinyLlamaSession::rng_ {std::random_device{}()}
private

Definition at line 297 of file api.h.

297{std::random_device{}()}; // RNG for sampling

Referenced by generate(), generate_batch(), sample_step(), and set_seed().

◆ score_ids_

std::vector<int> tinyllama::TinyLlamaSession::score_ids_
private

Definition at line 317 of file api.h.

Referenced by score_rows().

◆ scripted_replies_

std::vector<std::vector<int> > tinyllama::TinyLlamaSession::scripted_replies_
private

Definition at line 315 of file api.h.

Referenced by generate(), and set_scripted_replies().

◆ state_k_

int tinyllama::TinyLlamaSession::state_k_ = 0
private

Definition at line 316 of file api.h.

Referenced by bpe_prompt_ids(), generate(), state_k(), and TinyLlamaSession().

◆ state_vocab_

int tinyllama::TinyLlamaSession::state_vocab_ = 0
private

Definition at line 316 of file api.h.

Referenced by bpe_prompt_ids(), generate(), and TinyLlamaSession().

◆ threads_

int tinyllama::TinyLlamaSession::threads_
private

Definition at line 301 of file api.h.

◆ token_callback_

std::function<void(int)> tinyllama::TinyLlamaSession::token_callback_
private

Definition at line 314 of file api.h.

Referenced by generate(), and set_token_callback().

◆ tokenizer_

std::unique_ptr<Tokenizer> tinyllama::TinyLlamaSession::tokenizer_
private

Definition at line 293 of file api.h.

Referenced by generate(), generate_batch(), get_tokenizer(), and TinyLlamaSession().

◆ use_batch_generation_

bool tinyllama::TinyLlamaSession::use_batch_generation_
private

Definition at line 302 of file api.h.

Referenced by generate_batch().

◆ word_template_

std::unique_ptr<microtorch::chat::Template> tinyllama::TinyLlamaSession::word_template_
private

Definition at line 308 of file api.h.

Referenced by chat_template(), and TinyLlamaSession().


The documentation for this class was generated from the following files: