llama_cpp-rs

repository·main·Indexed 42 days ago

https://github.com/edgenai/llama_cpp-rs

High-level Rust bindings to llama.cpp version 0.3.2 for running GGUF-based large language models on CPU or hardware-accelerated backends including CUDA, Vulkan, Metal, and hipBLAS. Features a two-tier abstraction using LlamaModel for shared weights and Session for lightweight inference contexts, as well as support for constrained sampling via LlamaGrammar and custom Sampler traits.

Tokens
8.9K
Snippets
29
Records
41
Agent score
51%

What's inside llama_cpp-rs

  1. Build llama_cpp-rs for performance

    main

    Because llama.cpp is computationally intensive, standard debug builds will perform poorly. It is highly recommended to use the --release flag when building or running your application to enable necessary optimizations.

    cargo build --release
  2. Configure hardware acceleration via Cargo features

    main

    You can enable different hardware backends for llama.cpp by enabling specific Cargo features. Note that enabling these features requires the corresponding SDKs to be installed on your system.

    Supported features:

    • cuda: Enables the CUDA backend (requires CUDA Toolkit).
    • vulkan: Enables the Vulkan backend (requires Vulkan SDK).
    • metal: Enables the Metal backend (macOS only).
    • hipblas: Enables the hipBLAS/ROCm backend (requires ROCm).
  3. Stream decoded strings with TokensToStrings

    main

    When consuming model output, using into_strings() returns a TokensToStrings iterator or stream. This is critical for text-based applications because it uses an internal TokenDecoder to buffer incomplete UTF-8 sequences. If a token ends in the middle of a multi-byte character (like an Emoji), TokensToStrings will wait until the next token provides the remaining bytes before yielding the completed String piece, ensuring valid UTF-8 output.

    // Returns a Stream of String pieces that are UTF-8 safe
    let mut string_stream = completion_handle.into_strings();
  4. How LlamaModel and LlamaSession work together

    main

    The library uses a two-tier abstraction for managing LLM resources:

    1. LlamaModel: Represents the loaded model weights (e.g., a .gguf file). This object is heavy (often several gigabytes) and is intended to be loaded once and shared across multiple sessions.
    2. LlamaSession: Represents the execution context for a specific interaction. Sessions are lightweight (typically dozens to hundreds of megabytes) and are created from a LlamaModel using model.create_session(SessionParams::default()).

    This separation allows you to run multiple independent conversations or tasks in parallel using the same underlying model weights in memory.

  5. How the llama.cpp backend is managed

    main

    The llama_cpp-rs library manages the underlying llama.cpp lifecycle using a reference-counting pattern via the BackendRef abstraction.

    Because llama.cpp requires specific initialization (llama_backend_init) and cleanup (llama_backend_free) sequences, the library ensures that the backend is initialized exactly once when the first reference is created and is only freed when the last active reference is dropped.

    Users do not interact with the Backend struct directly; instead, they use BackendRef to ensure the environment is ready for model execution. This prevents errors related to uninitialized backends or premature freeing of resources while models are still in use.

  6. Manage model state with LlamaSession

    main

    A LlamaSession represents an evaluation session for a LlamaModel. It maintains the model's context (tokens and KV cache) and can be used to advance the context or start token generation. You can create multiple independent sessions from a single model using LlamaModel::create_session.

    Key capabilities:

    • Advancing Context: Feed new tokens or raw bytes into the session to update the model's history.
    • Completion: Start generating new tokens using a sampler.
    • Context Manipulation: Truncate, remove specific ranges of tokens, or reset the context entirely.
    • Deep Copying: Create an independent copy of a session (including its state) using deep_copy.
    // Assuming a LlamaModel has been loaded
    let mut session = model.create_session(params)?;
    
    // Advance context with bytes
    session.advance_context("Hello, how are you?")?;
    
    // Start generating tokens
    let mut handle = session.start_completing()?;
  7. Quickstart: Load a model and generate text

    main

    To use llama_cpp-rs, you need to create a LlamaModel to hold the weights and a LlamaSession to manage the context and generation. A LlamaModel is large and shared, while a LlamaSession is relatively small and used for individual interactions.

    To generate text, you can use ctx.start_completing_with which returns an iterator of strings. Note that the completion process runs in a worker thread and will stop automatically when the completion handle is dropped.

    use llama_cpp::{LlamaModel, LlamaParams, SessionParams};
    use llama_cpp::standard_sampler::StandardSampler;
    use std::io::{self, Write};
    
    // 1. Load the model
    let model = LlamaModel::load_from_file("path_to_model.gguf", LlamaParams::default()).expect("Could not load model");
    
    // 2. Create a session
    let mut ctx = model.create_session(SessionParams::default()).expect("Failed to create session");
    
    // 3. Feed context
    ctx.advance_context("This is the story of a man named Stanley.").unwrap();
    
    // 4. Generate tokens using a sampler
    let max_tokens = 1024;
    let mut decoded_tokens = 0;
    let mut completions = ctx.start_completing_with(StandardSampler::default(), 1024).into_strings();
    
    for completion in completions {
        print!("{completion}");
        let _ = io::stdout().flush();
    
        decoded_tokens += 1;
        if decoded_tokens > max_tokens {
            break;
        }
    }
  8. Build requirements and performance tips

    main

    Dependencies

    • libclang must be installed.
    • A C/C++ toolchain (preferably clang) is required to build llama.cpp from source.

    Performance

    llama.cpp is computationally intensive. Standard debug builds (cargo build) will perform very poorly. It is highly recommended to always use the --release flag for any meaningful testing or production use.

    Logging

    Logs from the bundled GGML and llama.cpp binaries are re-routed through the tracing crate instead of stderr. Use tracing to debug issues.

  9. Run GGUF models with llama_cpp-rs

    main

    You can run GGUF-based large language models by loading a model file into a LlamaModel, creating a Session (context), and using start_completing_with to generate tokens.

    Key concepts:

    • LlamaModel: Holds the heavy weights shared across multiple sessions.
    • Session: A lightweight context (typically 10s to 100s of MBs) used for inference.
    • start_completing_with: Spawns a worker thread for token generation. Dropping the completion handle stops generation.
    // Create a model from anything that implements `AsRef<Path>`:
    let model = LlamaModel::load_from_file("path_to_model.gguf", LlamaParams::default()).expect("Could not load model");
    
    // A `LlamaModel` holds the weights shared across many _sessions_; while your model may be
    // several gigabytes large, a session is typically a few dozen to a hundred megabytes!
    let mut ctx = model.create_session(SessionParams::default()).expect("Failed to create session");
    
    // You can feed anything that implements `AsRef<[u8]>` into the model's context.
    ctx.advance_context("This is the story of a man named Stanley.").unwrap();
    
    // LLMs are typically used to predict the next word in a sequence. Let's generate some tokens!
    let max_tokens = 1024;
    let mut decoded_tokens = 0;
    
    // `ctx.start_completing_with` creates a worker thread that generates tokens. When the completion
    // handle is dropped, tokens stop generating!
    let mut completions = ctx.start_completing_with(StandardSampler::default(), 1024).into_strings();
    
    for completion in completions {
        print!("{completion}");
        let _ = io::stdout().flush();
        
        decoded_tokens += 1;
        
        if decoded_tokens > max_tokens {
            break;
        }
    }
  10. Troubleshoot llama.cpp internal errors

    main

    If an internal assertion fails within the underlying llama.cpp or ggml C libraries, the crate returns a LlamaInternalError.

    Because these errors occur across the FFI boundary, the specific error details are logged via the llama.cpp logging system. To see why a call failed, you must set up a tracing subscriber at the top of your application to capture the re-routed logs.

  11. Handle LlamaGrammar parsing errors

    main

    When creating a grammar, you may encounter GrammarParseError variants. Common issues include:

    • UnexpectedEndOfInput: The input ended prematurely during a specific parsing stage.
    • ExpectedEqualsAfterName: A rule name was provided but the ::= operator was missing.
    • MissingClosingBracketInNestedRule: A nested rule (...) was not closed.
    • ExpectedRuleBeforePostfixOperator: A postfix operator (*, +, or ?) was used without a preceding rule.
    • UnknownEscape: An invalid escape character was used (e.g., \z).
    • ExtraneousCharsAfterRule: Characters were found after a rule definition that were not whitespace or comments.

    Errors are wrapped in LlamaGrammarFromStrError when using LlamaGrammar::from_str.

  12. Use the `Batch` struct for model inference

    main

    The Batch struct is a safe wrapper around llama_batch used to prepare groups of tokens for model inference. It manages the memory for tokens, positions, sequence IDs, and logits required by the underlying llama.cpp engine.

    Key Methods

    • new(capacity: usize, embed: usize, max_sequences: usize) -> Self: Initializes a new batch.
      • capacity: The maximum number of tokens this batch can hold.
      • embed: The embedding dimension (used if the batch is for embedding tasks).
      • max_sequences: The maximum number of sequences that can be generated/processed in this batch.
      • Note: Panics if capacity or max_sequences is 0.
    • add(&mut self, token: Token, position: usize, sequence_ids: &[i32], logits: bool) -> usize: Adds a token to the batch.
      • token: The Token to add.
      • position: The position of the token in the sequence.
      • sequence_ids: A slice of sequence IDs for the token.
      • logits: A boolean indicating whether to compute logits for this token.
      • Returns: The index of the added token, or usize::MAX if the batch is at capacity or exceeds max_sequences limits.
    • clear(&mut self): Resets the number of tokens in the batch to 0, allowing the batch to be reused.
    • set_logits(&self, idx: usize, value: bool): Manually sets whether logits should be computed for a specific token index.
    • tokens(&self) -> usize: Returns the current number of tokens in the batch.
    • handle(&self) -> llama_batch: Returns the underlying raw llama_batch pointer for low-level FFI operations.
    // Example: Creating and adding tokens to a batch
    let mut batch = Batch::new(512, 128, 1);
    
    // Adding a token
    let token_idx = batch.add(my_token, 0, &[0], true);
    if token_idx == usize::MAX {
        // Handle capacity error
    }
    
    // Checking current token count
    let count = batch.tokens();