Привет,
давно хотел написать о популярном движке, или бэкенде, да запуска LLM моделей- LLama.cpp
https://en.wikipedia.org/wiki/Llama.cpp
https://github.com/ggml-org/llama.cpp
LLama.cpp используется во множестве приложений, таких как:
LM Studio
https://lmstudio.ai
https://github.com/lmstudio-ai
Ollama
https://ollama.com
https://github.com/ollama/ollama
GPT4All
https://www.nomic.ai/gpt4all
https://github.com/nomic-ai/gpt4all
и других.
LLama.cpp работает с форматом AI LLM моделей .gguf
https://huggingface.co/docs/hub/gguf
в этом формате можно найти все популярные модели на HuggingFace
https://huggingface.co/models?library=gguf&sort=trending
Сегодня постараюсь показать, как он работает и что в нем происходит.
LLama.cpp как понятно из названия написана на C++, и здесь буду приводить методы на этом языке и постараюсь объяснить, что в них происходит.
Понятно что не все знакомы с С++, но если плюс минус понимаете С подобный синтаксис, должно быть понятно.
Здесь идея в том, чтобы понять общие принципы, как все работает, что примерно происходит.
Не важные или повторяющиеся куски кода я убрал.
Буду писать методы по мере их вызова при инициализации модели и генерации текста.
Загрузка модели
// Файл llama-model.cpp void llama_backend_init(void) { // Включает систему замера времени внутри ggml (особенно важно для Windows). // Это нужно для точных измерений времени работы модели, например, чтобы узнать, сколько занял инференс. ggml_time_init(); { // Создаём параметры для инициализации "рабочего окружения" (контекста) ggml. // 0 — не выделяем память под тензоры, NULL — не передаём свой буфер, false — не используем многопоточность. struct ggml_init_params params = { 0, NULL, false }; // Инициализируем временный контекст ggml. // Он нужен, чтобы подготовить внутренние таблицы и вспомогательные структуры, например, для работы с 16-битными числами (float16). // Это делается один раз и ускоряет работу модели в будущем. struct ggml_context * ctx = ggml_init(params); // Сразу удаляем временный контекст, так как он больше не нужен. // Подготовленные таблицы уже сохранены глобально и будут использоваться дальше. ggml_free(ctx); } }
long load_model(auto path_to_model) { // Получаем параметры по умолчанию для загрузки модели. llama_model_params model_params = llama_model_default_params(); // Загружаем модель из файла по указанному пути, используя заданные параметры. auto model = llama_model_load_from_file(path_to_model, model_params); // Освобождаем временно выделенную C-строку после использования. env->ReleaseStringUTFChars(filename, path_to_model); // Возвращаем указатель на модель как целое число (jlong), чтобы Java могла использовать его как "дескриптор". return reinterpret_cast<jlong>(model); }
// Файл llama.cpp static struct llama_model * llama_model_load_from_file_impl(const std::string & path_model, std::vector<std::string> & splits, struct llama_model_params params) { // Инициализирует систему измерения времени (важно для профилирования и корректной работы в Windows). ggml_time_init(); // Создаёт объект модели, в который будет загружена информация из файла. llama_model * model = new llama_model(params); // Настраивает список устройств, на которых будет работать модель. if (params.devices) { // Если устройства явно указаны — добавляет их. for (ggml_backend_dev_t * dev = params.devices; *dev; ++dev) { model->devices.push_back(*dev); } } else { std::vector<ggml_backend_dev_t> rpc_servers; // Если устройства не указаны — перебирает все доступные устройства (GPU, RPC и т. д.). for (size_t i = 0; i < ggml_backend_dev_count(); ++i) { ggml_backend_dev_t dev = ggml_backend_dev_get(i); switch (ggml_backend_dev_type(dev)) { case GGML_BACKEND_DEVICE_TYPE_CPU: case GGML_BACKEND_DEVICE_TYPE_ACCEL: // Пропускает CPU и акселераторы — они обрабатываются отдельно. break; case GGML_BACKEND_DEVICE_TYPE_GPU: // Если устройство — GPU, определяет, является ли оно RPC-сервером. ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(dev); if (ggml_backend_reg_name(reg) == std::string("RPC")) { rpc_servers.push_back(dev); } else { model->devices.push_back(dev); } break; } } // Сначала добавляет RPC-серверы, если они есть (для удалённой загрузки модели). if (!rpc_servers.empty()) { model->devices.insert(model->devices.begin(), rpc_servers.begin(), rpc_servers.end()); } } // Если включён режим "одного GPU", оставляет только указанную видеокарту (main_gpu). if (params.split_mode == LLAMA_SPLIT_MODE_NONE) { if (params.main_gpu < 0) { // Если не указана видеокарта — очищает список устройств (будет использоваться CPU). model->devices.clear(); } else { // Проверяет корректность указанного GPU. if (params.main_gpu >= (int)model->devices.size()) { LLAMA_LOG_ERROR("%s: invalid value for main_gpu: %d (available devices: %zu)\n", __func__, params.main_gpu, model->devices.size()); llama_model_free(model); return nullptr; } // Оставляет только выбранный GPU. ggml_backend_dev_t main_gpu = model->devices[params.main_gpu]; model->devices.clear(); model->devices.push_back(main_gpu); } } // Загружает саму модель из файла (тензоры, веса и т. д.). const int status = llama_model_load(path_model, splits, *model, params); // Если всё успешно — возвращает загруженную модель. return model; }
// Файл llama.cpp static int llama_model_load(const std::string & fname, std::vector<std::string> & splits, llama_model & model, llama_model_params & params) { // Обнуляем время загрузки (оно будет рассчитано позже, при первом запуске модели). model.t_load_us = 0; // Запускаем таймер, чтобы измерить общее время загрузки модели (включая подгрузку из памяти). time_meas tm(model.t_load_us); // Сохраняем время начала загрузки (используется для логирования и статистики). model.t_start_us = tm.t_start_us; try { // Создаём объект загрузчика модели — он читает данные из файла, учитывая mmap, проверки и переопределения. llama_model_loader ml(fname, splits, params.use_mmap, params.check_tensors, params.kv_overrides, params.tensor_buft_overrides); // Печатаем базовую информацию о загружаемой модели (архив, размер, конфигурация). ml.print_info(); // Указываем, нужно ли загружать только словарь (без весов и тензоров). model.hparams.vocab_only = params.vocab_only; // Загружаем архитектуру модели (например, количество слоёв, тип attention и т.д.). try { model.load_arch(ml); } catch(const std::exception & e) { throw std::runtime_error("error loading model architecture: " + std::string(e.what())); } // Загружаем гиперпараметры модели (размеры слоёв, embedding и прочее). try { model.load_hparams(ml); } catch(const std::exception & e) { throw std::runtime_error("error loading model hyperparameters: " + std::string(e.what())); } // Загружаем словарь (токенизация, ID токенов, специальная лексика). try { model.load_vocab(ml); } catch(const std::exception & e) { throw std::runtime_error("error loading model vocabulary: " + std::string(e.what())); } // Загружаем метаинформацию: хэши, размеры, контрольные суммы и т.д. model.load_stats(ml); // Печатаем подробности о загруженной модели (после всех этапов). model.print_info(); // Если нужно только загрузить словарь — завершаем на этом (тензоры не читаем). if (params.vocab_only) { LLAMA_LOG_INFO("%s: vocab only - skipping tensors\n", __func__); return 0; } // Загружаем тензоры модели (веса слоёв, embeddings и т. д.). // Если не удалось — возвращаем -2 (ошибка или отмена). if (!model.load_tensors(ml)) { return -2; } } catch (const std::exception & err) { // Если на любом этапе произошла ошибка — выводим сообщение и возвращаем -1. LLAMA_LOG_ERROR("%s: error loading model: %s\n", __func__, err.what()); return -1; } // Если всё прошло успешно — возвращаем 0. return 0; }
// Файл llama-model.cpp // Загружает тензоры модели (веса, bias, embeddings) и распределяет их по устройствам (CPU/GPU) bool llama_model::load_tensors(llama_model_loader & ml) { const auto & split_mode = params.split_mode; // Режим разделения слоёв между устройствами const auto & n_gpu_layers = params.n_gpu_layers; // Сколько слоёв поместить на GPU const auto & use_mlock = params.use_mlock; // Блокировать память от выгрузки в swap const auto & tensor_split = params.tensor_split; // Явное задание долей тензоров по GPU const int n_layer = hparams.n_layer; // Общее количество слоёв в модели LLAMA_LOG_INFO("%s: loading model tensors, this can take a while... (mmap = %s)\n", __func__, ml.use_mmap ? "true" : "false"); // Формирует список доступных типов буферов (памяти) для CPU и GPU pimpl->cpu_buft_list = make_cpu_buft_list(devices); for (auto * dev : devices) { buft_list_t buft_list = make_gpu_buft_list(dev, split_mode, tensor_split); buft_list.insert(buft_list.end(), pimpl->cpu_buft_list.begin(), pimpl->cpu_buft_list.end()); pimpl->gpu_buft_list.emplace(dev, std::move(buft_list)); } // Распределяет объёмы тензоров между устройствами по available memory или tensor_split[] std::vector<float> splits(n_devices()); // ... (подсчёт и нормализация сумм) // Определяет, какие слои отправлять на GPU, а какие оставить на CPU const int i_gpu_start = std::max((int) hparams.n_layer - n_gpu_layers, 0); const int act_gpu_layers = devices.empty() ? 0 : std::min(n_gpu_layers, (int)n_layer + 1); // Присваивает каждому слою устройство (CPU или конкретный GPU), учитывая распределение и режим auto get_layer_buft_list = [&](int il) -> llama_model::impl::layer_dev { if (il < i_gpu_start || (il - i_gpu_start) >= act_gpu_layers) { return {cpu_dev, &pimpl->cpu_buft_list}; } int layer_gpu = std::upper_bound(...) - splits.begin(); return {devices.at(layer_gpu), &pimpl->gpu_buft_list.at(devices.at(layer_gpu))}; }; // Устанавливает устройство и буферы для input, output и каждого слоя pimpl->dev_input = { cpu_dev, &pimpl->cpu_buft_list }; pimpl->dev_output = get_layer_buft_list(n_layer); pimpl->dev_layer.resize(n_layer); for (int il = 0; il < n_layer; ++il) { pimpl->dev_layer[il] = get_layer_buft_list(il); } // Создаёт ggml-контексты для разных типов буферов (каждый контекст управляет своими тензорами) std::map<ggml_backend_buffer_type_t, ggml_context *> ctx_map; auto ctx_for_buft = [&](ggml_backend_buffer_type_t buft) { // ... создаёт новый ggml_context при необходимости }; // Создаёт и размещает тензоры модели (веса, bias и т. д.) auto create_tensor = [&](const LLM_TN_IMPL & tn, const std::initializer_list<int64_t> & ne, int flags) { ggml_tensor * t_meta = ml.get_tensor_meta(tn.str().c_str()); // ... пропускает неиспользуемые, выбирает подходящий буфер (CPU/GPU), создаёт тензор return ml.create_tensor(...); }; // Повторяющиеся слои инициализируются в `layers` layers.resize(n_layer); switch (arch) { // Здесь я приведу пример для свежей модели от Google - Gemma 3n, // но для каждой модели и варианта архитектуры есть свой case, я насчитал в сумме около 80ти case LLM_ARCH_GEMMA3N: { // Извлекаем параметры архитектуры: altup — дополнительные направления, laurel — ранжированные матрицы const int64_t n_altup = hparams.n_altup; const int64_t laurel_rank = hparams.laurel_rank; const int64_t n_embd_altup = hparams.n_embd_altup; // Загружаем выходной слой модели (если не найден — используем входной embedding повторно) output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), {n_embd, n_vocab}, TENSOR_NOT_REQUIRED); if (output == NULL) { output = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, TENSOR_DUPLICATED); } // Загружаем embedding для входных токенов (обычный и по-слойный вариант) tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, 0); tok_embd_per_layer = create_tensor(tn(LLM_TENSOR_PER_LAYER_TOKEN_EMBD, "weight"), {n_embd_altup * n_layer, n_vocab}, 0); // Загружаем специальные матрицы проекций для механизма AltUp (улучшенная активация слоёв) altup_proj = create_tensor(tn(LLM_TENSOR_ALTUP_PROJ, "weight"), {n_embd, n_embd, n_altup - 1}, 0); altup_unembd_proj = create_tensor(tn(LLM_TENSOR_ALTUP_UNEMBD_PROJ, "weight"), {n_embd, n_embd, n_altup - 1}, 0); per_layer_model_proj = create_tensor(tn(LLM_TENSOR_PER_LAYER_MODEL_PROJ, "weight"), {n_embd, n_embd_altup * n_layer}, 0); per_layer_proj_norm = create_tensor(tn(LLM_TENSOR_PER_LAYER_PROJ_NORM, "weight"), {n_embd_altup}, 0); // Загружаем финальную нормализацию для выходного слоя output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd}, 0); // Цикл по каждому слою модели (например, 28 слоёв) for (int i = 0; i < n_layer; ++i) { auto & layer = layers[i]; // Нормализация перед вниманием layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, 0); // Загружаем матрицы для внимания: Q, K, V — запрос, ключ, значение; O — выход layer.wq = create_tensor(tn(LLM_TENSOR_ATTN_Q, "weight", i), {n_embd, n_embd_head_k * n_head}, 0); layer.wk = create_tensor(tn(LLM_TENSOR_ATTN_K, "weight", i), {n_embd, n_embd_k_gqa}, 0); layer.wv = create_tensor(tn(LLM_TENSOR_ATTN_V, "weight", i), {n_embd, n_embd_v_gqa}, 0); layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_head_k * n_head, n_embd}, 0); // Дополнительные нормализации в блоке внимания layer.attn_q_norm = create_tensor(tn(LLM_TENSOR_ATTN_Q_NORM, "weight", i), {n_embd_head_k}, 0); layer.attn_k_norm = create_tensor(tn(LLM_TENSOR_ATTN_K_NORM, "weight", i), {n_embd_head_k}, 0); layer.attn_post_norm = create_tensor(tn(LLM_TENSOR_ATTN_POST_NORM, "weight", i), {n_embd}, 0); // Блок feed-forward (полносвязный слой) layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, 0); layer.ffn_gate = create_tensor(tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd, n_ff}, 0); layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), {n_embd, n_ff}, 0); layer.ffn_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), {n_ff, n_embd}, 0); layer.ffn_post_norm = create_tensor(tn(LLM_TENSOR_FFN_POST_NORM, "weight", i), {n_embd}, 0); // Проекции, специфичные для AltUp и Laurel — улучшенные механизмы маршрутизации и нормализации layer.per_layer_inp_gate = create_tensor(tn(LLM_TENSOR_PER_LAYER_INP_GATE, "weight", i), {n_embd, n_embd_altup}, 0); layer.per_layer_proj = create_tensor(tn(LLM_TENSOR_PER_LAYER_PROJ, "weight", i), {n_embd_altup, n_embd}, 0); layer.per_layer_post_norm = create_tensor(tn(LLM_TENSOR_PER_LAYER_POST_NORM, "weight", i), {n_embd}, 0); layer.altup_correct_coef = create_tensor(tn(LLM_TENSOR_ALTUP_CORRECT_COEF, "weight", i), {n_altup, n_altup}, 0); layer.altup_correct_scale = create_tensor(tn(LLM_TENSOR_ALTUP_CORRECT_SCALE, "weight", i), {n_embd}, 0); layer.altup_predict_coef = create_tensor(tn(LLM_TENSOR_ALTUP_PREDICT_COEF, "weight", i), {n_altup, n_altup * n_altup}, 0); layer.altup_router = create_tensor(tn(LLM_TENSOR_ALTUP_ROUTER, "weight", i), {n_embd, n_altup}, 0); layer.altup_router_norm = create_tensor(tn(LLM_TENSOR_ALTUP_ROUTER_NORM, "weight", i), {n_embd}, 0); // Laurel: обучаемая маршрутизация через две матрицы и нормализацию layer.laurel_l = create_tensor(tn(LLM_TENSOR_LAUREL_L, "weight", i), {n_embd, laurel_rank}, 0); layer.laurel_r = create_tensor(tn(LLM_TENSOR_LAUREL_R, "weight", i), {laurel_rank, n_embd}, 0); layer.laurel_post_norm = create_tensor(tn(LLM_TENSOR_LAUREL_POST_NORM, "weight", i), {n_embd}, 0); } } break; // Выкидываем ошибку, если архитектура не известна default: throw std::runtime_error("unknown architecture"); } // Если хотя бы один тензор пришлось разместить не в том буфере (например, на CPU вместо GPU), логируем это if (n_moved_tensors > 0) { LLAMA_LOG_DEBUG("%s: tensor '%s' (%s) (and %d others) cannot be used with preferred buffer type %s, using %s instead\n", __func__, first_moved_tensor->name, ggml_type_name(first_moved_tensor->type), n_moved_tensors - 1, ggml_backend_buft_name(first_moved_from_buft), ggml_backend_buft_name(first_moved_to_buft)); } // Сообщаем, что все тензоры описаны и готовы к загрузке ml.done_getting_tensors(); // Настраиваем отображения памяти (mmap), возможно с блокировкой (mlock), чтобы не выгружались из RAM ml.init_mappings(true, use_mlock ? &pimpl->mlock_mmaps : nullptr); pimpl->mappings.reserve(ml.mappings.size()); // Готовим к созданию буферов под веса модели std::vector<std::pair<ggml_context *, llama_buf_map>> ctx_bufs; ctx_bufs.reserve(ctx_map.size()); const size_t n_max_backend_buffer = ctx_map.size() * ml.files.size(); pimpl->bufs.reserve(n_max_backend_buffer); // Для каждого типа памяти (буфера), в котором создавались тензоры for (auto & it : ctx_map) { ggml_backend_buffer_type_t buft = it.first; ggml_context * ctx = it.second; // Пропускаем контексты, в которых нет тензоров if (ggml_get_first_tensor(ctx) == nullptr) { continue; } llama_buf_map buf_map; buf_map.reserve(n_max_backend_buffer); // Определяем, к какому устройству относится этот буфер (например, CPU или GPU) ggml_backend_dev_t dev = ggml_backend_buft_get_device(buft); if (!dev) { // Обход бага: если у CPU-буфера нет устройства, назначаем вручную dev = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU); if (!dev) { throw std::runtime_error(format("%s: no CPU backend found", __func__)); } } // Узнаём возможности устройства (например, поддерживает ли оно загрузку из mmap-буфера) ggml_backend_dev_props props; ggml_backend_dev_get_props(dev, &props); bool buffer_from_host_ptr_supported = props.caps.buffer_from_host_ptr; bool is_default_buft = buft == ggml_backend_dev_buffer_type(dev); // Если можно напрямую отдать mmap-область устройству — создаём буфер из неё if (ml.use_mmap && use_mmap_buffer && buffer_from_host_ptr_supported && is_default_buft) { for (uint32_t idx = 0; idx < ml.files.size(); idx++) { void * addr = nullptr; size_t first, last; ml.get_mapping_range(&first, &last, &addr, idx, ctx); if (first >= last) { continue; } const size_t max_size = ggml_get_max_tensor_size(ctx); ggml_backend_buffer_t buf = ggml_backend_dev_buffer_from_host_ptr(dev, (char *) addr + first, last - first, max_size); if (buf == nullptr) { throw std::runtime_error(format("unable to allocate %s buffer", ggml_backend_buft_name(buft))); } pimpl->bufs.emplace_back(buf); buf_map.emplace(idx, buf); } } else { // Иначе выделяем обычный буфер под все тензоры для этого контекста ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx, buft); if (buf == nullptr) { throw std::runtime_error(format("unable to allocate %s buffer", ggml_backend_buft_name(buft))); } pimpl->bufs.emplace_back(buf); // Если используется блокировка памяти и буфер размещён в RAM — блокируем его от выгрузки системой if (use_mlock && ggml_backend_buffer_is_host(buf)) { pimpl->mlock_bufs.emplace_back(new llama_mlock); auto & mlock_buf = pimpl->mlock_bufs.back(); mlock_buf->init (ggml_backend_buffer_get_base(buf)); mlock_buf->grow_to(ggml_backend_buffer_get_size(buf)); } // Один и тот же буфер используется для всех частей файла (если без mmap) for (uint32_t idx = 0; idx < ml.files.size(); idx++) { buf_map.emplace(idx, buf); } } // Проверка на случай, если ни один буфер не был создан if (pimpl->bufs.empty()) { throw std::runtime_error("failed to allocate buffer"); } // Устанавливаем флаг, что буфер содержит веса (важно для планировщика операций в ggml) for (auto & buf : buf_map) { ggml_backend_buffer_set_usage(buf.second, GGML_BACKEND_BUFFER_USAGE_WEIGHTS); } // Связываем контекст с его буферами ctx_bufs.emplace_back(ctx, buf_map); } // Если используется GPU, логируем, сколько слоёв перенесено на него if (llama_supports_gpu_offload()) { const int n_gpu = std::min(n_gpu_layers, int(hparams.n_layer)); LLAMA_LOG_INFO("%s: offloading %d repeating layers to GPU\n", __func__, n_gpu); if (n_gpu_layers > (int) hparams.n_layer) { LLAMA_LOG_INFO("%s: offloading output layer to GPU\n", __func__); } LLAMA_LOG_INFO("%s: offloaded %d/%d layers to GPU\n", __func__, std::min(n_gpu_layers, hparams.n_layer + 1), hparams.n_layer + 1); } // Выводим размер каждого выделенного буфера (например, сколько памяти занято на GPU) for (auto & buf : pimpl->bufs) { LLAMA_LOG_INFO("%s: %12s model buffer size = %8.2f MiB\n", __func__, ggml_backend_buffer_name(buf.get()), ggml_backend_buffer_get_size(buf.get()) / 1024.0 / 1024.0); } // Собираем все тензоры в map по имени, чтобы можно было к ним быстро обращаться for (auto & ctx : pimpl->ctxs) { for (auto * cur = ggml_get_first_tensor(ctx.get()); cur != NULL; cur = ggml_get_next_tensor(ctx.get(), cur)) { tensors_by_name.emplace_back(ggml_get_name(cur), cur); } } // Загружаем бинарные данные всех тензоров в выделенные буферы for (auto & it : ctx_bufs) { ggml_context * ctx = it.first; auto & bufs = it.second; if (!ml.load_all_data(ctx, bufs, use_mlock ? &pimpl->mlock_mmaps : NULL, params.progress_callback, params.progress_callback_user_data)) { return false; } } // Если используется mmap, сохраняем все отображения файла if (use_mmap_buffer) { for (auto & mapping : ml.mappings) { pimpl->mappings.emplace_back(std::move(mapping)); } } // Успешная загрузка всех тензоров завершена return true; }
long new_context( jlong jmodel, jint jctx_size, jint n_threads, jint n_threads_batch, jint availableRamThresholdMb, jobject lowRamCallback ) { // Преобразуем числовой идентификатор модели обратно в указатель, чтобы работать с загруженной моделью. auto model = reinterpret_cast<llama_model *>(jmodel); // Берём стандартные настройки для создания нового рабочего контекста модели. llama_context_params ctx_params = llama_context_default_params(); // Указываем, на сколько токенов назад модель может "оглядываться", то есть видеть в текущем контексте. ctx_params.n_ctx = static_cast<uint32_t>(jctx_size); // Устанавливаем количество потоков, с помощью которых модель будет выполнять основные вычисления. ctx_params.n_threads = n_threads; // Указываем число потоков, которые будут использоваться при обработке входных данных в виде пакета токенов. ctx_params.n_threads_batch = n_threads_batch; // Создаём рабочее окружение (контекст), где модель будет выполнять генерацию текста с учётом всех указанных параметров. llama_context *context = llama_new_context_with_model(model, ctx_params); // Возвращаем адрес созданного контекста как число, чтобы его можно было использовать или передать дальше. return reinterpret_cast<jlong>(context); }
long new_batch( jint n_tokens, // сколько токенов мы хотим передать модели jint embd, // если 0 — передаём просто ID слов, если больше 0 — передаём готовые вектора (эмбеддинги) jint n_seq_max, // максимальное количество "потоков" (например, если один токен относится сразу к нескольким фразам) jint availableRamThresholdMb, // (не используется в этой версии, можно удалить) jobject lowRamCallback // (тоже не используется здесь) ) { // Создаём новую структуру, в которую мы запишем всё, что хотим передать модели: // список токенов или эмбеддингов, их позиции, принадлежность к фразам и флаги для вывода результата. llama_batch *batch = new llama_batch{ 0, // Пока что указываем, что токенов 0 — позже можно будет задать это вручную nullptr, // Здесь будет список ID слов (если не передаются вектора) nullptr, // Здесь будут эмбеддинги (если передаются готовые вектора) nullptr, // Здесь будут позиции токенов в тексте (например, 0, 1, 2...) nullptr, // Количество "потоков" (цепочек), к которым относится каждый токен nullptr, // Сами ID этих цепочек nullptr // Флаги: нужно ли получать ответ (логиты) для каждого токена }; // Если пользователь передаёт эмбеддинги (вектора чисел), создаём под них память. // Иначе создаём массив, в который запишем просто ID слов. if (embd) { batch->embd = (float *) malloc(sizeof(float) * n_tokens * embd); } else { batch->token = (llama_token *) malloc(sizeof(llama_token) * n_tokens); } // Для каждого токена создаём ячейку, куда запишется его позиция в тексте — 0, 1, 2 и т.д. batch->pos = (llama_pos *) malloc(sizeof(llama_pos) * n_tokens); // Создаём массив, в который будет записано, сколько "потоков" (фраз) связан с каждым токеном. batch->n_seq_id = (int32_t *) malloc(sizeof(int32_t) * n_tokens); // Для каждого токена создаём список всех потоков (цепочек), к которым он относится. batch->seq_id = (llama_seq_id **) malloc(sizeof(llama_seq_id *) * n_tokens); for (int i = 0; i < n_tokens; ++i) { batch->seq_id[i] = (llama_seq_id *) malloc(sizeof(llama_seq_id) * n_seq_max); } // Создаём массив флагов, где будет указано: нужно ли для этого токена получить логиты (предсказания модели). batch->logits = (int8_t *) malloc(sizeof(int8_t) * n_tokens); // Возвращаем указатель на нашу структуру в виде обычного числа — его можно будет использовать в других местах. return reinterpret_cast<jlong>(batch); }
long new_sampler() { // Получаем стандартные настройки для цепочки сэмплеров — это объект, который управляет тем, // как модель выбирает следующее слово или токен в тексте. auto sparams = llama_sampler_chain_default_params(); // Отключаем сбор статистики производительности — это ускоряет работу, если метрики не нужны. sparams.no_perf = true; // Создаём новую цепочку сэмплеров с этими настройками. llama_sampler *smpl = llama_sampler_chain_init(sparams); // Добавляем в цепочку самый простой способ генерации — greedy-сэмплер (всегда выбирает токен с максимальной вероятностью). llama_sampler_chain_add(smpl, llama_sampler_init_greedy()); // Возвращаем указатель на сэмплер в виде числа, чтобы им можно было пользоваться в других частях программы. return reinterpret_cast<jlong>(smpl); }
Модель загружена, после запроса пользователя приступаем к генерации текста