Почему ваш NPC на 70B параметрах тормозит сервер, а диалог все равно плоский
Вы поставили на торговца в таверне локальную LLM на 7 миллиардов параметров. Запустили через Ollama. Ждете шедевр. А получаете односложные ответы и задержку в 3 секунды на реплику. Сервер греется, видеокарта плачет, а игрок уже ушел к конкуренту.
Проблема не в модели. Проблема в том, что вы лечите насморк хирургической пилой. Для NPC в игре не нужна модель, способная писать диссертации по квантовой физике. Нужна модель, которая:
- Понимает контекст на 500-1000 токенов (не 32к!)
- Стабильно выдает ответ в определенном формате (JSON, а не поток сознания)
- Работает на карточке уровня RTX 3060 одновременно для 10 NPC
- Не галлюцинирует про API методы, которых нет в вашей игре
На 28 марта 2026 года моделей размером 1-3B параметров стало больше, а качество выросло настолько, что ставить на NPC что-то тяжелее 2.5B - это уже расточительство. Особенно после релиза Qwen 2.5B Instruct и Phi-3-mini, которые перевернули представление о возможностях SLM.
Выбор модели: математика вместо веры в маркетинг
Открываете Hugging Face. Видите 50 моделей с тегом "instruct". Какую брать? Не ту, у которой больше звезд. И не ту, что рекомендует рандомный блогер.
Берете вот этот чеклист:
| Модель (на 28.03.2026) | Параметры | Контекст | Ключевая фишка для NPC | VRAM на fp16 |
|---|---|---|---|---|
| Qwen 2.5B Instruct | 2.5B | 32K | Отличное понимание JSON, стабильность | ~5 GB |
| Phi-3-mini-4k-instruct | 3.8B | 4K | Быстрая инференция, малый размер | ~8 GB |
| Gemma 2-2B-it | 2B | 8K | Хорошая англоязычная база | ~4 GB |
| DeepSeek-V2-Lite-Coder | 2.4B | 64K | Идеально для structured output | ~5 GB |
Я остановился на Qwen 2.5B Instruct. Почему? Потому что у Alibaba были ресурсы на нормальный датасет для инструкций, модель отлично слушается system prompt, и что важно - не пытается быть "безопасной" до идиотизма. Ваш злой маг может оставаться злым.
JSON vs Естественный язык: спор, который решается в пользу парсинга
Типичная ошибка новичка:
# Как НЕ надо делать
prompt = """
Ты - стражник у ворот. Игрок подходит к тебе. Что ты скажешь?
"""
# Модель отвечает:
# "Приветствую, путник! state = 'neutral', dialogue = 'Могу ли я чем-то помочь?'"
# Попробуй распарси это...
Правильный подход - заставить модель думать в JSON с самого начала. Но не просто "выдай JSON", а объяснить структуру так, чтобы у модели не было шансов ошибиться.
1 Системный промпт как контракт
System prompt для NPC - это не пожелания. Это техническое задание. Если в нем есть двусмысленности - модель их найдет и использует против вас.
# Правильный system prompt для NPC
system_prompt = """
Ты - NPC в ролевой игре. ВСЕГДА отвечай строго в следующем JSON формате:
{
"dialogue": "реплика персонажа",
"emotion": "neutral|happy|angry|sad",
"action": "none|attack|trade|follow",
"memory_key": "ключ для сохранения в память"
}
Правила:
1. dialogue - всегда строка, заканчивается точкой или восклицательным знаком
2. emotion - только одно из четырех значений
3. action - только одно из четырех значений
4. memory_key - одно слово, описывающее суть диалога
Если игрок говорит что-то непонятное, используй emotion: "neutral" и action: "none".
"""
Видите разницу? В первом случае мы просим. Во втором - приказываем. И указываем, что делать в краевых случаях.
Семантическое кодирование расстояний: как объяснить NPC, что "рядом" - это 5 метров, а не 50
Самая частая галлюцинация у NPC - пространственная. Игрок стоит в 20 метрах, а NPC кричит "Подойди ближе!" как будто он в двух сантиметрах.
Решение - семантическое кодирование. Не просто "игрок рядом", а точные категории с четкими границами.
| Дистанция | Код в промпте | Пример реакции NPC |
|---|---|---|
| 0-2 м | DISTANCE_TOUCH | "Отойди, ты слишком близко!" |
| 2-5 м | DISTANCE_CLOSE | "Я тебя слышу. Что нужно?" |
| 5-15 м | DISTANCE_NORMAL | "Эй, ты там! Подойди поближе!" |
| 15+ м | DISTANCE_FAR | Игнорирует, если не кричать |
В промпт это встраивается так:
user_message = """
Игрок: Привет!
Дистанция: DISTANCE_CLOSE
Время суток: NIGHT
Настроение NPC: ANNOYED
Память: ['player_stole_apple_yesterday']
"""
Модель видит не сырые метры, а семантические маркеры. DISTANCE_CLOSE четко определяет, как NPC должен реагировать. Это в тысячу раз надежнее, чем передавать "distance: 3.5".
Prompt engineering для Qwen 2.5B: холодный расчет вместо творчества
Забудьте про креативные промпты в духе "Представь, что ты уставший стражник...". С SLM это не работает. Они слишком маленькие, чтобы понимать метафоры.
Вместо этого - explicit instructions. Чем более вы прямолинейны, тем лучше модель выполняет задачу.
2 Шаблон для диалогового NPC
template = """
Контекст игры: {world_context}
Твой персонаж:
Имя: {npc_name}
Роль: {npc_role}
Черты характера: {traits}
Цели: {goals}
Текущее состояние:
Локация: {location}
Время: {time}
Погода: {weather}
Память о игроке:
{memory}
Последние события:
{recent_events}
Инструкции по ответу:
1. Длина реплики: 1-2 предложения
2. Стиль речи: {speech_style}
3. Не задавай вопросов игроку, если это не торговец
4. Не меняй тему разговора резко
5. Если не знаешь что сказать, используй стандартную фразу для своей роли
Формат ответа - JSON:
{{
"text": "твоя реплика",
"next_action": "wait|move|trade",
"emotion": "neutral|happy|angry|sad",
"memory_update": "ключ_события"
}}
Диалог:
Игрок: {player_message}
Твой ответ:
"""
Температура (temperature) для SLM в играх должна быть низкой - 0.1-0.3. Вам не нужна креативность, вам нужна предсказуемость. Высокая температура заставит модель "фантазировать" и выходить из роли.
Грамматически-ограниченное поколение: заставляем Qwen 1.5B слушаться
Есть прием, который уменьшает количество битого JSON на 90%. Называется grammar-constrained generation. По сути, вы говорите модели: "Ты можешь генерировать только токены, которые допустимы в этом месте JSON".
В 2026 году большинство инференс-серверов поддерживают это из коробки. Например, в vLLM или llama.cpp.
# Пример с использованием outlines (библиотека для грамматик)
import outlines
# Определяем грамматику JSON ответа
json_grammar = """
root ::= object
object ::= "{" pair ("," pair)* "}"
pair ::= string ":" value
value ::= string | "null" | "true" | "false"
string ::= "\"" [a-zA-Z0-9_ ]* "\""
"""
# Создаем модель с ограничением
model = outlines.models.transformers("Qwen/Qwen2.5-2B-Instruct", device="cuda")
generator = outlines.generate.json(model, json_grammar)
# Генерация будет ТОЛЬКО валидный JSON
result = generator(prompt)
Если ваш NPC внезапно начинает отвечать "Я не могу говорить об этом", значит грамматика не настроена. Модель пытается выдать что-то вне JSON, но система ее останавливает.
Полный пайплайн от промпта до Unity
3 Настройка Ollama для массового использования
Ollama хорош для тестов, но для 10+ NPC одновременно нужна оптимизация.
# Запуск Ollama с настройками для множества NPC
OLLAMA_NUM_PARALLEL=10 \
OLLAMA_MAX_LOADED_MODELS=3 \
ollama serve
# Создаем модель-шаблон с нашим system prompt
cat > Modelfile << EOF
FROM qwen2.5:2.5b-instruct
PARAMETER temperature 0.2
PARAMETER top_p 0.9
SYSTEM """
Ты - NPC в игре. Всегда отвечай в JSON формате...
"""
EOF
ollama create npc-base -f Modelfile
4 Интеграция в игровой движок
Не делайте HTTP запрос к Ollama на каждом кадре. Это убьет производительность.
// Пример для Unity - менеджер NPC с очередью запросов
public class NPCDialogueManager : MonoBehaviour
{
private Queue requestQueue = new();
private bool isProcessing = false;
public void RequestDialogue(NPC npc, string playerMessage)
{
// Собираем контекст из памяти NPC
var context = npc.BuildPromptContext(playerMessage);
// Кладем в очередь
requestQueue.Enqueue(new NPCRequest(npc, context));
if (!isProcessing)
StartCoroutine(ProcessQueue());
}
private IEnumerator ProcessQueue()
{
isProcessing = true;
while (requestQueue.Count > 0)
{
var request = requestQueue.Dequeue();
// Асинхронный запрос к Ollama
yield return StartCoroutine(SendToLLM(request));
// Обработка ответа
var response = JsonUtility.FromJson(llmResponse);
request.npc.OnDialogueResponse(response);
}
isProcessing = false;
}
}
Важный нюанс: делайте timeout на запросы. Если модель думает дольше 2 секунд - прерывайте и давайте fallback-ответ.
Что сломается первым (и как это починить)
Реальные проблемы, с которыми столкнетесь:
Проблема 1: Модель внезапно забывает формат JSON после 20 диалогов.
Решение: В каждом пользовательском промпте дублируйте ключевые инструкции. Не надейтесь, что системаный промпт "запомнится".
Проблема 2: NPC начинает повторять одни и те же фразы.
Решение: Добавьте в контекст список последних 5 реплик NPC. И явно укажите: "Не повторяй предыдущие фразы".
Проблема 3: При длинной памяти (100+ токенов) качество ответов падает.
Решение: Не храните всю историю. Храните семантические summary. Вместо "Игрок спросил о цене на меч, потом о броне, потом ушел" пишите "player_interested_in_prices".
Когда все-таки нужна большая модель
SLM - не панацея. Если ваш NPC должен:
- Вести глубокие философские диалоги (квестовые NPC)
- Анализировать сложные игровые ситуации (советник короля)
- Генерировать уникальные истории на лету (сказочник)
...тогда стоит посмотреть в сторону моделей 7B+. Но даже там не берите 70B - это overkill. Как я писал в статье про uncensored LLM для ролевых игр, разница между 7B и 13B часто не стоит дополнительных ресурсов.
Будущее SLM: почему через год мы будем смеяться над сегодняшними 2B моделями
На 28 марта 2026 уже есть слухи о Qwen 3B с качеством ответов как у сегодняшних 7B. Технология сжатия знаний (knowledge distillation) развивается так быстро, что через год 2B модель будет делать то, что сегодня делает 7B.
Но принципы останутся:
- Четкий формат вывода (JSON, XML, YAML) всегда побеждает свободный текст
- Семантические маркеры надежнее сырых значений
- Грамматические ограничения экономят 30% вычислительных ресурсов
- Температура ниже = стабильнее диалог
Поставьте сегодня Qwen 2.5B на своих массовых NPC. Через месяц, когда поймете его ограничения, попробуете Phi-4 или следующую версию Gemma. Но не начинайте со сложного - настройте пайплайн на маленькой модели, отладьте интеграцию, а потом уже думайте об апгрейде.
Главный секрет не в выборе самой крутой модели. А в том, чтобы ваша архитектура позволяла легко заменить Qwen 2.5B на следующую 2B-модель, которая выйдет через полгода. Пишите код так, как будто завтра появится модель в 10 раз лучше - и она действительно появится.