附註:

在 Oracle Kubernetes 引擎 (OKE) 上部署 OpenAI vLLM Production Stack

簡介

採用大型語言模型 (LLM) 進行生產工作負載的組織面臨關鍵基礎架構決策:依賴第三方推論 API 或部署自行代管的推論堆疊。自主代管的部署具有顯著的優勢:完整的資料隱私與合規控制、子 100 毫秒的推論延遲,可免除往返網路、大規模可預測的成本,以及無供應商綁定的自由微調和提供任何開放原始碼模型。

不過,從頭開始建立生產級 LLM 推論堆疊相當複雜。它需要 GPU 感知容器協調、跨多個模型複本的智慧型要求路由、大型模型權重的永久儲存體,以及持續監控,而且全都能夠可靠地整合及執行。

Oracle Cloud Infrastructure 提供多種 AI 推論路徑。OCI Generative AI 服務提供完全託管的體驗,將專用 AI 叢集隔離到您的租用戶中,適用於想要快速開始使用受支援模型的團隊。本教學課程採用另一種方法:在 OKE 上部署您自己的推論堆疊。此路徑是專為需要精確控制 GPU 驅動程式、CUDA 版本、模型組態及提供參數的團隊所設計,或是訓練和微調自訂模型並想要直接提供服務的團隊所設計。OCI 提供由超低延遲 RDMA 叢集網路連線的 NVIDIA A10、A100 和 H100 GPU 裸機 GPU 執行個體,讓您享有與內部部署相同層級的硬體控制,同時享有雲端彈性。

vLLM Production Stack 透過在 vLLM 上建置的開放原始碼 Kubernetes 原生平台 (即 Meta、Mistral AI 和 IBM 等組織在生產中使用的高輸送量推論引擎),解決了自行代管推論的複雜性。透過高效率的 GPU 記憶體管理和 KV 快取最佳化,與標準服務架構相比,傳輸量最高可提高 24x。與 OKE 和 OCI GPU 資源配置結合,您將獲得具備企業級網路、儲存及安全性的符合生產環境需求推論平台。本教學課程中使用的 OCI 部署命令檔是在官方 vLLM 生產堆疊儲存區域中提供和維護。

本教學課程將逐步引導您完成 OKE 上的 vLLM Production Stack 部署,從基礎架構佈建到執行您的第一個推論要求。

注意:本教學課程會逐步佈建使用 OCI CLI 的資源,協助您瞭解 GPU 推論部署所需的 OCI 雲端資源完整流程。對於生產環境,建議使用 TerraformOCI Resource Manager (Shepherd) 為可重複且由版本控制的部署編碼此基礎架構。

此架構圖顯示含有子網路、OKE 叢集、GPU 節點集區、vLLM Pod 及負載平衡器的 VCN

本教學課程使用下列 OCI 服務:

服務 目的
Oracle Kubernetes 引擎 (OKE) 適用於容器協調和 GPU 工作負載排程的受管理 Kubernetes 叢集
OCI Compute (GPU 資源配置) 用於模型推論的 NVIDIA A10 (24GB) 和 A100 (80GB) GPU 執行個體
OCI 區塊磁碟區 透過可設定的效能層提供模型權重的永久儲存
OCI 虛擬雲端網絡 (VCN) 網路基礎架構,包括子網路、閘道和安全清單
OCI 負載平衡器 外部存取推論端點
OCI 堡壘主機 專用叢集存取的受管理 SSH 通道
OCI Object Storage 使用預先認證要求 (PAR) URL 的替代模型來源

目標

在本教學中,您將:

必備條件

注意:本教學課程中的輸出和螢幕擷取畫面範例使用 us-chicago-1 。您可以透過設定 OCI_REGION,在任何支援的區域中部署。GPU 容量因區域和可用性網域而異,因此在部署之前,請確認您的目標 GPU 資源配置可供使用。請查看依區域區分的 GPU 資源配置可用性,如果您發生容量錯誤,請準備好嘗試其他可用性網域 (GPU_AD_INDEX)。

注意:本教學課程提供付費 GPU 資源 (例如 VM.GPU.A10.1)。它不是 OCI 永遠免費工作負載。完成時一律執行清除步驟,以避免持續收費。

注意:本教學課程部署 openai/gpt-oss-20b,這是來自 OpenAI 的 Apache 2.0 授權模型。不需要 Hugging Face 權杖。如果您想要部署關卡模型 (例如 Meta Llama 3.1),您將需要一個具有 API 權杖的 Hugging Face 帳戶。

工作 1:設定環境變數

請先設定必要的 OCI 組態後再部署基礎架構。

  1. 在 OCI 主控台中找到您的區間 OCID。導覽至身分識別與安全 > 區間,然後按一下您的目標區間並複製 OCID。

    oci iam compartment list --query 'data[].{name:name,id:id}' --output table

    顯示區間 OCID 的 OCI CLI 輸出

  2. 匯出必要的環境變數。

    export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx"
  3. 您可以選擇設定下列任一環境變數來覆寫預設組態。

    變數 Default 描述
    OCI_REGION us-ashburn-1 用於部署的 OCI 區域
    OCI_PROFILE DEFAULT OCI CLI 組態設定檔
    CLUSTER_NAME production-stack OKE 叢集的名稱
    GPU_SHAPE VM.GPU.A10.1 節點集區的 GPU 運算資源配置
    GPU_NODE_COUNT 1 集區中的 GPU 節點數目
    GPU_BOOT_VOLUME_GB 200 GPU 節點的開機磁區大小 (GB)
    CPU_BOOT_VOLUME_GB 100 CPU 節點的開機磁區大小 (GB)
    GPU_AD_INDEX 1 GPU 位置的可用性網域索引 (以 0 為基礎)
    PRIVATE_CLUSTER true 對公用 Kubernetes API 端點設為 false
    KUBERNETES_VERSION v1.31.10 OKE 叢集的 Kubernetes 版本

    例如,若要部署兩個 A100 GPU 節點:

    export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx"
    export GPU_SHAPE="BM.GPU.A100-v2.8"
    export GPU_NODE_COUNT="2"
  4. 請複查可用的 GPU 資源配置,並根據您的模型大小需求選取一個資源配置。

    形狀 GPU GPU 型態 GPU 記憶體 建議為
    VM.GPU.A10.1 1 NVIDIA A10 24 GB 7B – 13B 參數模型
    VM.GPU.A10.2 2 NVIDIA A10 48 GB Tensor 與小型模型平行
    BM.GPU4.8 8 NVIDIA A100 40 GB 320 GB 70B 模型,符合成本效益
    BM.GPU.A100-v2.8 8 NVIDIA A100 80 GB 640 GB 70B+ 參數模型
    BM.GPU.H100.8 8 NVIDIA H100 640 GB 最大的模型,RDMA 支援

    備註:裸機資源配置 (BM.*) 提供專屬硬體,不會產生虛擬化負荷,同時支援多 GPU Tensor 平行機制。虛擬機器資源配置 (VM.*) 對小型模型而言更具成本效益。

    注意:本教學課程使用 VM.GPU.A10.1 (具有 24 GB GPU 記憶體的單一 NVIDIA A10) 來部署 openai/gpt-oss-20b,這是一款通常適用於單一 A10 GPU 之 3.6B 作用中參數的專家混合 (MoE) 模型。進階部分針對大型模型 (例如 Llama 3.1 70B) 使用 BM.GPU.H100.8 示範多 GPU 組態。

工作 2:使用自動命令檔部署 (快速入門)

vLLM Production Stack 包含自動化部署指令碼,可佈建所有 OCI 資源,並使用單一指令部署推論堆疊。使用此方法進行快速部署。任務 3 到 10 會針對想要自訂處理的使用者個別涵蓋每個步驟。

  1. 複製 vLLM Production Stack 儲存區域。

    git clone https://github.com/vllm-project/production-stack.git
    cd production-stack/deployment_on_cloud/oci
  2. 匯出您的區間 OCID。

    export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx"
  3. 執行部署指令碼。

    ./entry_point.sh setup

    entry_point.sh 設定的終端機輸出,顯示建立 VCN、叢集、堡壘主機以及節點集區

    若為公用叢集 (PRIVATE_CLUSTER=false),則設定會以單一命令建立所有基礎架構,並部署 vLLM 堆疊。將 Helm 值檔案傳送為第二個引數:

    PRIVATE_CLUSTER=false ./entry_point.sh setup ./production_stack_specification.yaml

    若為專用叢集 (預設值),則設定會建立基礎架構,但無法直接連線 Kubernetes API。開啟個別的終端機並啟動通道,然後部署:

    # In a separate terminal, start the SSH tunnel (auto-reconnects on drops):
    ./entry_point.sh tunnel
    
    # Back in the first terminal, deploy vLLM:
    ./entry_point.sh deploy-vllm ./production_stack_specification.yaml
  4. 驗證部署是否在執行中。

    kubectl get pods

    預期輸出:

    NAME                                            READY   STATUS    RESTARTS   AGE
    vllm-deployment-router-xxxxxxxxxx-xxxxx          1/1     Running   0          5m
    vllm-gpt-oss-deployment-vllm-xxxxxxxxxx-xxxxx    1/1     Running   0          5m

注意:如果兩個 Pod 都顯示 Running 狀態,您的部署就緒。先跳過工作 10:測試推論端點

注意: GPU 執行個體受 OCI 容量限制。如果指令碼停留在「等待 GPU 節點」迴圈超過 15 分鐘,則選取的可用性網域中可能無法使用 GPU 資源配置。檢查節點集區狀態與 oci ce node-pool get,並尋找「超出主機容量」錯誤。若要解決此問題,請使用 ./entry_point.sh cleanup 進行清除,然後使用不同的可用性網域 (例如 GPU_AD_INDEX=0GPU_AD_INDEX=2) 或其他 GPU 資源配置 (例如 GPU_SHAPE=VM.GPU.A10.2) 重新部署。

注意:部署命令檔使用會產生大量成本的 GPU 執行處理 (單一 A10 GPU 的每日約 50 美元)。完成時一律執行 ./entry_point.sh cleanup,以避免持續收費。

作業 3:建立 VCN 和網路

建立 OKE 叢集所需的 OCI 網路基礎架構。這包括虛擬雲端網路 (VCN)、閘道、路由表、安全清單以及子網路。每個網路資源會在幾秒鐘內建立;完整的一組指令會在 2 分鐘內完成。

  1. 建立一個含有 10.0.0.0/16 CIDR 區塊的 VCN。

    VCN_ID=$(oci network vcn create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --display-name "${CLUSTER_NAME}-vcn" \
        --cidr-blocks '["10.0.0.0/16"]' \
        --dns-label "prodstack" \
        --query "data.id" \
        --raw-output)
  2. 建立公用子網路路由的網際網路閘道。

    IGW_ID=$(oci network internet-gateway create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-igw" \
        --is-enabled true \
        --query "data.id" \
        --raw-output)
  3. 建立專用子網路輸出流量的 NAT 閘道。

    NAT_ID=$(oci network nat-gateway create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-nat" \
        --query "data.id" \
        --raw-output)
  4. 建立服務閘道以存取 Oracle Services Network。OKE 雲端控制器會使用 Oracle 服務來初始化工作者節點 (設定可用性網域標籤、移除初始化原則)。若無服務閘道,GPU 節點可能會維持未起始狀態,而區塊磁碟區佈建將會失敗。

    SGW_SERVICE_ID=$(oci network service list \
        --query "data[?contains(name, 'All') && contains(name, 'Services')].id | [0]" \
        --raw-output)
    
    SGW_SERVICE_NAME=$(oci network service list \
        --query "data[?contains(name, 'All') && contains(name, 'Services')].\"cidr-block\" | [0]" \
        --raw-output)
    
    SGW_ID=$(oci network service-gateway create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-sgw" \
        --services "[{\"serviceId\": \"${SGW_SERVICE_ID}\"}]" \
        --query "data.id" \
        --raw-output)
  5. 建立專用和公用子網路的路由表。

    PRIVATE_RT_ID=$(oci network route-table create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-private-rt" \
        --route-rules "[
            {\"cidrBlock\": \"0.0.0.0/0\", \"networkEntityId\": \"${NAT_ID}\"},
            {\"destination\": \"${SGW_SERVICE_NAME}\", \"destinationType\": \"SERVICE_CIDR_BLOCK\", \"networkEntityId\": \"${SGW_ID}\"}
        ]" \
        --query "data.id" \
        --raw-output)
    
    PUBLIC_RT_ID=$(oci network route-table create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-public-rt" \
        --route-rules "[{\"cidrBlock\": \"0.0.0.0/0\", \"networkEntityId\": \"${IGW_ID}\"}]" \
        --query "data.id" \
        --raw-output)

    注意:專用路由表有兩個規則:用於一般網際網路存取的 NAT 閘道路由 (提取容器映像檔、下載模型),以及用於直接存取 Oracle Services Network 的服務閘道路由。服務閘道路由至關重要。若無,OKE 雲端控制器便無法初始化工作節點,以防止區塊磁碟區佈建。公用路由表使用網際網路閘道存取負載平衡器。

  6. 使用 OKE 所需的傳入和傳出規則建立安全清單。

    SL_ID=$(oci network security-list create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-sl" \
        --egress-security-rules '[{"destination": "0.0.0.0/0", "protocol": "all", "isStateless": false}]' \
        --ingress-security-rules '[
            {"source": "0.0.0.0/0", "protocol": "6", "isStateless": false, "tcpOptions": {"destinationPortRange": {"min": 22, "max": 22}}, "description": "SSH access"},
            {"source": "10.0.0.0/16", "protocol": "all", "isStateless": false, "description": "VCN internal traffic"},
            {"source": "10.244.0.0/16", "protocol": "all", "isStateless": false, "description": "Kubernetes pods CIDR"},
            {"source": "10.96.0.0/16", "protocol": "all", "isStateless": false, "description": "Kubernetes services CIDR"},
            {"source": "0.0.0.0/0", "protocol": "1", "isStateless": false, "icmpOptions": {"type": 3, "code": 4}, "description": "Path MTU discovery"}
        ]' \
        --query "data.id" \
        --raw-output)

    安全性備註:此安全性清單的範圍廣泛,以簡化操作。若為生產環境,請將 SSH 限制在堡壘主機子網路和您的 IP 範圍,而且偏好個別的安全清單或每個子網路的 NSG,因此負載平衡器子網路不允許使用來自 0.0.0.0/0 的 SSH。

    安全預設值:從將 SSH 限制為您的公用 IP 開始,然後只將 SSH 規則連附至堡壘主機子網路。您可以將 Kubernetes Pod/ 服務 CIDR 保留在工作者子網路上,並完全從負載平衡器子網路省略 SSH。

    選擇性 (建議) 分割:為堡壘主機子網路建立僅限 SSH 的小型安全清單,並為工作者 /LB 子網路建立個別清單。

    BASTION_SL_ID=$(oci network security-list create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-bastion-sl" \
        --egress-security-rules '[{"destination": "0.0.0.0/0", "protocol": "all", "isStateless": false}]' \
        --ingress-security-rules '[
            {"source": "YOUR_PUBLIC_IP/32", "protocol": "6", "isStateless": false, "tcpOptions": {"destinationPortRange": {"min": 22, "max": 22}}, "description": "SSH from your IP"}
        ]' \
        --query "data.id" \
        --raw-output)
    
    WORKER_SL_ID=$(oci network security-list create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-worker-sl" \
        --egress-security-rules '[{"destination": "0.0.0.0/0", "protocol": "all", "isStateless": false}]' \
        --ingress-security-rules '[
            {"source": "10.0.0.0/16", "protocol": "all", "isStateless": false, "description": "VCN internal traffic"},
            {"source": "10.244.0.0/16", "protocol": "all", "isStateless": false, "description": "Kubernetes pods CIDR"},
            {"source": "10.96.0.0/16", "protocol": "all", "isStateless": false, "description": "Kubernetes services CIDR"},
            {"source": "0.0.0.0/0", "protocol": "1", "isStateless": false, "icmpOptions": {"type": 3, "code": 4}, "description": "Path MTU discovery"}
        ]' \
        --query "data.id" \
        --raw-output)
    
    LB_SL_ID=$(oci network security-list create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-lb-sl" \
        --egress-security-rules '[{"destination": "0.0.0.0/0", "protocol": "all", "isStateless": false}]' \
        --ingress-security-rules '[
            {"source": "10.0.0.0/16", "protocol": "all", "isStateless": false, "description": "VCN internal traffic"},
            {"source": "0.0.0.0/0", "protocol": "6", "isStateless": false, "tcpOptions": {"destinationPortRange": {"min": 80, "max": 80}}, "description": "HTTP (public LB)"},
            {"source": "0.0.0.0/0", "protocol": "6", "isStateless": false, "tcpOptions": {"destinationPortRange": {"min": 443, "max": 443}}, "description": "HTTPS (public LB)"}
        ]' \
        --query "data.id" \
        --raw-output)

    注意:如果您只使用內部負載平衡器,請將上方的 0.0.0.0/0 來源取代為 10.0.0.0/16 (或您的 VCN CIDR)。用法:BASTION_SL_ID 附加至堡壘主機子網路、將 WORKER_SL_ID 附加至 API/ 工作者子網路,以及將 LB_SL_ID 附加至負載平衡器子網路。

    注意: GPU 工作節點必須要有 Kubernetes Pod CIDR (10.244.0.0/16) 和服務 CIDR (10.96.0.0/16) 規則,才能向叢集註冊。ICMP 類型 3 代碼 4 規則會啟用路徑 MTU 尋找,以避免發生封包片段問題。

  7. 建立子網路。叢集需要四個子網路:一個用於 Kubernetes API 端點、一個用於工作節點、一個用於負載平衡器,另一個用於存取專用叢集的堡壘主機。

    API_SUBNET_ID=$(oci network subnet create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-api-subnet" \
        --cidr-block "10.0.0.0/28" \
        --route-table-id "${PRIVATE_RT_ID}" \
        --security-list-ids "[\"${SL_ID}\"]" \
        --dns-label "kubeapi" \
        --prohibit-public-ip-on-vnic true \
        --query "data.id" \
        --raw-output)
    
    WORKER_SUBNET_ID=$(oci network subnet create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-worker-subnet" \
        --cidr-block "10.0.10.0/24" \
        --route-table-id "${PRIVATE_RT_ID}" \
        --security-list-ids "[\"${SL_ID}\"]" \
        --dns-label "workers" \
        --prohibit-public-ip-on-vnic true \
        --query "data.id" \
        --raw-output)
    
    LB_SUBNET_ID=$(oci network subnet create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-lb-subnet" \
        --cidr-block "10.0.20.0/24" \
        --route-table-id "${PUBLIC_RT_ID}" \
        --security-list-ids "[\"${SL_ID}\"]" \
        --dns-label "loadbalancers" \
        --query "data.id" \
        --raw-output)
    
    BASTION_SUBNET_ID=$(oci network subnet create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --vcn-id "${VCN_ID}" \
        --display-name "${CLUSTER_NAME}-bastion-subnet" \
        --cidr-block "10.0.30.0/24" \
        --route-table-id "${PUBLIC_RT_ID}" \
        --security-list-ids "[\"${SL_ID}\"]" \
        --dns-label "bastion" \
        --query "data.id" \
        --raw-output)
    子網路 CIDR 可見性 目的
    API 端點 10.0.0.0/28 專用 Kubernetes API 伺服器
    工作節點 10.0.10.0/24 專用 GPU 運算節點
    負載平衡器 10.0.20.0/24 公用 外部服務存取
    堡壘主機 10.0.30.0/24 公用 專用叢集存取的 SSH 通道

作業 4:建立 OKE 叢集

使用在任務 3 中建立的網路資源,在 OKE 上部署託管的 Kubernetes 叢集。叢集佈建時間約為 10 分鐘。本教學課程會建立一個專用叢集 (命令檔預設值),不會對 Kubernetes API 端點使用保留的公用 IP。生產環境工作負載建議使用專用叢集,因為 API 伺服器未公開至公用網際網路。

  1. 建立具有專用端點的 OKE 叢集。

    oci ce cluster create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --name "${CLUSTER_NAME}" \
        --vcn-id "${VCN_ID}" \
        --kubernetes-version "${KUBERNETES_VERSION}" \
        --endpoint-subnet-id "${API_SUBNET_ID}" \
        --service-lb-subnet-ids "[\"${LB_SUBNET_ID}\"]" \
        --endpoint-public-ip-enabled false

    指令會傳回工作要求 ID。從叢集清單中取得叢集 ID。

    CLUSTER_ID=$(oci ce cluster list \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --lifecycle-state CREATING \
        --query 'data[0].id' \
        --raw-output)
    echo "CLUSTER_ID=${CLUSTER_ID}"

    注意:專用叢集不需要保留的公用 IP。工作者節點仍可透過 NAT 閘道存取網際網路,以提取容器映像檔和下載模型。唯有 kubectl 存取需要透過堡壘主機的 SSH 通道 (在後續步驟中設定)。

  2. 等待叢集變成 ACTIVE。此步驟大約需要 10 分鐘。

    oci ce cluster get \
        --cluster-id "${CLUSTER_ID}" \
        --query "data.\"lifecycle-state\"" \
        --raw-output

    輪詢命令,直到輸出傳回 ACTIVE 為止。

    選擇性:顯示簡潔的狀態摘要 (包括專用 API 端點)。

    oci ce cluster get \
        --cluster-id "${CLUSTER_ID}" \
        --query 'data.{name:name,state:"lifecycle-state","k8s-version":"kubernetes-version",endpoint:endpoints."private-endpoint"}' \
        --output table

    顯示 OKE 叢集為 ACTIVE 狀態的 OCI CLI 輸出

    kubectl get nodes -o wide

    顯示兩個「就緒」節點的 kubectl 取得節點輸出

  3. 建立 OCI 堡壘主機以存取專用叢集。此堡壘主機為專用 Kubernetes API 端點提供受管理的 SSH 通道。

    BASTION_ID=$(oci bastion bastion create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --bastion-type STANDARD \
        --target-subnet-id "${BASTION_SUBNET_ID}" \
        --name "${CLUSTER_NAME}-bastion" \
        --client-cidr-list '["YOUR_PUBLIC_IP/32"]' \
        --query "data.id" \
        --raw-output)

    注意:請以您目前的公用 IP 取代 YOUR_PUBLIC_IP/32。如果是共用網路,請改用公司 CIDR 區塊。

    等待堡壘主機變成 ACTIVE (約 1 分鐘)。

    oci bastion bastion get \
        --bastion-id "${BASTION_ID}" \
        --query "data.\"lifecycle-state\"" \
        --raw-output

    安全性備註:對於生產,請勿使用 0.0.0.0/0。將 --client-cidr-list 限制為您的公用 IP 或公司 CIDR (例如 "YOUR_PUBLIC_IP/32"),否則網際網路上的任何人都可以嘗試堡壘主機階段作業。

  4. 使用專用端點下載 kubeconfig。

    oci ce cluster create-kubeconfig \
        --cluster-id "${CLUSTER_ID}" \
        --file "${HOME}/.kube/config" \
        --region "${OCI_REGION}" \
        --token-version 2.0.0 \
        --kube-endpoint PRIVATE_ENDPOINT
  5. 取得 SSH 通道的專用端點 IP 位址。

    PRIVATE_ENDPOINT=$(oci ce cluster get \
        --cluster-id "${CLUSTER_ID}" \
        --query "data.endpoints.\"private-endpoint\"" \
        --raw-output)
    PRIVATE_IP="${PRIVATE_ENDPOINT%:*}"
    echo "Private endpoint IP: ${PRIVATE_IP}"
  6. 建立堡壘主機連接埠轉送階段作業。您將需要一個 SSH 公開金鑰檔案。

    SESSION_ID=$(oci bastion session create-port-forwarding \
        --bastion-id "${BASTION_ID}" \
        --target-private-ip "${PRIVATE_IP}" \
        --target-port 6443 \
        --session-ttl 10800 \
        --display-name "kubectl-tunnel" \
        --ssh-public-key-file ~/.ssh/id_rsa.pub \
        --query "data.id" \
        --raw-output)

    等待階段作業變成 ACTIVE,然後取得 SSH 命令。

    oci bastion session get \
        --session-id "${SESSION_ID}" \
        --query "data.{state:\"lifecycle-state\", ssh:\"ssh-metadata\".command}" 2>&1
  7. 開啟個別的終端機,然後使用上一個步驟的命令啟動 SSH 通道。通道會將本機連接埠 6443 轉送至專用 Kubernetes API。

    ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -N -L 6443:<PRIVATE_IP>:6443 \
        -p 22 -o ServerAliveInterval=30 \
        <SESSION_OCID>@host.bastion.<REGION>.oci.oraclecloud.com

    注意:<PRIVATE_IP><SESSION_OCID><REGION> 取代為上一個步驟的值。在作業階段期間保持開啟此終端機。當您的 SSH 代理程式載入多個金鑰時,-o IdentitiesOnly=yes 旗標會防止「認證失敗太多」錯誤。

  8. 更新 kubeconfig 以透過本機通道連線。

    CLUSTER_NAME_KUBE=$(kubectl config view --minify -o jsonpath='{.clusters[0].name}')
    kubectl config set-cluster "${CLUSTER_NAME_KUBE}" \
        --server=https://127.0.0.1:6443 \
        --insecure-skip-tls-verify=true

    注意:由於已針對專用端點 IP 發出叢集憑證,而非 127.0.0.1,因此需要 --insecure-skip-tls-verify 旗標。這是安全的,因為流量是透過 SSH 通道進行加密。

  9. 如果您使用非預設的 OCI CLI 設定檔 (例如,API_KEY_AUTH),請更新 kubeconfig 以使用它。產生的 kubeconfig 預設為產生權杖的 DEFAULT 設定檔。

    kubectl config set-credentials \
        $(kubectl config view --minify -o jsonpath='{.users[0].name}') \
        --exec-env=OCI_CLI_PROFILE=${OCI_PROFILE}

    提示:步驟 6-9 會由 ./entry_point.sh tunnel 自動化,如果 SSH 通道在長時間執行的作業 (例如磁碟擴充) 中刪除,也會自動重新連線。在個別的終端機執行該程序,並在階段作業期間讓它繼續執行。

  10. 驗證叢集存取。

kubectl get nodes

此時,輸出將不會顯示任何節點,因為尚未新增 GPU 節點集區。

No resources found

作業 5:新增 GPU 節點集區

新增具有 GPU 運算執行處理的節點集區至 OKE 叢集。

  1. 尋找與 GPU 相容的最新 OKE 節點映像檔。OKE 需要預先安裝 kubelet 和節點註冊元件的特定影像。使用 node-pool-options API 尋找 Kubernetes 版本的正確映像檔。

    GPU_IMAGE_ID=$(oci ce node-pool-options get \
        --node-pool-option-id all \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --query "data.sources[?contains(\"source-name\", 'GPU') && contains(\"source-name\", 'OKE-${KUBERNETES_VERSION#v}') && contains(\"source-name\", '8.10')].\"image-id\" | [0]" \
        --raw-output)
    echo "GPU Image: ${GPU_IMAGE_ID}"

    注意:符合您 Kubernetes 版本 (例如 OKE-1.31.10) 之 Oracle Linux 8.10 GPU 映像檔的查詢篩選。如果您需要以 ARM 為基礎的影像,請以適當的篩選器取代 8.10

  2. 決定具有 GPU 資源配置的可用性網域。並非所有可用性網域都有 GPU 容量。

    AD=$(oci iam availability-domain list \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --query "data[${GPU_AD_INDEX}].name" \
        --raw-output)
    echo "Availability Domain: ${AD}"

    注意: GPU 容量因區域和可用性網域而異。如果建立節點集區因發生「超出主機容量」錯誤而失敗,請嘗試其他可用性網域 (GPU_AD_INDEX) 或 GPU 資源配置,或透過一般 OCI 處理作業要求容量。

  3. 建立具有 200 GB 開機磁碟區的 GPU 節點集區。

    oci ce node-pool create \
        --compartment-id "${OCI_COMPARTMENT_ID}" \
        --cluster-id "${CLUSTER_ID}" \
        --name "${GPU_NODE_POOL_NAME:-gpu-pool}" \
        --kubernetes-version "${KUBERNETES_VERSION}" \
        --node-shape "${GPU_SHAPE}" \
        --node-image-id "${GPU_IMAGE_ID}" \
        --node-boot-volume-size-in-gbs "${GPU_BOOT_VOLUME_GB:-200}" \
        --size "${GPU_NODE_COUNT}" \
        --placement-configs "[{\"availabilityDomain\": \"${AD}\", \"subnetId\": \"${WORKER_SUBNET_ID}\"}]" \
        --initial-node-labels '[{"key": "app", "value": "gpu"}, {"key": "nvidia.com/gpu", "value": "true"}]'

    注意: vLLM Helm 圖表稍後會使用節點標籤 app=gpunvidia.com/gpu=true 來排定 GPU 節點上的推論 Pod。200 GB 開機磁碟區提供 vLLM 容器映像檔 (~10 GB) 和模型權重的空間,但檔案系統必須先擴充後才能使用 (請參閱作業 8)。

  4. 等待 GPU 節點變成「就緒」。這通常需要 5 – 10 分鐘的時間,而節點佈建、開機、安裝 GPU 驅動程式,並向叢集註冊。

    注意: GPU 執行處理會受到容量限制的限制。如果節點集區維持「建立中」狀態,請檢查 OCI 主控台中的節點狀態,或使用 oci ce node-pool get。「主機容量不足」錯誤表示該可用性網域中沒有可用的 GPU 執行處理。若要解決此問題,請嘗試使用其他可用性網域 (GPU_AD_INDEX=0GPU_AD_INDEX=2)、嘗試其他 GPU 資源配置,或透過 OCI 主控台或支援回報項目要求保留容量。

    kubectl get nodes -w

    節點就緒後預期輸出:

    NAME          STATUS   ROLES   AGE   VERSION
    10.0.10.x     Ready    node    5m    v1.31.10
  5. 驗證在節點上偵測到 GPU。

    kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.capacity.'nvidia\.com/gpu'

    預期輸出:

    NAME          GPUs
    10.0.10.x     1
  6. 修正要在 GPU 節點上排定的 CoreDNS。OKE GPU 節點具有 nvidia.com/gpu=present:NoSchedule 樣式。在只有 GPU 節點的叢集中,系統 Pod (例如 CoreDNS) 無法在不容許此提示的情況下排定。若無 DNS,Pod 便無法解析外部主機名稱以下載模型。

    kubectl patch deployment coredns -n kube-system --type='json' \
      -p='[{"op": "add", "path": "/spec/template/spec/tolerations/-", "value": {"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}}]'
    
    kubectl patch deployment kube-dns-autoscaler -n kube-system --type='json' \
      -p='[{"op": "add", "path": "/spec/template/spec/tolerations/-", "value": {"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}}]'

    確認 CoreDNS 正在執行中。

    kubectl get pods -n kube-system | grep coredns

    注意:如果您的叢集具有系統工作負載的專用 CPU 節點集區,則不需要此步驟。只有當 GPU 節點是叢集中的唯一節點時,才需要此修正程式。

    oci ce node-pool list --compartment-id "${OCI_COMPARTMENT_ID}" --cluster-id "${CLUSTER_ID}" \
        --query 'data[].{name:name,state:"lifecycle-state",shape:"node-shape",size:"node-config-details".size}' --output table

    OCI CLI 輸出顯示 cpu-pool 和 gpu-pool (兩者都處於 ACTIVE 狀態)

工作 6:安裝 NVIDIA 裝置外掛程式

安裝 NVIDIA 裝置 Plugin,讓 Kubernetes 能夠偵測 GPU 並排定工作負載。

  1. 套用 NVIDIA 裝置 Plugin DaemonSet。

    kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.1/nvidia-device-plugin.yml
  2. 等待外掛程式 Pod 準備就緒。

    kubectl wait --for=condition=Ready pods -l name=nvidia-device-plugin-ds -n kube-system --timeout=300s

    注意:部分 OKE GPU 節點映像檔包含預先安裝的 NVIDIA 裝置 Plugin (nvidia-gpu-device-plugin)。如果映像檔已經包括在內,套用上游 DaemonSet 會建立一個不會導致衝突的第二個執行處理。自動化命令檔 (entry_point.sh deploy-vllm) 一律會安裝它,以確保無論節點映像檔版本為何,GPU 偵測都能運作。

  3. 確認 GPU 可由 Kubernetes 配置。

    kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.allocatable.'nvidia\.com/gpu'

    預期輸出:

    NAME          GPUs
    10.0.10.x     1
  4. 修正 CoreDNS 以容忍 GPU 節點 Taint。在 GPU 節點為唯一工作節點的叢集中,無法排定 CoreDNS Pod,因為 OKE GPU 節點含有 nvidia.com/gpu=present:NoSchedule 污點。若無 DNS,Pod 便無法解析影像註冊或模型下載 URL。

    kubectl patch deployment coredns -n kube-system --type='json' -p='[
      {"op": "add", "path": "/spec/template/spec/tolerations/-", "value": {"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}}
    ]'
    kubectl rollout status deployment/coredns -n kube-system --timeout=60s

    注意:只有當 GPU 節點是叢集中的唯一工作節點時,才需要執行此步驟。如果您有系統工作負載的專用 CPU 節點集區,CoreDNS 預設會在該處排定此修正程式,而且此修正程式不需要。

作業 7:設定儲存

套用 OCI 區塊磁碟區 StorageClass 以提供模型加權的永久儲存。

  1. 套用 StorageClass 定義。

    kubectl apply -f oci-block-storage-sc.yaml

    此檔案定義兩個效能層:

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: oci-block-storage-enc
    provisioner: blockvolume.csi.oraclecloud.com
    parameters:
      vpusPerGB: "10"
    reclaimPolicy: Delete
    volumeBindingMode: WaitForFirstConsumer
    allowVolumeExpansion: true
    StorageClass 效能 使用案例
    oci-block-storage-enc 平衡 (vpusPerGB: 10) 大部分模型的預設、符合成本效益
    oci-block-storage-hp 高效能 (vpusPerGB: 20) 更快速地載入大型模型
  2. 確認 StorageClasses 可用。

    kubectl get storageclass

注意:對於需要跨多個 Pod 共用儲存體的多節點部署,請使用 OCI File Storage Service (NFS) 搭配 ReadWriteMany 存取模式,而不是 Block Volumes。

作業 8:展開 GPU 節點檔案系統

無論您指定的開機磁碟區大小為何,OCI 開機磁碟區都有固定的 ~47 GB 分割區。僅 vLLM 容器映像檔約為 10 GB,而模型權重需要額外的空間。您必須先擴充檔案系統,再部署 vLLM,以避免 DiskPressure 收回。

備註:這是 OCI 特定需求。開機磁碟區佈建為 200 GB,但作業系統預設只使用約 47 GB 的分割區。剩餘空間必須手動宣告。

  1. 驗證 GPU 節點上目前的檔案系統大小。

    GPU_NODE=$(kubectl get nodes -l app=gpu -o jsonpath='{.items[0].metadata.name}')
    kubectl run check-disk --rm -i --restart=Never \
      --image=busybox:latest \
      --overrides="{\"spec\":{\"nodeName\":\"${GPU_NODE}\",\"tolerations\":[{\"operator\":\"Exists\"}],\"containers\":[{\"name\":\"check\",\"image\":\"busybox:latest\",\"command\":[\"sh\",\"-c\",\"chroot /host df -h / | tail -1\"],\"securityContext\":{\"privileged\":true},\"volumeMounts\":[{\"name\":\"host\",\"mountPath\":\"/host\"}]}],\"volumes\":[{\"name\":\"host\",\"hostPath\":{\"path\":\"/\"}}]}}"

    輸出將顯示約 47 GB 總計,確認需要擴充。

  2. 在 GPU 節點上建立授權的 Pod,以執行擴充命令。

    kubectl run expand-disk --restart=Never \
      --image=busybox:latest \
      --overrides="{\"spec\":{\"nodeName\":\"${GPU_NODE}\",\"tolerations\":[{\"operator\":\"Exists\"}],\"containers\":[{\"name\":\"expand\",\"image\":\"busybox:latest\",\"command\":[\"sleep\",\"600\"],\"securityContext\":{\"privileged\":true},\"volumeMounts\":[{\"name\":\"host\",\"mountPath\":\"/host\"}]}],\"volumes\":[{\"name\":\"host\",\"hostPath\":{\"path\":\"/\"}}]}}"

    等待 Pod 啟動。

    kubectl wait --for=condition=Ready pod/expand-disk --timeout=60s
  3. 在單一 kubectl exec 指令中執行全部四個擴充步驟。將它們一起執行,可避免 kubectl exec 在步驟之間傳回結束碼 137 (SIGKILL) 的風險,這些步驟在主機上的重磁碟 I/O 期間可能會發生。

    kubectl exec expand-disk -- chroot /host bash -c '
      set -x
      growpart /dev/sda 3 || echo "growpart: partition may already be expanded"
      sleep 3
      pvresize /dev/sda3
      lvextend -l +100%FREE /dev/ocivolume/root || echo "lvextend: may already be extended"
      xfs_growfs /
      echo "EXPANSION_COMPLETE"
      df -h /
    '
    步驟 命令 目的
    1 growpart /dev/sda 3 展開分割區 3 以使用滿磁碟
    2 pvresize /dev/sda3 調整 LVM 實體磁碟區大小
    3 lvextend -l +100%FREE /dev/ocivolume/root 擴充邏輯磁碟區
    4 xfs_growfs / 增加 XFS 檔案系統以填滿音量

    注意:這四個作業都是等冪的。如果執行傳回結束代碼 137,您可以安全地重新執行整個區塊。在輸出中尋找 EXPANSION_COMPLETE 以確認成功。

  4. 重新啟動 kubelet,讓節點報告更新的可配置儲存,然後驗證並清除。

    kubectl exec expand-disk -- nsenter -t 1 -m -p -- systemctl restart kubelet 2>/dev/null \
        || echo "Warning: kubelet restart returned non-zero (non-critical)"
    kubectl exec expand-disk -- chroot /host df -h /
    kubectl delete pod expand-disk --force

    注意:nsenter 命令會輸入主機的 PID 命名空間,以存取 systemd。一般 chroot /host systemctl restart kubelet 無法從 chroot 內連線至 systemd 匯流排,因此失敗。

    預期的輸出應顯示約 189 GB 的總計。

任務 9:部署 vLLM 生產堆疊

使用 Helm 安裝 vLLM 推論堆疊。

  1. 新增 vLLM Helm 儲存區域。

    helm repo add vllm https://vllm-project.github.io/production-stack
    helm repo update
  2. 複查 Helm 值檔案。production_stack_specification.yaml 會設定 OCI 的模型、資源和儲存。

    servingEngineSpec:
      runtimeClassName: ""
      modelSpec:
      - name: "gpt-oss"
        repository: "vllm/vllm-openai"
        tag: "latest"
        modelURL: "openai/gpt-oss-20b"
    
        replicaCount: 1
        requestCPU: 4
        requestMemory: "24Gi"
        requestGPU: 1
    
        # No HF token needed - Apache 2.0 licensed OpenAI open source model
    
        pvcStorage: "100Gi"
        pvcAccessMode:
          - ReadWriteOnce
        storageClass: "oci-block-storage-enc"
    
        nodeSelector:
          app: gpu
        tolerations:
          - key: "nvidia.com/gpu"
            operator: "Exists"
            effect: "NoSchedule"
    
        extraArgs:
          - "--max-model-len=8192"
          - "--gpu-memory-utilization=0.90"

    備註:openai/gpt-oss-20b 模型是專家混合 (MoE) 模型,具有 20B 總參數和 3.6B 個正向傳遞作用中參數。它是根據 Apache 2.0 授權發行的,因此不需要 Hugging Face 權杖。vllm/vllm-openai 容器映像檔提供與 OpenAI 相容的 API 伺服器,可讓從屬端針對自行代管的端點使用標準 OpenAI SDK 呼叫。

  3. 部署堆疊。請不要在此處使用 --wait,因為路由器 Pod 在下一個步驟修正之前將會是 CrashLoop。

    helm upgrade -i \
        vllm vllm/vllm-stack \
        -f production_stack_specification.yaml

    等待 vLLM 引擎 Pod 啟動 (路由器將會在下一步進行修正)。

    kubectl wait --for=condition=Ready pods -l model=gpt-oss --timeout=600s

    注意:引擎 Pod 在第一次啟動時下載模型加權,因此需要幾分鐘的時間才能變成「就緒」。如果 Pod 停留在 ContainerCreating 中,容器映像檔 (~10 GB) 仍會被提取。使用 kubectl describe pod <pod-name> 檢查進度。

  4. 修正路由器部署。路由器需要 GPU 容忍度 (因此,當 GPU 節點是唯一具有容量的節點時,便可進行排程) 並增加記憶體限制 (預設的 500 Mi 可能會導致 OOMKill)。

    kubectl patch deployment vllm-deployment-router --type='json' -p='[
      {"op": "add", "path": "/spec/template/spec/tolerations", "value": [{"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}]},
      {"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "512Mi"},
      {"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "1Gi"}
    ]'

    注意:由於 OKE GPU 節點具有可防止非 GPU 工作負載排程的 nvidia.com/gpu=present:NoSchedule 提示符號,因此需要 GPU 允差。由於路由器不使用 GPU,而需要在某個地方執行,因此這項容忍能力可允許在 GPU 節點上進行排程。在具有專用 CPU 節點集區的叢集中,不需要此允差。

  5. 確認已部署 Helm 版本。

    helm list

    顯示已部署 vllm-stack 之 helm 清單的終端機輸出

  6. 驗證 Pod 是否在執行中。

    kubectl get pods

    預期輸出:

    NAME                                            READY   STATUS    RESTARTS   AGE
    vllm-deployment-router-xxxxxxxxxx-xxxxx          1/1     Running   0          5m
    vllm-gpt-oss-deployment-vllm-xxxxxxxxxx-xxxxx    1/1     Running   0          5m
    kubectl get pods

    kubectl 取得 pod,顯示 1/1 執行中的路由器和引擎 Pod

  7. 檢查 Pod 日誌中的模型載入進度。

    kubectl logs -f deployment/vllm-gpt-oss-deployment-vllm

    等到您看到一則訊息,指出模型已載入,且伺服器已準備好接受要求。

工作 10:測試推論端點

驗證部署是否提供推論要求。vLLM Production Stack 會透過路由器服務公開與 OpenAI 相容的 API,因此任何 OpenAI SDK 從屬端或 curl 命令都可以與其互動。

下圖顯示推論要求生命週期:從從屬端要求到路由器的引擎選擇邏輯,到 vLLM 引擎的預先填入和解碼階段,再回溯作為串流回應。

此順序圖表顯示從從屬端到路由器到 vLLM 引擎和 GPU 的推論要求生命週期

  1. 列出可用的模型以確認部署狀況良好。

    kubectl get svc vllm-router-service

    路由器服務為所有部署的模型提供 API 閘道。由於叢集使用專用端點,因此您可以透過 kubectl port-forward 存取服務。

  2. 從本機機器啟動連接埠轉送至路由器服務。開啟新終端機 (將 SSH 通道保持在另一個通道中執行) 並執行:

    kubectl port-forward svc/vllm-router-service 8080:80

    這會將您機器上的 localhost:8080 對應至叢集內路由器服務的連接埠 80。

    安全注意事項:kubectl port-forward 會在本機連結,不會公開服務。透過堡壘主機通道使用專用叢集時,這是測試的最安全方式。

    注意:連接埠轉送命令會在前景執行。測試時保持開啟此終端機 。按 Ctrl+C 即可在完成時停止。

  3. 在另一個終端機中,透過查詢模型端點來驗證模型是否可用。

    curl -s http://localhost:8080/v1/models | python3 -m json.tool

    預期輸出:

    {
        "object": "list",
        "data": [
            {
                "id": "openai/gpt-oss-20b",
                "object": "model",
                "created": 1234567890,
                "owned_by": "vllm"
            }
        ]
    }
  4. 傳送文字完成要求。

    curl -s http://localhost:8080/v1/completions \
      -H "Content-Type: application/json" \
      -d '{
        "model": "openai/gpt-oss-20b",
        "prompt": "Oracle Cloud Infrastructure is",
        "max_tokens": 50
      }' | python3 -m json.tool

    預期的輸出 (縮寫):

    {
        "id": "cmpl-xxxxxxxxxxxx",
        "object": "text_completion",
        "model": "openai/gpt-oss-20b",
        "choices": [
            {
                "index": 0,
                "text": " a cloud computing platform that provides ...",
                "finish_reason": "length"
            }
        ],
        "usage": {
            "prompt_tokens": 5,
            "completion_tokens": 50,
            "total_tokens": 55
        }
    }
  5. 傳送線上交談完成要求。此格式與 OpenAI Python SDK 使用的格式相同,也是以程式設計方式與 LLM 互動的最常見方式。

    curl -s http://localhost:8080/v1/chat/completions \
      -H "Content-Type: application/json" \
      -d '{
        "model": "openai/gpt-oss-20b",
        "messages": [{"role": "user", "content": "What is Oracle Cloud Infrastructure in one sentence?"}],
        "max_tokens": 100
      }' | python3 -m json.tool

    預期的輸出 (縮寫):

    {
        "id": "chatcmpl-xxxxxxxxxxxx",
        "object": "chat.completion",
        "model": "openai/gpt-oss-20b",
        "choices": [
            {
                "index": 0,
                "message": {
                    "role": "assistant",
                    "content": "Oracle Cloud Infrastructure (OCI) is Oracle's enterprise-grade cloud platform that provides a full range of services for building, deploying, and managing applications and workloads..."
                },
                "finish_reason": "length"
            }
        ],
        "usage": {
            "prompt_tokens": 71,
            "completion_tokens": 100,
            "total_tokens": 171
        }
    }

    這兩個回應都包括模型在 choices 陣列中產生的文字、記號使用狀況統計資料,以及 stop (模型自然完成) 或 lengthfinish_reason (輸出在 max_tokens 被截斷)。

    注意: API 要求中的模型名稱是與 Helm 值中 modelURL 欄位相符的完整模型路徑 (openai/gpt-oss-20b)。任何與 OpenAI 相容的從屬端都可以透過將 base_url 設為 http://localhost:8080/v1 來使用此端點。

    顯示 curl 交談完成要求與 openai/gpt-oss-20b 的 JSON 回應的終端機輸出

  6. 評估簡短要求的端對端延遲。

    time curl -s http://localhost:8080/v1/chat/completions \
        -H "Content-Type: application/json" \
        -d '{"model":"openai/gpt-oss-20b","messages":[{"role":"user","content":"What is Kubernetes?"}],"max_tokens":50}' > /dev/null

    在單一 A10 GPU 上,預期短時間內完成了 1-3 秒的端對端。第一個記號 (TTFT) 的時間通常為 50-200ms,視提示長度而定。若要提高傳輸量,請增加 Helm 值中的 replicaCount,以便在路由器後新增更多引擎複本。

工作 11:透過 OCI 負載平衡器公開 (選擇性)

讓推論端點可透過 OCI 負載平衡器從外部存取。

安全注意事項:預設會將推論 API 公用網際網路公開。在不使用 TLS、認證 (API 金鑰 /JWT/mTLS) 和 IP 允許清單或 WAF 控制的情況下,請勿在生產環境中啟用此功能。如果必須公開,請在其前面加上輸入控制器或 API 閘道,以強制執行認證和速率限制,或使用內部負載平衡器。

  1. 將路由器服務修正為使用 LoadBalancer 類型。

    kubectl patch svc vllm-router-service \
      -p '{"spec": {"type": "LoadBalancer"}}'

    注意:如果想要使用內部負載平衡器,請將 OCI 註解新增至服務 (範例如下)。這會將端點專用保留在 VCN 中。

    kubectl annotate svc vllm-router-service \
      "service.beta.kubernetes.io/oci-load-balancer-internal"="true"
  2. 等待指派外部 IP。

    kubectl get svc vllm-router-service -w

    負載平衡器佈建之後預期輸出:

    NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)
    vllm-router-service    LoadBalancer   10.96.x.x      129.xxx.xxx.xx   80:xxxxx/TCP
    kubectl get svc

    顯示路由器和引擎服務的 kubectl 取得 svc 輸出

  3. 測試外部端點。

    curl http://<EXTERNAL-IP>/v1/completions \
      -H "Content-Type: application/json" \
      -d '{
        "model": "openai/gpt-oss-20b",
        "prompt": "Hello from OCI!",
        "max_tokens": 50
      }'

作業 12:設定多重 GPU Tensor 平行機制 (進階)

在裸機資源配置的多個 GPU 上部署大型模型。

Tensor 平行程度可將模型分割為單一節點上的多個 GPU。當模型的記憶體需求超過單一 GPU 時,這是必要的。例如,Meta Llama 3.1 70B 需要大約 140 GB 的 GPU 記憶體,該記憶體超出任何單一 GPU 的容量,但可容納 8x A100 80 GB 或 8x H100 GPU。

  1. 使用 Hugging Face 權杖建立 Kubernetes 加密密碼。限定模型 (例如 Llama 3.1 70B) 需要認證。

    kubectl create secret generic hf-token-secret \
      --from-literal=token=YOUR_HUGGINGFACE_TOKEN
  2. 使用多重 GPU 組態更新 production_stack_specification.yaml

    servingEngineSpec:
      modelSpec:
      - name: "llama70b"
        repository: "vllm/vllm-openai"
        tag: "latest"
        modelURL: "meta-llama/Llama-3.1-70B-Instruct"
    
        replicaCount: 1
        tensorParallelSize: 8
    
        requestCPU: 32
        requestMemory: "256Gi"
        requestGPU: 8
    
        hf_token:
          secretName: "hf-token-secret"
          secretKey: "token"
    
        pvcStorage: "500Gi"
        pvcAccessMode:
          - ReadWriteOnce
        storageClass: "oci-block-storage-enc"
    
        nodeSelector:
          node.kubernetes.io/instance-type: "BM.GPU.H100.8"
        tolerations:
          - key: "nvidia.com/gpu"
            operator: "Exists"
            effect: "NoSchedule"
    
        extraArgs:
          - "--max-model-len=8192"
          - "--gpu-memory-utilization=0.95"
          - "--tensor-parallel-size=8"
  3. 使用更新的值進行部署。如同工作 9,請使用 --wait。路由器將 CrashLoop 直到修正為止。

    helm upgrade -i \
        vllm vllm/vllm-stack \
        -f production_stack_specification.yaml
    kubectl wait --for=condition=Ready pods -l model=llama70b --timeout=900s

    然後修補路由器 (和任務 9 相同,步驟 4) 並驗證:

    kubectl patch deployment vllm-deployment-router --type='json' -p='[
      {"op": "add", "path": "/spec/template/spec/tolerations", "value": [{"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}]},
      {"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "512Mi"},
      {"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "1Gi"}
    ]'
  4. 確定 Pod 正在執行,而且所有 GPU 都在使用中。

    kubectl get pods
    kubectl logs -f deployment/vllm-llama70b-deployment-vllm

注意:請先確定 OKE 叢集具有具有適當裸機 GPU 資源配置的節點集區 (例如 BM.GPU.H100.8),再部署多重 GPU 組態。

工作 13:對模型使用 OCI 物件儲存 (進階)

從 OCI Object Storage 載入模型權重,而不是從 Hugging Face 下載。這對於專用模型、OCI 內的下載速度較快,或沒有外部網際網路存取的環境非常有用。

  1. 將您的模型加權上傳至 OCI Object Storage 貯體。瀏覽至 OCI 主控台中的儲存體 > 物件儲存體,如果還沒有儲存桶,請建立儲存桶。

  2. 為您的儲存桶建立預先認證要求 (PAR) URL。在 OCI 主控台中,選取您的儲存桶,按一下預先認證的要求,然後建立具有讀取存取權的新要求。

  3. 更新 production_stack_specification.yaml 以使用 PAR URL。

    servingEngineSpec:
      modelSpec:
      - name: "custom-model"
        repository: "iad.ocir.io/YOUR_TENANCY/vllm-custom:latest"
        modelURL: "/models/custom-model"
    
        env:
          - name: BUCKET_PAR_URL
            value: "https://objectstorage.us-ashburn-1.oraclecloud.com/p/xxx/n/namespace/b/bucket/o"
          - name: MODEL_NAME
            value: "custom-model"
  4. 使用更新的值進行部署 (不含 --wait)。請參閱「任務 9」以瞭解原因。

    helm upgrade -i \
        vllm vllm/vllm-stack \
        -f production_stack_specification.yaml
    kubectl wait --for=condition=Ready pods -l model=custom-model --timeout=600s
  5. 檢查 Pod 日誌,以驗證物件儲存的模型負載。

    kubectl logs -f deployment/vllm-custom-model-deployment-vllm

作業 14:清除資源

移除所有已部署的資源,以避免持續收費。

  1. 使用清除命令檔移除 Kubernetes 資源。

    cd production-stack/deployment_on_cloud/oci
    ./clean_up.sh

    這會解除安裝 Helm 版本、刪除所有 PersistentVolumeClaims、PersistentVolumes 和自訂 vLLM 資源。

  2. 刪除 OKE 叢集和所有 OCI 網路資源。

    ./entry_point.sh cleanup

    這會依下列順序刪除下列資源:

    • GPU 節點集區
    • OKE 叢集
    • 堡壘主機 (如果已建立)
    • 子網路 (API、工作節點、負載平衡器、堡壘主機)
    • 安全清單
    • 服務閘道、NAT 閘道及網際網路閘道
    • 路由表
    • VCN
  3. 確認所有資源都已在 OCI 主控台的「開發人員服務 > Kubernetes 叢集」和「網路 > 虛擬雲端網路」底下移除。

    ./entry_point.sh cleanup

    entry_point.sh 清除的終端機輸出,顯示完整資源卸除

注意:請確定已刪除所有資源,以避免持續收費。即使閒置,GPU 執行個體和區塊磁碟區也會產生成本。

新功能

此教學課程已部署功能推論堆疊。對於生產環境工作負載,請考慮下列增強功能:

確認

其他學習資源

docs.oracle.com/learn 上探索其他實驗室,或在 Oracle Learning YouTube 頻道上存取更多免費學習內容。此外,請造訪 education.oracle.com/learning-explorer 以成為 Oracle Learning Explorer。

如需產品文件,請造訪 Oracle Help Center