Распределённый инференс и шардирование LLM. Часть 2: скрипт vLLM, Ray Serve для вывода API и настройка KubeRay Cluster

МЕНЮ


Главная страница
Поиск
Регистрация на сайте
Помощь проекту
Архив новостей

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


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

В этой части мы перейдём к организации распределённого инференса с помощью vLLM и обеспечим доступ к нему через Ray Serve. А ещё выясним, как запустить модель Gemma 3 в Ray-кластере и как проверить работу нашего OpenAI-совместимого эндпойнта с JWT-аутентификацией.

Если вы ещё не читали первую часть, стоит начать с неё. Там описано «железо» моей домашней лаборатории и процесс подготовки всего необходимого для развёртывания распределённого инференса с Ray Serve и vLLM.

Написание скрипта vLLM для шардирования, распределения вычислений и Ray Serve для вывода API

Начнём с ключевых концепций шардирования, параллелизации вычислений и принципов работы API.

Что такое vLLM и почему он выбран

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

  • Нативную поддержку шардирования (tensor parallel): разбивает весовые тензоры модели между GPU в рамках одной ноды, позволяя каждому ускорителю обрабатывать свою часть вычислений одновременно.

  • Пайплайн-параллелизм (pipeline parallel): разбивает модель на последовательные блоки-слои и распределяет их по разным нодам: первые слои выполняются на GPU ноды А, следующие — на GPU ноды B и так далее. При этом пока нода А обрабатывает следующий запрос, нода B уже догоняет и завершает предыдущий — конвейер остаётся загруженным.

  • Интеграцию с NVIDIA Collective Communications Library (NCCL), которая обеспечивает сверхбыстрый обмен промежуточными активациями и градиентами между GPU и между нодами (в том числе по RDMA/InfiniBand), автоматически группируя все устройства в единый вычислительный пул.

  • Стриминговые ответы: отдаёт результат по мере генерации, как в OpenAI ChatCompletion API, чтобы клиенты видели ответ постепенно.

Вместе это даёт нам:

  • Объединённую видеопамять: все VRAM-карты работают как одно целое, суммируя свои ресурсы.

  • Постоянную загрузку: тензорные операции на каждой карте идут параллельно, а конвейерные этапы никогда не простаивают.

  • Горизонтальное масштабирование «из коробки»: независимо от того, 1 или 8 GPU на ноде и каково общее количество нод, vLLM + NCCL автоматически развернут любую большую модель сразу на десятках карт.

Распределение инференса и параметры шардирования

В конфиге vLLM задаём:

  1. Tensor parallel size — сколько «кусочков» весов распределять по GPU внутри узла.

  2. Pipeline parallel size — на сколько стадий дробим модель по нодам.

  3. gpu_memory_utilization и cpu_offload_gb — параметры, которые помогают эффективно расходовать видеопамять и часть объёма оперативной памяти хоста.

Так мы балансируем память и производительность под свой кластер.

Что такое Ray Serve

Ray Serve — это компонент фреймворка Ray, который предоставляет микросервисную архитектуру для инференса:

  • Позволяет развернуть FastAPI-приложение и автоматически управлять его репликами.

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

  • Может эффективно работать с GPU, когда под каждый сервис зарезервирована часть видеокарт.

Мы используем Ray Serve, чтобы предоставить внешний HTTP API, совместимый с OpenAI-протоколами (ChatCompletion). Через этот API клиенты смогут делать запросы к модели, развёрнутой в vLLM.

Как это работает вместе

  1. Внутри узла. Tensor Parallelism дробит модель на куски, загружает их на все локальные GPU и с помощью NCCL синхронизирует парамет­ры.

  2. Между узлами. Pipeline Parallelism превращает несколько серверов с GPU в конвейер: каждый узел обрабатывает свою «стадию» модели, передавая промежуточные данные дальше — так все карты работают без простоев.

  3. Внешний интерфейс. Ray Serve оборачивает всю эту мощь в единый HTTP-сервис по OpenAI-протоколу. Он разбивает входящий запрос на нужные шард-группы, отправляет их на соответствующие GPU и ноды, а затем собирает полученные фрагменты в единый осмысленный ответ.

Описания скриптов

Дисклеймер: я не Python-разработчик, поэтому скрипты далеко не production-ready и написаны больше для ознакомительных целей.

Скрипт serve.py

Первый скрипт — serve.py. В нём:

  • инициируется vLLM с заданными параметрами (tensor parallel, pipeline parallel и так далее);

  • создаётся FastAPI-приложение с эндпойнтами для ChatCompletion — стримингового и обычного;

  • добавляется JWT-аутентификация для контроля доступа.

  • Ray Serve оборачивает это приложение и распределяет HTTP-запросы по рабочим процессам.

Скрипт auth.py

Второй скрипт — auth.py, который:

  • работает с JWT — создание и проверка токенов, роль пользователя;

  • берёт загрузку пользователя (логин, роль, хэш пароля) из переменных окружения (например, USER_LIST);

  • предоставляет вспомогательные функции для проверки роли (admin/user) и времени жизни токена.

Этот модуль подключается внутри serve.py, чтобы проверять токены при обращении к эндпойнтам. Его реализация максимально упрощена (то же касается хранения логинов, паролей и ролей) и служит только демонстрационным примером.

Промежуточные итоги 

Теперь у нас есть два скрипта для дальнейшего использования:

  • serve.py — основной сервер, где Ray Serve запускает FastAPI-приложение с vLLM под капотом;

  • auth.py — вспомогательная логика JWT-аутентификации, используемая в серверном скрипте.

Благодаря этим двум модулям мы получаем распределённый инференс, шардирование больших языковых моделей и удобный API для взаимодействия с ними. Теперь нужно собрать скрипты в Docker-образ и развернуть всё в KubeRay.

Настройка KubeRay

В этом разделе мы разберёмся, как подготовить Docker-образ, поместить его в Registry и развернуть Ray Cluster с нужными параметрами. Это поможет организовать распределённый инференс, использовать CephFS или другое хранилище, настраивать CPU- и GPU-ресурсы и так далее. В примерах я беру за основу официальный Docker-образ Ray, но вы можете легко заменить его на любой другой совместимый базовый образ.

Подготовка Docker-образа и загрузка в Registry

1. Базовый образ Ray: 

  • Берём официальный образ Ray соответствующей версии, например rayproject/ray:2.44.0-py310-cu124. Он уже содержит нужные компоненты Ray, CUDA-библиотеки и Python 3.10.

2. Добавляем скрипты и пакеты:

  • Помещаем serve.py, auth.py (упаковывая всё в zip-архив).

  • Устанавливаем vLLM, httpx, PyJWT и другие зависимости.

3. Сборка:

  • Пишем Dockerfile (dockerfile.ray), в котором описываем добавление zip-файла и установку pip-зависимостей.

  • Собираем образ с помощью Make (Makefile) командой:

make package-container

4. Загрузка образа:

  • Пушим результат в нужный Registry — DockerHub, GitLab Registry и так далее:

docker push gitlab.example.com:5050/it-operations/k8s-config/vllm-0.8.4-ray-2.44.0-py310-cu124-serve:1.1.7
  • Убеждаемся, что Kubernetes-узлы или кластер имеют доступ к этому Registry. Кстати, в Deckhouse уже есть возможность добавить внутренний registry, а также авторизацию для него.

Настройка KubeRay Cluster

Для удобства описываем Ray Cluster с помощью Helm-чарта и values-файла. Основная идея такая:

  • head (головной узел) — содержит Ray Head Pod с установленным Ray Dashboard, Autoscaler, если он нужен, и обеспечивает взаимодействие с worker'ами. Для обеспечения режима HA есть отдельная опция gcsFaultToleranceOptions — подробнее о её работе расскажу чуть позже.

  • worker — 1+ подов, каждый из которых может работать на CPU или GPU.

  • additionalWorkerGroups — дополнительные группы worker'ов с другими конфигурациями (например, меньше памяти, более слабый GPU).

Ниже приведён пример ap-values.yaml с основными настройками.

1. image

image:   repository: gitlab.example.com:5050/it-operations/k8s-config/vllm-0.8.4-ray-2.44.0-py310-cu124-serve   tag: 1.1.7   pullPolicy: IfNotPresent

Здесь:

  • repository и tag — это Docker-образ, который мы собрали на предыдущем шаге;

  • pullPolicy: IfNotPresent означает, что Kubernetes не будет заново скачивать образ, если он уже есть локально.

2. imagePullSecrets

imagePullSecrets:   - name: regcred

Если Registry приватный, нужно прописать секрет (credential).

3. common

common:   containerEnv:     - name: HF_HOME       value: "/data/model-cache"     - name: DTYPE       value: "bfloat16"     - name: GPU_MEMORY_UTIL       value: "0.98"     - name: MAX_MODEL_LEN       value: "131072"

containerEnv задаёт переменные окружения, которые попадут и в head, и в worker:

  • DTYPE (например, float16) — тип данных при инференсе;

  • GPU_MEMORY_UTIL — доля видеопамяти, которую можно занимать при работе vLLM;

  • MAX_MODEL_LEN — максимально допустимая длина токенов для модели (по необходимости).

4. head

head:   rayVersion: "2.44.0"   enableGcsFT: true     gcsFT:     # externalStorageNamespace: ray-gcs-backup       redisAddress: redis:6379                             redisSecret:         name: redis-password-secret         key: password   labels:     component: ray-head   serviceAccountName: "sa-deepseek-cluster"   rayStartParams:     dashboard-host: "0.0.0.0"     num-cpus: "0"     metrics-export-port: "8080"   containerEnv:     - name: RAY_PROMETHEUS_HOST       value: https://prometheus.d8-monitoring:9090   envFrom:     - secretRef:         name: auth-config   resources:     limits:       cpu: "6"       memory: "8G"     requests:       cpu: "3"       memory: "4G"   volumes:     - name: model-cache       persistentVolumeClaim:         claimName: model-cache-pvc   volumeMounts:     - mountPath: /data/model-cache       name: model-cache

Здесь:

  • rayVersion — указывает, какая версия Ray используется Autoscaler'ом, если включён in-tree Autoscaling;

  • serviceAccountName — ServiceAccount, который нужно дать поду. Он может понадобиться для доступа к PV, Secret и так далее;

  • envFrom — берём переменные из Secret auth-config. Это логины, пароли, JWT-ключи;

  • resources — запросы и лимиты CPU/RAM для head-пода. В примере: лимит 6 CPU, 8GiB RAM;

  • volumes/ volumeMounts — примонтированный CephFS (через PVC model-cache-pvc) на путь /data/model-cache, чтобы модель была доступна для head.

HA-режим GCS (GCS Fault Tolerance)

Чтобы защитить метаданные Ray-кластера от единственной точки отказа (GCS Head), мы включили опциональный режим репликации через Redis:

  • enableGcsFT: true — кастомный флаг, добавленный вручную, который заставляет Helm-шаблон сгенерировать секцию gcsFaultToleranceOptions. На момент написания статьи этого флага ещё нет в официальном чарте RayCluster.

  • gcsFT.redisAddress: redis:6379 — адрес нашего Redis-сервиса, где будут храниться дубли GCS-метаданных.

  • gcsFT.redisSecret.name/key — Kubernetes Secret с учётными данными доступа к Redis, чтобы пароль не «плавал» в YAML.

Принцип работы

При старте Ray-кластера GCS head теперь дублирует свои служебные данные — информацию о запущенных задачах и состояние акторов — не только во внутреннее хранилище, но и в Redis. Если оригинальный head-под выходит из строя, резервный head подхватывает сохранённые в Redis метаданные и продолжает обслуживание без потери состояния.

5. worker

worker:   groupName: rtx-3090   replicas: 2   minReplicas: 2   maxReplicas: 2   resources:     limits:       cpu: "16"       memory: "24G"       nvidia.com/gpu: "1"     requests:       cpu: "8"       memory: "12G"       nvidia.com/gpu: "1"   nodeSelector:     node.deckhouse.io/group: "w-gpu"   tolerations:     - key: "dedicated.apiac.ru"       operator: "Equal"       value: "w-gpu"       effect: "NoExecute"   volumes:     - name: model-cache       persistentVolumeClaim:         claimName: model-cache-pvc   volumeMounts:     - mountPath: /data/model-cache       name: model-cache

Здесь:

  • groupName — название группы worker'ов (rtx-3090);

  • replicas и minReplicas/maxReplicas — сколько подов worker будет по умолчанию и какой допускается диапазон;

  • resources — лимиты CPU/Memory и GPU. Например, 1 карта на под;

  • nodeSelector и tolerations — размещаем worker'ы на узлах с лейблом node.deckhouse.io/group=w-gpu;

  • volumes/volumeMounts — аналогично head монтируем PVC для общей модели.

6. additionalWorkerGroups

additionalWorkerGroups:   rtx-3060:     disabled: true     replicas: 1     minReplicas: 1     maxReplicas: 1     nodeSelector:       node.deckhouse.io/group: "w-gpu-3060"     ...

Можно добавить сколько угодно дополнительных групп, каждая — со своими ресурсами, лейблами, affinities и так далее. Если пока не хотим их запускать, ставим disabled: true, как в примере. 

Развёртывание Ray Application. Запуск модели Gemma 3 и тестирование API

Gemma 3 — одна из передовых LLM от Google, доступных на Hugging Face. Она умеет работать с очень длинными текстами (до 128K токенов) и спроектирована так, чтобы эффективно использовать память и быстро отвечать. Этот пример подходит для демонстрации работы, но вы можете использовать аналогичные шаги для развёртывания других доступных в открытом доступе LLM.

В этом разделе разберёмся, как выбрать и запустить модель в Ray-кластере, а также как проверить работу нашего OpenAI-совместимого эндпойнта с JWT-аутентификацией.

Запуск модели через Ray Application

Для запуска модели в Ray-кластере мы используем Ray Application. Данный механизм указывает Ray Serve, какие файлы брать, какую модель загружать и под какими настройками параллелизации (tensor/pipeline) работать.

Пример JSON-запроса к Ray Dashboard

Отправляем POST-запрос на https://ray-dashboard.k8s.example.com/api/serve/applications/ с телом:

{   "applications": [     {       "import_path": "serve:model",       "name": "Gemma-3-12b",       "route_prefix": "/",       "autoscaling_config": {         "min_replicas": 1,         "initial_replicas": 1,         "max_replicas": 1       },       "deployments": [         {           "name": "VLLMDeployment",           "num_replicas": 1,           "ray_actor_options": {},           "deployment_ready_timeout_s": 1200         }       ],       "runtime_env": {         "working_dir": "file:///home/ray/serve.zip",          "env_vars": {            "MODEL_ID": "google/gemma-3-12b-it",            "TENSOR_PARALLELISM": "1",            "PIPELINE_PARALLELISM": "2",            "MODEL_NAME": "gemma-3-12b",            "MAX_MODEL_LEN": "131072",            "MAX_NUM_SEQS": "256",            "ENABLE_ENFORCE_EAGER": "true",            "CPU_OFFLOAD_GB": "0",            "DTYPE": "auto", 	       "VLLM_USE_V1": "1",            "GPU_MEMORY_UTIL": "0.98"          }       }     }   ] }

Ключевые моменты JSON-запроса

1. import_path: "serve:model"

  • Указывает Ray, что в архиве (working_dir) находится Python-модуль serve.py, в котором определён объект model.

  • Именно он развёртывается как Ray Serve Deployment.

2. name: "Gemma-3-12b"

  • Имя приложения в Ray Dashboard, чтобы легко отличать его от других.

3. route_prefix: "/"

  • Базовый путь, по которому будет доступен сервис при наличии Ingress или сервисов.

4. autoscaling_config

  • min_replicas, initial_replicas, max_replicas задают политику масштабирования. В примере — 1 реплика без автоскейла.

5. deployments

  • Здесь описан VLLMDeployment с num_replicas = 1. Это класс/обёртка vLLM в serve.py.

  • deployment_ready_timeout_s = 1200 даёт время (20 минут) на инициализацию модели. Это полезно при больших загрузках.

6. runtime_env

  • working_dir: "file:///home/ray/serve.zip" говорит Ray, где лежит код — скрипты serve.py, auth.py.

  • env_vars: задаёт переменные окружения для vLLM:

    • "MODEL_ID" — название модели на Hugging Face, здесь "google/gemma-3-12b-it";

    • "TENSOR_PARALLELISM" и "PIPELINE_PARALLELISM" — регламентируют шардирование и конвейерную параллельность ( "TENSOR_PARALLELISM": "1" — на каждом узле 1 GPU, "PIPELINE_PARALLELISM": "2" — 2 GPU всего в кластере);

    • "MODEL_NAME" — отображается в ответах API как название модели;

    • "MAX_MODEL_LEN" — максимальная длина обрабатываемой последовательности в токенах, можно задать в чарте Ray Cluster;

    • "MAX_NUM_SEQS" — максимальное число одновременных сессий/запросов к модели;

    • "ENABLE_ENFORCE_EAGER" — принудительное включение eager-режима в vLLM для улучшения детерминированности и дебага;

    • "CPU_OFFLOAD_GB" — объём оперативной памяти (GB), выделяемой под офлоад части вычислений с GPU на CPU;

    • "DTYPE" — выбор формата данных (bfloat16/float32 и т.д.) для инференса, можно задать в чарте Ray Cluster;

    • "VLLM_USE_V1" — переключает движок на новую архитектуру vLLM V1 с единым диспетчером задач, менеджером KV-кэша и другими оптимизациями, включёнными по умолчанию;

    • "GPU_MEMORY_UTIL" — доля доступной видеопамяти (98 %) для использования моделью, можно задать в чарте Ray Cluster.

Результат деплоя и вид в Ray Dashboard

После успешного запроса Ray:

  1. Извлекает файлы из serve.zip и создаёт Ray Serve Application под именем Gemma-3-12b.

  2. В разделе Deployments появляется VLLMDeployment. vLLM инициализируется, подгружая Gemma-3-12b из Hugging Face.

  3. При желании можно просмотреть логи, где будет видно, как vLLM скачивает вес модели и запускает инференс.

С этого момента модель Gemma 3 готова к приёму запросов на OpenAI-совместимые эндпойнты (например, /v1/chat/completions), используя конфигурацию параллелизации (tensor/pipeline), указанную в env_vars.

Тестирование API и аутентификации

1. Создание пользователей

В Kubernetes создаём Secret (auth.yaml) с JWT-ключом, параметрами истечения токена, а также списком пользователей (логины, роли, хэши паролей).

Пример auth.yaml:

apiVersion: v1 kind: Secret metadata:   name: auth-config   namespace: kuberay-projects type: Opaque data:   JWT_KEY: ZGVmYXVsdF9qd3RfS2V5   ACCESS_TOKEN_EXPIRE_MINUTES: NjA=   SKIP_EXP_CHECK: ZmFsc2U=   HUGGING_FACE_HUB_TOKEN: ""   USER_LIST: QUxJQ0UsIEJPQg==   ALICE_USERNAME: YWxpY2U=   ALICE_HASHED_PASSWORD: ZmFrZWhhc2hlZGhhc2g=   ALICE_ROLE: YWRtaW4=   BOB_USERNAME: Ym9i   BOB_HASHED_PASSWORD: ZmFrZWhhc2hlZGhhc2g=   BOB_ROLE: Z3Vlc3Q=

В переменных окружения ALICE_HASHED_PASSWORD и так далее хранится уже «соль + хэш» пароля.

2. Генерация пароля и хэша

Используем Python-скрипт gen_pwd.py, где с помощью secrets.token_hex и hashlib.sha256 вычисляем соли и хэши паролей. Затем записываем хэшированный пароль в значение строки PASSWORD для конкретного пользователя, например ALICE_HASHED_PASSWORD. Соль записываем в строку JWT_KEY. Это нужно, чтобы auth.py мог сравнивать введённые данные при аутентификации.

В качестве демонстрации у нас для всех паролей и ключа JWT одинаковая соль, но в реальных условиях, конечно же, так делать не нужно.

3. Авторизация и получение JWT

Выполняем запрос на /token, передавая username / password в формате form-data:

curl --location 'https://openai-api.example.com/token'    --form 'username="admin"'    --form 'password="password"'

В ответ:

{   "access_token": "<токен>",   "token_type": "bearer" }

4. Вызов инференса 

Теперь пробуем вызвать через API сам инференс, задав один из самых сложных вопросов во вселенной:

curl --location 'https://openai-api.example.com/v1/chat/completions'  --header 'Content-Type: application/json'  --header 'Authorization: Bearer <токен>'  --data '{   "model": "Gemma-3-12b",   "messages": [     {       "role": "user",       "content": "Сколько четвергов в марте 2025 года?"     }   ],   "stream": false,   "max_tokens": 2000,   "temperature": 0.85,   "top_p": 0.95,   "top_k": 50,   "repetition_penalty": 1.0 }'

Получаем ответ, по которому видим, что ИИ успешно справился с задачей — хотя другие модели с трудом отвечали на этот вопрос (это не шутка):

{    "id": "chatcmpl-1745243015.727946",    "object": "chat.completion",    "created": 1745243015,    "model": "Gemma-3-12b",    "choices": [        {            "index": 0,            "message": {                "role": "assistant",                "content": "В марте 2025 года будет **четыре** четверга.  Вот даты:  *   6 марта *   13 марта *   20 марта *   27 марта"            },            "finish_reason": "stop"        }    ],    "usage": {        "prompt_tokens": 23,        "completion_tokens": 46,        "total_tokens": 69    } }
Вот для сравнения модель Qwen3-14B, которая рассуждала 48 сек., но где-то просчиталась
Вот для сравнения модель Qwen3-14B, которая рассуждала 48 сек., но где-то просчиталась

Мониторинг в Ray Dashboard

В Ray Dashboard видим загруженность GPU: скорость ~40 token/s в зависимости от конкретной модели и конфигурации.

Честно признаюсь, скрин логов взят со старой версии Ray и для модели Dolphin, которая использует Engine V0. В новой версии V1 можно сделать вывод логов по эндпойнту /metrics, но я это ещё не настроил
Честно признаюсь, скрин логов взят со старой версии Ray и для модели Dolphin, которая использует Engine V0. В новой версии V1 можно сделать вывод логов по эндпойнту /metrics, но я это ещё не настроил
Так выглядит сам Ray Cluster во время работы
Так выглядит сам Ray Cluster во время работы

А логи отображают процесс инференса, можно анализировать ошибки или задержки.

Пришло время для тестов

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

Как я тестировал

Тест скорости и нагрузки:

  • Скрипт token_benchmark_ray.py.

  • Менял число параллельных запросов (1, 4, 8, 16, 20), длину контекста (от 512 до 32 768 токенов) и способ сэмплинга (greedy, Top-K, Top-P, их сочетание).

Для каждого случая я запускал от 30 до 100 запросов и фиксировал:

  • время на токен (inter_token_latency_s);

  • время до первого токена (ttft_s);

  • общую задержку (end_to_end_latency_s);

  • скорость (throughput_token_per_s);

  • p50/p95/p99 в каждой метрике.

Тест стабильности и точности:

  • Скрипт llm_correctness.py.

  • Отправлял до 300 запросов при 20 параллельных сессиях и смотрел, стали ли ответы «уезжать» от ожидаемого или уходить в ошибку. Фиксировал error_rate и mismatch_rate.

Особенности окружения

  • CUDA Graph для Gemma-3-12B пришлось отключить (ENABLE_ENFORCE_EAGER=true), иначе модель падала. На Dolphin3.0 с графом производительность была ? 40 t/s.

Если у вас есть идеи, как вернуть CUDA Graph для Gemma-3 или другие способы ускорить эту модель, напишите, пожалуйста, в комментариях!

  • Тесты шли через внешний OpenAI API, поэтому в задержку добавлялся сетевой оверхед.

  • Все тесты на одной и той же конфигурации с постоянными GPU и RAM, чтобы сравнения были честными.

Результаты тестирования

  • Базовая производительность: модель выдаёт новый токен каждые ? 0,116 с, скорость генерации ? 10 tok/s, время до первого токена ? 3,2 с.

  • Параллелизм: оптимально использовать до 8 одновременных сессий — при дальнейшем росте latency начинает расти непропорционально.

  • Контекст: безопасный предел — до ? 2?000–4?000 токенов; при больших окнах существенно падает скорость (до 6 tok/s на 8?192 токенах и ниже) и возрастает риск ошибок.

  • Сэмплинг: выбор Top-K или Top-P даёт незначительное замедление (< 0,005 с/токен), но может улучшить качество генерации.

  • Надёжность: при 20 concurrent запросах error_rate = 0 %, mismatch_rate < 1 % — модель отвечает стабильно, без падений.

Все подробные результаты и исходные скрипты доступны в репозитории.

Промежуточные итоги

  1. Успешно подключили Gemma 3 из Hugging Face, развернув её в Ray-кластере с vLLM «под капотом».

  2. API /v1/chat/completions проверен — он выдаёт JSON-ответ, совместимый с OpenAI-протоколом.

  3. JWT-аутентификация ограничивает доступ к этому эндпойнту, пользователи хранятся в Secret.

  4. Провели ряд тестов производительности с помощью LLMPerf. Средняя межтокеновая задержка составила ?0,116 с, а throughput — ?10 т/с, что наглядно подтверждает стабильность и корректность работы инференса. Не так быстро, как хотелось бы, но я продолжаю исследовать возможности улучшить результат.

В следующей части

Итак, мы протестировали API. Дальше хочется подключить удобный интерфейс. В следующей, заключительной, статье мы рассмотрим, как развернуть OpenWebUI — бесплатный веб-интерфейс для взаимодействия с LLM. Подробно разберём его основные возможности и настройки, а также посмотрим, как связать OpenWebUI с нашим Ray-кластером, чтобы общаться с моделью через OpenAI-совместимый API.


Источник: habr.com

Комментарии: