惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
美团技术团队
H
Hacker News: Front Page
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
Simon Willison's Weblog
Simon Willison's Weblog
T
The Exploit Database - CXSecurity.com
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
C
CXSECURITY Database RSS Feed - CXSecurity.com
Application and Cybersecurity Blog
Application and Cybersecurity Blog
A
About on SuperTechFans
F
Fortinet All Blogs
量子位
GbyAI
GbyAI
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Hacker News
The Hacker News
AWS News Blog
AWS News Blog
Forbes - Security
Forbes - Security
Help Net Security
Help Net Security
I
InfoQ
有赞技术团队
有赞技术团队
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
S
Secure Thoughts
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Webroot Blog
Webroot Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园_首页
C
Check Point Blog
T
Troy Hunt's Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Latest news
Latest news
P
Proofpoint News Feed
Jina AI
Jina AI
Last Week in AI
Last Week in AI
Martin Fowler
Martin Fowler
雷峰网
雷峰网
博客园 - Franky
L
LangChain Blog
罗磊的独立博客
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
D
Docker
G
GRAHAM CLULEY
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Я устал писать одноразовые скрипты для бенчмарков LLM и собрал харнесс, который сам считает Pareto-front
Alexey · 2026-06-28 · via Все публикации подряд на Хабре

Средний

9 мин

151

LLM inference benchmark

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 — модель не нужна вообще

transformers

AutoModelForCausalLM от HF, CUDA

llama-cpp

GGUF-квантизация, pre-built CUDA-wheel, без nvcc

openai

любой сервер с /v1/chat/completions (Ollama, LM Studio, vLLM)

onnx

ONNX Runtime через Optimum, INT8/FP16

vllm

high-throughput движок, Linux only

Дальше — самое интересное, что под капотом.

Под капотом: YAML, Run Matrix и Sweep

Один эксперимент = один YAML

Базовая единица — конфиг одного прогона. Никакой магии, просто декларация того, что мы меряем:

# configs/llama-cpp-gpu.yaml
backend: llama-cpp
model: ~/models/Llama-3.2-3B-Instruct-Q4_K_M.gguf
requests: 20
warmup_requests: 2
prompts_file: data/prompts/smoke.txt
repeats: 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 42

Run Matrix: перестать копипастить конфиги

Один прогон — это скучно. Реальный вопрос всегда звучит как «а сравни-ка мне вот эти пять вариантов». Для этого есть матрица. В явном виде это просто список прогонов с общей папкой результатов:

results_dir: results

runs:
  - name: quant-q4km
    config: configs/llama-cpp-q4km-best.yaml
  - name: quant-q8
    config: configs/llama-cpp-q8-best.yaml
llm-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.yaml
results_dir: results

sweep:
  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)

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/*.csv
CLI: классификация по Парето

CLI: классификация по Парето

Web UI: сравнение прогонов (Δ% к базе)

Web UI: сравнение прогонов (Δ% к базе)

Числа в разных разделах сняты в разных прогонах, отсюда небольшой разброс — именно поэтому к каждому прогону пишется манифест.

Рекомендатель: «дай мне лучший конфиг под мои ограничения»

Pareto-фронт — это все еще несколько вариантов. Финальный шаг — сказать харнессу свои жесткие ограничения, и пусть выбирает сам:

llm-bench recommend results/*.csv \
  --max-vram-mb 4096 \
  --max-p95-ms 1000 \
  --max-ttft-ms 200 \
  --min-sanity 1.0

Логика в три шага, без магии:

  1. Фильтр. Выкидываем все прогоны, которые нарушают хоть одно ограничение. Причем — важная деталь — если по метрике задано ограничение, а у прогона этого значения нет (не померили), он тоже вылетает. Не знаешь VRAM, а я просил --max-vram-mb? Извини, в кандидаты не берем.

  2. Pareto среди выживших. Из тех, кто прошел фильтр, берем Pareto-оптимальные.

  3. Tiebreak по p95. Если оптимальных несколько — побеждает самый низкий p95.

И — то, что я особенно ценю — он показывает причину исключения каждого кандидата:

Recommendation
__________________________________________________

Backend  : transformers
Model    : gpt2-medium
N        : 10
p95      : 512.02 ms
tok/s    : 104.1
Load     : 9015.0 ms
TTFT p50 : 10.8 ms
VRAM     : 875.0 MB
Sanity   : 100.0%
Task Q   : N/A
PPL      : N/A
Judge    : N/A

Why: 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: рекомендация

Web UI: рекомендация

CLI: рекомендация

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

Самая моя любимая метрика, потому что про нее все забывают. Throughput — это хорошо, но если конфиг A на 10% быстрее конфига B, а жрет при этом в полтора раза больше ватт, то в продакшене с тысячами GPU это «чуть быстрее» превращается в неприятный счет за электричество.

Меряется в два захода:

  1. GPU через nvidia-smi. Фоновый поток поллит power.draw каждые 0.5 с, в конце берем среднюю мощность и умножаем на длительность окна → джоули.

  2. CPU через Intel RAPL. Читаем счетчик energy_uj из /sys/class/powercap/... на входе и выходе, берем дельту. Тут пришлось повозиться с переполнением счетчика (он циклический) — если дельта отрицательная, добавляем max_energy_range_uj. Классический edge-case, который ловишь только когда долгий прогон дает отрицательную энергию и ты сидишь и думаешь, в какой момент нарушил законы термодинамики.

На выходе — energy_joules и tokens_per_joule. Если ни один источник не читается — там честный None, а не выдуманный ноль.

График: tokens/joule по бэкендам (gpt2-medium — модель другого класса, межбэкендный tok/J здесь лишь для иллюстрации разброса)

График: tokens/joule по бэкендам (gpt2-medium — модель другого класса, межбэкендный tok/J здесь лишь для иллюстрации разброса)

Реальные цифры

Чтобы это была не «тулза ради тулзы», вот живой результат с моего железа — RTX 3050 Laptop (4 GB), Llama 3.2 3B Instruct, llama.cpp, все 28 слоев на GPU:

Квантизация

p50 (ms)

p95 (ms)

tok/s

VRAM (MiB)

Sanity

Q4_K_M

899

910

55.6

2361 (58%)

100%

Q8_0

1186

1210

41.9

3697 (90%)

100%

Q4_K_M выходит в 1.33× быстрее и ест в 1.57× меньше VRAM при той же sanity. На карте с 4 ГБ это разница между «работает с запасом» и «впритык».

А вот свип по n_gpu_layers — наглядно, почему частичная выгрузка слоев это ловушка:

Слоев на GPU

p95 (ms)

tok/s

VRAM (MiB)

0 / 28 (только CPU)

3093

17.5

655

20 / 28 (частично)

1420

36.6

1829

28 / 28 (все на GPU)

984

51.4

2361

Полная выгрузка — в 3.1× быстрее, чем чистый CPU.

Web UI: нормализованное сравнение метрик (выше — лучше)

Web UI: нормализованное сравнение метрик (выше — лучше)

Воспроизводимость: JSON-манифест на каждый прогон

Это то, что отличает харнесс от скрипта на коленке. На каждый прогон пишется JSON-манифест, по которому результат можно защитить на ревью:

  • git-коммит и git_dirty — был ли рабочий каталог грязным в момент прогона;

  • SHA-256 конфига и файла промптов — чтобы доказать, что мерили именно то, что думали;

  • полный фингерпринт окружения — Python, OS, CPU, версия драйвера, имя GPU, объем VRAM, версии torch/transformers/vllm.

Все это собирается «мягко»: на CPU-only машине без nvidia-smi манифест все равно запишется, просто GPU-поля будут None. Никаких падений из-за того, что на CI нет видеокарты.

Как запустить за две минуты

Самый быстрый путь — поднять дашборд и кликать:

pip install git+https://github.com/Happynood/llm-inference-benchmark.git
llm-bench serve
# http://localhost:8080 → жмем "+ New Run"
Web UI: список прогонов

Web UI: список прогонов

Web UI: карточка прогона

Web UI: карточка прогона

Web UI: форма нового прогона

Web UI: форма нового прогона

Или из исходников через uv (рекомендуемый вариант):

git clone https://github.com/Happynood/llm-inference-benchmark
cd llm-inference-benchmark
uv sync && uv run llm-bench serve

С GPU (NVIDIA, CUDA 12.x, nvcc не нужен — ставится pre-built wheel):

make setup-gpu     # детект GPU + CUDA-wheel + прокидка runtime-либ
make webui-gpu     # дашборд на :8080

Для CI есть отдельный гейт на регрессии — падает, если любая метрика просела больше чем на N%:

llm-bench diff results/before.csv results/after.csv --fail-on-regression 5

В дашборде, помимо очевидного, есть live-стриминг stdout через Server-Sent Events (видно, как идет прогон), вкладка Recommend с формой ограничений и интерактивный Pareto-scatter на Plotly с выбором осей.

Web UI: Pareto-чарт (оси переключаются, фронт подсвечен)

Web UI: Pareto-чарт (оси переключаются, фронт подсвечен)

Web UI: лидерборд

Web UI: лидерборд

Честно о костылях и планах

Раз уж обещал без глянца — вот что меня самого до сих пор раздражает:

  • CUDA 12/13 и pre-built wheel. Чтобы cu124-wheel завелся на свежем драйвере, приходится руками собирать LD_LIBRARY_PATH из nvidia/*.so внутри venv. Это те самые танцы с бубном, которые я спрятал в make-таргет, но осадочек остался.

  • peak_cuda_memory_mb = 0.0 у llama.cpp. Он использует свой CUDA-аллокатор, а не PyTorch’евский, поэтому метрика из torch всегда ноль. Реальную память приходится брать из peak_vram_memory_mb (поллинг nvidia-smi). Грабли, на которые наступит каждый, кто сравнит две колонки.

  • TTFT только в streaming-режиме, а perplexity и judge — только transformers. Это не лень, это ограничения бэкендов, но в документации про это надо помнить.

В планах — расширять список бэкендов (присматриваюсь к TensorRT-LLM и SGLang) и докрутить контаминация-устойчивую оценку качества на реальных датасетах (wildchat, gsm8k, humaneval уже подтягиваются, но как полноценную метрику качества это пока не доведено).

Вопрос к залу

И вот тут мне правда нужно мнение тех, кто гоняет инференс в проде.

Насколько вообще честно сравнивать tokens_per_joule между бэкендами? У llama.cpp свой CUDA-аллокатор, vLLM агрессивно занимает VRAM под KV-кэш, transformers живет по-своему — и nvidia-smi power.draw меряет потребление всей карты, а не конкретного процесса. Получается, на шумном GPU (где крутится что-то еще) метрика энергии превращается в тыкву.

Я вижу три пути: (а) требовать эксклюзивный GPU на время замера, (б) вычитать idle-baseline мощности, (в) забить и считать энергию валидной только для изолированных прогонов. Каждый вариант что-то ломает.

Как вы меряете энергоэффективность инференса — и меряете ли вообще? Не делаю ли я ошибку, считая p95 при фиксированной concurrency, когда в реале нагрузка open-loop с переменным arrival-rate? Расскажите в комментариях, как у вас устроено.


Репозиторий: github.com/Happynood/llm-inference-benchmark — MIT, PR и issue приветствуются. Если потыкаете на своем железе и числа сойдутся (или не сойдутся) — это самый ценный фидбек.