From 76bf522f655c54868d2167a02474bf9944e02397 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Thu, 8 May 2025 16:59:48 +0000 Subject: [PATCH 01/44] [Feat] Added kuberay installation script via helm. Initial commit. Signed-off-by: insukim1994 --- utils/install-kuberay.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100755 utils/install-kuberay.sh diff --git a/utils/install-kuberay.sh b/utils/install-kuberay.sh new file mode 100755 index 000000000..8498c9f0d --- /dev/null +++ b/utils/install-kuberay.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# original kuberay installation reference: https://github.com/ray-project/kuberay?tab=readme-ov-file#helm-charts + +# Add the Helm repo +helm repo add kuberay https://ray-project.github.io/kuberay-helm/ +helm repo update + +# Confirm the repo exists +helm search repo kuberay --devel + +# Install both CRDs and KubeRay operator v1.1.0. +helm install kuberay-operator kuberay/kuberay-operator --version 1.1.0 + +# Check the KubeRay operator Pod in `default` namespace +kubectl get pods +# NAME READY STATUS RESTARTS AGE +# kuberay-operator-78cb88fb88-gbw7w 0/1 Running 0 5s From 3a0a1ce3af5920195b29bb33b32d75f817c33a53 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sat, 10 May 2025 15:24:04 +0000 Subject: [PATCH 02/44] Added initial helm chart template file for ray cluster creation. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 248 ++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 helm/templates/ray-cluster.yaml diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml new file mode 100644 index 000000000..24d07b4b4 --- /dev/null +++ b/helm/templates/ray-cluster.yaml @@ -0,0 +1,248 @@ +{{- if .Values.servingEngineSpec.enableEngine -}} +{{- range $modelSpec := .Values.servingEngineSpec.modelSpec }} +{{- with $ -}} +{{- if .Values.servingEngineSpec.enableKubeRay }} +apiVersion: ray.io/v1 +kind: RayCluster +metadata: + name: "{{ .Release.Name }}-{{$modelSpec.name}}-raycluster-vllm" + namespace: {{ .Release.Namespace }} + labels: + model: {{ $modelSpec.name }} + helm-release-name: {{ .Release.Name }} + {{- include "chart.engineLabels" . | nindent 4 }} +spec: + headGroupSpec: + serviceType: ClusterIP + rayStartParams: + dashboard-host: "0.0.0.0" + template: + spec: + terminationGracePeriodSeconds: 0 + {{- if .Values.servingEngineSpec.securityContext }} + securityContext: + {{- toYaml .Values.servingEngineSpec.securityContext | nindent 8 }} + {{- end }} + containers: + - name: vllm-ray-head + env: + - name: HF_HOME + value: "{{ .Values.servingEngineSpec.modelMountPath }}" + image: "{{ required "Required value 'modelSpec.repository' must be defined !" $modelSpec.repository }}:{{ required "Required value 'modelSpec.tag' must be defined !" $modelSpec.tag }}" + livenessProbe: + exec: + command: ["bash", "-c", "true"] + readinessProbe: + exec: + command: ["bash", "-c", "true"] + volumeMounts: + - mountPath: {{ .Values.servingEngineSpec.modelMountPath | quote }} + name: models + volumes: + - name: models + persistentVolumeClaim: + claimName: {{ .Values.servingEngineSpec.pvcName | quote }} + workerGroupSpecs: + - rayStartParams: {} + replicas: {{ $modelSpec.replicaCount }} + groupName: vllm-ray-worker + template: + {{- if .Values.servingEngineSpec.securityContext }} + securityContext: + {{- toYaml .Values.servingEngineSpec.securityContext | nindent 8 }} + {{- end }} + spec: + containers: + - name: vllm-ray-worker + volumeMounts: + - mountPath: {{ .Values.servingEngineSpec.modelMountPath | quote }} + name: models + env: + - name: HF_HOME + {{- if hasKey $modelSpec "pvcStorage" }} + value: /data + {{- else }} + value: /tmp + {{- end }} + {{- with $modelSpec.vllmConfig}} + - name: LMCACHE_LOG_LEVEL + value: "DEBUG" + {{- if hasKey . "v1" }} + - name: VLLM_USE_V1 + value: {{ default 0 $modelSpec.vllmConfig.v1 | quote }} + {{- end}} + {{- end}} + {{- if $modelSpec.hf_token }} + - name: HF_TOKEN + {{- if kindIs "string" $modelSpec.hf_token }} + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: hf_token_{{ $modelSpec.name }} + {{- else }} + valueFrom: + secretKeyRef: + name: {{ $modelSpec.hf_token.secretName }} + key: {{ $modelSpec.hf_token.secretKey }} + {{- end }} + {{- end }} + {{- $vllmApiKey := $.Values.servingEngineSpec.vllmApiKey }} + {{- if $vllmApiKey }} + - name: VLLM_API_KEY + {{- if kindIs "string" $vllmApiKey }} + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: vllmApiKey + {{- else }} + valueFrom: + secretKeyRef: + name: {{ $vllmApiKey.secretName }} + key: {{ $vllmApiKey.secretKey }} + {{- end }} + {{- end }} + {{- with $modelSpec.env }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- if $modelSpec.lmcacheConfig }} + {{- if $modelSpec.lmcacheConfig.enabled }} + - name: LMCACHE_USE_EXPERIMENTAL + value: "True" + - name: VLLM_RPC_TIMEOUT + value: "1000000" + {{- end }} + {{- if $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }} + - name: LMCACHE_LOCAL_CPU + value: "True" + - name: LMCACHE_MAX_LOCAL_CPU_SIZE + value: "{{ $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }}" + {{- end }} + {{- if $modelSpec.lmcacheConfig.diskOffloadingBufferSize }} + - name: LMCACHE_LOCAL_DISK + value: "True" + - name: LMCACHE_MAX_LOCAL_DISK_SIZE + value: "{{ $modelSpec.lmcacheConfig.diskOffloadingBufferSize }}" + {{- end }} + {{- if .Values.cacheserverSpec }} + - name: LMCACHE_REMOTE_URL + value: "{{ include "cacheserver.formatRemoteUrl" (dict "service_name" (print .Release.Name "-cache-server-service") "port" .Values.cacheserverSpec.servicePort) }}" + - name: LMCACHE_REMOTE_SERDE + value: "{{ .Values.cacheserverSpec.serde }}" + {{- end }} + {{- end }} + {{- if .Values.servingEngineSpec.configs }} + envFrom: + - configMapRef: + name: "{{ .Release.Name }}-configs" + {{- end }} + ports: + - name: {{ include "chart.container-port-name" . }} + containerPort: {{ include "chart.container-port" . }} + {{- include "chart.probes" . | indent 10 }} + resources: {{- include "chart.resources" $modelSpec | nindent 12 }} + {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumeMounts") }} + volumeMounts: + {{- end }} + {{- if hasKey $modelSpec "pvcStorage" }} + - name: {{ .Release.Name }}-storage + mountPath: /data + {{- end }} + {{- with $modelSpec.vllmConfig }} + {{- if hasKey $modelSpec.vllmConfig "tensorParallelSize"}} + - name: shm + mountPath: /dev/shm + {{- end}} + {{- end}} + {{- if $modelSpec.chatTemplate }} + - name: vllm-templates + mountPath: /templates + {{- end }} + {{- if hasKey $modelSpec "extraVolumeMounts" }} + {{- toYaml $modelSpec.extraVolumeMounts | nindent 10 }} + {{- end }} + {{- if $modelSpec.imagePullSecret }} + imagePullSecrets: + - name: {{ $modelSpec.imagePullSecret }} + {{- end }} + {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumes") }} + volumes: + {{- end}} + {{- if hasKey $modelSpec "pvcStorage" }} + - name: {{ .Release.Name }}-storage + persistentVolumeClaim: + claimName: "{{ .Release.Name }}-{{$modelSpec.name}}-storage-claim" + {{- end }} + {{- with $modelSpec.vllmConfig }} + {{- if hasKey $modelSpec.vllmConfig "tensorParallelSize"}} + - name: shm + emptyDir: + medium: Memory + sizeLimit: {{ default "20Gi" $modelSpec.shmSize }} + {{- end}} + {{- end}} + {{- if $modelSpec.chatTemplate}} + {{- if hasKey $modelSpec "chatTemplateConfigMap" }} + - name: {{ .Release.Name }}-chat-templates + configMap: + name: "{{ .Release.Name }}-{{$modelSpec.name}}-chat-templates" + {{- else }} + - name: vllm-templates + persistentVolumeClaim: + claimName: vllm-templates-pvc + {{- end }} + {{- end}} + {{- if hasKey $modelSpec "extraVolumes" }} + {{- toYaml $modelSpec.extraVolumes | nindent 8 }} + {{- end}} + {{- if .Values.servingEngineSpec.tolerations }} + {{- with .Values.servingEngineSpec.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + + {{- if .Values.servingEngineSpec.runtimeClassName }} + runtimeClassName: {{ .Values.servingEngineSpec.runtimeClassName }} + {{- end }} + {{- if .Values.servingEngineSpec.schedulerName }} + schedulerName: {{ .Values.servingEngineSpec.schedulerName }} + {{- end }} + {{- if $modelSpec.nodeSelectorTerms}} + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + {{- with $modelSpec.nodeSelectorTerms }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} + + image: {{ .Values.servingEngineSpec.image | quote }} + resources: + limits: + nvidia.com/gpu: "{{ .Values.servingEngineSpec.gpuLimit }}" + livenessProbe: + exec: + command: ["/bin/bash", "-c", "echo TBD"] + readinessProbe: + exec: + command: ["bash", "-c", "true"] + volumes: + - name: models + persistentVolumeClaim: + claimName: {{ .Values.servingEngineSpec.pvcName | quote }} +{{- if and $modelSpec.chatTemplate (hasKey $modelSpec "chatTemplateConfigMap") }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ .Release.Name }}-{{$modelSpec.name}}-chat-templates" + namespace: "{{ .Release.Namespace }}" +data: + {{ $modelSpec.chatTemplate }}: |- + {{ $modelSpec.chatTemplateConfigMap }} +{{- end }} +{{- end }} +--- +{{- end }} +{{- end }} From 2b2cdfa9c882dc955e9d42da2b5285f40da306d2 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sat, 10 May 2025 17:16:39 +0000 Subject: [PATCH 03/44] [Feat] Fixed typo at ray cluster template file. Added example values for the helm chart. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 97 ++++++++++--------- ...-15-minimal-pipeline-parallel-example.yaml | 14 +++ 2 files changed, 64 insertions(+), 47 deletions(-) create mode 100644 tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index 24d07b4b4..1262c896d 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -25,16 +25,16 @@ spec: {{- end }} containers: - name: vllm-ray-head - env: - - name: HF_HOME - value: "{{ .Values.servingEngineSpec.modelMountPath }}" + envFrom: + - configMapRef: + name: "{{ .Release.Name }}-{{$modelSpec.name}}-envs" image: "{{ required "Required value 'modelSpec.repository' must be defined !" $modelSpec.repository }}:{{ required "Required value 'modelSpec.tag' must be defined !" $modelSpec.tag }}" livenessProbe: exec: - command: ["bash", "-c", "true"] + command: ["/bin/bash", "-c", "echo TBD"] readinessProbe: exec: - command: ["bash", "-c", "true"] + command: ["/bin/bash", "-c", "echo TBD"] volumeMounts: - mountPath: {{ .Values.servingEngineSpec.modelMountPath | quote }} name: models @@ -58,20 +58,6 @@ spec: - mountPath: {{ .Values.servingEngineSpec.modelMountPath | quote }} name: models env: - - name: HF_HOME - {{- if hasKey $modelSpec "pvcStorage" }} - value: /data - {{- else }} - value: /tmp - {{- end }} - {{- with $modelSpec.vllmConfig}} - - name: LMCACHE_LOG_LEVEL - value: "DEBUG" - {{- if hasKey . "v1" }} - - name: VLLM_USE_V1 - value: {{ default 0 $modelSpec.vllmConfig.v1 | quote }} - {{- end}} - {{- end}} {{- if $modelSpec.hf_token }} - name: HF_TOKEN {{- if kindIs "string" $modelSpec.hf_token }} @@ -104,37 +90,14 @@ spec: {{- with $modelSpec.env }} {{- toYaml . | nindent 10 }} {{- end }} - {{- if $modelSpec.lmcacheConfig }} - {{- if $modelSpec.lmcacheConfig.enabled }} - - name: LMCACHE_USE_EXPERIMENTAL - value: "True" - - name: VLLM_RPC_TIMEOUT - value: "1000000" - {{- end }} - {{- if $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }} - - name: LMCACHE_LOCAL_CPU - value: "True" - - name: LMCACHE_MAX_LOCAL_CPU_SIZE - value: "{{ $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }}" - {{- end }} - {{- if $modelSpec.lmcacheConfig.diskOffloadingBufferSize }} - - name: LMCACHE_LOCAL_DISK - value: "True" - - name: LMCACHE_MAX_LOCAL_DISK_SIZE - value: "{{ $modelSpec.lmcacheConfig.diskOffloadingBufferSize }}" - {{- end }} - {{- if .Values.cacheserverSpec }} - - name: LMCACHE_REMOTE_URL - value: "{{ include "cacheserver.formatRemoteUrl" (dict "service_name" (print .Release.Name "-cache-server-service") "port" .Values.cacheserverSpec.servicePort) }}" - - name: LMCACHE_REMOTE_SERDE - value: "{{ .Values.cacheserverSpec.serde }}" - {{- end }} - {{- end }} {{- if .Values.servingEngineSpec.configs }} envFrom: - configMapRef: name: "{{ .Release.Name }}-configs" {{- end }} + envFrom: + - configMapRef: + name: "{{ .Release.Name }}-{{$modelSpec.name}}-envs" ports: - name: {{ include "chart.container-port-name" . }} containerPort: {{ include "chart.container-port" . }} @@ -217,7 +180,7 @@ spec: {{- end }} {{- end }} - image: {{ .Values.servingEngineSpec.image | quote }} + image: "{{ required "Required value 'modelSpec.repository' must be defined !" $modelSpec.repository }}:{{ required "Required value 'modelSpec.tag' must be defined !" $modelSpec.tag }}" resources: limits: nvidia.com/gpu: "{{ .Values.servingEngineSpec.gpuLimit }}" @@ -226,11 +189,50 @@ spec: command: ["/bin/bash", "-c", "echo TBD"] readinessProbe: exec: - command: ["bash", "-c", "true"] + command: ["/bin/bash", "-c", "echo TBD"] volumes: - name: models persistentVolumeClaim: claimName: {{ .Values.servingEngineSpec.pvcName | quote }} + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ .Release.Name }}-{{$modelSpec.name}}-envs" + namespace: "{{ .Release.Namespace }}" +data: + HF_HOME: {{- if hasKey $modelSpec "pvcStorage" }}"\/data"{{ else }}"/tmp"{{ end }} + LMCACHE_LOG_LEVEL: "DEBUG" + {{- if hasKey $modelSpec.vllmConfig "v1" }} + VLLM_USE_V1: {{ default 0 $modelSpec.vllmConfig.v1 | quote }} + {{- end }} + {{- if $modelSpec.lmcacheConfig }} + {{- if $modelSpec.lmcacheConfig.enabled }} + LMCACHE_USE_EXPERIMENTAL: "True" + VLLM_RPC_TIMEOUT: "1000000" + {{- end }} + {{- if $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }} + LMCACHE_LOCAL_CPU: "True" + LMCACHE_MAX_LOCAL_CPU_SIZE: "{{ $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }}" + {{- end }} + {{- if $modelSpec.lmcacheConfig.diskOffloadingBufferSize }} + LMCACHE_LOCAL_DISK: "True" + LMCACHE_MAX_LOCAL_DISK_SIZE: "{{ $modelSpec.lmcacheConfig.diskOffloadingBufferSize }}" + {{- end }} + {{- if .Values.cacheserverSpec }} + LMCACHE_REMOTE_URL: "{{ include "cacheserver.formatRemoteUrl" (dict "service_name" (print .Release.Name "-cache-server-service") "port" .Values.cacheserverSpec.servicePort) }}" + LMCACHE_REMOTE_SERDE: "{{ .Values.cacheserverSpec.serde }}" + {{- end }} + {{- end }} + {{- with $modelSpec.env }} + {{- range $k, $v := . }} + {{ $k }}: "{{ $v }}" + {{- end }} + {{- end }} +--- + + {{- if and $modelSpec.chatTemplate (hasKey $modelSpec "chatTemplateConfigMap") }} --- apiVersion: v1 @@ -246,3 +248,4 @@ data: --- {{- end }} {{- end }} +{{- end }} diff --git a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml new file mode 100644 index 000000000..f97da986f --- /dev/null +++ b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml @@ -0,0 +1,14 @@ +servingEngineSpec: + runtimeClassName: "" + modelSpec: + - name: "gpt2" + repository: "vllm/vllm-openai" + tag: "latest" + modelURL: "gpt2" + + replicaCount: 2 + + requestCPU: 6 + requestMemory: "16Gi" + requestGPU: 1 + enableKubeRay: "true" From 34aa920b46cadced673d7729b98e34ef9d652504 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sat, 10 May 2025 17:57:50 +0000 Subject: [PATCH 04/44] [Feat] Removed unused fields at the moment. Bugfixed conflicting resource creation for kuberay. Signed-off-by: insukim1994 --- helm/templates/deployment-vllm-multi.yaml | 2 +- helm/templates/ray-cluster.yaml | 189 +----------------- helm/values.yaml | 1 + ...-15-minimal-pipeline-parallel-example.yaml | 2 +- 4 files changed, 9 insertions(+), 185 deletions(-) diff --git a/helm/templates/deployment-vllm-multi.yaml b/helm/templates/deployment-vllm-multi.yaml index 7459c508f..c77d05b99 100644 --- a/helm/templates/deployment-vllm-multi.yaml +++ b/helm/templates/deployment-vllm-multi.yaml @@ -1,4 +1,4 @@ -{{- if .Values.servingEngineSpec.enableEngine -}} +{{- if and .Values.servingEngineSpec.enableEngine (not .Values.servingEngineSpec.enableKubeRay) -}} {{- range $modelSpec := .Values.servingEngineSpec.modelSpec }} {{- $kv_role := "kv_both" }} {{- $kv_rank := 0 }} diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index 1262c896d..0c7c548b5 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -1,22 +1,23 @@ -{{- if .Values.servingEngineSpec.enableEngine -}} +{{- if and .Values.servingEngineSpec.enableEngine .Values.servingEngineSpec.enableKubeRay }} {{- range $modelSpec := .Values.servingEngineSpec.modelSpec }} {{- with $ -}} -{{- if .Values.servingEngineSpec.enableKubeRay }} apiVersion: ray.io/v1 kind: RayCluster metadata: name: "{{ .Release.Name }}-{{$modelSpec.name}}-raycluster-vllm" namespace: {{ .Release.Namespace }} labels: - model: {{ $modelSpec.name }} helm-release-name: {{ .Release.Name }} - {{- include "chart.engineLabels" . | nindent 4 }} spec: headGroupSpec: serviceType: ClusterIP rayStartParams: dashboard-host: "0.0.0.0" template: + metadata: + labels: + model: {{ $modelSpec.name }} + {{- include "chart.engineLabels" . | nindent 10 }} spec: terminationGracePeriodSeconds: 0 {{- if .Values.servingEngineSpec.securityContext }} @@ -25,9 +26,6 @@ spec: {{- end }} containers: - name: vllm-ray-head - envFrom: - - configMapRef: - name: "{{ .Release.Name }}-{{$modelSpec.name}}-envs" image: "{{ required "Required value 'modelSpec.repository' must be defined !" $modelSpec.repository }}:{{ required "Required value 'modelSpec.tag' must be defined !" $modelSpec.tag }}" livenessProbe: exec: @@ -35,13 +33,6 @@ spec: readinessProbe: exec: command: ["/bin/bash", "-c", "echo TBD"] - volumeMounts: - - mountPath: {{ .Values.servingEngineSpec.modelMountPath | quote }} - name: models - volumes: - - name: models - persistentVolumeClaim: - claimName: {{ .Values.servingEngineSpec.pvcName | quote }} workerGroupSpecs: - rayStartParams: {} replicas: {{ $modelSpec.replicaCount }} @@ -54,183 +45,16 @@ spec: spec: containers: - name: vllm-ray-worker - volumeMounts: - - mountPath: {{ .Values.servingEngineSpec.modelMountPath | quote }} - name: models - env: - {{- if $modelSpec.hf_token }} - - name: HF_TOKEN - {{- if kindIs "string" $modelSpec.hf_token }} - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-secrets - key: hf_token_{{ $modelSpec.name }} - {{- else }} - valueFrom: - secretKeyRef: - name: {{ $modelSpec.hf_token.secretName }} - key: {{ $modelSpec.hf_token.secretKey }} - {{- end }} - {{- end }} - {{- $vllmApiKey := $.Values.servingEngineSpec.vllmApiKey }} - {{- if $vllmApiKey }} - - name: VLLM_API_KEY - {{- if kindIs "string" $vllmApiKey }} - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-secrets - key: vllmApiKey - {{- else }} - valueFrom: - secretKeyRef: - name: {{ $vllmApiKey.secretName }} - key: {{ $vllmApiKey.secretKey }} - {{- end }} - {{- end }} - {{- with $modelSpec.env }} - {{- toYaml . | nindent 10 }} - {{- end }} - {{- if .Values.servingEngineSpec.configs }} - envFrom: - - configMapRef: - name: "{{ .Release.Name }}-configs" - {{- end }} - envFrom: - - configMapRef: - name: "{{ .Release.Name }}-{{$modelSpec.name}}-envs" - ports: - - name: {{ include "chart.container-port-name" . }} - containerPort: {{ include "chart.container-port" . }} - {{- include "chart.probes" . | indent 10 }} - resources: {{- include "chart.resources" $modelSpec | nindent 12 }} - {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumeMounts") }} - volumeMounts: - {{- end }} - {{- if hasKey $modelSpec "pvcStorage" }} - - name: {{ .Release.Name }}-storage - mountPath: /data - {{- end }} - {{- with $modelSpec.vllmConfig }} - {{- if hasKey $modelSpec.vllmConfig "tensorParallelSize"}} - - name: shm - mountPath: /dev/shm - {{- end}} - {{- end}} - {{- if $modelSpec.chatTemplate }} - - name: vllm-templates - mountPath: /templates - {{- end }} - {{- if hasKey $modelSpec "extraVolumeMounts" }} - {{- toYaml $modelSpec.extraVolumeMounts | nindent 10 }} - {{- end }} - {{- if $modelSpec.imagePullSecret }} - imagePullSecrets: - - name: {{ $modelSpec.imagePullSecret }} - {{- end }} - {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumes") }} - volumes: - {{- end}} - {{- if hasKey $modelSpec "pvcStorage" }} - - name: {{ .Release.Name }}-storage - persistentVolumeClaim: - claimName: "{{ .Release.Name }}-{{$modelSpec.name}}-storage-claim" - {{- end }} - {{- with $modelSpec.vllmConfig }} - {{- if hasKey $modelSpec.vllmConfig "tensorParallelSize"}} - - name: shm - emptyDir: - medium: Memory - sizeLimit: {{ default "20Gi" $modelSpec.shmSize }} - {{- end}} - {{- end}} - {{- if $modelSpec.chatTemplate}} - {{- if hasKey $modelSpec "chatTemplateConfigMap" }} - - name: {{ .Release.Name }}-chat-templates - configMap: - name: "{{ .Release.Name }}-{{$modelSpec.name}}-chat-templates" - {{- else }} - - name: vllm-templates - persistentVolumeClaim: - claimName: vllm-templates-pvc - {{- end }} - {{- end}} - {{- if hasKey $modelSpec "extraVolumes" }} - {{- toYaml $modelSpec.extraVolumes | nindent 8 }} - {{- end}} - {{- if .Values.servingEngineSpec.tolerations }} - {{- with .Values.servingEngineSpec.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- end }} - - {{- if .Values.servingEngineSpec.runtimeClassName }} - runtimeClassName: {{ .Values.servingEngineSpec.runtimeClassName }} - {{- end }} - {{- if .Values.servingEngineSpec.schedulerName }} - schedulerName: {{ .Values.servingEngineSpec.schedulerName }} - {{- end }} - {{- if $modelSpec.nodeSelectorTerms}} - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - {{- with $modelSpec.nodeSelectorTerms }} - {{- toYaml . | nindent 12 }} - {{- end }} - {{- end }} - image: "{{ required "Required value 'modelSpec.repository' must be defined !" $modelSpec.repository }}:{{ required "Required value 'modelSpec.tag' must be defined !" $modelSpec.tag }}" resources: limits: - nvidia.com/gpu: "{{ .Values.servingEngineSpec.gpuLimit }}" + nvidia.com/gpu: {{ .Values.servingEngineSpec.gpuLimit }} livenessProbe: exec: command: ["/bin/bash", "-c", "echo TBD"] readinessProbe: exec: command: ["/bin/bash", "-c", "echo TBD"] - volumes: - - name: models - persistentVolumeClaim: - claimName: {{ .Values.servingEngineSpec.pvcName | quote }} - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: "{{ .Release.Name }}-{{$modelSpec.name}}-envs" - namespace: "{{ .Release.Namespace }}" -data: - HF_HOME: {{- if hasKey $modelSpec "pvcStorage" }}"\/data"{{ else }}"/tmp"{{ end }} - LMCACHE_LOG_LEVEL: "DEBUG" - {{- if hasKey $modelSpec.vllmConfig "v1" }} - VLLM_USE_V1: {{ default 0 $modelSpec.vllmConfig.v1 | quote }} - {{- end }} - {{- if $modelSpec.lmcacheConfig }} - {{- if $modelSpec.lmcacheConfig.enabled }} - LMCACHE_USE_EXPERIMENTAL: "True" - VLLM_RPC_TIMEOUT: "1000000" - {{- end }} - {{- if $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }} - LMCACHE_LOCAL_CPU: "True" - LMCACHE_MAX_LOCAL_CPU_SIZE: "{{ $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }}" - {{- end }} - {{- if $modelSpec.lmcacheConfig.diskOffloadingBufferSize }} - LMCACHE_LOCAL_DISK: "True" - LMCACHE_MAX_LOCAL_DISK_SIZE: "{{ $modelSpec.lmcacheConfig.diskOffloadingBufferSize }}" - {{- end }} - {{- if .Values.cacheserverSpec }} - LMCACHE_REMOTE_URL: "{{ include "cacheserver.formatRemoteUrl" (dict "service_name" (print .Release.Name "-cache-server-service") "port" .Values.cacheserverSpec.servicePort) }}" - LMCACHE_REMOTE_SERDE: "{{ .Values.cacheserverSpec.serde }}" - {{- end }} - {{- end }} - {{- with $modelSpec.env }} - {{- range $k, $v := . }} - {{ $k }}: "{{ $v }}" - {{- end }} - {{- end }} ---- {{- if and $modelSpec.chatTemplate (hasKey $modelSpec "chatTemplateConfigMap") }} @@ -248,4 +72,3 @@ data: --- {{- end }} {{- end }} -{{- end }} diff --git a/helm/values.yaml b/helm/values.yaml index fef374efe..1250af2b5 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -4,6 +4,7 @@ # -- Serving engine configuratoon servingEngineSpec: enableEngine: true + enableKubeRay: false # -- Customized labels for the serving engine deployment labels: environment: "test" diff --git a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml index f97da986f..391f921ce 100644 --- a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml +++ b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml @@ -1,5 +1,6 @@ servingEngineSpec: runtimeClassName: "" + enableKubeRay: true modelSpec: - name: "gpt2" repository: "vllm/vllm-openai" @@ -11,4 +12,3 @@ servingEngineSpec: requestCPU: 6 requestMemory: "16Gi" requestGPU: 1 - enableKubeRay: "true" From 720cebf43cfa6595c4d351fe48bff9fc6bff0f27 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 03:53:16 +0000 Subject: [PATCH 05/44] [Feat] Added startup probe to check if all ray cluster nodes are up. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 53 +++++++- uv.lock | 208 +++++++++----------------------- 2 files changed, 111 insertions(+), 150 deletions(-) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index 0c7c548b5..fef662cf3 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -22,7 +22,7 @@ spec: terminationGracePeriodSeconds: 0 {{- if .Values.servingEngineSpec.securityContext }} securityContext: - {{- toYaml .Values.servingEngineSpec.securityContext | nindent 8 }} + {{- toYaml .Values.servingEngineSpec.securityContext | nindent 10 }} {{- end }} containers: - name: vllm-ray-head @@ -33,6 +33,23 @@ spec: readinessProbe: exec: command: ["/bin/bash", "-c", "echo TBD"] + env: + - name: EXPECTED_NODES + value: "{{ add $modelSpec.replicaCount 1}}" + startupProbe: + exec: + command: ["/bin/bash", "-c", "python3 /scripts/wait_for_ray.py"] + failureThreshold: 30 + periodSeconds: 15 + timeoutSeconds: 10 + volumeMounts: + - name: wait-script + mountPath: /scripts + readOnly: true + volumes: + - name: wait-script + configMap: + name: wait-for-ray-script workerGroupSpecs: - rayStartParams: {} replicas: {{ $modelSpec.replicaCount }} @@ -56,6 +73,40 @@ spec: exec: command: ["/bin/bash", "-c", "echo TBD"] +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: wait-for-ray-script +data: + wait_for_ray.py: | + import ray + import logging + import os + import sys + + logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(message)s') + + try: + ray.init(address="auto") + except Exception as e: + logging.error(f"Failed to initialize Ray: {e}") + sys.exit(1) + + expected_nodes = int(os.environ.get("EXPECTED_NODES", "1")) + + alive_nodes = [n for n in ray.nodes() if n["Alive"]] + alive_count = len(alive_nodes) + + logging.info(f"Ray cluster status: {alive_count}/{expected_nodes} nodes alive.") + + if alive_count >= expected_nodes: + logging.info("Cluster is ready.") + sys.exit(0) + else: + logging.info("Cluster is NOT ready.") + sys.exit(1) +--- {{- if and $modelSpec.chatTemplate (hasKey $modelSpec "chatTemplateConfigMap") }} --- diff --git a/uv.lock b/uv.lock index 4af014b99..91b65982f 100644 --- a/uv.lock +++ b/uv.lock @@ -229,45 +229,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "coverage" -version = "7.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/b4/a707d96c2c1ce9402ce1ce7124c53b9e4e1f3e617652a5ed2fbba4c9b4be/coverage-7.8.1.tar.gz", hash = "sha256:d41d4da5f2871b1782c6b74948d2d37aac3a5b39b43a6ba31d736b97a02ae1f1", size = 812193, upload-time = "2025-05-21T12:39:46.1Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/78/781501aa4759026dcef8024b404cacc4094348e5e199ed660c31f4650a33/coverage-7.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d8f844e837374a9497e11722d9eb9dfeb33b1b5d31136786c39a4c1a3073c6d", size = 211875, upload-time = "2025-05-21T12:38:20.497Z" }, - { url = "https://files.pythonhosted.org/packages/e6/00/a8a4548c22b73f8fd4373714f5a4cce3584827e2603847a8d90fba129807/coverage-7.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9cd54a762667c32112df5d6f059c5d61fa532ee06460948cc5bcbf60c502f5c9", size = 212129, upload-time = "2025-05-21T12:38:22.085Z" }, - { url = "https://files.pythonhosted.org/packages/9e/41/5cdc34afdc53b7f200439eb915f50d6ba17e3b0b5cdb6bb04d0ed9662703/coverage-7.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:958b513e23286178b513a6b4d975fe9e7cddbcea6e5ebe8d836e4ef067577154", size = 246176, upload-time = "2025-05-21T12:38:23.802Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1f/ca8e37fdd282dd6ebc4191a9fafcb46b6bf75e05a0fd796d6907399e44ea/coverage-7.8.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b31756ea647b6ef53190f6b708ad0c4c2ea879bc17799ba5b0699eee59ecf7b", size = 243068, upload-time = "2025-05-21T12:38:25.755Z" }, - { url = "https://files.pythonhosted.org/packages/cf/89/727503da5870fe1031ec443699beab44e02548d9873fe0a60adf6589fdd1/coverage-7.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccad4e29ac1b6f75bfeedb2cac4860fe5bd9e0a2f04c3e3218f661fa389ab101", size = 245329, upload-time = "2025-05-21T12:38:28.69Z" }, - { url = "https://files.pythonhosted.org/packages/25/1f/6935baf26071a66f390159ceb5c5bccfc898d00a90166b6ffc61b964856a/coverage-7.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:452f3831c64f5f50260e18a89e613594590d6ceac5206a9b7d76ba43586b01b3", size = 245100, upload-time = "2025-05-21T12:38:31.339Z" }, - { url = "https://files.pythonhosted.org/packages/3b/1f/0e5d68b12deb8a5c9648f61b515798e201f72fec17a0c7373a5f4903f8d8/coverage-7.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9296df6a33b8539cd753765eb5b47308602263a14b124a099cbcf5f770d7cf90", size = 243314, upload-time = "2025-05-21T12:38:34.974Z" }, - { url = "https://files.pythonhosted.org/packages/21/5d/375ba28a78e96a06ef0f1572b393e3fefd9d0deecf3ef9995eff1b1cea67/coverage-7.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d52d79dfd3b410b153b6d65b0e3afe834eca2b969377f55ad73c67156d35af0d", size = 244487, upload-time = "2025-05-21T12:38:37.84Z" }, - { url = "https://files.pythonhosted.org/packages/08/92/1b7fdf0924d8e6d7c2418d313c12d6e19c9a748448faedcc017082ec5b63/coverage-7.8.1-cp312-cp312-win32.whl", hash = "sha256:ebdf212e1ed85af63fa1a76d556c0a3c7b34348ffba6e145a64b15f003ad0a2b", size = 214367, upload-time = "2025-05-21T12:38:39.676Z" }, - { url = "https://files.pythonhosted.org/packages/07/b1/632f9e128ee9e149cfa80a3130362684244668b0dc6efedd6e466baaeb48/coverage-7.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:c04a7903644ccea8fa07c3e76db43ca31c8d453f93c5c94c0f9b82efca225543", size = 215169, upload-time = "2025-05-21T12:38:42.497Z" }, - { url = "https://files.pythonhosted.org/packages/ed/0a/696a8d6c245a72f61589e2015a633fab5aacd8c916802df41d23e387b442/coverage-7.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd5c305faa2e69334a53061b3168987847dadc2449bab95735242a9bde92fde8", size = 211902, upload-time = "2025-05-21T12:38:44.54Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2f/0c065dfaf497586cf1693dee2a94e7489a4be840a5bbe765a7a78735268b/coverage-7.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:af6b8cdf0857fd4e6460dd6639c37c3f82163127f6112c1942b5e6a52a477676", size = 212175, upload-time = "2025-05-21T12:38:46.143Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a1/a8a40658f67311c96c3d9073293fefee8a9485906ed531546dffe35fdd4b/coverage-7.8.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e233a56bbf99e4cb134c4f8e63b16c77714e3987daf2c5aa10c3ba8c4232d730", size = 245564, upload-time = "2025-05-21T12:38:47.843Z" }, - { url = "https://files.pythonhosted.org/packages/6e/94/dc36e2256ce484f482ed5b2a103a261009c301cdad237fdefe2a9b6ddeab/coverage-7.8.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dabc70012fd7b58a8040a7bc1b5f71fd0e62e2138aefdd8367d3d24bf82c349", size = 242719, upload-time = "2025-05-21T12:38:49.517Z" }, - { url = "https://files.pythonhosted.org/packages/73/d7/d096859c59f02d4550e6bc9180bd06c88313c32977d7458e0d4ed06ed057/coverage-7.8.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1f8e96455907496b3e4ea16f63bb578da31e17d2805278b193525e7714f17f2", size = 244634, upload-time = "2025-05-21T12:38:51.18Z" }, - { url = "https://files.pythonhosted.org/packages/be/a0/6f4db84d1d3334ca37c2dae02a54761a1a3918aec56faec26f1590077181/coverage-7.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0034ceec8e91fdaf77350901cc48f47efd00f23c220a3f9fc1187774ddf307cb", size = 244824, upload-time = "2025-05-21T12:38:52.789Z" }, - { url = "https://files.pythonhosted.org/packages/96/46/1e74016ba7d9f4242170f9d814454e6483a640332a67c0e139dab7d85762/coverage-7.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82db9344a07dd9106796b9fe8805425633146a7ea7fed5ed07c65a64d0bb79e1", size = 242872, upload-time = "2025-05-21T12:38:54.493Z" }, - { url = "https://files.pythonhosted.org/packages/22/41/51df77f279b49e7dd05ee9dfe746cf8698c873ffdf7fbe57aaee9522ec67/coverage-7.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9772c9e266b2ca4999180c12b90c8efb4c5c9ad3e55f301d78bc579af6467ad9", size = 244179, upload-time = "2025-05-21T12:38:56.762Z" }, - { url = "https://files.pythonhosted.org/packages/b8/83/6207522f3afb64592c47353bc79b0e3e6c3f48fde5e5221ab2b80a12e93d/coverage-7.8.1-cp313-cp313-win32.whl", hash = "sha256:6f24a1e2c373a77afae21bc512466a91e31251685c271c5309ee3e557f6e3e03", size = 214395, upload-time = "2025-05-21T12:38:58.631Z" }, - { url = "https://files.pythonhosted.org/packages/43/b8/cd40a8fff1633112ac40edde9006aceaa55b32a84976394a42c33547ef95/coverage-7.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:76a4e1d62505a21971968be61ae17cbdc5e0c483265a37f7ddbbc050f9c0b8ec", size = 215195, upload-time = "2025-05-21T12:39:00.614Z" }, - { url = "https://files.pythonhosted.org/packages/7e/f0/8fea9beb378cdce803ba838293314b21527f4edab58dcbe2e6a5553e7dc8/coverage-7.8.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:35dd5d405a1d378c39f3f30f628a25b0b99f1b8e5bdd78275df2e7b0404892d7", size = 212738, upload-time = "2025-05-21T12:39:02.808Z" }, - { url = "https://files.pythonhosted.org/packages/0c/90/f28953cd1246ad7839874ef97e181f153d4274cc6db21857fbca18b89c97/coverage-7.8.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:87b86a87f8de2e1bd0bcd45faf1b1edf54f988c8857157300e0336efcfb8ede6", size = 212958, upload-time = "2025-05-21T12:39:04.536Z" }, - { url = "https://files.pythonhosted.org/packages/fb/70/3f3d34ef68534afa73aee75537d1daf1e91029738cbf052ef828313aa960/coverage-7.8.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce4553a573edb363d5db12be1c044826878bec039159d6d4eafe826ef773396d", size = 257024, upload-time = "2025-05-21T12:39:06.703Z" }, - { url = "https://files.pythonhosted.org/packages/cf/66/96ab415609b777adfcfa00f29d75d2278da139c0958de7a50dd0023811e6/coverage-7.8.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db181a1896e0bad75b3bf4916c49fd3cf6751f9cc203fe0e0ecbee1fc43590fa", size = 252867, upload-time = "2025-05-21T12:39:08.818Z" }, - { url = "https://files.pythonhosted.org/packages/52/4f/3d48704c62fa5f72447005b8a77cc9cce5e164c2df357433442d17f2ac0a/coverage-7.8.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ce2606a171f9cf7c15a77ca61f979ffc0e0d92cd2fb18767cead58c1d19f58e", size = 255096, upload-time = "2025-05-21T12:39:10.516Z" }, - { url = "https://files.pythonhosted.org/packages/64/1d/e8d4ac647c1967dd3dbc250fb4595b838b7067ad32602a7339ac467d9c5a/coverage-7.8.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4fc4f7cff2495d6d112353c33a439230a6de0b7cd0c2578f1e8d75326f63d783", size = 256276, upload-time = "2025-05-21T12:39:12.177Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e4/62e2f9521f3758dea07bcefc2c9c0dd34fa67d7035b0443c7c3072e6308b/coverage-7.8.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ff619c58322d9d6df0a859dc76c3532d7bdbc125cb040f7cd642141446b4f654", size = 254478, upload-time = "2025-05-21T12:39:14.325Z" }, - { url = "https://files.pythonhosted.org/packages/49/41/7af246f5e68272f97a31a122da5878747e941fef019430485534d1f6a44a/coverage-7.8.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0d6290a466a6f3fadf6add2dd4ec11deba4e1a6e3db2dd284edd497aadf802f", size = 255255, upload-time = "2025-05-21T12:39:16.059Z" }, - { url = "https://files.pythonhosted.org/packages/05/5d/5dacd7915972f82d909f36974c6415667dae08a32478d87dfdbac6788e22/coverage-7.8.1-cp313-cp313t-win32.whl", hash = "sha256:e4e893c7f7fb12271a667d5c1876710fae06d7580343afdb5f3fc4488b73209e", size = 215112, upload-time = "2025-05-21T12:39:18.263Z" }, - { url = "https://files.pythonhosted.org/packages/8b/89/48e77e71e81e5b79fd6471083d087cd69517e5f585b548d87c92d5ae873c/coverage-7.8.1-cp313-cp313t-win_amd64.whl", hash = "sha256:41d142eefbc0bb3be160a77b2c0fbec76f345387676265052e224eb6c67b7af3", size = 216270, upload-time = "2025-05-21T12:39:20.461Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a1/4d968d4605f3a87a809f0c8f495eed81656c93cf6c00818334498ad6ad45/coverage-7.8.1-py3-none-any.whl", hash = "sha256:e54b80885b0e61d346accc5709daf8762471a452345521cc9281604a907162c2", size = 203623, upload-time = "2025-05-21T12:39:43.473Z" }, -] - [[package]] name = "distlib" version = "0.3.9" @@ -653,62 +614,62 @@ wheels = [ [[package]] name = "multidict" -version = "6.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/2f/a3470242707058fe856fe59241eee5635d79087100b7042a867368863a27/multidict-6.4.4.tar.gz", hash = "sha256:69ee9e6ba214b5245031b76233dd95408a0fd57fdb019ddcc1ead4790932a8e8", size = 90183, upload-time = "2025-05-19T14:16:37.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/b5/5675377da23d60875fe7dae6be841787755878e315e2f517235f22f59e18/multidict-6.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc388f75a1c00000824bf28b7633e40854f4127ede80512b44c3cfeeea1839a2", size = 64293, upload-time = "2025-05-19T14:14:44.724Z" }, - { url = "https://files.pythonhosted.org/packages/34/a7/be384a482754bb8c95d2bbe91717bf7ccce6dc38c18569997a11f95aa554/multidict-6.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:98af87593a666f739d9dba5d0ae86e01b0e1a9cfcd2e30d2d361fbbbd1a9162d", size = 38096, upload-time = "2025-05-19T14:14:45.95Z" }, - { url = "https://files.pythonhosted.org/packages/66/6d/d59854bb4352306145bdfd1704d210731c1bb2c890bfee31fb7bbc1c4c7f/multidict-6.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aff4cafea2d120327d55eadd6b7f1136a8e5a0ecf6fb3b6863e8aca32cd8e50a", size = 37214, upload-time = "2025-05-19T14:14:47.158Z" }, - { url = "https://files.pythonhosted.org/packages/99/e0/c29d9d462d7cfc5fc8f9bf24f9c6843b40e953c0b55e04eba2ad2cf54fba/multidict-6.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169c4ba7858176b797fe551d6e99040c531c775d2d57b31bcf4de6d7a669847f", size = 224686, upload-time = "2025-05-19T14:14:48.366Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4a/da99398d7fd8210d9de068f9a1b5f96dfaf67d51e3f2521f17cba4ee1012/multidict-6.4.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9eb4c59c54421a32b3273d4239865cb14ead53a606db066d7130ac80cc8ec93", size = 231061, upload-time = "2025-05-19T14:14:49.952Z" }, - { url = "https://files.pythonhosted.org/packages/21/f5/ac11add39a0f447ac89353e6ca46666847051103649831c08a2800a14455/multidict-6.4.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cf3bd54c56aa16fdb40028d545eaa8d051402b61533c21e84046e05513d5780", size = 232412, upload-time = "2025-05-19T14:14:51.812Z" }, - { url = "https://files.pythonhosted.org/packages/d9/11/4b551e2110cded705a3c13a1d4b6a11f73891eb5a1c449f1b2b6259e58a6/multidict-6.4.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f682c42003c7264134bfe886376299db4cc0c6cd06a3295b41b347044bcb5482", size = 231563, upload-time = "2025-05-19T14:14:53.262Z" }, - { url = "https://files.pythonhosted.org/packages/4c/02/751530c19e78fe73b24c3da66618eda0aa0d7f6e7aa512e46483de6be210/multidict-6.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920f9cf2abdf6e493c519492d892c362007f113c94da4c239ae88429835bad1", size = 223811, upload-time = "2025-05-19T14:14:55.232Z" }, - { url = "https://files.pythonhosted.org/packages/c7/cb/2be8a214643056289e51ca356026c7b2ce7225373e7a1f8c8715efee8988/multidict-6.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:530d86827a2df6504526106b4c104ba19044594f8722d3e87714e847c74a0275", size = 216524, upload-time = "2025-05-19T14:14:57.226Z" }, - { url = "https://files.pythonhosted.org/packages/19/f3/6d5011ec375c09081f5250af58de85f172bfcaafebff286d8089243c4bd4/multidict-6.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecde56ea2439b96ed8a8d826b50c57364612ddac0438c39e473fafad7ae1c23b", size = 229012, upload-time = "2025-05-19T14:14:58.597Z" }, - { url = "https://files.pythonhosted.org/packages/67/9c/ca510785df5cf0eaf5b2a8132d7d04c1ce058dcf2c16233e596ce37a7f8e/multidict-6.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dc8c9736d8574b560634775ac0def6bdc1661fc63fa27ffdfc7264c565bcb4f2", size = 226765, upload-time = "2025-05-19T14:15:00.048Z" }, - { url = "https://files.pythonhosted.org/packages/36/c8/ca86019994e92a0f11e642bda31265854e6ea7b235642f0477e8c2e25c1f/multidict-6.4.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f3d3b3c34867579ea47cbd6c1f2ce23fbfd20a273b6f9e3177e256584f1eacc", size = 222888, upload-time = "2025-05-19T14:15:01.568Z" }, - { url = "https://files.pythonhosted.org/packages/c6/67/bc25a8e8bd522935379066950ec4e2277f9b236162a73548a2576d4b9587/multidict-6.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:87a728af265e08f96b6318ebe3c0f68b9335131f461efab2fc64cc84a44aa6ed", size = 234041, upload-time = "2025-05-19T14:15:03.759Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a0/70c4c2d12857fccbe607b334b7ee28b6b5326c322ca8f73ee54e70d76484/multidict-6.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9f193eeda1857f8e8d3079a4abd258f42ef4a4bc87388452ed1e1c4d2b0c8740", size = 231046, upload-time = "2025-05-19T14:15:05.698Z" }, - { url = "https://files.pythonhosted.org/packages/c1/0f/52954601d02d39742aab01d6b92f53c1dd38b2392248154c50797b4df7f1/multidict-6.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be06e73c06415199200e9a2324a11252a3d62030319919cde5e6950ffeccf72e", size = 227106, upload-time = "2025-05-19T14:15:07.124Z" }, - { url = "https://files.pythonhosted.org/packages/af/24/679d83ec4379402d28721790dce818e5d6b9f94ce1323a556fb17fa9996c/multidict-6.4.4-cp312-cp312-win32.whl", hash = "sha256:622f26ea6a7e19b7c48dd9228071f571b2fbbd57a8cd71c061e848f281550e6b", size = 35351, upload-time = "2025-05-19T14:15:08.556Z" }, - { url = "https://files.pythonhosted.org/packages/52/ef/40d98bc5f986f61565f9b345f102409534e29da86a6454eb6b7c00225a13/multidict-6.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:5e2bcda30d5009996ff439e02a9f2b5c3d64a20151d34898c000a6281faa3781", size = 38791, upload-time = "2025-05-19T14:15:09.825Z" }, - { url = "https://files.pythonhosted.org/packages/df/2a/e166d2ffbf4b10131b2d5b0e458f7cee7d986661caceae0de8753042d4b2/multidict-6.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:82ffabefc8d84c2742ad19c37f02cde5ec2a1ee172d19944d380f920a340e4b9", size = 64123, upload-time = "2025-05-19T14:15:11.044Z" }, - { url = "https://files.pythonhosted.org/packages/8c/96/e200e379ae5b6f95cbae472e0199ea98913f03d8c9a709f42612a432932c/multidict-6.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6a2f58a66fe2c22615ad26156354005391e26a2f3721c3621504cd87c1ea87bf", size = 38049, upload-time = "2025-05-19T14:15:12.902Z" }, - { url = "https://files.pythonhosted.org/packages/75/fb/47afd17b83f6a8c7fa863c6d23ac5ba6a0e6145ed8a6bcc8da20b2b2c1d2/multidict-6.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5883d6ee0fd9d8a48e9174df47540b7545909841ac82354c7ae4cbe9952603bd", size = 37078, upload-time = "2025-05-19T14:15:14.282Z" }, - { url = "https://files.pythonhosted.org/packages/fa/70/1af3143000eddfb19fd5ca5e78393985ed988ac493bb859800fe0914041f/multidict-6.4.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9abcf56a9511653fa1d052bfc55fbe53dbee8f34e68bd6a5a038731b0ca42d15", size = 224097, upload-time = "2025-05-19T14:15:15.566Z" }, - { url = "https://files.pythonhosted.org/packages/b1/39/d570c62b53d4fba844e0378ffbcd02ac25ca423d3235047013ba2f6f60f8/multidict-6.4.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6ed5ae5605d4ad5a049fad2a28bb7193400700ce2f4ae484ab702d1e3749c3f9", size = 230768, upload-time = "2025-05-19T14:15:17.308Z" }, - { url = "https://files.pythonhosted.org/packages/fd/f8/ed88f2c4d06f752b015933055eb291d9bc184936903752c66f68fb3c95a7/multidict-6.4.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbfcb60396f9bcfa63e017a180c3105b8c123a63e9d1428a36544e7d37ca9e20", size = 231331, upload-time = "2025-05-19T14:15:18.73Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6f/8e07cffa32f483ab887b0d56bbd8747ac2c1acd00dc0af6fcf265f4a121e/multidict-6.4.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0f1987787f5f1e2076b59692352ab29a955b09ccc433c1f6b8e8e18666f608b", size = 230169, upload-time = "2025-05-19T14:15:20.179Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2b/5dcf173be15e42f330110875a2668ddfc208afc4229097312212dc9c1236/multidict-6.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0121ccce8c812047d8d43d691a1ad7641f72c4f730474878a5aeae1b8ead8c", size = 222947, upload-time = "2025-05-19T14:15:21.714Z" }, - { url = "https://files.pythonhosted.org/packages/39/75/4ddcbcebe5ebcd6faa770b629260d15840a5fc07ce8ad295a32e14993726/multidict-6.4.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83ec4967114295b8afd120a8eec579920c882831a3e4c3331d591a8e5bfbbc0f", size = 215761, upload-time = "2025-05-19T14:15:23.242Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c9/55e998ae45ff15c5608e384206aa71a11e1b7f48b64d166db400b14a3433/multidict-6.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:995f985e2e268deaf17867801b859a282e0448633f1310e3704b30616d269d69", size = 227605, upload-time = "2025-05-19T14:15:24.763Z" }, - { url = "https://files.pythonhosted.org/packages/04/49/c2404eac74497503c77071bd2e6f88c7e94092b8a07601536b8dbe99be50/multidict-6.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d832c608f94b9f92a0ec8b7e949be7792a642b6e535fcf32f3e28fab69eeb046", size = 226144, upload-time = "2025-05-19T14:15:26.249Z" }, - { url = "https://files.pythonhosted.org/packages/62/c5/0cd0c3c6f18864c40846aa2252cd69d308699cb163e1c0d989ca301684da/multidict-6.4.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d21c1212171cf7da703c5b0b7a0e85be23b720818aef502ad187d627316d5645", size = 221100, upload-time = "2025-05-19T14:15:28.303Z" }, - { url = "https://files.pythonhosted.org/packages/71/7b/f2f3887bea71739a046d601ef10e689528d4f911d84da873b6be9194ffea/multidict-6.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:cbebaa076aaecad3d4bb4c008ecc73b09274c952cf6a1b78ccfd689e51f5a5b0", size = 232731, upload-time = "2025-05-19T14:15:30.263Z" }, - { url = "https://files.pythonhosted.org/packages/e5/b3/d9de808349df97fa75ec1372758701b5800ebad3c46ae377ad63058fbcc6/multidict-6.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c93a6fb06cc8e5d3628b2b5fda215a5db01e8f08fc15fadd65662d9b857acbe4", size = 229637, upload-time = "2025-05-19T14:15:33.337Z" }, - { url = "https://files.pythonhosted.org/packages/5e/57/13207c16b615eb4f1745b44806a96026ef8e1b694008a58226c2d8f5f0a5/multidict-6.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8cd8f81f1310182362fb0c7898145ea9c9b08a71081c5963b40ee3e3cac589b1", size = 225594, upload-time = "2025-05-19T14:15:34.832Z" }, - { url = "https://files.pythonhosted.org/packages/3a/e4/d23bec2f70221604f5565000632c305fc8f25ba953e8ce2d8a18842b9841/multidict-6.4.4-cp313-cp313-win32.whl", hash = "sha256:3e9f1cd61a0ab857154205fb0b1f3d3ace88d27ebd1409ab7af5096e409614cd", size = 35359, upload-time = "2025-05-19T14:15:36.246Z" }, - { url = "https://files.pythonhosted.org/packages/a7/7a/cfe1a47632be861b627f46f642c1d031704cc1c0f5c0efbde2ad44aa34bd/multidict-6.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:8ffb40b74400e4455785c2fa37eba434269149ec525fc8329858c862e4b35373", size = 38903, upload-time = "2025-05-19T14:15:37.507Z" }, - { url = "https://files.pythonhosted.org/packages/68/7b/15c259b0ab49938a0a1c8f3188572802704a779ddb294edc1b2a72252e7c/multidict-6.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6a602151dbf177be2450ef38966f4be3467d41a86c6a845070d12e17c858a156", size = 68895, upload-time = "2025-05-19T14:15:38.856Z" }, - { url = "https://files.pythonhosted.org/packages/f1/7d/168b5b822bccd88142e0a3ce985858fea612404edd228698f5af691020c9/multidict-6.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d2b9712211b860d123815a80b859075d86a4d54787e247d7fbee9db6832cf1c", size = 40183, upload-time = "2025-05-19T14:15:40.197Z" }, - { url = "https://files.pythonhosted.org/packages/e0/b7/d4b8d98eb850ef28a4922ba508c31d90715fd9b9da3801a30cea2967130b/multidict-6.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d2fa86af59f8fc1972e121ade052145f6da22758f6996a197d69bb52f8204e7e", size = 39592, upload-time = "2025-05-19T14:15:41.508Z" }, - { url = "https://files.pythonhosted.org/packages/18/28/a554678898a19583548e742080cf55d169733baf57efc48c2f0273a08583/multidict-6.4.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50855d03e9e4d66eab6947ba688ffb714616f985838077bc4b490e769e48da51", size = 226071, upload-time = "2025-05-19T14:15:42.877Z" }, - { url = "https://files.pythonhosted.org/packages/ee/dc/7ba6c789d05c310e294f85329efac1bf5b450338d2542498db1491a264df/multidict-6.4.4-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5bce06b83be23225be1905dcdb6b789064fae92499fbc458f59a8c0e68718601", size = 222597, upload-time = "2025-05-19T14:15:44.412Z" }, - { url = "https://files.pythonhosted.org/packages/24/4f/34eadbbf401b03768dba439be0fb94b0d187facae9142821a3d5599ccb3b/multidict-6.4.4-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66ed0731f8e5dfd8369a883b6e564aca085fb9289aacabd9decd70568b9a30de", size = 228253, upload-time = "2025-05-19T14:15:46.474Z" }, - { url = "https://files.pythonhosted.org/packages/c0/e6/493225a3cdb0d8d80d43a94503fc313536a07dae54a3f030d279e629a2bc/multidict-6.4.4-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:329ae97fc2f56f44d91bc47fe0972b1f52d21c4b7a2ac97040da02577e2daca2", size = 226146, upload-time = "2025-05-19T14:15:48.003Z" }, - { url = "https://files.pythonhosted.org/packages/2f/70/e411a7254dc3bff6f7e6e004303b1b0591358e9f0b7c08639941e0de8bd6/multidict-6.4.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c27e5dcf520923d6474d98b96749e6805f7677e93aaaf62656005b8643f907ab", size = 220585, upload-time = "2025-05-19T14:15:49.546Z" }, - { url = "https://files.pythonhosted.org/packages/08/8f/beb3ae7406a619100d2b1fb0022c3bb55a8225ab53c5663648ba50dfcd56/multidict-6.4.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:058cc59b9e9b143cc56715e59e22941a5d868c322242278d28123a5d09cdf6b0", size = 212080, upload-time = "2025-05-19T14:15:51.151Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ec/355124e9d3d01cf8edb072fd14947220f357e1c5bc79c88dff89297e9342/multidict-6.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:69133376bc9a03f8c47343d33f91f74a99c339e8b58cea90433d8e24bb298031", size = 226558, upload-time = "2025-05-19T14:15:52.665Z" }, - { url = "https://files.pythonhosted.org/packages/fd/22/d2b95cbebbc2ada3be3812ea9287dcc9712d7f1a012fad041770afddb2ad/multidict-6.4.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d6b15c55721b1b115c5ba178c77104123745b1417527ad9641a4c5e2047450f0", size = 212168, upload-time = "2025-05-19T14:15:55.279Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c5/62bfc0b2f9ce88326dbe7179f9824a939c6c7775b23b95de777267b9725c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a887b77f51d3d41e6e1a63cf3bc7ddf24de5939d9ff69441387dfefa58ac2e26", size = 217970, upload-time = "2025-05-19T14:15:56.806Z" }, - { url = "https://files.pythonhosted.org/packages/79/74/977cea1aadc43ff1c75d23bd5bc4768a8fac98c14e5878d6ee8d6bab743c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:632a3bf8f1787f7ef7d3c2f68a7bde5be2f702906f8b5842ad6da9d974d0aab3", size = 226980, upload-time = "2025-05-19T14:15:58.313Z" }, - { url = "https://files.pythonhosted.org/packages/48/fc/cc4a1a2049df2eb84006607dc428ff237af38e0fcecfdb8a29ca47b1566c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a145c550900deb7540973c5cdb183b0d24bed6b80bf7bddf33ed8f569082535e", size = 220641, upload-time = "2025-05-19T14:15:59.866Z" }, - { url = "https://files.pythonhosted.org/packages/3b/6a/a7444d113ab918701988d4abdde373dbdfd2def7bd647207e2bf645c7eac/multidict-6.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc5d83c6619ca5c9672cb78b39ed8542f1975a803dee2cda114ff73cbb076edd", size = 221728, upload-time = "2025-05-19T14:16:01.535Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b0/fdf4c73ad1c55e0f4dbbf2aa59dd37037334091f9a4961646d2b7ac91a86/multidict-6.4.4-cp313-cp313t-win32.whl", hash = "sha256:3312f63261b9df49be9d57aaa6abf53a6ad96d93b24f9cc16cf979956355ce6e", size = 41913, upload-time = "2025-05-19T14:16:03.199Z" }, - { url = "https://files.pythonhosted.org/packages/8e/92/27989ecca97e542c0d01d05a98a5ae12198a243a9ee12563a0313291511f/multidict-6.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:ba852168d814b2c73333073e1c7116d9395bea69575a01b0b3c89d2d5a87c8fb", size = 46112, upload-time = "2025-05-19T14:16:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/84/5d/e17845bb0fa76334477d5de38654d27946d5b5d3695443987a094a71b440/multidict-6.4.4-py3-none-any.whl", hash = "sha256:bd4557071b561a8b3b6075c3ce93cf9bfb6182cb241805c3d66ced3b75eff4ac", size = 10481, upload-time = "2025-05-19T14:16:36.024Z" }, +version = "6.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/2c/e367dfb4c6538614a0c9453e510d75d66099edf1c4e69da1b5ce691a1931/multidict-6.4.3.tar.gz", hash = "sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec", size = 89372, upload-time = "2025-04-10T22:20:17.956Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/bb/3abdaf8fe40e9226ce8a2ba5ecf332461f7beec478a455d6587159f1bf92/multidict-6.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f1c2f58f08b36f8475f3ec6f5aeb95270921d418bf18f90dffd6be5c7b0e676", size = 64019, upload-time = "2025-04-10T22:18:23.174Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b5/1b2e8de8217d2e89db156625aa0fe4a6faad98972bfe07a7b8c10ef5dd6b/multidict-6.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:26ae9ad364fc61b936fb7bf4c9d8bd53f3a5b4417142cd0be5c509d6f767e2f1", size = 37925, upload-time = "2025-04-10T22:18:24.834Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e2/3ca91c112644a395c8eae017144c907d173ea910c913ff8b62549dcf0bbf/multidict-6.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:659318c6c8a85f6ecfc06b4e57529e5a78dfdd697260cc81f683492ad7e9435a", size = 37008, upload-time = "2025-04-10T22:18:26.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/23/79bc78146c7ac8d1ac766b2770ca2e07c2816058b8a3d5da6caed8148637/multidict-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1eb72c741fd24d5a28242ce72bb61bc91f8451877131fa3fe930edb195f7054", size = 224374, upload-time = "2025-04-10T22:18:27.714Z" }, + { url = "https://files.pythonhosted.org/packages/86/35/77950ed9ebd09136003a85c1926ba42001ca5be14feb49710e4334ee199b/multidict-6.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3cd06d88cb7398252284ee75c8db8e680aa0d321451132d0dba12bc995f0adcc", size = 230869, upload-time = "2025-04-10T22:18:29.162Z" }, + { url = "https://files.pythonhosted.org/packages/49/97/2a33c6e7d90bc116c636c14b2abab93d6521c0c052d24bfcc231cbf7f0e7/multidict-6.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4543d8dc6470a82fde92b035a92529317191ce993533c3c0c68f56811164ed07", size = 231949, upload-time = "2025-04-10T22:18:30.679Z" }, + { url = "https://files.pythonhosted.org/packages/56/ce/e9b5d9fcf854f61d6686ada7ff64893a7a5523b2a07da6f1265eaaea5151/multidict-6.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a3ebdc068c27e9d6081fca0e2c33fdf132ecea703a72ea216b81a66860adde", size = 231032, upload-time = "2025-04-10T22:18:32.146Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ac/7ced59dcdfeddd03e601edb05adff0c66d81ed4a5160c443e44f2379eef0/multidict-6.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b038f10e23f277153f86f95c777ba1958bcd5993194fda26a1d06fae98b2f00c", size = 223517, upload-time = "2025-04-10T22:18:33.538Z" }, + { url = "https://files.pythonhosted.org/packages/db/e6/325ed9055ae4e085315193a1b58bdb4d7fc38ffcc1f4975cfca97d015e17/multidict-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c605a2b2dc14282b580454b9b5d14ebe0668381a3a26d0ac39daa0ca115eb2ae", size = 216291, upload-time = "2025-04-10T22:18:34.962Z" }, + { url = "https://files.pythonhosted.org/packages/fa/84/eeee6d477dd9dcb7691c3bb9d08df56017f5dd15c730bcc9383dcf201cf4/multidict-6.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bd2b875f4ca2bb527fe23e318ddd509b7df163407b0fb717df229041c6df5d3", size = 228982, upload-time = "2025-04-10T22:18:36.443Z" }, + { url = "https://files.pythonhosted.org/packages/82/94/4d1f3e74e7acf8b0c85db350e012dcc61701cd6668bc2440bb1ecb423c90/multidict-6.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c2e98c840c9c8e65c0e04b40c6c5066c8632678cd50c8721fdbcd2e09f21a507", size = 226823, upload-time = "2025-04-10T22:18:37.924Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/1e54b95bda7cd01080e5732f9abb7b76ab5cc795b66605877caeb2197476/multidict-6.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66eb80dd0ab36dbd559635e62fba3083a48a252633164857a1d1684f14326427", size = 222714, upload-time = "2025-04-10T22:18:39.807Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a2/f6cbca875195bd65a3e53b37ab46486f3cc125bdeab20eefe5042afa31fb/multidict-6.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c23831bdee0a2a3cf21be057b5e5326292f60472fb6c6f86392bbf0de70ba731", size = 233739, upload-time = "2025-04-10T22:18:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/79/68/9891f4d2b8569554723ddd6154375295f789dc65809826c6fb96a06314fd/multidict-6.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1535cec6443bfd80d028052e9d17ba6ff8a5a3534c51d285ba56c18af97e9713", size = 230809, upload-time = "2025-04-10T22:18:42.817Z" }, + { url = "https://files.pythonhosted.org/packages/e6/72/a7be29ba1e87e4fc5ceb44dabc7940b8005fd2436a332a23547709315f70/multidict-6.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b73e7227681f85d19dec46e5b881827cd354aabe46049e1a61d2f9aaa4e285a", size = 226934, upload-time = "2025-04-10T22:18:44.311Z" }, + { url = "https://files.pythonhosted.org/packages/12/c1/259386a9ad6840ff7afc686da96808b503d152ac4feb3a96c651dc4f5abf/multidict-6.4.3-cp312-cp312-win32.whl", hash = "sha256:8eac0c49df91b88bf91f818e0a24c1c46f3622978e2c27035bfdca98e0e18124", size = 35242, upload-time = "2025-04-10T22:18:46.193Z" }, + { url = "https://files.pythonhosted.org/packages/06/24/c8fdff4f924d37225dc0c56a28b1dca10728fc2233065fafeb27b4b125be/multidict-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:11990b5c757d956cd1db7cb140be50a63216af32cd6506329c2c59d732d802db", size = 38635, upload-time = "2025-04-10T22:18:47.498Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4b/86fd786d03915c6f49998cf10cd5fe6b6ac9e9a071cb40885d2e080fb90d/multidict-6.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a76534263d03ae0cfa721fea40fd2b5b9d17a6f85e98025931d41dc49504474", size = 63831, upload-time = "2025-04-10T22:18:48.748Z" }, + { url = "https://files.pythonhosted.org/packages/45/05/9b51fdf7aef2563340a93be0a663acba2c428c4daeaf3960d92d53a4a930/multidict-6.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:805031c2f599eee62ac579843555ed1ce389ae00c7e9f74c2a1b45e0564a88dd", size = 37888, upload-time = "2025-04-10T22:18:50.021Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/53fc25394386c911822419b522181227ca450cf57fea76e6188772a1bd91/multidict-6.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c56c179839d5dcf51d565132185409d1d5dd8e614ba501eb79023a6cab25576b", size = 36852, upload-time = "2025-04-10T22:18:51.246Z" }, + { url = "https://files.pythonhosted.org/packages/8a/68/7b99c751e822467c94a235b810a2fd4047d4ecb91caef6b5c60116991c4b/multidict-6.4.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c64f4ddb3886dd8ab71b68a7431ad4aa01a8fa5be5b11543b29674f29ca0ba3", size = 223644, upload-time = "2025-04-10T22:18:52.965Z" }, + { url = "https://files.pythonhosted.org/packages/80/1b/d458d791e4dd0f7e92596667784fbf99e5c8ba040affe1ca04f06b93ae92/multidict-6.4.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3002a856367c0b41cad6784f5b8d3ab008eda194ed7864aaa58f65312e2abcac", size = 230446, upload-time = "2025-04-10T22:18:54.509Z" }, + { url = "https://files.pythonhosted.org/packages/e2/46/9793378d988905491a7806d8987862dc5a0bae8a622dd896c4008c7b226b/multidict-6.4.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d75e621e7d887d539d6e1d789f0c64271c250276c333480a9e1de089611f790", size = 231070, upload-time = "2025-04-10T22:18:56.019Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b8/b127d3e1f8dd2a5bf286b47b24567ae6363017292dc6dec44656e6246498/multidict-6.4.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:995015cf4a3c0d72cbf453b10a999b92c5629eaf3a0c3e1efb4b5c1f602253bb", size = 229956, upload-time = "2025-04-10T22:18:59.146Z" }, + { url = "https://files.pythonhosted.org/packages/0c/93/f70a4c35b103fcfe1443059a2bb7f66e5c35f2aea7804105ff214f566009/multidict-6.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b0fabae7939d09d7d16a711468c385272fa1b9b7fb0d37e51143585d8e72e0", size = 222599, upload-time = "2025-04-10T22:19:00.657Z" }, + { url = "https://files.pythonhosted.org/packages/63/8c/e28e0eb2fe34921d6aa32bfc4ac75b09570b4d6818cc95d25499fe08dc1d/multidict-6.4.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ed4d82f8a1e67eb9eb04f8587970d78fe7cddb4e4d6230b77eda23d27938f9", size = 216136, upload-time = "2025-04-10T22:19:02.244Z" }, + { url = "https://files.pythonhosted.org/packages/72/f5/fbc81f866585b05f89f99d108be5d6ad170e3b6c4d0723d1a2f6ba5fa918/multidict-6.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:062428944a8dc69df9fdc5d5fc6279421e5f9c75a9ee3f586f274ba7b05ab3c8", size = 228139, upload-time = "2025-04-10T22:19:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ba/7d196bad6b85af2307d81f6979c36ed9665f49626f66d883d6c64d156f78/multidict-6.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b90e27b4674e6c405ad6c64e515a505c6d113b832df52fdacb6b1ffd1fa9a1d1", size = 226251, upload-time = "2025-04-10T22:19:06.117Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e2/fae46a370dce79d08b672422a33df721ec8b80105e0ea8d87215ff6b090d/multidict-6.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7d50d4abf6729921e9613d98344b74241572b751c6b37feed75fb0c37bd5a817", size = 221868, upload-time = "2025-04-10T22:19:07.981Z" }, + { url = "https://files.pythonhosted.org/packages/26/20/bbc9a3dec19d5492f54a167f08546656e7aef75d181d3d82541463450e88/multidict-6.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:43fe10524fb0a0514be3954be53258e61d87341008ce4914f8e8b92bee6f875d", size = 233106, upload-time = "2025-04-10T22:19:09.5Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8d/f30ae8f5ff7a2461177f4d8eb0d8f69f27fb6cfe276b54ec4fd5a282d918/multidict-6.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:236966ca6c472ea4e2d3f02f6673ebfd36ba3f23159c323f5a496869bc8e47c9", size = 230163, upload-time = "2025-04-10T22:19:11Z" }, + { url = "https://files.pythonhosted.org/packages/15/e9/2833f3c218d3c2179f3093f766940ded6b81a49d2e2f9c46ab240d23dfec/multidict-6.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:422a5ec315018e606473ba1f5431e064cf8b2a7468019233dcf8082fabad64c8", size = 225906, upload-time = "2025-04-10T22:19:12.875Z" }, + { url = "https://files.pythonhosted.org/packages/f1/31/6edab296ac369fd286b845fa5dd4c409e63bc4655ed8c9510fcb477e9ae9/multidict-6.4.3-cp313-cp313-win32.whl", hash = "sha256:f901a5aace8e8c25d78960dcc24c870c8d356660d3b49b93a78bf38eb682aac3", size = 35238, upload-time = "2025-04-10T22:19:14.41Z" }, + { url = "https://files.pythonhosted.org/packages/23/57/2c0167a1bffa30d9a1383c3dab99d8caae985defc8636934b5668830d2ef/multidict-6.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1c152c49e42277bc9a2f7b78bd5fa10b13e88d1b0328221e7aef89d5c60a99a5", size = 38799, upload-time = "2025-04-10T22:19:15.869Z" }, + { url = "https://files.pythonhosted.org/packages/c9/13/2ead63b9ab0d2b3080819268acb297bd66e238070aa8d42af12b08cbee1c/multidict-6.4.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be8751869e28b9c0d368d94f5afcb4234db66fe8496144547b4b6d6a0645cfc6", size = 68642, upload-time = "2025-04-10T22:19:17.527Z" }, + { url = "https://files.pythonhosted.org/packages/85/45/f1a751e1eede30c23951e2ae274ce8fad738e8a3d5714be73e0a41b27b16/multidict-6.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d4b31f8a68dccbcd2c0ea04f0e014f1defc6b78f0eb8b35f2265e8716a6df0c", size = 40028, upload-time = "2025-04-10T22:19:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/a7/29/fcc53e886a2cc5595cc4560df333cb9630257bda65003a7eb4e4e0d8f9c1/multidict-6.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:032efeab3049e37eef2ff91271884303becc9e54d740b492a93b7e7266e23756", size = 39424, upload-time = "2025-04-10T22:19:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f0/056c81119d8b88703971f937b371795cab1407cd3c751482de5bfe1a04a9/multidict-6.4.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e78006af1a7c8a8007e4f56629d7252668344442f66982368ac06522445e375", size = 226178, upload-time = "2025-04-10T22:19:22.17Z" }, + { url = "https://files.pythonhosted.org/packages/a3/79/3b7e5fea0aa80583d3a69c9d98b7913dfd4fbc341fb10bb2fb48d35a9c21/multidict-6.4.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:daeac9dd30cda8703c417e4fddccd7c4dc0c73421a0b54a7da2713be125846be", size = 222617, upload-time = "2025-04-10T22:19:23.773Z" }, + { url = "https://files.pythonhosted.org/packages/06/db/3ed012b163e376fc461e1d6a67de69b408339bc31dc83d39ae9ec3bf9578/multidict-6.4.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f6f90700881438953eae443a9c6f8a509808bc3b185246992c4233ccee37fea", size = 227919, upload-time = "2025-04-10T22:19:25.35Z" }, + { url = "https://files.pythonhosted.org/packages/b1/db/0433c104bca380989bc04d3b841fc83e95ce0c89f680e9ea4251118b52b6/multidict-6.4.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f84627997008390dd15762128dcf73c3365f4ec0106739cde6c20a07ed198ec8", size = 226097, upload-time = "2025-04-10T22:19:27.183Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/910db2618175724dd254b7ae635b6cd8d2947a8b76b0376de7b96d814dab/multidict-6.4.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3307b48cd156153b117c0ea54890a3bdbf858a5b296ddd40dc3852e5f16e9b02", size = 220706, upload-time = "2025-04-10T22:19:28.882Z" }, + { url = "https://files.pythonhosted.org/packages/d1/af/aa176c6f5f1d901aac957d5258d5e22897fe13948d1e69063ae3d5d0ca01/multidict-6.4.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead46b0fa1dcf5af503a46e9f1c2e80b5d95c6011526352fa5f42ea201526124", size = 211728, upload-time = "2025-04-10T22:19:30.481Z" }, + { url = "https://files.pythonhosted.org/packages/e7/42/d51cc5fc1527c3717d7f85137d6c79bb7a93cd214c26f1fc57523774dbb5/multidict-6.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1748cb2743bedc339d63eb1bca314061568793acd603a6e37b09a326334c9f44", size = 226276, upload-time = "2025-04-10T22:19:32.454Z" }, + { url = "https://files.pythonhosted.org/packages/28/6b/d836dea45e0b8432343ba4acf9a8ecaa245da4c0960fb7ab45088a5e568a/multidict-6.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:acc9fa606f76fc111b4569348cc23a771cb52c61516dcc6bcef46d612edb483b", size = 212069, upload-time = "2025-04-10T22:19:34.17Z" }, + { url = "https://files.pythonhosted.org/packages/55/34/0ee1a7adb3560e18ee9289c6e5f7db54edc312b13e5c8263e88ea373d12c/multidict-6.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:31469d5832b5885adeb70982e531ce86f8c992334edd2f2254a10fa3182ac504", size = 217858, upload-time = "2025-04-10T22:19:35.879Z" }, + { url = "https://files.pythonhosted.org/packages/04/08/586d652c2f5acefe0cf4e658eedb4d71d4ba6dfd4f189bd81b400fc1bc6b/multidict-6.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ba46b51b6e51b4ef7bfb84b82f5db0dc5e300fb222a8a13b8cd4111898a869cf", size = 226988, upload-time = "2025-04-10T22:19:37.434Z" }, + { url = "https://files.pythonhosted.org/packages/82/e3/cc59c7e2bc49d7f906fb4ffb6d9c3a3cf21b9f2dd9c96d05bef89c2b1fd1/multidict-6.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:389cfefb599edf3fcfd5f64c0410da686f90f5f5e2c4d84e14f6797a5a337af4", size = 220435, upload-time = "2025-04-10T22:19:39.005Z" }, + { url = "https://files.pythonhosted.org/packages/e0/32/5c3a556118aca9981d883f38c4b1bfae646f3627157f70f4068e5a648955/multidict-6.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:64bc2bbc5fba7b9db5c2c8d750824f41c6994e3882e6d73c903c2afa78d091e4", size = 221494, upload-time = "2025-04-10T22:19:41.447Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3b/1599631f59024b75c4d6e3069f4502409970a336647502aaf6b62fb7ac98/multidict-6.4.3-cp313-cp313t-win32.whl", hash = "sha256:0ecdc12ea44bab2807d6b4a7e5eef25109ab1c82a8240d86d3c1fc9f3b72efd5", size = 41775, upload-time = "2025-04-10T22:19:43.707Z" }, + { url = "https://files.pythonhosted.org/packages/e8/4e/09301668d675d02ca8e8e1a3e6be046619e30403f5ada2ed5b080ae28d02/multidict-6.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7146a8742ea71b5d7d955bffcef58a9e6e04efba704b52a460134fefd10a8208", size = 45946, upload-time = "2025-04-10T22:19:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/96/10/7d526c8974f017f1e7ca584c71ee62a638e9334d8d33f27d7cdfc9ae79e4/multidict-6.4.3-py3-none-any.whl", hash = "sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9", size = 10400, upload-time = "2025-04-10T22:20:16.445Z" }, ] [[package]] @@ -1182,19 +1143,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/17/3493c5624e48fd97156ebaec380dcaafee9506d7e2c46218ceebbb57d7de/pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3", size = 19467, upload-time = "2025-01-28T18:37:56.798Z" }, ] -[[package]] -name = "pytest-cov" -version = "6.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1285,11 +1233,11 @@ wheels = [ [[package]] name = "redis" -version = "6.1.0" +version = "6.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/af/e875d57383653e5d9065df8552de1deb7576b4d3cf3af90cde2e79ff7f65/redis-6.1.0.tar.gz", hash = "sha256:c928e267ad69d3069af28a9823a07726edf72c7e37764f43dc0123f37928c075", size = 4629300, upload-time = "2025-05-13T12:16:57.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/12/dffaaa4374b8d5f3b7ff5c40025c9db387e06264302d5a9da6043cd84e1f/redis-6.0.0.tar.gz", hash = "sha256:5446780d2425b787ed89c91ddbfa1be6d32370a636c8fdb687f11b1c26c1fa88", size = 4620969, upload-time = "2025-04-30T19:09:30.798Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/5f/cf36360f80ae233bd1836442f5127818cfcfc7b1846179b60b2e9a4c45c9/redis-6.1.0-py3-none-any.whl", hash = "sha256:3b72622f3d3a89df2a6041e82acd896b0e67d9f54e9bcd906d091d23ba5219f6", size = 273750, upload-time = "2025-05-13T12:16:55.661Z" }, + { url = "https://files.pythonhosted.org/packages/08/c8/68081c9d3531f7b2a4d663326b96a9dcbc2aef47df3c6b5c38dea90dff02/redis-6.0.0-py3-none-any.whl", hash = "sha256:a2e040aee2cdd947be1fa3a32e35a956cd839cc4c1dbbe4b2cdee5b9623fd27c", size = 268950, upload-time = "2025-04-30T19:09:28.432Z" }, ] [[package]] @@ -1859,44 +1807,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, ] -[[package]] -name = "xxhash" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241, upload-time = "2024-08-17T09:20:38.972Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969, upload-time = "2024-08-17T09:18:24.025Z" }, - { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787, upload-time = "2024-08-17T09:18:25.318Z" }, - { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959, upload-time = "2024-08-17T09:18:26.518Z" }, - { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006, upload-time = "2024-08-17T09:18:27.905Z" }, - { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326, upload-time = "2024-08-17T09:18:29.335Z" }, - { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380, upload-time = "2024-08-17T09:18:30.706Z" }, - { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934, upload-time = "2024-08-17T09:18:32.133Z" }, - { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301, upload-time = "2024-08-17T09:18:33.474Z" }, - { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351, upload-time = "2024-08-17T09:18:34.889Z" }, - { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294, upload-time = "2024-08-17T09:18:36.355Z" }, - { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674, upload-time = "2024-08-17T09:18:38.536Z" }, - { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022, upload-time = "2024-08-17T09:18:40.138Z" }, - { url = "https://files.pythonhosted.org/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170, upload-time = "2024-08-17T09:18:42.163Z" }, - { url = "https://files.pythonhosted.org/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040, upload-time = "2024-08-17T09:18:43.699Z" }, - { url = "https://files.pythonhosted.org/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796, upload-time = "2024-08-17T09:18:45.29Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795, upload-time = "2024-08-17T09:18:46.813Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792, upload-time = "2024-08-17T09:18:47.862Z" }, - { url = "https://files.pythonhosted.org/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950, upload-time = "2024-08-17T09:18:49.06Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980, upload-time = "2024-08-17T09:18:50.445Z" }, - { url = "https://files.pythonhosted.org/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324, upload-time = "2024-08-17T09:18:51.988Z" }, - { url = "https://files.pythonhosted.org/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370, upload-time = "2024-08-17T09:18:54.164Z" }, - { url = "https://files.pythonhosted.org/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911, upload-time = "2024-08-17T09:18:55.509Z" }, - { url = "https://files.pythonhosted.org/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352, upload-time = "2024-08-17T09:18:57.073Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410, upload-time = "2024-08-17T09:18:58.54Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322, upload-time = "2024-08-17T09:18:59.943Z" }, - { url = "https://files.pythonhosted.org/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725, upload-time = "2024-08-17T09:19:01.332Z" }, - { url = "https://files.pythonhosted.org/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070, upload-time = "2024-08-17T09:19:03.007Z" }, - { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172, upload-time = "2024-08-17T09:19:04.355Z" }, - { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041, upload-time = "2024-08-17T09:19:05.435Z" }, - { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801, upload-time = "2024-08-17T09:19:06.547Z" }, -] - [[package]] name = "yarl" version = "1.20.0" From c1fa817b8a0b300e5652a4da0037264442306876 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 06:35:33 +0000 Subject: [PATCH 06/44] [Feat] Added vllm command composing and execute logic in the background script due to kuberay operator args concatenation. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 119 +++++++++++++++++- ...-15-minimal-pipeline-parallel-example.yaml | 8 +- 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index fef662cf3..f4cc55836 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -27,6 +27,13 @@ spec: containers: - name: vllm-ray-head image: "{{ required "Required value 'modelSpec.repository' must be defined !" $modelSpec.repository }}:{{ required "Required value 'modelSpec.tag' must be defined !" $modelSpec.tag }}" + command: + - >- + /bin/bash -c " + cp /entrypoint/vllm-entrypoint.sh \$HOME/vllm-entrypoint.sh && + chmod +x \$HOME/vllm-entrypoint.sh && + \$HOME/vllm-entrypoint.sh & + echo \"Running vllm command in the background.\"" livenessProbe: exec: command: ["/bin/bash", "-c", "echo TBD"] @@ -45,11 +52,15 @@ spec: volumeMounts: - name: wait-script mountPath: /scripts - readOnly: true + - name: vllm-script + mountPath: /entrypoint volumes: - name: wait-script configMap: name: wait-for-ray-script + - name: vllm-script + configMap: + name: vllm-start-script workerGroupSpecs: - rayStartParams: {} replicas: {{ $modelSpec.replicaCount }} @@ -107,6 +118,112 @@ data: logging.info("Cluster is NOT ready.") sys.exit(1) --- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vllm-start-script +data: + vllm-entrypoint.sh: | + #!/bin/bash + set -e + + echo "Waiting for Ray to become available..." + until python3 /scripts/wait_for_ray.py; do + echo "Ray not ready yet. Retrying in 2 seconds..." + sleep 2 + done + + echo "Ray is ready. Starting vLLM..." + + # Start constructing command + ARGS=( + "vllm" + "serve" + "{{ $modelSpec.modelURL | quote }}" + "--host" "0.0.0.0" + "--port" "{{ include "chart.container-port" . }}" + "--distributed-executor-backend" "ray" + ) + + {{- if $modelSpec.enableLoRA }} + ARGS+=("--enable-lora") + {{- end }} + + {{- if $modelSpec.enableTool }} + ARGS+=("--enable-auto-tool-choice") + {{- end }} + + {{- if $modelSpec.toolCallParser }} + ARGS+=("--tool-call-parser" {{ $modelSpec.toolCallParser | quote }}) + {{- end }} + + {{- with $modelSpec.vllmConfig }} + {{- if hasKey . "enableChunkedPrefill" }} + {{- if .enableChunkedPrefill }} + ARGS+=("--enable-chunked-prefill") + {{- else }} + ARGS+=("--no-enable-chunked-prefill") + {{- end }} + {{- end }} + + {{- if .enablePrefixCaching }} + ARGS+=("--enable-prefix-caching") + {{- end }} + + {{- if hasKey . "maxModelLen" }} + ARGS+=("--max-model-len" {{ .maxModelLen | quote }}) + {{- end }} + + {{- if hasKey . "dtype" }} + ARGS+=("--dtype" {{ .dtype | quote }}) + {{- end }} + + {{- if hasKey . "tensorParallelSize" }} + ARGS+=("--tensor-parallel-size" {{ .tensorParallelSize | quote }}) + {{- end }} + + {{- if hasKey . "pipelineParallelSize" }} + ARGS+=("--pipeline-parallel-size" {{ .pipelineParallelSize | quote }}) + {{- end }} + + {{- if hasKey . "maxNumSeqs" }} + ARGS+=("--max-num-seqs" {{ .maxNumSeqs | quote }}) + {{- end }} + + {{- if hasKey . "gpuMemoryUtilization" }} + ARGS+=("--gpu-memory-utilization" {{ .gpuMemoryUtilization | quote }}) + {{- end }} + + {{- if hasKey . "maxLoras" }} + ARGS+=("--max-loras" {{ .maxLoras | quote }}) + {{- end }} + + {{- range .extraArgs }} + ARGS+=({{ . | quote }}) + {{- end }} + {{- end }} + + {{- if $modelSpec.lmcacheConfig }} + {{- if $modelSpec.lmcacheConfig.enabled }} + {{- if hasKey $modelSpec.vllmConfig "v1" }} + {{- if eq (toString $modelSpec.vllmConfig.v1) "1" }} + ARGS+=("--kv-transfer-config" "{\"kv_connector\":\"LMCacheConnectorV1\",\"kv_role\":\"kv_both\"}") + {{- else }} + ARGS+=("--kv-transfer-config" "{\"kv_connector\":\"LMCacheConnector\",\"kv_role\":\"kv_both\"}") + {{- end }} + {{- else }} + ARGS+=("--kv-transfer-config" "{\"kv_connector\":\"LMCacheConnector\",\"kv_role\":\"kv_both\"}") + {{- end }} + {{- end }} + {{- end }} + + {{- if $modelSpec.chatTemplate }} + ARGS+=("--chat-template" {{ $modelSpec.chatTemplate | quote }}) + {{- end }} + + echo "Executing: ${ARGS[@]}" + exec "${ARGS[@]}" + {{- if and $modelSpec.chatTemplate (hasKey $modelSpec "chatTemplateConfigMap") }} --- diff --git a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml index 391f921ce..6d06c4aa5 100644 --- a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml +++ b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml @@ -2,13 +2,17 @@ servingEngineSpec: runtimeClassName: "" enableKubeRay: true modelSpec: - - name: "gpt2" + - name: "distilgpt2" repository: "vllm/vllm-openai" tag: "latest" - modelURL: "gpt2" + modelURL: "distilbert/distilgpt2" replicaCount: 2 requestCPU: 6 requestMemory: "16Gi" requestGPU: 1 + + vllmConfig: + tensorParallelSize: 1 + pipelineParallelSize: 2 From e9c23a4c6c70982bc72043c0a9aa3ea87de6afd2 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 12:02:30 +0000 Subject: [PATCH 07/44] [Feat] Added pod relevant settings from servingEngineSpec for both head and worker grouops. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 151 ++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index f4cc55836..6de19d06d 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -7,7 +7,9 @@ metadata: name: "{{ .Release.Name }}-{{$modelSpec.name}}-raycluster-vllm" namespace: {{ .Release.Namespace }} labels: + model: {{ $modelSpec.name }} helm-release-name: {{ .Release.Name }} + {{- include "chart.engineLabels" . | nindent 4 }} spec: headGroupSpec: serviceType: ClusterIP @@ -17,6 +19,7 @@ spec: metadata: labels: model: {{ $modelSpec.name }} + helm-release-name: {{ .Release.Name }} {{- include "chart.engineLabels" . | nindent 10 }} spec: terminationGracePeriodSeconds: 0 @@ -24,6 +27,29 @@ spec: securityContext: {{- toYaml .Values.servingEngineSpec.securityContext | nindent 10 }} {{- end }} + {{- if hasKey $modelSpec "initContainer" }} + {{- $container := $modelSpec.initContainer }} + initContainers: + - name: {{ $container.name }} + image: {{ $container.image }} + {{- if $container.command }} + command: {{ toYaml $container.command | nindent 12 }} + {{- end }} + {{- if $container.args }} + args: {{ toYaml $container.args | nindent 12 }} + {{- end }} + {{- if $container.env }} + env: {{ toYaml $container.env | nindent 12 }} + {{- end }} + {{- if $container.resources }} + resources: {{ toYaml $container.resources | nindent 12 }} + {{- end }} + {{- if and (hasKey $container "mountPvcStorage") ($container.mountPvcStorage) (hasKey $modelSpec "pvcStorage") }} + volumeMounts: + - name: {{ .Release.Name }}-storage + mountPath: /data + {{- end }} + {{- end }} containers: - name: vllm-ray-head image: "{{ required "Required value 'modelSpec.repository' must be defined !" $modelSpec.repository }}:{{ required "Required value 'modelSpec.tag' must be defined !" $modelSpec.tag }}" @@ -61,11 +87,72 @@ spec: - name: vllm-script configMap: name: vllm-start-script + {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumes") }} + {{- if hasKey $modelSpec "pvcStorage" }} + - name: {{ .Release.Name }}-storage + persistentVolumeClaim: + claimName: "{{ .Release.Name }}-{{$modelSpec.name}}-storage-claim" + {{- end }} + {{- with $modelSpec.vllmConfig }} + {{- if hasKey $modelSpec.vllmConfig "tensorParallelSize"}} + - name: shm + emptyDir: + medium: Memory + sizeLimit: {{ default "20Gi" $modelSpec.shmSize }} + {{- end}} + {{- end}} + {{- if $modelSpec.chatTemplate}} + {{- if hasKey $modelSpec "chatTemplateConfigMap" }} + - name: {{ .Release.Name }}-chat-templates + configMap: + name: "{{ .Release.Name }}-{{$modelSpec.name}}-chat-templates" + {{- else }} + - name: vllm-templates + persistentVolumeClaim: + claimName: vllm-templates-pvc + {{- end }} + {{- end}} + {{- if hasKey $modelSpec "extraVolumes" }} + {{- toYaml $modelSpec.extraVolumes | nindent 8 }} + {{- end}} + {{- end}} + {{- if $modelSpec.imagePullSecret }} + imagePullSecrets: + - name: {{ $modelSpec.imagePullSecret }} + {{- end }} + {{- if .Values.servingEngineSpec.tolerations }} + {{- with .Values.servingEngineSpec.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + + {{- if .Values.servingEngineSpec.runtimeClassName }} + runtimeClassName: {{ .Values.servingEngineSpec.runtimeClassName }} + {{- end }} + {{- if .Values.servingEngineSpec.schedulerName }} + schedulerName: {{ .Values.servingEngineSpec.schedulerName }} + {{- end }} + {{- if $modelSpec.nodeName }} + nodeName: {{ $modelSpec.nodeName }} + {{- else if $modelSpec.nodeSelectorTerms}} + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + {{- with $modelSpec.nodeSelectorTerms }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} workerGroupSpecs: - rayStartParams: {} replicas: {{ $modelSpec.replicaCount }} groupName: vllm-ray-worker template: + metadata: + labels: + model: {{ $modelSpec.name }} + helm-release-name: {{ .Release.Name }} {{- if .Values.servingEngineSpec.securityContext }} securityContext: {{- toYaml .Values.servingEngineSpec.securityContext | nindent 8 }} @@ -83,6 +170,70 @@ spec: readinessProbe: exec: command: ["/bin/bash", "-c", "echo TBD"] + volumes: + - name: wait-script + configMap: + name: wait-for-ray-script + - name: vllm-script + configMap: + name: vllm-start-script + {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumes") }} + {{- if hasKey $modelSpec "pvcStorage" }} + - name: {{ .Release.Name }}-storage + persistentVolumeClaim: + claimName: "{{ .Release.Name }}-{{$modelSpec.name}}-storage-claim" + {{- end }} + {{- with $modelSpec.vllmConfig }} + {{- if hasKey $modelSpec.vllmConfig "tensorParallelSize"}} + - name: shm + emptyDir: + medium: Memory + sizeLimit: {{ default "20Gi" $modelSpec.shmSize }} + {{- end}} + {{- end}} + {{- if $modelSpec.chatTemplate}} + {{- if hasKey $modelSpec "chatTemplateConfigMap" }} + - name: {{ .Release.Name }}-chat-templates + configMap: + name: "{{ .Release.Name }}-{{$modelSpec.name}}-chat-templates" + {{- else }} + - name: vllm-templates + persistentVolumeClaim: + claimName: vllm-templates-pvc + {{- end }} + {{- end}} + {{- if hasKey $modelSpec "extraVolumes" }} + {{- toYaml $modelSpec.extraVolumes | nindent 8 }} + {{- end}} + {{- end}} + {{- if $modelSpec.imagePullSecret }} + imagePullSecrets: + - name: {{ $modelSpec.imagePullSecret }} + {{- end }} + {{- if .Values.servingEngineSpec.tolerations }} + {{- with .Values.servingEngineSpec.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + + {{- if .Values.servingEngineSpec.runtimeClassName }} + runtimeClassName: {{ .Values.servingEngineSpec.runtimeClassName }} + {{- end }} + {{- if .Values.servingEngineSpec.schedulerName }} + schedulerName: {{ .Values.servingEngineSpec.schedulerName }} + {{- end }} + {{- if $modelSpec.nodeName }} + nodeName: {{ $modelSpec.nodeName }} + {{- else if $modelSpec.nodeSelectorTerms}} + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + {{- with $modelSpec.nodeSelectorTerms }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} --- apiVersion: v1 From b88fa17f75208a5bd02f4f1138d03a9d83f5656f Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 12:31:38 +0000 Subject: [PATCH 08/44] [Feat] Added env templates for head and worker spec. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 183 ++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index 6de19d06d..001a3f0af 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -69,6 +69,97 @@ spec: env: - name: EXPECTED_NODES value: "{{ add $modelSpec.replicaCount 1}}" + - name: HF_HOME + {{- if hasKey $modelSpec "pvcStorage" }} + value: /data + {{- else }} + value: /tmp + {{- end }} + {{- with $modelSpec.vllmConfig}} + - name: LMCACHE_LOG_LEVEL + value: "DEBUG" + {{- if hasKey . "v1" }} + - name: VLLM_USE_V1 + value: {{ $modelSpec.vllmConfig.v1 | quote }} + {{- else }} + - name: VLLM_USE_V1 + value: "0" + {{- end}} + {{- end}} + {{- if $modelSpec.hf_token }} + - name: HF_TOKEN + {{- if kindIs "string" $modelSpec.hf_token }} + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: hf_token_{{ $modelSpec.name }} + {{- else }} + valueFrom: + secretKeyRef: + name: {{ $modelSpec.hf_token.secretName }} + key: {{ $modelSpec.hf_token.secretKey }} + {{- end }} + {{- end }} + {{- $vllmApiKey := $.Values.servingEngineSpec.vllmApiKey }} + {{- if $vllmApiKey }} + - name: VLLM_API_KEY + {{- if kindIs "string" $vllmApiKey }} + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: vllmApiKey + {{- else }} + valueFrom: + secretKeyRef: + name: {{ $vllmApiKey.secretName }} + key: {{ $vllmApiKey.secretKey }} + {{- end }} + {{- end }} + {{- with $modelSpec.env }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- if $modelSpec.lmcacheConfig }} + {{- if $modelSpec.lmcacheConfig.enabled }} + - name: LMCACHE_USE_EXPERIMENTAL + value: "True" + - name: VLLM_RPC_TIMEOUT + value: "1000000" + {{- end }} + {{- if $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }} + - name: LMCACHE_LOCAL_CPU + value: "True" + - name: LMCACHE_MAX_LOCAL_CPU_SIZE + value: "{{ $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }}" + {{- end }} + {{- if $modelSpec.lmcacheConfig.diskOffloadingBufferSize }} + - name: LMCACHE_LOCAL_DISK + value: "True" + - name: LMCACHE_MAX_LOCAL_DISK_SIZE + value: "{{ $modelSpec.lmcacheConfig.diskOffloadingBufferSize }}" + {{- end }} + {{- if .Values.cacheserverSpec }} + - name: LMCACHE_REMOTE_URL + value: "{{ include "cacheserver.formatRemoteUrl" (dict "service_name" (print .Release.Name "-cache-server-service") "port" .Values.cacheserverSpec.servicePort) }}" + - name: LMCACHE_REMOTE_SERDE + value: "{{ .Values.cacheserverSpec.serde }}" + {{- end }} + {{- if hasKey $modelSpec.lmcacheConfig "enableController" }} + - name: LMCACHE_ENABLE_CONTROLLER + value: {{ ternary "True" "False" $modelSpec.lmcacheConfig.enableController | quote }} + {{- end }} + {{- if hasKey $modelSpec.lmcacheConfig "instanceId" }} + - name: LMCACHE_INSTANCE_ID + value: {{ $modelSpec.lmcacheConfig.instanceId | quote }} + {{- end }} + {{- if hasKey $modelSpec.lmcacheConfig "controllerPort" }} + - name: LMCACHE_CONTROLLER_URL + value: "{{ .Release.Name }}-{{$modelSpec.name}}-service:{{ $modelSpec.lmcacheConfig.controllerPort }}" + {{- end }} + {{- if hasKey $modelSpec.lmcacheConfig "workerPort" }} + - name: LMCACHE_WORKER_PORT + value: "{{ .Release.Name }}-service:{{ $modelSpec.lmcacheConfig.workerPort }}" + {{- end }} + {{- end }} startupProbe: exec: command: ["/bin/bash", "-c", "python3 /scripts/wait_for_ray.py"] @@ -161,6 +252,98 @@ spec: containers: - name: vllm-ray-worker image: "{{ required "Required value 'modelSpec.repository' must be defined !" $modelSpec.repository }}:{{ required "Required value 'modelSpec.tag' must be defined !" $modelSpec.tag }}" + env: + - name: HF_HOME + {{- if hasKey $modelSpec "pvcStorage" }} + value: /data + {{- else }} + value: /tmp + {{- end }} + {{- with $modelSpec.vllmConfig}} + - name: LMCACHE_LOG_LEVEL + value: "DEBUG" + {{- if hasKey . "v1" }} + - name: VLLM_USE_V1 + value: {{ $modelSpec.vllmConfig.v1 | quote }} + {{- else }} + - name: VLLM_USE_V1 + value: "0" + {{- end}} + {{- end}} + {{- if $modelSpec.hf_token }} + - name: HF_TOKEN + {{- if kindIs "string" $modelSpec.hf_token }} + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: hf_token_{{ $modelSpec.name }} + {{- else }} + valueFrom: + secretKeyRef: + name: {{ $modelSpec.hf_token.secretName }} + key: {{ $modelSpec.hf_token.secretKey }} + {{- end }} + {{- end }} + {{- $vllmApiKey := $.Values.servingEngineSpec.vllmApiKey }} + {{- if $vllmApiKey }} + - name: VLLM_API_KEY + {{- if kindIs "string" $vllmApiKey }} + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets + key: vllmApiKey + {{- else }} + valueFrom: + secretKeyRef: + name: {{ $vllmApiKey.secretName }} + key: {{ $vllmApiKey.secretKey }} + {{- end }} + {{- end }} + {{- with $modelSpec.env }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- if $modelSpec.lmcacheConfig }} + {{- if $modelSpec.lmcacheConfig.enabled }} + - name: LMCACHE_USE_EXPERIMENTAL + value: "True" + - name: VLLM_RPC_TIMEOUT + value: "1000000" + {{- end }} + {{- if $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }} + - name: LMCACHE_LOCAL_CPU + value: "True" + - name: LMCACHE_MAX_LOCAL_CPU_SIZE + value: "{{ $modelSpec.lmcacheConfig.cpuOffloadingBufferSize }}" + {{- end }} + {{- if $modelSpec.lmcacheConfig.diskOffloadingBufferSize }} + - name: LMCACHE_LOCAL_DISK + value: "True" + - name: LMCACHE_MAX_LOCAL_DISK_SIZE + value: "{{ $modelSpec.lmcacheConfig.diskOffloadingBufferSize }}" + {{- end }} + {{- if .Values.cacheserverSpec }} + - name: LMCACHE_REMOTE_URL + value: "{{ include "cacheserver.formatRemoteUrl" (dict "service_name" (print .Release.Name "-cache-server-service") "port" .Values.cacheserverSpec.servicePort) }}" + - name: LMCACHE_REMOTE_SERDE + value: "{{ .Values.cacheserverSpec.serde }}" + {{- end }} + {{- if hasKey $modelSpec.lmcacheConfig "enableController" }} + - name: LMCACHE_ENABLE_CONTROLLER + value: {{ ternary "True" "False" $modelSpec.lmcacheConfig.enableController | quote }} + {{- end }} + {{- if hasKey $modelSpec.lmcacheConfig "instanceId" }} + - name: LMCACHE_INSTANCE_ID + value: {{ $modelSpec.lmcacheConfig.instanceId | quote }} + {{- end }} + {{- if hasKey $modelSpec.lmcacheConfig "controllerPort" }} + - name: LMCACHE_CONTROLLER_URL + value: "{{ .Release.Name }}-{{$modelSpec.name}}-service:{{ $modelSpec.lmcacheConfig.controllerPort }}" + {{- end }} + {{- if hasKey $modelSpec.lmcacheConfig "workerPort" }} + - name: LMCACHE_WORKER_PORT + value: "{{ .Release.Name }}-service:{{ $modelSpec.lmcacheConfig.workerPort }}" + {{- end }} + {{- end }} resources: limits: nvidia.com/gpu: {{ .Values.servingEngineSpec.gpuLimit }} From d35cdb47ca6b5a92f9125cb0363dacf483ac2820 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 12:51:46 +0000 Subject: [PATCH 09/44] [Feat] Added volumemounts template for head and worker spec. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 43 +++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index 001a3f0af..c8865793e 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -171,6 +171,25 @@ spec: mountPath: /scripts - name: vllm-script mountPath: /entrypoint + {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumeMounts") }} + {{- if hasKey $modelSpec "pvcStorage" }} + - name: {{ .Release.Name }}-storage + mountPath: /data + {{- end }} + {{- with $modelSpec.vllmConfig }} + {{- if hasKey $modelSpec.vllmConfig "tensorParallelSize"}} + - name: shm + mountPath: /dev/shm + {{- end}} + {{- end}} + {{- if $modelSpec.chatTemplate }} + - name: vllm-templates + mountPath: /templates + {{- end }} + {{- if hasKey $modelSpec "extraVolumeMounts" }} + {{- toYaml $modelSpec.extraVolumeMounts | nindent 14 }} + {{- end }} + {{- end }} volumes: - name: wait-script configMap: @@ -178,7 +197,7 @@ spec: - name: vllm-script configMap: name: vllm-start-script - {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumes") }} + {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumes") }} {{- if hasKey $modelSpec "pvcStorage" }} - name: {{ .Release.Name }}-storage persistentVolumeClaim: @@ -353,6 +372,26 @@ spec: readinessProbe: exec: command: ["/bin/bash", "-c", "echo TBD"] + {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumeMounts") }} + volumeMounts: + {{- end }} + {{- if hasKey $modelSpec "pvcStorage" }} + - name: {{ .Release.Name }}-storage + mountPath: /data + {{- end }} + {{- with $modelSpec.vllmConfig }} + {{- if hasKey $modelSpec.vllmConfig "tensorParallelSize"}} + - name: shm + mountPath: /dev/shm + {{- end}} + {{- end}} + {{- if $modelSpec.chatTemplate }} + - name: vllm-templates + mountPath: /templates + {{- end }} + {{- if hasKey $modelSpec "extraVolumeMounts" }} + {{- toYaml $modelSpec.extraVolumeMounts | nindent 14 }} + {{- end }} volumes: - name: wait-script configMap: @@ -360,7 +399,7 @@ spec: - name: vllm-script configMap: name: vllm-start-script - {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumes") }} + {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumes") }} {{- if hasKey $modelSpec "pvcStorage" }} - name: {{ .Release.Name }}-storage persistentVolumeClaim: From 12ccab73ea6ced4226025400288beb03c4bd6c25 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 13:09:10 +0000 Subject: [PATCH 10/44] [Feat] Adeed templates for resource, probe, port and etc. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 35 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index c8865793e..cfe81517c 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -60,12 +60,6 @@ spec: chmod +x \$HOME/vllm-entrypoint.sh && \$HOME/vllm-entrypoint.sh & echo \"Running vllm command in the background.\"" - livenessProbe: - exec: - command: ["/bin/bash", "-c", "echo TBD"] - readinessProbe: - exec: - command: ["/bin/bash", "-c", "echo TBD"] env: - name: EXPECTED_NODES value: "{{ add $modelSpec.replicaCount 1}}" @@ -160,6 +154,16 @@ spec: value: "{{ .Release.Name }}-service:{{ $modelSpec.lmcacheConfig.workerPort }}" {{- end }} {{- end }} + {{- if .Values.servingEngineSpec.configs }} + envFrom: + - configMapRef: + name: "{{ .Release.Name }}-configs" + {{- end }} + ports: + - name: {{ include "chart.container-port-name" . }} + containerPort: {{ include "chart.container-port" . }} + {{- include "chart.probes" . | indent 12 }} + resources: {{- include "chart.resources" $modelSpec | nindent 14 }} startupProbe: exec: command: ["/bin/bash", "-c", "python3 /scripts/wait_for_ray.py"] @@ -363,15 +367,16 @@ spec: value: "{{ .Release.Name }}-service:{{ $modelSpec.lmcacheConfig.workerPort }}" {{- end }} {{- end }} - resources: - limits: - nvidia.com/gpu: {{ .Values.servingEngineSpec.gpuLimit }} - livenessProbe: - exec: - command: ["/bin/bash", "-c", "echo TBD"] - readinessProbe: - exec: - command: ["/bin/bash", "-c", "echo TBD"] + {{- if .Values.servingEngineSpec.configs }} + envFrom: + - configMapRef: + name: "{{ .Release.Name }}-configs" + {{- end }} + ports: + - name: {{ include "chart.container-port-name" . }} + containerPort: {{ include "chart.container-port" . }} + {{- include "chart.probes" . | indent 14 }} + resources: {{- include "chart.resources" $modelSpec | nindent 16 }} {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumeMounts") }} volumeMounts: {{- end }} From 90f3f1cbca7dffa57526e723bd55ee978598a745 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 14:38:53 +0000 Subject: [PATCH 11/44] [Feat] Initial working example. Signed-off-by: insukim1994 --- helm/templates/deployment-vllm-multi.yaml | 2 +- helm/templates/ray-cluster.yaml | 28 +++++++++++++++---- ...-15-minimal-pipeline-parallel-example.yaml | 11 ++++++-- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/helm/templates/deployment-vllm-multi.yaml b/helm/templates/deployment-vllm-multi.yaml index c77d05b99..fd2b2d800 100644 --- a/helm/templates/deployment-vllm-multi.yaml +++ b/helm/templates/deployment-vllm-multi.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.servingEngineSpec.enableEngine (not .Values.servingEngineSpec.enableKubeRay) -}} +{{- if and .Values.servingEngineSpec.enableEngine (not (hasKey .Values.servingEngineSpec "raySpec")) -}} {{- range $modelSpec := .Values.servingEngineSpec.modelSpec }} {{- $kv_role := "kv_both" }} {{- $kv_rank := 0 }} diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index cfe81517c..174bbd727 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -1,10 +1,10 @@ -{{- if and .Values.servingEngineSpec.enableEngine .Values.servingEngineSpec.enableKubeRay }} +{{- if and .Values.servingEngineSpec.enableEngine (hasKey .Values.servingEngineSpec "raySpec")}} {{- range $modelSpec := .Values.servingEngineSpec.modelSpec }} {{- with $ -}} apiVersion: ray.io/v1 kind: RayCluster metadata: - name: "{{ .Release.Name }}-{{$modelSpec.name}}-raycluster-vllm" + name: "{{ .Release.Name }}-{{$modelSpec.name}}-raycluster" namespace: {{ .Release.Namespace }} labels: model: {{ $modelSpec.name }} @@ -162,8 +162,19 @@ spec: ports: - name: {{ include "chart.container-port-name" . }} containerPort: {{ include "chart.container-port" . }} - {{- include "chart.probes" . | indent 12 }} - resources: {{- include "chart.resources" $modelSpec | nindent 14 }} + readinessProbe: + exec: + command: ["/bin/bash", "-c", "echo TBD"] + livenessProbe: + exec: + command: ["/bin/bash", "-c", "echo TBD"] + resources: + limits: + cpu: {{ default "2" .Values.servingEngineSpec.raySpec.headNode.requestCPU }} + memory: {{ default "8Gi" .Values.servingEngineSpec.raySpec.headNode.requestMemory }} + {{- if hasKey .Values.servingEngineSpec.raySpec.headNode "requestGPU" }} + nvidia.com/gpu: {{ .Values.servingEngineSpec.raySpec.headNode.requestGPU }} + {{- end }} startupProbe: exec: command: ["/bin/bash", "-c", "python3 /scripts/wait_for_ray.py"] @@ -261,7 +272,7 @@ spec: workerGroupSpecs: - rayStartParams: {} replicas: {{ $modelSpec.replicaCount }} - groupName: vllm-ray-worker + groupName: ray template: metadata: labels: @@ -375,7 +386,12 @@ spec: ports: - name: {{ include "chart.container-port-name" . }} containerPort: {{ include "chart.container-port" . }} - {{- include "chart.probes" . | indent 14 }} + readinessProbe: + exec: + command: ["/bin/bash", "-c", "echo TBD"] + livenessProbe: + exec: + command: ["/bin/bash", "-c", "echo TBD"] resources: {{- include "chart.resources" $modelSpec | nindent 16 }} {{- if or (hasKey $modelSpec "pvcStorage") (and $modelSpec.vllmConfig (hasKey $modelSpec.vllmConfig "tensorParallelSize")) (hasKey $modelSpec "chatTemplate") (hasKey $modelSpec "extraVolumeMounts") }} volumeMounts: diff --git a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml index 6d06c4aa5..80d20a464 100644 --- a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml +++ b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml @@ -1,6 +1,9 @@ servingEngineSpec: runtimeClassName: "" - enableKubeRay: true + raySpec: + headNode: + requestCPU: 2 + requestMemory: "20Gi" modelSpec: - name: "distilgpt2" repository: "vllm/vllm-openai" @@ -9,10 +12,12 @@ servingEngineSpec: replicaCount: 2 - requestCPU: 6 - requestMemory: "16Gi" + requestCPU: 2 + requestMemory: "20Gi" requestGPU: 1 vllmConfig: tensorParallelSize: 1 pipelineParallelSize: 2 + + shmSize: "20Gi" From 96dedc7b1d42ae7717b97587a3dc311a4eccbe67 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 15:27:51 +0000 Subject: [PATCH 12/44] [Doc] Added documentation to run vllm with kuberay for pipeline parallelism. Signed-off-by: insukim1994 --- tutorials/00-b-install-kuberay-operator.md | 83 +++++++++ tutorials/15-basic-pipeline-parallel.md | 190 +++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 tutorials/00-b-install-kuberay-operator.md create mode 100644 tutorials/15-basic-pipeline-parallel.md diff --git a/tutorials/00-b-install-kuberay-operator.md b/tutorials/00-b-install-kuberay-operator.md new file mode 100644 index 000000000..203911c68 --- /dev/null +++ b/tutorials/00-b-install-kuberay-operator.md @@ -0,0 +1,83 @@ +# Tutorial: Setting Up a Kuberay Operator on Your Kubernetes Environment + +## Introduction + +This tutorial provides a step-by-step guide to installing and configuring the KubeRay operator within a Kubernetes environment. We will use the helm chart to set up kuberay, enabling distributed inference with vLLM. By the end of this tutorial, you will have a fully operational KubeRay operator ready to support the deployment of the vLLM Production Stack. + +## Table of Contents + +- [Introduction](#introduction) +- [Table of Contents](#table-of-contents) +- [Prerequisites](#prerequisites) +- [Steps](#steps) + - [Step 1: Install the KubeRay Operator Using Helm](#step-1-install-the-kuberay-operator-using-helm) + - [Step 2: Verify the KubeRay Configuration](#step-2-verify-the-kuberay-configuration) + +## Prerequisites + +Before you begin, ensure the following: + +1. **GPU Server Requirements:** + - A server with a GPU and drivers properly installed (e.g., NVIDIA drivers). + - [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) installed for GPU workloads. + +2. **Access and Permissions:** + - Root or administrative access to the server. + - Internet connectivity to download required packages and tools. + +3. **Environment Setup:** + - A Linux-based operating system (e.g., Ubuntu 20.04 or later). + - Basic understanding of Linux shell commands. + +4. **Kubernetes Installation:** + - Follow the instructions in [`00-install-kubernetes-env.md`](00-install-kubernetes-env.md) to set up your Kubernetes environment. + +5. **Kubernetes Installation:** + - Review the [`official KubeRay documentation`](https://docs.ray.io/en/latest/cluster/kubernetes/index.html) for additional context and best practices. + +## Steps + +### Step 1: Install the KubeRay Operator Using Helm + +1. Add the KubeRay Helm repository: + + ```bash + helm repo add kuberay https://ray-project.github.io/kuberay-helm/ + helm repo update + ``` + +2. Install the Custom Resource Definitions (CRDs) and the KubeRay operator (version 1.1.0) in the default namespace: + + ```bash + helm install kuberay-operator kuberay/kuberay-operator --version 1.1.0 + ``` + +3. **Explanation:** + This step deploys the stable KubeRay operator in your Kubernetes cluster. The operator is essential for managing Ray clusters and enables you to scale multiple vLLM instances for distributed inference workloads. + +### Step 2: Verify the KubeRay Configuration + +1. **Check the Operator Pod Status:** + - Ensure that the KubeRay operator pod is running in the default namespace: + + ```bash + kubectl get pods + ``` + +2. **Expected Output:** + Example output: + + ```plaintext + NAME READY STATUS RESTARTS AGE + kuberay-operator-78cb88fb88-gbw7w 0/1 Running 0 5s + ``` + +## Conclusion + +You have now successfully installed and verified the KubeRay operator in your Kubernetes environment. This setup lays the foundation for deploying and managing the vLLM Production Stack for distributed inference or training workloads. + +For advanced configurations and workload-specific tuning, refer to the official documentation for kuberay, kubectl, helm, and minikube. + +What's next: + +- [15-basic-pipeline-parallel](https://github.com/vllm-project/production-stack/blob/main/tutorials/15-basic-pipeline-parallel.md) diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md new file mode 100644 index 000000000..dcf5102d4 --- /dev/null +++ b/tutorials/15-basic-pipeline-parallel.md @@ -0,0 +1,190 @@ +# Tutorial: Basic vLLM Configurations + +## Introduction + +This tutorial guides you through the basic configurations required to deploy a vLLM serving engine in a Kubernetes environment with distributed inference support using Kuberay. You will learn how to launch the vLLM serving engine with pipeline parallelism. + +## Table of Contents + +1. [Prerequisites](#prerequisites) +2. [Step 1: Preparing the Configuration File](#step-1-preparing-the-configuration-file) +3. [Step 2: Applying the Configuration](#step-2-applying-the-configuration) +4. [Step 3: Verifying the Ray Cluster](#step-3-verifying-the-deployment) + +## Prerequisites + +- A Kubernetes environment with GPU support, as set up in the [00-install-kubernetes-env tutorial](00-install-kubernetes-env.md). +- Install kuberay operator on the Kubernetes environment with [00-install-kubernetes-env tutorial](00-install-kubernetes-env.md). +- Helm installed on your system. +- Access to a HuggingFace token (`HF_TOKEN`). + +## Step 1: Preparing the Configuration File + +1. Locate the example configuration file [`tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml`](assets/values-15-minimal-pipeline-parallel-example.yaml). +2. Open the file and update the following fields: + - Write your actual huggingface token in `hf_token: ` in the yaml file. + +### Explanation of Key Items in `values-15-minimal-pipeline-parallel-example.yaml` + +- **`name`**: The unique identifier for your model deployment. +- **`repository`**: The Docker repository containing the model's serving engine image. +- **`tag`**: Specifies the version of the model image to use. +- **`modelURL`**: The URL pointing to the model on Hugging Face or another hosting service. +- **`replicaCount`**: The number of replicas for each Kuberay worker pod. +- **`requestCPU`**: The amount of CPU resources requested per Kuberay worker pod. +- **`requestMemory`**: Memory allocation for each Kuberay worker pod; sufficient memory is required to load the model. +- **`requestGPU`**: Specifies the number of GPUs to allocate for each Kuberay worker pod. +- **`vllmConfig`**: Contains model-specific configurations: + - `tensorParallelSize`: Number of GPUs to assign for each worker pod. + - `pipelineParallelSize`: Pipeline parallel factor. `Total GPUs` = `pipelineParallelSize` x `tensorParallelSize` +- **`shmSize`**: Shared memory size to enable appropriate shared memory across multiple processes used to run tensor and pipeline parallelism. +- **`hf_token`**: The Hugging Face token for authenticating with the Hugging Face model hub. + +### Example Snippet + +```yaml +servingEngineSpec: + runtimeClassName: "" + raySpec: + headNode: + requestCPU: 2 + requestMemory: "20Gi" + modelSpec: + - name: "distilgpt2" + repository: "vllm/vllm-openai" + tag: "latest" + modelURL: "distilbert/distilgpt2" + + replicaCount: 2 + + requestCPU: 2 + requestMemory: "20Gi" + requestGPU: 1 + + vllmConfig: + tensorParallelSize: 1 + pipelineParallelSize: 2 + + shmSize: "20Gi" + + hf_token: +``` + +## Step 2: Applying the Configuration + +Deploy the configuration using Helm: + +```bash +helm repo add vllm https://vllm-project.github.io/production-stack +helm install vllm vllm/vllm-stack -f tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml +``` + +Expected output: + +You should see output indicating the successful deployment of the Helm chart: + +```plaintext +NAME: vllm +LAST DEPLOYED: Sun May 11 15:10:34 2025 +NAMESPACE: default +STATUS: deployed +REVISION: 1 +TEST SUITE: None +``` + +## Step 3: Verifying the Deployment + +1. Check the status of the pods: + + ```bash + kubectl get pods + ``` + + Expected output: + + You should see the following pods: + + ```plaintext + NAME READY STATUS RESTARTS AGE + kuberay-operator-975995b7d-75jqd 1/1 Running 0 24h + vllm-deployment-router-8666bf6464-ds8pm 1/1 Running 0 40s + vllm-distilgpt2-raycluster-head-74qvn 1/1 Running 0 40s + vllm-distilgpt2-raycluster-ray-worker-jlgj8 1/1 Running 0 40s + vllm-distilgpt2-raycluster-ray-worker-jrcrl 1/1 Running 0 40s + ``` + + - The `vllm-deployment-router` pod acts as the router, managing requests and routing them to the appropriate model-serving pod. + - The `vllm-distilgpt2-raycluster-head` pod runs actual vllm command. + - `vllm-distilgpt2-raycluster-ray-worker-*` pods serves the actual model for inference. + +2. Verify the service is exposed correctly: + + ```bash + kubectl get services + ``` + + Expected output: + + Ensure there are services for both the serving engine and the router: + + ```plaintext + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + kuberay-operator ClusterIP 10.99.122.176 8080/TCP 24h + kubernetes ClusterIP 10.96.0.1 443/TCP 5d1h + vllm-distilgpt2-engine-service ClusterIP 10.105.193.58 80/TCP 2m42s + vllm-distilgpt2-raycluster-head-svc ClusterIP None 8000/TCP,8080/TCP 2m42s + vllm-router-service ClusterIP 10.108.70.18 80/TCP 2m42s + ``` + + - The `vllm-engine-service` exposes the serving engine. + - The `vllm-router-service` handles routing and load balancing across model-serving pods. + +3. Test the health endpoint: + + ```bash + curl http:///health + ``` + + Replace `` with the external IP of the service. If everything is configured correctly, you will get: + + ```plaintext + {"status":"healthy"} + ``` + +Please refer to Step 3 in the [01-minimal-helm-installation](01-minimal-helm-installation.md) tutorial for querying the deployed vLLM service. + +## Step 4 (Optional): Multi-GPU Deployment + +So far, you have configured and deployment vLLM serving engine with a single GPU. You may also deploy a serving engine on multiple GPUs with the following example configuration snippet: + +```yaml +servingEngineSpec: + runtimeClassName: "" + modelSpec: + - name: "llama3" + repository: "vllm/vllm-openai" + tag: "latest" + modelURL: "meta-llama/Llama-3.1-8B-Instruct" + replicaCount: 1 + requestCPU: 10 + requestMemory: "16Gi" + requestGPU: 2 + pvcStorage: "50Gi" + pvcAccessMode: + - ReadWriteOnce + vllmConfig: + enableChunkedPrefill: false + enablePrefixCaching: false + maxModelLen: 4096 + tensorParallelSize: 2 + dtype: "bfloat16" + extraArgs: ["--disable-log-requests", "--gpu-memory-utilization", "0.8"] + hf_token: + shmSize: "20Gi" +``` + +Note that only tensor parallelism is supported for now. The field ``shmSize`` has to be configured if you are requesting ``requestGPU`` to be more than one, to enable appropriate shared memory across multiple processes used to run tensor parallelism. + +## Conclusion + +In this tutorial, you configured and deployed a vLLM serving engine with pipeline parallelism support (on multiple GPUs) in a Kubernetes environment with Kuberay. You also learned how to verify its deployment and pods and ensure it is running as expected. For further customization, refer to the `values.yaml` file and Helm chart documentation. From 0f878ec416e18c57aa2ab69c139db1738dcc8843 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 16:03:17 +0000 Subject: [PATCH 13/44] [Doc] Elaborated tutorial documentation. Signed-off-by: insukim1994 --- tutorials/15-basic-pipeline-parallel.md | 71 +++++++++++-------------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md index dcf5102d4..4c8f2f53a 100644 --- a/tutorials/15-basic-pipeline-parallel.md +++ b/tutorials/15-basic-pipeline-parallel.md @@ -26,6 +26,8 @@ This tutorial guides you through the basic configurations required to deploy a v ### Explanation of Key Items in `values-15-minimal-pipeline-parallel-example.yaml` +- **`raySpec`**: Required when using KubeRay to enable pipeline parallelism. +- **`headNode`**: Specifies the resource requirements for the Ray head node and must be defined accordingly. - **`name`**: The unique identifier for your model deployment. - **`repository`**: The Docker repository containing the model's serving engine image. - **`tag`**: Specifies the version of the model image to use. @@ -35,9 +37,9 @@ This tutorial guides you through the basic configurations required to deploy a v - **`requestMemory`**: Memory allocation for each Kuberay worker pod; sufficient memory is required to load the model. - **`requestGPU`**: Specifies the number of GPUs to allocate for each Kuberay worker pod. - **`vllmConfig`**: Contains model-specific configurations: - - `tensorParallelSize`: Number of GPUs to assign for each worker pod. - - `pipelineParallelSize`: Pipeline parallel factor. `Total GPUs` = `pipelineParallelSize` x `tensorParallelSize` -- **`shmSize`**: Shared memory size to enable appropriate shared memory across multiple processes used to run tensor and pipeline parallelism. + - `tensorParallelSize`: Defines the number of GPUs allocated to each worker pod. + - `pipelineParallelSize`: Specifies the degree of pipeline parallelism. `The total number of GPUs` used is calculated as `pipelineParallelSize × tensorParallelSize`. +- **`shmSize`**: Configures the shared memory size to ensure adequate memory is available for inter-process communication during tensor and pipeline parallelism execution. - **`hf_token`**: The Hugging Face token for authenticating with the Hugging Face model hub. ### Example Snippet @@ -113,9 +115,11 @@ TEST SUITE: None vllm-distilgpt2-raycluster-ray-worker-jrcrl 1/1 Running 0 40s ``` - - The `vllm-deployment-router` pod acts as the router, managing requests and routing them to the appropriate model-serving pod. - - The `vllm-distilgpt2-raycluster-head` pod runs actual vllm command. - - `vllm-distilgpt2-raycluster-ray-worker-*` pods serves the actual model for inference. + - The vllm-deployment-router pod functions as the request router, directing incoming traffic to the appropriate model-serving pod. + + - The vllm-distilgpt2-raycluster-head pod is responsible for running the primary vLLM command. + + - The vllm-distilgpt2-raycluster-ray-worker-* pods serve the model and handle inference requests. 2. Verify the service is exposed correctly: @@ -141,50 +145,39 @@ TEST SUITE: None 3. Test the health endpoint: + To verify that the service is operational, execute the following commands: + ```bash - curl http:///health + kubectl port-forward svc/vllm-router-service 30080:80 + curl http://localhost:30080/v1/models ``` - Replace `` with the external IP of the service. If everything is configured correctly, you will get: + **Note:** Port forwarding must be performed from a separate shell session. If the deployment is configured correctly, you should receive a response similar to the following: ```plaintext - {"status":"healthy"} + {"object":"list","data":[{"id":"distilbert/distilgpt2","object":"model","created":1746978162,"owned_by":"vllm","root":null}]} ``` -Please refer to Step 3 in the [01-minimal-helm-installation](01-minimal-helm-installation.md) tutorial for querying the deployed vLLM service. + You may also perform a basic inference test to validate that pipeline parallelism is functioning as expected. Use the following curl command: -## Step 4 (Optional): Multi-GPU Deployment + ```bash + curl -X POST http://localhost:30080/v1/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "distilbert/distilgpt2", + "prompt": "Once upon a time,", + "max_tokens": 10 + }' + ``` -So far, you have configured and deployment vLLM serving engine with a single GPU. You may also deploy a serving engine on multiple GPUs with the following example configuration snippet: + A successful response should resemble the following output: -```yaml -servingEngineSpec: - runtimeClassName: "" - modelSpec: - - name: "llama3" - repository: "vllm/vllm-openai" - tag: "latest" - modelURL: "meta-llama/Llama-3.1-8B-Instruct" - replicaCount: 1 - requestCPU: 10 - requestMemory: "16Gi" - requestGPU: 2 - pvcStorage: "50Gi" - pvcAccessMode: - - ReadWriteOnce - vllmConfig: - enableChunkedPrefill: false - enablePrefixCaching: false - maxModelLen: 4096 - tensorParallelSize: 2 - dtype: "bfloat16" - extraArgs: ["--disable-log-requests", "--gpu-memory-utilization", "0.8"] - hf_token: - shmSize: "20Gi" -``` + ```plaintext + {"id":"cmpl-27e058ce9f0443dd96b76aced16f8b90","object":"text_completion","created":1746978495,"model":"distilbert/distilgpt2","choices":[{"index":0,"text":" the dim of light lingered as it projected enough","logprobs":null,"finish_reason":"length","stop_reason":null,"prompt_logprobs":null}],"usage":{"prompt_tokens":5,"total_tokens":15,"completion_tokens":10,"prompt_tokens_details":null}} + ``` -Note that only tensor parallelism is supported for now. The field ``shmSize`` has to be configured if you are requesting ``requestGPU`` to be more than one, to enable appropriate shared memory across multiple processes used to run tensor parallelism. +Please refer to Step 3 in the [01-minimal-helm-installation](01-minimal-helm-installation.md) tutorial for querying the deployed vLLM service. ## Conclusion -In this tutorial, you configured and deployed a vLLM serving engine with pipeline parallelism support (on multiple GPUs) in a Kubernetes environment with Kuberay. You also learned how to verify its deployment and pods and ensure it is running as expected. For further customization, refer to the `values.yaml` file and Helm chart documentation. +In this tutorial, you configured and deployed the vLLM serving engine with support for pipeline parallelism across multiple GPUs within a Kubernetes environment using KubeRay. Additionally, you learned how to verify the deployment and monitor the associated pods to ensure proper operation. For further customization and configuration options, please consult the `values.yaml` file and the Helm chart documentation. From d1c62fa35da26d66ee39f2372a95b95348d867c2 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 16:05:11 +0000 Subject: [PATCH 14/44] [Chore] Fixed typo in kuberay operator installation tutorial document. Signed-off-by: insukim1994 --- tutorials/00-b-install-kuberay-operator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/00-b-install-kuberay-operator.md b/tutorials/00-b-install-kuberay-operator.md index 203911c68..bb998d914 100644 --- a/tutorials/00-b-install-kuberay-operator.md +++ b/tutorials/00-b-install-kuberay-operator.md @@ -32,7 +32,7 @@ Before you begin, ensure the following: 4. **Kubernetes Installation:** - Follow the instructions in [`00-install-kubernetes-env.md`](00-install-kubernetes-env.md) to set up your Kubernetes environment. -5. **Kubernetes Installation:** +5. **Kubereay Operator Installation:** - Review the [`official KubeRay documentation`](https://docs.ray.io/en/latest/cluster/kubernetes/index.html) for additional context and best practices. ## Steps From fa8a7227ad6467bcabc16e39259da7db8f8ef02a Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 16:06:52 +0000 Subject: [PATCH 15/44] [Chore] Fixed a wording in kuberay operator installation tutorial document. Signed-off-by: insukim1994 --- tutorials/00-b-install-kuberay-operator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/00-b-install-kuberay-operator.md b/tutorials/00-b-install-kuberay-operator.md index bb998d914..66e1bc5c5 100644 --- a/tutorials/00-b-install-kuberay-operator.md +++ b/tutorials/00-b-install-kuberay-operator.md @@ -32,7 +32,7 @@ Before you begin, ensure the following: 4. **Kubernetes Installation:** - Follow the instructions in [`00-install-kubernetes-env.md`](00-install-kubernetes-env.md) to set up your Kubernetes environment. -5. **Kubereay Operator Installation:** +5. **Kubereay Concept Review:** - Review the [`official KubeRay documentation`](https://docs.ray.io/en/latest/cluster/kubernetes/index.html) for additional context and best practices. ## Steps From e3507e19a0561b1eff322c00d15e2d88596d88d0 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 16:07:19 +0000 Subject: [PATCH 16/44] [Chore] Fixed typo in kuberay operator installation tutorial document. Signed-off-by: insukim1994 --- tutorials/00-b-install-kuberay-operator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/00-b-install-kuberay-operator.md b/tutorials/00-b-install-kuberay-operator.md index 66e1bc5c5..4494f1d1f 100644 --- a/tutorials/00-b-install-kuberay-operator.md +++ b/tutorials/00-b-install-kuberay-operator.md @@ -32,7 +32,7 @@ Before you begin, ensure the following: 4. **Kubernetes Installation:** - Follow the instructions in [`00-install-kubernetes-env.md`](00-install-kubernetes-env.md) to set up your Kubernetes environment. -5. **Kubereay Concept Review:** +5. **Kuberay Concept Review:** - Review the [`official KubeRay documentation`](https://docs.ray.io/en/latest/cluster/kubernetes/index.html) for additional context and best practices. ## Steps From 6148ff79654fd315398933869e64e337411ee111 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 16:11:29 +0000 Subject: [PATCH 17/44] [Chore] Removed unused value from helm chart default value. Signed-off-by: insukim1994 --- helm/values.yaml | 1 - tutorials/15-basic-pipeline-parallel.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/helm/values.yaml b/helm/values.yaml index 1250af2b5..fef374efe 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -4,7 +4,6 @@ # -- Serving engine configuratoon servingEngineSpec: enableEngine: true - enableKubeRay: false # -- Customized labels for the serving engine deployment labels: environment: "test" diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md index 4c8f2f53a..7a261a50e 100644 --- a/tutorials/15-basic-pipeline-parallel.md +++ b/tutorials/15-basic-pipeline-parallel.md @@ -27,7 +27,7 @@ This tutorial guides you through the basic configurations required to deploy a v ### Explanation of Key Items in `values-15-minimal-pipeline-parallel-example.yaml` - **`raySpec`**: Required when using KubeRay to enable pipeline parallelism. -- **`headNode`**: Specifies the resource requirements for the Ray head node and must be defined accordingly. +- **`headNode`**: Specifies the resource requirements for the Kuberay head node and must be defined accordingly. - **`name`**: The unique identifier for your model deployment. - **`repository`**: The Docker repository containing the model's serving engine image. - **`tag`**: Specifies the version of the model image to use. From 17cee8a907d552ea0818bebcf71df275d99bacdf Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 16:12:54 +0000 Subject: [PATCH 18/44] [Chore] Elaborated expression on tutorial document. Signed-off-by: insukim1994 --- tutorials/15-basic-pipeline-parallel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md index 7a261a50e..457a3fd52 100644 --- a/tutorials/15-basic-pipeline-parallel.md +++ b/tutorials/15-basic-pipeline-parallel.md @@ -32,7 +32,7 @@ This tutorial guides you through the basic configurations required to deploy a v - **`repository`**: The Docker repository containing the model's serving engine image. - **`tag`**: Specifies the version of the model image to use. - **`modelURL`**: The URL pointing to the model on Hugging Face or another hosting service. -- **`replicaCount`**: The number of replicas for each Kuberay worker pod. +- **`replicaCount`**: The number of total Kuberay worker pods. - **`requestCPU`**: The amount of CPU resources requested per Kuberay worker pod. - **`requestMemory`**: Memory allocation for each Kuberay worker pod; sufficient memory is required to load the model. - **`requestGPU`**: Specifies the number of GPUs to allocate for each Kuberay worker pod. From 6058820eedcd51bbebf532cb7fc0b308fe241269 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 11 May 2025 16:14:10 +0000 Subject: [PATCH 19/44] [Chore] Elaborated expression on tutorial document. Signed-off-by: insukim1994 --- tutorials/00-b-install-kuberay-operator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/00-b-install-kuberay-operator.md b/tutorials/00-b-install-kuberay-operator.md index 4494f1d1f..d58581876 100644 --- a/tutorials/00-b-install-kuberay-operator.md +++ b/tutorials/00-b-install-kuberay-operator.md @@ -68,8 +68,8 @@ Before you begin, ensure the following: Example output: ```plaintext - NAME READY STATUS RESTARTS AGE - kuberay-operator-78cb88fb88-gbw7w 0/1 Running 0 5s + NAME READY STATUS RESTARTS AGE + kuberay-operator-975995b7d-75jqd 1/1 Running 0 25h ``` ## Conclusion From 2967dd2f75f125b88d44d15b95ccb007ad9206f8 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 12 May 2025 19:20:43 +0000 Subject: [PATCH 20/44] [Feat] Set readiness httpGet probe for ray head node. Removed unused container ports from ray worker nodes. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index 174bbd727..5a6ba5116 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -163,8 +163,11 @@ spec: - name: {{ include "chart.container-port-name" . }} containerPort: {{ include "chart.container-port" . }} readinessProbe: - exec: - command: ["/bin/bash", "-c", "echo TBD"] + httpGet: + path: /health + port: {{ include "chart.container-port" . }} + failureThreshold: 1 + periodSeconds: 10 livenessProbe: exec: command: ["/bin/bash", "-c", "echo TBD"] @@ -251,7 +254,6 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} {{- end }} - {{- if .Values.servingEngineSpec.runtimeClassName }} runtimeClassName: {{ .Values.servingEngineSpec.runtimeClassName }} {{- end }} @@ -383,9 +385,6 @@ spec: - configMapRef: name: "{{ .Release.Name }}-configs" {{- end }} - ports: - - name: {{ include "chart.container-port-name" . }} - containerPort: {{ include "chart.container-port" . }} readinessProbe: exec: command: ["/bin/bash", "-c", "echo TBD"] From 6779ebf3374abcedfceb4c07161103da1551b94c Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 12 May 2025 20:18:34 +0000 Subject: [PATCH 21/44] [Feat] Added VLLM_HOST_IP based on official vllm docs. Added ray installation step. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index 5a6ba5116..b46e6e4a1 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -56,11 +56,16 @@ spec: command: - >- /bin/bash -c " + pip install ray[default] && cp /entrypoint/vllm-entrypoint.sh \$HOME/vllm-entrypoint.sh && chmod +x \$HOME/vllm-entrypoint.sh && \$HOME/vllm-entrypoint.sh & echo \"Running vllm command in the background.\"" env: + - name: VLLM_HOST_IP + valueFrom: + fieldRef: + fieldPath: status.podIP - name: EXPECTED_NODES value: "{{ add $modelSpec.replicaCount 1}}" - name: HF_HOME @@ -289,6 +294,10 @@ spec: - name: vllm-ray-worker image: "{{ required "Required value 'modelSpec.repository' must be defined !" $modelSpec.repository }}:{{ required "Required value 'modelSpec.tag' must be defined !" $modelSpec.tag }}" env: + - name: VLLM_HOST_IP + valueFrom: + fieldRef: + fieldPath: status.podIP - name: HF_HOME {{- if hasKey $modelSpec "pvcStorage" }} value: /data From 38979b9f08036c2fea0c2dbd3231e912601be251 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 12 May 2025 20:41:12 +0000 Subject: [PATCH 22/44] [Feat] Added missing dashboard related setting and a step for reinstalling ray. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index b46e6e4a1..1554d2df8 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -14,6 +14,7 @@ spec: headGroupSpec: serviceType: ClusterIP rayStartParams: + include-dashboard: "true" dashboard-host: "0.0.0.0" template: metadata: @@ -56,10 +57,10 @@ spec: command: - >- /bin/bash -c " - pip install ray[default] && cp /entrypoint/vllm-entrypoint.sh \$HOME/vllm-entrypoint.sh && chmod +x \$HOME/vllm-entrypoint.sh && \$HOME/vllm-entrypoint.sh & + pip install -U "ray[default]" && echo \"Running vllm command in the background.\"" env: - name: VLLM_HOST_IP From 428738bc7f0f711386b19fdefcde63c26f68178d Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 12 May 2025 21:24:18 +0000 Subject: [PATCH 23/44] [Feat] Removed initContainer section that will be overwritted by kuberay operator. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index 1554d2df8..f45b5342b 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -14,7 +14,6 @@ spec: headGroupSpec: serviceType: ClusterIP rayStartParams: - include-dashboard: "true" dashboard-host: "0.0.0.0" template: metadata: @@ -28,29 +27,6 @@ spec: securityContext: {{- toYaml .Values.servingEngineSpec.securityContext | nindent 10 }} {{- end }} - {{- if hasKey $modelSpec "initContainer" }} - {{- $container := $modelSpec.initContainer }} - initContainers: - - name: {{ $container.name }} - image: {{ $container.image }} - {{- if $container.command }} - command: {{ toYaml $container.command | nindent 12 }} - {{- end }} - {{- if $container.args }} - args: {{ toYaml $container.args | nindent 12 }} - {{- end }} - {{- if $container.env }} - env: {{ toYaml $container.env | nindent 12 }} - {{- end }} - {{- if $container.resources }} - resources: {{ toYaml $container.resources | nindent 12 }} - {{- end }} - {{- if and (hasKey $container "mountPvcStorage") ($container.mountPvcStorage) (hasKey $modelSpec "pvcStorage") }} - volumeMounts: - - name: {{ .Release.Name }}-storage - mountPath: /data - {{- end }} - {{- end }} containers: - name: vllm-ray-head image: "{{ required "Required value 'modelSpec.repository' must be defined !" $modelSpec.repository }}:{{ required "Required value 'modelSpec.tag' must be defined !" $modelSpec.tag }}" @@ -60,7 +36,6 @@ spec: cp /entrypoint/vllm-entrypoint.sh \$HOME/vllm-entrypoint.sh && chmod +x \$HOME/vllm-entrypoint.sh && \$HOME/vllm-entrypoint.sh & - pip install -U "ray[default]" && echo \"Running vllm command in the background.\"" env: - name: VLLM_HOST_IP From c27154b62f6020c266761dc5f5fa17b6ca4ace1f Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 12 May 2025 21:35:23 +0000 Subject: [PATCH 24/44] [Feat] Kuberay operator version updated needed. Signed-off-by: insukim1994 --- tutorials/00-b-install-kuberay-operator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/00-b-install-kuberay-operator.md b/tutorials/00-b-install-kuberay-operator.md index d58581876..4ef1cb28e 100644 --- a/tutorials/00-b-install-kuberay-operator.md +++ b/tutorials/00-b-install-kuberay-operator.md @@ -46,10 +46,10 @@ Before you begin, ensure the following: helm repo update ``` -2. Install the Custom Resource Definitions (CRDs) and the KubeRay operator (version 1.1.0) in the default namespace: +2. Install the Custom Resource Definitions (CRDs) and the KubeRay operator (version 1.2.0) in the default namespace: ```bash - helm install kuberay-operator kuberay/kuberay-operator --version 1.1.0 + helm install kuberay-operator kuberay/kuberay-operator --version 1.2.0 ``` 3. **Explanation:** From 62ec64976dc4eac974622a402b4f5364906874a8 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 12 May 2025 21:49:02 +0000 Subject: [PATCH 25/44] [Doc] Minor fix in tutorial. Signed-off-by: insukim1994 --- tutorials/15-basic-pipeline-parallel.md | 24 +++++++++---------- ...-15-minimal-pipeline-parallel-example.yaml | 3 ++- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md index 457a3fd52..fdb6749df 100644 --- a/tutorials/15-basic-pipeline-parallel.md +++ b/tutorials/15-basic-pipeline-parallel.md @@ -51,13 +51,14 @@ servingEngineSpec: headNode: requestCPU: 2 requestMemory: "20Gi" + requestGPU: 1 modelSpec: - name: "distilgpt2" repository: "vllm/vllm-openai" tag: "latest" modelURL: "distilbert/distilgpt2" - replicaCount: 2 + replicaCount: 1 requestCPU: 2 requestMemory: "20Gi" @@ -108,11 +109,10 @@ TEST SUITE: None ```plaintext NAME READY STATUS RESTARTS AGE - kuberay-operator-975995b7d-75jqd 1/1 Running 0 24h - vllm-deployment-router-8666bf6464-ds8pm 1/1 Running 0 40s - vllm-distilgpt2-raycluster-head-74qvn 1/1 Running 0 40s - vllm-distilgpt2-raycluster-ray-worker-jlgj8 1/1 Running 0 40s - vllm-distilgpt2-raycluster-ray-worker-jrcrl 1/1 Running 0 40s + kuberay-operator-f89ddb644-psts7 1/1 Running 0 11m + vllm-deployment-router-8666bf6464-7nz5f 1/1 Running 0 2m34s + vllm-distilgpt2-raycluster-head-xrcgw 1/1 Running 0 2m34s + vllm-distilgpt2-raycluster-ray-worker-92zrr 1/1 Running 0 2m34s ``` - The vllm-deployment-router pod functions as the request router, directing incoming traffic to the appropriate model-serving pod. @@ -132,12 +132,12 @@ TEST SUITE: None Ensure there are services for both the serving engine and the router: ```plaintext - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - kuberay-operator ClusterIP 10.99.122.176 8080/TCP 24h - kubernetes ClusterIP 10.96.0.1 443/TCP 5d1h - vllm-distilgpt2-engine-service ClusterIP 10.105.193.58 80/TCP 2m42s - vllm-distilgpt2-raycluster-head-svc ClusterIP None 8000/TCP,8080/TCP 2m42s - vllm-router-service ClusterIP 10.108.70.18 80/TCP 2m42s + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + kuberay-operator ClusterIP 10.99.21.240 8080/TCP 11m + kubernetes ClusterIP 10.96.0.1 443/TCP 6d8h + vllm-distilgpt2-engine-service ClusterIP 10.96.242.69 80/TCP 2m57s + vllm-distilgpt2-raycluster-head-svc ClusterIP None 8000/TCP,8080/TCP 2m56s + vllm-router-service ClusterIP 10.99.111.73 80/TCP 2m57s ``` - The `vllm-engine-service` exposes the serving engine. diff --git a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml index 80d20a464..3f56f6290 100644 --- a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml +++ b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml @@ -4,13 +4,14 @@ servingEngineSpec: headNode: requestCPU: 2 requestMemory: "20Gi" + requestGPU: 1 modelSpec: - name: "distilgpt2" repository: "vllm/vllm-openai" tag: "latest" modelURL: "distilbert/distilgpt2" - replicaCount: 2 + replicaCount: 1 requestCPU: 2 requestMemory: "20Gi" From a833fea10fbd899dc966bd020d3d805897dcdc29 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 12 May 2025 21:58:11 +0000 Subject: [PATCH 26/44] [Doc] Added sample gpu usage example for each ray head and worker node. Signed-off-by: insukim1994 --- tutorials/15-basic-pipeline-parallel.md | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md index fdb6749df..ebfbb7e9b 100644 --- a/tutorials/15-basic-pipeline-parallel.md +++ b/tutorials/15-basic-pipeline-parallel.md @@ -176,6 +176,58 @@ TEST SUITE: None {"id":"cmpl-27e058ce9f0443dd96b76aced16f8b90","object":"text_completion","created":1746978495,"model":"distilbert/distilgpt2","choices":[{"index":0,"text":" the dim of light lingered as it projected enough","logprobs":null,"finish_reason":"length","stop_reason":null,"prompt_logprobs":null}],"usage":{"prompt_tokens":5,"total_tokens":15,"completion_tokens":10,"prompt_tokens_details":null}} ``` + You can also monitor GPU usage for each Ray head and worker pod: + + ```plaintext + kubectl exec -it vllm-distilgpt2-raycluster-head-xrcgw -- /bin/bash + root@vllm-distilgpt2-raycluster-head-xrcgw:/vllm-workspace# nvidia-smi + Mon May 12 14:51:41 2025 + +-----------------------------------------------------------------------------------------+ + | NVIDIA-SMI 550.90.07 Driver Version: 550.90.07 CUDA Version: 12.4 | + |-----------------------------------------+------------------------+----------------------+ + | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | + | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | + | | | MIG M. | + |=========================================+========================+======================| + | 0 NVIDIA L4 Off | 00000000:00:03.0 Off | 0 | + | N/A 76C P0 40W / 72W | 20129MiB / 23034MiB | 0% Default | + | | | N/A | + +-----------------------------------------+------------------------+----------------------+ + + +-----------------------------------------------------------------------------------------+ + | Processes: | + | GPU GI CI PID Type Process name GPU Memory | + | ID ID Usage | + |=========================================================================================| + | 0 N/A N/A 13 C /usr/bin/python3 0MiB | + +-----------------------------------------------------------------------------------------+ + + ########################################################################################### + + kubectl exec -it vllm-distilgpt2-raycluster-head-xrcgw -- /bin/bash + root@vllm-distilgpt2-raycluster-ray-worker-92zrr:/vllm-workspace# nvidia-smi + Mon May 12 14:51:44 2025 + +-----------------------------------------------------------------------------------------+ + | NVIDIA-SMI 550.90.07 Driver Version: 550.90.07 CUDA Version: 12.4 | + |-----------------------------------------+------------------------+----------------------+ + | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | + | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | + | | | MIG M. | + |=========================================+========================+======================| + | 0 NVIDIA L4 Off | 00000000:00:04.0 Off | 0 | + | N/A 71C P0 39W / 72W | 20119MiB / 23034MiB | 0% Default | + | | | N/A | + +-----------------------------------------+------------------------+----------------------+ + + +-----------------------------------------------------------------------------------------+ + | Processes: | + | GPU GI CI PID Type Process name GPU Memory | + | ID ID Usage | + |=========================================================================================| + | 0 N/A N/A 273 C ray::RayWorkerWrapper 0MiB | + +-----------------------------------------------------------------------------------------+ + ``` + Please refer to Step 3 in the [01-minimal-helm-installation](01-minimal-helm-installation.md) tutorial for querying the deployed vLLM service. ## Conclusion From 618c50c6e78110bc5e910e08ed56ef2cf31f7cc7 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 12 May 2025 22:02:28 +0000 Subject: [PATCH 27/44] [Chore] Fixed typo in basic pipeline parallel tutorial doc. Signed-off-by: insukim1994 --- tutorials/15-basic-pipeline-parallel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md index ebfbb7e9b..2e2954444 100644 --- a/tutorials/15-basic-pipeline-parallel.md +++ b/tutorials/15-basic-pipeline-parallel.md @@ -204,7 +204,7 @@ TEST SUITE: None ########################################################################################### - kubectl exec -it vllm-distilgpt2-raycluster-head-xrcgw -- /bin/bash + kubectl exec -it vllm-distilgpt2-raycluster-ray-worker-92zrr -- /bin/bash root@vllm-distilgpt2-raycluster-ray-worker-92zrr:/vllm-workspace# nvidia-smi Mon May 12 14:51:44 2025 +-----------------------------------------------------------------------------------------+ From 57bab8709b3d0c5baf9e4781b8295eba6e45a449 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 12 May 2025 22:03:50 +0000 Subject: [PATCH 28/44] [Chore] Reverted unnecessary change. Signed-off-by: insukim1994 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e185277d7..64559c05e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ semantic_cache = [ "huggingface-hub==0.25.2", # downgrade to 0.25.2 to avoid breaking changes ] lmcache = [ - "lmcache==0.2.1", + "lmcache==0.2.11", ] [tool.pytest.ini_options] From f41ba7961871368d4074f741ac86f08a3b3a5660 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 12 May 2025 22:07:10 +0000 Subject: [PATCH 29/44] [Chore] Fixed typo in kuberay install util script. Signed-off-by: insukim1994 --- utils/install-kuberay.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/install-kuberay.sh b/utils/install-kuberay.sh index 8498c9f0d..40b27567a 100755 --- a/utils/install-kuberay.sh +++ b/utils/install-kuberay.sh @@ -9,10 +9,10 @@ helm repo update # Confirm the repo exists helm search repo kuberay --devel -# Install both CRDs and KubeRay operator v1.1.0. -helm install kuberay-operator kuberay/kuberay-operator --version 1.1.0 +# Install both CRDs and KubeRay operator v1.2.0. +helm install kuberay-operator kuberay/kuberay-operator --version 1.2.0 # Check the KubeRay operator Pod in `default` namespace kubectl get pods # NAME READY STATUS RESTARTS AGE -# kuberay-operator-78cb88fb88-gbw7w 0/1 Running 0 5s +# kuberay-operator-f89ddb644-psts7 1/1 Running 0 33m From b4168acc277b021f91469a7ba48fbb9da0ed86ba Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Thu, 15 May 2025 16:34:37 +0000 Subject: [PATCH 30/44] [Doc] Added utility script to install kubeadm. Signed-off-by: insukim1994 --- utils/install-kubernetes-with-kubeadm.sh | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 utils/install-kubernetes-with-kubeadm.sh diff --git a/utils/install-kubernetes-with-kubeadm.sh b/utils/install-kubernetes-with-kubeadm.sh new file mode 100644 index 000000000..1dfdeb15d --- /dev/null +++ b/utils/install-kubernetes-with-kubeadm.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Refer to https://v1-32.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ +# for more detailed explanation of kubeadm installation. +# Following instructions are for linux distributions like Ubuntu, Debian, etc. +# This script is from above official documentation, but modified to work with Debian 11 (bullseye). + +sudo apt-get update +# apt-transport-https may be a dummy package; if so, you can skip that package +sudo apt-get install -y apt-transport-https ca-certificates curl gpg + +# If the directory `/etc/apt/keyrings` does not exist, it should be created before the curl command, read the note below. +sudo mkdir -p -m 755 /etc/apt/keyrings +curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg + +# This overwrites any existing configuration in /etc/apt/sources.list.d/kubernetes.list +echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list + +# Update the apt package index, install kubelet, kubeadm and kubectl, and pin their version: +sudo apt-get update +sudo apt-get install -y kubelet kubeadm kubectl +sudo apt-mark hold kubelet kubeadm kubectl + +# (Optional) Enable the kubelet service before running kubeadm: +sudo systemctl enable --now kubelet From 9100a42301b5e5f15fc0e91b46e402baa7959a69 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Thu, 15 May 2025 17:11:51 +0000 Subject: [PATCH 31/44] [Doc] Added cri-o container runtime installation script & a script to create a control plane node. Signed-off-by: insukim1994 --- utils/create-kubernetes-controlplane-node.sh | 36 +++++++++++++++++++ utils/install-cri-o.sh | 24 +++++++++++++ ...tes-with-kubeadm.sh => install-kubeadm.sh} | 0 3 files changed, 60 insertions(+) create mode 100755 utils/create-kubernetes-controlplane-node.sh create mode 100755 utils/install-cri-o.sh rename utils/{install-kubernetes-with-kubeadm.sh => install-kubeadm.sh} (100%) mode change 100644 => 100755 diff --git a/utils/create-kubernetes-controlplane-node.sh b/utils/create-kubernetes-controlplane-node.sh new file mode 100755 index 000000000..8a954407e --- /dev/null +++ b/utils/create-kubernetes-controlplane-node.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +sudo swapoff -a +sudo modprobe br_netfilter +sudo sysctl -w net.ipv4.ip_forward=1 + +# Refer to https://v1-32.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ +# for more information. +# This script will create a Kubernetes cluster using kubeadm. + +# On one of your nodes which to become a control node, execute following command: +sudo kubeadm init --cri-socket=unix:///var/run/crio/crio.sock + +# The output will look like this: +# -------------------------------------------------------------------------------- +# Your Kubernetes control-plane has initialized successfully! + +# To start using your cluster, you need to run the following as a regular user: + +# mkdir -p $HOME/.kube +# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config +# sudo chown $(id -u):$(id -g) $HOME/.kube/config + +# Alternatively, if you are the root user, you can run: + +# export KUBECONFIG=/etc/kubernetes/admin.conf + +# You should now deploy a pod network to the cluster. +# Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: +# https://kubernetes.io/docs/concepts/cluster-administration/addons/ + +# Then you can join any number of worker nodes by running the following on each as root: + +# kubeadm join --token \ +# --discovery-token-ca-cert-hash +# -------------------------------------------------------------------------------- diff --git a/utils/install-cri-o.sh b/utils/install-cri-o.sh new file mode 100755 index 000000000..c08d0e147 --- /dev/null +++ b/utils/install-cri-o.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Refer to https://github.com/cri-o/packaging/blob/main/README.md#distributions-using-deb-packages +# for more information. + +# Install the dependencies for adding repositories +sudo apt-get update +sudo apt-get install -y software-properties-common curl + +export CRIO_VERSION=v1.32 + +# Add the CRI-O repository +curl -fsSL https://download.opensuse.org/repositories/isv:/cri-o:/stable:/$CRIO_VERSION/deb/Release.key | + sudo gpg --dearmor -o /etc/apt/keyrings/cri-o-apt-keyring.gpg + +echo "deb [signed-by=/etc/apt/keyrings/cri-o-apt-keyring.gpg] https://download.opensuse.org/repositories/isv:/cri-o:/stable:/$CRIO_VERSION/deb/ /" | + sudo tee /etc/apt/sources.list.d/cri-o.list + +# Install the packages +sudo apt-get update +sudo apt-get install -y cri-o + +# Start CRI-O +sudo systemctl start crio.service diff --git a/utils/install-kubernetes-with-kubeadm.sh b/utils/install-kubeadm.sh old mode 100644 new mode 100755 similarity index 100% rename from utils/install-kubernetes-with-kubeadm.sh rename to utils/install-kubeadm.sh From 3d8b58ab8219504aa5461651f76070b18f49e1fd Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Thu, 15 May 2025 17:43:14 +0000 Subject: [PATCH 32/44] [Doc] Added script to join worker nodes. Elaborated control plane init script and cni installation. Signed-off-by: insukim1994 --- ...h => init-kubernetes-controlplane-node.sh} | 4 +++ utils/install-cri-o.sh | 6 ++++ utils/join-kubernetes-worker-node.sh | 30 +++++++++++++++++++ 3 files changed, 40 insertions(+) rename utils/{create-kubernetes-controlplane-node.sh => init-kubernetes-controlplane-node.sh} (85%) create mode 100755 utils/join-kubernetes-worker-node.sh diff --git a/utils/create-kubernetes-controlplane-node.sh b/utils/init-kubernetes-controlplane-node.sh similarity index 85% rename from utils/create-kubernetes-controlplane-node.sh rename to utils/init-kubernetes-controlplane-node.sh index 8a954407e..ebd340cdd 100755 --- a/utils/create-kubernetes-controlplane-node.sh +++ b/utils/init-kubernetes-controlplane-node.sh @@ -34,3 +34,7 @@ sudo kubeadm init --cri-socket=unix:///var/run/crio/crio.sock # kubeadm join --token \ # --discovery-token-ca-cert-hash # -------------------------------------------------------------------------------- + +# Make sure to save the following command from your output: +# sudo kubeadm join --token \ +# --discovery-token-ca-cert-hash --cri-socket=unix:///var/run/crio/crio.sock diff --git a/utils/install-cri-o.sh b/utils/install-cri-o.sh index c08d0e147..eab9ef642 100755 --- a/utils/install-cri-o.sh +++ b/utils/install-cri-o.sh @@ -1,6 +1,8 @@ #!/bin/bash # Refer to https://github.com/cri-o/packaging/blob/main/README.md#distributions-using-deb-packages +# and +# https://github.com/cri-o/cri-o/blob/main/contrib/cni/README.md#configuration-directory # for more information. # Install the dependencies for adding repositories @@ -22,3 +24,7 @@ sudo apt-get install -y cri-o # Start CRI-O sudo systemctl start crio.service + +# Install CNI (container network interface) plugins +wget https://raw.githubusercontent.com/cri-o/cri-o/refs/heads/main/contrib/cni/10-crio-bridge.conflist +sudo cp 10-crio-bridge.conflist /etc/cni/net.d diff --git a/utils/join-kubernetes-worker-node.sh b/utils/join-kubernetes-worker-node.sh new file mode 100755 index 000000000..c6140d54a --- /dev/null +++ b/utils/join-kubernetes-worker-node.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# You got following output from previous control node initialization: + +# -------------------------------------------------------------------------------- +# Your Kubernetes control-plane has initialized successfully! +# +# ... +# +# Then you can join any number of worker nodes by running the following on each as root: +# +# kubeadm join --token \ +# --discovery-token-ca-cert-hash sha256: +# -------------------------------------------------------------------------------- + +# Make sure to execute the following command on your worker node: +sudo kubeadm join --token \ + --discovery-token-ca-cert-hash sha256: --cri-socket=unix:///var/run/crio/crio.sock + +# If you lost above information, you can get the token and hash by running following command on your CONTROL PLANE node: +# To get +kubectl get nodes -o wide | grep -i control-plane | awk '{printf $6}' + +# To get +sudo kubeadm token create + +# To get +openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \ +openssl rsa -pubin -outform der 2>/dev/null | \ +sha256sum | awk '{print $1}' From 52ec88732d09d1692bb9c223c8ddabd039edb099 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Thu, 15 May 2025 18:14:47 +0000 Subject: [PATCH 33/44] [Doc] Added nvidia gpu setup script for each node. Signed-off-by: insukim1994 --- utils/install-cri-o.sh | 4 +-- utils/nvidia-gpu-setup-k8s.sh | 61 +++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100755 utils/nvidia-gpu-setup-k8s.sh diff --git a/utils/install-cri-o.sh b/utils/install-cri-o.sh index eab9ef642..c3ccef722 100755 --- a/utils/install-cri-o.sh +++ b/utils/install-cri-o.sh @@ -26,5 +26,5 @@ sudo apt-get install -y cri-o sudo systemctl start crio.service # Install CNI (container network interface) plugins -wget https://raw.githubusercontent.com/cri-o/cri-o/refs/heads/main/contrib/cni/10-crio-bridge.conflist -sudo cp 10-crio-bridge.conflist /etc/cni/net.d +wget https://raw.githubusercontent.com/cri-o/cri-o/refs/heads/main/contrib/cni/11-crio-ipv4-bridge.conflist +sudo cp 11-crio-ipv4-bridge.conflist /etc/cni/net.d diff --git a/utils/nvidia-gpu-setup-k8s.sh b/utils/nvidia-gpu-setup-k8s.sh new file mode 100755 index 000000000..1c98a216a --- /dev/null +++ b/utils/nvidia-gpu-setup-k8s.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -e + +# Allow users to override the paths for the NVIDIA tools. +: "${NVIDIA_SMI_PATH:=nvidia-smi}" +: "${NVIDIA_CTK_PATH:=nvidia-ctk}" + +# --- Debug and Environment Setup --- +echo "Current PATH: $PATH" +echo "Operating System: $(uname -a)" + +# Get the script directory to reference local scripts reliably. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# --- Install Prerequisites --- +echo "Installing kubectl and helm..." +bash "$SCRIPT_DIR/install-kubectl.sh" +bash "$SCRIPT_DIR/install-helm.sh" + + +# --- Configure BPF (if available) --- +if [ -f /proc/sys/net/core/bpf_jit_harden ]; then + echo "Configuring BPF: Setting net.core.bpf_jit_harden=0" + echo "net.core.bpf_jit_harden=0" | sudo tee -a /etc/sysctl.conf + sudo sysctl -p +else + echo "BPF JIT hardening configuration not available, skipping..." +fi + +# --- NVIDIA GPU Setup --- +GPU_AVAILABLE=false +if command -v "$NVIDIA_SMI_PATH" >/dev/null 2>&1; then + echo "NVIDIA GPU detected via nvidia-smi at: $(command -v "$NVIDIA_SMI_PATH")" + if command -v "$NVIDIA_CTK_PATH" >/dev/null 2>&1; then + echo "nvidia-ctk found at: $(command -v "$NVIDIA_CTK_PATH")" + GPU_AVAILABLE=true + else + echo "nvidia-ctk not found. Please install the NVIDIA Container Toolkit to enable GPU support." + fi +fi + +if [ "$GPU_AVAILABLE" = true ]; then + # Configure Docker for GPU support. + echo "Configuring Docker runtime for GPU support..." + if sudo "$NVIDIA_CTK_PATH" runtime configure --runtime=docker; then + echo "Restarting Docker to apply changes..." + sudo systemctl restart docker + echo "Docker runtime configured successfully." + else + echo "Error: Failed to configure Docker runtime using the NVIDIA Container Toolkit." + exit 1 + fi + + # Install the GPU Operator via Helm. + echo "Adding NVIDIA helm repo and updating..." + helm repo add nvidia https://helm.ngc.nvidia.com/nvidia && helm repo update + echo "Installing GPU Operator..." + helm install --wait gpu-operator -n gpu-operator --create-namespace nvidia/gpu-operator --version=v24.9.1 +fi + +echo "NVIDIA GPU Setup complete." From 9c1bdee4c07da1156842817a258bfdc37d3c2f35 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Fri, 16 May 2025 15:26:32 +0000 Subject: [PATCH 34/44] [Doc] Script modification during testing. Signed-off-by: insukim1994 --- utils/11-crio-ipv4-bridge.conflist | 22 ++++++++++++++++ utils/init-kubernetes-controlplane-node.sh | 7 +++--- ...up-k8s.sh => init-nvidia-gpu-setup-k8s.sh} | 0 utils/install-cri-o.sh | 25 ++++++++++++++++--- 4 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 utils/11-crio-ipv4-bridge.conflist rename utils/{nvidia-gpu-setup-k8s.sh => init-nvidia-gpu-setup-k8s.sh} (100%) diff --git a/utils/11-crio-ipv4-bridge.conflist b/utils/11-crio-ipv4-bridge.conflist new file mode 100644 index 000000000..41a443e07 --- /dev/null +++ b/utils/11-crio-ipv4-bridge.conflist @@ -0,0 +1,22 @@ +{ + "cniVersion": "1.0.0", + "name": "crio", + "plugins": [ + { + "type": "bridge", + "bridge": "cni0", + "isGateway": true, + "ipMasq": true, + "hairpinMode": true, + "ipam": { + "type": "host-local", + "routes": [ + { "dst": "0.0.0.0/0" } + ], + "ranges": [ + [{ "subnet": "10.85.0.0/16" }] + ] + } + } + ] +} diff --git a/utils/init-kubernetes-controlplane-node.sh b/utils/init-kubernetes-controlplane-node.sh index ebd340cdd..03c36a3e4 100755 --- a/utils/init-kubernetes-controlplane-node.sh +++ b/utils/init-kubernetes-controlplane-node.sh @@ -1,13 +1,12 @@ #!/bin/bash -sudo swapoff -a -sudo modprobe br_netfilter -sudo sysctl -w net.ipv4.ip_forward=1 - # Refer to https://v1-32.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ # for more information. # This script will create a Kubernetes cluster using kubeadm. +# Look for a line starting with "default via" +ip route show + # On one of your nodes which to become a control node, execute following command: sudo kubeadm init --cri-socket=unix:///var/run/crio/crio.sock diff --git a/utils/nvidia-gpu-setup-k8s.sh b/utils/init-nvidia-gpu-setup-k8s.sh similarity index 100% rename from utils/nvidia-gpu-setup-k8s.sh rename to utils/init-nvidia-gpu-setup-k8s.sh diff --git a/utils/install-cri-o.sh b/utils/install-cri-o.sh index c3ccef722..ed3904a9f 100755 --- a/utils/install-cri-o.sh +++ b/utils/install-cri-o.sh @@ -25,6 +25,25 @@ sudo apt-get install -y cri-o # Start CRI-O sudo systemctl start crio.service -# Install CNI (container network interface) plugins -wget https://raw.githubusercontent.com/cri-o/cri-o/refs/heads/main/contrib/cni/11-crio-ipv4-bridge.conflist -sudo cp 11-crio-ipv4-bridge.conflist /etc/cni/net.d +sudo swapoff -a +sudo modprobe br_netfilter +sudo sysctl -w net.ipv4.ip_forward=1 + +# Update crio config by creating (or editing) /etc/crio/crio.conf +# sudo vi /etc/crio/crio.conf +# [crio.image] +# pause_image="registry.k8s.io/pause:3.10" +# [crio.runtime] +# conmon_cgroup = "pod" +# cgroup_manager = "systemd" + +# sysctl params required by setup, params persist across reboots +cat < Date: Fri, 16 May 2025 18:03:09 +0000 Subject: [PATCH 35/44] [Doc] Elaborated k8s controlplane initialization and worker node join script. Signed-off-by: insukim1994 --- utils/11-crio-ipv4-bridge.conflist | 22 --------------------- utils/init-kubernetes-controlplane-node.sh | 12 ++++++++++- utils/install-calico.sh | 10 ++++++++++ utils/install-cri-o.sh | 23 ++++++++++------------ utils/join-kubernetes-worker-node.sh | 11 +++++++---- 5 files changed, 38 insertions(+), 40 deletions(-) delete mode 100644 utils/11-crio-ipv4-bridge.conflist create mode 100755 utils/install-calico.sh diff --git a/utils/11-crio-ipv4-bridge.conflist b/utils/11-crio-ipv4-bridge.conflist deleted file mode 100644 index 41a443e07..000000000 --- a/utils/11-crio-ipv4-bridge.conflist +++ /dev/null @@ -1,22 +0,0 @@ -{ - "cniVersion": "1.0.0", - "name": "crio", - "plugins": [ - { - "type": "bridge", - "bridge": "cni0", - "isGateway": true, - "ipMasq": true, - "hairpinMode": true, - "ipam": { - "type": "host-local", - "routes": [ - { "dst": "0.0.0.0/0" } - ], - "ranges": [ - [{ "subnet": "10.85.0.0/16" }] - ] - } - } - ] -} diff --git a/utils/init-kubernetes-controlplane-node.sh b/utils/init-kubernetes-controlplane-node.sh index 03c36a3e4..0698f512b 100755 --- a/utils/init-kubernetes-controlplane-node.sh +++ b/utils/init-kubernetes-controlplane-node.sh @@ -4,11 +4,21 @@ # for more information. # This script will create a Kubernetes cluster using kubeadm. +# IMPORTANT: THIS STEP IS REQUIRED FOR CNI SETUP VIA CALICO + # Look for a line starting with "default via" +# For example: default via 10.128.0.1 dev ens5 ip route show +# Or get your network interface's ip address using the following command: +export K8S_NET_IP=$(ip addr show dev $(ip route show | awk '/^default/ {print $5}') | awk '/inet / {print $2}' | cut -d/ -f1) +echo "K8S_NET_IP=${K8S_NET_IP}" + # On one of your nodes which to become a control node, execute following command: -sudo kubeadm init --cri-socket=unix:///var/run/crio/crio.sock +sudo kubeadm init \ + --cri-socket=unix:///var/run/crio/crio.sock \ + --apiserver-advertise-address=${K8S_NET_IP} \ + --pod-network-cidr=192.168.0.0/16 # The output will look like this: # -------------------------------------------------------------------------------- diff --git a/utils/install-calico.sh b/utils/install-calico.sh new file mode 100755 index 000000000..e8bf8608c --- /dev/null +++ b/utils/install-calico.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Refer to https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart +# for more information. + +# Install the Tigera operator and custom resource definitions: +kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.30.0/manifests/tigera-operator.yaml + +# Install Calico by creating the necessary custom resources: +kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.30.0/manifests/custom-resources.yaml diff --git a/utils/install-cri-o.sh b/utils/install-cri-o.sh index ed3904a9f..87c7a7c7f 100755 --- a/utils/install-cri-o.sh +++ b/utils/install-cri-o.sh @@ -22,6 +22,16 @@ echo "deb [signed-by=/etc/apt/keyrings/cri-o-apt-keyring.gpg] https://download.o sudo apt-get update sudo apt-get install -y cri-o +# Update crio config by creating (or editing) /etc/crio/crio.conf +sudo tee /etc/crio/crio.conf > /dev/null < --token \ --discovery-token-ca-cert-hash sha256: --cri-socket=unix:///var/run/crio/crio.sock +exit 0 + # If you lost above information, you can get the token and hash by running following command on your CONTROL PLANE node: -# To get -kubectl get nodes -o wide | grep -i control-plane | awk '{printf $6}' +# To get : +export K8S_NET_IP=$(ip addr show dev $(ip route show | awk '/^default/ {print $5}') | awk '/inet / {print $2}' | cut -d/ -f1) +echo "K8S_NET_IP=${K8S_NET_IP}" -# To get +# To get : sudo kubeadm token create -# To get +# To get : openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \ openssl rsa -pubin -outform der 2>/dev/null | \ sha256sum | awk '{print $1}' From 29fde46db83be7dbf634f1465edf52d13e68006d Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sat, 17 May 2025 07:27:31 +0000 Subject: [PATCH 36/44] [Doc] Elaborated basic pipeline parallelism tutorial document. Signed-off-by: insukim1994 --- .../00-a-install-multinode-kubernetes-env.md | 237 ++++++++++++++++++ tutorials/15-basic-pipeline-parallel.md | 159 ++++++++---- ...-15-minimal-pipeline-parallel-example.yaml | 6 +- 3 files changed, 353 insertions(+), 49 deletions(-) create mode 100644 tutorials/00-a-install-multinode-kubernetes-env.md diff --git a/tutorials/00-a-install-multinode-kubernetes-env.md b/tutorials/00-a-install-multinode-kubernetes-env.md new file mode 100644 index 000000000..e764e5a0d --- /dev/null +++ b/tutorials/00-a-install-multinode-kubernetes-env.md @@ -0,0 +1,237 @@ +# Tutorial: Setting Up a Kubernetes Environment with GPUs on Your GPU Server + +## Introduction + +This tutorial guides you through the process of setting up a Kubernetes environment on a GPU-enabled server. We will install and configure `kubectl`, `helm`, and `minikube`, ensuring GPU compatibility for workloads requiring accelerated computing. By the end of this tutorial, you will have a fully functional Kubernetes environment ready for deploy the vLLM Production Stack. + +## Table of Contents + +- [Introduction](#introduction) +- [Table of Contents](#table-of-contents) +- [Prerequisites](#prerequisites) +- [Steps](#steps) + - [Step 1: Installing kubectl](#step-1-installing-kubectl) + - [Step 2: Installing Helm](#step-2-installing-helm) + - [Step 3: Installing Minikube with GPU Support](#step-3-installing-minikube-with-gpu-support) + - [Step 4: Verifying GPU Configuration](#step-4-verifying-gpu-configuration) + +## Prerequisites + +Before you begin, ensure the following: + +1. **GPU Server Requirements:** + - A server with a GPU and drivers properly installed (e.g., NVIDIA drivers). + - [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) installed for GPU workloads. + +2. **Access and Permissions:** + - Root or administrative access to the server. + - Internet connectivity to download required packages and tools. + +3. **Environment Setup:** + - A Linux-based operating system (e.g., Ubuntu 20.04 or later). + - Basic understanding of Linux shell commands. + +## Steps + +### Step 1: Installing kubectl + +1. Clone the repository and navigate to the [`utils/`](../utils/) folder: + + ```bash + git clone https://github.com/vllm-project/production-stack.git + cd production-stack/utils + ``` + +2. Execute the script [`install-kubectl.sh`](../utils/install-kubectl.sh): + + ```bash + bash install-kubectl.sh + ``` + +3. **Explanation:** + This script downloads the latest version of [`kubectl`](https://kubernetes.io/docs/reference/kubectl), the Kubernetes command-line tool, and places it in your PATH for easy execution. + +4. **Expected Output:** + - Confirmation that `kubectl` was downloaded and installed. + - Verification message using: + + ```bash + kubectl version --client + ``` + + Example output: + + ```plaintext + Client Version: v1.32.1 + ``` + +### Step 2: Installing Helm + +1. Execute the script [`install-helm.sh`](../utils/install-helm.sh): + + ```bash + bash install-helm.sh + ``` + +2. **Explanation:** + - Downloads and installs Helm, a package manager for Kubernetes. + - Places the Helm binary in your PATH. + +3. **Expected Output:** + - Successful installation of Helm. + - Verification message using: + + ```bash + helm version + ``` + + Example output: + + ```plaintext + version.BuildInfo{Version:"v3.17.0", GitCommit:"301108edc7ac2a8ba79e4ebf5701b0b6ce6a31e4", GitTreeState:"clean", GoVersion:"go1.23.4"} + ``` + +### Step 3: Installing Minikube with GPU Support + +Before proceeding, ensure Docker runs without requiring sudo. To add your user to the docker group, run: + +```bash +sudo usermod -aG docker $USER && newgrp docker +``` + +If Minikube is already installed on your system, we recommend uninstalling the existing version before proceeding. You may use one of the following commands based on your operating system and package manager: + +```bash +# Ubuntu / Debian +sudo apt remove minikube + +# RHEL / CentOS / Fedora +sudo yum remove minikube +# or +sudo dnf remove minikube + +# macOS (installed via Homebrew) +brew uninstall minikube + +# Arch Linux +sudo pacman -Rs minikube + +# Windows (via Chocolatey) +choco uninstall minikube + +# Windows (via Scoop) +scoop uninstall minikube +``` + +After removing the previous installation, please execute the script provided below to install the latest version. + +1. Execute the script `install-minikube-cluster.sh`: + + ```bash + bash install-minikube-cluster.sh + ``` + +2. **Explanation:** + - Installs Minikube if not already installed. + - Configures the system to support GPU workloads by enabling the NVIDIA Container Toolkit and starting Minikube with GPU support. + - Installs the NVIDIA `gpu-operator` chart to manage GPU resources within the cluster. + +3. **Expected Output:** + If everything goes smoothly, you should see the example output like following: + + ```plaintext + 😄 minikube v1.35.0 on Ubuntu 22.04 (kvm/amd64) + ❗ minikube skips various validations when --force is supplied; this may lead to unexpected behavior + ✨ Using the docker driver based on user configuration + ...... + ...... + 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default + "nvidia" has been added to your repositories + Hang tight while we grab the latest from your chart repositories... + ...... + ...... + NAME: gpu-operator-1737507918 + LAST DEPLOYED: Wed Jan 22 01:05:21 2025 + NAMESPACE: gpu-operator + STATUS: deployed + REVISION: 1 + TEST SUITE: None + ``` + +4. Some troubleshooting tips for installing gpu-operator: + + If gpu-operator fails to start because of the common seen “too many open files” issue for minikube (and [kind](https://kind.sigs.k8s.io/)), then a quick fix below may be helpful. + + The issue can be observed by one or more gpu-operator pods in `CrashLoopBackOff` status, and be confirmed by checking their logs. For example, + + ```console + $ kubectl -n gpu-operator logs daemonset/nvidia-device-plugin-daemonset -c nvidia-device-plugin + IS_HOST_DRIVER=true + NVIDIA_DRIVER_ROOT=/ + DRIVER_ROOT_CTR_PATH=/host + NVIDIA_DEV_ROOT=/ + DEV_ROOT_CTR_PATH=/host + Starting nvidia-device-plugin + I0131 19:35:42.895845 1 main.go:235] "Starting NVIDIA Device Plugin" version=< + d475b2cf + commit: d475b2cfcf12b983a4975d4fc59d91af432cf28e + > + I0131 19:35:42.895917 1 main.go:238] Starting FS watcher for /var/lib/kubelet/device-plugins + E0131 19:35:42.895933 1 main.go:173] failed to create FS watcher for /var/lib/kubelet/device-plugins/: too many open files + ``` + + The fix is [well documented](https://kind.sigs.k8s.io/docs/user/known-issues#pod-errors-due-to-too-many-open-files) by kind, it also works for minikube. + +### Step 4: Verifying GPU Configuration + +1. Ensure Minikube is running: + + ```bash + minikube status + ``` + + Expected output: + + ```plaintext + minikube + type: Control Plane + host: Running + kubelet: Running + apiserver: Running + kubeconfig: Configured + ``` + +2. Verify GPU access within Kubernetes: + + ```bash + kubectl describe nodes | grep -i gpu + ``` + + Expected output: + + ```plaintext + nvidia.com/gpu: 1 + ... (plus many lines related to gpu information) + ``` + +3. Deploy a test GPU workload: + + ```bash + kubectl run gpu-test --image=nvidia/cuda:12.2.0-runtime-ubuntu22.04 --restart=Never -- nvidia-smi + ``` + + Wait for kubernetes to download and create the pod and then check logs to confirm GPU usage: + + ```bash + kubectl logs gpu-test + ``` + + You should see the nvidia-smi output from the terminal + +## Conclusion + +By following this tutorial, you have successfully set up a Kubernetes environment with GPU support on your server. You are now ready to deploy and test vLLM Production Stack on Kubernetes. For further configuration and workload-specific setups, consult the official documentation for `kubectl`, `helm`, and `minikube`. + +What's next: + +- [01-minimal-helm-installation](https://github.com/vllm-project/production-stack/blob/main/tutorials/01-minimal-helm-installation.md) diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md index 2e2954444..04c7807d5 100644 --- a/tutorials/15-basic-pipeline-parallel.md +++ b/tutorials/15-basic-pipeline-parallel.md @@ -2,43 +2,65 @@ ## Introduction -This tutorial guides you through the basic configurations required to deploy a vLLM serving engine in a Kubernetes environment with distributed inference support using Kuberay. You will learn how to launch the vLLM serving engine with pipeline parallelism. +This tutorial provides a step-by-step guide for configuring and deploying the vLLM serving engine on a multi-node Kubernetes cluster with support for distributed inference using KubeRay. It also explains how to launch the vLLM serving engine with pipeline parallelism enabled. ## Table of Contents 1. [Prerequisites](#prerequisites) -2. [Step 1: Preparing the Configuration File](#step-1-preparing-the-configuration-file) -3. [Step 2: Applying the Configuration](#step-2-applying-the-configuration) -4. [Step 3: Verifying the Ray Cluster](#step-3-verifying-the-deployment) +2. [Step 1: Basic explanation of Ray and Kuberay](#step-1-basic-explanation-of-ray-and-kuberay) +3. [Step 2: Preparing the Configuration File](#step-2-preparing-the-configuration-file) +4. [Step 3: Applying the Configuration](#step-3-applying-the-configuration) +5. [Step 4: Verifying the Ray Cluster](#step-4-verifying-the-deployment) ## Prerequisites -- A Kubernetes environment with GPU support, as set up in the [00-install-kubernetes-env tutorial](00-install-kubernetes-env.md). -- Install kuberay operator on the Kubernetes environment with [00-install-kubernetes-env tutorial](00-install-kubernetes-env.md). +- A Kubernetes cluster with multiple nodes with GPU support, as set up in the [00-a-install-multinode-kubernetes-env tutorial](00-a-install-multinode-kubernetes-env.md). +- Install kuberay operator on the Kubernetes environment with [00-b-install-kuberay-operator tutorial](00-b-install-kuberay-operator.md). - Helm installed on your system. - Access to a HuggingFace token (`HF_TOKEN`). +- A basic understanding of Ray is recommended. For more information, refer to the [official ray documentation](https://docs.ray.io/en/latest/cluster/kubernetes/index.html). -## Step 1: Preparing the Configuration File +## Step 1: Basic explanation of Ray and Kuberay + +1. Ray is a framework designed for distributed workloads, such as distributed training and inference. It operates by running multiple processes—typically containers or pods—to distribute and synchronize tasks efficiently. + +2. Ray organizes these processes into a Ray cluster, which consists of a single head node and multiple worker nodes. The term "node" here refers to a logical process, which can be deployed as a container or pod. + +3. KubeRay is a Kubernetes operator that simplifies the creation and management of Ray clusters within a Kubernetes environment. Without KubeRay, setting up Ray nodes requires manual configuration. + +4. Using KubeRay, you can easily deploy Ray clusters on Kubernetes. These clusters enable distributed inference with vLLM, supporting both tensor parallelism and pipeline parallelism. + +## Step 2: Preparing the Configuration File 1. Locate the example configuration file [`tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml`](assets/values-15-minimal-pipeline-parallel-example.yaml). + 2. Open the file and update the following fields: - - Write your actual huggingface token in `hf_token: ` in the yaml file. + +- Write your actual huggingface token in `hf_token: ` in the yaml file. ### Explanation of Key Items in `values-15-minimal-pipeline-parallel-example.yaml` - **`raySpec`**: Required when using KubeRay to enable pipeline parallelism. -- **`headNode`**: Specifies the resource requirements for the Kuberay head node and must be defined accordingly. +- **`headNode`**: Specifies the resource requirements for the Kuberay head node and must be defined accordingly: + - **`requestCPU`**: The amount of CPU resources requested for Kuberay head pod. + - **`requestMemory`**: Memory allocation for Kuberay head pod. Sufficient memory is required to load the model. + - **`requestGPU`**: Specifies the number of GPUs to allocate for Kuberay head pod. - **`name`**: The unique identifier for your model deployment. - **`repository`**: The Docker repository containing the model's serving engine image. - **`tag`**: Specifies the version of the model image to use. - **`modelURL`**: The URL pointing to the model on Hugging Face or another hosting service. - **`replicaCount`**: The number of total Kuberay worker pods. - **`requestCPU`**: The amount of CPU resources requested per Kuberay worker pod. -- **`requestMemory`**: Memory allocation for each Kuberay worker pod; sufficient memory is required to load the model. +- **`requestMemory`**: Memory allocation for each Kuberay worker pod. Sufficient memory is required to load the model. - **`requestGPU`**: Specifies the number of GPUs to allocate for each Kuberay worker pod. - **`vllmConfig`**: Contains model-specific configurations: - `tensorParallelSize`: Defines the number of GPUs allocated to each worker pod. - - `pipelineParallelSize`: Specifies the degree of pipeline parallelism. `The total number of GPUs` used is calculated as `pipelineParallelSize × tensorParallelSize`. + - `pipelineParallelSize`: Specifies the degree of pipeline parallelism. + - **Important Note:** + - The total number of GPUs required is calculated as: `pipelineParallelSize` × `tensorParallelSize` + - This value must exactly match the sum of: + - `replicaCount` × `requestGPU` (i.e., the total number of GPUs allocated to Ray worker nodes) + - `raySpec.headNode.requestGPU` (i.e., the number of GPUs allocated to the Ray head node). - **`shmSize`**: Configures the shared memory size to ensure adequate memory is available for inter-process communication during tensor and pipeline parallelism execution. - **`hf_token`**: The Hugging Face token for authenticating with the Hugging Face model hub. @@ -51,7 +73,7 @@ servingEngineSpec: headNode: requestCPU: 2 requestMemory: "20Gi" - requestGPU: 1 + requestGPU: 2 modelSpec: - name: "distilgpt2" repository: "vllm/vllm-openai" @@ -62,10 +84,10 @@ servingEngineSpec: requestCPU: 2 requestMemory: "20Gi" - requestGPU: 1 + requestGPU: 2 vllmConfig: - tensorParallelSize: 1 + tensorParallelSize: 2 pipelineParallelSize: 2 shmSize: "20Gi" @@ -73,7 +95,7 @@ servingEngineSpec: hf_token: ``` -## Step 2: Applying the Configuration +## Step 3: Applying the Configuration Deploy the configuration using Helm: @@ -95,7 +117,7 @@ REVISION: 1 TEST SUITE: None ``` -## Step 3: Verifying the Deployment +## Step 4: Verifying the Deployment 1. Check the status of the pods: @@ -108,13 +130,17 @@ TEST SUITE: None You should see the following pods: ```plaintext - NAME READY STATUS RESTARTS AGE - kuberay-operator-f89ddb644-psts7 1/1 Running 0 11m - vllm-deployment-router-8666bf6464-7nz5f 1/1 Running 0 2m34s - vllm-distilgpt2-raycluster-head-xrcgw 1/1 Running 0 2m34s - vllm-distilgpt2-raycluster-ray-worker-92zrr 1/1 Running 0 2m34s + NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES + kuberay-operator-f89ddb644-858bw 1/1 Running 0 12h 192.168.165.203 insudevmachine + vllm-deployment-router-8666bf6464-v97v8 1/1 Running 0 12h 192.168.165.206 insudevmachine + vllm-distilgpt2-raycluster-head-wvqj5 1/1 Running 0 12h 192.168.190.20 instance-20250503-060921 + vllm-distilgpt2-raycluster-ray-worker-fdvnh 1/1 Running 0 12h 192.168.165.207 insudevmachine ``` + - In this example, the production stack is deployed in a Kubernetes environment consisting of two nodes, each equipped with two GPUs. + + - The Ray head and worker nodes are scheduled on separate nodes. A total of four GPUs are utilized, with each node contributing two GPUs. + - The vllm-deployment-router pod functions as the request router, directing incoming traffic to the appropriate model-serving pod. - The vllm-distilgpt2-raycluster-head pod is responsible for running the primary vLLM command. @@ -132,16 +158,16 @@ TEST SUITE: None Ensure there are services for both the serving engine and the router: ```plaintext - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - kuberay-operator ClusterIP 10.99.21.240 8080/TCP 11m - kubernetes ClusterIP 10.96.0.1 443/TCP 6d8h - vllm-distilgpt2-engine-service ClusterIP 10.96.242.69 80/TCP 2m57s - vllm-distilgpt2-raycluster-head-svc ClusterIP None 8000/TCP,8080/TCP 2m56s - vllm-router-service ClusterIP 10.99.111.73 80/TCP 2m57s + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + kuberay-operator ClusterIP 10.97.0.153 8080/TCP 13h + kubernetes ClusterIP 10.96.0.1 443/TCP 13h + vllm-distilgpt2-engine-service ClusterIP 10.106.237.111 80/TCP 12h + vllm-distilgpt2-raycluster-head-svc ClusterIP None 8000/TCP,8080/TCP 12h + vllm-router-service ClusterIP 10.97.229.184 80/TCP 12h ``` - - The `vllm-engine-service` exposes the serving engine. - - The `vllm-router-service` handles routing and load balancing across model-serving pods. + - The `vllm-*-engine-service` exposes the head node of the ray cluster. + - The `vllm-*-router-service` handles routing and load balancing across model-serving pods. 3. Test the health endpoint: @@ -155,7 +181,18 @@ TEST SUITE: None **Note:** Port forwarding must be performed from a separate shell session. If the deployment is configured correctly, you should receive a response similar to the following: ```plaintext - {"object":"list","data":[{"id":"distilbert/distilgpt2","object":"model","created":1746978162,"owned_by":"vllm","root":null}]} + { + "object": "list", + "data": [ + { + "id": "distilbert/distilgpt2", + "object": "model", + "created": 1747465656, + "owned_by": "vllm", + "root": null + } + ] + } ``` You may also perform a basic inference test to validate that pipeline parallelism is functioning as expected. Use the following curl command: @@ -173,15 +210,36 @@ TEST SUITE: None A successful response should resemble the following output: ```plaintext - {"id":"cmpl-27e058ce9f0443dd96b76aced16f8b90","object":"text_completion","created":1746978495,"model":"distilbert/distilgpt2","choices":[{"index":0,"text":" the dim of light lingered as it projected enough","logprobs":null,"finish_reason":"length","stop_reason":null,"prompt_logprobs":null}],"usage":{"prompt_tokens":5,"total_tokens":15,"completion_tokens":10,"prompt_tokens_details":null}} + { + "id": "cmpl-92c4ceef0f1c42c9bba10da8306bf86c", + "object": "text_completion", + "created": 1747465724, + "model": "distilbert/distilgpt2", + "choices": [ + { + "index": 0, + "text": "? Huh, are you all red?\n\n", + "logprobs": null, + "finish_reason": "length", + "stop_reason": null, + "prompt_logprobs": null + } + ], + "usage": { + "prompt_tokens": 5, + "total_tokens": 15, + "completion_tokens": 10, + "prompt_tokens_details": null + } + } ``` You can also monitor GPU usage for each Ray head and worker pod: - ```plaintext - kubectl exec -it vllm-distilgpt2-raycluster-head-xrcgw -- /bin/bash - root@vllm-distilgpt2-raycluster-head-xrcgw:/vllm-workspace# nvidia-smi - Mon May 12 14:51:41 2025 + ```plaintext + kubectl exec -it vllm-distilgpt2-raycluster-head-wvqj5 -- /bin/bash + root@vllm-distilgpt2-raycluster-head-wvqj5:/vllm-workspace# nvidia-smi + Sat May 17 00:10:48 2025 +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 550.90.07 Driver Version: 550.90.07 CUDA Version: 12.4 | |-----------------------------------------+------------------------+----------------------+ @@ -190,7 +248,11 @@ TEST SUITE: None | | | MIG M. | |=========================================+========================+======================| | 0 NVIDIA L4 Off | 00000000:00:03.0 Off | 0 | - | N/A 76C P0 40W / 72W | 20129MiB / 23034MiB | 0% Default | + | N/A 76C P0 35W / 72W | 20313MiB / 23034MiB | 0% Default | + | | | N/A | + +-----------------------------------------+------------------------+----------------------+ + | 1 NVIDIA L4 Off | 00000000:00:04.0 Off | 0 | + | N/A 70C P0 33W / 72W | 20305MiB / 23034MiB | 0% Default | | | | N/A | +-----------------------------------------+------------------------+----------------------+ @@ -199,14 +261,16 @@ TEST SUITE: None | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=========================================================================================| - | 0 N/A N/A 13 C /usr/bin/python3 0MiB | + | 0 N/A N/A 8 C /usr/bin/python3 0MiB | + | 1 N/A N/A 1082 C ray::RayWorkerWrapper 0MiB | +-----------------------------------------------------------------------------------------+ ########################################################################################### - kubectl exec -it vllm-distilgpt2-raycluster-ray-worker-92zrr -- /bin/bash - root@vllm-distilgpt2-raycluster-ray-worker-92zrr:/vllm-workspace# nvidia-smi - Mon May 12 14:51:44 2025 + kubectl exec -it vllm-distilgpt2-raycluster-ray-worker-fdvnh -- /bin/bash + Defaulted container "vllm-ray-worker" out of: vllm-ray-worker, wait-gcs-ready (init) + root@vllm-distilgpt2-raycluster-ray-worker-fdvnh:/vllm-workspace# nvidia-smi + Sat May 17 00:12:06 2025 +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 550.90.07 Driver Version: 550.90.07 CUDA Version: 12.4 | |-----------------------------------------+------------------------+----------------------+ @@ -214,8 +278,12 @@ TEST SUITE: None | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+========================+======================| - | 0 NVIDIA L4 Off | 00000000:00:04.0 Off | 0 | - | N/A 71C P0 39W / 72W | 20119MiB / 23034MiB | 0% Default | + | 0 NVIDIA L4 Off | 00000000:00:03.0 Off | 0 | + | N/A 76C P0 40W / 72W | 20065MiB / 23034MiB | 0% Default | + | | | N/A | + +-----------------------------------------+------------------------+----------------------+ + | 1 NVIDIA L4 Off | 00000000:00:04.0 Off | 0 | + | N/A 72C P0 38W / 72W | 20063MiB / 23034MiB | 0% Default | | | | N/A | +-----------------------------------------+------------------------+----------------------+ @@ -224,12 +292,11 @@ TEST SUITE: None | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=========================================================================================| - | 0 N/A N/A 273 C ray::RayWorkerWrapper 0MiB | + | 0 N/A N/A 243 C ray::RayWorkerWrapper 0MiB | + | 1 N/A N/A 244 C ray::RayWorkerWrapper 0MiB | +-----------------------------------------------------------------------------------------+ ``` -Please refer to Step 3 in the [01-minimal-helm-installation](01-minimal-helm-installation.md) tutorial for querying the deployed vLLM service. - ## Conclusion -In this tutorial, you configured and deployed the vLLM serving engine with support for pipeline parallelism across multiple GPUs within a Kubernetes environment using KubeRay. Additionally, you learned how to verify the deployment and monitor the associated pods to ensure proper operation. For further customization and configuration options, please consult the `values.yaml` file and the Helm chart documentation. +In this tutorial, you configured and deployed the vLLM serving engine with support for pipeline parallelism across multiple GPUs within a multi-node Kubernetes environment using KubeRay. Additionally, you learned how to verify the deployment and monitor the associated pods to ensure proper operation. For further customization and configuration options, please consult the `values.yaml` file and the Helm chart documentation. diff --git a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml index 3f56f6290..62dbf8d54 100644 --- a/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml +++ b/tutorials/assets/values-15-minimal-pipeline-parallel-example.yaml @@ -4,7 +4,7 @@ servingEngineSpec: headNode: requestCPU: 2 requestMemory: "20Gi" - requestGPU: 1 + requestGPU: 2 modelSpec: - name: "distilgpt2" repository: "vllm/vllm-openai" @@ -15,10 +15,10 @@ servingEngineSpec: requestCPU: 2 requestMemory: "20Gi" - requestGPU: 1 + requestGPU: 2 vllmConfig: - tensorParallelSize: 1 + tensorParallelSize: 2 pipelineParallelSize: 2 shmSize: "20Gi" From 4516b63e0c8db30cc3e63948226f82d0947e0533 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sat, 17 May 2025 14:48:03 +0000 Subject: [PATCH 37/44] [Doc] Added guide for settig up kubernetes cluster with 2 nodes (control and worker). Signed-off-by: insukim1994 --- .../00-a-install-multinode-kubernetes-env.md | 390 +++++++++++++----- utils/init-kubernetes-controlplane-node.sh | 49 --- utils/join-kubernetes-worker-node.sh | 33 -- 3 files changed, 282 insertions(+), 190 deletions(-) delete mode 100755 utils/init-kubernetes-controlplane-node.sh delete mode 100755 utils/join-kubernetes-worker-node.sh diff --git a/tutorials/00-a-install-multinode-kubernetes-env.md b/tutorials/00-a-install-multinode-kubernetes-env.md index e764e5a0d..379818e13 100644 --- a/tutorials/00-a-install-multinode-kubernetes-env.md +++ b/tutorials/00-a-install-multinode-kubernetes-env.md @@ -2,7 +2,7 @@ ## Introduction -This tutorial guides you through the process of setting up a Kubernetes environment on a GPU-enabled server. We will install and configure `kubectl`, `helm`, and `minikube`, ensuring GPU compatibility for workloads requiring accelerated computing. By the end of this tutorial, you will have a fully functional Kubernetes environment ready for deploy the vLLM Production Stack. +This tutorial guides you through the process of setting up a Kubernetes environment on multiple GPU-enabled server. We will install and configure `kubeadm`, `kubectl` and `helm`, ensuring GPU compatibility for workloads requiring accelerated computing. By the end of this tutorial, you will have a fully functional multi-node Kubernetes environment ready for deploy the vLLM Production Stack. ## Table of Contents @@ -10,10 +10,12 @@ This tutorial guides you through the process of setting up a Kubernetes environm - [Table of Contents](#table-of-contents) - [Prerequisites](#prerequisites) - [Steps](#steps) - - [Step 1: Installing kubectl](#step-1-installing-kubectl) - - [Step 2: Installing Helm](#step-2-installing-helm) - - [Step 3: Installing Minikube with GPU Support](#step-3-installing-minikube-with-gpu-support) - - [Step 4: Verifying GPU Configuration](#step-4-verifying-gpu-configuration) + - [Step 1: Installing kubeadm on each node](#step-1-installing-kubeadm-on-each-node) + - [Step 2: Installing container runtime on each node](#step-2-installing-container-runtime-on-each-node) + - [Step 3: Setting up a control plane node](#step-3-setting-up-a-control-plane-node) + - [Step 4: Setting and joining a worker node](#step-4-setting-and-joining-a-worker-node) + - [Step 5: Installing container network interface](#step-5-installing-container-network-interface) + - [Step 6: Installing nvidia device plugin](#step-6-installing-nvidia-device-plugin) ## Prerequisites @@ -31,125 +33,343 @@ Before you begin, ensure the following: - A Linux-based operating system (e.g., Ubuntu 20.04 or later). - Basic understanding of Linux shell commands. +4. **Tested Environment:** + - This guide was tested at Debian 11 (bullseye) OS with 24 CPUs, 100Gi of RAM and 300Gi disk space. Thus, some settings might not work on your system depends on your environment. + ## Steps -### Step 1: Installing kubectl +### Step 1: Installing kubeadm on each node + +1. Access to your baremetal server which will become a control plane node. -1. Clone the repository and navigate to the [`utils/`](../utils/) folder: +2. Clone the repository and navigate to the [`utils/`](../utils/) folder: ```bash git clone https://github.com/vllm-project/production-stack.git cd production-stack/utils ``` -2. Execute the script [`install-kubectl.sh`](../utils/install-kubectl.sh): +3. Execute the script [`install-kubeadm.sh`](../utils/install-kubectl.sh): ```bash bash install-kubectl.sh ``` +4. **Expected Output:** + - Confirmation that `kubeadm` was downloaded and installed. + - Verification message using: + + ```bash + kubeadm version + ``` + + Example output: + + ```plaintext + kubeadm version: &version.Info{Major:"1", Minor:"32", GitVersion:"v1.32.4", GitCommit:"59526cd4867447956156ae3a602fcbac10a2c335", GitTreeState:"clean", BuildDate:"2025-04-22T16:02:27Z", GoVersion:"go1.23.6", Compiler:"gc", Platform:"linux/amd64"} + ``` + +5. **Explanation:** + This script downloads v1.32 version of [`kubeadm`](https://v1-32.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/), the Kubernetes command-line tool for managing cluster, kubectl and kubelet on your current node. + +6. Repeat the above step 1 - 3 on your another baremetal server which will become a worker node. + +### Step 2: Installing container runtime on each node + +1. Access to your baremetal server which will become a control plane node + +2. Execute the script [`install-cri-o.sh`](../utils/install-helm.sh): + + ```bash + bash install-cri-o.sh + ``` + 3. **Explanation:** - This script downloads the latest version of [`kubectl`](https://kubernetes.io/docs/reference/kubectl), the Kubernetes command-line tool, and places it in your PATH for easy execution. + - Downloads, installs and configures v1.32 version of cri-o container runtime for your Kubernetes cluster. 4. **Expected Output:** - - Confirmation that `kubectl` was downloaded and installed. + - Successful installation of cri-o runtime. + - Verification message using: + + ```bash + sudo systemctl status crio + ``` + + Example output: + + ```plaintext + ● crio.service - Container Runtime Interface for OCI (CRI-O) + Loaded: loaded (/lib/systemd/system/crio.service; enabled; vendor preset: enabled) + Active: active (running) since Fri 2025-05-16 16:32:31 UTC; 20h ago + Docs: https://github.com/cri-o/cri-o + Main PID: 2332175 (crio) + Tasks: 61 + Memory: 14.4G + CPU: 17min 55.486s + CGroup: /system.slice/crio.service + ``` + +5. **Explanation:** + This script downloads v1.32 version of [`cri-0`](https://github.com/cri-o/packaging/blob/main/README.md#distributions-using-deb-packages), one of container runtimes for Kubernetes for managing pods on your cluster. + +6. Repeat the above step 1 - 5 on your another baremetal server which will become a worker node. + +### Step 3: Setting up a control plane node + +1. Access to your baremetal server which will become a control plane node + +2. Execute the following command and wait for completion: + + ```bash + # Look for a line starting with "default via" + # For example: default via 10.128.0.1 dev ens5 + ip route show + + # Or get your network interface's ip address using the following command: + export K8S_NET_IP=$(ip addr show dev $(ip route show | awk '/^default/ {print $5}') | awk '/inet / {print $2}' | cut -d/ -f1) + echo "K8S_NET_IP=${K8S_NET_IP}" + + # On one of your nodes which to become a control node, execute following command: + sudo kubeadm init \ + --cri-socket=unix:///var/run/crio/crio.sock \ + --apiserver-advertise-address=${K8S_NET_IP} \ + --pod-network-cidr=192.168.0.0/16 + ``` + + Example output: + + ```plaintext + # Your Kubernetes control-plane has initialized successfully! + + # To start using your cluster, you need to run the following as a regular user: + + # mkdir -p $HOME/.kube + # sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config + # sudo chown $(id -u):$(id -g) $HOME/.kube/config + + # Alternatively, if you are the root user, you can run: + + # export KUBECONFIG=/etc/kubernetes/admin.conf + + # You should now deploy a pod network to the cluster. + # Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: + # https://kubernetes.io/docs/concepts/cluster-administration/addons/ + + # Then you can join any number of worker nodes by running the following on each as root: + + # kubeadm join --token \ + # --discovery-token-ca-cert-hash + ``` + + Perform following command to set your kube config: + + ```bash + mkdir -p $HOME/.kube + sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config + sudo chown $(id -u):$(id -g) $HOME/.kube/config + ``` + + If your control plane node contains GPUs and you want pods with GPUs be scheduled on it, you have to remove a taint from the node: + + ```bash + kubectl taint node instance-20250503-060921 node-role.kubernetes.io/control-plane- + ``` + +3. **Expected Output:** + - Successful initialize of control plane node. - Verification message using: ```bash - kubectl version --client + kubectl get nodes -o wide ``` Example output: ```plaintext - Client Version: v1.32.1 + NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME + instance-20250503-060921 Ready control-plane 20h v1.32.4 10.xxx.x.xx Debian GNU/Linux 11 (bullseye) 5.10.0-33-cloud-amd64 cri-o://1.32.4 ``` -### Step 2: Installing Helm + Refer to [`official kubeadm documentation`](https://v1-32.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/) for more information. + +### Step 4: Setting and joining a worker node + +1. Access to your baremetal server which will become a worker node + +2. Execute the following command and wait for completion: + + ```bash + # You got following output from previous control node initialization: + + # -------------------------------------------------------------------------------- + # Your Kubernetes control-plane has initialized successfully! + # + # ... + # + # Then you can join any number of worker nodes by running the following on each as root: + # + # kubeadm join --token \ + # --discovery-token-ca-cert-hash sha256: + # -------------------------------------------------------------------------------- + + # Make sure to execute the following command on your worker node: + sudo kubeadm join :6443 --token \ + --discovery-token-ca-cert-hash sha256: \ + --cri-socket=unix:///var/run/crio/crio.sock + ``` -1. Execute the script [`install-helm.sh`](../utils/install-helm.sh): + If you lost above information, you can get the token and hash by running following command on your CONTROL PLANE node:: ```bash - bash install-helm.sh + # To get : + export K8S_NET_IP=$(ip addr show dev $(ip route show | awk '/^default/ {print $5}') | awk '/inet / {print $2}' | cut -d/ -f1) + echo "K8S_NET_IP=${K8S_NET_IP}" + + # To get : + sudo kubeadm token create + + # To get : + openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \ + openssl rsa -pubin -outform der 2>/dev/null | \ + sha256sum | awk '{print $1}' ``` -2. **Explanation:** - - Downloads and installs Helm, a package manager for Kubernetes. - - Places the Helm binary in your PATH. + Example output: + + ```plaintext + sudo kubeadm join :6443 --token --discovery-token-ca-cert-hash sha256: --cri-socket=unix:///var/run/crio/crio.sock + [preflight] Running pre-flight checks + [preflight] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"... + [preflight] Use 'kubeadm init phase upload-config --config your-config.yaml' to re-upload it. + [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml" + [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env" + [kubelet-start] Starting the kubelet + [kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s + [kubelet-check] The kubelet is healthy after 500.795239ms + [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap + + This node has joined the cluster: + * Certificate signing request was sent to apiserver and a response was received. + * The Kubelet was informed of the new secure connection details. + + Run 'kubectl get nodes' on the control-plane to see this node join the cluster. + ``` + + Copy kube config file from your control plane node to current worker node (with ssh or scp): + + ```bash + mkdir -p $HOME/.kube + scp YOUR_SSH_ACCOUNT:$HOME/.kube/config $HOME/.kube/config + sudo chown $(id -u):$(id -g) $HOME/.kube/config + ``` 3. **Expected Output:** - - Successful installation of Helm. + - Successful initialize of worker node. - Verification message using: ```bash - helm version + kubectl get nodes -o wide ``` Example output: ```plaintext - version.BuildInfo{Version:"v3.17.0", GitCommit:"301108edc7ac2a8ba79e4ebf5701b0b6ce6a31e4", GitTreeState:"clean", GoVersion:"go1.23.4"} + NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME + instance-20250503-060921 Ready control-plane 20h v1.32.4 10.xxx.x.xxx Debian GNU/Linux 11 (bullseye) 5.10.0-33-cloud-amd64 cri-o://1.32.4 + insudevmachine Ready 14m v1.32.4 10.yyy.y.yyy Debian GNU/Linux 11 (bullseye) 5.10.0-33-cloud-amd64 cri-o://1.32.4 ``` -### Step 3: Installing Minikube with GPU Support + Refer to [`official kubeadm documentation`](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/) for more information. + +### Step 5: Installing container network interface + +1. Access to any of your node (controlplane or worker). + +2. Clone the repository and navigate to the [`utils/`](../utils/) folder: + + ```bash + git clone https://github.com/vllm-project/production-stack.git + cd production-stack/utils + ``` + +3. Execute the script [`install-calico.sh`](../utils/install-calico.sh): + + ```bash + bash install-calico.sh + ``` + +4. **Expected Output:** + - Confirmation that `Tigera` operator successfully installed and related custom resources were installed. + - Verification message using: + + ```bash + kubectl get pods -o wide + ``` + + Example output: + + ```plaintext + NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS + GATES + calico-apiserver calico-apiserver-cccf4bb9f-8lbc7 1/1 Running 0 21h 192.168.190.7 instance-20250503-060921 + calico-apiserver calico-apiserver-cccf4bb9f-knn9c 1/1 Running 0 21h 192.168.190.4 instance-20250503-060921 + calico-system calico-kube-controllers-56dfdbb787-c24gd 1/1 Running 0 21h 192.168.190.2 instance-20250503-060921 + calico-system calico-node-dtbcq 1/1 Running 0 21h 10.xxx.xxx.xxx instance-20250503-060921 + calico-system calico-node-jptsp 1/1 Running 0 33m 10.xxx.xxx.xxx insudevmachine + calico-system calico-typha-b7d75bc58-h6vrb 1/1 Running 0 37m 10.xxx.xxx.xxx instance-20250503-060921 + calico-system csi-node-driver-884sn 2/2 Running 0 26m 192.168.165.193 insudevmachine + calico-system csi-node-driver-bb7dl 2/2 Running 0 21h 192.168.190.1 instance-20250503-060921 + calico-system goldmane-7b5b4cd5d9-6bk5p 1/1 Running 0 21h 192.168.190.6 instance-20250503-060921 + calico-system whisker-5dbf545674-hnkpz 2/2 Running 0 21h 192.168.190.8 instance-20250503-060921 + ... + kube-system coredns-668d6bf9bc-5hvx7 1/1 Running 0 21h 192.168.190.3 instance-20250503-060921 + kube-system coredns-668d6bf9bc-wb7qq 1/1 Running 0 21h 192.168.190.5 instance-20250503-060921 + ``` -Before proceeding, ensure Docker runs without requiring sudo. To add your user to the docker group, run: + Make sure to check if each node status is ready and coredns pods are running: -```bash -sudo usermod -aG docker $USER && newgrp docker -``` + ```bash + kubectl get nodes -If Minikube is already installed on your system, we recommend uninstalling the existing version before proceeding. You may use one of the following commands based on your operating system and package manager: + # NAME STATUS ROLES AGE VERSION + # instance-20250503-060921 Ready control-plane 21h v1.32.4 + # insudevmachine Ready 37m v1.32.4 -```bash -# Ubuntu / Debian -sudo apt remove minikube + kubectl get pods -n kube-system | grep -i coredns -# RHEL / CentOS / Fedora -sudo yum remove minikube -# or -sudo dnf remove minikube + # coredns-668d6bf9bc-5hvx7 1/1 Running 0 21h + # coredns-668d6bf9bc-wb7qq 1/1 Running 0 21h + ``` -# macOS (installed via Homebrew) -brew uninstall minikube +5. **Explanation:** + This script downloads v3.30.0 version of [`calico`](https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart), which is one of container network interface for Kubernetes cluster. -# Arch Linux -sudo pacman -Rs minikube +### Step 6: Installing nvidia device plugin -# Windows (via Chocolatey) -choco uninstall minikube +1. Access to any of your node (controlplane or worker). -# Windows (via Scoop) -scoop uninstall minikube -``` +2. Clone the repository and navigate to the [`utils/`](../utils/) folder: -After removing the previous installation, please execute the script provided below to install the latest version. + ```bash + git clone https://github.com/vllm-project/production-stack.git + cd production-stack/utils + ``` -1. Execute the script `install-minikube-cluster.sh`: +3. Execute the script [`init-nvidia-gpu-setup-k8s.sh`](../utils/init-nvidia-gpu-setup-k8s.sh): ```bash - bash install-minikube-cluster.sh + bash init-nvidia-gpu-setup-k8s.sh ``` -2. **Explanation:** - - Installs Minikube if not already installed. +4. **Explanation:** - Configures the system to support GPU workloads by enabling the NVIDIA Container Toolkit and starting Minikube with GPU support. - Installs the NVIDIA `gpu-operator` chart to manage GPU resources within the cluster. -3. **Expected Output:** +5. **Expected Output:** If everything goes smoothly, you should see the example output like following: ```plaintext - 😄 minikube v1.35.0 on Ubuntu 22.04 (kvm/amd64) - ❗ minikube skips various validations when --force is supplied; this may lead to unexpected behavior - ✨ Using the docker driver based on user configuration - ...... - ...... - 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default - "nvidia" has been added to your repositories - Hang tight while we grab the latest from your chart repositories... - ...... - ...... + ... NAME: gpu-operator-1737507918 LAST DEPLOYED: Wed Jan 22 01:05:21 2025 NAMESPACE: gpu-operator @@ -158,7 +378,7 @@ After removing the previous installation, please execute the script provided bel TEST SUITE: None ``` -4. Some troubleshooting tips for installing gpu-operator: +6. Some troubleshooting tips for installing gpu-operator: If gpu-operator fails to start because of the common seen “too many open files” issue for minikube (and [kind](https://kind.sigs.k8s.io/)), then a quick fix below may be helpful. @@ -182,56 +402,10 @@ After removing the previous installation, please execute the script provided bel The fix is [well documented](https://kind.sigs.k8s.io/docs/user/known-issues#pod-errors-due-to-too-many-open-files) by kind, it also works for minikube. -### Step 4: Verifying GPU Configuration - -1. Ensure Minikube is running: - - ```bash - minikube status - ``` - - Expected output: - - ```plaintext - minikube - type: Control Plane - host: Running - kubelet: Running - apiserver: Running - kubeconfig: Configured - ``` - -2. Verify GPU access within Kubernetes: - - ```bash - kubectl describe nodes | grep -i gpu - ``` - - Expected output: - - ```plaintext - nvidia.com/gpu: 1 - ... (plus many lines related to gpu information) - ``` - -3. Deploy a test GPU workload: - - ```bash - kubectl run gpu-test --image=nvidia/cuda:12.2.0-runtime-ubuntu22.04 --restart=Never -- nvidia-smi - ``` - - Wait for kubernetes to download and create the pod and then check logs to confirm GPU usage: - - ```bash - kubectl logs gpu-test - ``` - - You should see the nvidia-smi output from the terminal - ## Conclusion -By following this tutorial, you have successfully set up a Kubernetes environment with GPU support on your server. You are now ready to deploy and test vLLM Production Stack on Kubernetes. For further configuration and workload-specific setups, consult the official documentation for `kubectl`, `helm`, and `minikube`. +By following this tutorial, you have successfully set up a multi-node Kubernetes environment with GPU support on your server. You are now ready to deploy and test vLLM Production Stack on Kubernetes. For further configuration and workload-specific setups, consult the official documentation for `kubectl`, `helm`, and `minikube`. What's next: -- [01-minimal-helm-installation](https://github.com/vllm-project/production-stack/blob/main/tutorials/01-minimal-helm-installation.md) +- [00-b-install-kuberay-operator](https://github.com/vllm-project/production-stack/blob/main/tutorials/00-b-install-kuberay-operator.md) diff --git a/utils/init-kubernetes-controlplane-node.sh b/utils/init-kubernetes-controlplane-node.sh deleted file mode 100755 index 0698f512b..000000000 --- a/utils/init-kubernetes-controlplane-node.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -# Refer to https://v1-32.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ -# for more information. -# This script will create a Kubernetes cluster using kubeadm. - -# IMPORTANT: THIS STEP IS REQUIRED FOR CNI SETUP VIA CALICO - -# Look for a line starting with "default via" -# For example: default via 10.128.0.1 dev ens5 -ip route show - -# Or get your network interface's ip address using the following command: -export K8S_NET_IP=$(ip addr show dev $(ip route show | awk '/^default/ {print $5}') | awk '/inet / {print $2}' | cut -d/ -f1) -echo "K8S_NET_IP=${K8S_NET_IP}" - -# On one of your nodes which to become a control node, execute following command: -sudo kubeadm init \ - --cri-socket=unix:///var/run/crio/crio.sock \ - --apiserver-advertise-address=${K8S_NET_IP} \ - --pod-network-cidr=192.168.0.0/16 - -# The output will look like this: -# -------------------------------------------------------------------------------- -# Your Kubernetes control-plane has initialized successfully! - -# To start using your cluster, you need to run the following as a regular user: - -# mkdir -p $HOME/.kube -# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config -# sudo chown $(id -u):$(id -g) $HOME/.kube/config - -# Alternatively, if you are the root user, you can run: - -# export KUBECONFIG=/etc/kubernetes/admin.conf - -# You should now deploy a pod network to the cluster. -# Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: -# https://kubernetes.io/docs/concepts/cluster-administration/addons/ - -# Then you can join any number of worker nodes by running the following on each as root: - -# kubeadm join --token \ -# --discovery-token-ca-cert-hash -# -------------------------------------------------------------------------------- - -# Make sure to save the following command from your output: -# sudo kubeadm join --token \ -# --discovery-token-ca-cert-hash --cri-socket=unix:///var/run/crio/crio.sock diff --git a/utils/join-kubernetes-worker-node.sh b/utils/join-kubernetes-worker-node.sh deleted file mode 100755 index 5c29aed44..000000000 --- a/utils/join-kubernetes-worker-node.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# You got following output from previous control node initialization: - -# -------------------------------------------------------------------------------- -# Your Kubernetes control-plane has initialized successfully! -# -# ... -# -# Then you can join any number of worker nodes by running the following on each as root: -# -# kubeadm join --token \ -# --discovery-token-ca-cert-hash sha256: -# -------------------------------------------------------------------------------- - -# Make sure to execute the following command on your worker node: -sudo kubeadm join --token \ - --discovery-token-ca-cert-hash sha256: --cri-socket=unix:///var/run/crio/crio.sock - -exit 0 - -# If you lost above information, you can get the token and hash by running following command on your CONTROL PLANE node: -# To get : -export K8S_NET_IP=$(ip addr show dev $(ip route show | awk '/^default/ {print $5}') | awk '/inet / {print $2}' | cut -d/ -f1) -echo "K8S_NET_IP=${K8S_NET_IP}" - -# To get : -sudo kubeadm token create - -# To get : -openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \ -openssl rsa -pubin -outform der 2>/dev/null | \ -sha256sum | awk '{print $1}' From 6d0a8ec5cadc17c71357e3fd191f592812fc4fba Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 18 May 2025 08:05:57 +0000 Subject: [PATCH 38/44] [Doc] Elaborated K8s cluster initialization guide and applied a review comment. Signed-off-by: insukim1994 --- .../00-a-install-multinode-kubernetes-env.md | 56 +++++++++---------- tutorials/15-basic-pipeline-parallel.md | 2 + 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/tutorials/00-a-install-multinode-kubernetes-env.md b/tutorials/00-a-install-multinode-kubernetes-env.md index 379818e13..2f7dae565 100644 --- a/tutorials/00-a-install-multinode-kubernetes-env.md +++ b/tutorials/00-a-install-multinode-kubernetes-env.md @@ -2,7 +2,7 @@ ## Introduction -This tutorial guides you through the process of setting up a Kubernetes environment on multiple GPU-enabled server. We will install and configure `kubeadm`, `kubectl` and `helm`, ensuring GPU compatibility for workloads requiring accelerated computing. By the end of this tutorial, you will have a fully functional multi-node Kubernetes environment ready for deploy the vLLM Production Stack. +This tutorial provides a comprehensive guide to setting up a Kubernetes environment across multiple GPU-enabled servers. It covers the installation and configuration of `kubeadm`, `kubectl`, and `helm`, with a focus on ensuring GPU compatibility for workloads that require accelerated computing. By the end of this tutorial, you will have a fully operational multi-node Kubernetes cluster prepared for deploying the vLLM Production Stack. ## Table of Contents @@ -34,13 +34,13 @@ Before you begin, ensure the following: - Basic understanding of Linux shell commands. 4. **Tested Environment:** - - This guide was tested at Debian 11 (bullseye) OS with 24 CPUs, 100Gi of RAM and 300Gi disk space. Thus, some settings might not work on your system depends on your environment. + - This guide was tested on a Debian 11 (Bullseye) operating system with 24 CPUs, 100 GiB of RAM, and 300 GiB of disk space. Please note that certain configurations or settings may vary or not function as expected on different systems, depending on your specific environment. ## Steps ### Step 1: Installing kubeadm on each node -1. Access to your baremetal server which will become a control plane node. +1. Access to a bare-metal server that will serve as the control plane node. 2. Clone the repository and navigate to the [`utils/`](../utils/) folder: @@ -49,10 +49,10 @@ Before you begin, ensure the following: cd production-stack/utils ``` -3. Execute the script [`install-kubeadm.sh`](../utils/install-kubectl.sh): +3. Execute the script [`install-kubeadm.sh`](../utils/install-kubeadm.sh): ```bash - bash install-kubectl.sh + bash install-kubeadm.sh ``` 4. **Expected Output:** @@ -70,13 +70,13 @@ Before you begin, ensure the following: ``` 5. **Explanation:** - This script downloads v1.32 version of [`kubeadm`](https://v1-32.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/), the Kubernetes command-line tool for managing cluster, kubectl and kubelet on your current node. + This script downloads version 1.32 of [`kubeadm`](https://v1-32.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/), the Kubernetes command-line tool for cluster management, along with kubectl and kubelet, on the current node. -6. Repeat the above step 1 - 3 on your another baremetal server which will become a worker node. +6. Repeat steps 1 to 3 on your other bare-metal server, which will serve as a worker node. ### Step 2: Installing container runtime on each node -1. Access to your baremetal server which will become a control plane node +1. Access to a bare-metal server that will serve as the control plane node. 2. Execute the script [`install-cri-o.sh`](../utils/install-helm.sh): @@ -84,10 +84,7 @@ Before you begin, ensure the following: bash install-cri-o.sh ``` -3. **Explanation:** - - Downloads, installs and configures v1.32 version of cri-o container runtime for your Kubernetes cluster. - -4. **Expected Output:** +3. **Expected Output:** - Successful installation of cri-o runtime. - Verification message using: @@ -109,16 +106,19 @@ Before you begin, ensure the following: CGroup: /system.slice/crio.service ``` +4. **Explanation:** + - Downloads, installs and configures v1.32 version of cri-o container runtime for your Kubernetes cluster. + 5. **Explanation:** This script downloads v1.32 version of [`cri-0`](https://github.com/cri-o/packaging/blob/main/README.md#distributions-using-deb-packages), one of container runtimes for Kubernetes for managing pods on your cluster. -6. Repeat the above step 1 - 5 on your another baremetal server which will become a worker node. +6. Repeat steps 1 to 2 on your other bare-metal server, which will serve as a worker node. ### Step 3: Setting up a control plane node -1. Access to your baremetal server which will become a control plane node +1. Access to a bare-metal server that will serve as the control plane node. -2. Execute the following command and wait for completion: +2. Execute the following command and wait for it to complete: ```bash # Look for a line starting with "default via" @@ -129,7 +129,7 @@ Before you begin, ensure the following: export K8S_NET_IP=$(ip addr show dev $(ip route show | awk '/^default/ {print $5}') | awk '/inet / {print $2}' | cut -d/ -f1) echo "K8S_NET_IP=${K8S_NET_IP}" - # On one of your nodes which to become a control node, execute following command: + # On one of the nodes designated to become a control plane node, execute the following command: sudo kubeadm init \ --cri-socket=unix:///var/run/crio/crio.sock \ --apiserver-advertise-address=${K8S_NET_IP} \ @@ -169,14 +169,14 @@ Before you begin, ensure the following: sudo chown $(id -u):$(id -g) $HOME/.kube/config ``` - If your control plane node contains GPUs and you want pods with GPUs be scheduled on it, you have to remove a taint from the node: + If your control plane node is equipped with GPUs and you want GPU-enabled pods to be scheduled on it, you must remove the default taint from the node: ```bash kubectl taint node instance-20250503-060921 node-role.kubernetes.io/control-plane- ``` 3. **Expected Output:** - - Successful initialize of control plane node. + - Successful initialization of control plane node. - Verification message using: ```bash @@ -194,9 +194,9 @@ Before you begin, ensure the following: ### Step 4: Setting and joining a worker node -1. Access to your baremetal server which will become a worker node +1. Access to a bare-metal server that will serve as the worker node. -2. Execute the following command and wait for completion: +2. Execute the following command and wait for it to complete: ```bash # You got following output from previous control node initialization: @@ -212,7 +212,7 @@ Before you begin, ensure the following: # --discovery-token-ca-cert-hash sha256: # -------------------------------------------------------------------------------- - # Make sure to execute the following command on your worker node: + # Execute the following command on your worker node: sudo kubeadm join :6443 --token \ --discovery-token-ca-cert-hash sha256: \ --cri-socket=unix:///var/run/crio/crio.sock @@ -264,7 +264,7 @@ Before you begin, ensure the following: ``` 3. **Expected Output:** - - Successful initialize of worker node. + - Successful initialization of worker node. - Verification message using: ```bash @@ -283,7 +283,7 @@ Before you begin, ensure the following: ### Step 5: Installing container network interface -1. Access to any of your node (controlplane or worker). +1. Access to a bare-metal server that will serve as the control plane node. 2. Clone the repository and navigate to the [`utils/`](../utils/) folder: @@ -299,7 +299,7 @@ Before you begin, ensure the following: ``` 4. **Expected Output:** - - Confirmation that `Tigera` operator successfully installed and related custom resources were installed. + - Confirmation that the `Tigera` operator and its associated custom resources have been successfully installed. - Verification message using: ```bash @@ -326,7 +326,7 @@ Before you begin, ensure the following: kube-system coredns-668d6bf9bc-wb7qq 1/1 Running 0 21h 192.168.190.5 instance-20250503-060921 ``` - Make sure to check if each node status is ready and coredns pods are running: + Ensure that the status of each node is marked as “Ready” and that the CoreDNS pods are running: ```bash kubectl get nodes @@ -342,11 +342,11 @@ Before you begin, ensure the following: ``` 5. **Explanation:** - This script downloads v3.30.0 version of [`calico`](https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart), which is one of container network interface for Kubernetes cluster. + This script downloads version 3.30.0 of [`calico`](https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart), a container network interface (CNI) plugin for Kubernetes clusters. ### Step 6: Installing nvidia device plugin -1. Access to any of your node (controlplane or worker). +1. Access to a bare-metal server that will serve as the control plane node. 2. Clone the repository and navigate to the [`utils/`](../utils/) folder: @@ -404,7 +404,7 @@ Before you begin, ensure the following: ## Conclusion -By following this tutorial, you have successfully set up a multi-node Kubernetes environment with GPU support on your server. You are now ready to deploy and test vLLM Production Stack on Kubernetes. For further configuration and workload-specific setups, consult the official documentation for `kubectl`, `helm`, and `minikube`. +By completing this tutorial, you have successfully established a multi-node Kubernetes environment with GPU support on your servers. You are now prepared to deploy and test the vLLM Production Stack within this Kubernetes cluster. For additional configuration and workload-specific guidance, please refer to the official documentation for `kubectl`, `helm`, and `minikube`. What's next: diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md index 04c7807d5..252dc12ce 100644 --- a/tutorials/15-basic-pipeline-parallel.md +++ b/tutorials/15-basic-pipeline-parallel.md @@ -122,6 +122,7 @@ TEST SUITE: None 1. Check the status of the pods: ```bash + kubectl wait --for=condition=ready pod -l environment=router,release=router --namespace=default --timeout=60s && \ kubectl get pods ``` @@ -130,6 +131,7 @@ TEST SUITE: None You should see the following pods: ```plaintext + pod/vllm-deployment-router-8666bf6464-v97v8 condition met NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kuberay-operator-f89ddb644-858bw 1/1 Running 0 12h 192.168.165.203 insudevmachine vllm-deployment-router-8666bf6464-v97v8 1/1 Running 0 12h 192.168.165.206 insudevmachine From 57d3aad2fb031c43cc6df8ce4267a2c1b7d087a0 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 18 May 2025 08:11:55 +0000 Subject: [PATCH 39/44] [Chore] Strict total number of ray node checking. Tested helm chart with helm template command. Signed-off-by: insukim1994 --- helm/templates/ray-cluster.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/templates/ray-cluster.yaml b/helm/templates/ray-cluster.yaml index f45b5342b..3a1454c30 100644 --- a/helm/templates/ray-cluster.yaml +++ b/helm/templates/ray-cluster.yaml @@ -489,7 +489,7 @@ data: logging.info(f"Ray cluster status: {alive_count}/{expected_nodes} nodes alive.") - if alive_count >= expected_nodes: + if alive_count == expected_nodes: logging.info("Cluster is ready.") sys.exit(0) else: From 85fc5ce2512149dba278c514c9551af07ffc5711 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Sun, 18 May 2025 14:24:38 +0000 Subject: [PATCH 40/44] [Doc] Elaborated important note when applying pipeline parallelism (with ray). Signed-off-by: insukim1994 --- tutorials/15-basic-pipeline-parallel.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md index 252dc12ce..c1b949d00 100644 --- a/tutorials/15-basic-pipeline-parallel.md +++ b/tutorials/15-basic-pipeline-parallel.md @@ -54,13 +54,16 @@ This tutorial provides a step-by-step guide for configuring and deploying the vL - **`requestMemory`**: Memory allocation for each Kuberay worker pod. Sufficient memory is required to load the model. - **`requestGPU`**: Specifies the number of GPUs to allocate for each Kuberay worker pod. - **`vllmConfig`**: Contains model-specific configurations: - - `tensorParallelSize`: Defines the number of GPUs allocated to each worker pod. - - `pipelineParallelSize`: Specifies the degree of pipeline parallelism. + - `tensorParallelSize`: Specifies the number of GPUs assigned to each worker pod. This value must be identical to both `requestGPU` and `raySpec.headNode.requestGPU`. + - `pipelineParallelSize`: Indicates the level of pipeline parallelism. This value must be equal to `replicaCount + 1`, representing the total number of Ray cluster nodes, including both head and worker nodes. - **Important Note:** - - The total number of GPUs required is calculated as: `pipelineParallelSize` × `tensorParallelSize` - - This value must exactly match the sum of: - - `replicaCount` × `requestGPU` (i.e., the total number of GPUs allocated to Ray worker nodes) - - `raySpec.headNode.requestGPU` (i.e., the number of GPUs allocated to the Ray head node). + - The total number of GPUs required is computed as `pipelineParallelSize × tensorParallelSize`. + - This total must exactly match the sum of: + - `replicaCount × requestGPU` (the total number of GPUs allocated to Ray worker nodes), and + - `raySpec.headNode.requestGPU` (the number of GPUs allocated to the Ray head node). + - The `requestGPU` value for the Ray head node must be identical to that of each worker node. + - `tensorParallelSize` defines the number of GPUs allocated per Ray node (including both head and worker nodes), and must be consistent across all nodes. + - `pipelineParallelSize` represents the total number of Ray nodes, and must therefore be set to replicaCount + 1 (i.e., the number of worker nodes plus the head node). - **`shmSize`**: Configures the shared memory size to ensure adequate memory is available for inter-process communication during tensor and pipeline parallelism execution. - **`hf_token`**: The Hugging Face token for authenticating with the Hugging Face model hub. From 9c5d2f8738203ad3b882d3784657b5a066e12404 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Mon, 19 May 2025 13:05:03 +0000 Subject: [PATCH 41/44] [Doc] Elaborated basic pipeline parallelism tutorial example. Signed-off-by: insukim1994 --- tutorials/15-basic-pipeline-parallel.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tutorials/15-basic-pipeline-parallel.md b/tutorials/15-basic-pipeline-parallel.md index c1b949d00..2d5b6d0b0 100644 --- a/tutorials/15-basic-pipeline-parallel.md +++ b/tutorials/15-basic-pipeline-parallel.md @@ -44,7 +44,7 @@ This tutorial provides a step-by-step guide for configuring and deploying the vL - **`headNode`**: Specifies the resource requirements for the Kuberay head node and must be defined accordingly: - **`requestCPU`**: The amount of CPU resources requested for Kuberay head pod. - **`requestMemory`**: Memory allocation for Kuberay head pod. Sufficient memory is required to load the model. - - **`requestGPU`**: Specifies the number of GPUs to allocate for Kuberay head pod. + - **`requestGPU`**: Defines the number of GPUs to allocate for the KubeRay head pod. Currently, the Ray head node must also participate in both tensor parallelism and pipeline parallelism. This requirement exists because the `vllm serve ...` command is executed on the Ray head node, and vLLM mandates that the pod where this command is run must have at least one visible GPU. - **`name`**: The unique identifier for your model deployment. - **`repository`**: The Docker repository containing the model's serving engine image. - **`tag`**: Specifies the version of the model image to use. @@ -69,6 +69,8 @@ This tutorial provides a step-by-step guide for configuring and deploying the vL ### Example Snippet +In the following example, we configure a total of two Ray nodes each equipped with two GPUs (one head node and one worker node) to serve a distilgpt2 model. We set the tensor parallelism size to 2, as each node contains two GPUs, and the pipeline parallelism size to 2, corresponding to the two Ray nodes being utilized. + ```yaml servingEngineSpec: runtimeClassName: "" From a657a126a6930d1e2b82313d53a081497a8c7909 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Tue, 20 May 2025 13:02:12 +0000 Subject: [PATCH 42/44] [Doc] Review updates (prevent duplicated line appends & added warning message of docker restart). Signed-off-by: insukim1994 --- utils/init-nvidia-gpu-setup-k8s.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/utils/init-nvidia-gpu-setup-k8s.sh b/utils/init-nvidia-gpu-setup-k8s.sh index 1c98a216a..c49d4144e 100755 --- a/utils/init-nvidia-gpu-setup-k8s.sh +++ b/utils/init-nvidia-gpu-setup-k8s.sh @@ -17,11 +17,12 @@ echo "Installing kubectl and helm..." bash "$SCRIPT_DIR/install-kubectl.sh" bash "$SCRIPT_DIR/install-helm.sh" - # --- Configure BPF (if available) --- if [ -f /proc/sys/net/core/bpf_jit_harden ]; then echo "Configuring BPF: Setting net.core.bpf_jit_harden=0" - echo "net.core.bpf_jit_harden=0" | sudo tee -a /etc/sysctl.conf + if ! grep -q "net.core.bpf_jit_harden=0" /etc/sysctl.conf; then + echo "net.core.bpf_jit_harden=0" | sudo tee -a /etc/sysctl.conf + fi sudo sysctl -p else echo "BPF JIT hardening configuration not available, skipping..." @@ -44,6 +45,7 @@ if [ "$GPU_AVAILABLE" = true ]; then echo "Configuring Docker runtime for GPU support..." if sudo "$NVIDIA_CTK_PATH" runtime configure --runtime=docker; then echo "Restarting Docker to apply changes..." + echo "WARNING: Restarting Docker will stop and restart all containers." sudo systemctl restart docker echo "Docker runtime configured successfully." else From 3c7810f4268fdcb51174ff14d03b980828fef3e7 Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Fri, 23 May 2025 05:25:37 +0000 Subject: [PATCH 43/44] [Doc] Review updates (elaborated prerequisites for kuberay operator installation). Signed-off-by: insukim1994 --- tutorials/00-b-install-kuberay-operator.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tutorials/00-b-install-kuberay-operator.md b/tutorials/00-b-install-kuberay-operator.md index 4ef1cb28e..470521e1c 100644 --- a/tutorials/00-b-install-kuberay-operator.md +++ b/tutorials/00-b-install-kuberay-operator.md @@ -30,7 +30,9 @@ Before you begin, ensure the following: - Basic understanding of Linux shell commands. 4. **Kubernetes Installation:** - - Follow the instructions in [`00-install-kubernetes-env.md`](00-install-kubernetes-env.md) to set up your Kubernetes environment. + - To quickly and easily set up a single-node Kubernetes environment, you may install Minikube by following the instructions provided in[`00-install-kubernetes-env.md`](00-install-kubernetes-env.md). + - For setting up a multi-node cluster or a more generalized Kubernetes environment, you may install Kubernetes from scratch using Kubeadm. This involves configuring the container runtime and container network interface (CNI), as outlined in [`00-a-install-multinode-kubernetes-env.md`](00-a-install-multinode-kubernetes-env.md) + - If you already have a running Kubernetes cluster, you may skip this step. 5. **Kuberay Concept Review:** - Review the [`official KubeRay documentation`](https://docs.ray.io/en/latest/cluster/kubernetes/index.html) for additional context and best practices. From 90f32986df03fab758021fb5a2f2b0002574622b Mon Sep 17 00:00:00 2001 From: insukim1994 Date: Fri, 23 May 2025 09:07:14 +0000 Subject: [PATCH 44/44] [Bugfix] Fixed version typo of lmcache from toml file. Signed-off-by: insukim1994 --- pyproject.toml | 2 +- uv.lock | 208 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 150 insertions(+), 60 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 64559c05e..e185277d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ semantic_cache = [ "huggingface-hub==0.25.2", # downgrade to 0.25.2 to avoid breaking changes ] lmcache = [ - "lmcache==0.2.11", + "lmcache==0.2.1", ] [tool.pytest.ini_options] diff --git a/uv.lock b/uv.lock index 91b65982f..4af014b99 100644 --- a/uv.lock +++ b/uv.lock @@ -229,6 +229,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coverage" +version = "7.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/b4/a707d96c2c1ce9402ce1ce7124c53b9e4e1f3e617652a5ed2fbba4c9b4be/coverage-7.8.1.tar.gz", hash = "sha256:d41d4da5f2871b1782c6b74948d2d37aac3a5b39b43a6ba31d736b97a02ae1f1", size = 812193, upload-time = "2025-05-21T12:39:46.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/78/781501aa4759026dcef8024b404cacc4094348e5e199ed660c31f4650a33/coverage-7.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d8f844e837374a9497e11722d9eb9dfeb33b1b5d31136786c39a4c1a3073c6d", size = 211875, upload-time = "2025-05-21T12:38:20.497Z" }, + { url = "https://files.pythonhosted.org/packages/e6/00/a8a4548c22b73f8fd4373714f5a4cce3584827e2603847a8d90fba129807/coverage-7.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9cd54a762667c32112df5d6f059c5d61fa532ee06460948cc5bcbf60c502f5c9", size = 212129, upload-time = "2025-05-21T12:38:22.085Z" }, + { url = "https://files.pythonhosted.org/packages/9e/41/5cdc34afdc53b7f200439eb915f50d6ba17e3b0b5cdb6bb04d0ed9662703/coverage-7.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:958b513e23286178b513a6b4d975fe9e7cddbcea6e5ebe8d836e4ef067577154", size = 246176, upload-time = "2025-05-21T12:38:23.802Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1f/ca8e37fdd282dd6ebc4191a9fafcb46b6bf75e05a0fd796d6907399e44ea/coverage-7.8.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b31756ea647b6ef53190f6b708ad0c4c2ea879bc17799ba5b0699eee59ecf7b", size = 243068, upload-time = "2025-05-21T12:38:25.755Z" }, + { url = "https://files.pythonhosted.org/packages/cf/89/727503da5870fe1031ec443699beab44e02548d9873fe0a60adf6589fdd1/coverage-7.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccad4e29ac1b6f75bfeedb2cac4860fe5bd9e0a2f04c3e3218f661fa389ab101", size = 245329, upload-time = "2025-05-21T12:38:28.69Z" }, + { url = "https://files.pythonhosted.org/packages/25/1f/6935baf26071a66f390159ceb5c5bccfc898d00a90166b6ffc61b964856a/coverage-7.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:452f3831c64f5f50260e18a89e613594590d6ceac5206a9b7d76ba43586b01b3", size = 245100, upload-time = "2025-05-21T12:38:31.339Z" }, + { url = "https://files.pythonhosted.org/packages/3b/1f/0e5d68b12deb8a5c9648f61b515798e201f72fec17a0c7373a5f4903f8d8/coverage-7.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9296df6a33b8539cd753765eb5b47308602263a14b124a099cbcf5f770d7cf90", size = 243314, upload-time = "2025-05-21T12:38:34.974Z" }, + { url = "https://files.pythonhosted.org/packages/21/5d/375ba28a78e96a06ef0f1572b393e3fefd9d0deecf3ef9995eff1b1cea67/coverage-7.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d52d79dfd3b410b153b6d65b0e3afe834eca2b969377f55ad73c67156d35af0d", size = 244487, upload-time = "2025-05-21T12:38:37.84Z" }, + { url = "https://files.pythonhosted.org/packages/08/92/1b7fdf0924d8e6d7c2418d313c12d6e19c9a748448faedcc017082ec5b63/coverage-7.8.1-cp312-cp312-win32.whl", hash = "sha256:ebdf212e1ed85af63fa1a76d556c0a3c7b34348ffba6e145a64b15f003ad0a2b", size = 214367, upload-time = "2025-05-21T12:38:39.676Z" }, + { url = "https://files.pythonhosted.org/packages/07/b1/632f9e128ee9e149cfa80a3130362684244668b0dc6efedd6e466baaeb48/coverage-7.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:c04a7903644ccea8fa07c3e76db43ca31c8d453f93c5c94c0f9b82efca225543", size = 215169, upload-time = "2025-05-21T12:38:42.497Z" }, + { url = "https://files.pythonhosted.org/packages/ed/0a/696a8d6c245a72f61589e2015a633fab5aacd8c916802df41d23e387b442/coverage-7.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd5c305faa2e69334a53061b3168987847dadc2449bab95735242a9bde92fde8", size = 211902, upload-time = "2025-05-21T12:38:44.54Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2f/0c065dfaf497586cf1693dee2a94e7489a4be840a5bbe765a7a78735268b/coverage-7.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:af6b8cdf0857fd4e6460dd6639c37c3f82163127f6112c1942b5e6a52a477676", size = 212175, upload-time = "2025-05-21T12:38:46.143Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a1/a8a40658f67311c96c3d9073293fefee8a9485906ed531546dffe35fdd4b/coverage-7.8.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e233a56bbf99e4cb134c4f8e63b16c77714e3987daf2c5aa10c3ba8c4232d730", size = 245564, upload-time = "2025-05-21T12:38:47.843Z" }, + { url = "https://files.pythonhosted.org/packages/6e/94/dc36e2256ce484f482ed5b2a103a261009c301cdad237fdefe2a9b6ddeab/coverage-7.8.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dabc70012fd7b58a8040a7bc1b5f71fd0e62e2138aefdd8367d3d24bf82c349", size = 242719, upload-time = "2025-05-21T12:38:49.517Z" }, + { url = "https://files.pythonhosted.org/packages/73/d7/d096859c59f02d4550e6bc9180bd06c88313c32977d7458e0d4ed06ed057/coverage-7.8.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1f8e96455907496b3e4ea16f63bb578da31e17d2805278b193525e7714f17f2", size = 244634, upload-time = "2025-05-21T12:38:51.18Z" }, + { url = "https://files.pythonhosted.org/packages/be/a0/6f4db84d1d3334ca37c2dae02a54761a1a3918aec56faec26f1590077181/coverage-7.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0034ceec8e91fdaf77350901cc48f47efd00f23c220a3f9fc1187774ddf307cb", size = 244824, upload-time = "2025-05-21T12:38:52.789Z" }, + { url = "https://files.pythonhosted.org/packages/96/46/1e74016ba7d9f4242170f9d814454e6483a640332a67c0e139dab7d85762/coverage-7.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82db9344a07dd9106796b9fe8805425633146a7ea7fed5ed07c65a64d0bb79e1", size = 242872, upload-time = "2025-05-21T12:38:54.493Z" }, + { url = "https://files.pythonhosted.org/packages/22/41/51df77f279b49e7dd05ee9dfe746cf8698c873ffdf7fbe57aaee9522ec67/coverage-7.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9772c9e266b2ca4999180c12b90c8efb4c5c9ad3e55f301d78bc579af6467ad9", size = 244179, upload-time = "2025-05-21T12:38:56.762Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/6207522f3afb64592c47353bc79b0e3e6c3f48fde5e5221ab2b80a12e93d/coverage-7.8.1-cp313-cp313-win32.whl", hash = "sha256:6f24a1e2c373a77afae21bc512466a91e31251685c271c5309ee3e557f6e3e03", size = 214395, upload-time = "2025-05-21T12:38:58.631Z" }, + { url = "https://files.pythonhosted.org/packages/43/b8/cd40a8fff1633112ac40edde9006aceaa55b32a84976394a42c33547ef95/coverage-7.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:76a4e1d62505a21971968be61ae17cbdc5e0c483265a37f7ddbbc050f9c0b8ec", size = 215195, upload-time = "2025-05-21T12:39:00.614Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f0/8fea9beb378cdce803ba838293314b21527f4edab58dcbe2e6a5553e7dc8/coverage-7.8.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:35dd5d405a1d378c39f3f30f628a25b0b99f1b8e5bdd78275df2e7b0404892d7", size = 212738, upload-time = "2025-05-21T12:39:02.808Z" }, + { url = "https://files.pythonhosted.org/packages/0c/90/f28953cd1246ad7839874ef97e181f153d4274cc6db21857fbca18b89c97/coverage-7.8.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:87b86a87f8de2e1bd0bcd45faf1b1edf54f988c8857157300e0336efcfb8ede6", size = 212958, upload-time = "2025-05-21T12:39:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/fb/70/3f3d34ef68534afa73aee75537d1daf1e91029738cbf052ef828313aa960/coverage-7.8.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce4553a573edb363d5db12be1c044826878bec039159d6d4eafe826ef773396d", size = 257024, upload-time = "2025-05-21T12:39:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/cf/66/96ab415609b777adfcfa00f29d75d2278da139c0958de7a50dd0023811e6/coverage-7.8.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db181a1896e0bad75b3bf4916c49fd3cf6751f9cc203fe0e0ecbee1fc43590fa", size = 252867, upload-time = "2025-05-21T12:39:08.818Z" }, + { url = "https://files.pythonhosted.org/packages/52/4f/3d48704c62fa5f72447005b8a77cc9cce5e164c2df357433442d17f2ac0a/coverage-7.8.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ce2606a171f9cf7c15a77ca61f979ffc0e0d92cd2fb18767cead58c1d19f58e", size = 255096, upload-time = "2025-05-21T12:39:10.516Z" }, + { url = "https://files.pythonhosted.org/packages/64/1d/e8d4ac647c1967dd3dbc250fb4595b838b7067ad32602a7339ac467d9c5a/coverage-7.8.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4fc4f7cff2495d6d112353c33a439230a6de0b7cd0c2578f1e8d75326f63d783", size = 256276, upload-time = "2025-05-21T12:39:12.177Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e4/62e2f9521f3758dea07bcefc2c9c0dd34fa67d7035b0443c7c3072e6308b/coverage-7.8.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ff619c58322d9d6df0a859dc76c3532d7bdbc125cb040f7cd642141446b4f654", size = 254478, upload-time = "2025-05-21T12:39:14.325Z" }, + { url = "https://files.pythonhosted.org/packages/49/41/7af246f5e68272f97a31a122da5878747e941fef019430485534d1f6a44a/coverage-7.8.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0d6290a466a6f3fadf6add2dd4ec11deba4e1a6e3db2dd284edd497aadf802f", size = 255255, upload-time = "2025-05-21T12:39:16.059Z" }, + { url = "https://files.pythonhosted.org/packages/05/5d/5dacd7915972f82d909f36974c6415667dae08a32478d87dfdbac6788e22/coverage-7.8.1-cp313-cp313t-win32.whl", hash = "sha256:e4e893c7f7fb12271a667d5c1876710fae06d7580343afdb5f3fc4488b73209e", size = 215112, upload-time = "2025-05-21T12:39:18.263Z" }, + { url = "https://files.pythonhosted.org/packages/8b/89/48e77e71e81e5b79fd6471083d087cd69517e5f585b548d87c92d5ae873c/coverage-7.8.1-cp313-cp313t-win_amd64.whl", hash = "sha256:41d142eefbc0bb3be160a77b2c0fbec76f345387676265052e224eb6c67b7af3", size = 216270, upload-time = "2025-05-21T12:39:20.461Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a1/4d968d4605f3a87a809f0c8f495eed81656c93cf6c00818334498ad6ad45/coverage-7.8.1-py3-none-any.whl", hash = "sha256:e54b80885b0e61d346accc5709daf8762471a452345521cc9281604a907162c2", size = 203623, upload-time = "2025-05-21T12:39:43.473Z" }, +] + [[package]] name = "distlib" version = "0.3.9" @@ -614,62 +653,62 @@ wheels = [ [[package]] name = "multidict" -version = "6.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/2c/e367dfb4c6538614a0c9453e510d75d66099edf1c4e69da1b5ce691a1931/multidict-6.4.3.tar.gz", hash = "sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec", size = 89372, upload-time = "2025-04-10T22:20:17.956Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/bb/3abdaf8fe40e9226ce8a2ba5ecf332461f7beec478a455d6587159f1bf92/multidict-6.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f1c2f58f08b36f8475f3ec6f5aeb95270921d418bf18f90dffd6be5c7b0e676", size = 64019, upload-time = "2025-04-10T22:18:23.174Z" }, - { url = "https://files.pythonhosted.org/packages/7e/b5/1b2e8de8217d2e89db156625aa0fe4a6faad98972bfe07a7b8c10ef5dd6b/multidict-6.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:26ae9ad364fc61b936fb7bf4c9d8bd53f3a5b4417142cd0be5c509d6f767e2f1", size = 37925, upload-time = "2025-04-10T22:18:24.834Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e2/3ca91c112644a395c8eae017144c907d173ea910c913ff8b62549dcf0bbf/multidict-6.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:659318c6c8a85f6ecfc06b4e57529e5a78dfdd697260cc81f683492ad7e9435a", size = 37008, upload-time = "2025-04-10T22:18:26.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/23/79bc78146c7ac8d1ac766b2770ca2e07c2816058b8a3d5da6caed8148637/multidict-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1eb72c741fd24d5a28242ce72bb61bc91f8451877131fa3fe930edb195f7054", size = 224374, upload-time = "2025-04-10T22:18:27.714Z" }, - { url = "https://files.pythonhosted.org/packages/86/35/77950ed9ebd09136003a85c1926ba42001ca5be14feb49710e4334ee199b/multidict-6.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3cd06d88cb7398252284ee75c8db8e680aa0d321451132d0dba12bc995f0adcc", size = 230869, upload-time = "2025-04-10T22:18:29.162Z" }, - { url = "https://files.pythonhosted.org/packages/49/97/2a33c6e7d90bc116c636c14b2abab93d6521c0c052d24bfcc231cbf7f0e7/multidict-6.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4543d8dc6470a82fde92b035a92529317191ce993533c3c0c68f56811164ed07", size = 231949, upload-time = "2025-04-10T22:18:30.679Z" }, - { url = "https://files.pythonhosted.org/packages/56/ce/e9b5d9fcf854f61d6686ada7ff64893a7a5523b2a07da6f1265eaaea5151/multidict-6.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a3ebdc068c27e9d6081fca0e2c33fdf132ecea703a72ea216b81a66860adde", size = 231032, upload-time = "2025-04-10T22:18:32.146Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ac/7ced59dcdfeddd03e601edb05adff0c66d81ed4a5160c443e44f2379eef0/multidict-6.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b038f10e23f277153f86f95c777ba1958bcd5993194fda26a1d06fae98b2f00c", size = 223517, upload-time = "2025-04-10T22:18:33.538Z" }, - { url = "https://files.pythonhosted.org/packages/db/e6/325ed9055ae4e085315193a1b58bdb4d7fc38ffcc1f4975cfca97d015e17/multidict-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c605a2b2dc14282b580454b9b5d14ebe0668381a3a26d0ac39daa0ca115eb2ae", size = 216291, upload-time = "2025-04-10T22:18:34.962Z" }, - { url = "https://files.pythonhosted.org/packages/fa/84/eeee6d477dd9dcb7691c3bb9d08df56017f5dd15c730bcc9383dcf201cf4/multidict-6.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bd2b875f4ca2bb527fe23e318ddd509b7df163407b0fb717df229041c6df5d3", size = 228982, upload-time = "2025-04-10T22:18:36.443Z" }, - { url = "https://files.pythonhosted.org/packages/82/94/4d1f3e74e7acf8b0c85db350e012dcc61701cd6668bc2440bb1ecb423c90/multidict-6.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c2e98c840c9c8e65c0e04b40c6c5066c8632678cd50c8721fdbcd2e09f21a507", size = 226823, upload-time = "2025-04-10T22:18:37.924Z" }, - { url = "https://files.pythonhosted.org/packages/09/f0/1e54b95bda7cd01080e5732f9abb7b76ab5cc795b66605877caeb2197476/multidict-6.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66eb80dd0ab36dbd559635e62fba3083a48a252633164857a1d1684f14326427", size = 222714, upload-time = "2025-04-10T22:18:39.807Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a2/f6cbca875195bd65a3e53b37ab46486f3cc125bdeab20eefe5042afa31fb/multidict-6.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c23831bdee0a2a3cf21be057b5e5326292f60472fb6c6f86392bbf0de70ba731", size = 233739, upload-time = "2025-04-10T22:18:41.341Z" }, - { url = "https://files.pythonhosted.org/packages/79/68/9891f4d2b8569554723ddd6154375295f789dc65809826c6fb96a06314fd/multidict-6.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1535cec6443bfd80d028052e9d17ba6ff8a5a3534c51d285ba56c18af97e9713", size = 230809, upload-time = "2025-04-10T22:18:42.817Z" }, - { url = "https://files.pythonhosted.org/packages/e6/72/a7be29ba1e87e4fc5ceb44dabc7940b8005fd2436a332a23547709315f70/multidict-6.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b73e7227681f85d19dec46e5b881827cd354aabe46049e1a61d2f9aaa4e285a", size = 226934, upload-time = "2025-04-10T22:18:44.311Z" }, - { url = "https://files.pythonhosted.org/packages/12/c1/259386a9ad6840ff7afc686da96808b503d152ac4feb3a96c651dc4f5abf/multidict-6.4.3-cp312-cp312-win32.whl", hash = "sha256:8eac0c49df91b88bf91f818e0a24c1c46f3622978e2c27035bfdca98e0e18124", size = 35242, upload-time = "2025-04-10T22:18:46.193Z" }, - { url = "https://files.pythonhosted.org/packages/06/24/c8fdff4f924d37225dc0c56a28b1dca10728fc2233065fafeb27b4b125be/multidict-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:11990b5c757d956cd1db7cb140be50a63216af32cd6506329c2c59d732d802db", size = 38635, upload-time = "2025-04-10T22:18:47.498Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4b/86fd786d03915c6f49998cf10cd5fe6b6ac9e9a071cb40885d2e080fb90d/multidict-6.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a76534263d03ae0cfa721fea40fd2b5b9d17a6f85e98025931d41dc49504474", size = 63831, upload-time = "2025-04-10T22:18:48.748Z" }, - { url = "https://files.pythonhosted.org/packages/45/05/9b51fdf7aef2563340a93be0a663acba2c428c4daeaf3960d92d53a4a930/multidict-6.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:805031c2f599eee62ac579843555ed1ce389ae00c7e9f74c2a1b45e0564a88dd", size = 37888, upload-time = "2025-04-10T22:18:50.021Z" }, - { url = "https://files.pythonhosted.org/packages/0b/43/53fc25394386c911822419b522181227ca450cf57fea76e6188772a1bd91/multidict-6.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c56c179839d5dcf51d565132185409d1d5dd8e614ba501eb79023a6cab25576b", size = 36852, upload-time = "2025-04-10T22:18:51.246Z" }, - { url = "https://files.pythonhosted.org/packages/8a/68/7b99c751e822467c94a235b810a2fd4047d4ecb91caef6b5c60116991c4b/multidict-6.4.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c64f4ddb3886dd8ab71b68a7431ad4aa01a8fa5be5b11543b29674f29ca0ba3", size = 223644, upload-time = "2025-04-10T22:18:52.965Z" }, - { url = "https://files.pythonhosted.org/packages/80/1b/d458d791e4dd0f7e92596667784fbf99e5c8ba040affe1ca04f06b93ae92/multidict-6.4.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3002a856367c0b41cad6784f5b8d3ab008eda194ed7864aaa58f65312e2abcac", size = 230446, upload-time = "2025-04-10T22:18:54.509Z" }, - { url = "https://files.pythonhosted.org/packages/e2/46/9793378d988905491a7806d8987862dc5a0bae8a622dd896c4008c7b226b/multidict-6.4.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d75e621e7d887d539d6e1d789f0c64271c250276c333480a9e1de089611f790", size = 231070, upload-time = "2025-04-10T22:18:56.019Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b8/b127d3e1f8dd2a5bf286b47b24567ae6363017292dc6dec44656e6246498/multidict-6.4.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:995015cf4a3c0d72cbf453b10a999b92c5629eaf3a0c3e1efb4b5c1f602253bb", size = 229956, upload-time = "2025-04-10T22:18:59.146Z" }, - { url = "https://files.pythonhosted.org/packages/0c/93/f70a4c35b103fcfe1443059a2bb7f66e5c35f2aea7804105ff214f566009/multidict-6.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b0fabae7939d09d7d16a711468c385272fa1b9b7fb0d37e51143585d8e72e0", size = 222599, upload-time = "2025-04-10T22:19:00.657Z" }, - { url = "https://files.pythonhosted.org/packages/63/8c/e28e0eb2fe34921d6aa32bfc4ac75b09570b4d6818cc95d25499fe08dc1d/multidict-6.4.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ed4d82f8a1e67eb9eb04f8587970d78fe7cddb4e4d6230b77eda23d27938f9", size = 216136, upload-time = "2025-04-10T22:19:02.244Z" }, - { url = "https://files.pythonhosted.org/packages/72/f5/fbc81f866585b05f89f99d108be5d6ad170e3b6c4d0723d1a2f6ba5fa918/multidict-6.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:062428944a8dc69df9fdc5d5fc6279421e5f9c75a9ee3f586f274ba7b05ab3c8", size = 228139, upload-time = "2025-04-10T22:19:04.151Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ba/7d196bad6b85af2307d81f6979c36ed9665f49626f66d883d6c64d156f78/multidict-6.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b90e27b4674e6c405ad6c64e515a505c6d113b832df52fdacb6b1ffd1fa9a1d1", size = 226251, upload-time = "2025-04-10T22:19:06.117Z" }, - { url = "https://files.pythonhosted.org/packages/cc/e2/fae46a370dce79d08b672422a33df721ec8b80105e0ea8d87215ff6b090d/multidict-6.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7d50d4abf6729921e9613d98344b74241572b751c6b37feed75fb0c37bd5a817", size = 221868, upload-time = "2025-04-10T22:19:07.981Z" }, - { url = "https://files.pythonhosted.org/packages/26/20/bbc9a3dec19d5492f54a167f08546656e7aef75d181d3d82541463450e88/multidict-6.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:43fe10524fb0a0514be3954be53258e61d87341008ce4914f8e8b92bee6f875d", size = 233106, upload-time = "2025-04-10T22:19:09.5Z" }, - { url = "https://files.pythonhosted.org/packages/ee/8d/f30ae8f5ff7a2461177f4d8eb0d8f69f27fb6cfe276b54ec4fd5a282d918/multidict-6.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:236966ca6c472ea4e2d3f02f6673ebfd36ba3f23159c323f5a496869bc8e47c9", size = 230163, upload-time = "2025-04-10T22:19:11Z" }, - { url = "https://files.pythonhosted.org/packages/15/e9/2833f3c218d3c2179f3093f766940ded6b81a49d2e2f9c46ab240d23dfec/multidict-6.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:422a5ec315018e606473ba1f5431e064cf8b2a7468019233dcf8082fabad64c8", size = 225906, upload-time = "2025-04-10T22:19:12.875Z" }, - { url = "https://files.pythonhosted.org/packages/f1/31/6edab296ac369fd286b845fa5dd4c409e63bc4655ed8c9510fcb477e9ae9/multidict-6.4.3-cp313-cp313-win32.whl", hash = "sha256:f901a5aace8e8c25d78960dcc24c870c8d356660d3b49b93a78bf38eb682aac3", size = 35238, upload-time = "2025-04-10T22:19:14.41Z" }, - { url = "https://files.pythonhosted.org/packages/23/57/2c0167a1bffa30d9a1383c3dab99d8caae985defc8636934b5668830d2ef/multidict-6.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1c152c49e42277bc9a2f7b78bd5fa10b13e88d1b0328221e7aef89d5c60a99a5", size = 38799, upload-time = "2025-04-10T22:19:15.869Z" }, - { url = "https://files.pythonhosted.org/packages/c9/13/2ead63b9ab0d2b3080819268acb297bd66e238070aa8d42af12b08cbee1c/multidict-6.4.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be8751869e28b9c0d368d94f5afcb4234db66fe8496144547b4b6d6a0645cfc6", size = 68642, upload-time = "2025-04-10T22:19:17.527Z" }, - { url = "https://files.pythonhosted.org/packages/85/45/f1a751e1eede30c23951e2ae274ce8fad738e8a3d5714be73e0a41b27b16/multidict-6.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d4b31f8a68dccbcd2c0ea04f0e014f1defc6b78f0eb8b35f2265e8716a6df0c", size = 40028, upload-time = "2025-04-10T22:19:19.465Z" }, - { url = "https://files.pythonhosted.org/packages/a7/29/fcc53e886a2cc5595cc4560df333cb9630257bda65003a7eb4e4e0d8f9c1/multidict-6.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:032efeab3049e37eef2ff91271884303becc9e54d740b492a93b7e7266e23756", size = 39424, upload-time = "2025-04-10T22:19:20.762Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f0/056c81119d8b88703971f937b371795cab1407cd3c751482de5bfe1a04a9/multidict-6.4.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e78006af1a7c8a8007e4f56629d7252668344442f66982368ac06522445e375", size = 226178, upload-time = "2025-04-10T22:19:22.17Z" }, - { url = "https://files.pythonhosted.org/packages/a3/79/3b7e5fea0aa80583d3a69c9d98b7913dfd4fbc341fb10bb2fb48d35a9c21/multidict-6.4.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:daeac9dd30cda8703c417e4fddccd7c4dc0c73421a0b54a7da2713be125846be", size = 222617, upload-time = "2025-04-10T22:19:23.773Z" }, - { url = "https://files.pythonhosted.org/packages/06/db/3ed012b163e376fc461e1d6a67de69b408339bc31dc83d39ae9ec3bf9578/multidict-6.4.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f6f90700881438953eae443a9c6f8a509808bc3b185246992c4233ccee37fea", size = 227919, upload-time = "2025-04-10T22:19:25.35Z" }, - { url = "https://files.pythonhosted.org/packages/b1/db/0433c104bca380989bc04d3b841fc83e95ce0c89f680e9ea4251118b52b6/multidict-6.4.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f84627997008390dd15762128dcf73c3365f4ec0106739cde6c20a07ed198ec8", size = 226097, upload-time = "2025-04-10T22:19:27.183Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/910db2618175724dd254b7ae635b6cd8d2947a8b76b0376de7b96d814dab/multidict-6.4.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3307b48cd156153b117c0ea54890a3bdbf858a5b296ddd40dc3852e5f16e9b02", size = 220706, upload-time = "2025-04-10T22:19:28.882Z" }, - { url = "https://files.pythonhosted.org/packages/d1/af/aa176c6f5f1d901aac957d5258d5e22897fe13948d1e69063ae3d5d0ca01/multidict-6.4.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead46b0fa1dcf5af503a46e9f1c2e80b5d95c6011526352fa5f42ea201526124", size = 211728, upload-time = "2025-04-10T22:19:30.481Z" }, - { url = "https://files.pythonhosted.org/packages/e7/42/d51cc5fc1527c3717d7f85137d6c79bb7a93cd214c26f1fc57523774dbb5/multidict-6.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1748cb2743bedc339d63eb1bca314061568793acd603a6e37b09a326334c9f44", size = 226276, upload-time = "2025-04-10T22:19:32.454Z" }, - { url = "https://files.pythonhosted.org/packages/28/6b/d836dea45e0b8432343ba4acf9a8ecaa245da4c0960fb7ab45088a5e568a/multidict-6.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:acc9fa606f76fc111b4569348cc23a771cb52c61516dcc6bcef46d612edb483b", size = 212069, upload-time = "2025-04-10T22:19:34.17Z" }, - { url = "https://files.pythonhosted.org/packages/55/34/0ee1a7adb3560e18ee9289c6e5f7db54edc312b13e5c8263e88ea373d12c/multidict-6.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:31469d5832b5885adeb70982e531ce86f8c992334edd2f2254a10fa3182ac504", size = 217858, upload-time = "2025-04-10T22:19:35.879Z" }, - { url = "https://files.pythonhosted.org/packages/04/08/586d652c2f5acefe0cf4e658eedb4d71d4ba6dfd4f189bd81b400fc1bc6b/multidict-6.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ba46b51b6e51b4ef7bfb84b82f5db0dc5e300fb222a8a13b8cd4111898a869cf", size = 226988, upload-time = "2025-04-10T22:19:37.434Z" }, - { url = "https://files.pythonhosted.org/packages/82/e3/cc59c7e2bc49d7f906fb4ffb6d9c3a3cf21b9f2dd9c96d05bef89c2b1fd1/multidict-6.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:389cfefb599edf3fcfd5f64c0410da686f90f5f5e2c4d84e14f6797a5a337af4", size = 220435, upload-time = "2025-04-10T22:19:39.005Z" }, - { url = "https://files.pythonhosted.org/packages/e0/32/5c3a556118aca9981d883f38c4b1bfae646f3627157f70f4068e5a648955/multidict-6.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:64bc2bbc5fba7b9db5c2c8d750824f41c6994e3882e6d73c903c2afa78d091e4", size = 221494, upload-time = "2025-04-10T22:19:41.447Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3b/1599631f59024b75c4d6e3069f4502409970a336647502aaf6b62fb7ac98/multidict-6.4.3-cp313-cp313t-win32.whl", hash = "sha256:0ecdc12ea44bab2807d6b4a7e5eef25109ab1c82a8240d86d3c1fc9f3b72efd5", size = 41775, upload-time = "2025-04-10T22:19:43.707Z" }, - { url = "https://files.pythonhosted.org/packages/e8/4e/09301668d675d02ca8e8e1a3e6be046619e30403f5ada2ed5b080ae28d02/multidict-6.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7146a8742ea71b5d7d955bffcef58a9e6e04efba704b52a460134fefd10a8208", size = 45946, upload-time = "2025-04-10T22:19:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/96/10/7d526c8974f017f1e7ca584c71ee62a638e9334d8d33f27d7cdfc9ae79e4/multidict-6.4.3-py3-none-any.whl", hash = "sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9", size = 10400, upload-time = "2025-04-10T22:20:16.445Z" }, +version = "6.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/2f/a3470242707058fe856fe59241eee5635d79087100b7042a867368863a27/multidict-6.4.4.tar.gz", hash = "sha256:69ee9e6ba214b5245031b76233dd95408a0fd57fdb019ddcc1ead4790932a8e8", size = 90183, upload-time = "2025-05-19T14:16:37.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/b5/5675377da23d60875fe7dae6be841787755878e315e2f517235f22f59e18/multidict-6.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc388f75a1c00000824bf28b7633e40854f4127ede80512b44c3cfeeea1839a2", size = 64293, upload-time = "2025-05-19T14:14:44.724Z" }, + { url = "https://files.pythonhosted.org/packages/34/a7/be384a482754bb8c95d2bbe91717bf7ccce6dc38c18569997a11f95aa554/multidict-6.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:98af87593a666f739d9dba5d0ae86e01b0e1a9cfcd2e30d2d361fbbbd1a9162d", size = 38096, upload-time = "2025-05-19T14:14:45.95Z" }, + { url = "https://files.pythonhosted.org/packages/66/6d/d59854bb4352306145bdfd1704d210731c1bb2c890bfee31fb7bbc1c4c7f/multidict-6.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aff4cafea2d120327d55eadd6b7f1136a8e5a0ecf6fb3b6863e8aca32cd8e50a", size = 37214, upload-time = "2025-05-19T14:14:47.158Z" }, + { url = "https://files.pythonhosted.org/packages/99/e0/c29d9d462d7cfc5fc8f9bf24f9c6843b40e953c0b55e04eba2ad2cf54fba/multidict-6.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169c4ba7858176b797fe551d6e99040c531c775d2d57b31bcf4de6d7a669847f", size = 224686, upload-time = "2025-05-19T14:14:48.366Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4a/da99398d7fd8210d9de068f9a1b5f96dfaf67d51e3f2521f17cba4ee1012/multidict-6.4.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9eb4c59c54421a32b3273d4239865cb14ead53a606db066d7130ac80cc8ec93", size = 231061, upload-time = "2025-05-19T14:14:49.952Z" }, + { url = "https://files.pythonhosted.org/packages/21/f5/ac11add39a0f447ac89353e6ca46666847051103649831c08a2800a14455/multidict-6.4.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cf3bd54c56aa16fdb40028d545eaa8d051402b61533c21e84046e05513d5780", size = 232412, upload-time = "2025-05-19T14:14:51.812Z" }, + { url = "https://files.pythonhosted.org/packages/d9/11/4b551e2110cded705a3c13a1d4b6a11f73891eb5a1c449f1b2b6259e58a6/multidict-6.4.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f682c42003c7264134bfe886376299db4cc0c6cd06a3295b41b347044bcb5482", size = 231563, upload-time = "2025-05-19T14:14:53.262Z" }, + { url = "https://files.pythonhosted.org/packages/4c/02/751530c19e78fe73b24c3da66618eda0aa0d7f6e7aa512e46483de6be210/multidict-6.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920f9cf2abdf6e493c519492d892c362007f113c94da4c239ae88429835bad1", size = 223811, upload-time = "2025-05-19T14:14:55.232Z" }, + { url = "https://files.pythonhosted.org/packages/c7/cb/2be8a214643056289e51ca356026c7b2ce7225373e7a1f8c8715efee8988/multidict-6.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:530d86827a2df6504526106b4c104ba19044594f8722d3e87714e847c74a0275", size = 216524, upload-time = "2025-05-19T14:14:57.226Z" }, + { url = "https://files.pythonhosted.org/packages/19/f3/6d5011ec375c09081f5250af58de85f172bfcaafebff286d8089243c4bd4/multidict-6.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecde56ea2439b96ed8a8d826b50c57364612ddac0438c39e473fafad7ae1c23b", size = 229012, upload-time = "2025-05-19T14:14:58.597Z" }, + { url = "https://files.pythonhosted.org/packages/67/9c/ca510785df5cf0eaf5b2a8132d7d04c1ce058dcf2c16233e596ce37a7f8e/multidict-6.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dc8c9736d8574b560634775ac0def6bdc1661fc63fa27ffdfc7264c565bcb4f2", size = 226765, upload-time = "2025-05-19T14:15:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/36/c8/ca86019994e92a0f11e642bda31265854e6ea7b235642f0477e8c2e25c1f/multidict-6.4.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f3d3b3c34867579ea47cbd6c1f2ce23fbfd20a273b6f9e3177e256584f1eacc", size = 222888, upload-time = "2025-05-19T14:15:01.568Z" }, + { url = "https://files.pythonhosted.org/packages/c6/67/bc25a8e8bd522935379066950ec4e2277f9b236162a73548a2576d4b9587/multidict-6.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:87a728af265e08f96b6318ebe3c0f68b9335131f461efab2fc64cc84a44aa6ed", size = 234041, upload-time = "2025-05-19T14:15:03.759Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a0/70c4c2d12857fccbe607b334b7ee28b6b5326c322ca8f73ee54e70d76484/multidict-6.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9f193eeda1857f8e8d3079a4abd258f42ef4a4bc87388452ed1e1c4d2b0c8740", size = 231046, upload-time = "2025-05-19T14:15:05.698Z" }, + { url = "https://files.pythonhosted.org/packages/c1/0f/52954601d02d39742aab01d6b92f53c1dd38b2392248154c50797b4df7f1/multidict-6.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be06e73c06415199200e9a2324a11252a3d62030319919cde5e6950ffeccf72e", size = 227106, upload-time = "2025-05-19T14:15:07.124Z" }, + { url = "https://files.pythonhosted.org/packages/af/24/679d83ec4379402d28721790dce818e5d6b9f94ce1323a556fb17fa9996c/multidict-6.4.4-cp312-cp312-win32.whl", hash = "sha256:622f26ea6a7e19b7c48dd9228071f571b2fbbd57a8cd71c061e848f281550e6b", size = 35351, upload-time = "2025-05-19T14:15:08.556Z" }, + { url = "https://files.pythonhosted.org/packages/52/ef/40d98bc5f986f61565f9b345f102409534e29da86a6454eb6b7c00225a13/multidict-6.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:5e2bcda30d5009996ff439e02a9f2b5c3d64a20151d34898c000a6281faa3781", size = 38791, upload-time = "2025-05-19T14:15:09.825Z" }, + { url = "https://files.pythonhosted.org/packages/df/2a/e166d2ffbf4b10131b2d5b0e458f7cee7d986661caceae0de8753042d4b2/multidict-6.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:82ffabefc8d84c2742ad19c37f02cde5ec2a1ee172d19944d380f920a340e4b9", size = 64123, upload-time = "2025-05-19T14:15:11.044Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/e200e379ae5b6f95cbae472e0199ea98913f03d8c9a709f42612a432932c/multidict-6.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6a2f58a66fe2c22615ad26156354005391e26a2f3721c3621504cd87c1ea87bf", size = 38049, upload-time = "2025-05-19T14:15:12.902Z" }, + { url = "https://files.pythonhosted.org/packages/75/fb/47afd17b83f6a8c7fa863c6d23ac5ba6a0e6145ed8a6bcc8da20b2b2c1d2/multidict-6.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5883d6ee0fd9d8a48e9174df47540b7545909841ac82354c7ae4cbe9952603bd", size = 37078, upload-time = "2025-05-19T14:15:14.282Z" }, + { url = "https://files.pythonhosted.org/packages/fa/70/1af3143000eddfb19fd5ca5e78393985ed988ac493bb859800fe0914041f/multidict-6.4.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9abcf56a9511653fa1d052bfc55fbe53dbee8f34e68bd6a5a038731b0ca42d15", size = 224097, upload-time = "2025-05-19T14:15:15.566Z" }, + { url = "https://files.pythonhosted.org/packages/b1/39/d570c62b53d4fba844e0378ffbcd02ac25ca423d3235047013ba2f6f60f8/multidict-6.4.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6ed5ae5605d4ad5a049fad2a28bb7193400700ce2f4ae484ab702d1e3749c3f9", size = 230768, upload-time = "2025-05-19T14:15:17.308Z" }, + { url = "https://files.pythonhosted.org/packages/fd/f8/ed88f2c4d06f752b015933055eb291d9bc184936903752c66f68fb3c95a7/multidict-6.4.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbfcb60396f9bcfa63e017a180c3105b8c123a63e9d1428a36544e7d37ca9e20", size = 231331, upload-time = "2025-05-19T14:15:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/8e07cffa32f483ab887b0d56bbd8747ac2c1acd00dc0af6fcf265f4a121e/multidict-6.4.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0f1987787f5f1e2076b59692352ab29a955b09ccc433c1f6b8e8e18666f608b", size = 230169, upload-time = "2025-05-19T14:15:20.179Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2b/5dcf173be15e42f330110875a2668ddfc208afc4229097312212dc9c1236/multidict-6.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d0121ccce8c812047d8d43d691a1ad7641f72c4f730474878a5aeae1b8ead8c", size = 222947, upload-time = "2025-05-19T14:15:21.714Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/4ddcbcebe5ebcd6faa770b629260d15840a5fc07ce8ad295a32e14993726/multidict-6.4.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83ec4967114295b8afd120a8eec579920c882831a3e4c3331d591a8e5bfbbc0f", size = 215761, upload-time = "2025-05-19T14:15:23.242Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c9/55e998ae45ff15c5608e384206aa71a11e1b7f48b64d166db400b14a3433/multidict-6.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:995f985e2e268deaf17867801b859a282e0448633f1310e3704b30616d269d69", size = 227605, upload-time = "2025-05-19T14:15:24.763Z" }, + { url = "https://files.pythonhosted.org/packages/04/49/c2404eac74497503c77071bd2e6f88c7e94092b8a07601536b8dbe99be50/multidict-6.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d832c608f94b9f92a0ec8b7e949be7792a642b6e535fcf32f3e28fab69eeb046", size = 226144, upload-time = "2025-05-19T14:15:26.249Z" }, + { url = "https://files.pythonhosted.org/packages/62/c5/0cd0c3c6f18864c40846aa2252cd69d308699cb163e1c0d989ca301684da/multidict-6.4.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d21c1212171cf7da703c5b0b7a0e85be23b720818aef502ad187d627316d5645", size = 221100, upload-time = "2025-05-19T14:15:28.303Z" }, + { url = "https://files.pythonhosted.org/packages/71/7b/f2f3887bea71739a046d601ef10e689528d4f911d84da873b6be9194ffea/multidict-6.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:cbebaa076aaecad3d4bb4c008ecc73b09274c952cf6a1b78ccfd689e51f5a5b0", size = 232731, upload-time = "2025-05-19T14:15:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b3/d9de808349df97fa75ec1372758701b5800ebad3c46ae377ad63058fbcc6/multidict-6.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c93a6fb06cc8e5d3628b2b5fda215a5db01e8f08fc15fadd65662d9b857acbe4", size = 229637, upload-time = "2025-05-19T14:15:33.337Z" }, + { url = "https://files.pythonhosted.org/packages/5e/57/13207c16b615eb4f1745b44806a96026ef8e1b694008a58226c2d8f5f0a5/multidict-6.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8cd8f81f1310182362fb0c7898145ea9c9b08a71081c5963b40ee3e3cac589b1", size = 225594, upload-time = "2025-05-19T14:15:34.832Z" }, + { url = "https://files.pythonhosted.org/packages/3a/e4/d23bec2f70221604f5565000632c305fc8f25ba953e8ce2d8a18842b9841/multidict-6.4.4-cp313-cp313-win32.whl", hash = "sha256:3e9f1cd61a0ab857154205fb0b1f3d3ace88d27ebd1409ab7af5096e409614cd", size = 35359, upload-time = "2025-05-19T14:15:36.246Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7a/cfe1a47632be861b627f46f642c1d031704cc1c0f5c0efbde2ad44aa34bd/multidict-6.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:8ffb40b74400e4455785c2fa37eba434269149ec525fc8329858c862e4b35373", size = 38903, upload-time = "2025-05-19T14:15:37.507Z" }, + { url = "https://files.pythonhosted.org/packages/68/7b/15c259b0ab49938a0a1c8f3188572802704a779ddb294edc1b2a72252e7c/multidict-6.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6a602151dbf177be2450ef38966f4be3467d41a86c6a845070d12e17c858a156", size = 68895, upload-time = "2025-05-19T14:15:38.856Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7d/168b5b822bccd88142e0a3ce985858fea612404edd228698f5af691020c9/multidict-6.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d2b9712211b860d123815a80b859075d86a4d54787e247d7fbee9db6832cf1c", size = 40183, upload-time = "2025-05-19T14:15:40.197Z" }, + { url = "https://files.pythonhosted.org/packages/e0/b7/d4b8d98eb850ef28a4922ba508c31d90715fd9b9da3801a30cea2967130b/multidict-6.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d2fa86af59f8fc1972e121ade052145f6da22758f6996a197d69bb52f8204e7e", size = 39592, upload-time = "2025-05-19T14:15:41.508Z" }, + { url = "https://files.pythonhosted.org/packages/18/28/a554678898a19583548e742080cf55d169733baf57efc48c2f0273a08583/multidict-6.4.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50855d03e9e4d66eab6947ba688ffb714616f985838077bc4b490e769e48da51", size = 226071, upload-time = "2025-05-19T14:15:42.877Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/7ba6c789d05c310e294f85329efac1bf5b450338d2542498db1491a264df/multidict-6.4.4-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5bce06b83be23225be1905dcdb6b789064fae92499fbc458f59a8c0e68718601", size = 222597, upload-time = "2025-05-19T14:15:44.412Z" }, + { url = "https://files.pythonhosted.org/packages/24/4f/34eadbbf401b03768dba439be0fb94b0d187facae9142821a3d5599ccb3b/multidict-6.4.4-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66ed0731f8e5dfd8369a883b6e564aca085fb9289aacabd9decd70568b9a30de", size = 228253, upload-time = "2025-05-19T14:15:46.474Z" }, + { url = "https://files.pythonhosted.org/packages/c0/e6/493225a3cdb0d8d80d43a94503fc313536a07dae54a3f030d279e629a2bc/multidict-6.4.4-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:329ae97fc2f56f44d91bc47fe0972b1f52d21c4b7a2ac97040da02577e2daca2", size = 226146, upload-time = "2025-05-19T14:15:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/2f/70/e411a7254dc3bff6f7e6e004303b1b0591358e9f0b7c08639941e0de8bd6/multidict-6.4.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c27e5dcf520923d6474d98b96749e6805f7677e93aaaf62656005b8643f907ab", size = 220585, upload-time = "2025-05-19T14:15:49.546Z" }, + { url = "https://files.pythonhosted.org/packages/08/8f/beb3ae7406a619100d2b1fb0022c3bb55a8225ab53c5663648ba50dfcd56/multidict-6.4.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:058cc59b9e9b143cc56715e59e22941a5d868c322242278d28123a5d09cdf6b0", size = 212080, upload-time = "2025-05-19T14:15:51.151Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ec/355124e9d3d01cf8edb072fd14947220f357e1c5bc79c88dff89297e9342/multidict-6.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:69133376bc9a03f8c47343d33f91f74a99c339e8b58cea90433d8e24bb298031", size = 226558, upload-time = "2025-05-19T14:15:52.665Z" }, + { url = "https://files.pythonhosted.org/packages/fd/22/d2b95cbebbc2ada3be3812ea9287dcc9712d7f1a012fad041770afddb2ad/multidict-6.4.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d6b15c55721b1b115c5ba178c77104123745b1417527ad9641a4c5e2047450f0", size = 212168, upload-time = "2025-05-19T14:15:55.279Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c5/62bfc0b2f9ce88326dbe7179f9824a939c6c7775b23b95de777267b9725c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a887b77f51d3d41e6e1a63cf3bc7ddf24de5939d9ff69441387dfefa58ac2e26", size = 217970, upload-time = "2025-05-19T14:15:56.806Z" }, + { url = "https://files.pythonhosted.org/packages/79/74/977cea1aadc43ff1c75d23bd5bc4768a8fac98c14e5878d6ee8d6bab743c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:632a3bf8f1787f7ef7d3c2f68a7bde5be2f702906f8b5842ad6da9d974d0aab3", size = 226980, upload-time = "2025-05-19T14:15:58.313Z" }, + { url = "https://files.pythonhosted.org/packages/48/fc/cc4a1a2049df2eb84006607dc428ff237af38e0fcecfdb8a29ca47b1566c/multidict-6.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a145c550900deb7540973c5cdb183b0d24bed6b80bf7bddf33ed8f569082535e", size = 220641, upload-time = "2025-05-19T14:15:59.866Z" }, + { url = "https://files.pythonhosted.org/packages/3b/6a/a7444d113ab918701988d4abdde373dbdfd2def7bd647207e2bf645c7eac/multidict-6.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc5d83c6619ca5c9672cb78b39ed8542f1975a803dee2cda114ff73cbb076edd", size = 221728, upload-time = "2025-05-19T14:16:01.535Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b0/fdf4c73ad1c55e0f4dbbf2aa59dd37037334091f9a4961646d2b7ac91a86/multidict-6.4.4-cp313-cp313t-win32.whl", hash = "sha256:3312f63261b9df49be9d57aaa6abf53a6ad96d93b24f9cc16cf979956355ce6e", size = 41913, upload-time = "2025-05-19T14:16:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/8e/92/27989ecca97e542c0d01d05a98a5ae12198a243a9ee12563a0313291511f/multidict-6.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:ba852168d814b2c73333073e1c7116d9395bea69575a01b0b3c89d2d5a87c8fb", size = 46112, upload-time = "2025-05-19T14:16:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/84/5d/e17845bb0fa76334477d5de38654d27946d5b5d3695443987a094a71b440/multidict-6.4.4-py3-none-any.whl", hash = "sha256:bd4557071b561a8b3b6075c3ce93cf9bfb6182cb241805c3d66ced3b75eff4ac", size = 10481, upload-time = "2025-05-19T14:16:36.024Z" }, ] [[package]] @@ -1143,6 +1182,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/17/3493c5624e48fd97156ebaec380dcaafee9506d7e2c46218ceebbb57d7de/pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3", size = 19467, upload-time = "2025-01-28T18:37:56.798Z" }, ] +[[package]] +name = "pytest-cov" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1233,11 +1285,11 @@ wheels = [ [[package]] name = "redis" -version = "6.0.0" +version = "6.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/12/dffaaa4374b8d5f3b7ff5c40025c9db387e06264302d5a9da6043cd84e1f/redis-6.0.0.tar.gz", hash = "sha256:5446780d2425b787ed89c91ddbfa1be6d32370a636c8fdb687f11b1c26c1fa88", size = 4620969, upload-time = "2025-04-30T19:09:30.798Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/af/e875d57383653e5d9065df8552de1deb7576b4d3cf3af90cde2e79ff7f65/redis-6.1.0.tar.gz", hash = "sha256:c928e267ad69d3069af28a9823a07726edf72c7e37764f43dc0123f37928c075", size = 4629300, upload-time = "2025-05-13T12:16:57.538Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/c8/68081c9d3531f7b2a4d663326b96a9dcbc2aef47df3c6b5c38dea90dff02/redis-6.0.0-py3-none-any.whl", hash = "sha256:a2e040aee2cdd947be1fa3a32e35a956cd839cc4c1dbbe4b2cdee5b9623fd27c", size = 268950, upload-time = "2025-04-30T19:09:28.432Z" }, + { url = "https://files.pythonhosted.org/packages/28/5f/cf36360f80ae233bd1836442f5127818cfcfc7b1846179b60b2e9a4c45c9/redis-6.1.0-py3-none-any.whl", hash = "sha256:3b72622f3d3a89df2a6041e82acd896b0e67d9f54e9bcd906d091d23ba5219f6", size = 273750, upload-time = "2025-05-13T12:16:55.661Z" }, ] [[package]] @@ -1807,6 +1859,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, ] +[[package]] +name = "xxhash" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241, upload-time = "2024-08-17T09:20:38.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/0e/1bfce2502c57d7e2e787600b31c83535af83746885aa1a5f153d8c8059d6/xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00", size = 31969, upload-time = "2024-08-17T09:18:24.025Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d6/8ca450d6fe5b71ce521b4e5db69622383d039e2b253e9b2f24f93265b52c/xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9", size = 30787, upload-time = "2024-08-17T09:18:25.318Z" }, + { url = "https://files.pythonhosted.org/packages/5b/84/de7c89bc6ef63d750159086a6ada6416cc4349eab23f76ab870407178b93/xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84", size = 220959, upload-time = "2024-08-17T09:18:26.518Z" }, + { url = "https://files.pythonhosted.org/packages/fe/86/51258d3e8a8545ff26468c977101964c14d56a8a37f5835bc0082426c672/xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793", size = 200006, upload-time = "2024-08-17T09:18:27.905Z" }, + { url = "https://files.pythonhosted.org/packages/02/0a/96973bd325412feccf23cf3680fd2246aebf4b789122f938d5557c54a6b2/xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be", size = 428326, upload-time = "2024-08-17T09:18:29.335Z" }, + { url = "https://files.pythonhosted.org/packages/11/a7/81dba5010f7e733de88af9555725146fc133be97ce36533867f4c7e75066/xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6", size = 194380, upload-time = "2024-08-17T09:18:30.706Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7d/f29006ab398a173f4501c0e4977ba288f1c621d878ec217b4ff516810c04/xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90", size = 207934, upload-time = "2024-08-17T09:18:32.133Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6e/6e88b8f24612510e73d4d70d9b0c7dff62a2e78451b9f0d042a5462c8d03/xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27", size = 216301, upload-time = "2024-08-17T09:18:33.474Z" }, + { url = "https://files.pythonhosted.org/packages/af/51/7862f4fa4b75a25c3b4163c8a873f070532fe5f2d3f9b3fc869c8337a398/xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2", size = 203351, upload-time = "2024-08-17T09:18:34.889Z" }, + { url = "https://files.pythonhosted.org/packages/22/61/8d6a40f288f791cf79ed5bb113159abf0c81d6efb86e734334f698eb4c59/xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d", size = 210294, upload-time = "2024-08-17T09:18:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/17/02/215c4698955762d45a8158117190261b2dbefe9ae7e5b906768c09d8bc74/xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab", size = 414674, upload-time = "2024-08-17T09:18:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/b7a8db8a3237cff3d535261325d95de509f6a8ae439a5a7a4ffcff478189/xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e", size = 192022, upload-time = "2024-08-17T09:18:40.138Z" }, + { url = "https://files.pythonhosted.org/packages/78/e3/dd76659b2811b3fd06892a8beb850e1996b63e9235af5a86ea348f053e9e/xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8", size = 30170, upload-time = "2024-08-17T09:18:42.163Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6b/1c443fe6cfeb4ad1dcf231cdec96eb94fb43d6498b4469ed8b51f8b59a37/xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e", size = 30040, upload-time = "2024-08-17T09:18:43.699Z" }, + { url = "https://files.pythonhosted.org/packages/0f/eb/04405305f290173acc0350eba6d2f1a794b57925df0398861a20fbafa415/xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2", size = 26796, upload-time = "2024-08-17T09:18:45.29Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b8/e4b3ad92d249be5c83fa72916c9091b0965cb0faeff05d9a0a3870ae6bff/xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6", size = 31795, upload-time = "2024-08-17T09:18:46.813Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d8/b3627a0aebfbfa4c12a41e22af3742cf08c8ea84f5cc3367b5de2d039cce/xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5", size = 30792, upload-time = "2024-08-17T09:18:47.862Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cc/762312960691da989c7cd0545cb120ba2a4148741c6ba458aa723c00a3f8/xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc", size = 220950, upload-time = "2024-08-17T09:18:49.06Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e9/cc266f1042c3c13750e86a535496b58beb12bf8c50a915c336136f6168dc/xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3", size = 199980, upload-time = "2024-08-17T09:18:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/bf/85/a836cd0dc5cc20376de26b346858d0ac9656f8f730998ca4324921a010b9/xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c", size = 428324, upload-time = "2024-08-17T09:18:51.988Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0e/15c243775342ce840b9ba34aceace06a1148fa1630cd8ca269e3223987f5/xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb", size = 194370, upload-time = "2024-08-17T09:18:54.164Z" }, + { url = "https://files.pythonhosted.org/packages/87/a1/b028bb02636dfdc190da01951d0703b3d904301ed0ef6094d948983bef0e/xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f", size = 207911, upload-time = "2024-08-17T09:18:55.509Z" }, + { url = "https://files.pythonhosted.org/packages/80/d5/73c73b03fc0ac73dacf069fdf6036c9abad82de0a47549e9912c955ab449/xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7", size = 216352, upload-time = "2024-08-17T09:18:57.073Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2a/5043dba5ddbe35b4fe6ea0a111280ad9c3d4ba477dd0f2d1fe1129bda9d0/xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326", size = 203410, upload-time = "2024-08-17T09:18:58.54Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b2/9a8ded888b7b190aed75b484eb5c853ddd48aa2896e7b59bbfbce442f0a1/xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf", size = 210322, upload-time = "2024-08-17T09:18:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/98/62/440083fafbc917bf3e4b67c2ade621920dd905517e85631c10aac955c1d2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7", size = 414725, upload-time = "2024-08-17T09:19:01.332Z" }, + { url = "https://files.pythonhosted.org/packages/75/db/009206f7076ad60a517e016bb0058381d96a007ce3f79fa91d3010f49cc2/xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c", size = 192070, upload-time = "2024-08-17T09:19:03.007Z" }, + { url = "https://files.pythonhosted.org/packages/1f/6d/c61e0668943a034abc3a569cdc5aeae37d686d9da7e39cf2ed621d533e36/xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637", size = 30172, upload-time = "2024-08-17T09:19:04.355Z" }, + { url = "https://files.pythonhosted.org/packages/96/14/8416dce965f35e3d24722cdf79361ae154fa23e2ab730e5323aa98d7919e/xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43", size = 30041, upload-time = "2024-08-17T09:19:05.435Z" }, + { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801, upload-time = "2024-08-17T09:19:06.547Z" }, +] + [[package]] name = "yarl" version = "1.20.0"