Перейти к содержимому
13 мар. 2025 г.·7 мин чтения

Noisy neighbor в мультиарендном LLM: квоты и параллелизм

Noisy neighbor в мультиарендном LLM возникает, когда один продукт съедает общие лимиты. Разберем квоты, параллелизм и пулы моделей без лишней теории.

Noisy neighbor в мультиарендном LLM: квоты и параллелизм

Где возникает noisy neighbor

Проблема noisy neighbor в мультиарендном LLM-контуре появляется там, где несколько продуктов делят один запас пропускной способности. На схеме все выглядит просто: один эндпоинт, общая маршрутизация, общий бюджет токенов. В работе все жестче. Один сервис за несколько минут может съесть почти весь RPM или TPM, и соседние запросы сразу начинают ждать, получать 429 или уходить в деградацию.

Конфликт почти никогда не сводится только к числу запросов. Важнее то, как долго запрос держит инфраструктуру занятой. Короткая классификация проходит быстро. Большой промпт с длинным контекстом и длинной генерацией держит воркер заметно дольше. Если таких запросов много, очередь растет даже тогда, когда формальный лимит по RPM еще не исчерпан.

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

Обычно арендаторы делят сразу несколько ресурсов: лимит запросов в минуту, лимит токенов в минуту, воркеры, соединения, очередь и общий набор запасных маршрутов. С фолбэками ситуация особенно неприятная. Если один продукт дает всплеск нагрузки и основной маршрут упирается в лимиты, трафик уходит на резерв. Если этот резерв общий, перегрузка быстро переезжает на соседние сервисы. Проблема перестает быть локальной.

Даже при работе через единый эндпоинт, совместимый с OpenAI, риск сам не исчезает. Если всем продуктам выдали общие квоты, общий параллелизм и один набор фолбэков, они начнут мешать друг другу в первый же заметный пик. Изоляция нужна не для порядка на бумаге, а чтобы всплеск в одном месте не ломал чужой SLA.

Что арендаторы делят между собой

Если несколько команд ходят через один шлюз, они делят не только общий эндпоинт. Они делят все узкие места на пути запроса.

Первый слой - лимиты у внешнего провайдера или у внутреннего кластера моделей. Обычно это RPM, TPM и число одновременных запросов. Для чата с короткими ответами узким местом часто становится число запросов в минуту. Для суммаризации или RAG с длинным контекстом потолок быстрее упирается в токены. Два арендатора могут послать одинаковое число запросов и дать нагрузку, которая отличается в разы.

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

Третий слой - доступ к дефицитным моделям. Дорогие reasoning-модели, новые frontier-модели и редкие fine-tuned варианты почти всегда ограничены сильнее, чем массовые. Если не отделить такие вызовы по пулам, один аналитический продукт может забрать почти всю доступность и оставить, например, саппорт-бот без ответа в час пик.

Четвертый общий ресурс - деньги. Суточный бюджет спасает от резкого всплеска, месячный - от тихого перерасхода. Это хорошо видно, когда одна команда запускает пакетную обработку вечером, а утром другая получает отказ уже не из-за SLA, а из-за исчерпанного лимита расходов.

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

Какие сигналы видны в метриках

Средняя задержка почти всегда успокаивает и часто скрывает проблему. Смотрите не на общую картину, а на срезы по продукту, пулу моделей, очереди и провайдеру. Если несколько команд ходят в один общий эндпоинт, ровный p50 еще ничего не значит. Один продукт уже может терять SLA, пока средняя линия выглядит "нормально".

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

Второй сигнал - волны 429 и таймаутов сразу после чужого релиза. Команда выкатила новую функцию с автосуммаризацией или массовой классификацией, и через несколько минут ошибки пошли у соседнего продукта. Если на одном графике совместить время релиза, занятость слотов параллелизма и долю ошибок по арендаторам, связь обычно видна сразу.

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

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

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

Как разделить продукты по классам нагрузки

Одинаковые лимиты для всех продуктов почти всегда ломают изоляцию. Чат с ответом за 2-3 секунды и ночная обработка тысяч документов живут по разным правилам, даже если ими владеет одна команда.

Смотрите не на оргструктуру, а на поведение трафика. Один и тот же отдел может вести чат-помощника, пакетную суммаризацию и поиск по длинным договорам. Это уже не один сервис, а три разных класса нагрузки.

Сначала отделите интерактивные сценарии. Это все, где пользователь ждет ответ на экране: чат поддержки, copilot в CRM, подсказки оператору, поиск с генерацией. Для них важны низкая задержка, предсказуемый хвост latency и жесткий лимит по одновременным запросам.

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

Продукты с длинным контекстом тоже лучше собрать в свой класс. Они потребляют больше токенов на входе, дольше держат соединение и сильнее давят на общие пулы моделей. Даже небольшой поток таких запросов может испортить задержку для коротких чатов.

Отдельно пометьте сценарии, где нельзя проседать по качеству модели. Разбор претензий, анализ договоров или ответы в банковском кабинете не стоит молча переводить на более слабую модель только потому, что она сейчас свободна.

На практике удобно держать четыре класса:

  • A - интерактивный трафик с жестким SLA и короткими ответами
  • B - интерактивный трафик, где допустима чуть большая задержка
  • C - длинный контекст и тяжелые запросы
  • D - пакетные и фоновые задачи

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

Как настроить изоляцию

Отделите чат от длинных цепочек
Оставьте чатам свой маршрут в RU LLM, чтобы длинные прогоны не тянули их вниз.

Начинать стоит не с лимитов, а с профиля нагрузки. Для каждого продукта посмотрите RPM, TPM, среднюю и p95 длину ответа, а для стриминга еще и время жизни соединения. Два сервиса с одинаковым RPM могут вести себя совсем по-разному: один чат держит слот 30 секунд, а короткая классификация освобождает его почти сразу.

После этого задайте квоты в трех слоях. Сначала на продукт, чтобы один тип нагрузки не съел общий бюджет. Потом на арендатора, чтобы крупный клиент не давил остальных. И только потом на модель, потому что узкое место часто находится не в шлюзе, а в конкретном пуле или у внешнего провайдера.

Рабочая схема обычно выглядит так:

  • у продукта есть свой потолок по RPM, TPM и числу одновременных запросов
  • у арендатора внутри продукта есть собственный лимит, ниже общего
  • у каждой модели или пула есть отдельный предел, даже если продукту еще есть куда расти
  • стриминг считается отдельно от обычных вызовов
  • у каждого маршрута есть основной и резервный пул

На практике ограничение по параллелизму часто спасает лучше, чем один общий rate limit. Общий лимит защищает провайдера. Изоляция по concurrency защищает соседние продукты.

Как делить модели и пулы

Если все продукты ходят в один и тот же пул, проблема появляется почти сразу. Один длинный агентный прогон или ночная пакетная обработка легко съедает параллелизм, и обычный чат внезапно отвечает вдвое дольше. Делить нужно не только по арендаторам, но и по типу работы.

Чаты лучше держать в отдельном пуле. У них короткий запрос, жесткое ожидание по задержке и много мелких вызовов. Агентные сценарии ведут себя иначе: они делают серию шагов, повторные вызовы, иногда запускают инструменты и дольше держат слот. Если смешать эти два потока, чат пострадает первым, хотя сам по себе он может быть легким.

Тяжелые модели не стоит ставить в общий маршрут "на всякий случай". Лучше отвести их под редкие, но важные задачи: сложную аналитику, длинное суммирование, проверку спорных ответов. Такой пул можно держать меньше по размеру, но с отдельными лимитами и более строгими правилами запуска. Тогда дорогой и медленный запрос не вытеснит повседневный трафик.

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

Пакетную нагрузку полезно уводить не только в свой пул, но и в свое окно запуска. Ночью или в часы низкой нагрузки она мешает меньше. Если запускать такие задачи рядом с дневным продом, средний график может выглядеть терпимо, а пользователи уже будут ждать лишние секунды.

Тестовый трафик с продом смешивать тоже не стоит. Нагрузочные прогоны, эксперименты с промптами и A/B-тесты создают шум, который сложно читать в метриках. Отдельный тестовый пул сразу показывает, что именно сломалось: новая версия продукта или общий дефицит ресурсов.

В шлюзе вроде RU LLM это обычно раскладывают через разные маршруты, лимиты параллелизма и списки разрешенных моделей для каждого продукта. Подход простой: чатам дают быстрый и предсказуемый путь, агентам - контролируемый запас, пакетным задачам - отдельную очередь, а резерв не трогают без причины.

Ошибки, которые ломают изоляцию

Замените только адрес API
Переведите трафик на RU LLM и сохраните SDK, код и промпты.

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

Самая частая ошибка - один общий rate limit на весь контур. Он защищает внешний API или провайдера, но не защищает арендаторов друг от друга. Если пакетная обработка внезапно забирает весь бюджет запросов, чат, где люди ждут ответ за пару секунд, начинает получать 429 и расти в очереди.

Вторая ошибка - считать только запросы и не считать токены. Для LLM один запрос на 300 токенов и один запрос на 40 000 токенов - это разная нагрузка. Они по-разному занимают очередь, воркеры и бюджет. Когда квота живет только в RPS, длинные задачи тихо выдавливают короткие, хотя на графике число запросов выглядит нормальным.

Третья ошибка - одинаковый таймаут для всех задач. Короткая классификация, поиск с кратким ответом и длинная генерация отчета работают в разном темпе. Слишком короткий таймаут обрывает длинные запросы и запускает новые повторы. Слишком длинный держит занятыми соединения и параллелизм там, где ответ мог бы прийти быстро.

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

И наконец, систему быстро добивают повторы без паузы и без верхнего лимита. Один сбой на стороне модели превращается в лавину одинаковых запросов. Через минуту вы уже боретесь не с причиной, а со своими retry.

Минимальная защита простая: считайте и запросы, и токены; задавайте таймауты по типу задачи; ограничивайте число повторов и ставьте паузу между ними. Если изоляция уже трещит, не начинайте с полной переделки. Сначала уберите общий лимит, потом разделите длинные и короткие задачи, и только после этого трогайте фолбэки.

Пример с тремя продуктами

Одна и та же LLM-платформа часто обслуживает сразу несколько сценариев, но держать их в одном котле - плохая идея. Если чат поддержки, внутренний помощник и ночная аналитика делят один пул и одни лимиты, самый шумный поток быстро съедает очередь.

Представим три продукта в одной компании. Чат поддержки отвечает клиенту прямо в диалоге, и задержка выше 2-3 секунд там уже заметна. Внутренний помощник пишет длинные черновики писем, инструкций и отчетов, поэтому терпит чуть большую паузу. Ночная аналитика прогоняет большие документы пачками и почти никогда не требует мгновенного ответа.

Разнести их лучше так. Чат поддержки получает свой пул моделей с короткой очередью и жесткий лимит параллелизма для каждого арендатора. Внутренний помощник идет в отдельный пул, где можно держать больше контекста и длиннее генерацию, но ограничить число одновременных запросов. Пакетная аналитика уходит в третий пул с низким приоритетом и окном запуска, например ночью или в часы спада.

Утром внутренний помощник почти всегда дает всплеск. Сотрудники приходят на работу, открывают CRM, просят сделать сводки и черновики. Если этот поток не отделен, он забивает декодирование и очередь, а чат поддержки начинает отвечать медленнее. Пользователь в чате чувствует это сразу, хотя сам поток поддержки может быть небольшим.

Отдельные квоты решают проблему с двух сторон. Чат сохраняет свой запас по запросам и по одновременным вызовам. Внутренний помощник может упереться в собственный потолок, но не тянет за собой остальную систему. Пакетную нагрузку еще лучше не пускать без расписания. Если аналитика отправит 500 длинных документов в тот же час, когда у поддержки пик обращений, конфликт почти гарантирован.

Проверка перед запуском

Держите данные в РФ
Храните логи и бэкапы в РФ и маскируйте PII в каждом запросе.

Перед релизом полезно пройти короткую проверку на изоляцию, а не надеяться на общие лимиты. Один шумный продукт легко забирает запас по токенам и соединениям, и тогда страдает не он один.

Для каждого продукта заранее задайте три числа: RPM, TPM и предел одновременных запросов. Эти лимиты должны жить отдельно, даже если все сервисы ходят в один и тот же API. Если цифры взяты с большим запасом "на всякий случай", проблема проявится уже в первый пиковый час.

Критичный трафик не стоит держать в общем пуле с пакетными задачами, переиндексацией и фоновыми саммари. Платежный сценарий, поиск по базе знаний и ночная генерация отчетов дают очень разный профиль нагрузки. Для важного потока нужен свой пул моделей или хотя бы резерв по параллелизму, который фоновые задачи не могут занять.

Отдельно проверьте поведение пакетных заданий. Они должны ждать свою очередь, снижать скорость и делать редкие повторы с паузой. Если пакетная задача после 429 сразу шлет новую волну запросов, она сама разгоняет очередь и ломает изоляцию лучше любого внешнего пика.

Метрики тоже лучше проверить до запуска, а не после жалоб от команд. На дашборде должны быть срезы по продукту, модели и провайдеру. Иначе вы увидите только общий рост задержки, но не поймете, кто именно съел лимит и где включился фолбэк.

Хороший минимум для алертов такой: очередь растет дольше обычного, доля 429 выходит за ваш порог, число фолбэков резко подскакивает, а latency у одного продукта уходит вверх при нормальном общем трафике. Такой набор ловит проблему раньше, чем пользователи начинают писать в поддержку.

С чего начать в проде

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

Эта таблица быстро показывает перекос. Чат поддержки может жить в одном классе с жестким лимитом на длинный контекст, а внутренний аналитический copilot - в другом, где запросов меньше, но каждый запрос тяжелее. Если оба продукта сидят в одном пуле без правил, один всплеск легко съест чужую емкость.

Дальше нужен один честный нагрузочный тест. Не средний, а неприятный: дайте всплеск запросов и добавьте длинный контекст. Такой тест быстро покажет, где ломается изоляция: в RPM, в числе одновременных запросов, в очереди на конкретную модель или в фолбэке на более дорогой маршрут.

Еще на старте стоит назначить владельцев правил. Кто меняет лимиты по продуктам без долгих согласований? Кто утверждает фолбэк, когда основной пул перегружен? Какие метрики команда смотрит после релиза? Если на эти вопросы нет ответа, схема быстро расползается. Один продукт получает "временное" повышение квоты, второй добавляет новый сценарий с большим контекстом, и через месяц уже непонятно, почему задержка выросла у всех.

Если нужен совместимый с OpenAI шлюз в РФ, смотрите не только на список моделей. Полезно проверить, как сервис задает квоты по арендаторам и продуктам, как хранит аудит-трейлы и как разводит пулы моделей. В RU LLM это можно делать через единый эндпоинт, сохраняя прежние SDK, код и промпты, а для команд с требованиями по 152-ФЗ важны хранение логов и бэкапов в РФ, маскирование PII и встроенные метки AI-Law.

После запуска не оставляйте лимиты "как есть". Пересматривайте их после каждого крупного релиза, подключения нового продукта и заметного роста трафика. Хорошая схема изоляции не живет годами в одном документе. Она меняется вместе с реальной нагрузкой.

Часто задаваемые вопросы

Что такое noisy neighbor в LLM-контуре?

Это ситуация, когда один продукт забирает общий запас RPM, TPM, слотов параллелизма или соединений, а соседние сервисы начинают ждать и ловить 429. Снаружи у вас может быть один общий endpoint, но внутри короткий чат и длинная суммаризация давят на систему совсем по-разному.

Почему одного лимита по RPM мало?

Нет. RPM защищает только число вызовов, а не их вес. Один запрос с коротким ответом освобождает слот быстро, а длинный контекст и длинная генерация держат воркер, очередь и бюджет токенов заметно дольше. Поэтому вместе с RPM почти всегда нужны лимиты по TPM и по одновременным запросам.

Чем стриминг мешает соседним продуктам?

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

Какие метрики лучше всего показывают noisy neighbor?

Смотрите не на среднюю задержку, а на p95 и p99 по каждому продукту, пулу моделей и провайдеру. Еще полезно держать перед глазами длину очереди, занятые слоты параллелизма, долю 429, число фолбэков и стоимость на тысячу успешных ответов. Если один продукт резко просел, общая средняя часто это прячет.

Как правильно разделить продукты по классам нагрузки?

Делите их по поведению трафика, а не по отделам. Обычно хватает интерактивных сценариев с жестким SLA, интерактивных сценариев с более мягкой задержкой, тяжелых запросов с длинным контекстом и пакетных задач. Так проще выдать разным потокам свои квоты, таймауты и пулы моделей.

Какие лимиты стоит настроить в первую очередь?

Сначала задайте для каждого продукта свой потолок по RPM, TPM и числу одновременных запросов. Потом добавьте лимиты на арендатора внутри продукта и отдельные пределы на конкретные модели или пулы. Стриминг лучше считать отдельно от обычных вызовов, иначе длинные ответы быстро съедят общий запас.

Зачем выносить пакетные задачи в отдельный пул?

Пакетная обработка легко забирает параллелизм и раздувает очередь, хотя пользователи ее не видят в реальном времени. Если вы держите ее отдельно, интерактивный трафик сохраняет свою задержку даже во время массовой переобработки документов. Лучше еще дать пакетным задачам свое окно запуска, например ночью.

Нужно ли разделять фолбэки, если основной маршрут общий?

Да, иначе авария просто переедет в другую точку. Когда все классы нагрузки падают в один резерв, чат, аналитика и ночные джобы начинают конкурировать уже там. Намного безопаснее держать отдельные резервные пулы или хотя бы жесткие лимиты на фолбэк для каждого класса.

Какие ошибки чаще всего ломают изоляцию?

Чаще всего все ломает один общий rate limit на весь контур, учет только запросов без токенов, одинаковые таймауты для разных задач и повторы без паузы. Еще часто мешает общий пул для чатов, агентных цепочек и пакетных джоб. Такая схема работает только пока трафик ровный.

С чего начать, если у меня уже один общий OpenAI-совместимый endpoint?

Начните с простой таблицы: продукт, класс нагрузки, пул моделей. Затем дайте каждому продукту свои RPM, TPM и concurrency, вынесите пакетные задачи из живого трафика и прогоните неприятный нагрузочный тест с длинным контекстом и всплеском запросов. Если вы работаете через OpenAI-совместимый шлюз вроде RU LLM, менять SDK и промпты не нужно, но правила квот и пулов все равно надо настроить отдельно.