llama.cpp的C语言API使用

news2025/2/5 7:49:54

我们知道,一般运行大语言模型都是在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.coloradmin.cn/o/2292194.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鼠标拖尾特效

文章目录 鼠标拖尾特效一、引言二、实现原理1、监听鼠标移动事件2、生成拖尾元素3、控制元素生命周期 三、代码实现四、使用示例五、总结 鼠标拖尾特效 一、引言 鼠标拖尾特效是一种非常酷炫的前端交互效果&#xff0c;能够为网页增添独特的视觉体验。它通常通过JavaScript和C…

金山打字游戏2010绿色版,Win7-11可用DxWnd完美运行

金山打字游戏2010绿色版&#xff0c;Win7-11可用DxWnd完美运行 链接&#xff1a;https://pan.xunlei.com/s/VOIAYCzmkbDfdASGJa_uLjquA1?pwd67vw# 进入游戏后&#xff0c;如果输入不了英文字母&#xff08;很可能是中文输入状态&#xff09;&#xff0c;就按一下“Shift”键…

爬虫学习笔记之Robots协议相关整理

定义 Robots协议也称作爬虫协议、机器人协议&#xff0c;全名为网络爬虫排除标准&#xff0c;用来告诉爬虫和搜索引擎哪些页面可以爬取、哪些不可以。它通常是一个叫做robots.txt的文本文件&#xff0c;一般放在网站的根目录下。 robots.txt文件的样例 对有所爬虫均生效&#…

(10) 如何获取 linux 系统上的 TCP 、 UDP 套接字的收发缓存的默认大小,以及代码范例

&#xff08;1&#xff09; 先介绍下后面的代码里要用到的基础函数&#xff1a; 以及&#xff1a; &#xff08;2&#xff09; 接着给出现代版的 读写 socket 参数的系统函数 &#xff1a; 以及&#xff1a; &#xff08;3&#xff09; 给出 一言的 范例代码&#xff0c;获取…

【玩转 Postman 接口测试与开发2_016】第13章:在 Postman 中实现契约测试(Contract Testing)与 API 接口验证(上)

《API Testing and Development with Postman》最新第二版封面 文章目录 第十三章 契约测试与 API 接口验证1 契约测试的概念2 契约测试的工作原理3 契约测试的分类4 DeepSeek 给出的契约测试相关背景5 契约测试在 Postman 中的创建方法6 API 实例的基本用法7 API 实例的类型实…

day8-面向对象

目录 面向对象1、面向对象介绍2、类和对象类和对象类的几个补充注意事项 3、封装 面向对象 1、面向对象介绍 ⭐️面向对象介绍&#xff1a; 面向&#xff1a;拿、找对象&#xff1a;能干活的东西面向对象编程&#xff1a;拿东西过来做对应的事情 面向对象编程的例子&#x…

【Java】位图 布隆过滤器

位图 初识位图 位图, 实际上就是将二进制位作为哈希表的一个个哈希桶的数据结构, 由于二进制位只能表示 0 和 1, 因此通常用于表示数据是否存在. 如下图所示, 这个位图就用于标识 0 ~ 14 中有什么数字存在 可以看到, 我们这里相当于是把下标作为了 key-value 的一员. 但是这…

【自然语言处理(NLP)】生成词向量:GloVe(Global Vectors for Word Representation)原理及应用

文章目录 介绍GloVe 介绍核心思想共现矩阵1. 共现矩阵的定义2. 共现概率矩阵的定义3. 共现概率矩阵的意义4. 共现概率矩阵的构建步骤5. 共现概率矩阵的应用6. 示例7. 优缺点优点缺点 **总结** 目标函数训练过程使用预训练的GloVe词向量 优点应用总结 个人主页&#xff1a;道友老…

如何获取sql数据中时间的月份、年份(类型为date)

可用自带的函数month来实现 如&#xff1a; 创建表及插入数据&#xff1a; create table test (id int,begindate datetime) insert into test values (1,2015-01-01) insert into test values (2,2015-02-01) 执行sql语句,获取月份&#xff1a; select MONTH(begindate)…

NSSCTF Pwn [NISACTF 2022]ezpie 题解

下载 exeinfo checksec 32位 NX PIE开启 查看main函数&#xff1a; 后门函数&#xff1a; 发现已经给出了main函数地址 因为开启了PIE 所以IDA中 后门函数减去main函数的后三位就是偏移 给出的函数加上这个数就是后门函数地址 exp&#xff1a; from pwn import *p remote(…

【01】共识机制

BTF共识 拜占庭将军问题 拜占庭将军问题是一个共识问题 起源 Leslie Lamport在论文《The Byzantine Generals Problem》提出拜占庭将军问题。 核心描述 军中可能有叛徒&#xff0c;却要保证进攻一致&#xff0c;由此引申到计算领域&#xff0c;发展成了一种容错理论。随着…

文字加持:让 OpenCV 轻松在图像中插上文字

前言 在很多图像处理任务中,我们不仅需要提取图像信息,还希望在图像上加上一些文字,或是标注,或是动态展示。正如在一幅画上添加一个标语,或者在一个视频上加上动态字幕,cv2.putText 就是这个“文字魔术师”,它能让我们的图像从“沉默寡言”变得生动有趣。 今天,我们…

【R语言】环境空间

一、环境空间的特点 环境空间是一种特殊类型的变量&#xff0c;它可以像其它变量一样被分配和操作&#xff0c;还可以以参数的形式传递给函数。 R语言中环境空间具有如下3个特点&#xff1a; 1、对象名称唯一性 此特点指的是在不同的环境空间中可以有同名的变量出现&#x…

惰性函数【Ⅱ】《事件绑定的自我修养:从青铜到王者的进化之路》

【Ⅱ】《事件绑定的自我修养&#xff1a;从青铜到王者的进化之路》 1. 代码功能大白话&#xff08;给室友讲明白版&#xff09; // 青铜写法&#xff1a;每次都要问浏览器"你行不行&#xff1f;" function addEvent青铜版(element, type, handler) {if (window.add…

Vue3的el-table-column下拉输入实时查询API数据选择的实现方法

由于本人对el-table-column有下拉输入选择的要求&#xff0c;根据网上搜索的资料及本人优化&#xff0c;推出我比较满意的方法&#xff0c;供各位读者参考使用。 效果图 el-table-column写法 <el-table-columnlabel"货品编号"align"center"prop"…

[mmdetection]fast-rcnn模型训练自己的数据集的详细教程

本篇博客是由本人亲自调试成功后的学习笔记。使用了mmdetection项目包进行fast-rcnn模型的训练&#xff0c;数据集是自制图像数据。废话不多说&#xff0c;下面进入训练步骤教程。 注&#xff1a;本人使用linux服务器进行展示&#xff0c;Windows环境大差不差。另外&#xff0…

RFID涉密载体管控系统|支持国产化、自主研发

涉密载体管理系统DW-S402是一套成熟系统&#xff0c;是用于对各种涉密载体进行有效管理&#xff0c;实现对载体的智能化、规范化、标准化管理&#xff0c;广泛应用于保密、机要单位以及企事业单位等有载体保管需求的行业。 用户为对文件资料、存储介质等管理严格的单位&#x…

BUU14 [极客大挑战 2019]PHP1

用dirsearch扫描文件&#xff0c;扫了一万年什么也没扫出来 从网上看的wp&#xff0c;他们扫出来www.zip 这里直接用上了&#xff0c;以后有空再扫一遍 下载www.zip 在index.php中 说明要输入select 打开class.php <?php include flag.php;error_reporting(0);class…

数据分析师使用Kutools for Excel 插件

数据分析师使用Kutools for Excel 插件 Kutools for Excel 是一款功能强大的 Excel 插件&#xff0c;旨在提高 Excel 用户的工作效率&#xff0c;简化复杂的操作。它提供了超过 300 个增强功能&#xff0c;帮助用户快速完成数据管理、格式化、排序、分析等任务&#xff0c;特别…

【高级篇 / IPv6】(7.2) ❀ 04. 在60E上配置ADSL拨号宽带上网(IPv4) ❀ FortiGate 防火墙

【简介】除了单位用户以外&#xff0c;大部分个人用户目前使用的仍然是30E、50E、60E系列防火墙&#xff0c;固件无法达到目前最高版本7.6&#xff0c;这里以最常用的60E为例&#xff0c;演示固件版本7.2下实现ADSL拨号宽带的IPv6上网。由于内容比较多&#xff0c;文章分上、下…