Фото від 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--------------------------------)
Зміст
- Про llama.cpp
- Налаштування
- Завантаження моделі
- Виконання виведення
- Хороші звички: написання деструктора
- Запуск аплікації
Про 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