Просмотр новости

Найдите то, что Вас интересует

Я устал писать одноразовые скрипты для бенчмарков LLM и собрал харнесс, который сам считает Pareto-front

Дата публикации: 27-06-2026 17:19:26

LLM inference benchmarkС чего все началосьУ меня была вполне приземленная задача: понять, на каком бэкенде гонять одну и ту же открытую модель — на vLLM, llama.cpp, ONNX Runtime или просто на transformers. Звучит как вопрос на пять минут, пока ты не начинаешь честно мерить.Проблема в том, что почти все готовые бенчмарки меряют не то, что нужно на практике. А мне нужно было держать в голове сразу четыре оси: p95-latency, throughput (tok/s), пиковый VRAM и то, что модель вообще не сошла с ума под нагрузкой. И главный вопрос звучал так: какая конфигурация влезает в мой бюджет по видеопамяти и при этом держит p95 ниже порога? Ответа на него не давал никто, поэтому я в очередной раз открывал ноутбук и писал bench_v3_final_FINAL.py.Когда таких скриптов накопилось штук пять, и каждый мерил по-своему (а значит, числа между собой сравнивать было нельзя), я сел писать нормальный харнесс. Так появился llm-inference-benchmark.Что это в итоге за тулзаЕсли совсем коротко — это воспроизводимый харнесс для экспериментов с инференсом. Ты описываешь эксперимент в YAML, он гоняет одну и ту же нагрузку через разные бэкенды и конфиги, складывает результаты в CSV + JSON-манифест, а потом сам говорит, какая конфигурация оптимальна при заданных ограничениях. Сверху прикручен браузерный дашборд, чтобы не втыкать в голый CSV.Кому это нужно:тем, кто выбирает бэкенд/квантизацию под конкретное железо;тем, кому нужна воспроизводимость — чтобы через месяц можно было доказать, откуда взялась цифра;тем, кто хочет CI-гейт на регрессии инференса (об этом ниже).Бэкенды, которые поддерживаются прямо сейчас (v1.8.3):БэкендЧем хорошmockдетерминированный, для CI — модель не нужна вообщеtransformersAutoModelForCausalLM от HF, CUDAllama-cppGGUF-квантизация, pre-built CUDA-wheel, без nvccopenaiлюбой сервер с /v1/chat/completions (Ollama, LM Studio, vLLM)onnxONNX Runtime через Optimum, INT8/FP16vllmhigh-throughput движок, Linux onlyДальше — самое интересное, что под капотом.Под капотом: YAML, Run Matrix и SweepОдин эксперимент = один YAMLБазовая единица — конфиг одного прогона. Никакой магии, просто декларация того, что мы меряем:# configs/llama-cpp-gpu.yamlbackend: llama-cppmodel: ~/models/Llama-3.2-3B-Instruct-Q4_K_M.ggufrequests: 20warmup_requests: 2prompts_file: data/prompts/smoke.txtrepeats: 3 # медиана ± std по 3 прогонамllama_cpp: n_ctx: 2048 n_gpu_layers: 99 # 99 = выгрузить все слои на GPU; 0 = только CPU max_tokens: 50 temperature: 0.0 # greedy, детерминированноЗапуск:uv run llm-bench —config configs/llama-cpp-gpu.yaml —output results/run.csvЛюбое поле можно переопределить из CLI, не трогая YAML — удобно, когда хочется быстро дернуть один параметр:uv run llm-bench —config configs/llama-cpp-gpu.yaml \ —set llama_cpp.n_gpu_layers=20 —requests 50 —seed 42Run Matrix: перестать копипастить конфигиОдин прогон — это скучно. Реальный вопрос всегда звучит как «а сравни-ка мне вот эти пять вариантов». Для этого есть матрица. В явном виде это просто список прогонов с общей папкой результатов:results_dir: resultsruns: - name: quant-q4km config: configs/llama-cpp-q4km-best.yaml - name: quant-q8 config: configs/llama-cpp-q8-best.yamlllm-bench matrix —config configs/llama-cpp-quant-compare.yamlКаждая комбинация дает свой CSV и свой JSON-манифест — ничего не перетирается, имена прогонов валидируются (никаких путей и ../ в name, иначе словишь ValueError еще на этапе парсинга, а не в середине часового прогона).Sweep: декартово произведение из коробкиА вот когда вариантов становится много, явный список превращается в запутанную конструкцию. Тут включается sweep — ты задаешь оси, а харнесс сам разворачивает их в cartesian product:# n_gpu_layers × n_ctx → 3 × 2 = 6 прогонов из одного base-конфигаbase_config: configs/llama-cpp-gpu.yamlresults_dir: resultssweep: llama_cpp.n_gpu_layers: [0, 20, 99] llama_cpp.n_ctx: [512, 2048]# Сначала посмотреть, что вообще сгенерится, не запуская:llm-bench matrix —config configs/llama-cpp-sweep.yaml —dry-runПод капотом это буквально itertools.product по спискам значений, но с парой важных предохранителей, на которые я потратил больше времени, чем планировал:dot-path валидируется до запуска. llama_cpp.n_gpu_layers сверяется с реальными полями pydantic-модели. Опечатался в имени поля — узнаешь об этом сразу, а не через час, когда прогон тихо проигнорировал твой «оверрайд».Имена прогонов детерминированы и собираются из значений (sweep-n_gpu_layers-99-n_ctx-2048). Если два значения схлопываются в одно и то же имя — харнесс ругается и просит перейти на явный runs:, а не молча затирает результат.CLI: предпросмотр матрицы прогонов (--dry-run)Глубину оверрайдов я намеренно ограничил двумя уровнями (секция.поле). Можно было сделать произвольную вложенность через рекурсию, но это ровно тот случай, когда гибкость оборачивается тем, что конфиги становится невозможно читать. Лучше пусть будет чуть жестче, зато предсказуемо.Аналитика: Pareto и рекомендатель, который думает за тебяОкей, мы нагенерили двадцать CSV. Дальше начинается то, ради чего все затевалось.Pareto: отсеиваем заведомо проигрышные конфигиСравнивать конфиги «на глаз» по таблице из двадцати строк и семи колонок — неприятное дело. Поэтому есть классификация по доминированию по Парето.Конфиг A доминирует над B, если A не хуже B по каждой метрике и строго лучше хотя бы по одной. Все, что доминируется, — это заведомо проигрышный вариант: всегда найдется другой конфиг, который не хуже по всем фронтам. Остаются только Pareto-оптимальные точки — фронт компромиссов, между которыми уже есть смысл выбирать руками.Направления оптимизации зашиты честно по смыслу метрики:минимизируем: p95-latency, VRAM, perplexity, время загрузки модели, TTFT;максимизируем: tok/s, sanity-rate, task-quality, judge-score.Ключевая деталь, которой я горжусь чуть больше, чем стоило бы: отсутствующие метрики сужают сравнение, а не роняют его. Если у одного прогона нет, скажем, perplexity (потому что бэкенд ее не отдает), эта ось просто не участвует в сравнении этой пары — вместо того чтобы крашить анализ или штрафовать прогон нулем. На практике это значит, что можно мешать в одну кучу результаты с transformers (где есть perplexity и judge) и с llama-cpp (где их нет), и ничего не развалится.llm-bench pareto results/*.csvCLI: классификация по ПаретоWeb UI: сравнение прогонов (Δ% к базе)Числа в разных разделах сняты в разных прогонах, отсюда небольшой разброс — именно поэтому к каждому прогону пишется манифест.Рекомендатель: «дай мне лучший конфиг под мои ограничения»Pareto-фронт — это все еще несколько вариантов. Финальный шаг — сказать харнессу свои жесткие ограничения, и пусть выбирает сам:llm-bench recommend results/*.csv \ —max-vram-mb 4096 \ —max-p95-ms 1000 \ —max-ttft-ms 200 \ —min-sanity 1.0Логика в три шага, без магии:Фильтр. Выкидываем все прогоны, которые нарушают хоть одно ограничение. Причем — важная деталь — если по метрике задано ограничение, а у прогона этого значения нет (не померили), он тоже вылетает. Не знаешь VRAM, а я просил —max-vram-mb? Извини, в кандидаты не берем.Pareto среди выживших. Из тех, кто прошел фильтр, берем Pareto-оптимальные.Tiebreak по p95. Если оптимальных несколько — побеждает самый низкий p95.И — то, что я особенно ценю — он показывает причину исключения каждого кандидата:Recommendation__________________________________________________Backend : transformersModel : gpt2-mediumN : 10p95 : 512.02 mstok/s : 104.1Load : 9015.0 msTTFT p50 : 10.8 msVRAM : 875.0 MBSanity : 100.0%Task Q : N/APPL : N/AJudge : N/AWhy: lowest p95 among 1 candidate(s) passing all constraints; Pareto-optimal.Excluded (3)__________________________________________________ llama-cpp Llama-3.2-3B-Instruct-Q4_K_M.gguf → VRAM too high (2361.0 MB > 1000.0 MB) llama-cpp Llama-3.2-3B-Instruct-Q8_0.gguf → VRAM too high (3697.0 MB > 1000.0 MB) llama-cpp Bonsai-8B.gguf → VRAM too high (1499.0 MB > 1000.0 MB)Никакого «доверься мне, это лучший вариант». Видно, кого выкинули и за что.Web UI: рекомендацияCLI: рекомендацияНестандартные метрики: скорость бессмысленна, если модель сломаласьЭто та часть, из-за которой я и считаю, что обычные бенчмарки показывают неполную картину.Sanity checks: а оно вообще что-то осмысленное выдавало?Можно разогнать throughput до космоса, если модель в ответ генерирует пустые строки или зациклилась на одном токене. Поэтому каждый прогон считает простые структурные проверки:empty_output_count — сколько ответов оказались пустыми;min_output_chars / mean_output_chars — длины ответов;repeated_output_count — сколько ответов дословно повторяют друг друга;sanity_pass_rate — доля непустых ответов, в [0.0, 1.0].Честная оговорка, которую я зашил прямо в докстринг: при temperature=0.0 и циклической прогонке промптов повторы математически ожидаемы. Так что repeated_output_count — это сигнал деградации, а не абсолютный приговор. Это не оценка смысла, это детектор того, что под капотом что-то сломалось, пока мы радостно мерили скорость.Task-quality, perplexity и LLM-as-judgeКому нужна оценка посерьезнее — есть три уровня:quality_file: — YAML-рубрика с проверками вроде contains_all, regex, forbidden. Тупо, но работает и воспроизводимо.Self-perplexity — на собственных логитах модели.LLM-as-judge — модель в один forward-pass отвечает «да/нет» на фиксированный вопрос о своем же ответе, а score — это P(yes), вытащенная из конкурирующих логитов Yes/No через 2-way softmax (по сути sigmoid(yes − no)).Сразу отмечу, без иллюзий: judge тут — это модель, судящая саму себя одним фиксированным вопросом. Это не калиброванная preference-модель и не замена человеческой разметке. Я держу это как простой вариант, а не как истину в последней инстанции. Perplexity и judge, кстати, пока живут только на бэкенде transformers — там есть доступ к токен-левел логитам, а у GGUF через llama.cpp его так просто не возьмешь.Energy efficiency: tokens per JouleСамая моя любимая метрика, потому что про нее все забывают....

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1X. Главное происходит здесь.0827-06-2026
2Рекомендации по использованию AI при разработке открытого кода0724-06-2026
3Опубликована 67 редакция рейтинга самых высокопроизводительных суперкомпьютеров0824-06-2026
4В МГУ прошла одна из старейших конференций России по компьютерной лингвистике0526-06-2026
5Локальный Deep Research. Совершенствуем собственный ИИ-поисковик0704-06-2026
6Проект rars подготовил свободную реализацию RAR с поддержкой создания архивов5725-06-2026
7«Яндекс» представил открытое решение на базе большой языковой модели для ускорения миграции iOS-кода на Swift0705-05-2026
8Этика и академическая честность в эпоху нейросетей: где грань между умным помощником и плагиатом?0801-01-1970
9HOWTO: как установить и настроить собственный ИИ на игровом ПК5721-05-2023
10Погружаем модели в сказки русские, да рассказы древние – тестируем возможности Qwen и Whisper на дореволюционномъ0724-06-2026

Классификация: . Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 7. Источник: vk.com.