Обычный домашний замер выглядит так: человек запускает модель, просит написать что-нибудь, засекает секундомером и делит слова на секунды. Через неделю он меняет настройку, повторяет процедуру и делает вывод, что стало быстрее.
Вывод почти наверняка неверный. В первом замере модель грузилась с диска, во втором была уже в памяти. Первый запрос был длиннее второго. В первый раз был открыт браузер, во второй нет. Разница в 30%, которую он увидел, объясняется чем угодно, кроме той настройки, которую он менял.
Замер полезен только тогда, когда воспроизводим. Ниже — протокол, который это обеспечивает.
#Что понадобится
- Утилита замера. В llama.cpp это
llama-bench, собирается вместе с остальными бинарниками. Для Ollama штатного эквивалента нет, но её HTTP-API отдаёт нужную статистику. - Фиксированная модель и квантизация. Не «qwen2.5:7b», а конкретный файл с конкретным уровнем квантизации.
- Десять минут без другой нагрузки на машину.
#Протокол из шести шагов
#Шаг 1. Зафиксировать конфигурацию письменно
До первого запуска запишите шесть параметров: модель и уровень квантизации, размер контекста, число слоёв на GPU, тип KV-кэша, версию движка, режим питания. Замер без этой записи бесполезен через неделю — вы не вспомните, с чем сравниваете.
Версию движка стоит записывать точно: изменения производительности между релизами llama.cpp бывают заметными, и сравнивать замеры, сделанные на разных версиях, некорректно.
#Шаг 2. Освободить машину
Закройте браузер и всё, что использует видеокарту. Проверьте, что память свободна:
nvidia-smi --query-gpu=memory.used,utilization.gpu --format=csv
Заодно убедитесь, что не запущен другой экземпляр модели: Ollama могла оставить веса загруженными после прошлого сеанса. Проверяется командой ollama ps.
Ноутбук подключите к сети: в режиме от батареи частоты урезаны, и замер будет описывать не то железо, которым вы пользуетесь.
#Шаг 3. Прогреть и отбросить первый прогон
Первый запуск читает веса с диска. На модели в 5 гигабайт это добавляет секунды, которые размазываются по результату и завышают его. Правило простое: первый прогон всегда отбрасывается, независимо от того, насколько он выглядит нормальным.
llama-bench делает прогрев самостоятельно. При ручном замере сделайте один холостой запрос и не учитывайте его.
#Шаг 4. Зафиксировать длину промпта и длину генерации
Это второй по частоте источник неверных выводов. Скорость обработки промпта и скорость генерации — разные величины, и обе зависят от длины. Запрос «привет» и запрос на две страницы дадут несравнимые результаты.
В протоколе фиксируются оба числа: сколько токенов на входе, сколько генерируется. Стандартные значения для сравнений — 512 токенов промпта и 128 токенов генерации; для проверки поведения на длинном контексте — 4096 и больше.
#Шаг 5. Повторить минимум три раза и взять медиану
Разброс между прогонами на одной и той же конфигурации — обычное дело: фоновая активность системы, тепловой режим, работа диска. Один прогон ничего не доказывает. Медиана из трёх-пяти устойчивее среднего, потому что не уезжает от одного неудачного замера.
#Шаг 6. Записать два числа, а не одно
Скорость обработки промпта и скорость генерации записываются раздельно и сравниваются раздельно. Настройка, ускорившая одно и замедлившая другое, — типичная ситуация, и усреднение её маскирует.
#Команды
#llama.cpp
./build/bin/llama-bench -m models/qwen2.5-7b-instruct-q4_k_m.gguf -ngl 99 -p 512 -n 128 -r 5
Флаг -r 5 задаёт число повторов. Вывод содержит две строки: pp512 — скорость обработки промпта в токенах в секунду, tg128 — скорость генерации.
Утилита умеет перебирать значения списком, и это её главная ценность: вместо пяти отдельных запусков вы получаете таблицу за один раз.
./build/bin/llama-bench -m models/qwen2.5-7b-instruct-q4_k_m.gguf -ngl 20,30,40,99 -p 512 -n 128
Так подбирается оптимальное число слоёв на видеокарте. Аналогично перебираются потоки (-t 4,8,12) и размеры батча. Разбор остальных флагов — в гайде по llama.cpp без магии.
#Ollama
Быстрый вариант — флаг подробного вывода, который печатает статистику после ответа:
ollama run qwen2.5:7b "Опиши устройство велосипедной втулки" --verbose
Точный вариант — через API, где числа приходят в машиночитаемом виде:
curl -s http://localhost:11434/api/generate -d '{"model":"qwen2.5:7b","prompt":"Опиши устройство велосипедной втулки","stream":false,"options":{"num_predict":128}}' > out.json
В ответе интересны четыре поля: prompt_eval_count и prompt_eval_duration для обработки запроса, eval_count и eval_duration для генерации. Длительности указаны в наносекундах, поэтому скорость считается так:
python -c "import json;d=json.load(open('out.json'));print('gen', d['eval_count']/d['eval_duration']*1e9); print('prompt', d['prompt_eval_count']/d['prompt_eval_duration']*1e9)"
Повторите запрос трижды, первый результат отбросьте, из оставшихся возьмите медиану.
#Проверка результата
Признак первый: разброс между повторами меньше 10%. Больший разброс означает, что на машине происходит что-то ещё, и замер недостоверен.
Признак второй: результат согласуется с расчётным потолком. Разделите пропускную способность памяти вашего устройства на вес модели в гигабайтах. Замеренная скорость генерации должна составлять 40–70% этого числа. Заметно выше — вы измерили не то (например, забыли, что модель кэширует ответ). Заметно ниже — есть проблема в конфигурации.
Признак третий: числа воспроизводятся завтра. Повторите замер на следующий день на той же конфигурации. Расхождение больше 10% означает, что вы не зафиксировали какое-то условие.
Что считать хорошим результатом в абсолютных величинах — в материале о том, сколько токенов в секунду это нормально.
#Что может пойти не так
Сравнение разных квантизаций как «до и после». В каталоге Ollama за коротким именем стоит конкретный квант; локальный файл GGUF может быть другим. Убедитесь командой ollama show, что сравниваете одинаковое.
Разный контекст. Значение по умолчанию в Ollama и значение, которое вы указали в llama.cpp, скорее всего, не совпадают. Задавайте контекст явно в обоих инструментах.
Термотроттлинг на длинных прогонах. На ноутбуках первые двадцать секунд идут быстрее последующих. Если вам важна устойчивая скорость, замеряйте несколько минут подряд, а не один короткий прогон.
Кэш промпта. Движки умеют переиспользовать уже обработанный префикс запроса. Повторный одинаковый запрос может дать нереалистично быструю обработку промпта. Меняйте текст запроса между повторами, сохраняя длину.
Сравнение с чужими числами из интернета. Даже корректный чужой замер сделан на другом железе, другой версии движка и, скорее всего, на английском тексте. Как читать такие публикации — в материале о том, как читать бенчмарки LLM.
#Частые вопросы
#Зачем измерять, если и так видно, что медленно?
Чтобы понять, куда двигаться. «Медленно» не отличает ситуацию «упёрлись в пропускную способность памяти» (лечится только сменой модели или железа) от ситуации «половина слоёв ушла на процессор» (лечится настройкой за минуту). Порядок диагностики — в материале о том, почему локальная модель тормозит.
#Сколько повторов достаточно?
Три как минимум, пять комфортно. Если разброс между ними превышает 10%, повторов нужно больше — либо, что вероятнее, нужно устранить постороннюю нагрузку.
#Почему llama-bench показывает больше, чем ощущается в чате?
Утилита замеряет чистую генерацию на фиксированной длине. В реальном диалоге добавляются обработка растущего контекста, накладные расходы клиента и сетевой обмен с локальным сервером. Расхождение в 10–20% нормально.
#Можно ли сравнивать замеры на разных моделях?
Только с оговоркой: скорость обратно пропорциональна размеру модели в памяти. Модель вдвое меньше при прочих равных даст примерно вдвое больше токенов в секунду. Сравнивать имеет смысл либо одну модель при разных настройках, либо разные модели с поправкой на их вес.
#Что важнее — скорость промпта или генерации?
Зависит от сценария. Для диалога короткими репликами важнее генерация. Для работы с документами, где в модель заливаются длинные тексты, обработка промпта определяет всё ощущение от инструмента. Механику обеих фаз разбирает материал о том, от чего зависит скорость LLM на процессоре.