【阅读原文】戳:ACK Gateway with AI Extension:大模型推理的模型灰度实践
ACK Gateway with AI Extension组件专为LLM推理场景设计,支持四层/七层流量路由,并提供基于模型服务器负载智能感知的负载均衡能力。此外,通过InferencePool和InferenceModel自定义资源(CRD),可以灵活定义推理服务的流量分发策略,包括模型灰度发布、LLM流量镜像等。
本文主要介绍在大模型推理服务云原生化部署后,对模型进行灰度发布的典型场景以及基于ACK Gateway with AI Extension进行模型灰度的场景实践。对于模型灰度场景,又可以细分为针对LoRA模型的灰度和针对基础大模型的灰度。
LoRA模型灰度场景
低秩适应LoRA(Low-Rank Adaptation)是一种流行的大语言模型(LLM)微调技术,可以以较小的代价对LLM进行微调,以满足LLM在垂直领域(例如医疗、金融、教育)的定制化需求。在构建推理服务时,可以基于同一个基础大模型加载多个不同的LoRA模型权重进行推理。通过这种方式,可以实现多个LoRA模型共享GPU资源的效果,这被称作Multi-LoRA技术。由于LoRA技术的高效性,因此被广泛应用于部署垂直领域定制化大模型的场景中。
在Multi-LoRA场景下,多个LoRA模型可以被加载到同一LLM推理服务中,对不同LoRA模型的请求通过请求中的模型名称进行区分。通过这种方式,可以在同一基础大模型上训练不同的LoRA模型,并在不同的LoRA模型之间进行灰度测试,以评估大模型的微调效果。
在Kubernetes集群中部署大型语言模型(LLM)推理服务时,基于低秩适应LoRA技术对大模型进行微调并提供定制化推理能力,已成为高效且灵活的最佳实践。使用ACK Gateway with AI Extension,基于Multi-LoRA的微调LLM推理服务、可以指定多LoRA模型的流量分发策略,从而实现LoRA模型灰度。
前提条件
1. 已创建包含GPU的Kubernetes集群,具体操作请参考创建包含GPU的Kubernetes集群[1]。
2. 至少准备一个ecs.gn7i-c8g1.2xlarge(1xA10)GPU节点,本文示例使用了包含2个ecs.gn7i-c8g1.2xlargeGPU节点的集群。
步骤一:部署示例LLM推理服务
实践以在集群中部署基于vLLM的Llama2大模型为基础模型,并注册了基于该基础模型的10个LoRA模型,分别是sql-lora到sql-lora-4,以及tweet-summary到tweet-summary-4。
使用以下指令部署加载多个LoRA模型的llama2模型:
kubectl apply -f- <<EOF apiVersion: v1 kind: Service metadata: name: vllm-llama2-7b-pool spec: selector: app: vllm-llama2-7b-pool ports: - protocol: TCP port: 8000 targetPort: 8000 type: ClusterIP --- apiVersion: v1 kind: ConfigMap metadata: name: chat-template data: llama-2-chat.jinja: | {% if messages[0]['role'] == 'system' %} {% set system_message = '<<SYS>>\n' + messages[0]['content'] | trim + '\n<</SYS>>\n\n' %} {% set messages = messages[1:] %} {% else %} {% set system_message = '' %} {% endif %} {% for message in messages %} {% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %} {{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }} {% endif %} {% if loop.index0 == 0 %} {% set content = system_message + message['content'] %} {% else %} {% set content = message['content'] %} {% endif %} {% if message['role'] == 'user' %} {{ bos_token + '[INST] ' + content | trim + ' [/INST]' }} {% elif message['role'] == 'assistant' %} {{ ' ' + content | trim + ' ' + eos_token }} {% endif %} {% endfor %} --- apiVersion: apps/v1 kind: Deployment metadata: name: vllm-llama2-7b-pool namespace: default spec: replicas: 3 selector: matchLabels: app: vllm-llama2-7b-pool template: metadata: annotations: prometheus.io/path: /metrics prometheus.io/port: '8000' prometheus.io/scrape: 'true' labels: app: vllm-llama2-7b-pool spec: containers: - name: lora image: "registry-cn-hangzhou-vpc.ack.aliyuncs.com/dev/llama2-with-lora:v0.2" imagePullPolicy: IfNotPresent command: ["python3", "-m", "vllm.entrypoints.openai.api_server"] args: - "--model" - "/model/llama2" - "--tensor-parallel-size" - "1" - "--port" - "8000" - '--gpu_memory_utilization' - '0.8' - "--enable-lora" - "--max-loras" - "10" - "--max-cpu-loras" - "12" - "--lora-modules" - 'sql-lora=/adapters/yard1/llama-2-7b-sql-lora-test_0' - 'sql-lora-1=/adapters/yard1/llama-2-7b-sql-lora-test_1' - 'sql-lora-2=/adapters/yard1/llama-2-7b-sql-lora-test_2' - 'sql-lora-3=/adapters/yard1/llama-2-7b-sql-lora-test_3' - 'sql-lora-4=/adapters/yard1/llama-2-7b-sql-lora-test_4' - 'tweet-summary=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_0' - 'tweet-summary-1=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_1' - 'tweet-summary-2=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_2' - 'tweet-summary-3=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_3' - 'tweet-summary-4=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_4' - '--chat-template' - '/etc/vllm/llama-2-chat.jinja' env: - name: PORT value: "8000" ports: - containerPort: 8000 name: http protocol: TCP livenessProbe: failureThreshold: 2400 httpGet: path: /health port: http scheme: HTTP initialDelaySeconds: 5 periodSeconds: 5 successThreshold: 1 timeoutSeconds: 1 readinessProbe: failureThreshold: 6000 httpGet: path: /health port: http scheme: HTTP initialDelaySeconds: 5 periodSeconds: 5 successThreshold: 1 timeoutSeconds: 1 resources: limits: nvidia.com/gpu: 1 requests: nvidia.com/gpu: 1 volumeMounts: - mountPath: /data name: data - mountPath: /dev/shm name: shm - mountPath: /etc/vllm name: chat-template restartPolicy: Always schedulerName: default-scheduler terminationGracePeriodSeconds: 30 volumes: - name: data emptyDir: {} - name: shm emptyDir: medium: Memory - name: chat-template configMap: name: chat-template EOF
步骤二:使用ACK Gateway with AI Extension组件配置LoRA模型灰度
1. 在ACK集群的组件管理中开启ACK Gateway with AI Extension组件。具体请参考管理ACK托管集群的相关组件[2]。开启时打开“启用Gateway API推理扩展”选项。
2. 创建网关实例:
kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: inference-gateway spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: inference-gateway spec: gatewayClassName: inference-gateway listeners: - name: llm-gw protocol: HTTP port: 8081 EOF
3. 在网关8081端口上启用推理扩展
使用以下指令创建InferencePool资源和InferenceModel资源,在推理扩展提供的CRD中:InferencePool资源通过标签选择器声明一组在集群中运行的LLM推理服务工作负载,而InferenceModel指定了InferencePool中具体模型的流量分发策略。
kubectl apply -f- <<EOF apiVersion: inference.networking.x-k8s.io/v1alpha1 kind: InferencePool metadata: annotations: inference.networking.x-k8s.io/attach-to: | name: inference-gateway port: 8081 name: vllm-llama2-7b-pool spec: targetPortNumber: 8000 selector: app: vllm-llama2-7b-pool extensionRef: name: inference-gateway-ext-proc --- apiVersion: inference.networking.x-k8s.io/v1alpha1 kind: InferenceModel metadata: name: inferencemodel-sample spec: modelName: lora-request poolRef: group: inference.networking.x-k8s.io kind: InferencePool name: vllm-llama2-7b-pool targetModels: - name: tweet-summary weight: 50 - name: sql-lora weight: 50 EOF
上述内容中:
• InferencePool指令了一组提供基于Llama2模型的LoRA模型的模型服务器端点。
• InferenceModel配置了当请求的模型名称是lora-request时,总和为50%的请求由tweet-summary LoRA模型进行推理,另外50%的请求发往sql-lora LoRA模型进行推理。
步骤三:验证执行结果
多次执行以下命令发起测试:
GATEWAY_IP=$(kubectl get gateway/inference-gateway -o jsonpath='{.status.addresses[0].value}') curl -H "host: test.com" ${GATEWAY_IP}:8081/v1/completions -H 'Content-Type: application/json' -d '{ "model": "lora-request", "prompt": "Write as if you were a critic: San Francisco", "max_tokens": 100, "temperature": 0 }' -v
可以看到和下面内容相似的输出:
{"id":"cmpl-2fc9a351-d866-422b-b561-874a30843a6b","object":"text_completion","created":1736933141,"model":"tweet-summary","choices":[{"index":0,"text":", I'm a newbie to this forum. Write a summary of the article.\nWrite a summary of the article.\nWrite a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary","logprobs":null,"finish_reason":"length","stop_reason":null,"prompt_logprobs":null}],"usage":{"prompt_tokens":2,"total_tokens":102,"completion_tokens":100,"prompt_tokens_details":null}}
其中model字段表示真正提供服务的模型。多次访问之后,可以看到tweet-summary和sql-lora模型的请求总量比例大致为1:1。
基础模型灰度场景
在Multi-LoRA架构下,可以在基于同一基础模型的多个LoRA之间进行灰度,不同的LoRA模型之间共享GPU资源。然而,介于大模型技术的快速发展,实际场景中也可能需要对业务使用的基础模型进行更新,此时,需要针对基础模型进行灰度过程。
使用ACK Gateway with AI Extension,也可以在两批加载了不同基础模型的LLM推理服务之间进行灰度流程。本文以DeepSeek-R1-Distill-Qwen-7B和QwQ-32B模型之间的灰度为例进行实践流程。
QwQ-32B模型简介
QwQ-32B是阿里云最新发布的高效能大语言模型,拥有32亿参数,性能可媲美DeepSeek-R1 671B。该模型在数学、代码生成等核心指标上表现出色,能够以更低的资源消耗提供卓越的推理能力。QwQ-32B模型支持bf16精度,仅需64GB显存即可运行,最低配置为4xA10 GPU节点。
DeepSeek-R1-Distill-Qwen-7B模型简介
DeepSeek-R1-Distill-Qwen-7B是由DeepSeek公司推出的高效能语言模型,参数规模为70亿,通过知识蒸馏技术将DeepSeek-R1(671B参数)的推理能力迁移至Qwen架构。该模型在数学推理、编程任务及逻辑推演中表现卓越,在AIME 2024基准测试中达到55.5%的Pass@1,超越同类开源模型,并通过蒸馏实现推理速度提升3倍。
步骤一:部署QwQ-32B和DeepSeek-R1-Distill-Qwen-7B模型
前提条件
1. 已创建包含GPU的Kubernetes集群,具体操作请参考创建包含GPU的Kubernetes集群[1]。
2. 至少准备一个ecs.gn7i-c32g1.32xlarge(4xA10)GPU节点和一个ecs.gn7i-c8g1.2xlarge(1xA10)GPU节点,本文示例使用了包含5个ecs.gn7i-c32g1.32xlargeGPU节点和2个ecs.gn7i-c8g1.2xlargeGPU节点的集群。
1. 下载模型。
GIT_LFS_SKIP_SMUDGE=1 git clone https://www.modelscope.cn/Qwen/QwQ-32B.git cd QwQ-32B git lfs pull GIT_LFS_SKIP_SMUDGE=1 git clone https://www.modelscope.cn/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B.git cd DeepSeek-R1-Distill-Qwen-7B git lfs pull
2. 上传模型至OSS。
ossutil mkdir oss://<Your-Bucket-Name>/QwQ-32B ossutil cp -r ./QwQ-32B oss://<Your-Bucket-Name>/QwQ-32B ossutil mkdir oss://<Your-Bucket-Name>/DeepSeek-R1-7B ossutil cp -r ./DeepSeek-R1-Distill-Qwen-7B oss://<Your-Bucket-Name>/DeepSeek-R1-7B
3. 为目标集群配置存储卷PV和PVC
具体操作请参考使用OSS静态存储卷[3]。
配置项 | 说明 |
存储卷类型 | OSS |
名称 | llm-model(用于QwQ-32B)或llm-model-ds(用于DeepSeek-R1-Distill-Qwen-7B) |
访问证书 | 配置用于访问OSS的AccessKey ID和AccessKey Secret。 |
Bucket ID | 选择已创建的OSS Bucket。 |
OSS Path | 选择模型所在的路径,如/models/QwQ-32B 或 /models/DeepSeek-R1-7B |
以下为示例PVC的配置信息:
配置项 | 说明 |
存储声明类型 | OSS |
名称 | llm-model(用于QwQ-32B)或llm-model-ds(用于DeepSeek-R1-Distill-Qwen-7B) |
分配模式 | 选择已有存储卷。 |
已有存储卷 | 单击选择已有存储卷链接,选择已创建的存储卷PV。 |
4. 部署QwQ-32B和DeepSeek-R1-Distill-Qwen-7B模型推理服务
执行以下命令,基于vLLM模型推理框架部署QwQ-32B和DeepSeek-R1-Distill-Qwen-7B模型推理服务。
kubectl apply -f- <<EOF apiVersion: apps/v1 kind: Deployment metadata: labels: app: custom-serving release: qwq-32b name: qwq-32b spec: progressDeadlineSeconds: 600 replicas: 5 # 根据ecs.gn7i-c32g1.32xlarge节点数调整 revisionHistoryLimit: 10 selector: matchLabels: app: custom-serving release: qwq-32b strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate template: metadata: annotations: prometheus.io/path: /metrics prometheus.io/port: "8000" prometheus.io/scrape: "true" labels: app: custom-serving release: qwq-32b spec: containers: - command: - sh - -c - vllm serve /model/QwQ-32B --port 8000 --trust-remote-code --served-model-name qwq-32b --tensor-parallel=4 --max-model-len 8192 --gpu-memory-utilization 0.95 --enforce-eager env: - name: ARENA_NODE_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: spec.nodeName - name: ARENA_POD_NAMESPACE valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.namespace - name: ARENA_POD_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: metadata.name - name: ARENA_POD_IP valueFrom: fieldRef: apiVersion: v1 fieldPath: status.podIP image: kube-ai-registry.cn-shanghai.cr.aliyuncs.com/kube-ai/vllm:v0.7.2 imagePullPolicy: IfNotPresent name: custom-serving ports: - containerPort: 8000 name: restful protocol: TCP readinessProbe: failureThreshold: 3 initialDelaySeconds: 30 periodSeconds: 30 successThreshold: 1 tcpSocket: port: 8000 timeoutSeconds: 1 resources: limits: nvidia.com/gpu: "4" terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /dev/shm name: dshm - mountPath: /model/QwQ-32B name: llm-model dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 volumes: - emptyDir: medium: Memory sizeLimit: 30Gi name: dshm - name: llm-model persistentVolumeClaim: claimName: llm-model --- apiVersion: v1 kind: Service metadata: labels: app: custom-serving release: qwq-32b name: qwq-32b spec: ports: - name: http-serving port: 8000 protocol: TCP targetPort: 8000 selector: app: custom-serving release: qwq-32b --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: custom-serving release: deepseek-r1 name: deepseek-r1 namespace: default spec: replicas: 2 # 根据ecs.gn7i-c8g1.2xlarge节点数量进行调整 selector: matchLabels: app: custom-serving release: deepseek-r1 template: metadata: labels: app: custom-serving release: deepseek-r1 annotations: prometheus.io/path: /metrics prometheus.io/port: "8000" prometheus.io/scrape: "true" spec: volumes: - name: model persistentVolumeClaim: claimName: llm-model-ds - name: dshm emptyDir: medium: Memory sizeLimit: 30Gi containers: - command: - sh - -c - vllm serve /models/DeepSeek-R1-1.5B --port 8000 --trust-remote-code --served-model-name deepseek-r1 --max-model-len 8192 --gpu-memory-utilization 0.9 --enforce-eager image: registry-cn-hangzhou.ack.aliyuncs.com/dev/vllm:v0.7.2 name: vllm ports: - containerPort: 8000 readinessProbe: tcpSocket: port: 8000 initialDelaySeconds: 30 periodSeconds: 30 resources: limits: nvidia.com/gpu: "1" volumeMounts: - mountPath: /models/DeepSeek-R1-1.5B name: model - mountPath: /dev/shm name: dshm --- apiVersion: v1 kind: Service metadata: name: deepseek-r1 spec: type: ClusterIP ports: - port: 8000 protocol: TCP targetPort: 8000 selector: app: custom-serving release: deepseek-r1 EOF
步骤二:使用ACK Gateway with AI Extension组件配置模型灰度
1. 在ACK集群的组件管理中开启ACK Gateway with AI Extension组件。具体请参考管理ACK托管集群的相关组件[4]。开启时打开“启用Gateway API推理扩展”选项。
2. 创建网关实例:
kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: inference-gateway spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: inference-gateway spec: gatewayClassName: inference-gateway listeners: - name: llm-gw protocol: HTTP port: 8081 EOF
3. 在网关8081端口上启用推理扩展。
使用以下指令创建InferencePool资源和InferenceModel资源,在推理扩展提供的CRD中:InferencePool资源通过标签选择器声明一组在集群中运行的LLM推理服务工作负载,而InferenceModel指定了InferencePool中具体模型的流量分发策略。
kubectl apply -f- <<EOF apiVersion: inference.networking.x-k8s.io/v1alpha1 kind: InferencePool metadata: annotations: inference.networking.x-k8s.io/attach-to: | name: inference-gateway port: 8081 name: reasoning-pool spec: extensionRef: group: "" kind: Service name: inference-gateway-ext-proc selector: app: custom-serving targetPortNumber: 8000 --- apiVersion: inference.networking.x-k8s.io/v1alpha1 kind: InferenceModel metadata: name: inferencemodel-sample spec: criticality: Critical modelName: qwq poolRef: group: inference.networking.x-k8s.io kind: InferencePool name: reasoning-pool targetModels: - name: qwq-32b weight: 50 - name: deepseek-r1 weight: 50 EOF
上述内容中:
InferencePool指令了一组提供推理大模型服务的服务端点,其中包含了QwQ-32B和DeepSeek-R1-Distrill-Qwen-7B两个模型的推理服务端点。
InferenceModel配置了当请求的模型名称是qwq时,总和为50%的请求由qwq-32b模型进行推理,另外50%的请求发往deepseek-r1模型进行推理。
步骤三:验证基础模型灰度效果
多次执行以下命令发起测试:
GATEWAY_IP=$(kubectl get gateway/inference-gateway -o jsonpath='{.status.addresses[0].value}') curl -X POST ${GATEWAY_IP}:8081/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{ "model": "qwq", "messages": [ { "role": "user", "content": "你是谁?" } ] }' -v
可以看到和下面内容相似的输出:
curl -X POST ${GATEWAY_IP}:8081/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{ "model": "qwq", "messages": [ { "role": "user", "content": "你是谁?" } ] }' {"id":"chatcmpl-c84b098e-3eea-4a8f-9a26-2ce86c8f02d7","object":"chat.completion","created":1741866637,"model":"qwq-32b","choices":[{"index":0,"message":{"role":"assistant","reasoning_content":null,"content":"好的,用户问“你是谁?”,我需要回答我的身份。根据之前的指示,要保持口语化,简洁易懂。\n\n首先,应该明确说明自己是阿里巴巴集团旗下的通义实验室自主研发的超大规模语言模型,名字叫通义千问,英文名Qwen。然后提到我的功能,比如回答问题、创作文字、逻辑推理、编程等,这样用户知道我有哪些能力。\n\n接下来,要保持友好,邀请用户提问或给出任务,这样可以促进进一步的互动。注意不要使用复杂术语,保持自然。\n\n可能用户想确认我的能力或者有什么具体需求,所以需要简明扼要地涵盖关键点,同时保持亲切的语气。检查有没有需要补充的信息,比如技术支持或应用场景,但不要过于冗长。\n</think>\n\n你好!我是通义千问,阿里巴巴集团旗下的通义实验室自主研发的超大规模语言模型。你可以叫我Qwen。我的设计目标是成为一款能够理解和生成自然语言,同时具备多语言支持的智能助手。 \n\n我可以帮助你: \n- **回答问题**(比如常识、知识、技术等) \n- **创作文字**(写故事、公文、邮件、剧本等) \n- **表达观点**(分享对某个话题的看法或分析) \n- **逻辑推理**和**编程**(在一定范围内支持代码理解和编写) \n- **玩游戏**(比如猜谜语、脑筋急转弯等) \n\n如果你有任何问题或需要帮助,随时告诉我!😊","tool_calls":[]},"logprobs":null,"finish_reason":"stop","stop_reason":null}],"usage":{"prompt_tokens":13,"total_tokens":323,"completion_tokens":310,"prompt_tokens_details":null},"prompt_logprobs":null}% curl -X POST ${GATEWAY_IP}:8081/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{ "model": "qwq", "messages": [ { "role": "user", "content": "你是谁?" } ] }' -v {"id":"chatcmpl-c80c3414-1a2d-4e90-8569-f480bdfc5621","object":"chat.completion","created":1741866652,"model":"deepseek-r1","choices":[{"index":0,"message":{"role":"assistant","reasoning_content":null,"content":"您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1。如您有任何任何问题,我会尽我所能为您提供帮助。\n</think>\n\n您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1。如您有任何任何问题,我会尽我所能为您提供帮助。","tool_calls":[]},"logprobs":null,"finish_reason":"stop","stop_reason":null}],"usage":{"prompt_tokens":8,"total_tokens":81,"completion_tokens":73,"prompt_tokens_details":null},"prompt_logprobs":null}%
多次访问之后,可以看到QwQ-32B和DeepSeek-R1模型在均等地提供服务。
总结
通过ACK Gateway with AI Extension组件,不仅可以实现LLM推理服务多个模型服务器端点之间的智能路由和负载均衡,也可以实现针对LoRA模型、基础模型灰度场景下的模型灰度,为LLM推理场景提供了更优的解决方案。
如果您对ACK Gateway with AI Extension感兴趣,欢迎参考官方文档[5]来进行进一步探索!
相关链接:
[1] 创建包含GPU的Kubernetes集群
[2] 管理ACK托管集群的相关组件
[3] 使用OSS静态存储卷
[4] 管理ACK托管集群的相关组件
[5] 官方文档
我们是阿里巴巴云计算和大数据技术幕后的核心技术输出者。
获取关于我们的更多信息~