ГлавнаяБлогСравнение LLM: как я тестировал 6 моделей за 30 минут
Python

Сравнение LLM: как я тестировал 6 моделей за 30 минут

Сравнение LLM: тест 6 моделей на Python. Узнайте, как измерить скорость и стоимость, и выберите лучшую для своих задач. Читайте сейчас!

Al
Редакция Algolitalgolit.ru
8 мин чтения2 сентября 2026 г.

Сравнение LLM: почему я перестал выбирать модели наугад

Каждый раз, когда я разворачиваю модель за эндпоинтом, я принимаю одно и то же ленивое решение. Я беру то, что использовал в прошлый раз, или то, о чём недавно читал, и говорю себе, что позже проведу нормальное бенчмаркирование. Но «позже» никогда не наступает, потому что всегда есть что-то с реальным дедлайном. Сравнение задержек моделей кажется прокрастинацией, даже когда это не так. Я никогда этого не делал. Ни разу.

Поэтому я создал инструмент, который заставит меня это сделать. Один промпт, отправленный шести моделям одновременно, со стримингом в колонках, с временем до первого токена и стоимостью под каждой. Около 390 строк на Python. Код здесь, лицензия MIT, берите.

Затем я запустил его, и произошло три вещи, которых я не планировал.

Интеграция: две строки кода, и это самое неинтересное

Эндпоинт DigitalOcean поддерживает OpenAI-совместимый API, так что вот весь код подключения:

from openai import OpenAI
import os

client = OpenAI(
    base_url="https://inference.do-ai.run/v1/",
    api_key=os.environ["DIGITAL_OCEAN_MODEL_ACCESS_KEY"],
)

Все модели ниже используют этого клиента. Llama, DeepSeek, Mistral, Qwen, open-weight линейка OpenAI gpt-oss. Меняется только строка с именем модели.

Это и есть суть, и она реальна. Но вы уже знали, что OpenAI-совместимый эндпоинт будет работать как OpenAI-совместимый эндпоинт. Что я не знал — так это всё, что последует.

Одно примечание перед тем, как вставите сниппет: учётные данные — это model access key, созданный в Gradient AI Platform. Это не API-токен из Settings → API. Это разные вещи и разные страницы. (Хотя, как я выяснил позже, эндпоинт не так строг к этому различию, как документация.)

Шесть стримов без event loop: как я организовал параллельный запуск

Я хотел, чтобы колонки заполнялись одновременно. Настоящая гонка, а не шесть последовательных прогресс-баров, притворяющихся параллельными.

Чистый способ сделать это — один эндпоинт, который сам распределяет запросы и мультиплексирует ответы в одно соединение. Но я не пошёл чистым путём. Браузер открывает один EventSource на каждую модель:

GET /stream?model=<id>&prompt=<text>

Шесть моделей, шесть соединений, шесть независимых жизненных циклов. Ничего не объединяется. Flask остаётся синхронным, без async, без оркестрации, и весь путь стриминга — около сорока строк.

Я сделал так, потому что это проще, и я настаиваю на этом. Но причина, по которой я рад, что сделал именно так, оказалась иной, чем та, по которой я выбрал этот путь. К этому я ещё вернусь.

Предсказанная ошибка: очередь вместо зависания

Вот где я потерял час.

Я знал, что синхронный воркер gunicorn по умолчанию будет проблемой. Он обрабатывает одно соединение на процесс и держит его до завершения ответа. Для запросов, которые длятся 40 миллисекунд, это нормально. Стриминговые ответы остаются открытыми секунды, поэтому шесть одновременных стримов требуют шесть воркеров, иначе они встанут в очередь.

Я предсказывал, что страница зависнет. Она не зависает. Вот что реально происходит с шестью одновременными стримами на одном синхронном воркере:

МодельПервый токен
mistral-3-14B1250 мс
openai-gpt-oss-120b5326 мс
openai-gpt-oss-20b7278 мс
deepseek-3.28347 мс
llama-4-maverick9147 мс

Посмотрите на интервалы. Первый токен каждого стрима появляется примерно тогда, когда предыдущий стрим завершился. Это не медленные модели, это очередь. Шесть запросов, по одному за раз, 10.7 секунд на все.

Переключаемся на многопоточные воркеры:

web: gunicorn --worker-class gthread --threads 16 --timeout 120 'app:create_app()'

Теперь четыре из шести первых токенов попадают в окно 1.4 секунды вместо того, чтобы растянуться на десять секунд, и всё занимает 6.4 секунды.

Но вернитесь к первой таблице: самое интересное не в исправлении. Каждый из этих запросов завершился успешно. Правильные ответы, без таймаутов, без ошибок, ничего в логах. Если бы я запустил сломанную версию в продакшн, я бы не завёл баг против себя, а просто смотрел, как колонки заполняются одна за другой, заключил бы, что модели медленные, и пошёл писать кэширующий слой для проблемы, которая всё это время была в моём Procfile. Зависание было бы добрее. Зависание заставляет смотреть на сервер.

Каталог моделей врёт: список доступных моделей не совпадает с реальностью

Выбор моделей построен на основе GET /v1/models, потому что хардкодить список моделей — это способ зашить мёртвую модель.

Этот вызов возвращает 72 модели. Мой аккаунт может вызвать шесть.

Всё от Anthropic, плюс GPT-4o и o3, возвращают:

403 - {'error': {'message': 'this model is not available for your subscription tier', 'type': 'forbidden_error'}}

В ответе /v1/models нет ничего, что подсказало бы, какая модель доступна. Ни флага доступности, ни поля с тарифом, ни намёка. Вы узнаёте это, вызвав модель и прочитав 403.

Именно так я и отправил в продакшн сломанный дефолт. В моём предвыбранном списке был anthropic-claude-haiku-4.5, потому что он есть в опубликованном каталоге и на странице с ценами, с реальной ставкой за токен. И ничто не подсказало мне проверить, могу ли я вызвать эту модель со своего аккаунта. Первый реальный запуск — и эта колонка покраснела у меня на глазах.

Теперь вспомните про шесть независимых соединений. Мёртвая модель вернула 403, показала ошибку в своей колонке, а остальные пять продолжили стримить, как ни в чём не бывало. Я создал эту изоляцию для гипотетических сбоев. Первый реальный сбой случился через шестьдесят секунд после первого контакта с API.

Если вы создаёте что-то, что заполняет меню из этого эндпоинта, и документация DigitalOcean прямо на него ссылается, предполагайте, что большая часть того, что возвращается, недостижима.

Мелочь о доступе: разные ключи, но один работает везде

Документация утверждает, что model access key и API-токен — это разные учётные данные. Так и есть. Но API-токен вида dop_v1_... успешно аутентифицируется на inference.do-ai.run. Я проверил это и на api.digitalocean.com/v2/account — работает в обоих местах.

Всё равно используйте узкий ключ. Утёкший model access key стоит вам немного денег на инференс. Утёкший API-токен стоит вам аккаунта.

Результаты: цифры, которые меня удивили

Три формы промптов (короткий фактологический, длинное объяснение, генерация кода), шесть моделей, по три запуска каждой. 54 вызова, max_tokens=512, регион nyc, запуск с ноутбука в Европе около десяти вечера.

Медианы по всем девяти запускам на модель:

МодельTTFTОбщее времяСтоимостьЗапусков без текста
mistral-3-14B533 мс3.3 с$0.0001080/9
llama-4-maverick676 мс17.9 с$0.0003620/9
deepseek-3.2869 мс5.4 с$0.0004160/9
openai-gpt-oss-20b1792 мс4.6 с$0.0002352/9
openai-gpt-oss-120b4797 мс16.7 с$0.0003670/9
qwen3.5-397b-a17b9332 мс32.0 с$0.0009957/9

Mistral 14B выиграл по всем измеренным параметрам. Самый быстрый до первого токена, самый быстрый в целом, самый дешёвый за запуск, и отвечал каждый раз. Здесь нет кривой компромиссов. Для этой нагрузки дорогие модели не дали мне ничего. Такого результата я не ожидал и не угадал бы за день до запуска.

Время до первого токена варьировалось от 533 мс до 9.3 секунд. Это разброс в 17 раз. Если модель стоит за чем-то, чего ждёт человек, этот разрыв решает, работает ли функция. И это невозможно угадать по карточке модели.

Пустой ответ за полную цену: скрытая проблема reasoning-моделей

Теперь последняя колонка.

Ничего не упало. Девять запусков вернулись пустыми.

Все 54 вызова успешны. Без исключений, без не-200 кодов, без таймаутов. Прогоните это через любой мониторинг — будет чисто.

Девять из этих вызовов не вернули читаемого текста вообще. За полную цену.

qwen3.5-397b-a17b сделал это семь раз из девяти. Это также самая дорогая модель в гонке, примерно в 9 раз дороже Mistral, и самая медленная — 32 секунды. Тридцать две секунды, самый дорогой билет, пустая коробка.

Это reasoning-модель. Что произошло: она потратила 487 из 512 токенов бюджета на размышления, не оставив места для написания ответа, и транслировала все эти размышления в поле delta.reasoning_content, которое не входит в схему OpenAI и потому невидимо для всех OpenAI-совместимых клиентов, включая мой. Запрос успешен. Токены списаны. Коробка пуста.

completion_tokens: 512
reasoning_tokens: 487
content: 0 символов

Вы можете заплатить полную цену за тишину, и ваши дашборды назовут это успехом.

Я пропатчил приложение, чтобы колонка без контента, но с ненулевыми reasoning_tokens, объясняла сама себя, а не выглядела сломанной. Поднимите max_tokens — и Qwen отвечает. Но общая версия этой проблемы хуже, чем мой конкретный баг: если ваша оценка смотрит на задержку и статус-коды, она структурно неспособна увидеть этот сбой. Вы должны смотреть на то, что вернулось.

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

Счёт: 0,0185 доллара за все 54 вызова

Я потратил больше времени на чтение страницы с ценами, чем стоило провести эксперимент. Это переосмыслило для меня всё упражнение. Причина, по которой никто не измеряет это перед выбором модели, — не стоимость и не время. Причина в том, что нет готового инструмента. Теперь он есть.

Вывод: стоит ли использовать этот подход снова

Для выбора модели под конкретную задачу — да. Теперь у меня есть обоснованное мнение о Mistral 14B, которого не было неделю назад, и оно основано на данных, а не на случайно прочитанном треде.

Трения были реальными, но небольшими. Каталог, который рекламирует модели, недоступные для вашего аккаунта. Различие в учётных данных, которое на практике менее строгое, чем описано. Ловушка с конфигурацией воркеров, которая ударит по любому стриминговому приложению на любой платформе. Только первая проблема действительно принадлежит DigitalOcean, и именно её я хотел бы исправить. Поле available в /v1/models — это полдня работы, и оно сэкономило бы всем этот 403.

Что я рекомендую — это скучная часть, которую я пропустил в начале. Сравнение четырёх провайдеров обычно означает четыре SDK с разными конвенциями стриминга, четыре ключа в разных местах, четыре дашборда и четыре счёта в конце месяца. К тому времени, как эта обвязка заработает, вы потратите больше усилий, чем на исходный вопрос. Здесь это свелось к редактированию списка строк.

Оговорки: n=3, один регион, один вечер, один тарифный план, один набор промптов, ноутбук в Европе, бьющий в дата-центр в Нью-Йорке. Это не бенчмарк. Это среда одного разработчика. Смысл не в том, чтобы дать вам авторитетные цифры, а в том, чтобы сделать процесс дешёвым, чтобы вы пошли и получили свои собственные — на своих промптах, на своём аккаунте.

Практический вывод: что делать прямо сейчас

Возьмите код с GitHub (ссылка в начале), создайте model access key в DigitalOcean, вставьте свой промпт и запустите сравнение на своих задачах. Уделите этому 30 минут — и вы узнаете, какая модель действительно лучшая для вашего случая, а не та, что популярна в твиттере.

#сравнение LLM#бенчмаркинг#OpenAI API#Python#DigitalOcean
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →