Продакшн шлюзов LLM: архитектура, компромиссы и суровые уроки — Каниш Мануджа, Twilio
AAI Engineer
Computing/SoftwareManagementInternet Technology
Transcript
00:00:00Я Ганеш Мануджа, ведущий инженер в Twilio. Давайте начнем с небольшого опроса поднятием рук.
00:00:20Кто здесь видел сообщение «Что-то пошло не так, пожалуйста, попробуйте снова»?
00:00:27Что ж, у нас есть несколько счастливчиков и те, кто плотно пообедал.
00:00:33Итак, за этим простым сообщением на самом деле скрывается очень сложная система, которая выдает его вам, несмотря на сбои у провайдеров моделей.
00:00:45И именно об этом мы сегодня будем говорить в контексте запуска в продакшн.
00:00:50Итак, что такое шлюз LLM?
00:00:52Шлюз LLM — это точка входа или промежуточный слой между вашими приложениями и стоящими за ними провайдерами моделей.
00:00:58Он выполняет множество задач: маршрутизацию, аутентификацию, резервное переключение, ограничение скорости и всевозможные правила управления.
00:01:08И в самом сердце этого шлюза лежит борьба между четырьмя факторами.
00:01:13Это доступность, задержка, защитные барьеры и стоимость.
00:01:18В случае ухудшения условий максимизировать все четыре параметра не получится.
00:01:22Вам нужно выбрать то, что важнее.
00:01:25Поэтому в этом докладе я хочу помочь вам сделать этот выбор для вашего сценария использования, если вы применяете шлюз LLM.
00:01:35А если вы проектируете шлюз, я хочу, чтобы вы заложили эти рычаги управления для своих пользователей и клиентов, чтобы они оставались довольны.
00:01:46Начнем с доступности.
00:01:50Если у вас один-единственный провайдер моделей, его потолок — это ваш потолок.
00:01:56Их сбой — это ваш сбой.
00:02:03Поэтому в традиционной разработке ПО ненадежные зависимости принято обрабатывать с помощью повторных попыток.
00:02:11Повторные попытки с экспоненциальной задержкой и случайными отклонениями.
00:02:15А когда все это не работает, срабатывает автомат защиты, который отключается после достаточного числа сбоев, и вы перестаете дергать эту чертову систему.
00:02:24Для LLM этого недостаточно.
00:02:26Модели LLM сильно отличаются от быстрых и дешевых API, к которым мы привыкли применять повторные попытки.
00:02:32Повторный запрос к LLM очень быстро исчерпывает ваш лимит времени отклика.
00:02:38К тому же, задействовать автомат защиты, когда у вас есть другой прекрасно работающий провайдер моделей для маршрутизации, не имеет смысла.
00:02:47Вам следует использовать второго провайдера.
00:02:48И в-третьих, как я уже говорил, вызовы работают медленно и стоят дорого.
00:02:53Поэтому слепые повторные попытки лишь умножают ваши расходы и хвостовые задержки.
00:02:58Что же здесь будет лучшим решением?
00:03:02Фактически это резервное переключение для каждого отдельного запроса.
00:03:05Это значит, что вы можете сначала попробовать провайдер моделей A, а затем последовательно переключиться на провайдер B, если запрос к провайдеру A завершился неудачей.
00:03:14Еще один вариант — отправлять запросы обоим провайдерам параллельно, но это только если вы помешаны на задержках, поскольку это просто удвоит ваши затраты.
00:03:26Некоторые похожие паттерны схемотехники с защитой от сбоев применимы и здесь, к LLM.
00:03:32Если вы знаете, что ваш основной провайдер какое-то время сбоит, пробовать его снова бессмысленно.
00:03:40Вы убираете его из балансировщика нагрузки или пути запроса, отправляете на охлаждение, а через несколько минут пробуете вернуть обратно.
00:03:51Интересный выбор, который вам предстоит сделать, — где именно хранить счетчики сбоев.
00:03:59Вы можете хранить счетчики сбоев в памяти на тех экземплярах, которые обслуживают трафик, либо использовать общую информацию, доступную для всего пула.
00:04:12Здесь есть свои плюсы и минусы.
00:04:14Если вам нужны быстрые переключения, помогает статистика по всему пулу.
00:04:19А при использовании локальных счетчиков состояния экземпляра проблема в том, что при каждом изменении размера развертывания меняются ваши конфигурации и ожидания.
00:04:30Так что об этом стоит подумать.
00:04:34То чистая схема на самом деле не показала некоторые другие подводные камни, о которых я расскажу.
00:04:39Резервные копии и фоллбеки не так прозрачны.
00:04:41Хотя индустрия и сходится на едином формате, совместимом с OpenAI API, я бы сказал, что нюансы все еще остаются.
00:04:49Поэтому вам нужно тщательно тестировать резервные механизмы.
00:04:52У них могут быть отличия в схемах вызова инструментов, лимитах токенов, причинах остановки генерации и многом другом.
00:04:58Поэтому со шлюзами LLM можно использовать уровень нормализации, который гарантирует корректное переключение между разными провайдерами.
00:05:08Еще один момент — потоковая передача (стриминг).
00:05:15По сути, никто не хочет ждать 30 секунд, пока перед ним вырастет стена текста.
00:05:22Поэтому существуют сценарии, где стриминг абсолютно необходим.
00:05:26Но он обходится дорого.
00:05:27Вы жертвуете своими рычагами управления.
00:05:29Вы не можете — как только вы решили работать с Провайдером А, вы обязаны продолжать работу с ним.
00:05:36Нельзя сменить провайдера посреди потока.
00:05:39Все, что отправлено клиенту, уже отправлено.
00:05:42И именно поэтому появляется сообщение «Что-то пошло не так».
00:05:46Вы видите именно его.
00:05:48И дело тут вовсе не в лени.
00:05:49Такое поведение заложено архитектурно.
00:05:52И это один из компромиссов.
00:05:54Я хотел бы отметить еще одну вещь, на которой команды спотыкаются снова и снова.
00:06:00Они отлично настраивают и тестируют своих основных провайдеров.
00:06:05Но второй провайдер, резервный, зачастую получает куда меньше внимания.
00:06:11Хотя я бы утверждал, что показатели пропускной способности, емкости и запас прочности для резервного провайдера должны быть еще выше.
00:06:21Потому что это ваш последний рубеж обороны.
00:06:23Если упадет он, упадет и все ваше приложение.
00:06:29Давайте обсудим задержки.
00:06:31Проблемы с доступностью бьют прямо в лоб.
00:06:34Все падает.
00:06:36Срабатывают алармы.
00:06:37Инженерам высылают пейджеры.
00:06:38Но высокие задержки могут действовать скрытно и тихо.
00:06:42И им нужно уделять больше внимания, чем простой настройке служб на одну лишь доступность.
00:06:49Стоит упомянуть об одной детали.
00:06:54Шлюз может обрабатывать смешанные рабочие нагрузки.
00:06:58У вас могут быть запросы на эмбеддинги, которые выполняются меньше секунды.
00:07:04Могут быть запросы классификации, занимающие менее секунды.
00:07:07Есть чат-запросы, которые длятся три секунды.
00:07:10И запросы на рассуждения (reasoning), занимающие много времени.
00:07:13Поднимите руки.
00:07:15Поднимите руки те, кто измеряет общую совокупную задержку для всего сервиса целиком.
00:07:20Что ж, это был вопрос с подвохом.
00:07:23Простите.
00:07:24Вам не следует этого делать.
00:07:25Это не имеет смысла.
00:07:26Это искажение фактов.
00:07:27Вы должны отслеживать показатель P99 для каждой конкретной модели и каждого маршрута, а не усредненное по всему шлюзу число.
00:07:32Общая цифра по шлюзу бесполезна, особенно если вы обрабатываете разнородные рабочие нагрузки.
00:07:36И я надеюсь, что те, кто поднял руки, так на самом деле не делают.
00:07:40Еще одна вещь, важность которой невозможно переоценить — это настройка тайм-аутов для каждого класса моделей и каждого маршрута.
00:07:49Именно в этом кроется первопричина ваших скрытых сбоев.
00:07:54Если у вас нет тайм-аута, шлюз считает, что запрос успешно обрабатывается, хотя на самом деле это не так.
00:08:00И в завершение темы задержек я оставлю вам такую мысль.
00:08:05Нормальное время отклика модели рассуждений — это фактически сбой для обычного чат-бота.
00:08:09Поэтому вам определенно нужно отслеживать задержку для каждого маршрута.
00:08:13Хорошо, это самый болезненный слайд, доставивший мне больше всего хлопот: модели рассуждений и модели-маршрутизаторы.
00:08:24Именно здесь задержка становится по-настоящему непредсказуемой.
00:08:29Модели рассуждений обладают высокой недетерминированностью, гораздо большей, чем привычные модели.
00:08:40Во многих случаях вы не можете установить температуру на ноль.
00:08:43И один и тот же промпт может обрабатываться от двух до 60 секунд.
00:08:48Мы видели в продакшене, как показатель P99 внезапно подскакивал до 60 секунд без видимых на то причин.
00:08:53Хотя волшебного решения тут нет, я рекомендую начать хотя бы с фиксации уровня рассуждений для каждого маршрута.
00:09:03Что касается моделей-маршрутизаторов, они скрывают эту абстракцию от вас.
00:09:08Например, они сами выбирают, какие модели запустить.
00:09:11И я настоятельно рекомендую сделать запросы настолько детерминированными, насколько это возможно в рамках недетерминированной системы.
00:09:23Еще одна идея — страховка «хвоста» распределения.
00:09:26Вы можете отправить параллельный запрос, если ваш основной запрос уже исчерпал, скажем, 90 процентов вашего бюджета задержки.
00:09:37Это действительно поможет сгладить хвостовые задержки P99 для ваших сервисов.
00:09:44Хорошо, это одна из моих любимых тем.
00:09:47Чтобы защитить ваши модели, необходимы защитные экраны (guardrails).
00:09:53Защитные фильтры нужны для предотвращения атак инъекций промптов, фильтрации персональных данных (PII), подавления токсичности и пресечения попыток языковых моделей грубить вашим клиентам.
00:10:08И все в таком духе.
00:10:11Но, как и у провайдера моделей, здесь тоже есть свои компромиссы.
00:10:15Защитные фильтры — это просто еще один сервис.
00:10:18Который может упасть.
00:10:19Который может оказаться ненадежным.
00:10:21И тут вам приходится делать выбор.
00:10:24Вы пропускаете запрос при сбое (fail open) или блокируете его (fail close)?
00:10:27Когда я говорю «пропускать при сбое», вы все равно можете обслужить запрос, даже если защитные фильтры недоступны.
00:10:32При блокировке вы отклоняете запрос и говорите: извините, сервис недоступен.
00:10:36В определенной степени это компромисс между доступностью и безопасностью.
00:10:41Универсального ответа нет, всё зависит от вашего сценария использования.
00:10:45Вы можете решить, например, что если фильтр токсичности не работает, запрос всё равно можно обработать.
00:10:53Поэтому выбором по умолчанию должен быть худший сценарий, с которым вы можете смириться.
00:11:02Есть несколько способов улучшить поведение систем при сбоях защитных экранов и справиться с их ненадежностью.
00:11:17Первое — это временной бюджет.
00:11:20Время выполнения вашего запроса никогда не должно зависеть от работы защитных экранов.
00:11:25Фактором, определяющим скорость, всегда должна быть языковая модель.
00:11:29Поэтому убедитесь, что у вас настроены тайм-ауты, а защитные экраны работают в рамках определенного временного бюджета.
00:11:38Еще одна важная вещь — резервные варианты.
00:11:40Вы наверняка слышали, и я об этом говорил, мы всегда обсуждаем резервные варианты применительно к провайдерам моделей.
00:11:47Но защитные экраны — это тоже критически важные сервисы, для которых можно предусмотреть резервные каналы, запасных провайдеров, дополнительные проверки и кэширование решений, чтобы сохранить доступность сервиса при сбое защитного экрана.
00:12:03Еще один интересный вопрос, возникающий в отношении защитных экранов, — это их размещение.
00:12:10Обычно защитный экран можно разместить тремя способами.
00:12:15Вы можете использовать предварительный перехватчик, который проверяет входные данные.
00:12:19Это, пожалуй, самый безопасный вариант, но он добавляет последовательную задержку вашим запросам.
00:12:26Второй вариант — параллельная работа.
00:12:29Это один из моих любимых способов, но стоит учесть, что потоковая передача здесь работать не будет.
00:12:35Поэтому, если вы генерируете структурированный вывод, пожалуйста, не используйте потоковую передачу.
00:12:40Старайтесь экономить время и запускать эти защитные экраны параллельно для структурированных данных.
00:12:46Еще один вариант — последующие перехватчики.
00:12:48Они лучше всего подходят для мониторинга результатов, аудита вывода и тому подобного.
00:12:58Итак, до сих пор мы обсуждали все возможные проблемы, связанные с нашими зависимостями.
00:13:06Мы еще не упомянули, что фактически добавляем еще одну зависимость в сам путь запроса — центральный шлюз языковых моделей.
00:13:15С ним связано несколько проблем, на которых мы обожглись, и я хочу поделиться этими уроками, если вы работаете над таким шлюзом или используете его.
00:13:25Первое — это общие лимиты.
00:13:28Убедитесь, что ваши ключи API разделены для каждого маршрута и каждого сценария использования настолько детально, насколько это вообще возможно.
00:13:40Наличие «шумного» соседа может стать одной из самых больших проблем.
00:13:47Второе — сброс нагрузки.
00:13:50Убедитесь в рамках ваших инструкций и учений, что используемый вами шлюз поддерживает сброс нагрузки.
00:13:59Потому что при шквале повторных запросов масштабировать систему становится невероятно трудно.
00:14:03Нельзя просто так взять и масштабировать сервисы, находящиеся под волной повторных запросов.
00:14:07У всех этих веб-серверов есть внутренняя очередь, и ее можно настраивать.
00:14:13Убедитесь, что размер очереди ограничен и они не принимают запросы бесконечно.
00:14:19Если вам нужна особая логика, вы даже можете настроить приоритезацию трафика, чтобы гарантировать качественное обслуживание самых важных сценариев при высокой нагрузке.
00:14:29Последнее, что я хочу обсудить, — это саму концепцию центрального шлюза.
00:14:38Это единая точка отказа.
00:14:40Поэтому, если вы планируете создать единый шлюз для всей компании под пару языковых моделей, я бы порекомендовал пересмотреть это решение и взвесить причины.
00:14:52Я заметил, что в большинстве случаев людям нужен вовсе не центральный шлюз.
00:14:57Им требуется централизованное управление.
00:15:00Существует подход, позволяющий децентрализовать шлюз и при этом сохранить централизованное управление.
00:15:07Поэтому не пытайтесь централизовать весь трафик через один узел, лучше используйте плагины и кастомный код для централизации управления.
00:15:17Управление может выражаться в отслеживании затрат, управлении лимитами и других возможных решениях.
00:15:24Изучите эти варианты, прежде чем внедрять единственный центральный шлюз для всей компании.
00:15:30Им может управлять одна команда, но я бы не рекомендовал развертывать его как единое целое для всей компании, даже если он распределенный.
00:15:43В завершение я хочу сказать несколько слов личного характера.
00:15:47Сегодня день рождения моего сына, а я стою здесь и рассказываю незнакомцам об ограничении сбоев.
00:15:54Так что всё, что вы можете для меня сделать, — пожалуйста, предотвратите хотя бы один инцидент ради меня и ваших клиентов.
00:16:02Спасибо.
00:16:03Если у вас есть вопросы, да.
00:16:06.