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,其原型如下:

c 复制代码
struct llama_model_params llama_model_default_params(void);

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

c 复制代码
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,其原型如下:

c 复制代码
struct llama_context_params llama_context_default_params(void);

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

c 复制代码
struct llama_context * llama_init_from_model(
                     struct llama_model * model,
            struct llama_context_params   params);

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

获得词汇表

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

c 复制代码
const struct llama_vocab *llama_model_get_vocab(const struct llama_model* model);

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

处理提示词

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

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

c 复制代码
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函数,这个函数的原型如下:

c 复制代码
struct llama_batch llama_batch_get_one(
                  llama_token * tokens,
                      int32_t   n_tokens);

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

设置采样器

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

c 复制代码
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));

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

基础采样器

  • 贪婪采样器

    c 复制代码
    LLAMA_API struct llama_sampler * llama_sampler_init_greedy(void);

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

  • 随机采样器

    c 复制代码
    LLAMA_API struct llama_sampler * llama_sampler_init_dist(uint32_t seed);

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

概率调整采样器

  • Softmax 采样器

    c 复制代码
    DEPRECATED(LLAMA_API struct llama_sampler * llama_sampler_init_softmax(void));

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

基于截断的采样器

  • Top-K 采样器

    c 复制代码
    LLAMA_API struct llama_sampler * llama_sampler_init_top_k(int32_t k);

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

  • Nucleus 采样器

    c 复制代码
    LLAMA_API struct llama_sampler * llama_sampler_init_top_p(float p, size_t min_keep);

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

  • Min-P 采样器

    c 复制代码
    LLAMA_API struct llama_sampler * llama_sampler_init_min_p(float p, size_t min_keep);

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

  • 局部典型采样器

    c 复制代码
    LLAMA_API struct llama_sampler * llama_sampler_init_typical(float p, size_t min_keep);

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

温度调整采样器

  • 温度采样器

    c 复制代码
    LLAMA_API struct llama_sampler * llama_sampler_init_temp(float t);

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

  • 动态温度采样器

    c 复制代码
    LLAMA_API struct llama_sampler * llama_sampler_init_temp_ext(float t, float delta, float exponent);

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

特殊采样器

  • XTC 采样器

    c 复制代码
    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 采样器

    c 复制代码
    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 采样器

    c 复制代码
    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 类似,但实现更通用。

其他采样器

  • 语法采样器

    c 复制代码
    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 是语法根节点。

  • 惩罚采样器

    c 复制代码
    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 采样器

    c 复制代码
    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 偏置采样器

    c 复制代码
    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 是偏置数组。

  • 填空采样器

    c 复制代码
    LLAMA_API struct llama_sampler * llama_sampler_init_infill(const struct llama_vocab * vocab);

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

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

循环进行解码采样

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

llama_decode的原型如下:

c 复制代码
int32_t llama_decode(
            struct llama_context * ctx,
              struct llama_batch   batch);

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

c 复制代码
llama_token llama_sampler_sample(struct llama_sampler * smpl, struct llama_context * ctx, int32_t idx);

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

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

c 复制代码
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内的文本数据,返回的是反序列化的长度。如果出错,返回负数。

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

c 复制代码
batch.token[0] = next_token;
batch.n_tokens = 1;

释放资源

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

c 复制代码
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模型,进行测试。

完整代码如下:

c 复制代码
#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."

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

相关推荐
agicall.com7 小时前
座机通话双方语音分离技术解决方案详解
人工智能·语音识别·信创电话助手·座机语音转文字·固话座机录音转文字
AI机器学习算法7 小时前
《动手学深度学习PyTorch版》笔记
人工智能·学习·机器学习
Goboy8 小时前
「我的第一次移动端 AI 办公」TRAE SOLO 三端联动, 通勤路上就把活干了,这设计,老罗看了都想当场退役
人工智能·ai编程·trae
qq_452396238 小时前
第二十篇:《UI自动化测试的未来:AI驱动的智能测试与低代码平台》
人工智能·低代码·ui
视觉&物联智能8 小时前
【杂谈】-人工智能风险文化对组织决策的深远影响
人工智能·安全·ai·agi
β添砖java8 小时前
深度学习(12)Kaggle房价竞赛
人工智能·深度学习
冬奇Lab8 小时前
RAG 系列(十):混合检索——让召回更全面
人工智能·llm
冬奇Lab8 小时前
一天一个开源项目(第95篇):Claude for Financial Services - Anthropic 官方金融行业 AI 代理套件
人工智能·开源·资讯
bbsh20999 小时前
AI辅助编程时代,企业级网站系统建设为什么还需要专业平台?
人工智能
05候补工程师9 小时前
[实战复盘] 拒绝 AI 屎山!我从设计模式中学到的“调教”AI 新范式
人工智能·python·设计模式·ai·ai编程