
Полная версия
ChatGPT на вашем ноутбуке: бесплатно, анонимно, навсегда
bash
CMAKE_ARGS="-DGGML_METAL=on" pip install llama-cpp-python
После установки модель будет использовать встроенную графику Apple, что заметно быстрее чистого CPU.
Для видеокарт AMD (Vulkan)
Карты AMD поддерживаются через бэкенд Vulkan:
bash
CMAKE_ARGS="-DGGML_VULKAN=on" pip install llama-cpp-python
Перед этим убедитесь, что у вас установлен Vulkan SDK (vulkan.lunarg.com).
Выбор способа установки под вашежелезо.
Только процессор (Intel/AMD). Самый простой и универсальный вариант. Установка одной командой pip install llama-cpp-python без дополнительных флагов. Модель будет использовать все ядра процессора, скорость генерации составит от низкой до средней — примерно 5–15 токенов в секунду для 8B-модели. Подходит, если у вас нет дискретной видеокарты или вы не хотите возиться с драйверами.
NVIDIA GeForce/RTX. Если у вас видеокарта NVIDIA с поддержкой CUDA, вы можете ускорить модель в разы. Команда установки: CMAKE_ARGS="-DGGML_CUDA=on" pip install llama-cpp-python. Перед этим убедитесь, что установлены драйверы NVIDIA и CUDA Toolkit. Скорость будет высокой — 30–60 токенов в секунду, модель загрузится в видеопамять и освободит процессор для других задач.
Apple M1/M2/M3/M4. На компьютерах Mac с процессорами Apple Silicon можно использовать встроенное GPU-ускорение через Metal. В большинстве случаев достаточно обычной команды pip install llama-cpp-python — система автоматически задействует Accelerate. Для максимальной производительности можно указать флаг -DGGML_METAL=on. Скорость будет от средней до высокой, в зависимости от поколения чипа и объёма unified memory.
AMD Radeon. Видеокарты AMD поддерживаются через бэкенд Vulkan. Команда установки: CMAKE_ARGS="-DGGML_VULKAN=on" pip install llama-cpp-python. Предварительно потребуется установить Vulkan SDK с сайта vulkan.lunarg.com. Скорость средняя или высокая, в зависимости от модели карты и объёма видеопамяти.
Важно: Если вы не уверены, какое у вас железо — просто выполните pip install llama-cpp-python без флагов. Модель заработает в любом случае, просто на процессоре. Позже всегда можно переустановить с флагами под GPU.
Проверяем установку: пишем «Hello, world» для модели
Создайте в проекте файл test_model.py и напишите минимальный код для проверки:
python
# test_model.py
from model_loader import download_model
from llama_cpp import Llama
# 1. Скачиваем модель ( если ещё нет )
model_path = download_model()
# 2. Загружаем модель
print("Загрузка модели в память...")
model = Llama(
model_path=model_path,
n_ctx=2048, # размер контекстного окна (сколько токенов модель «помнит»)
n_threads=4, # количество потоков CPU (поставьте своё число ядер)
verbose=False # убираем технический вывод
)
# 3. Отправляем запрос
print("Генерация ответа...")
response = model.create_chat_completion(
messages=[
{"role": "system", "content": "Ты — полезный ассистент. Отвечай кратко."},
{"role": "user", "content": "Что такое локальный ИИ одним предложением?"}
],
temperature=0.7,
max_tokens=100
)
# 4. Печатаем ответ
answer = response["choices"][0]["message"]["content"]
print(f"\nОтвет модели:\n{answer}")
Запустите:
bash
python test_model.py
Если всё настроено правильно, вы увидите что-то вроде:
text
Загрузка модели Llama-3.1-8B-Instruct-Q4_K_M.gguf из unsloth/Llama-3.1-8B-Instruct...
Модель уже загружена: models/Llama-3.1-8B-Instruct-Q4_K_M.gguf
Загрузка модели в память...
Генерация ответа...
Ответ модели:
Локальный ИИ — это искусственный интеллект, работающий непосредственно на вашем устройстве без подключения к интернету и передачи данных в облачные серверы.
Что означают параметры в Llama()
· model_path — путь к файлу модели. Мы получаем его из нашей функции download_model().
· n_ctx — размер контекстного окна в токенах. Это максимальная длина диалога, которую модель «помнит». Для начала 2048 токенов достаточно (это около 1500 слов). Позже, при подключении RAG, увеличим до 4096 или 8192.
· n_threads — число потоков CPU, которые будет использовать модель. Поставьте количество физических ядер вашего процессора. Для 4-ядерного процессора — 4, для 8-ядерного — 8.
· verbose=False — отключает подробный лог загрузки. Если что-то пойдёт не так, можно включить обратно (True) для диагностики.
Возможные ошибки и их решение
ModuleNotFoundError: No module named 'llama_cpp'. Библиотека не установлена в текущем виртуальном окружении. Убедитесь, что вы активировали venv и выполнили pip install llama-cpp-python. Если библиотека установлена, но ошибка остаётся — проверьте, что вы находитесь в правильном окружении: в начале строки терминала должен быть префикс (venv).
ImportError: DLL load failed. Проблема возникает на Windows при отсутствии рантаймов Visual C++. Скачайте и установите Microsoft Visual C++ Redistributable с официального сайта Microsoft. После установки перезагрузите компьютер — ошибка должна исчезнуть.
RuntimeError: CUDA error: no CUDA-capable device is detected. Вы установили версию с поддержкой CUDA, но видеокарта NVIDIA не обнаружена. Либо у вас нет карты NVIDIA, либо не установлены драйверы. Решение: установите драйверы с сайта nvidia.com, или переустановите llama-cpp-python без флага CUDA для работы на процессоре.
Illegal instruction (core dumped). Возникает на Linux, когда процессор не поддерживает инструкции AVX2, которые llama.cpp использует по умолчанию. Обычно это случается на очень старых процессорах. Добавьте флаг -DGGML_NATIVE=off при установке — это соберёт библиотеку с базовыми инструкциями, совместимыми со всеми процессорами.
Очень медленная генерация — 1–2 токена в секунду. Скорее всего, модель работает на процессоре в однопоточном режиме. Проверьте параметр n_threads при создании объекта Llama — он должен быть равен количеству физических ядер вашего процессора. Если у вас есть видеокарта, переустановите библиотеку с поддержкой GPU — скорость вырастет в разы.
Ошибка памяти — out of memory. Модель не помещается в доступную оперативную или видеопамять. У вас два варианта: выбрать более сильное квантование (например, Q3_K_M вместо Q4_K_M — файл будет меньше), или взять модель меньшего размера (7B вместо 8B). Также закройте другие программы, занимающие память — браузеры, IDE, мессенджеры.
Как переключить бэкенд в коде
По умолчанию llama-cpp-python использует тот бэкенд, который был включён при компиляции. Но можно явно указать, сколько слоёв модели загружать на GPU, а сколько оставить на CPU. Это полезно, если видеопамяти не хватает на всю модель:
python
model = Llama(
model_path=model_path,
n_ctx=2048,
n_threads=4,
n_gpu_layers=20 # Первые 20 слоёв — на GPU, остальные — на CPU
)
Параметр n_gpu_layers принимает число от 0 (всё на CPU) до -1 (всё на GPU, если хватает памяти). Поэкспериментируйте с этим значением, чтобы найти баланс между скоростью и стабильностью для вашего железа.
Что мы получили
Теперь в нашем проекте есть:
· Файл model_loader.py — умеет скачивать модель.
· Файл test_model.py — умеет загружать модель и получать от неё ответ.
Мы готовы к следующему шагу: написать полноценный класс-обёртку LocalModel, который станет фундаментом для всех будущих возможностей (чат, потоковая генерация, RAG, голос). Этим мы займёмся в главе 3.3.
Глава 3.3. Пишемкласс-обёртку LocalModel с методами chat() и stream().
Мы установили llama-cpp-python, проверили, что модель отвечает на запросы в тестовом скрипте. Теперь пора превратить этот разрозненный код в аккуратный, переиспользуемый компонент. Мы создадим класс LocalModel, который станет «сердцем» нашего проекта. Все будущие возможности — чат, работа с документами, голос — будут опираться именно на него.
Зачем нужна обёртка
Прямые вызовы Llama() из llama-cpp-python работают, но у них есть недостатки:
· Каждый раз приходится передавать десятки параметров (путь к модели, количество потоков, температуру).
· Нет удобного управления историей диалога.
· Код, который работает с моделью, перемешан с бизнес-логикой приложения.
· Трудно тестировать и заменять реализацию (вдруг мы захотим переключиться на другую библиотеку).
Наш класс LocalModel решит эти проблемы. Он будет:
· Хранить загруженную модель и её конфигурацию,
· Предоставлять простые методы chat() и stream_chat(),
· Следить за историей диалога (опционально),
· Давать тот же формат сообщений, что и OpenAI API, чтобы к нему привыкли все, кто писал под ChatGPT.
Код класса LocalModel
Создайте в проекте файл local_model.py. Ниже — полный код класса с подробными комментариями.
python
# local_model.py
from typing import List, Dict, Optional, Generator
from llama_cpp import Llama
class LocalModel:
"""
Обёртка над llama-cpp-python, предоставляющая удобный интерфейс
для общения с локальной языковой моделью.
"""
def __init__(
self,
model_path: str,
n_ctx: int = 2048,
n_threads: int = 4,
n_gpu_layers: int = 0,
verbose: bool = False
):
"""
Инициализация модели.
:param model_path: путь к файлу .gguf
:param n_ctx: размер контекстного окна (максимальное количество токенов,
которое модель «помнит»)
:param n_threads: количество потоков процессора
:param n_gpu_layers: сколько слоёв загрузить на GPU (-1 = все, 0 = ничего)
:param verbose: выводить ли подробный лог загрузки
"""
self.model_path = model_path
self.n_ctx = n_ctx
# Загружаем модель в память
self._llm = Llama(
model_path=model_path,
n_ctx=n_ctx,
n_threads=n_threads,
n_gpu_layers=n_gpu_layers,
verbose=verbose
)
# Хранилище истории диалогов (ключ — id беседы)
self._histories: Dict[str, List[Dict[str, str]]] = {}
def chat(
self,
messages: List[Dict[str, str]],
temperature: float = 0.7,
max_tokens: int = 512,
system_prompt: Optional[str] = None
) -> str:
"""
Отправляет сообщения модели и возвращает её ответ.
:param messages: список сообщений в формате OpenAI:
[{"role": "user", "content": "..."}, ...]
:param temperature: креативность (0 — строго, до 1.5 — фантазия)
:param max_tokens: максимальная длина ответа
:param system_prompt: системный промпт (если не указан в messages)
:return: строка с ответом модели
"""
# Если передан system_prompt, добавляем его в начало списка
full_messages = []
if system_prompt:
full_messages.append({"role": "system", "content": system_prompt})
full_messages.extend(messages)
response = self._llm.create_chat_completion(
messages=full_messages,
temperature=temperature,
max_tokens=max_tokens
)
return response["choices"][0]["message"]["content"]
def stream_chat(
self,
messages: List[Dict[str, str]],
temperature: float = 0.7,
max_tokens: int = 512,
system_prompt: Optional[str] = None
) -> Generator[str, None, None]:
"""
Потоковая версия chat(). Возвращает генератор, который выдаёт
токены по мере их генерации. Позволяет отображать ответ
«на лету», как в ChatGPT.
:param messages: список сообщений
:param temperature: креативность
:param max_tokens: максимальная длина ответа
:param system_prompt: системный промпт
:yield: кусочки текста (токены)
"""
full_messages = []
if system_prompt:
full_messages.append({"role": "system", "content": system_prompt})
full_messages.extend(messages)
stream = self._llm.create_chat_completion(
messages=full_messages,
temperature=temperature,
max_tokens=max_tokens,
stream=True
)
for chunk in stream:
# В каждом чанке может быть одна или несколько «дельт»
if "choices" in chunk and len(chunk["choices"]) > 0:
delta = chunk["choices"][0].get("delta", {})
content = delta.get("content", "")
if content:
yield content
def save_history(self, conversation_id: str, messages: List[Dict[str, str]]):
"""Сохраняет историю диалога под указанным id."""
self._histories[conversation_id] = messages.copy()
def load_history(self, conversation_id: str) -> List[Dict[str, str]]:
"""Загружает историю диалога по id. Если нет — пустой список."""
return self._histories.get(conversation_id, [])
def clear_history(self, conversation_id: str):
"""Удаляет историю диалога."""
if conversation_id in self._histories:
del self._histories[conversation_id]
Объяснение ключевых моментов
Конструктор __init__
Мы передаём путь к модели и настройки производительности. Важно, что модель загружается один раз при создании объекта LocalModel и остаётся в памяти до его уничтожения. Это экономит время при множественных запросах. Параметр n_ctx определяет «память» модели: чем он больше, тем более длинные диалоги она может вести, но тем больше потребляется памяти. Для начала 2048 токенов достаточно. n_gpu_layers мы подробно обсудили в предыдущей главе.
Метод chat()
Принимает список сообщений в формате OpenAI — таком же, какой используют облачные API. Это значит, что вы можете взять любой код, написанный для ChatGPT, заменить создание клиента на наш LocalModel, и всё продолжит работать. Внутри мы добавляем системный промпт, если он передан, и вызываем create_chat_completion. Ответ возвращается целиком, в виде строки.
Метод stream_chat()
Потоковая генерация — это когда ответ появляется по одному слову, как в ChatGPT. Вместо того чтобы ждать полного ответа, мы получаем токены по мере их создания. В Python это реализуется через генератор (ключевое слово yield). Внешний код может делать так:
python
for token in model.stream_chat(messages):
print(token, end="", flush=True)
Это создаёт эффект «печатания» текста и делает интерфейс более отзывчивым. Мы будем использовать этот метод при создании GUI.
Управление историей
Класс содержит словарь _histories, который хранит списки сообщений по идентификаторам бесед. Это примитивная, но работающая система для многопользовательского или многозадачного режима. Позже мы заменим её на постоянное хранение (файлы или базу данных), но для старта достаточно.
Практический пример: ведём диалог
Создайте файл chat_demo.py:
python
# chat_demo.py
from model_loader import download_model
from local_model import LocalModel
# 1. Скачиваем и загружаем модель
model_path = download_model()
print("Загрузка модели...")
model = LocalModel(model_path=model_path, n_threads=4)
# 2. Системный промпт (необязательно, но полезно)
system = "Ты — ассистент-повар. Отвечай коротко, предлагай простые рецепты."
# 3. Диалог в цикле
conversation_id = "cooking"
messages = [] # Начнём с пустой истории
while True:
user_input = input("\nВы: ")
if user_input.lower() in ["выход", "quit", "exit"]:
break
# Добавляем сообщение пользователя в историю
messages.append({"role": "user", "content": user_input})
# Получаем ответ (потоково)
print("Повар: ", end="", flush=True)
full_response = ""
for token in model.stream_chat(
messages=messages,
system_prompt=system,
temperature=0.7
):
print(token, end="", flush=True)
full_response += token
print() # Перевод строки после ответа
# Сохраняем ответ в историю
messages.append({"role": "assistant", "content": full_response})
model.save_history(conversation_id, messages)
Запустите:
bash
python chat_demo.py
Пример диалога:
text
Вы: Как сварить яйцо?
Повар: Всмятку — 4 минуты в кипящей воде. Вкрутую — 8-10 минут. Охладите под холодной водой, чтобы легче чистились.
Вы: А яичницу?
Повар: Разогрейте сковороду с маслом, разбейте яйца, жарьте 2-3 минуты. Для глазуньи накройте крышкой на минуту, чтобы белок схватился сверху.
Управление памятью: что делать, если диалог слишком длинный
Контекстное окно n_ctx ограничивает суммарное количество токенов, которые модель может «видеть» одновременно. Если диалог становится слишком длинным, нужно обрезать историю. Простейший способ — оставить только последние N сообщений. Мы добавим метод trim_history в наш класс (можно дополнить local_model.py):
python
def trim_history(self, messages: List[Dict[str, str]], max_messages: int = 20) -> List[Dict[str, str]]:
"""
Оставляет только последние max_messages сообщений.
Системный промпт (первое сообщение с role='system') сохраняется всегда.
"""
if len(messages) <= max_messages:
return messages
# Сохраняем системный промпт, если он есть
system_messages = [m for m in messages if m["role"] == "system"]
other_messages = [m for m in messages if m["role"] != "system"]
# Обрезаем остальные
trimmed = other_messages[-max_messages:]
return system_messages + trimmed
Возможные проблемы и ихрешения.
Модель «забывает», о чём говорили раньше. Это происходит, когда диалог превышает размер контекстного окна n_ctx. Llama.cpp автоматически обрезает историю, и начало разговора теряется. Решение простое: увеличьте n_ctx при создании модели — например, с 2048 до 4096 токенов. Учитывайте, что каждый токен контекста потребляет дополнительную память. Если увеличить n_ctx нельзя из-за нехватки ОЗУ, используйте метод trim_history() — он вручную оставляет только последние N сообщений, сохраняя системный промпт.
Потоковый вывод «дёргается» или зависает. Обычно проблема в слишком маленьком параметре max_tokens — модель упирается в лимит и останавливается на полуслове. Увеличьте max_tokens до 500 или 1000. Другая возможная причина — буферизация вывода. Убедитесь, что при печати токенов используется flush=True: print(token, end="", flush=True). Это заставляет Python выводить текст немедленно, а не копить в буфере.
Ответ содержит бред на незнакомую тему. Модель не обучалась на ваших специфических данных и пытается «угадать» ответ, порождая галлюцинации. Первое, что стоит сделать — добавить системный промпт, ограничивающий тематику: «Ты — ассистент по договорному праву. Если вопрос не по теме, скажи об этом». Если этого недостаточно — подключите RAG из Части 4 книги: модель будет искать ответ в ваших документах, а не выдумывать.
Ошибка «KeyError: 'choices'». Изредка возникает при потоковой генерации, особенно если соединение было прервано. Llama.cpp возвращает chunk без ожидаемого ключа. Решение — добавить защитную проверку перед обработкой каждого чанка: if "choices" in chunk and len(chunk["choices"]) > 0:. Это гарантирует, что код не упадёт на некорректном ответе модели.
Что дальше.
Теперь у нас есть полноценный, переиспользуемый класс для работы с локальной LLM. Мы можем вставлять его в любой проект и общаться с моделью через простой Python-интерфейс. В следующей главе мы сделаем так, чтобы этот интерфейс стал доступен по сети — через HTTP-сервер, совместимый с OpenAI API. Это позволит подключаться к нашему локальному ИИ из любых приложений, которые умеют работать с ChatGPT.
Глава 3.4. Делаем APIсовместимым с OpenAI
Мы написали класс LocalModel, который умеет отвечать на вопросы в нашем Python-коде. Но что, если мы захотим подключить к нашему локальному ИИ какое-нибудь готовое приложение? Например, плагин для VS Code, веб-интерфейс, мобильное приложение или даже другой скрипт, написанный под ChatGPT? Переписывать всё под наш класс — тупиковый путь.
К счастью, мир уже договорился о стандарте. OpenAI предоставляет HTTP API, которое принимает запросы определённого формата и возвращает ответы. Сотни инструментов и библиотек умеют работать с этим API. Если мы сделаем свой сервер, который «притворяется» OpenAI, то сможем использовать весь этот зоопарк без изменений. Именно этим мы и займёмся в этой главе.
Что мы строим
Мы создадим небольшой HTTP-сервер на Python, который:
· Слушает порт (например, 8080) на вашем компьютере.
· Принимает POST-запросы на адрес /v1/chat/completions — точно такой же, как у OpenAI.
· Перенаправляет эти запросы в наш LocalModel.
· Возвращает ответ в том же JSON-формате, что и OpenAI.
· Поддерживает потоковую генерацию (streaming), чтобы ответ «печатался» на лету.
· Не требует никаких внешних ключей API — он работает строго локально.
После запуска сервера вы сможете, например, использовать официальную Python-библиотеку openai, просто указав base_url="http://localhost:8080/v1". И она будет работать с вашей локальной моделью Llama, как с ChatGPT. Магия!
Инструменты
Для создания HTTP-сервера мы будем использовать FastAPI. Это современный, быстрый и простой фреймворк, который идеально подходит для API. Он автоматически генерирует документацию, поддерживает асинхронность и потоковые ответы. Нам также понадобится uvicorn — сервер, который будет запускать наше FastAPI-приложение.
Установим их:
bash
pip install fastapi uvicorn
Код сервера
Создайте файл openai_api.py в корне проекта. Ниже — полный код с подробными комментариями.
python
# openai_api.py
import time
import uuid
from typing import Optional, List, Dict, Any
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field












