llama.cpp
Published: 4/8/2025
由于毕设以及后续学习可能都会深入使用llama.cpp,所以决定记录,以下内容以d2fe216提交为基础。
概述
llama.cpp是一个C/C++实现的开源LLM推理框架,较vLLM相比,更加轻量级,适合用户在本地进行推理,提供多种模型压缩手段以及硬件加速。现在已经在x86,arm等平台支持多种模型架构推理。其代码组成可分为负责通用处理模型加载与计算装填的前端(llama库)与负责具体计算的硬件实现后端(ggml库):
- llama库: 代码位于
./src/
中,具体包含对kv cache,量化,模型加载等的实现。 - 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_ceil
与
struct 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__)