In diesem Dokument finden Sie eine detaillierte Anleitung zum Bereitstellen von Open-Weight-Modellen in GDC-Umgebungen (Google Distributed Cloud) mit Air Gap. Es wird beschrieben, wie vLLM für die Bereitstellung mit hohem Durchsatz mithilfe der Funktionen der GDC-Plattform verwendet wird, einschließlich Kubernetes-, Harbor- und Beschleunigerressourcen (GPU).
Architektur
Die Lösung umfasst die Bereitstellung von vLLM als containerisiertes Inferenz-Serving-Backend (Deployment) in einem Standardcluster. Modellgewichtungen werden auf Persistent Volumes gespeichert, deren Größe dynamisch aus dem heruntergeladenen Modellverzeichnis mit 25% Overhead angepasst wird. Sie werden mit der Speicher-QoS-Richtlinie system-performance-<org>-admin (30 IOPS pro GiB mit einem Mindestwert von 6.148 IOPS oder performance-rwo, wenn leistungsstarker Blockspeicher aktiviert ist) konfiguriert und über einen Modell-Loader-Workflow befüllt. Kubernetes-Services vom Typ LoadBalancer stellen die Backend-API bereit und NetworkPolicy- / ProjectNetworkPolicy-Objekte sichern den Zugriff darauf.

Hinweis
Bevor Sie mit der Bereitstellung fortfahren, müssen Sie sicherstellen, dass Ihre Umgebung alle erforderlichen Voraussetzungen erfüllt und die erforderlichen Befehlszeilen-Dienstprogramme richtig konfiguriert sind. Die Einrichtung dieser Tools auf Ihrer Workstation ist für die Verwaltung von Container-Registries, die Interaktion mit Clustern und die Automatisierung des Bereitstellungsprozesses unerlässlich.
- Standardcluster mit ausreichenden Ressourcen (CPU, Arbeitsspeicher, Beschleuniger) erstellt. Die Mindestanforderungen an Beschleuniger variieren je nach Modell.
- Harbor-Instanz verfügbar und zugänglich.
- Erforderliche IAM-Berechtigungen angewendet.
- Workstation mit der erforderlichen Verbindung zur Umgebung und zum Internet
- Hugging Face-Token und Nutzungsbedingungen akzeptiert, wenn Gated-Modelle verwendet werden.
Umgebungskonfiguration
Identity and Access Management
Prüfen Sie, ob die erforderlichen IAM-Konten, -Rollen und -Berechtigungen richtig konfiguriert sind.
GDC-Nutzerrollen für die Organisation:
- Optional: Standard-Cluster-Administrator (Cluster über die Console ansehen)
GDC-Nutzer-Rollen für das Projekt (RoleBinding im Projekt-Namespace, zugewiesen von einem Projekt-IAM-Administrator):
- Harbor-Instanzbetrachter (
harbor-instance-viewer) - Harbor-Projektersteller (
harbor-project-creator, nur wenn das Harbor-Projekt noch nicht vorhanden ist) - Standard-Cluster-Administrator (
standard-cluster-admin, erforderlich fürgdcloud clusters get-credentials)
GDC-Nutzerrolle im Standardcluster: Die vorherigen Projektrollen gewähren keine Berechtigungen innerhalb des Clusters. Ein Projekt-IAM-Administrator muss den Nutzer zusätzlich mit einer StandardClusterRoleBinding im Projekt-Namespace auf dem Verwaltungs-API-Server an die StandardClusterRole cluster-admin binden. Die Bindung wird innerhalb von Sekunden an die Standardcluster des Projekts weitergegeben (status.clusters[].conditions zeigt Propagated=True).
cat <<EOF | kubectl apply --kubeconfig MANAGEMENT_API_SERVER -f -
apiVersion: iam.gdc.goog/v1
kind: StandardClusterRoleBinding
metadata:
name: BINDING_NAME
namespace: PROJECT
spec:
roleRef:
apiGroup: iam.gdc.goog
kind: StandardClusterRole
name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: USER
EOF
Ersetzen Sie Folgendes:
MANAGEMENT_API_SERVER: der Pfad zur kubeconfig-Datei des Management-API-Servers.BINDING_NAME: ein Name für die Bindung, z. B.alex-cluster-admin(Kleinbuchstaben, Ziffern und Bindestriche).PROJECT: der Name des Projekts.USER: der zu bindende Nutzername, genau wie er von Ihrem Identitätsanbieter gemeldet wird (z. B. eine E-Mail-Adresse).
Unterstützung durch den Infrastrukturbetreiber (nur wenn performance-rwo im Standardcluster nicht verfügbar ist): In den Nutzerhandbüchern wird das Modell PersistentVolumeClaim mit der Standard-StorageClass standard-rwo erstellt, wodurch die Modellvolumes mit der richtigen Größe auf 3 IOPS pro GiB begrenzt werden. Bitten Sie Ihren Infrastrukturbetreiber, die QoS-Richtlinie system-performance-<ORG>-admin auf das Volume anzuwenden, das das Modell PersistentVolumeClaim unterstützt, damit es die Mindestanzahl von 6.148 IOPS erhält. In den Nutzerhandbüchern werden die Werte ausgegeben, die der Infrastrukturbetreiber benötigt.
Berechtigungen des Harbor-Robot-Kontos für crane:
- Repository auflisten
- Pull-Repository
- Repository per Push übertragen
- Artefakt lesen
- Artefakt auflisten
- Tag erstellen
- Listen-Tag
Berechtigungen des Harbor-Robot-Kontos für Kubernetes-Image-Abrufe:
- Repository auflisten
- Pull-Repository
- Artefakt lesen
- Artefakt auflisten
- Listen-Tag
Workstation
Führen Sie alle Befehle in diesem Leitfaden auf einer Workstation mit den erforderlichen Tools und der erforderlichen Verbindung zur Umgebung und zum Internet aus, sofern in einem Schritt nichts anderes angegeben ist.
Voraussetzungen
Die folgenden Tools müssen auf der Workstation installiert sein:
crane: Container-Images zwischen Registries verwalten und kopieren (Dokumentation).docker: Erstellt das gepatchte vLLM-Container-Image und überträgt es per Push.gdcloud: Befehlszeile (CLI) zum Verwalten von GDC-Ressourcen (Dokumentation).hf: Befehlszeile (CLI) für den Zugriff auf Hugging Face.kubectl: Befehlszeile (CLI) für die Kommunikation mit einem Kubernetes-Cluster und dessen Verwaltung.jq: Ein einfacher und flexibler JSON-Prozessor für die Befehlszeile.yq: portabler YAML-Prozessor für die Befehlszeile.
Workstationkonfiguration
Die folgenden Informationen zur Umgebung sind für die Workstation-Konfiguration erforderlich:
GDC_STANDARD_CLUSTER_NAME: Der Name des GDC-Standardclusters.GDC_DOMAIN_SUFFIX: Das Domainsuffix für die GDC-Umgebung (z. B.gdc.example.com).GDC_ORG: Der Name der GDC-Organisation.GDC_PROJECT: Der Name des GDC-Projekts.GDC_ZONE: Der Name der GDC-Bereitstellungszone.GDC_HARBOR_INSTANCE_NAME: Der Name der Harbor-Instanz im Projekt.GDCS_HARBOR_PROJECT_NAME: Der Name des Harbor-Projekts, das für Images verwendet werden soll (Standard:solutions).GDCS_HARBOR_CRANE_ROBOT_NAME: Der Name des Harbor-crane-Roboterkontos.GDCS_HARBOR_CRANE_ROBOT_TOKEN: Das Authentifizierungstoken für das Harbor-crane-Roboterkonto.GDCS_HARBOR_K8S_ROBOT_NAME: Der Name des Harbor-Kubernetes-Image-Pull-Roboterkontos.GDCS_HARBOR_K8S_ROBOT_TOKEN: Das Authentifizierungstoken für das Harbor-Kubernetes-Image-Pull-Roboterkonto.
Nachdem Sie die Werte für alle erforderlichen Variablen erfasst haben, fahren Sie mit dem Erstellen der Datei mit den Umgebungsvariablen fort. Nachdem die Datei erstellt wurde, können Sie sie jederzeit manuell bearbeiten.
Erstellen Sie die Stammverzeichnisse der Lösung und den Ordner für Secrets:
mkdir -p ${HOME}/gdcag-solutions/env.d mkdir -p ${HOME}/gdcag-solutions/secrets touch ${HOME}/gdcag-solutions/secrets/harbor_crane_robot_token touch ${HOME}/gdcag-solutions/secrets/harbor_k8s_robot_token chmod u=rwx,go= ${HOME}/gdcag-solutions/secrets chmod -R u=rw,go= ${HOME}/gdcag-solutions/secrets/*Erstellen Sie die Konfigurationsdatei für die Plattformumgebung:
cat << 'EOF' > ${HOME}/gdcag-solutions/env.d/platform.sh && echo "Successfully created." || echo "Failed to create!" # Infrastructure (Platform Native) export GDC_STANDARD_CLUSTER_NAME="STANDARD_CLUSTER_NAME" export GDC_DOMAIN_SUFFIX="DOMAIN_SUFFIX" export GDC_ORG="ORG" export GDC_PROJECT="PROJECT" export GDC_ZONE="ZONE" export GDC_HARBOR_INSTANCE_NAME="HARBOR_INSTANCE_NAME" # Derived platform values export GDC_ZONAL_HOSTNAME="${GDC_ORG}.${GDC_ZONE}.${GDC_DOMAIN_SUFFIX}" export GDC_ZONAL_CONSOLE_URL="https://console.${GDC_ZONAL_HOSTNAME}" export GDC_HARBOR_HOST="${GDC_HARBOR_INSTANCE_NAME}-${GDC_PROJECT}.${GDC_ORG}.${GDC_ZONE}.${GDC_DOMAIN_SUFFIX}" EOFErsetzen Sie Folgendes:
STANDARD_CLUSTER_NAME: Name des GDC-Standardclusters.DOMAIN_SUFFIX: GDC-Domainsuffix.ORG: GDC-Organisation.PROJECT: GDC-Projekt.ZONE: GDC-Zone.HARBOR_INSTANCE_NAME: Name der GDC Harbor-Instanz.
Erstellen Sie die Konfigurationsdatei für die Registry-Umgebung:
cat << 'EOF' > ${HOME}/gdcag-solutions/env.d/registry.sh && echo "Successfully created." || echo "Failed to create!" # GDC Solutions Registry & Secrets export GDCS_HARBOR_PROJECT_NAME="solutions" export GDCS_HARBOR_CRANE_ROBOT_NAME='HARBOR_CRANE_ROBOT_NAME' export GDCS_HARBOR_CRANE_ROBOT_TOKEN="$(cat ${GDCS_ROOT_HOME}/secrets/harbor_crane_robot_token)" export GDCS_HARBOR_K8S_ROBOT_NAME='HARBOR_K8S_ROBOT_NAME' export GDCS_HARBOR_K8S_ROBOT_TOKEN="$(cat ${GDCS_ROOT_HOME}/secrets/harbor_k8s_robot_token)" export GDCS_HARBOR_K8S_PULL_SECRET="gdcs-image-pull-secret" # Derived registry values export GDCS_HARBOR_PROJECT_URI="${GDC_HARBOR_HOST}/${GDCS_HARBOR_PROJECT_NAME}" export GDCS_HARBOR_CHART_OCI_URI="oci://${GDCS_HARBOR_PROJECT_URI}" EOFErsetzen Sie Folgendes:
HARBOR_CRANE_ROBOT_NAME: der vollständige Name des Harbor-Roboterkontos fürcrane, wie er von Harbor angezeigt wird, einschließlich des Präfixesrobot$. Behalten Sie die einfachen Anführungszeichen bei, damit die Shell$nicht erweitert.HARBOR_K8S_ROBOT_NAME: der vollständige Name des Harbor-Roboterkontos für Kubernetes-Image-Abrufe, wie er von Harbor angezeigt wird, einschließlich des Präfixesrobot$. Behalten Sie die einfachen Anführungszeichen bei, damit die Shell$nicht erweitert.
Fügen Sie Ihre Tokens den Secret-Dateien hinzu:
set +o history echo "CRANE_ROBOT_TOKEN" > ${HOME}/gdcag-solutions/secrets/harbor_crane_robot_token echo "KUBERNETES_ROBOT_TOKEN" > ${HOME}/gdcag-solutions/secrets/harbor_k8s_robot_token set -o historyErsetzen Sie Folgendes:
CRANE_ROBOT_TOKEN: das Secret descrane-Roboterkontos.KUBERNETES_ROBOT_TOKEN: das Secret des Kubernetes-Image-Pull-Roboterkontos.
Erstellen Sie die Loader-Datei für die Root-Umgebung:
cat << 'EOF' > ${HOME}/gdcag-solutions/env.sh && echo "Successfully created." || echo "Failed to create!" export GDCS_ROOT_HOME="${HOME}/gdcag-solutions" echo "GDCS_ROOT_HOME=${GDCS_ROOT_HOME}" # Sourced in dependency order source "${GDCS_ROOT_HOME}/env.d/platform.sh" source "${GDCS_ROOT_HOME}/env.d/registry.sh" EOF
Lösungsvariablen konfigurieren
Erstellen Sie das Verzeichnis für die Lösungsimplementierung und den Ordner für Secrets:
mkdir -p ${HOME}/gdcag-solutions/open-weight-models/vllm/env.d mkdir -p ${HOME}/gdcag-solutions/open-weight-models/vllm/secrets touch ${HOME}/gdcag-solutions/open-weight-models/vllm/secrets/huggingface_token chmod u=rwx,go= ${HOME}/gdcag-solutions/open-weight-models/vllm/secrets chmod -R u=rw,go= ${HOME}/gdcag-solutions/open-weight-models/vllm/secrets/*Fügen Sie Ihr Hugging Face-Token zur Secret-Datei hinzu:
set +o history echo "HUGGINGFACE_TOKEN" > ${HOME}/gdcag-solutions/open-weight-models/vllm/secrets/huggingface_token set -o historyErsetzen Sie Folgendes:
HUGGINGFACE_TOKEN: Ihr Hugging Face-Zugriffstoken.
Erstellen Sie die Konfigurationsdatei für den Beschleuniger:
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/vllm/env.d/accelerator.sh && echo "Successfully created." || echo "Failed to create!" # Workload Accelerator (NVIDIA GPU) export GDCS_WORKLOAD_ACCELERATOR_TYPE="WORKLOAD_ACCELERATOR_TYPE" export GDCS_WORKLOAD_ACCELERATOR_COUNT="1" EOFErsetzen Sie Folgendes:
WORKLOAD_ACCELERATOR_TYPE: der Name der GPU-Ressource, die von Ihren GPU-Knoten bereitgestellt wird, z. B.nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE. In den Nutzerhandbüchern sind die Ressourcennamen aller GPUs aufgeführt, die GDC mit Air Gap bietet. Nachdem Sie die Cluster-Anmeldedaten abgerufen haben, können Sie die Namen in Ihrem Cluster mitkubectl get nodes --output=json | jq -r '[.items[].status.allocatable | keys[] | select(startswith("nvidia.com/"))] | unique[]'auflisten.
Erstellen Sie die Konfigurationsdatei für die Lösungsumgebung:
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/vllm/env.d/vllm.sh && echo "Successfully created." || echo "Failed to create!" # Hugging Face export HF_TOKEN="$(cat ${GDCS_IMPLEMENTATION_HOME}/secrets/huggingface_token 2>/dev/null || echo '')" # Images export GDCS_BUSYBOX_IMAGE_TAG="1.38.0" export GDCS_VLLM_IMAGE_TAG="v0.26.0" # Docker configuration directories for crane and Kubernetes export GDCS_HARBOR_CRANE_DOCKER_CONFIG="${GDCS_IMPLEMENTATION_HOME}/docker/crane" export GDCS_HARBOR_K8S_DOCKER_CONFIG="${GDCS_IMPLEMENTATION_HOME}/docker/k8s" EOFErstellen Sie die Loader-Datei für die Implementierungsumgebung:
cat << 'EOF' > ${HOME}/gdcag-solutions/open-weight-models/vllm/env.sh && echo "Successfully created." || echo "Failed to create!" source "${HOME}/gdcag-solutions/env.sh" export GDCS_IMPLEMENTATION_HOME="${HOME}/gdcag-solutions/open-weight-models/vllm" echo "GDCS_IMPLEMENTATION_HOME=${GDCS_IMPLEMENTATION_HOME}" # Sourced in dependency order source "${GDCS_IMPLEMENTATION_HOME}/env.d/accelerator.sh" source "${GDCS_IMPLEMENTATION_HOME}/env.d/vllm.sh" EOFBearbeiten und prüfen Sie die Umgebungsdateien mit Ihrem bevorzugten Editor:
${EDITOR:-vi} ${HOME}/gdcag-solutions/env.d/platform.sh ${EDITOR:-vi} ${HOME}/gdcag-solutions/env.d/registry.sh ${EDITOR:-vi} ${HOME}/gdcag-solutions/open-weight-models/vllm/env.d/accelerator.sh ${EDITOR:-vi} ${HOME}/gdcag-solutions/open-weight-models/vllm/env.d/vllm.shRufen Sie die Umgebungsdatei auf:
source ${HOME}/gdcag-solutions/open-weight-models/vllm/env.shDie Ausgabe sieht etwa so aus:
GDCS_ROOT_HOME=HOME_DIRECTORY_PATH/gdcag-solutions GDCS_IMPLEMENTATION_HOME=HOME_DIRECTORY_PATH/gdcag-solutions/open-weight-models/vllm
GDC
In diesem Leitfaden wird davon ausgegangen, dass Ihre Workstation so konfiguriert ist, dass sie den TLS-Zertifikaten für Ihre GDC-Umgebung und Harbor-Instanz vertraut.
Konfigurieren Sie
gdcloud:gdcloud config set core/account "default-user" gdcloud config set core/organization_console_url "${GDC_ZONAL_CONSOLE_URL}" gdcloud config set core/project "${GDC_PROJECT}" gdcloud config set core/zone "${GDC_ZONE}"Authentifizieren Sie sich bei der GDC-Umgebung:
gdcloud auth login
Cluster
Clusteranmeldedaten abrufen:
gdcloud clusters get-credentials "${GDC_STANDARD_CLUSTER_NAME}" \ --project="${GDC_PROJECT}" \ --standard \ --zone="${GDC_ZONE}"Prüfen Sie die Verbindung zum Cluster:
kubectl get nodes -L node.cluster.private.gdc.goog/machine-class
Vorbereitung der Artefaktmigration
- Prüfen Sie die Verbindung und Konfiguration für die Artifact Registry.
Erstellen Sie eine Docker-Konfigurationsdatei für Crane. Ein Roboterkonto wird verwendet, um große Image-Ebenen zu übertragen, um Zeitüberschreitungen bei Authentifizierungstokens zu vermeiden, wenn der Managed Harbor Service (MHS)-Anmeldedaten-Helper (docker-credential-mhs) mit einem Nutzerkonto verwendet wird:
export DOCKER_CONFIG="${GDCS_HARBOR_CRANE_DOCKER_CONFIG}" crane auth login "${GDC_HARBOR_HOST}" \ --password="${GDCS_HARBOR_CRANE_ROBOT_TOKEN}" \ --username="${GDCS_HARBOR_CRANE_ROBOT_NAME}"Erstellen Sie eine Docker-Konfigurationsdatei für Kubernetes:
export DOCKER_CONFIG="${GDCS_HARBOR_K8S_DOCKER_CONFIG}" crane auth login "${GDC_HARBOR_HOST}" \ --password="${GDCS_HARBOR_K8S_ROBOT_TOKEN}" \ --username="${GDCS_HARBOR_K8S_ROBOT_NAME}"Erstellen Sie das Skript
seed_registry.sh:cat << 'EOF' > ${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh && echo "Successfully created." || echo "Failed to create!" #!/bin/bash # seed_registry.sh: Modular artifact migration for GDC Solutions # Requires the env.sh file to be sourced first. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" set -o nounset source "${SCRIPT_DIR}/env.sh" # Set the Docker config export DOCKER_CONFIG="${GDCS_HARBOR_CRANE_DOCKER_CONFIG}" eval "${SERIALIZED_IMAGES}" # Ensure GDCS_REGISTRY_IMAGES is set if [[ ${#GDCS_REGISTRY_IMAGES[@]} -eq 0 ]]; then echo "GDCS_REGISTRY_IMAGES must be set, exiting..." exit 1 fi # Migrate the images for source_image in "${GDCS_REGISTRY_IMAGES[@]}"; do # Strip the registry host only when the first path segment is a host (contains a dot or a port) first_segment="${source_image%%/*}" if [[ "${first_segment}" == *.* || "${first_segment}" == *:* ]]; then image_path="${source_image#*/}" else image_path="${source_image}" fi destination_image="${GDCS_HARBOR_PROJECT_URI}/${image_path}" # Ensure the folder structure is created crane append \ --new_layer=<(tar czf - -T /dev/null) \ --new_tag="${destination_image%:*}:create" \ --oci-empty-base 2> /dev/null || true # Copy the linux/amd64 platform only to avoid transferring multi-arch layers over air-gapped links crane copy --platform linux/amd64 "${source_image}" "${destination_image}" || { echo "Failed to copy ${source_image} to ${destination_image}"; exit 1; } done echo "Migration complete: Images are available at ${GDCS_HARBOR_PROJECT_URI}" EOF chmod u+x "${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh"Definieren Sie die Liste der erforderlichen Container-Images für die Lösung:
declare -a GDCS_REGISTRY_IMAGES=( "docker.io/library/busybox:${GDCS_BUSYBOX_IMAGE_TAG}" "docker.io/vllm/vllm-openai:${GDCS_VLLM_IMAGE_TAG}" ) export SERIALIZED_IMAGES=$(declare -p GDCS_REGISTRY_IMAGES)Übertragen Sie die erforderlichen Container-Images per Seed in die Artifact Registry:
${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh
Erstellen Sie ein Dockerfile, um den vLLM-Container zu patchen, bis die Upstream-Änderungen implementiert sind:
cat <<EOF > ${GDCS_IMPLEMENTATION_HOME}/docker/vllm-${GDCS_VLLM_IMAGE_TAG}-patch.dockerfile && echo "Successfully created." || echo "Failed to create!" FROM docker.io/vllm/vllm-openai:${GDCS_VLLM_IMAGE_TAG} RUN pip install --upgrade "opencv-python-headless<=4.12.0.88" EOFErstellen Sie das gepatchte Image und übertragen Sie es per Push:
context_dir="${GDCS_IMPLEMENTATION_HOME}/docker/empty-context" image="${GDCS_HARBOR_PROJECT_URI}/vllm/vllm-openai:${GDCS_VLLM_IMAGE_TAG}-fips" mkdir -p "${context_dir}" rm -rf "${context_dir}"/* export DOCKER_CONFIG="${GDCS_HARBOR_CRANE_DOCKER_CONFIG}" docker build \ --file="${GDCS_IMPLEMENTATION_HOME}/docker/vllm-${GDCS_VLLM_IMAGE_TAG}-patch.dockerfile" \ --tag="${image}" \ "${context_dir}" docker push "${image}"Erstellen Sie das Skript
model_weight_upload.sh. Dabei wird ein lokales Modellverzeichnis über den Modell-LoaderPod(GNU-tar-Streaming, einschließlich Dateien, die größer als 8 GiB sind) inPersistentVolumekopiert. Dateien, die bereits mit derselben Größe vorhanden sind, werden übersprungen:cat << 'EOF' > ${GDCS_IMPLEMENTATION_HOME}/model_weight_upload.sh && echo "Successfully created." || echo "Failed to create!" #!/bin/bash # model_weight_upload.sh: copy LOCAL_MODEL_DIR into REMOTE_MODEL_DIR on the PersistentVolume mounted in the loader Pod. # Requires: GDCS_WORKLOAD_NAMESPACE, GDCS_MODEL_LOADER_POD_NAME, LOCAL_MODEL_DIR, REMOTE_MODEL_DIR # Optional: REMOTE_MOUNT_DIR (PersistentVolume mount path, defaults to REMOTE_MODEL_DIR) - its lost+found directory is removed. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" set -o errexit set -o nounset set -o pipefail # Ensure LOCAL_MODEL_DIR is set if [[ -z "${LOCAL_MODEL_DIR:-}" ]]; then echo "LOCAL_MODEL_DIR must be set, exiting..." exit 1 fi # Ensure REMOTE_MODEL_DIR is set if [[ -z "${REMOTE_MODEL_DIR:-}" ]]; then echo "REMOTE_MODEL_DIR must be set, exiting..." exit 1 fi REMOTE_MOUNT_DIR="${REMOTE_MOUNT_DIR:-${REMOTE_MODEL_DIR}}" KEEPALIVE_INTERVAL_SEC=300 start_session_keepalive() { echo "[KeepAlive] Started background heartbeat (every ${KEEPALIVE_INTERVAL_SEC}s)." while true; do sleep "${KEEPALIVE_INTERVAL_SEC}" # Lightweight API request to prevent session inactivity logout if kubectl get namespace "${GDCS_WORKLOAD_NAMESPACE}" >/dev/null 2>&1; then echo "[KeepAlive] Session heartbeat sent at $(date)" else echo "[KeepAlive] Heartbeat failed or context expired at $(date)" fi done } start_session_keepalive & MY_KEEPALIVE_PID=$! cleanup() { echo "[Cleanup] Stopping keep-alive process (PID: ${MY_KEEPALIVE_PID})..." kill "${MY_KEEPALIVE_PID}" 2>/dev/null || true wait "${MY_KEEPALIVE_PID}" 2>/dev/null || true echo "[Cleanup] Session keep-alive terminated." } trap cleanup EXIT INT TERM # Remove the lost+found directory kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- rm -rf "${REMOTE_MOUNT_DIR}/lost+found" # Create the model directory kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- mkdir -p "${REMOTE_MODEL_DIR}" # Loop through all files in the local directory FAILED_FILES=() MAX_UPLOAD_ATTEMPTS="${MAX_UPLOAD_ATTEMPTS:-3}" remote_size_of() { # Prints the remote file size (0 if the file does not exist) kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" --namespace="${GDCS_WORKLOAD_NAMESPACE}" -- stat -c%s "$1" 2>/dev/null || echo 0 } # Loop through all files in the local directory (process substitution keeps FAILED_FILES in this shell) while read -r local_file; do # Get relative path rel_path="${local_file#"${LOCAL_MODEL_DIR}"/}" remote_file="${REMOTE_MODEL_DIR}/${rel_path}" # Get local file size local_size=$(stat -L -c%s "${local_file}") # Get remote file size (returns 0 if file does not exist) remote_size=$(remote_size_of "${remote_file}") if [ "${local_size}" -eq "${remote_size}" ]; then echo "[Loader] Skipping: ${rel_path} (Size matches)" continue fi if [ "${remote_size}" -ne "0" ]; then echo "[Loader] Mismatch: ${rel_path} (${remote_size} remote vs ${local_size} local)" fi # Ensure remote parent subdirectories exist if your model has folders remote_parent=$(dirname "${remote_file}") kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- mkdir -p "${remote_parent}" attempt=1 while [ "${attempt}" -le "${MAX_UPLOAD_ATTEMPTS}" ]; do echo "[Loader] Uploading: ${rel_path} (${local_size} bytes, attempt ${attempt}/${MAX_UPLOAD_ATTEMPTS})..." # Copy the individual file using GNU tar streaming. # Note: 'kubectl cp' uses Go's archive/tar, which defaults to PAX format for files > 8 GiB. # BusyBox's tar in the loader pod does not parse PAX size headers and fails with 'tar: invalid tar magic'. # Streaming via GNU tar (--format=gnu) uses base-256 binary encoding for files > 8 GiB, which BusyBox tar supports. tar --format=gnu -C "$(dirname "${local_file}")" -cf - "$(basename "${local_file}")" | \ kubectl exec -i "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- tar -xf - -C "${remote_parent}" || true # Verify: an interrupted exec stream leaves a truncated file behind without an error remote_size=$(remote_size_of "${remote_file}") if [ "${local_size}" -eq "${remote_size}" ]; then echo "[Loader] Verified: ${rel_path} (${remote_size} bytes)" break fi echo "[Loader] Incomplete upload: ${rel_path} (${remote_size} remote vs ${local_size} local), retrying..." attempt=$((attempt + 1)) done if [ "${local_size}" -ne "${remote_size}" ]; then FAILED_FILES+=("${rel_path}") fi done < <(find -L "${LOCAL_MODEL_DIR}" -type f) if [ "${#FAILED_FILES[@]}" -ne 0 ]; then echo "[Loader] FAILED: ${#FAILED_FILES[@]} file(s) could not be uploaded completely after ${MAX_UPLOAD_ATTEMPTS} attempts:" printf '[Loader] %s\n' "${FAILED_FILES[@]}" exit 1 fi echo "[Loader] Completed" # Fix file permissions kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- chown 0:0 -R "${REMOTE_MODEL_DIR}" # Flush the volume before the loader Pod is deleted (a detached volume with unwritten page cache loses data) kubectl exec "pod/${GDCS_MODEL_LOADER_POD_NAME}" \ --namespace="${GDCS_WORKLOAD_NAMESPACE}" \ -- sync EOF chmod u+x "${GDCS_IMPLEMENTATION_HOME}/model_weight_upload.sh"
Modell bereitstellen
- Nutzerhandbuch für Gemma-Modelle mit vLLM: Gemma 4 E2B, E4B, 12B, 26B-A4B und 31B mit den GPU-Optionen von GDC mit Air Gap
- Nutzerhandbuch für Mistral-Modelle mit vLLM: Mistral 7B Instruct, Mistral NeMo 12B, Mistral Small 3.2 24B, Ministral 8B, Mixtral 8x7B und Magistral Small 1.2 mit den GPU-Optionen von GDC mit Air Gap