Запуск модели Qwen3 при помощи KServe на OpenScaler

Недавно нашему сообществу попалась на глаза статья от OpenAtom Foundation, где демонстрировался запуск Hugging Face Qwen3 при помощи инструмента KServe в кластере kubernetes на openEuler.

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

Отметим, что целью статьи является демонстрация использования модели Qwen3 от Hugging Face для задач генерации текста путем развертывания InferenceService, запускающего службу Hugging Face.

Среда выполнения Hugging Face от KServe использует vLLM в качестве серверной части для обслуживания Large Language Model (LLM) по умолчанию, что позволяет ускорить время отклика первого токена (TTFT) и повысить производительность генерации токенов по сравнению с официальным API Hugging Face.

Если вы не сталкивались с KServe, то упомянем, что это платформа для обслуживания моделей на базе Kubernetes, которая упрощает развертывание моделей машинного обучения в производственных средах и управление ими. Благодаря стандартизированным интерфейсам и CRD (custom resource definition), KServe поддерживает множество основных серверных систем онлайн-инференса (таких как TensorFlow Serving, TorchServe, Triton Inference Server и Hugging Face Server) для различных моделей глубокого обучения.

Подготовка стенда тестирования

Сервер Huawei 1288H V5:

  • 2 CPU Intel(R) Xeon(R) Gold 6230 CPU @ 2.10GHz
  • 384 Гб ОП
  • 2 SAS диска ST600MM0009
  • ОС: openScaler 24.03 LTS SP2
  • ядро: 6.6.0-107.0.0.113.oe2403sp2.x86_64

Установим необходимые для тестирования пакеты:

dnf install -y wget curl tar iptables

Теперь установим docker:

curl -sL https://raw.githubusercontent.com/cnrancher/euler-packer/refs/heads/main/scripts/others/install-docker.sh | bash

Установка kind, для лёгкого и быстрого способа развёртывания кластера kubernetes:

curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.29.0/kind-linux-amd64
chmod +x ./kind
mv ./kind /usr/local/bin/kind

Устанавливаем утилиту kubectl:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

Устанавливаем helm:

wget https://get.helm.sh/helm-v3.18.4-linux-amd64.tar.gz
tar -zxvf helm-v3.18.4-linux-amd64.tar.gz
mv linux-amd64/helm /usr/local/bin/helm

Разворачиваем кластер k8s:

kind create cluster

Настраиваем kubectl для управления нашим кластером:

kubectl config use-context kind-kind

Установка KServer на наш кластер k8s:

curl -sL "https://gitee.com/openeuler/openeuler-docker-images/raw/master/AI/kserve/controller/doc/quick_install.sh" | bash -s -- -r

Запуск модели Qwen3 на кластере

Создаём yaml манифест с CRD конфигурацией для сервиса Hugging Face Qwen3 qwen3.yaml:

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: huggingface-qwen3
spec:
predictor:
model:
modelFormat:
name: huggingface
args:
- --model_name=qwen3
- --model_id=Qwen/Qwen3-8B
- --max-model-len=16384
resources:
limits:
cpu: "6"
memory: 24Gi
requests:
cpu: "6"
memory: 24Gi

Заметьте, мы не указываем HF_TOKEN, а обращаемся для загрузки модели как anonymous.

и запускаем на кластере:

kubectl apply -f qwen3.yaml

Тут надо заметить, что придётся немного подождать, пока делается pull образу, а после запуска pod, он всё равно не станет сразу ready, потому что потратит минут 10 на скачивание модели.

Спустя какое-то время наш pod наконец-то поменял статус на ready:

kubectl get po
NAME                                           READY   STATUS    RESTARTS   AGE
huggingface-qwen3-predictor-644c59bd85-7qdl4   1/1     Running   0          14m


Проверяем inferenceservices:

kubectl get inferenceservices NAME URL READY PREV LATEST PREVROLLEDOUTREVISION LATESTREADYREVISION AGE huggingface-qwen3 http://huggingface-qwen3-default.example.com True 16m

Из этого вывода запоминаем URL.

Если нам нужна более подробная информация, читаем вывод команды:

kubectl describe inferenceservice huggingface-qwen3

Теперь, чтобы проверить работу нашего сервиса, нам надо постучаться в API. Вспоминаем, что мы используем kind и поэтому кластер k8s у нас работает внутри контейнера kind. Из-за этого у нас не получится привычно использовать ip адреса службы (service) k8s и стучаться в него прямо с хоста при помощи curl. Как вариант – можно настроить (KServe устанавливает как зависимость service mesh Istio) gateway, virtualservice. Но в развёрнутом нами варианте kind у нас нет сервиса раздающего LoadBalancer ip адреса, поэтому наш istio не работает как ему положено, а устанавливать и настраивать тот же MetalLB не входит в задачи данной статьи.

Поэтому для демонстрации мы выберем простой путь, а именно будем обращаться к API изнутри контейнера kind.

Для начала узнаём ip-адрес и порт API:

kubectl get svc huggingface-qwen3-predictor
NAME                          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
huggingface-qwen3-predictor   ClusterIP   10.96.202.20   <none>        80/TCP    54m

 

Из этого вывода запомним ip-адрес 10.96.202.20 и порт 80.

URL inferenceservices у нас уже есть http://huggingface-qwen3-default.example.com.

Осталось дело за малым, нужно провалиться внутрь системного контейнера kind-control-plane и оттуда уже запускать curl.

При помощи стандартной команды docker заходим внутрь контейнера. Можем удовлетворить любопытство и посмотреть список запущенных контейнеров (контейнеры внутри контейнера, да, вот такая “матрёшка”):

docker exec -ti kind-control-plane /bin/bash

root@kind-control-plane:/# crictl ps
CONTAINER           IMAGE               CREATED             STATE               NAME                      ATTEMPT             POD ID              POD                                            NAMESPACE
526cfb43897d9       7b4d2f6e6f0a5       52 minutes ago      Running             kserve-container          0                   52acb8d4eee76       huggingface-qwen3-predictor-644c59bd85-7qdl4   default
81d9933636da3       de5f04897a3d0       59 minutes ago      Running             manager                   0                   d49ec12c980a0       kserve-controller-manager-7fc87b466b-gdsnb     kserve
41f236c62cf3d       35def84111f7b       59 minutes ago      Running             kube-rbac-proxy           0                   d49ec12c980a0       kserve-controller-manager-7fc87b466b-gdsnb     kserve
d129cdbcd7937       94b6cc26be635       About an hour ago   Running             cert-manager-cainjector   0                   a95534c107610       cert-manager-cainjector-58c9d76cb8-76pjd       cert-manager
0adbcd90a7758       c5c110afda0f7       About an hour ago   Running             cert-manager-webhook      0                   9fa1d2bd074af       cert-manager-webhook-5875b545cf-5bfv5          cert-manager
3040b0479ca9d       ef702517cc982       About an hour ago   Running             cert-manager-controller   0                   7a1feaedfcffd       cert-manager-74b7f6cbbc-jv9sz                  cert-manager
1fcb35fc74685       25eeeeca367cf       About an hour ago   Running             istio-proxy               0                   c482479fbe453       istio-ingressgateway-74949b4866-xg65c          istio-system
ac75a9e7ec19f       a342234ebb356       About an hour ago   Running             discovery                 0                   a9e95bbd70abb       istiod-77bbc7c8bb-9lk68                        istio-system
93bc91663a249       bbb6209cc873b       About an hour ago   Running             local-path-provisioner    0                   e24bfad2a035c       local-path-provisioner-7dc846544d-x7pk8        local-path-storage
b9e8e7e22b595       1cf5f116067c6       About an hour ago   Running             coredns                   0                   cdff542b84e58       coredns-674b8bbfcf-lxvmv                       kube-system
f5385928538ec       1cf5f116067c6       About an hour ago   Running             coredns                   0                   71d6829fd6503       coredns-674b8bbfcf-949s6                       kube-system
bbc9cea0ee1cd       409467f978b4a       About an hour ago   Running             kindnet-cni               0                   eaa6866447dfe       kindnet-t7pvq                                  kube-system
35512c227075e       b79c189b052cd       About an hour ago   Running             kube-proxy                0                   b04f837b6d2a8       kube-proxy-z6tgj                               kube-system
91422c9a754c1       499038711c081       About an hour ago   Running             etcd                      0                   0e741d8897d5e       etcd-kind-control-plane                        kube-system
5baddbea7318c       ef43894fa110c       About an hour ago   Running             kube-controller-manager   0                   8907f364eb5d4       kube-controller-manager-kind-control-plane     kube-system
cb790972a922e       c6ab243b29f82       About an hour ago   Running             kube-apiserver            0                   e951d29c6cacc       kube-apiserver-kind-control-plane              kube-system
2ca9ea97272b4       398c985c0d950       About an hour ago   Running             kube-scheduler            0                   e254ad0e9d270       kube-scheduler-kind-control-plane              kube-system

Обращаемся к API при помощи curl. Давайте спросим у Qwen3, кто такой Марк Твен (при этом специально допустим опечатку в фамилии, посмотрим как отреагирует модель).

curl -v http://10.96.202.20/openai/v1/completions -H "content-type: application/json" \
-H "Host: huggingface-qwen3-default.example.com" \
-d '{"model": "qwen3", "prompt": "who is Mark Twen?", "stream":false, "max_tokens": 30}' * Trying 10.96.202.20:80... * Connected to 10.96.202.20 (10.96.202.20) port 80 (#0) > POST /openai/v1/completions HTTP/1.1 > Host: huggingface-qwen3-default.example.com > User-Agent: curl/7.88.1 > Accept: */* > content-type: application/json > Content-Length: 83 > < HTTP/1.1 200 OK < date: Fri, 05 Sep 2025 09:24:24 GMT < server: uvicorn < content-length: 421 < content-type: application/json < * Connection #0 to host 10.96.202.20 left intact {"id":"cmpl-48237740ebf14f23bbe085c5114adf2b","object":"text_completion","created":1757064265,
"model":"qwen3","choices":[{"index":0,"text":" Mark Twain is the pen name of Samuel Langhorne Clemens (1835–1910), an American writer, humor",
"logprobs":null,"finish_reason":"length","stop_reason":null,"prompt_logprobs":null}],
"usage":{"prompt_tokens":6,"total_tokens":36,"completion_tokens":30,"prompt_tokens_details":null}}

 

Ответ модели занял довольно продолжительное время, в моём случае целую 1 минуту и 13 секунд . Но это легко объяснимо тем, что мы запустили сервис только на использовании CPU, без GPU ускорителей. 

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

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