大語(yǔ)言模型推理框架llama.cpp開發(fā)實(shí)戰(zhàn) 原創(chuàng)
本文首先探索當(dāng)前熱門的大語(yǔ)言模型推理框架llama.cpp的內(nèi)部架構(gòu),然后使用此框架實(shí)現(xiàn)了一個(gè)基本形式的聊天程序。
簡(jiǎn)介
當(dāng)前,llama.cpp框架以其簡(jiǎn)單性被業(yè)界廣泛采用,徹底改變了LLM推理領(lǐng)域。它支持企業(yè)和個(gè)人開發(fā)人員能夠在從SBC到多GPU集群的各類型設(shè)備上部署機(jī)器學(xué)習(xí)大型語(yǔ)言模型。盡管llama.cpp的語(yǔ)言綁定方式使其使用方式變得容易,但是對(duì)于性能敏感或資源受限的情況,使用C/C++編程方案可能是一個(gè)更為可行的選擇。
本文旨在讓讀者詳細(xì)了解如何使用直接來(lái)自llama.cpp的低級(jí)函數(shù)執(zhí)行LLM推理。具體地講,我們將詳細(xì)探討llama.cpp框架開發(fā)程序的詳細(xì)流程、llama.cpp框架的架構(gòu),最后實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天應(yīng)用程序。
請(qǐng)注意,我們將在本文中編寫的C++代碼也用于SmolChat應(yīng)用程序中,這是一個(gè)原生Android應(yīng)用程序,它允許用戶在聊天界面中與LLM/SLM實(shí)現(xiàn)完全在設(shè)備上的交互。具體來(lái)說(shuō),我們將使用文章前面將定義的LLMInference類與JNI綁定一起使用,從而實(shí)現(xiàn)共同執(zhí)行??GGUF模型??。
另外,本文將分析的代碼實(shí)現(xiàn)可以在??鏈接??處找到。
還有,上述代碼也派生自llama.cpp的??官方簡(jiǎn)單聊天示例程序??。
關(guān)于llama.cpp
llama.cpp是一個(gè)C/C++框架,用于在多個(gè)執(zhí)行后端推斷以??GGUF格式??定義的機(jī)器學(xué)習(xí)模型。這個(gè)框架最初是Meta著名的Llama系列LLM的純C/C++實(shí)現(xiàn),可以在蘋果公司自研的Silicon處理器、AVX/AVX-512、CUDA和基于Arm Neon的環(huán)境中推斷。此外,這個(gè)框架還包括一個(gè)基于CLI的工具llama-cli來(lái)運(yùn)行GGUF LLM模型,還提供一個(gè)llama-server(OpenAI兼容服務(wù)器)通過(guò)HTTP請(qǐng)求方式執(zhí)行模型。
llama.cpp使用機(jī)器學(xué)習(xí)的??張量庫(kù)ggml???,這是一個(gè)低級(jí)框架,提供深度學(xué)習(xí)模型所需的原始函數(shù),并從用戶那里抽象后端實(shí)現(xiàn)細(xì)節(jié)。??Georgi Gerganov??是ggml庫(kù)和llama.cpp框架的創(chuàng)建者。
此外,llama.cpp框架存儲(chǔ)庫(kù)的??README文件??還列出了其他編程語(yǔ)言中基于llama.cpp構(gòu)建的包裝器。Ollama和LM Studio等流行工具也使用llama.cpp上的綁定來(lái)增強(qiáng)用戶友好性。該項(xiàng)目不依賴其他第三方庫(kù)。
llama.cpp與PyTorch/TensorFlow有何不同?
llama.cpp從一開始就強(qiáng)調(diào)ML模型的推理,而??PyTorch??? 和??TensorFlow?? 是端到端解決方案,通過(guò)一個(gè)安裝包的形式來(lái)提供數(shù)據(jù)處理、模型訓(xùn)練/驗(yàn)證和高效推理。
注意:PyTorch和TensorFlow也有各自的輕量級(jí)推理擴(kuò)展,即??ExecuTorch???和??TensorFlowLite??。
僅考慮模型的推理階段,llama.cpp的實(shí)現(xiàn)是輕量的,因?yàn)樗鼪](méi)有第三方依賴項(xiàng),并且自動(dòng)支持大量可用的運(yùn)算符或模型格式。此外,顧名思義,該項(xiàng)目最初是一個(gè)用于推斷LLM(來(lái)自Meta的Llama模型)的高效庫(kù),并繼續(xù)支持廣泛的開源LLM架構(gòu)。
如果把PyTorch/TensorFlow比作是豪華、耗電的游輪的話,那么llama.cpp就是小型、快速的摩托艇。PyTorch/TF和llama.cpp都有各自的使用場(chǎng)景。
設(shè)置
我們?cè)诨贚inux的環(huán)境(本機(jī)或WSL環(huán)境)中進(jìn)行開發(fā);為此,需要安裝cmake和GNU/clang工具鏈。我們將從源代碼編譯llama.cpp,并將其作為共享庫(kù)添加到我們的可執(zhí)行聊天程序中。
首先,我們創(chuàng)建一個(gè)項(xiàng)目目錄smol_chat,并使用一個(gè)externals目錄來(lái)存儲(chǔ)克隆自原項(xiàng)目的llama.cpp存儲(chǔ)庫(kù)。
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是我們定義構(gòu)建項(xiàng)目方案的文件,通過(guò)引用來(lái)自externals/llama.cpp的標(biāo)準(zhǔn)頭文件和共享庫(kù),允許CMake使用默認(rèn)工具鏈(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
)
加載模型
現(xiàn)在,我們已經(jīng)定義了如何通過(guò)CMake構(gòu)建我們的項(xiàng)目。接下來(lái),我們創(chuàng)建一個(gè)頭文件LLMInference.h,它聲明了一個(gè)包含高級(jí)函數(shù)的類,用于與LLM交互。llama.cpp提供了一個(gè)C樣式的API,因此將其嵌入到類中將有助于我們抽象/隱藏內(nèi)部工作細(xì)節(jié)。
#ifndef LLMINFERENCE_H
#define LLMINFERENCE_H
#include "common.h"
#include "llama.h"
#include <string>
#include <vector>
class LLMInference {
// llama.cpp特定的數(shù)據(jù)類型
llama_context* _ctx;
llama_model* _model;
llama_sampler* _sampler;
llama_batch _batch;
llama_token _currToken;
// 用于在聊天程序中存儲(chǔ)用戶/助手信息的容器
std::vector<llama_chat_message> _messages;
//將聊天模板應(yīng)用于所有消息后生成的字符串存儲(chǔ)在“_messages”中
std::vector<char> _formattedMessages;
// 將最后查詢的標(biāo)記存儲(chǔ)到“_messages”中
std::vector<llama_token> _promptTokens;
int _prevLen = 0;
// 存儲(chǔ)給定查詢的完整響應(yīng)
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
上面頭文件中聲明的私有成員將用于實(shí)現(xiàn)本文后續(xù)部分中描述的公共成員函數(shù)。首先,讓我們?cè)贚LMInference.cpp中定義每個(gè)成員函數(shù)。
#include "LLMInference.h"
#include <cstring>
#include <iostream>
void LLMInference::loadModel(const std::string& model_path, float min_p, float temperature) {
//創(chuàng)建一個(gè)llama_model的實(shí)例
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");
}
//創(chuàng)建 llama_context 實(shí)例
llama_context_params ctx_params = llama_context_default_params();
ctx_params.n_ctx = 0; // 從模型 GGUF 文件中獲取上下文大小
ctx_params.no_perf = true; // 禁用性能指標(biāo)
_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; // 禁用性能指標(biāo)
_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從文件內(nèi)部讀取模型,并使用給定的llama_model_params填充llama_model實(shí)例。用戶可以提供參數(shù),但我們可以使用llama_model_default_params獲取預(yù)初始化的默認(rèn)結(jié)構(gòu)。
llama_context表示加載的GGUF模型的執(zhí)行環(huán)境。llama_new_context_with_model實(shí)例化新的llama_context,并通過(guò)讀取llama_model_params或自動(dòng)檢測(cè)可用的后端來(lái)準(zhǔn)備執(zhí)行的后端。它還初始化K-V緩存,這在解碼或推理步驟中是很重要的。管理跨多個(gè)后端的計(jì)算的后端調(diào)度程序也被初始化。
llama_sampler決定了我們?nèi)绾螐哪P停ㄌ貏e是LLM的解碼器)的輸出(logits)得出概率分布中的采樣/選擇標(biāo)記。LLM為詞匯表中存在的每個(gè)標(biāo)記分配一個(gè)概率,表示該標(biāo)記出現(xiàn)在序列中的下一個(gè)概率。我們使用llama_sampler_init_temp和llama_sampler_init_min_p設(shè)置的溫度和min-p是控制標(biāo)記采樣過(guò)程的兩個(gè)參數(shù)。
執(zhí)行推理
推理過(guò)程涉及多個(gè)步驟,該過(guò)程將用戶的文本查詢作為輸入并返回LLM的響應(yīng)。
1. 將聊天模板應(yīng)用于查詢
對(duì)于LLM,傳入消息被歸類為屬于三個(gè)角色,即用戶、助手和系統(tǒng)。其中,用戶和助手消息分別由用戶和LLM給出,而系統(tǒng)表示整個(gè)對(duì)話中遵循的系統(tǒng)范圍提示。每條消息都由角色和內(nèi)容組成,其中內(nèi)容是實(shí)際文本,角色是三個(gè)角色中的任何一個(gè)。
<example>
系統(tǒng)提示是對(duì)話的第一條消息。在我們的代碼中,消息存儲(chǔ)為名為_messages的std::vector<llama_chat_message>。其中,llama_chat_message是具有角色和內(nèi)容屬性的llama.cpp結(jié)構(gòu)。我們使用llama.cpp中的llama_chat_apply_template函數(shù)將存儲(chǔ)在GGUF文件中的聊天模板應(yīng)用為元數(shù)據(jù)。我們將應(yīng)用聊天模板后獲得的字符串或std::vector<char>存儲(chǔ)在_formattedMessages中。
2. 標(biāo)記化
標(biāo)記化是將給定文本劃分為較小部分(標(biāo)記)的過(guò)程。我們?yōu)槊總€(gè)部分/標(biāo)記分配一個(gè)唯一的整數(shù)ID,從而將輸入文本轉(zhuǎn)換為整數(shù)序列,形成LLM的輸入。llama.cpp提供common_tokenize或llama_tokenize函數(shù)來(lái)執(zhí)行標(biāo)記化,其中common_tokenize將標(biāo)記序列作為std::vector<llama_token>返回。
void LLMInference::startCompletion(const std::string& query) {
addChatMessage(query, "user");
// 應(yīng)用聊天模板
int new_len = llama_chat_apply_template(
_model,
nullptr,
_messages.data(),
_messages.size(),
true,
_formattedMessages.data(),
_formattedMessages.size()
);
if (new_len > (int)_formattedMessages.size()) {
//調(diào)整輸出緩沖區(qū) `_formattedMessages`的大小并重新應(yīng)用聊天模板
_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);
// 標(biāo)記化
_promptTokens = common_tokenize(_model, prompt, true, true);
// 創(chuàng)建一個(gè)包含單個(gè)序列的llama_batch
// see llama_batch_init for more details
_batch.token = _promptTokens.data();
_batch.n_tokens = _promptTokens.size();
}
在上面代碼中,我們應(yīng)用聊天模板并在LLMInference::startCompletion方法中執(zhí)行標(biāo)記化,然后創(chuàng)建一個(gè)llama_batch實(shí)例來(lái)保存模型的最終輸入。
3. 解碼、采樣和KV緩存
如前所述,LLM通過(guò)連續(xù)預(yù)測(cè)給定序列中的下一個(gè)標(biāo)記來(lái)生成響應(yīng)。LLM還經(jīng)過(guò)訓(xùn)練以預(yù)測(cè)特殊的生成結(jié)束(EOG)標(biāo)記,指示預(yù)測(cè)標(biāo)記序列的結(jié)束。completion_loop函數(shù)返回序列中的下一個(gè)標(biāo)記,并不斷被調(diào)用,直到它返回的標(biāo)記是EOG標(biāo)記。
- 通過(guò)llama_n_ctx和llama_get_kv_cached_used_cells,我們可以確定用于存儲(chǔ)輸入的上下文的長(zhǎng)度。目前,如果標(biāo)記化輸入的長(zhǎng)度超過(guò)上下文大小的話,我們會(huì)拋出一個(gè)錯(cuò)誤。
- llama_decode根據(jù)變量_batch中的輸入信息對(duì)模型進(jìn)行前向傳遞。
- 通過(guò)在LLMInference::loadModel中初始化的_sampler,我們抽樣或選擇一個(gè)標(biāo)記作為我們的預(yù)測(cè)并將其存儲(chǔ)在_currToken中。我們檢查該標(biāo)記是否為EOG標(biāo)記,然后返回“EOG”,表示應(yīng)終止調(diào)用LLMInference::completionLoop的文本生成循環(huán)。終止時(shí),我們將一條新消息附加到_messages,這是具有角色assistant的LLM給出的完整響應(yīng)信息。
- _currToken仍然是一個(gè)整數(shù),它由common_token_to_piece函數(shù)轉(zhuǎn)換為字符串標(biāo)記片段。此字符串標(biāo)記從finishLoop方法返回。
- 我們需要重新初始化_batch以確保它現(xiàn)在僅包含_currToken而不是整個(gè)輸入序列,即_promptTokens。這是因?yàn)樗邢惹皹?biāo)記的“鍵”和“值”都已緩存。通過(guò)避免計(jì)算_promptTokens中所有標(biāo)記的所有“鍵”和“值”,可以減少推理時(shí)間。
std::string LLMInference::completionLoop() {
// 檢查模型輸入的長(zhǎng)度是否超出了模型的上下文大小
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);
}
//運(yùn)行模型
if (llama_decode(_ctx, _batch) < 0) {
throw std::runtime_error("llama_decode() failed");
}
// 采樣一個(gè)標(biāo)記并檢查它是否是EOG(生成結(jié)束標(biāo)記)
// 將整數(shù)標(biāo)記轉(zhuǎn)換為其對(duì)應(yīng)的單詞片段
_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);
// 使用新預(yù)測(cè)的標(biāo)記重新初始化批次
// 所有先前標(biāo)記的鍵值對(duì)都已緩存在KV緩存中
_batch.token = &_currToken;
_batch.n_tokens = 1;
return piece;
}
- 此外,對(duì)于用戶的每個(gè)查詢,LLM將整個(gè)標(biāo)記化對(duì)話(存儲(chǔ)在_messages中的所有消息)作為輸入。如果我們每次都在startCompletion方法中標(biāo)記整個(gè)對(duì)話,那么隨著對(duì)話變長(zhǎng),預(yù)處理時(shí)間和總體推理時(shí)間將會(huì)增加。
- 為了避免這種計(jì)算,我們只需要標(biāo)記添加到_messages的最新消息/查詢。_formattedMessages中消息被標(biāo)記的長(zhǎng)度存儲(chǔ)在_prevLen中。在響應(yīng)生成結(jié)束時(shí),即在LLMInference::stopCompletion中,我們通過(guò)將LLM的響應(yīng)附加到_messages并使用llama_chat_apply_template的返回值來(lái)更新_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");
}
}
編寫析構(gòu)函數(shù)
我們?cè)赺messages和llama.cpp內(nèi)部實(shí)現(xiàn)了一個(gè)析構(gòu)函數(shù)方法來(lái)釋放動(dòng)態(tài)分配的對(duì)象。
LLMInference::~LLMInference() {
//釋放消息中消息文本所占用的內(nèi)存(因?yàn)槲覀円咽褂胹trdup()創(chuàng)建了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應(yīng)用程序
我們創(chuàng)建了一個(gè)小型接口程序,允許我們與LLM進(jìn)行轉(zhuǎn)換。核心工作包括實(shí)例化LLMInference類并調(diào)用我們?cè)谇懊娌糠种卸x的所有方法。
#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;
}
運(yùn)行示例程序
我們使用前面幾節(jié)中編寫的CMakeLists.txt文件。這個(gè)文件用于創(chuàng)建一個(gè)Makefile,該文件將編譯代碼并創(chuàng)建一個(gè)可供使用的可執(zhí)行文件。
mkdir build
cd build
cmake ..
make
./chat
輸出結(jié)果如下:
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
結(jié)論
llama.cpp簡(jiǎn)化了大型語(yǔ)言模型的部署,使其可以在各種設(shè)備和使用場(chǎng)景中訪問(wèn)。本文中,我們通過(guò)介紹這個(gè)框架的內(nèi)部結(jié)構(gòu)并構(gòu)建一個(gè)簡(jiǎn)單的C++推理程序,展示了開發(fā)人員應(yīng)該如何利用其低級(jí)函數(shù)來(lái)實(shí)現(xiàn)高性能但資源受限的應(yīng)用程序。本文不僅介紹了llama.cpp框架的核心架構(gòu),還強(qiáng)調(diào)了它在實(shí)際項(xiàng)目中的實(shí)用性,從而實(shí)現(xiàn)了與LLM的高效率設(shè)備交互。
對(duì)于有興趣突破LLM部署界限或旨在構(gòu)建強(qiáng)大應(yīng)用程序的開發(fā)人員來(lái)說(shuō),掌握l(shuí)lama.cpp等工具將打開無(wú)限可能的大門。在你進(jìn)一步探索時(shí),請(qǐng)記住,你可以進(jìn)一步擴(kuò)展這些基礎(chǔ)知識(shí),以便集成高級(jí)功能、優(yōu)化性能并適應(yīng)不斷發(fā)展的AI應(yīng)用場(chǎng)景。
最后,我希望本文能夠提供一些有用的信息,并讓你對(duì)直接在C++環(huán)境中運(yùn)行LLM感到著迷。
譯者介紹
朱先忠,51CTO社區(qū)編輯,51CTO專家博客、講師,濰坊一所高校計(jì)算機(jī)教師,自由編程界老兵一枚。
原文標(biāo)題:??llama.cpp: Writing A Simple C++ Inference Program for GGUF LLM Models??,作者:Shubham Panchal
