llama.cpp: Написання простої програми для інференсу на C++ для моделей GGUF LLM

pic

Фото від Mathew Schwartz на Unsplash

llama.cpp революціонізував область виведення LLM (моделей великого мовного ядра) завдяки широкому впровадженню та простоті. Це дозволило підприємствам і окремим розробникам розгортати LLM на пристроях від SBC до багатокарткових кластерах GPU. Хоча робота з llama.cpp спрощена за допомогою його мовних зв’язків, використання C/C++ може бути доцільним вибором для продуктивних або ресурсно обмежених сценаріїв.

Цей посібник має на меті детально розглянути, як виконуються виведення LLM за допомогою низькорівневих функцій, що безпосередньо походять від llama.cpp. Ми обговоримо потік програми, конструкції llama.cpp і в кінці зробимо простий чат.

C++ код, який ми напишемо в цьому блозі, також використовується в SmolChat, рідній Android-аплікації, яка дозволяє користувачам взаємодіяти з LLM/SLM в інтерфейсі чату повністю на пристрої. Зокрема, клас LLMInference, який ми визначимо далі, використовується з прив'язкою JNI для виконання моделей GGUF.

[

GitHub: shubham0204/SmolChat-Android: Запуск будь-яких GGUF SLM/LLM локально, на пристрої в Android

Запуск будь-яких GGUF SLM/LLM локально, на пристрої в Android (shubham0204/SmolChat-Android)

github.com

](https://github.com/shubham0204/SmolChat-Android?source=post_page-----12bc5f58505f--------------------------------)

Код для цього посібника можна знайти тут:

[

shubham0204/llama.cpp-simple-chat-interface

Сприяйте розвитку shubham0204/llama.cpp-simple-chat-interface, створивши обліковий запис на GitHub.

github.com

](https://github.com/shubham0204/llama.cpp-simple-chat-interface?source=post_page-----12bc5f58505f--------------------------------)

Зміст

  1. Про llama.cpp
  2. Налаштування
  3. Завантаження моделі
  4. Виконання виведення
  5. Хороші звички: написання деструктора
  6. Запуск аплікації

Про llama.cpp

llama.cpp — це C/C++ фреймворк для виконання моделей машинного навчання, визначених у форматі GGUF, на різних бекендах виконання. Він почався як чиста C/C++ реалізація відомих моделей Llama від Meta, які можна виконувати на чіпах Apple, AVX/AVX-512, CUDA та Arm Neon-сумісних середовищах. Також включає інструмент на основі CLI llama-cli для запуску моделей GGUF LLM та llama-server для виконання моделей через HTTP-запити (сервер, сумісний з OpenAI).

llama.cpp використовує ggml, низькорівневий фреймворк, що надає примітивні функції, необхідні для глибокого навчання, і абстрагує деталі реалізації бекенду від користувача. Georgi Gerganov є автором ggml та llama.cpp.

README репозиторію також містить список обгорток, побудованих на основі llama.cpp на інших мовах програмування. Популярні інструменти, такі як Ollama та LM Studio, також використовують зв'язки з llama.cpp для покращення зручності користування.
Проєкт не має залежностей від інших сторонніх бібліотек

Чим llama.cpp відрізняється від PyTorch/TensorFlow?

llama.cpp має акцент на виведення ML моделей з самого початку, тоді як PyTorch та TensorFlow є рішеннями «від початку до кінця», які пропонують обробку даних, тренування/валідацію моделей та ефективне виведення в одному пакеті.

PyTorch і TensorFlow також мають свої легкі розширення лише для виведення, а саме ExecuTorch та TensorFlow Lite

Якщо розглядати лише фазу виведення моделі, llama.cpp є легким у реалізації завдяки відсутності сторонніх залежностей та великого набору доступних операторів або форматів моделей для підтримки. Крім того, як і передбачає назва, проєкт почався як ефективна бібліотека для виведення LLM (моделі Llama від Meta) і продовжує підтримувати широкий спектр відкритих архітектур LLM.

Аналогія: Якщо PyTorch/TensorFlow — це розкішні, енергоємні круїзні кораблі, то llama.cpp — це маленький, швидкий моторний човен. PyTorch/TF і llama.cpp мають свої власні варіанти використання.

Налаштування

Ми почнемо реалізацію в середовищі на основі Linux (рідному або WSL) з встановленим cmake і інструментами GNU/clang. Ми скомпілюємо llama.cpp з вихідного коду і додамо його як спільну бібліотеку до нашої виконуваної програми chat.

Створюємо директорію проєкту 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 — це файл, в якому ми визначаємо, як має будуватися наш проєкт, дозволяючи CMake компілювати наш C/C++ код за допомогою стандартного інструментального комплексу (GNU/clang), включаючи заголовочні файли та спільні бібліотеки з externals/llama.cpp.

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 надає API в стилі C, тому вбудовування його в клас допоможе нам абстрагувати/сховати деталі внутрішньої реалізації.

#ifndef LLMINFERENCE_H  
#define LLMINFERENCE_H  

#include "common.h"  
#include "llama.h"  
#include   
#include   

class LLMInference {  

 // специфічні типи llama.cpp  
 llama_context* _ctx;  
 llama_model* _model;  
 llama_sampler* _sampler;  
 llama_batch _batch;  
 llama_token _currToken;  

 // контейнер для зберігання повідомлень користувача/асистента в чаті  
 std::vector _messages;  
 // зберігає рядок, згенерований після застосування  
 // шаблону чату до всіх повідомлень у `_messages`  
 std::vector _formattedMessages;  
 // зберігає токени для останнього запиту  
 // доданого до `_messages`  
 std::vector _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   
#include   

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() не вдалося");  
 }  

 // створення екземпляра 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() повернув 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(llama_n_ctx(_ctx));  
 _messages.clear();  
}

llama_load_model_from_file зчитує модель з файлу, використовуючи llama_load_model всередині, та заповнює екземпляр llama_model за допомогою наданих параметрів llama_model_params. Користувач може передати параметри, але ми можемо отримати попередньо ініціалізовану за замовчуванням структуру для них через llama_model_default_params.

llama_context представляє середовище виконання для завантаженої моделі GGUF. Функція llama_new_context_with_model ініціалізує новий llama_context та готує бекенд для виконання, або шляхом читання параметрів llama_model_params, або автоматично визначаючи доступні бекенди. Вона також ініціалізує кеш K-V, що є важливим етапом при декодуванні або виведенні.
Розкладчик бекендів, який управляє обчисленнями через кілька бекендів, також ініціалізується.

[

Оптимізація виведення LLM: Управління кешем KV

Вступ

medium.com

](https://medium.com/@aalokpatwa/optimizing-llm-inference-managing-the-kv-cache-34d961ead936?source=post_page-----12bc5f58505f--------------------------------)

llama_sampler визначає, як ми вибираємо/зразковуємо токени з ймовірнісного розподілу, що отримується з виходів (логітів) моделі (зокрема, декодера LLM). LLM призначає ймовірність кожному токену, що міститься в словнику, що представляє шанси появи токена наступним у послідовності. Температура та min-p, які ми налаштовуємо за допомогою llama_sampler_init_temp та llama_sampler_init_min_p, є двома параметрами, що контролюють процес вибору токенів.

[

Налаштування Top-K, Top-P та температури в LLM

Оволодіння Top-K, Top-P та температурою: керуйте LLM, такими як ChatGPT! Дізнайтеся, як ці налаштування впливають на виходи та оптимізують...

rumn.medium.com

](https://rumn.medium.com/setting-top-k-top-p-and-temperature-in-llms-3da3a8f74832?source=post_page-----12bc5f58505f--------------------------------)

Виконання виведення

Існує кілька етапів в процесі виведення, який приймає текстовий запит від користувача як вхід і повертає відповідь LLM.

1. Застосування шаблону чату до запитів

Для LLM вхідні повідомлення класифікуються як належні до трьох ролей: user , assistant та system . Повідомлення user та assistant надаються користувачем і LLM відповідно, в той час як system позначає системний запит, який слідується протягом усього чату. Кожне повідомлення складається з role та content, де content — це фактичний текст, а role — одна з трьох ролей.


Системний запит — це перше повідомлення в чаті. У нашому коді повідомлення зберігаються в std::vector, який називається _messages, де llama_chat_message є struct з llama.cpp з атрибутами role та content. Ми використовуємо функцію llama_chat_apply_template з llama.cpp, щоб застосувати шаблон чату, що зберігається у файлі GGUF як метадані. Отриманий рядок або std::vector, що утворився після застосування шаблону чату, зберігається в _formattedMessages .

2. Токенізація

Токенізація — це процес поділу заданого тексту на менші частини (токени). Кожній частині/токену присвоюється унікальний ідентифікатор ціле число, таким чином, перетворюючи вхідний текст на послідовність цілих чисел, які формують вхід для LLM.
llama.cpp надає функції common_tokenize або llama_tokenize для виконання токенізації, де common_tokenize повертає послідовність токенів у вигляді std::vector .

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() не вдалося");  
 }  
 std::string prompt(_formattedMessages.begin() + _prevLen, _formattedMessages.begin() + new_len);  

 // токенізація  
 _promptTokens = common_tokenize(_model, prompt, true, true);  

 // створюємо llama_batch, що містить одну послідовність  
 // див. llama_batch_init для деталей  
 _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 .
  • Використовуючи _sampler, ініціалізований в LLMInference::loadModel, ми зразковуємо або вибираємо токен як наше передбачення та зберігаємо його в _currToken . Ми перевіряємо, чи є цей токен EOG токеном, і тоді повертаємо "EOG", що означає, що цикл генерації тексту, що викликає LLMInference::completionLoop, має бути припинений. Після завершення ми додаємо нове повідомлення до _messages, яке є повною відповіддю _response, наданою LLM з роллю assistant .
  • _currToken все ще є цілим числом, яке перетворюється на рядковий токен piece за допомогою функції common_token_to_piece. Цей рядковий токен повертається з методу completionLoop.
  • Нам потрібно повторно ініціалізувати _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 << "розмір контексту перевищено" << '\n';  
 exit(0);  
 }  
 // запускаємо модель  
 if (llama_decode(_ctx, _batch) < 0) {  
 throw std::runtime_error("llama_decode() не вдалося");  
 }  

 // зразковуємо токен і перевіряємо, чи є він 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, ми оновлюємо значення _prevLen, додаючи відповідь LLM до _messages і використовуючи значення, що повертається від llama_chat_apply_template .
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() не вдалося");  
 }  
}

Хороші звички: написання деструктора

Ми реалізуємо метод деструктора, який звільняє динамічно виділені об'єкти, як у _messages, так і внутрішньо в llama.cpp.

LLMInference::~LLMInference() {  
 // звільняємо пам'ять, що утримується текстом повідомлень в messages  
 // (оскільки ми використовували 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   
#include   

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 = std::make_unique();  
 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: зареєстровано бекенд CPU (1 пристрій)  
register_device: зареєстровано пристрій CPU (11th Gen Intel(R) Core(TM) i3-1115G4 @ 3.00GHz)  
llama_model_loader: завантажено метадані з 33 пар ключ-значення та 290 тензорів з /home/shubham/CPP_Projects/llama-cpp-inference/models/smollm2-360m-instruct-q8_0.gguf (версія GGUF V3 (остання))  
llama_model_loader: Виведення метаданих ключів/значень. Примітка: переозначення KV не застосовуються в цьому виведенні.  
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 '' не позначено як EOG  
llm_load_vocab: control token: 13 '' не позначено як EOG  
llm_load_vocab: control token: 16 '' не позначено як EOG  
llm_load_vocab: control token: 11 '' не позначено як EOG  
llm_load_vocab: control token: 10 '' не позначено як EOG  
llm_load_vocab: control token: 6 '' не позначено як EOG  
llm_load_vocab: control token: 8 '' не позначено як EOG  
llm_load_vocab: control token: 3 '' не позначено як EOG  
llm_load_vocab: control token: 12 '' не позначено як EOG  
llm_load_vocab: control token: 15 '' не позначено як EOG  
llm_load_vocab: control token: 4 '' не позначено як EOG  
llm_load_vocab: control token: 1 '<|im_start|>' не позначено як EOG  
llm_load_vocab: control token: 9 '' не позначено як EOG  
llm_load_vocab: control token: 5 '' не позначено як EOG  
llm_load_vocab: control token: 14 '' не позначено як EOG  
llm_load_vocab: розмір кешу спеціальних токенів = 17  
llm_load_vocab: розмір кешу токенів у відповідні частини = 0.3170 MB  
llm_load_print_meta: формат = GGUF V3 (остання версія)  
llm_load_print_meta: архітектура = llama  
llm_load_print_meta: тип словника = 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: максимальна довжина токену = 162  
llm_load_tensors: розмір ggml ctx = 0.14 MiB  
llm_load_tensors: розмір CPU буферу = 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, відкриває величезні можливості. Вивчаючи далі, пам'ятайте, що ці базові знання можна розширювати для інтеграції передових функцій, оптимізації продуктивності та адаптації до нових варіантів використання штучного інтелекту.

Сподіваюся, що цей посібник був інформативним і залишив вас захопленими можливістю запускати LLM на C++ безпосередньо. Діліться своїми пропозиціями та питаннями в коментарях нижче, вони завжди будуть оцінені. Щасливого навчання та чудового дня!

Перекладено з: llama.cpp: Writing A Simple C++ Inference Program for GGUF LLM Models

Leave a Reply

Your email address will not be published. Required fields are marked *