KubeAI로 Hermes 3 등 오픈 LLM을 Kubernetes에서 서빙하기

KubeAI로 Hermes 3 등 오픈 LLM을 Kubernetes에서 서빙하기

Kubernetes 위에서 오픈 가중치 LLM을 서빙하고 싶을 때, KubeAI는 아주 편리한 선택지예요. KubeAI: Private Open AI on Kubernetes는 Nous Research의 Hermes 3(Llama 3.1 8B 기반 파인튜닝)와 NVIDIA의 Nemotron(Llama 3.1 70B 기반 파인튜닝) 같은 모델을 vLLM이나 Ollama 모델 서버로 띄워 주는 쿠버네티스 솔루션이에요.

웹 UI는 Open WebUI로, API는 OpenAI 호환 방식으로 제공돼요. 이 가이드에서는 8x H100 온디맨드 인스턴스에 K3s로 단일 노드 쿠버네티스 클러스터를 세우고, GPU Operator를 설치한 뒤 KubeAI로 Hermes 3와 Nemotron을 서빙하고, 마지막에 NVTOP으로 GPU 활용률까지 관찰해요. 한 번에 서빙과 관찰 흐름을 모두 잡을 수 있는 튜토리얼이에요.

출처: Using KubeAI to deploy Nous Research's Hermes 3 and other LLMs - Lambda Docs

단일 노드 쿠버네티스 클러스터 세우기

  1. 콘솔이나 Cloud API로 8x H100 인스턴스를 시작하고 SSH 접속해요.
ssh ubuntu@<INSTANCE-IP-ADDRESS> -L 8080:localhost:8080

<INSTANCE-IP-ADDRESS>는 인스턴스 IP로 바꿔주세요.

-L 8080:localhost:8080 옵션은 로컬 포트 포워딩을 활성화해요. KubeAI의 웹 UI를 내 컴퓨터에서 안전하게 접근하려면 로컬 포트 포워딩이 필요해요. SSH man page에서 더 자세히 볼 수 있어요.

  1. 이 튜토리얼 뒷부분의 kubectl port-forward 명령에 필요한 socat을 설치해요.
sudo apt update && sudo apt -y install socat
  1. K3s(Kubernetes)를 설치해요.
curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE=644 sh -s - --default-runtime=nvidia
  1. 쿠버네티스 클러스터가 준비됐는지 확인해요.
kubectl get nodes

다음과 비슷한 출력이 보여요.

NAME             STATUS   ROLES                  AGE   VERSION
192-222-54-148   Ready    control-plane,master   1s    v1.30.5+k3s1

kubectl 탭 완성을 켜고 싶다면 이렇게 실행해요. echo "source <(kubectl completion bash)" >> ~/.bashrc && source ~/.bashrc

NVIDIA GPU Operator 설치

쿠버네티스 클러스터가 인스턴스의 GPU를 쓰도록 GPU Operator를 설치해요.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: gpu-operator
---
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
  name: gpu-operator
  namespace: gpu-operator
spec:
  repo: https://helm.ngc.nvidia.com/nvidia
  chart: gpu-operator
  targetNamespace: gpu-operator
EOF

몇 분 뒤 클러스터가 GPU를 감지했는지 확인해요.

kubectl describe nodes | grep nvidia.com

다음과 비슷한 출력이 보여요. 핵심은 두 줄이에요. nvidia.com/gpu.count=8은 8개 GPU를 감지했다는 뜻이고, nvidia.com/gpu.product=NVIDIA-H100-80GB-HBM3은 감지된 GPU가 H100 80GB라는 뜻이에요.

nvidia.com/cuda.driver-version.full=535.129.03
nvidia.com/cuda.driver-version.major=535
nvidia.com/cuda.driver-version.minor=129
nvidia.com/cuda.driver-version.revision=03
nvidia.com/cuda.driver.major=535
nvidia.com/cuda.driver.minor=129
nvidia.com/cuda.driver.rev=03
nvidia.com/cuda.runtime-version.full=12.2
nvidia.com/cuda.runtime-version.major=12
nvidia.com/cuda.runtime-version.minor=2
nvidia.com/cuda.runtime.major=12
nvidia.com/cuda.runtime.minor=2
nvidia.com/gfd.timestamp=1729173628
nvidia.com/gpu-driver-upgrade-state=upgrade-done
nvidia.com/gpu.compute.major=9
nvidia.com/gpu.compute.minor=0
nvidia.com/gpu.count=8
nvidia.com/gpu.deploy.container-toolkit=true
nvidia.com/gpu.deploy.dcgm=true
nvidia.com/gpu.deploy.dcgm-exporter=true
nvidia.com/gpu.deploy.device-plugin=true
nvidia.com/gpu.deploy.driver=pre-installed
nvidia.com/gpu.deploy.gpu-feature-discovery=true
nvidia.com/gpu.deploy.mig-manager=true
nvidia.com/gpu.deploy.node-status-exporter=true
nvidia.com/gpu.deploy.operator-validator=true
nvidia.com/gpu.family=hopper
nvidia.com/gpu.machine=Standard-PC-Q35-ICH9-2009
nvidia.com/gpu.memory=81559
nvidia.com/gpu.mode=compute
nvidia.com/gpu.present=true
nvidia.com/gpu.product=NVIDIA-H100-80GB-HBM3
nvidia.com/gpu.replicas=1
nvidia.com/gpu.sharing-strategy=none
nvidia.com/mig.capable=true
nvidia.com/mig.config=all-disabled
nvidia.com/mig.config.state=success
nvidia.com/mig.strategy=single
nvidia.com/mps.capable=false
nvidia.com/vgpu.present=false
nvidia.com/gpu-driver-upgrade-enabled: true

KubeAI 설치

KubeAI를 쿠버네티스에 배포해요.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: kubeai
---
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
  name: kubeai
  namespace: kubeai
spec:
  repo: https://www.kubeai.org
  chart: kubeai
  targetNamespace: kubeai
EOF

설치는 보통 2~3분 걸려요. 준비가 됐는지 확인하려면 파드를 지켜봐요.

kubectl get -n kubeai -w --field-selector=status.phase=Running pods

이 명령은 kubeai 네임스페이스의 실행 중인 파드를 계속 보여줘요. 아래처럼 나오면 KubeAI가 준비된 거예요.

kubeai-5f6cb9984b-nghpj   1/1     Running   0          10s

1/1은 요청한 파드 1개 중 1개가 준비됐다는 뜻이에요. 감시를 멈추려면 Ctrl + C를 눌러요.

Hermes 3와 Nemotron 모델 내려받고 서빙하기

두 모델을 vLLM으로 내려받고 서빙해요.

cat <<EOF | kubectl apply -f -
apiVersion: kubeai.org/v1
kind: Model
metadata:
  name: hermes-3-llama-3.1-8b
  namespace: kubeai
spec:
  features: [TextGeneration]
  owner: NousResearch
  url: hf://NousResearch/Hermes-3-Llama-3.1-8B
  engine: VLLM
  resourceProfile: nvidia-gpu-h100:1
  minReplicas: 1
---
apiVersion: kubeai.org/v1
kind: Model
metadata:
  name: llama-3.1-nemotron-70b-instruct
  namespace: kubeai
spec:
  features: [TextGeneration]
  owner: nvidia
  url: hf://nvidia/Llama-3.1-Nemotron-70B-Instruct-HF
  engine: VLLM
  resourceProfile: nvidia-gpu-h100:4
  minReplicas: 1
  args:
    - --tensor-parallel-size=4
EOF

Hermes 3 모델은 단일 GPU에 올릴 수 있어서 spec.resourceProfilenvidia-gpu-h100:1로 두고 GPU 1개를 할당했어요. 반면 Nemotron 모델은 단일 GPU에 올릴 수 없어서 spec.resourceProfilenvidia-gpu-h100:4로 두고 GPU 4개를 할당하고, vLLM에 --tensor-parallel-size=4 인자를 넘겨 텐서 병렬 전략으로 모델을 4개 GPU에 나눠 담아요. 분산 추론·서빙은 vLLM 문서에서 더 자세히 볼 수 있어요.

모델이 내려받아지고 서빙되는 시점을 확인하려면 이렇게 파드를 지켜봐요.

kubectl get -n kubeai -w --field-selector=status.phase=Running pods | grep model-

아래처럼 나오면 두 모델이 내려받고 서빙 중이에요.

model-hermes-3-llama-3.1-8b-79cdb64947-cb7cd             1/1     Running   0          2m21s
model-llama-3.1-nemotron-70b-instruct-57cf757d9d-nc9l4   1/1     Running   0          5m5s

1/1은 각 모델에 요청한 파드 1개가 모두 실행 중이라는 뜻이에요. 감시를 멈추려면 Ctrl + C를 눌러요.

KubeAI 웹 UI로 모델 사용하기

KubeAI 웹 UI를 내 컴퓨터에서 접근할 수 있게 해요.

kubectl -n kubeai port-forward service/openwebui 8080:80 &> /dev/null &

브라우저에서 http://localhost:8080으로 가서, 페이지 왼쪽 위 Select a model을 누르고 다음 중 하나를 선택해요.

  • hermes-3-llama-3.1-8b — Hermes 3 모델
  • llama-3.1-nemotron-70b-instruct — Nemotron 모델

페이지 아래쪽 Send a Message 입력란에 테스트할 프롬프트를 넣어요.

In a small table, compare and contrast machine learning and deep learning.

KubeAI의 OpenAI 호환 API로 모델 사용하기

OpenAI 호환 API로도 모델을 쓸 수 있어요. 먼저 curl과 jq를 설치해요.

sudo apt update && sudo apt -y install curl jq

KubeAI의 OpenAI 호환 API에 접근을 활성화해요.

kubectl -n kubeai port-forward service/kubeai 8081:80 &> /dev/null &

API의 /models 엔드포인트로 KubeAI가 서빙하는 모델을 나열해요.

curl -sS http://localhost:8081/openai/v1/models | jq .

다음과 비슷한 출력이 보여요.

{
  "object": "list",
  "data": [
    {
      "id": "llama-3.1-nemotron-70b-instruct",
      "created": 1729174478,
      "object": "model",
      "owned_by": "nvidia",
      "features": [
        "TextGeneration"
      ]
    },
    {
      "id": "hermes-3-llama-3.1-8b",
      "created": 1729174478,
      "object": "model",
      "owned_by": "NousResearch",
      "features": [
        "TextGeneration"
      ]
    }
  ]
}

예제 프롬프트를 API에 보내볼게요.

curl -sS -d @- http://localhost:8081/openai/v1/completions -H "Content-Type: application/json" <<EOF | jq .
{
  "model": "llama-3.1-nemotron-70b-instruct",
  "prompt": "Machine learning engineers are ",
  "temperature": 0
}
EOF

위 예시에서 Nemotron 모델이 "Machine learning engineers are"라는 프롬프트에 응답해요. 다음과 비슷한 출력이 나와요.

{
  "id": "cmpl-04a8ee4abf7247b5859755e7684ade21",
  "object": "text_completion",
  "created": 1729179938,
  "model": "llama-3.1-nemotron-70b-instruct",
  "choices": [
    {
      "index": 0,
      "text": "2023's most in-demand tech professionals\nAccording to a new report from Indeed",
      "logprobs": null,
      "finish_reason": "length",
      "stop_reason": null,
      "prompt_logprobs": null
    }
  ],
  "usage": {
    "prompt_tokens": 6,
    "total_tokens": 22,
    "completion_tokens": 16
  }
}

API 사용법은 vLLM 문서에서 더 자세히 볼 수 있어요.

NVTOP으로 GPU 활용률 관찰하기

인스턴스에서 NVTOP을 실행해요.

nvtop -d 2

출력을 보면 모델이 GPU1·GPU4·GPU5·GPU6·GPU7에 올라와 있고, GPU0·GPU2·GPU3은 할당되지 않아 다른 작업에 쓸 수 있음을 알 수 있어요. 메모리 사용량에 주목하세요. 할당된 GPU는 메모리를 쓰고 있고, 할당되지 않은 GPU는 메모리 사용이 없을 거예요.

브라우저에서 다시 http://localhost:8080으로 가서 llama-3.1-nemotron-70b-instruct 모델을 선택하고, 큰 프롬프트를 넣어봐요.

In 10,000 words or more, explain what large language models are.

nvtop에서 순간적으로 GPU4부터 GPU7까지가 Nemotron 추론에 쓰이는 모습이 보여요. 이번에는 hermes-3-llama-3.1-8b 모델을 선택하고 같은 프롬프트를 넣으면, GPU1 하나만 Hermes 3 추론에 쓰이는 걸 확인할 수 있어요. 이렇게 nvtop 하나만으로 어떤 모델이 몇 개 GPU를 점유하고 있는지 한눈에 파악할 수 있어요.

더 알아보기