llama.cpp

Published: 4/8/2025

由于毕设以及后续学习可能都会深入使用llama.cpp,所以决定记录,以下内容以d2fe216提交为基础。

概述

llama.cpp是一个C/C++实现的开源LLM推理框架,较vLLM相比,更加轻量级,适合用户在本地进行推理,提供多种模型压缩手段以及硬件加速。现在已经在x86,arm等平台支持多种模型架构推理。其代码组成可分为负责通用处理模型加载与计算装填的前端(llama库)与负责具体计算的硬件实现后端(ggml库):

  1. llama库: 代码位于./src/中,具体包含对kv cache,量化,模型加载等的实现。
  2. ggml库: 代码位于./src/ggml/中,目前已支持多种类型的硬件计算后端,包括Cuda(ggml-cuda),Rocm(ggml-rocm),OpenCL(ggml-opencl),Vulkan(ggml-vulkan)等。

以下将以数据+操作的方式来具体阅读llama.cpp中的内容。

llama

Important Structs

llama_context

struct llama_context(llama-context.h)存储了模型推理时的上下文信息,包括一些profiling信息,所使用后端设备kv_cache,模型参数以及输入tensor等。

  • const struct llama_model& model: 所加载模型,存放模型参数,超参等内容

  • struct llama_kv_cache kv_self: 模型所使用的kv cache

  • vec<ggml_backend_ptr> backends: 存放模型所使用的后端设备指针,llama.cpp支持将不同层分配给指定的后端设备进行计算(大多数情况是CPU与GPU)。

  • // input tensors: 所有支持计算算子的输入张量,通过预先分配来降低重复分配延迟。

  • ggml_backend_buffer_ptr buf_output: 存放模型输出(logits与embds)

llama.cpp同样支持token batching,但由于我目前所设计的端侧设备通常不具有batch的算力与资源,所以暂时省略阈值相关的内容。

这个结构体是llama的支柱,几乎所有与模型推理相关的主干函数都需要使用该结构体。 在example/main/main.cpp(以往的main以及现在的llama-cli实现)使用common_init_from_params函数来加载模型并初始化llama_context

// main()
    common_init_result llama_init = common_init_from_params(params);
    model = llama_init.model.get();
    ctx = llama_init.context.get();

llama_kv_cache相关

llama中与kv cache相关的结构体主要有: struct llama_kv_ceilstruct llama_kv_cache(llama-kv-cache.h)

  • struct llama_kv_ceil(llama-kv-cache.h):用于记录kv cache在预分配的backend buffer slot中的位置信息

    • llama_pos pos,delta: pos用于记录该ceil对应token在序列中的绝对位置,delta用于实现滑动窗口等sparse attention,表示该token在当前ceil中的相对位置(这目前我还不太确定)
  • struct llama_kv_cache(llama-kv-cache.h)用于存储模型推理时的kv cache,计算上下文以及实际存储buffer,主要成员如下:

    • vector<llama_kv_ceil> : 用于存放每层kv cache slot的位置信息
    • vector<struct ggml_tensor*> k_l,每层的k cache tensor
    • vector<struct ggml_tensor*> v_l,每层的v cache tensor
    • vector<ggml_backend_buffer_ptr> : 用于存放kv cache的buffer(数组多缓冲,用于支持多种设备)
    • vector<ggml_context_ptr>: 存放kv cache计算的上下文(数组支持多类设备计算)

这里同样涉及到batch 相关内容,seqid用于区别batch中的对应序列,batch顾名思义就是将多个序列进行padding后同时处理同一位置token的计算,多用于server。与batch相关的还有sbatch(提交给llama的原始batch)与sbatch(llama处理后的统一batch)

llama_model

llama_vocab

llama_threadpool与llama_threadpool_params

ggml

Important Structs

ggml_tensor

struct ggml_tensor(ggml.h)是ggml库中最重要的结构体之一,用于表示计算时使用的张量。 其中重要的成员如下:

  • ggml_type type: 表示张量实际数据类型,从Q4_0到FP16等

  • ggml_backend_buffer* buffer: 指向存储张量的缓冲区结构体,该结构体同样很重要,毕竟张量存储在对应计算设备的存储资源中,ggml_tensor算是对其实际存储的引用,这对于实现零拷贝计算十分重要。

  • struct ggml_tensor *src[GGML_MAX_SRC]: 指向张量的组成张量,或者是用于生成该张量的源操作数(与下面的op结合利用对应的计算函数来计算得到结果)

  • ggml_op op: 表示计算该张量的操作,如果需要计算,则根据上面的src成员来计算得到结果存储入该结构体对应的内存中。

  • ne[GGML_MAX_DIMS]/nb[GGML_MAX_DIMS]: ne用于存储该tensor的维度(事实上好像目前最多支持4维),nb表示跨越对应维度的步长(也就是对应维度的字节长度)

  • void* data/extra;: data用于指向存储tensor数据的实际地址(结合上上面的ggml_backend_buffer);extra(后端私有数据)则通常用于存储对应设备使用tensor时需要使用的一些元数据等等。

ggml_context

ggml_cgraph

struct ggml_cgraph(ggml-impl.h)用于表示ggml库中的计算图,存放所有的计算操作,张量与梯度。

ggml_backend_context

ggml_backend_dev

ggml_backend_buffer

ggml_op与ggml_unary_op

ggml_op(ggml.h)与ggml_unary_op(ggml.h)枚举是ggml库中为ggml_tensor定义的操作类型,前者表示二元操作,后者表示一元操作。比如GGML_OP_ADD,GGML_UNARY_OP_ABS等。其利用函数多为ggml_compute_forward(根据该枚举来执行对应计算函数)与ggml_compute_forward_xxx,其中XXX表示对应的操作类型。比如ggml_compute_forward_add表示加法操作。

ggml-opencl

Log system

llama.cpp同样拥有自己实现的一套log system, 主要内容如下:

ggml internal

// ggml.h
enum ggml_log_level {
    GGML_LOG_LEVEL_NONE  = 0,
    GGML_LOG_LEVEL_DEBUG = 1,
    GGML_LOG_LEVEL_INFO  = 2,
    GGML_LOG_LEVEL_WARN  = 3,
    GGML_LOG_LEVEL_ERROR = 4,
    GGML_LOG_LEVEL_CONT  = 5, // continue previous log
};

// ggml-impl.cpp
void llama_log_internal(ggml_log_level level, const char * format, ...) {
    va_list args;
    va_start(args, format);
    llama_log_internal_v(level, format, args);
    va_end(args);
}
// ggml-impl.h
#define LLAMA_LOG(...)       llama_log_internal(GGML_LOG_LEVEL_NONE , __VA_ARGS__)
#define LLAMA_LOG_INFO(...)  llama_log_internal(GGML_LOG_LEVEL_INFO , __VA_ARGS__)
#define LLAMA_LOG_WARN(...)  llama_log_internal(GGML_LOG_LEVEL_WARN , __VA_ARGS__)
#define LLAMA_LOG_ERROR(...) llama_log_internal(GGML_LOG_LEVEL_ERROR, __VA_ARGS__)
#define LLAMA_LOG_DEBUG(...) llama_log_internal(GGML_LOG_LEVEL_DEBUG, __VA_ARGS__)
#define LLAMA_LOG_CONT(...)  llama_log_internal(GGML_LOG_LEVEL_CONT , __VA_ARGS__)

Usage