当前位置: 首页 > article >正文

llama.cpp的C语言API使用

我们知道,一般运行大语言模型都是在Python上运行的,可是Python的性能太差了,不适合用于生产环境,因此可以采用llama.cpp提供的API在C语言上运行大模型。

llama.cpp的下载

Windows下的下载

我们需要下载llama.cpp的两个部分,分别是它的源代码和windows预编译包。它的源代码直接在github上下载即可:

GitHub - ggerganov/llama.cpp: LLM inference in C/C++

它的预编译包在这里下载:
Releases · ggerganov/llama.cpp

Linux下的下载

linux下只需要下载源代码,然后编译即可:

make

如果想下载GPU加速的,则输入:

make GGML_CUDA=1

gguf文件的获取

从huggingface中下载

在llama.cpp上运行大语言模型需要一个gguf格式的文件,存储模型的相关信息。gguf文件可以从huggingface上直接获取,如:bartowski/Llama-3.2-1B-Instruct-GGUF · HF Mirror,然后选择一个合适的镜像即可。

从transformers中转换

当然,也可以从transformers模型中转换。在llama.cpp的源代码包下输入如下命令:

pip install -r requirements.txt
python convert_hf_to_gguf.py  <transfomers模型路径> --outtype f16 --outfile  <格式转换后的模型路径.gguf>

在其中,transformers模型路径是一个目录,目录里包括模型信息和分词器信息,–outtype指定的是量化信息,用于减小推理时的显存资源消耗,可以选择f32,f16,q8_0,q4_k_m等。–outfile是转换后的gguf路径,是一个.gguf格式的文件

API接口的使用

在使用API接口前,我们需要先创建一个文件夹,作为项目文件夹。然后把源代码包中的include/llama.hggml/src下的所有头文件全部复制到这个项目文件夹中,接着把预编译包中的所有dll文件复制进去(Linux下复制函数库),然后创建main.c,编写main函数。

在使用API完成推理的过程中,需要依次经历以下几步:

  1. 加载模型

  2. 创建上下文

  3. 获得词汇表

  4. 处理提示词

  5. 创建批次

  6. 设置采样器

  7. 循环进行解码和采样

  8. 释放资源

接下来对每一步用到的函数进行讲解:

加载模型

在加载模型时,需要先设定参数,在加载模型。通常获取默认参数即可。

获取默认参数的函数是llama_model_default_params,其原型如下:

struct llama_model_params llama_model_default_params(void);

它需要一个llama_model_params结构体来接它的返回值,有了这个返回值,就可以调用llama_model_load_from_file函数,用于加载模型,这个函数的原型如下:

struct llama_model * llama_model_load_from_file(
                             const char * path_model,
              struct llama_model_params   params);

它返回一个llama_model结构体的指针,就是从路径中获取到的模型的指针,path_model表示gguf文件的路径,params是加载模型时的参数。

创建上下文

创建上下文时同样需要参数,获取其默认参数的函数是llama_context_default_params,其原型如下:

struct llama_context_params llama_context_default_params(void);

需要一个llama_context_params的结构体来接它的返回值,有了这个返回值,就可以调用llama_init_from_model函数,用于创建上下文,这个函数的原型如下:

struct llama_context * llama_init_from_model(
                     struct llama_model * model,
            struct llama_context_params   params);

它的第一个参数就是加载后的模型。第二个参数就是刚创建的参数,返回一个llama_context结构体的指针,表示初始化的上下文。

获得词汇表

获得词汇表采用llama_model_get_vocab函数,其原型如下:

const struct llama_vocab *llama_model_get_vocab(const struct llama_model* model);

它接受一个模型,返回这个模型中的词汇表,存储到llama_vocab结构体中,并返回地址。

处理提示词

在将提示词传入模型前,需要对其进行标记化(tokenize,又叫序列化),将文字转换为一个数组,这样才可以让模型理解这段文字。

处理提示词的关键函数是llama_tokenize,它用于标记化一段文字,的原型如下:

int32_t llama_tokenize(
        const struct llama_vocab * vocab,
                      const char * text,
                         int32_t   text_len,
                     llama_token * tokens,
                         int32_t   n_tokens_max,
                            bool   add_special,
                            bool   parse_special);

vocab是词汇表,text是文字,text_len是文字长度,tokens指向的地址是标记化之后的存储位置,n_tokens_max是序列化地址的最大容纳长度,add_special是是否增加特殊标记,即段首标识和段末标识,parse_special表示是否解析特殊表示,包括段首标识、段末标识等。

如果成功,这个函数将返回标记化的数量,即tokens的有效长度;如果失败,这个函数将返回负数。

提示词不仅包括语言本身,还包括一些特殊标识,如llama的提示词样例如下:

<|begin_of_text|><|start_header_id|>system<|end_header_id|>

Cutting Knowledge Date: December 2023
Today Date: 26 Jul 2024

{system_prompt}<|eot_id|><|start_header_id|>user<|end_header_id|>

{prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>

在创建提示词的时候,需要注意包括这些特使标识。

创建批次

模型推理前,需要为推理创建一个批次。

创建批次采用llama_batch_get_one函数,这个函数的原型如下:

struct llama_batch llama_batch_get_one(
                  llama_token * tokens,
                      int32_t   n_tokens);

它会返回一个llama_batch结构体,表示创建的批次。参数tokens表示标记化后的数据,可以是一个数组,n_tokens是这个数组的有效长度。这两个参数都可以从llama_tokenize中获得。

设置采样器

采样器用于指定采样的方式,决定了以什么样的方式确定候选词。为了让采样方式多样化,同时进行多种采样,可以采取采样器链。如下代码就定义了一个采样器链:

struct llama_sampler_chain_params sparams = llama_sampler_chain_default_params();
struct llama_sampler *sampler = llama_sampler_chain_init(sparams);
llama_sampler_chain_add(sampler, llama_sampler_init_temp(0.8));
llama_sampler_chain_add(sampler, llama_sampler_init_top_k(50));
llama_sampler_chain_add(sampler, llama_sampler_init_top_p(0.9, 1));
long seed = time(NULL);
llama_sampler_chain_add(sampler, llama_sampler_init_dist(seed));

采样器链中,可以增加如下类型的采样器:

基础采样器

  • 贪婪采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_greedy(void);
    

    每次选择当前概率最高的词元作为输出,不考虑随机性。

  • 随机采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_dist(uint32_t seed);
    

    基于随机分布进行采样,seed 用于初始化随机数生成器。

概率调整采样器

  • Softmax 采样器

    DEPRECATED(LLAMA_API struct llama_sampler * llama_sampler_init_softmax(void));
    

    按照词元的 logits 对候选词元进行降序排序,并计算基于 logits 的概率。注意:不推荐在完整词汇表上使用,因为排序操作可能很慢,建议先进行 top-k 或 top-p 采样。

基于截断的采样器

  • Top-K 采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_top_k(int32_t k);
    

    选择概率最高的前 K 个词元进行采样,k 是截断的词元数量。

  • Nucleus 采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_top_p(float p, size_t min_keep);
    

    选择累积概率达到阈值 p 的最小词元集合进行采样,min_keep 是保留的最小词元数量。

  • Min-P 采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_min_p(float p, size_t min_keep);
    

    选择概率至少为 p 的词元进行采样,min_keep 是保留的最小词元数量。

  • 局部典型采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_typical(float p, size_t min_keep);
    

    选择与模型条件熵接近的词元进行采样,p 是截断阈值,min_keep 是保留的最小词元数量。

温度调整采样器

  • 温度采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_temp(float t);
    

    对 logits 进行缩放,公式为 li′​=li​/t。当 t <= 0.0f 时,保留最大 logit 的值,其余设置为负无穷。

  • 动态温度采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_temp_ext(float t, float delta, float exponent);
    

    动态调整温度,t 是基础温度,deltaexponent 是动态调整参数。

特殊采样器

  • XTC 采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_xtc(float p, float t, size_t min_keep, uint32_t seed);
    

    排除最可能的词元以增加创造性,p 是截断阈值,t 是温度,min_keep 是保留的最小词元数量,seed 是随机种子。

  • Mirostat 1.0 采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_mirostat(
        int32_t n_vocab, uint32_t seed, float tau, float eta, int32_t m, float mu);
    

    控制生成文本的交叉熵(surprise),n_vocab 是词汇表大小,tau 是目标交叉熵,eta 是学习率,m 是用于估计 s_hat 的词元数量,mu 是最大交叉熵。

  • Mirostat 2.0 采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_mirostat_v2(uint32_t seed, float tau, float eta, float mu);
    

    Mirostat 2.0 算法,参数与 Mirostat 1.0 类似,但实现更通用。

其他采样器

  • 语法采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_grammar(
        const struct llama_vocab * vocab, const char * grammar_str, const char * grammar_root);
    

    根据语法规则进行采样,vocab 是词汇表,grammar_str 是语法字符串,grammar_root 是语法根节点。

  • 惩罚采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_penalties(
        int32_t penalty_last_n, float penalty_repeat, float penalty_freq, float penalty_present);
    

    对重复词元进行惩罚,penalty_last_n 是考虑的最近 n 个词元,penalty_repeat 是重复惩罚,penalty_freq 是频率惩罚,penalty_present 是存在惩罚。

  • DRY 采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_dry(
        const struct llama_vocab * vocab, int32_t n_ctx_train, float dry_multiplier, float dry_base,
        int32_t dry_allowed_length, int32_t dry_penalty_last_n, const char ** seq_breakers, size_t num_breakers);
    

    用于减少重复和增强多样性的采样器,参数用于控制重复惩罚和序列中断。

  • Logit 偏置采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_logit_bias(
        int32_t n_vocab, int32_t n_logit_bias, const llama_logit_bias * logit_bias);
    

    对特定词元的 logits 进行偏置调整,n_vocab 是词汇表大小,n_logit_bias 是偏置词元数量,logit_bias 是偏置数组。

  • 填空采样器

    LLAMA_API struct llama_sampler * llama_sampler_init_infill(const struct llama_vocab * vocab);
    

    用于填空任务的采样器,主要用于在文本中间填充内容。

在采样器链的最后,必须是贪婪采样器、随机采样器和Mirostat采样器中的任意一种。

循环进行解码采样

在这里面,需要用到llama_decode函数进行解码,llama_sampler_sample函数进行采样,llama_detokenize函数进行反标记化(即将模型的输出转换为自然语言),最后需要将批次更新,增加刚输出的标识。

llama_decode的原型如下:

int32_t llama_decode(
            struct llama_context * ctx,
              struct llama_batch   batch);

它接受上下文和批次作为参数,返回值如果为0则成功,非0则失败。在成功解码后,就可以调用llama_sampler_sample函数进行采样,其原型如下:

llama_token llama_sampler_sample(struct llama_sampler * smpl, struct llama_context * ctx, int32_t idx);

它将会进行采样。它会对第idx个元素进行采样,如果idx为-1,则会采样最后一个。smpl是定义的采样器或采样器链,ctx是上下文。

llama_detokenize是反序列化函数,它的原型如下:

int32_t llama_detokenize(
        const struct llama_vocab * vocab,
               const llama_token * tokens,
                         int32_t   n_tokens,
                            char * text,
                         int32_t   text_len_max,
                            bool   remove_special,
                            bool   unparse_special);

llama_tokenize相反,它将tokens内的序列化数据转换为text内的文本数据,返回的是反序列化的长度。如果出错,返回负数。

接下来需要更新批次数据,这里面的更新指清除批次数据,并写入当前采样的数据:

batch.token[0] = next_token;
batch.n_tokens = 1;

释放资源

上面申请的模型、上下文、采样器、批次等都需要释放,代码如下:

llama_sampler_free(sampler);
llama_batch_free(batch);
llama_free(context);
llama_model_free(model);

完整代码

从[bartowski/Llama-3.2-1B-Instruct-GGUF · HF Mirror](bartowski/Llama-3.2-1B-Instruct-GGUF · HF Mirror)下载一个GGUF模型,进行测试。

完整代码如下:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include "llama.h"

#define MAX_TOKEN 10000

int main(void){
    // 创建模型
    struct llama_model_params model_params = llama_model_default_params();
    struct llama_model *model = llama_model_load_from_file("./Llama-3.2-1B-Instruct-f16.gguf", model_params);
	printf("create model down\n");
    
    // 创建上下文
    struct llama_context_params context_params = llama_context_default_params();
    struct llama_context *context = llama_init_from_model(model, context_params);
	printf("create context down\n");
    
    // 获得词汇表
    const struct llama_vocab *vocab = llama_model_get_vocab(model);
	printf("create vocab down\n");
    
    // 定义提示词
    char *prompt = 
	"<|begin_of_text|><|start_header_id|>user<|end_header_id|>Who are you?<|eot_id|><|start_header_id|>assistant<|end_header_id|>";
    
    // 对提示词进行标记化(tokenize)
    llama_token *tokens = (llama_token *)malloc(sizeof(llama_token) * MAX_TOKEN);
    int len = llama_tokenize(vocab, prompt, strlen(prompt), tokens, MAX_TOKEN, false, true);
	if (len < 0){
		fprintf(stderr, "Error:tokenize error\n");
		return -1;
	}
    printf("tokenize prompt down\n");
	
    // 创建批次
	struct llama_batch batch = llama_batch_get_one(tokens, len);
	printf("create batch down\n");
    
    // 初始化采样器链
    struct llama_sampler_chain_params sparams = llama_sampler_chain_default_params();
    struct llama_sampler *sampler = llama_sampler_chain_init(sparams);
	llama_sampler_chain_add(sampler, llama_sampler_init_temp(0.8));
    llama_sampler_chain_add(sampler, llama_sampler_init_top_k(50));
    llama_sampler_chain_add(sampler, llama_sampler_init_top_p(0.9, 1));
    long seed = time(NULL);
    llama_sampler_chain_add(sampler, llama_sampler_init_dist(seed));
	printf("create sampler chain down\n");
	
	// 循环
    llama_token next_token = LLAMA_TOKEN_NULL;
    llama_token eos = llama_vocab_eos(vocab);
    while (next_token != eos) {
		// 解码
		if(llama_decode(context, batch)){
			fprintf(stderr, "Error: decode error\n");
			return -1;
		}
		
        // 采样
        next_token = llama_sampler_sample(sampler, context, -1);
        
        // 反标记化
        char deprompt[100] = {0};
        if(llama_detokenize(vocab, &next_token, 1, deprompt, sizeof(deprompt) / sizeof(deprompt[0]), false, false) < 0){
			fprintf(stderr, "Error: detokenize error\n");
            return -1;
        }
        printf("%s", deprompt);
        
        // 更新 batch 以包含新生成的 token
        batch.token[0] = next_token;
		batch.n_tokens = 1;
    }
    
    // 释放资源
    llama_sampler_free(sampler);
	llama_batch_free(batch);
    llama_free(context);
    llama_model_free(model);
	free(tokens);
    
    return 0;
}

输出结果:

I'm an artificial intelligence model known as Llama. Llama stands for "Large Language Model Meta AI."

可以看到,模型没有问题。


http://www.kler.cn/a/532419.html

相关文章:

  • 毕业设计:基于深度学习的高压线周边障碍物自动识别与监测系统
  • 【大数据技术】教程05:本机DataGrip远程连接虚拟机MySQL/Hive
  • C++11中的bind
  • python:求解爱因斯坦场方程
  • Vue指令v-html
  • 手写单例模式
  • Linux环境下的Java项目部署技巧:安装 Nginx
  • 复现论文“去模糊算法”
  • Python分享10个Excel自动化脚本
  • ubuntu ip设置
  • 电路研究9.2.2.1——合宙Air780EP中分组域相关命令分析
  • 仿真设计|基于51单片机的分贝检测与远程传输系统仿真
  • 回溯法-排列,组合
  • llama.cpp GGML Quantization Type
  • 5.角色基础移动
  • 云夹:重新定义你的书签管理体验
  • [mmdetection]fast-rcnn模型训练自己的数据集的详细教程
  • concurrentHasMap为什么不允许kv为null
  • 使用线性回归模型逼近目标模型 | PyTorch 深度学习实战
  • Go方法接收者中值类型接收者和指针类型接收者的对比
  • STM32F103ZET6完整技术点(持续更新~)
  • 信息安全专业2025最新毕业设计选题汇总:课题精选
  • gltf工具
  • 游戏引擎学习第86天
  • 体系自适应的物联网漏洞挖掘系统研究背景及意义:物联网漏洞挖掘概述之物联网漏洞挖掘技术前景
  • 前端开发中的“原生模块化”——深入解析ES模块(ESM)的使用与优化