【项目分析】llama.cpp工程

打印 上一主题 下一主题

主题 1765|帖子 1765|积分 5295

概述

Llama.cpp是一个基于C++编写的高性能大模子推理框架,旨在提供快速、稳定且易于利用的计算工具,原本的目标是答应在MacBook上利用INT4量化的LLaMA模子,但如今Llama.cpp支持多种计算模式,包罗向量计算、矩阵运算、图算法等,可广泛应用于呆板学习、图像处理惩罚、数据分析等范畴。
目录结构

src是构建模子架构的基础库文件夹
examples是部分案例模子的源文件
ggml是计算操纵的库文件

源码分析

llama.cpp运行机制分析
可如下参考链接(注意:函数名有所变动):
CodeLeaner@微信公众号:llama.cpp源码分析
以下代码基于tag: b4033分支分析。
llama.cpp运行入口函数

  1. // llama  @examples/main/main.cpp
  2. main()  
  3.         gpt_params_parse(argc, argv, params, LLAMA_EXAMPLE_MAIN, print_usage)        // 解析传递进来的模型参数
  4.         llama_init_from_gpt_params()
  5.                 llama_load_model_from_file(params.model.c_str(), mparams);                // 加载model参数
  6.                 llama_new_context_with_model(model, cparams);                        //
  7.                         ggml_backend_cpu_init();
  8.                                 *cpu_backend                                // 定义指针指向 cpu_backend_i
  9.                 llama_tokenize(ctx, prompt, true, true)                                // 将prompt tokenize
  10.         while   // 循环产生token
  11.                            llama_decode(ctx, llama_batch_get_one(&embd[i], n_eval, n_past, 0))        // 生成token函数
  12.                            llama_token_to_piece(ctx, id, params.special)
  13.         gpt_perf_print(ctx, smpl);                                                // 打印性能结果
复制代码
模子计算图构建函数

  1. // decode 函数体 @src/llama.cpp
  2. int32_t llama_decode(        struct llama_context * ctx,          struct llama_batch   batch)
  3.         llama_decode_internal(*ctx, batch)
  4.                 while (lctx.sbatch.n_tokens > 0)
  5.                         llama_build_graph(lctx, ubatch, false);   // 构建计算图,包括self-attention、ffn等,计算图将计算方式赋值,但不输入数据计算,在构建计算图时定义计算类型
  6.                         llama_graph_compute(lctx, gf, n_threads, threadpool);    // 创建线程,并基于前期构建的计算图调用对应计算类型的函数进行计算
  7.                                 ggml_backend_cpu_set_n_threads(lctx.backend_cpu, n_threads);
  8.                                 ggml_backend_cpu_set_threadpool(lctx.backend_cpu, threadpool);
  9.                                 ggml_backend_cpu_set_abort_callback(lctx.backend_cpu, lctx.abort_callback, lctx.abort_callback_data);       
  10.                                 ggml_backend_sched_graph_compute_async(lctx.sched, gf);
  11. // 计算图构建函数
  12. static struct ggml_cgraph * llama_build_graph(  llama_context & lctx,  const llama_ubatch & batch,                  bool   worst_case)
  13.         llm.init();
  14.         result = llm.build_llama();
  15.                 inpL = llm_build_inp_embd(ctx0, lctx, hparams, batch, model.tok_embd, cb);
  16.                 for (int il = 0; il < n_layer; ++il) {
  17.                 cur = llm_build_norm(ctx0, inpL, hparams, model.layers[il].attn_norm, NULL, LLM_NORM_RMS, cb, il);
  18.                  // self-attention
  19.                  struct ggml_tensor * Qcur = llm_build_lora_mm(lctx, ctx0, model.layers[il].wq, cur);
  20.                  struct ggml_tensor * Kcur = llm_build_lora_mm(lctx, ctx0, model.layers[il].wk, cur);
  21.                  struct ggml_tensor * Vcur = llm_build_lora_mm(lctx, ctx0, model.layers[il].wv, cur);
  22.                  cur = llm_build_kv(ctx0, lctx, kv_self, gf,  model.layers[il].wo, model.layers[il].bo,  Kcur, Vcur, Qcur, KQ_mask, n_tokens, kv_head, n_kv, 1.0f/sqrtf(float(n_embd_head)), cb, il);
  23.                          llm_build_kv_store(ctx, hparams, cparams, kv, graph, k_cur, v_cur, n_tokens, kv_head, cb, il);
  24.                          cur  = llm_build_kqv(ctx, lctx, kv, graph, wo, wo_b, q_cur, kq_mask, n_tokens, n_kv, kq_scale, cb, il);
  25.                                  struct ggml_tensor * kq = ggml_mul_mat(ctx, k, q);
  26.                                  kq = ggml_soft_max_ext(ctx, kq, kq_mask, kq_scale, hparams.f_max_alibi_bias);
  27.                                  struct ggml_tensor * kqv = ggml_mul_mat(ctx, v, kq);
  28.                  // feed-forward network
  29.                  cur = llm_build_norm(ctx0, ffn_inp, hparams, model.layers[il].ffn_norm, NULL, LLM_NORM_RMS, cb, il);
  30.                  cur = llm_build_ffn(ctx0, lctx, cur, model.layers[il].ffn_up,   model.layers[il].ffn_up_b,   NULL, model.layers[il].ffn_gate, model.layers[il].ffn_gate_b, NULL, model.layers[il].ffn_down, model.layers[il].ffn_down_b, NULL, NULL, LLM_FFN_SILU, LLM_FFN_PAR, cb, il);
  31.                 }
  32.                 cur = llm_build_norm(ctx0, cur, hparams, model.output_norm, NULL, LLM_NORM_RMS, cb, -1);
  33.                 cur = llm_build_lora_mm(lctx, ctx0, model.output, cur);
  34.                 ggml_build_forward_expand(gf, cur);
复制代码
ggml计算库函数

  1. // backend计算图执行函数   ggml/src/ggml-backend.c
  2. enum ggml_status ggml_backend_sched_graph_compute_async(ggml_backend_sched_t sched, struct ggml_cgraph * graph)
  3.         ggml_backend_sched_alloc_graph(sched, graph)
  4.                 ggml_backend_sched_split_graph(sched, graph);                        // 该函数划分graph
  5.         ggml_backend_sched_compute_split(sched);
  6.                 ggml_backend_graph_compute_async(split_backedn, &split->graph)
  7.                         backend->iface.graph_compute(backend,cgraph);  //根据iface结构体中的信息,调用对应平台的ggml_graph_compute计算
  8.                                 ggml_backend_cpu_graph_compute()                                        // 例如cpu平台,则调用该函数
  9.                                         ggml_graph_compute(cgraph, &cplan);
  10.                                
  11. // ggml计算图中各类计算选通的主体函数  ggml/src/ggml.c
  12. enum ggml_status ggml_graph_compute(struct ggml_cgraph * cgraph, struct ggml_cplan * cplan)
  13.         ggml_graph_compute_thread(&threadpool->workers[0])
  14.                 ggml_compute_forward(&params, node);
  15.                         ggml_compute_forward_dup(params, tensor);
  16.                         ggml_compute_forward_add1(params, tensor);
  17.                         ggml_compute_forward_repeat(params, tensor);
  18.                         ggml_compute_forward_mul_mat(params, tensor);   // 函数计算时根据ggml_type选择对应精度的vec_dot函数执行
  19.                         ggml_compute_forward_soft_max(params, tensor);
  20.                         ggml_compute_forward_rms_norm(params, tensor);
复制代码
量化操纵函数

模子的计算图构建好后调用ggml_compute_forward函数举行计算,计算时会根据源操纵数的数据范例ggml_type调用对应的运算函数,如下函数ggml_compute_forward_mul_mat_one_chunk所示,假如type是GGML_TYPE_Q8_0,则运算函数vec_dot则是ggml_vec_dot_q8_0_q8_0。
  1. static const ggml_type_traits_t type_traits[GGML_TYPE_COUNT] = {
  2.     [GGML_TYPE_Q8_0] = {
  3.         .type_name                = "q8_0",
  4.         .blck_size                = QK8_0,
  5.         .type_size                = sizeof(block_q8_0),
  6.         .is_quantized             = true,
  7.         .to_float                 = (ggml_to_float_t) dequantize_row_q8_0,
  8.         .from_float               = quantize_row_q8_0,
  9.         .from_float_ref           = (ggml_from_float_t) quantize_row_q8_0_ref,
  10.         .from_float_to_mat        = quantize_mat_q8_0,
  11.         .vec_dot                  = ggml_vec_dot_q8_0_q8_0,
  12.         .vec_dot_type             = GGML_TYPE_Q8_0,
  13.     ......
  14. }
  15. static void ggml_compute_forward_mul_mat_one_chunk(...)
  16.         const enum ggml_type type = src0->type;                        // 源操作数据类型
  17.         ggml_vec_dot_t const vec_dot      = type_traits[type].vec_dot;  // 调用对应的运算函数
  18.         for (int64_t iir1 = ir1_start; iir1 < ir1_end; iir1 += blck_1) {
  19.                     for (int64_t iir0 = ir0_start; iir0 < ir0_end; iir0 += blck_0) {
  20.                         for (int64_t ir1 = iir1; ir1 < iir1 + blck_1 && ir1 < ir1_end; ir1 += num_rows_per_vec_dot) {
  21.                                                 ...
  22.                                                 for (int64_t ir0 = iir0; ir0 < iir0 + blck_0 && ir0 < ir0_end; ir0 += num_rows_per_vec_dot) {
  23.                                vec_dot(ne00, &tmp[ir0 - iir0], (num_rows_per_vec_dot > 1 ? 16 : 0), src0_row + ir0 * nb01, (num_rows_per_vec_dot > 1 ? nb01 : 0), src1_col, (num_rows_per_vec_dot > 1 ? src1_col_stride : 0), num_rows_per_vec_dot);
  24.                            }
  25.                            ...
  26.                                         }
  27.                         }
  28.         }
  29. void ggml_vec_dot_q8_0_q8_0(int n, float * restrict s, size_t bs, const void * restrict vx, size_t bx, const void * restrict vy, size_t by, int nrc)
  30.     size_t vl = __riscv_vsetvl_e8m1(qk);
  31.     for (; ib < nb; ++ib) {
  32.     // load elements
  33.     vint8m1_t bx_0 = __riscv_vle8_v_i8m1(x[ib].qs, vl);
  34.     vint8m1_t by_0 = __riscv_vle8_v_i8m1(y[ib].qs, vl);
  35.     vint16m2_t vw_mul = __riscv_vwmul_vv_i16m2(bx_0, by_0, vl);
  36.     vint32m1_t v_zero = __riscv_vmv_v_x_i32m1(0, vl);
  37.     vint32m1_t v_sum = __riscv_vwredsum_vs_i16m2_i32m1(vw_mul, v_zero, vl);
  38.     int sumi = __riscv_vmv_x_s_i32m1_i32(v_sum);
  39.     sumf += sumi*(GGML_FP16_TO_FP32(x[ib].d)*GGML_FP16_TO_FP32(y[ib].d));
  40.     }
复制代码
数据结构

  1. // cpu执行数据流结构体,将函数作为结构体成员。
  2. cpu_backend_i        // @ggml/src/ggml-backend.c   
  3.         ggml_backend_cpu_graph_plan_create()
  4.                 ggml_graph_plan()
  5.         ggml_backend_cpu_graph_plan_compute()
  6.                 ggml_graph_compute()
  7.         ggml_backend_cpu_graph_compute()
  8.                 ggml_graph_plan()
  9.                 ggml_graph_compute()
复制代码
  1. // ggml tensor
  2. struct ggml_tensor {
  3.         enum ggml_type         type;   // 通过type选择计算的数据精度
  4.         enum ggml_op                                 op;                // 通过op选择计算函数
  5.         ...
  6.         }
  7. enum ggml_type {
  8.         GGML_TYPE_F32     = 0,
  9.         GGML_TYPE_F16     = 1,
  10.         GGML_TYPE_Q4_0    = 2,
  11.         GGML_TYPE_Q4_1    = 3,
  12.         // GGML_TYPE_Q4_2 = 4, support has been removed
  13.         // GGML_TYPE_Q4_3 = 5, support has been removed
  14.         GGML_TYPE_Q5_0    = 6,
  15.         GGML_TYPE_Q5_1    = 7,
  16.         GGML_TYPE_Q8_0    = 8,
  17.         GGML_TYPE_Q8_1    = 9,
  18.         GGML_TYPE_Q2_K    = 10,
  19.         GGML_TYPE_Q3_K    = 11,
  20.         ...
  21. }
  22. enum ggml_op {
  23.         GGML_OP_NONE = 0,
  24.         GGML_OP_DUP,
  25.         GGML_OP_ADD,
  26.         GGML_OP_ADD1,
  27.         GGML_OP_ACC,
  28.         GGML_OP_SUB,
  29.         GGML_OP_MUL,
  30.         GGML_OP_DIV,
  31.         GGML_OP_SQR,
  32.         GGML_OP_SQRT,
  33.         GGML_OP_LOG,
  34.         GGML_OP_SIN,
  35.         ...
  36. }
复制代码
rvv移植代码分析

Github相关链接:
Llama.cpp中利用GGML中对RVV的支持1
Llama.cpp中利用GGML中对RVV的支持2
Tameem-10xE@llama.cpp Github:Added RISC-V Vector Intrinsics Support
起初移植代码在ggml.c中,后续迁移至ggml-quants.c文件中。
修改函数包罗12个:
量化转换
quantize_row_q8_0
quantize_row_q8_1
向量点乘:
ggml_vec_dot_q4_0_q8_0
ggml_vec_dot_q4_1_q8_1
ggml_vec_dot_q5_0_q8_0
ggml_vec_dot_q5_1_q8_1
ggml_vec_dot_q8_0_q8_0
ggml_vec_dot_q2_K_q8_K
ggml_vec_dot_q3_K_q8_K
ggml_vec_dot_q4_K_q8_K
ggml_vec_dot_q5_K_q8_K
ggml_vec_dot_q6_K_q8_K
中科院软件所PLCT实验室做了gemv和gemm的量化工作。
矩阵向量乘
gemv
ggml_gemv_q4_0_8x8_q8_0
矩阵矩阵乘
gemm
ggml_gemm_q4_0_8x8_q8_0
这些函数作为差别量化模式的成员函数,在ggml差别算子计算函数中被调用。
llama.cpp的运行机制

量化

llama.cpp的训练后量化利用convert-hf-to-gguf.py脚本对模子举行格式转换,以及数据量化;也可以利用llama-quantize命令。
量化操纵

利用llama-quantize
  1. ./llama-quantize ggml-model-f16.gguf ggml-model-q4_0.gguf   Q4_0
复制代码
量化选项Qn_0、Qn_1等含义



  • Q后面的第一个数字n表示了量化到 n bit
  • 下划线后为数字:表示简单量化方法,为0时表示对称量化,没有零点;为1时表示非对称量化,每个scale尚有一个zero point;
  • 下划线后为K:表示K-quant量化方法,K后面的字母表示量化模子的参数规模:Small, Meduim,Large

sgsprog@hackmd.io: Linux 核心專題: llama.cpp 效能分析
简单量化

在llama.cpp中,32 个矩阵参数为一个 block,每个 block 内完成一次量化操纵。也就是常说的 Block-wise Quantization,一个 block 中同一个放缩平静移参数,但差别 block 之间的参数则完全差别并不共享1。
在实现中对于一个 tensor (llama7B中常见的 4096x4096)的量化过程中,最外一层循环会将 tensor 分为 chunk 层(每个 chunk 有 4096 x 4 = 16384 个数值,一共 1024 个 chunk),这层循环一样寻常是可以多线程并行处理惩罚的。单个chunk 中的数据会进一步被分为 64 个 32 数值块的 block,接下来我们只分析一个 block 内的量化操纵。
后缀 _0 的方法(quantize_row_q8_0_reference)步骤为:


  • 1)分块,这里 llama.cpp 做好了较好的并行处理惩罚机制;分为 chunk 和 block,chunk 的尺寸一样寻常为 row size 的 4 倍,这里 chunk 紧张是为了并行的,chunk 之间并行,chunk 内串行执行。
  • 2)每个 block 内求绝对值 max 。
  • 3)每个数值按 0 到 max 的范围内切开成 127 份,也就是 -max 到 max 切开为 254 份。隔断即量化放缩系数,格式为 FP16,而且生存在新的参数文件中。
  • 4)将每个参数 fp 值通过直接的四舍五入 round 函数映射到 int 值上,并存储。
K-quant量化

K-quant量化利用了 16 x 8的“块”举行量化,每个“块”共有16个行。每 8 个权重为一组利用同一个量化参数scale,因此有 16 个一级量化参数。此外,为了进一步的降低资源消耗,尚有 1 个 fp16 的二级量化参数K_2,用于量化16个一级量化参数,相当于“量化参数的量化”,这可以进一步减小模子size和显存消耗2。
批处理惩罚基准

评估原理

模子推理速度评估

指标英文释义中文释义PPprompt tokens per batchTGgenerated tokens per batchBnumber of batchesN_KVrequired KV cache sizeT_PPprompt processing time (i.e. time to first token)首次token处理惩罚的时间S_PPprompt processing speed ( (B*PP)/T_PP Or PP/T_PP )prompt处理惩罚速度T_TGtime to generate all batchesS_TGtext generation speed((B*TG)/T_TG )token生成速度Ttotal timeStotal speed (i.e. all tokens /total time) 模子狐疑度评估

狐疑度一样寻常用于生成式语言模子的指标。它衡量模子推测数据样本的本领。狐疑度得分越低表示语言模子推测下个词的本领越强,而得分越高则表示模子对下个词的推测越不确定或越“狐疑”。
武辰@知乎:深入明确语言模子的狐疑度perplexity
参考文献


   

  • 刀刀宁@知乎:笔记:Llama.cpp 代码浅析(四):量化那些事 ↩︎
  • meton@知乎:CNN量化 vs. LLM量化 ↩︎

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

汕尾海湾

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表