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

В этой части мы перейдём к организации распределённого инференса с помощью 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 года будет **четыре** четверга.\n\nВот даты:\n\n*   6 марта\n*   13 марта\n*   20 марта\n*   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.

Комментарии (0)