大语言模型推理框架llama.cpp开发实战 原创

发布于 2025-1-20 07:53
浏览
0收藏

本文首先探索当前热门的大语言模型推理框架llama.cpp的内部架构,然后使用此框架实现了一个基本形式的聊天程序。

简介

当前,llama.cpp框架以其简单性被业界广泛采用,彻底改变了LLM推理领域。它支持企业和个人开发人员能够在从SBC到多GPU集群的各类型设备上部署机器学习大型语言模型。尽管llama.cpp的语言绑定方式使其使用方式变得容易,但是对于性能敏感或资源受限的情况,使用C/C++编程方案可能是一个更为可行的选择。

本文旨在让读者详细了解如何使用直接来自llama.cpp的低级函数执行LLM推理。具体地讲,我们将详细探讨llama.cpp框架开发程序的详细流程、llama.cpp框架的架构,最后实现一个简单的聊天应用程序。

请注意,我们将在本文中编写的C++代码也用于SmolChat应用程序中,这是一个原生Android应用程序,它允许用户在聊天界面中与LLM/SLM实现完全在设备上的交互。具体来说,我们将使用文章前面将定义的LLMInference类与JNI绑定一起使用,从而实现共同执行​GGUF模型​。

另外,本文将分析的代码实现可以在​链接​处找到。

还有,上述代码也派生自llama.cpp的​官方简单聊天示例程序​。

关于llama.cpp

llama.cpp是一个C/C++框架,用于在多个执行后端推断以​GGUF格式​定义的机器学习模型。这个框架最初是Meta著名的Llama系列LLM的纯C/C++实现,可以在苹果公司自研的Silicon处理器、AVX/AVX-512、CUDA和基于Arm Neon的环境中推断。此外,这个框架还包括一个基于CLI的工具llama-cli来运行GGUF LLM模型,还提供一个llama-server(OpenAI兼容服务器)通过HTTP请求方式执行模型。

llama.cpp使用机器学习的​张量库ggml​​,这是一个低级框架,提供深度学习模型所需的原始函数,并从用户那里抽象后端实现细节。​Georgi Gerganov​是ggml库和llama.cpp框架的创建者。

此外,llama.cpp框架存储库的​README文件​还列出了其他编程语言中基于llama.cpp构建的包装器。Ollama和LM Studio等流行工具也使用llama.cpp上的绑定来增强用户友好性。该项目不依赖其他第三方库。

llama.cpp与PyTorch/TensorFlow有何不同?

llama.cpp从一开始就强调ML模型的推理,而​PyTorch​​ 和​TensorFlow​ 是端到端解决方案,通过一个安装包的形式来提供数据处理、模型训练/验证和高效推理。

注意:PyTorch和TensorFlow也有各自的轻量级推理扩展,即​ExecuTorch​​和​TensorFlowLite​。

仅考虑模型的推理阶段,llama.cpp的实现是轻量的,因为它没有第三方依赖项,并且自动支持大量可用的运算符或模型格式。此外,顾名思义,该项目最初是一个用于推断LLM(来自Meta的Llama模型)的高效库,并继续支持广泛的开源LLM架构。

如果把PyTorch/TensorFlow比作是豪华、耗电的游轮的话,那么llama.cpp就是小型、快速的摩托艇。PyTorch/TF和llama.cpp都有各自的使用场景。

设置

我们在基于Linux的环境(本机或WSL环境)中进行开发;为此,需要安装cmake和GNU/clang工具链。我们将从源代码编译llama.cpp,并将其作为共享库添加到我们的可执行聊天程序中。

首先,我们创建一个项目目录smol_chat,并使用一个externals目录来存储克隆自原项目的llama.cpp存储库。

mkdir smol_chat
cd smol_chat

mkdir src
mkdir externals
touch CMakeLists.txt

cd externals
git clone --depth=1 https://github.com/ggerganov/llama.cpp

CMakeLists.txt是我们定义构建项目方案的文件,通过引用来自externals/llama.cpp的标准头文件和共享库,允许CMake使用默认工具链(GNU/clang)编译我们的C/C++代码。

cmake_minimum_required(VERSION 3.10)
project(llama_inference)

set(CMAKE_CXX_STANDARD 17)
set(LLAMA_BUILD_COMMON On)
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/externals/llama.cpp")

add_executable(
chat
src/LLMInference.cpp src/main.cpp
)
target_link_libraries(
chat 
PRIVATE
common llama ggml
)

加载模型

现在,我们已经定义了如何通过CMake构建我们的项目。接下来,我们创建一个头文件LLMInference.h,它声明了一个包含高级函数的类,用于与LLM交互。llama.cpp提供了一个C样式的API,因此将其嵌入到类中将有助于我们抽象/隐藏内部工作细节。

#ifndef LLMINFERENCE_H
#define LLMINFERENCE_H

#include "common.h"
#include "llama.h"
#include <string>
#include <vector>

class LLMInference {

// llama.cpp特定的数据类型
llama_context* _ctx;
llama_model* _model;
llama_sampler* _sampler;
llama_batch _batch;
llama_token _currToken;

// 用于在聊天程序中存储用户/助手信息的容器
std::vector<llama_chat_message> _messages;
//将聊天模板应用于所有消息后生成的字符串存储在“_messages”中
std::vector<char> _formattedMessages;
// 将最后查询的标记存储到“_messages”中
std::vector<llama_token> _promptTokens;
int _prevLen = 0;

// 存储给定查询的完整响应
std::string _response = "";

public:

void loadModel(const std::string& modelPath, float minP, float temperature);

void addChatMessage(const std::string& message, const std::string& role);

void startCompletion(const std::string& query);

std::string completionLoop();

void stopCompletion();

~LLMInference();
};

#endif

上面头文件中声明的私有成员将用于实现本文后续部分中描述的公共成员函数。首先,让我们在LLMInference.cpp中定义每个成员函数。

#include "LLMInference.h"
#include <cstring>
#include <iostream>

void LLMInference::loadModel(const std::string& model_path, float min_p, float temperature) {
//创建一个llama_model的实例
llama_model_params model_params = llama_model_default_params();
_model = llama_load_model_from_file(model_path.data(), model_params);

if (!_model) {
throw std::runtime_error("load_model() failed");
}

//创建 llama_context 实例
llama_context_params ctx_params = llama_context_default_params();
ctx_params.n_ctx = 0;               // 从模型 GGUF 文件中获取上下文大小
ctx_params.no_perf = true;          // 禁用性能指标
_ctx = llama_new_context_with_model(_model, ctx_params);

if (!_ctx) {
throw std::runtime_error("llama_new_context_with_model() returned null");
}

//初始化采样器
llama_sampler_chain_params sampler_params = llama_sampler_chain_default_params();
sampler_params.no_perf = true;      // 禁用性能指标
_sampler = llama_sampler_chain_init(sampler_params);
llama_sampler_chain_add(_sampler, llama_sampler_init_min_p(min_p, 1));
llama_sampler_chain_add(_sampler, llama_sampler_init_temp(temperature));
llama_sampler_chain_add(_sampler, llama_sampler_init_dist(LLAMA_DEFAULT_SEED));

_formattedMessages = std::vector<char>(llama_n_ctx(_ctx));
_messages.clear();
}

上述代码中,llama_load_model_from_file使用llama_load_model从文件内部读取模型,并使用给定的llama_model_params填充llama_model实例。用户可以提供参数,但我们可以使用llama_model_default_params获取预初始化的默认结构。

llama_context表示加载的GGUF模型的执行环境。llama_new_context_with_model实例化新的llama_context,并通过读取llama_model_params或自动检测可用的后端来准备执行的后端。它还初始化K-V缓存,这在解码或推理步骤中是很重要的。管理跨多个后端的计算的后端调度程序也被初始化。

llama_sampler决定了我们如何从模型(特别是LLM的解码器)的输出(logits)得出概率分布中的采样/选择标记。LLM为词汇表中存在的每个标记分配一个概率,表示该标记出现在序列中的下一个概率。我们使用llama_sampler_init_temp和llama_sampler_init_min_p设置的温度和min-p是控制标记采样过程的两个参数。

执行推理

推理过程涉及多个步骤,该过程将用户的文本查询作为输入并返回LLM的响应。

1. 将聊天模板应用于查询

对于LLM,传入消息被归类为属于三个角色,即用户、助手和系统。其中,用户和助手消息分别由用户和LLM给出,而系统表示整个对话中遵循的系统范围提示。每条消息都由角色和内容组成,其中内容是实际文本,角色是三个角色中的任何一个。

<example>

系统提示是对话的第一条消息。在我们的代码中,消息存储为名为_messages的std::vector<llama_chat_message>。其中,llama_chat_message是具有角色和内容属性的llama.cpp结构。我们使用llama.cpp中的llama_chat_apply_template函数将存储在GGUF文件中的聊天模板应用为元数据。我们将应用聊天模板后获得的字符串或std::vector<char>存储在_formattedMessages中。

2. 标记化

标记化是将给定文本划分为较小部分(标记)的过程。我们为每个部分/标记分配一个唯一的整数ID,从而将输入文本转换为整数序列,形成LLM的输入。llama.cpp提供common_tokenize或llama_tokenize函数来执行标记化,其中common_tokenize将标记序列作为std::vector<llama_token>返回。

void LLMInference::startCompletion(const std::string& query) {
addChatMessage(query, "user");

// 应用聊天模板 
int new_len = llama_chat_apply_template(
_model,
nullptr,
_messages.data(),
_messages.size(),
true,
_formattedMessages.data(),
_formattedMessages.size()
);
if (new_len > (int)_formattedMessages.size()) {
//调整输出缓冲区 `_formattedMessages`的大小并重新应用聊天模板
_formattedMessages.resize(new_len);
new_len = llama_chat_apply_template(_model, nullptr, _messages.data(), _messages.size(), true, _formattedMessages.data(), _formattedMessages.size());
}
if (new_len < 0) {
throw std::runtime_error("llama_chat_apply_template() in LLMInference::start_completion() failed");
}
std::string prompt(_formattedMessages.begin() + _prevLen, _formattedMessages.begin() + new_len);

// 标记化
_promptTokens = common_tokenize(_model, prompt, true, true);

// 创建一个包含单个序列的llama_batch
// see llama_batch_init for more details
_batch.token = _promptTokens.data();
_batch.n_tokens = _promptTokens.size();
}

在上面代码中,我们应用聊天模板并在LLMInference::startCompletion方法中执行标记化,然后创建一个llama_batch实例来保存模型的最终输入。

3. 解码、采样和KV缓存

如前所述,LLM通过连续预测给定序列中的下一个标记来生成响应。LLM还经过训练以预测特殊的生成结束(EOG)标记,指示预测标记序列的结束。completion_loop函数返回序列中的下一个标记,并不断被调用,直到它返回的标记是EOG标记。

  • 通过llama_n_ctx和llama_get_kv_cached_used_cells,我们可以确定用于存储输入的上下文的长度。目前,如果标记化输入的长度超过上下文大小的话,我们会抛出一个错误。
  • llama_decode根据变量_batch中的输入信息对模型进行前向传递。
  • 通过在LLMInference::loadModel中初始化的_sampler,我们抽样或选择一个标记作为我们的预测并将其存储在_currToken中。我们检查该标记是否为EOG标记,然后返回“EOG”,表示应终止调用LLMInference::completionLoop的文本生成循环。终止时,我们将一条新消息附加到_messages,这是具有角色assistant的LLM给出的完整响应信息。
  • _currToken仍然是一个整数,它由common_token_to_piece函数转换为字符串标记片段。此字符串标记从finishLoop方法返回。
  • 我们需要重新初始化_batch以确保它现在仅包含_currToken而不是整个输入序列,即_promptTokens。这是因为所有先前标记的“键”和“值”都已缓存。通过避免计算_promptTokens中所有标记的所有“键”和“值”,可以减少推理时间。

std::string LLMInference::completionLoop() {
// 检查模型输入的长度是否超出了模型的上下文大小
int contextSize = llama_n_ctx(_ctx);
int nCtxUsed = llama_get_kv_cache_used_cells(_ctx);
if (nCtxUsed + _batch.n_tokens > contextSize) {
std::cerr << "context size exceeded" << '\n';
exit(0);
}
//运行模型
if (llama_decode(_ctx, _batch) < 0) {
throw std::runtime_error("llama_decode() failed");
}

// 采样一个标记并检查它是否是EOG(生成结束标记)
// 将整数标记转换为其对应的单词片段
_currToken = llama_sampler_sample(_sampler, _ctx, -1);
if (llama_token_is_eog(_model, _currToken)) {
addChatMessage(strdup(_response.data()), "assistant");
_response.clear();
return "[EOG]";
}
std::string piece = common_token_to_piece(_ctx, _currToken, true);


// 使用新预测的标记重新初始化批次
// 所有先前标记的键值对都已缓存在KV缓存中
_batch.token = &_currToken;
_batch.n_tokens = 1;

return piece;
}
  • 此外,对于用户的每个查询,LLM将整个标记化对话(存储在_messages中的所有消息)作为输入。如果我们每次都在startCompletion方法中标记整个对话,那么随着对话变长,预处理时间和总体推理时间将会增加。
  • 为了避免这种计算,我们只需要标记添加到_messages的最新消息/查询。_formattedMessages中消息被标记的长度存储在_prevLen中。在响应生成结束时,即在LLMInference::stopCompletion中,我们通过将LLM的响应附加到_messages并使用llama_chat_apply_template的返回值来更新_prevLen的值。

void LLMInference::stopCompletion() {
_prevLen = llama_chat_apply_template(
_model,
nullptr,
_messages.data(),
_messages.size(),
false,
nullptr,
0
);
if (_prevLen < 0) {
throw std::runtime_error("llama_chat_apply_template() in LLMInference::stop_completion() failed");
}
}

编写析构函数

我们在_messages和llama.cpp内部实现了一个析构函数方法来释放动态分配的对象。

LLMInference::~LLMInference() {
//释放消息中消息文本所占用的内存(因为我们已使用strdup()创建了malloc副本)
for (llama_chat_message &message: _messages) {
delete message.content;
}
llama_kv_cache_clear(_ctx);
llama_sampler_free(_sampler);
llama_free(_ctx);
llama_free_model(_model);
}

编写小型CMD应用程序

我们创建了一个小型接口程序,允许我们与LLM进行转换。核心工作包括实例化LLMInference类并调用我们在前面部分中定义的所有方法。

#include "LLMInference.h"
#include <memory>
#include <iostream>

int main(int argc, char* argv[]) {

std::string modelPath = "smollm2-360m-instruct-q8_0.gguf";
float temperature = 1.0f;
float minP = 0.05f;
std::unique_ptr<LLMInference> llmInference = std::make_unique<LLMInference>();
llmInference->loadModel(modelPath, minP, temperature);

llmInference->addChatMessage("You are a helpful assistant", "system");

while (true) {
std::cout << "Enter query:\n";
std::string query;
std::getline(std::cin, query);
if (query == "exit") {
break;
}
llmInference->startCompletion(query);
std::string predictedToken;
while ((predictedToken = llmInference->completionLoop()) != "[EOG]") {
std::cout << predictedToken;
fflush(stdout);
}
std::cout << '\n';
}

return 0;
}

运行示例程序

我们使用前面几节中编写的CMakeLists.txt文件。这个文件用于创建一个Makefile,该文件将编译代码并创建一个可供使用的可执行文件。

mkdir build
cd build
cmake ..
make
./chat

输出结果如下:

register_backend: registered backend CPU (1 devices)
register_device: registered device CPU (11th Gen Intel(R) Core(TM) i3-1115G4 @ 3.00GHz)
llama_model_loader: loaded meta data with 33 key-value pairs and 290 tensors from /home/shubham/CPP_Projects/llama-cpp-inference/models/smollm2-360m-instruct-q8_0.gguf (version GGUF V3 (latest))
llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output.
llama_model_loader: - kv   0:                       general.architecture str              = llama
llama_model_loader: - kv   1:                               general.type str              = model
llama_model_loader: - kv   2:                               general.name str              = Smollm2 360M 8k Lc100K Mix1 Ep2
llama_model_loader: - kv   3:                       general.organization str              = Loubnabnl
llama_model_loader: - kv   4:                           general.finetune str              = 8k-lc100k-mix1-ep2
llama_model_loader: - kv   5:                           general.basename str              = smollm2
llama_model_loader: - kv   6:                         general.size_label str              = 360M
llama_model_loader: - kv   7:                            general.license str              = apache-2.0
llama_model_loader: - kv   8:                          general.languages arr[str,1]       = ["en"]
llama_model_loader: - kv   9:                          llama.block_count u32              = 32
llama_model_loader: - kv  10:                       llama.context_length u32              = 8192
llama_model_loader: - kv  11:                     llama.embedding_length u32              = 960
llama_model_loader: - kv  12:                  llama.feed_forward_length u32              = 2560
llama_model_loader: - kv  13:                 llama.attention.head_count u32              = 15
llama_model_loader: - kv  14:              llama.attention.head_count_kv u32              = 5
llama_model_loader: - kv  15:                       llama.rope.freq_base f32              = 100000.000000
llama_model_loader: - kv  16:     llama.attention.layer_norm_rms_epsilon f32              = 0.000010
llama_model_loader: - kv  17:                          general.file_type u32              = 7
llama_model_loader: - kv  18:                           llama.vocab_size u32              = 49152
llama_model_loader: - kv  19:                 llama.rope.dimension_count u32              = 64
llama_model_loader: - kv  20:            tokenizer.ggml.add_space_prefix bool             = false
llama_model_loader: - kv  21:               tokenizer.ggml.add_bos_token bool             = false
llama_model_loader: - kv  22:                       tokenizer.ggml.model str              = gpt2
llama_model_loader: - kv  23:                         tokenizer.ggml.pre str              = smollm
llama_model_loader: - kv  24:                      tokenizer.ggml.tokens arr[str,49152]   = ["<|endoftext|>", "<|im_start|>", "<|...
llama_model_loader: - kv  25:                  tokenizer.ggml.token_type arr[i32,49152]   = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, ...
llama_model_loader: - kv  26:                      tokenizer.ggml.merges arr[str,48900]   = ["Ġ t", "Ġ a", "i n", "h e", "Ġ Ġ...
llama_model_loader: - kv  27:                tokenizer.ggml.bos_token_id u32              = 1
llama_model_loader: - kv  28:                tokenizer.ggml.eos_token_id u32              = 2
llama_model_loader: - kv  29:            tokenizer.ggml.unknown_token_id u32              = 0
llama_model_loader: - kv  30:            tokenizer.ggml.padding_token_id u32              = 2
llama_model_loader: - kv  31:                    tokenizer.chat_template str              = {% for message in messages %}{% if lo...
llama_model_loader: - kv  32:               general.quantization_version u32              = 2
llama_model_loader: - type  f32:   65 tensors
llama_model_loader: - type q8_0:  225 tensors
llm_load_vocab: control token:      7 '<gh_stars>' is not marked as EOG
llm_load_vocab: control token:     13 '<jupyter_code>' is not marked as EOG
llm_load_vocab: control token:     16 '<empty_output>' is not marked as EOG
llm_load_vocab: control token:     11 '<jupyter_start>' is not marked as EOG
llm_load_vocab: control token:     10 '<issue_closed>' is not marked as EOG
llm_load_vocab: control token:      6 '<filename>' is not marked as EOG
llm_load_vocab: control token:      8 '<issue_start>' is not marked as EOG
llm_load_vocab: control token:      3 '<repo_name>' is not marked as EOG
llm_load_vocab: control token:     12 '<jupyter_text>' is not marked as EOG
llm_load_vocab: control token:     15 '<jupyter_script>' is not marked as EOG
llm_load_vocab: control token:      4 '<reponame>' is not marked as EOG
llm_load_vocab: control token:      1 '<|im_start|>' is not marked as EOG
llm_load_vocab: control token:      9 '<issue_comment>' is not marked as EOG
llm_load_vocab: control token:      5 '<file_sep>' is not marked as EOG
llm_load_vocab: control token:     14 '<jupyter_output>' is not marked as EOG
llm_load_vocab: special tokens cache size = 17
llm_load_vocab: token to piece cache size = 0.3170 MB
llm_load_print_meta: format           = GGUF V3 (latest)
llm_load_print_meta: arch             = llama
llm_load_print_meta: vocab type       = BPE
llm_load_print_meta: n_vocab          = 49152
llm_load_print_meta: n_merges         = 48900
llm_load_print_meta: vocab_only       = 0
llm_load_print_meta: n_ctx_train      = 8192
llm_load_print_meta: n_embd           = 960
llm_load_print_meta: n_layer          = 32
llm_load_print_meta: n_head           = 15
llm_load_print_meta: n_head_kv        = 5
llm_load_print_meta: n_rot            = 64
llm_load_print_meta: n_swa            = 0
llm_load_print_meta: n_embd_head_k    = 64
llm_load_print_meta: n_embd_head_v    = 64
llm_load_print_meta: n_gqa            = 3
llm_load_print_meta: n_embd_k_gqa     = 320
llm_load_print_meta: n_embd_v_gqa     = 320
llm_load_print_meta: f_norm_eps       = 0.0e+00
llm_load_print_meta: f_norm_rms_eps   = 1.0e-05
llm_load_print_meta: f_clamp_kqv      = 0.0e+00
llm_load_print_meta: f_max_alibi_bias = 0.0e+00
llm_load_print_meta: f_logit_scale    = 0.0e+00
llm_load_print_meta: n_ff             = 2560
llm_load_print_meta: n_expert         = 0
llm_load_print_meta: n_expert_used    = 0
llm_load_print_meta: causal attn      = 1
llm_load_print_meta: pooling type     = 0
llm_load_print_meta: rope type        = 0
llm_load_print_meta: rope scaling     = linear
llm_load_print_meta: freq_base_train  = 100000.0
llm_load_print_meta: freq_scale_train = 1
llm_load_print_meta: n_ctx_orig_yarn  = 8192
llm_load_print_meta: rope_finetuned   = unknown
llm_load_print_meta: ssm_d_conv       = 0
llm_load_print_meta: ssm_d_inner      = 0
llm_load_print_meta: ssm_d_state      = 0
llm_load_print_meta: ssm_dt_rank      = 0
llm_load_print_meta: ssm_dt_b_c_rms   = 0
llm_load_print_meta: model type       = 3B
llm_load_print_meta: model ftype      = Q8_0
llm_load_print_meta: model params     = 361.82 M
llm_load_print_meta: model size       = 366.80 MiB (8.50 BPW) 
llm_load_print_meta: general.name     = Smollm2 360M 8k Lc100K Mix1 Ep2
llm_load_print_meta: BOS token        = 1 '<|im_start|>'
llm_load_print_meta: EOS token        = 2 '<|im_end|>'
llm_load_print_meta: EOT token        = 0 '<|endoftext|>'
llm_load_print_meta: UNK token        = 0 '<|endoftext|>'
llm_load_print_meta: PAD token        = 2 '<|im_end|>'
llm_load_print_meta: LF token         = 143 'Ä'
llm_load_print_meta: EOG token        = 0 '<|endoftext|>'
llm_load_print_meta: EOG token        = 2 '<|im_end|>'
llm_load_print_meta: max token length = 162
llm_load_tensors: ggml ctx size =    0.14 MiB
llm_load_tensors:        CPU buffer size =   366.80 MiB
...............................................................................
llama_new_context_with_model: n_ctx      = 8192
llama_new_context_with_model: n_batch    = 2048
llama_new_context_with_model: n_ubatch   = 512
llama_new_context_with_model: flash_attn = 0
llama_new_context_with_model: freq_base  = 100000.0
llama_new_context_with_model: freq_scale = 1
llama_kv_cache_init:        CPU KV buffer size =   320.00 MiB
llama_new_context_with_model: KV self size  =  320.00 MiB, K (f16):  160.00 MiB, V (f16):  160.00 MiB
llama_new_context_with_model:        CPU  output buffer size =     0.19 MiB
ggml_gallocr_reserve_n: reallocating CPU buffer from size 0.00 MiB to 263.51 MiB
llama_new_context_with_model:        CPU compute buffer size =   263.51 MiB
llama_new_context_with_model: graph nodes  = 1030
llama_new_context_with_model: graph splits = 1
Enter query:
How are you?
I'm a text-based AI assistant. I don't have emotions or personal feelings, but I can understand and respond to your requests accordingly. If you have questions or need help with anything, feel free to ask.
Enter query:
Write a one line description on the C++ keyword 'new' 
New C++ keyword represents memory allocation for dynamically allocated memory.
Enter query:
exit

结论

llama.cpp简化了大型语言模型的部署,使其可以在各种设备和使用场景中访问。本文中,我们通过介绍这个框架的内部结构并构建一个简单的C++推理程序,展示了开发人员应该如何利用其低级函数来实现高性能但资源受限的应用程序。本文不仅介绍了llama.cpp框架的核心架构,还强调了它在实际项目中的实用性,从而实现了与LLM的高效率设备交互。

对于有兴趣突破LLM部署界限或旨在构建强大应用程序的开发人员来说,掌握llama.cpp等工具将打开无限可能的大门。在你进一步探索时,请记住,你可以进一步扩展这些基础知识,以便集成高级功能、优化性能并适应不断发展的AI应用场景。

最后,我希望本文能够提供一些有用的信息,并让你对直接在C++环境中运行LLM感到着迷。

译者介绍

朱先忠,51CTO社区编辑,51CTO专家博客、讲师,潍坊一所高校计算机教师,自由编程界老兵一枚。

原文标题:​llama.cpp: Writing A Simple C++ Inference Program for GGUF LLM Models​,作者:Shubham Panchal

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2025-1-20 07:59:15修改
收藏
回复
举报
回复
相关推荐