扩展推理运行时

简介

本文将引导你了解如何为 Large Language Models (LLMs) 以及其他模型(如图像分类、目标检测和文本分类模型)添加新的推理运行时。

Alauda AI 内置了 vLLM 推理引擎。借助自定义推理运行时,你可以引入其他推理引擎,例如 Seldon MLServer 和 Triton Inference Server。

通过引入自定义运行时,你可以扩展平台对更多模型类型和 GPU 类型的支持,并针对特定场景优化性能,以满足更广泛的业务需求。

在本节中,我们将演示如何使用自定义 Xinference 服务运行时扩展当前 AI 平台,以部署 LLM 并暴露一个兼容 OpenAI 的 API。

场景

如果你遇到以下任一情况,可以考虑扩展 AI Platform 推理服务运行时:

  • 支持新的模型类型:你的模型不被当前默认推理运行时 vLLM 原生支持。
  • 与其他硬件类型兼容:你需要在 AMD GPU 或 Huawei Ascend NPU 等硬件上执行 LLM 推理。
  • 针对特定场景进行性能优化:在某些推理场景下,新的运行时(例如 Xinference)可能比现有运行时提供更好的性能或资源利用率。
  • 自定义推理逻辑:你需要引入自定义推理逻辑或依赖库,而这些内容很难在现有默认运行时中实现。

前提条件

开始之前,请确保你满足以下条件:

  1. 你的 ACP 集群已部署并且运行正常。
  2. 你的 AI Platform 版本为 1.3 或更高。
  3. 你已准备好所需的推理运行时镜像。例如,对于 Xinference 运行时,镜像可能类似于 xprobe/xinference:v1.2.2(GPU)或 xprobe/xinference:v1.2.2-cpu(CPU)。
  4. 你拥有集群管理员权限(创建 CRD 实例所需)。

标准工作流(示例:Xinference)

按照以下步骤扩展平台。我们使用 Xinference 作为基线示例来演示标准流程。

创建推理运行时资源

你需要根据目标硬件环境(GPU/CPU/NPU)创建相应的推理运行时 ClusterServingRuntime 资源。

  1. 准备运行时 YAML 配置:

    根据你要添加的运行时类型(例如 Xinference)以及目标硬件环境,准备相应的 YAML 配置文件。以下是不同硬件环境下 Xinference 运行时的示例:

  • GPU 运行时示例
    # This is a sample YAML for Xinference GPU runtime
    apiVersion: serving.kserve.io/v1alpha1
    kind: ClusterServingRuntime
    metadata:
      name: aml-xinference-cuda-12.1 # Name of the runtime resource
      labels:
        cpaas.io/runtime-class: xinference # required runtime type label
        cpaas.io/accelerator-type: "nvidia"
        cpaas.io/cuda-version: "12.1"
      annotations:
        cpaas.io/display-name: xinference-cuda-12.1 # Display name in the UI
    spec:
      containers:
      - name: kserve-container
        image: xprobe/xinference:v1.2.2  # Replace with your actual GPU runtime image
        env:
        # Required across all runtimes – path to the model directory
        - name: MODEL_PATH
          value: /mnt/models/{{ index .Annotations "aml-model-repo" }}
        # The MODEL_UID parameter is optional for other runtimes.
        - name: MODEL_UID 
          value: '{{ index .Annotations "aml-model-repo" }}'
        # The MODEL_ENGINE parameter is required by the Xinference runtime, while it can be omitted for other runtimes.
        - name: MODEL_ENGINE 
          value: "transformers"
        # Required parameter for xinference runtime, please set it based on your model family, value: "llama" # e.g., "llama", "chatglm", etc.
        - name: MODEL_FAMILY 
          value: ""
        command:
        - bash
        - -c
        - |
            set +e
            if [ "${MODEL_PATH}" == "" ]; then
                echo "Need to set MODEL_PATH!"
                exit 1
            fi
            if [ "${MODEL_ENGINE}" == "" ]; then
                echo "Need to set MODEL_ENGINE!"
                exit 1
            fi
            if [ "${MODEL_UID}" == "" ]; then
                echo "Need to set MODEL_UID!"
                exit 1
            fi
            if [ "${MODEL_FAMILY}" == "" ]; then
                echo "Need to set MODEL_FAMILY!"
                exit 1
            fi
    
            xinference-local --host 0.0.0.0 --port 8080 &
            PID=$!
            while [ true ];
            do
                curl http://127.0.0.1:8080/docs
                if [ $? -eq 0 ]; then
                    break
                else
                    echo "waiting xinference-local server to become ready..."
                    sleep 1
                fi
            done
    
            set -e
            xinference launch --model_path ${MODEL_PATH} --model-engine ${MODEL_ENGINE} -u ${MODEL_UID} -n ${MODEL_FAMILY} -e http://127.0.0.1:8080 $@
            xinference list -e http://127.0.0.1:8080
            echo "model load succeeded, waiting server process: ${PID}..."
            wait ${PID}
        # Add this line to use $@ in the script:
        # see: https://unix.stackexchange.com/questions/144514/add-arguments-to-bash-c
        - bash
        resources:
          limits:
            cpu: 2
            memory: 6Gi
          requests:
            cpu: 2
            memory: 6Gi
        startupProbe:
          httpGet:
            path: /docs
            port: 8080
            scheme: HTTP
          failureThreshold: 60 
          periodSeconds: 10
          timeoutSeconds: 10
      supportedModelFormats:
        - name: transformers # The model format supported by the runtime
          version: "1"
    
    1. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 CPU limit。
    2. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 memory limit。
    • 提示:请务必将 image 字段的值替换为你实际准备好的运行时镜像路径。你还可以修改 annotations.cpaas.io/display-name 字段,以便在 AI Platform UI 中自定义该运行时的显示名称。
  1. 应用 YAML 文件以创建资源:

    在具有集群管理员权限的终端中,执行以下命令以应用 YAML 文件并创建推理运行时资源:

    kubectl apply -f your-xinference-runtime.yaml
    TIP
    • 重要提示:请参考上面的示例,并根据实际环境和推理需求创建/配置运行时。 这些示例仅供参考。你需要调整诸如镜像、资源 limits 和 requests 等参数,以确保运行时与模型和硬件环境兼容并高效运行。
    • 注意:只有在创建运行时资源之后,才能在推理服务发布页面上使用此自定义运行时!

发布 Xinference 推理服务并选择运行时

一旦成功创建 Xinference 推理运行时资源,你就可以在 AI Platform 上发布 LLM 推理服务时选择并配置它。

  1. 为模型配置推理框架:

    确保在即将发布的模型仓库的模型详情页面中,你已经使用 文件管理 元数据编辑功能选择了合适的 framework。此处选择的 framework 值必须与创建推理服务运行时时 supportedModelFormats 字段中包含的某个值一致。请确保模型 framework 值已列在推理运行时的 supportedModelFormats 字段中。

  2. 进入推理服务发布页面:

    登录 AI Platform 并进入 “Inference Services” 或 “Model Deployment” 模块,然后点击 “Publish Inference Service”。

  3. 选择 Xinference 运行时:

    在推理服务创建向导中,找到 “Runtime” 或 “Inference Framework” 选项。从下拉菜单或列表中,选择你在步骤 1 中创建的 Xinference 运行时(例如 “Xinference CPU Runtime” 或 “Xinference GPU Runtime (CUDA)”)。

  4. 设置环境变量: Xinference 运行时需要特定的环境变量才能正常工作。在推理服务配置页面中,找到 “Environment Variables” 或 “More Settings” 部分,并添加以下环境变量:

    • 环境变量

      参数名称描述
      MODEL_FAMILY必填。指定你正在部署的 LLM 模型所属的家族类型。Xinference 使用此参数来识别并加载该模型正确的推理逻辑。例如,如果你部署的是 Llama 3 模型,将其设置为 llama;如果是 ChatGLM 模型,则设置为 chatglm。请根据模型的实际家族类型进行设置。
    • 示例:

      • 变量名:MODEL_FAMILY
      • 变量值:llama(如果你使用的是 Llama 系列模型,请查阅 文档 了解更多详情。或者你也可以运行 xinference registrations -t LLM 来列出所有受支持的模型家族。)

特定运行时示例

了解标准工作流后,可参考以下示例查看其他运行时的具体配置。

MLServer

MLServer 运行时用途广泛,可同时用于 NVIDIA GPU 和 CPU。

kind: ClusterServingRuntime
apiVersion: serving.kserve.io/v1alpha1
metadata:
  annotations:
    cpaas.io/display-name: mlserver-cuda11.6-x86-arm
  creationTimestamp: 2026-01-05T07:02:33Z
  generation: 1
  labels:
    cpaas.io/accelerator-type: nvidia
    cpaas.io/cuda-version: "11.6"
    cpaas.io/runtime-class: mlserver
  name: aml-mlserver-cuda-11.6
spec:
  containers:
    - command:
        - /bin/bash
        - -lc
        - |
          if [ "$MODEL_TYPE" = "text-to-image" ]; then
            MODEL_IMPL="mlserver_diffusers.StableDiffusionRuntime"
          else
            MODEL_IMPL="mlserver_huggingface.HuggingFaceRuntime"
          fi            

          MODEL_DIR="${MLSERVER_MODEL_URI}/${MLSERVER_MODEL_NAME}"
          # a. using git lfs storage initializer, model will be in /mnt/models/<model_name>
          # b. using hf storage initializer, model will be in /mnt/models
          if [ ! -d "${MODEL_DIR}" ]; then
              MODEL_DIR="${MLSERVER_MODEL_URI}"
              echo "[WARNING] Model directory ${MODEL_DIR}/${MLSERVER_MODEL_NAME} not found, using ${MODEL_DIR} instead"
          fi

          export MLSERVER_MODEL_IMPLEMENTATION=${MODEL_IMPL}
          export MLSERVER_MODEL_EXTRA="{\"task\":\"${MODEL_TYPE}\",\"pretrained_model\":\"${MODEL_DIR}\"}"            

          mlserver start $MLSERVER_MODEL_URI $@
        - bash
      env:
        - name: MLSERVER_MODEL_URI
          value: /mnt/models
        - name: MLSERVER_MODEL_NAME
          value: '{{ index .Annotations "aml-model-repo" }}'
        - name: MODEL_TYPE
          value: '{{ index .Annotations "aml-pipeline-tag" }}'
      image: alaudadockerhub/seldon-mlserver:1.6.0-cu116-v1.3.1
      name: kserve-container
      resources:
        limits:
          cpu: 2
          memory: 6Gi
        requests:
          cpu: 2
          memory: 6Gi
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop:
            - ALL
        privileged: false
        runAsNonRoot: true
        runAsUser: 1000
      startupProbe:
        failureThreshold: 60
        httpGet:
          path: /v2/models/{{ index .Annotations "aml-model-repo" }}/ready
          port: 8080
          scheme: HTTP
        periodSeconds: 10
        timeoutSeconds: 10
  labels:
    modelClass: mlserver_sklearn.SKLearnModel
  supportedModelFormats:
    - name: mlflow
      version: "1"
    - name: transformers
      version: "1"
  1. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 CPU limit。
  2. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 memory limit。

Triton Inference Server

Triton Inference Server 运行时专为 NVIDIA GPU 设计,并支持多种模型格式。与 MLServer 类似,你需要先创建 ClusterServingRuntime 资源,然后再创建推理服务。

apiVersion: serving.kserve.io/v1alpha1
kind: ClusterServingRuntime
metadata:
  annotations:
    cpaas.io/display-name: triton-cuda12-x86
  labels:
    cpaas.io/accelerator-type: nvidia
    cpaas.io/cuda-version: "12.1"
    cpaas.io/runtime-class: triton
  name: aml-triton-cuda-12
spec:
  containers:
    - command:
        - /bin/bash
        - -c
        - >
          tritonserver --log-verbose=1 --http-port=8080
          --model-repository=/mnt/models
      env:
        - name: OMP_NUM_THREADS
          value: "1"
        - name: MODEL_REPO
          value: '{{ index .Annotations "aml-model-repo" }}'
      image: alaudadockerhub/tritonserver:25.02-py3
      name: kserve-container
      resources:
        limits:
          cpu: 2
          memory: 6Gi
        requests:
          cpu: 2
          memory: 6Gi
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop:
            - ALL
        privileged: false
        runAsNonRoot: true
        runAsUser: 1000
      startupProbe:
        failureThreshold: 60
        httpGet:
          path: /v2/models/{{ index .Annotations "aml-model-repo" }}/ready
          port: 8080
          scheme: HTTP
        periodSeconds: 10
        timeoutSeconds: 10
  supportedModelFormats:
    - name: triton
      version: "1"
  1. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 CPU limit。
  2. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 memory limit。

使用说明:

  1. 创建 ClusterServingRuntime:使用 kubectl apply -f triton-runtime.yaml 应用上述 YAML 配置
  2. 准备模型:确保你的模型格式受 Triton 支持(例如 TensorFlow、PyTorch、ONNX)
  3. 设置模型框架:在模型仓库中,将 framework 元数据设置为 triton,以匹配 supportedModelFormats 字段
  4. 创建推理服务:在发布推理服务时,从运行时下拉菜单中选择 Triton 运行时

vLLM-ascend(Ascend NPU)

vLLM-ascend 运行时适用于 Huawei Ascend NPU。它保留了 vLLM 的 OpenAI 兼容服务风格,同时需要为可写路径和组权限额外配置一些 InferenceService 设置。

此示例已在 Ascend 910B4 上验证。它也应适用于其他 Ascend NPU 型号,但你应根据实际环境调整资源键、镜像以及相关 版本字段。

Ascend 910 的 Modelcar 权限模式

Alauda AI 默认以非 root UID 1000 运行 KServe Modelcar。此 默认设置是为平台安全基线设计的,适用于常见 GPU 部署,例如 NVIDIA GPU 推理服务。它也适用于 Community vLLM-ascend 镜像在已验证的 Ascend 910 单卡部署中。

Ascend 910 单节点多卡推理有额外要求,因为 vLLM-ascend 使用 HCCL 进行分布式初始化。在这种场景下,请根据 运行时镜像和验证结果选择以下某一种 Modelcar 权限模式:

场景命名空间 PSA EnforceModelcar UID运行时镜像要求建议
Ascend 910 单卡推理restricted1000Community vLLM-ascend 镜像保持默认非 root 模式
使用与 UID 1000 兼容的镜像进行 Ascend 910 单节点多卡推理restricted1000该镜像允许 UID 1000 访问 Ascend 设备并支持 HCCL 初始化在验证后使用非 root 模式
在 UID 1000 兼容性尚未验证的情况下,使用社区镜像进行 Ascend 910 单节点多卡推理baseline0Community vLLM-ascend 镜像使用 root 模式
WARNING

这些模式在同一个集群中是互斥的,因为 Modelcar UID 是 在集群级别的 KServe Modelcar 设置中配置的。对于通用工作负载以及已验证的 Ascend 910 单卡服务,请保持默认的非 root UID 1000。仅在 Ascend 910 多卡 vLLM-ascend 部署确有需要时切换到 root 模式。

非 root 模式

非 root 模式保持平台默认的 Modelcar UID 1000,且命名空间 可以继续将 PSA Enforce 设置为 restricted。

对于使用 Community 镜像的 Ascend 910 单卡 vLLM-ascend 推理,请使用此模式。 对于 Ascend 910 单节点多卡推理,仅在验证该镜像支持 UID 1000 和 HCCL 初始化后再使用此模式。该镜像必须允许运行时进程访问 Ascend 设备 文件,并且 TP>1 / HCCL 初始化可能需要在 /etc/passwd 中存在匹配的 UID 1000 用户条目。

Root 模式

当所选镜像无法使用默认 Modelcar UID 1000 稳定运行时,root 模式是 Ascend 910 单节点多卡 vLLM-ascend 推理的兼容性选项。

root 模式会将集群级别的 Modelcar UID 更改为 0。这会影响集群中的 Modelcar 工作负载,而不仅仅是某个单独的推理服务。除非你的镜像或环境有要求,否则不要在单卡部署中使用 root 模式。

要启用 root 模式:

  1. 在平台控制台中,进入 项目 > 命名空间,选择推理服务使用的 命名空间,并将 Pod Security Admission Enforce 设置为 baseline。

  2. 在 AmlCluster 配置中将 KServe Modelcar UID 设置为 0:

    spec:
      components:
        kserve:
          values:
            kserve:
              storage:
                uidModelcar: 0

对于 root 模式部署,不要复用强制使用 非 root UID 的运行时配置,例如 runAsNonRoot: true 或 runAsUser: 1000。下面的 ClusterServingRuntime 示例适用于非 root 模式。

当不再需要 root 模式时,请将集群级别的 Modelcar UID 恢复为 平台默认的非 root 值 1000,并根据你的工作负载安全需求设置命名空间 PSA。

1. ClusterServingRuntime

apiVersion: serving.kserve.io/v1alpha1
kind: ClusterServingRuntime
metadata:
  annotations:
    aml.cpaas.io/model-type: '["generative"]'
    aml.cpaas.io/user-create: "true"
    cpaas.io/display-name: vllm-ascend-cann8.5
    helm.sh/resource-policy: keep
  labels:
    cpaas.io/accelerator-type: ascend
    cpaas.io/cann-version: "8.5.1"
    cpaas.io/runtime-class: vllm
  name: aml-vllm-ascend-cann-8.5.1
spec:
  containers:
    - command:
        - bash
        - -c
        - >
          set -ex

          # 1. check model path

          MODEL_DIR="/mnt/models/${MODEL_NAME}"

          # a. using git lfs storage initializer, model will be in
          /mnt/models/<model_name>

          # b. using hf storage initializer, model will be in /mnt/models

          if [ ! -d "${MODEL_DIR}" ]; then
              MODEL_DIR="/mnt/models"
              echo "[WARNING] Model directory ${MODEL_DIR}/${MODEL_NAME} not found, using ${MODEL_DIR} instead"
          fi


          # 2. check if using gguf models

          c=`find "${MODEL_DIR}" -maxdepth 1 -type f -name '*.gguf' | wc -l`

          echo "find ${c} gguf files"

          if [ "${c}" -gt 1 ]; then
              echo "[ERROR] More than one gguf file found in ${MODEL_DIR}"
              echo "Currently, vllm only supports loading single-file GGUF models. If you have a multi-files GGUF model, you can use gguf-split tool to merge them to a single-file model."
              exit 1
          elif [ "${c}" -eq 1 ]; then
              n=`find "${MODEL_DIR}" -maxdepth 1 -type f -name '*.gguf' -print`
              echo "[INFO] Using GGUF model file: ${n}"
              MODEL_PATH="${n}"
          else
              echo "[INFO] Using standard model directory"
              MODEL_PATH="${MODEL_DIR}"
          fi


          # 3. initialize Ascend toolkit environment

          if [[ -f "/usr/local/Ascend/ascend-toolkit/set_env.sh" ]]; then
              source /usr/local/Ascend/ascend-toolkit/set_env.sh
          else
              echo "ascend-toolkit package is incomplete please check it."
              exit 1
          fi


          # 4. launch vllm server

          python3 -m vllm.entrypoints.openai.api_server \

          --port 8080 \

          --served-model-name {{.Name}} {{.Namespace}}/{{.Name}} \

          --model ${MODEL_PATH} \

          --gpu-memory-utilization ${GPU_MEMORY_UTILIZATION} \

          $@
        - bash
      env:
        - name: MODEL_NAME
          value: '{{ index .Annotations "aml-model-repo" }}'
        - name: GPU_MEMORY_UTILIZATION
          value: "0.95"
        - name: HOME
          value: /tmp
        - name: USER
          value: vllm
      image: quay.io/ascend/vllm-ascend:v0.18.0rc1
      name: kserve-container
      ports:
        - containerPort: 8080
          name: http1
          protocol: TCP
      resources:
        limits:
          cpu: 2
          memory: 6Gi
        requests:
          cpu: 2
          memory: 6Gi
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop:
            - ALL
        privileged: false
        runAsNonRoot: true
        runAsUser: 1000
        seccompProfile:
          type: RuntimeDefault
      startupProbe:
        exec:
          command:
            - sh
            - -c
            - >
              curl -s -o /dev/null -w "%{http_code}" -X POST
              "http://127.0.0.1:8080/v1/completions" -H "Content-Type:
              application/json" -d '{"model": "{{ .Name }}", "prompt": "ping"}'
              | grep -q "200"
        failureThreshold: 60
        periodSeconds: 10
        timeoutSeconds: 180
      volumeMounts:
        - mountPath: /dev/shm
          name: devshm
  protocolVersions:
    - v2
  supportedModelFormats:
    - name: transformers
      version: "1"
  volumes:
    - emptyDir:
        medium: Memory
        sizeLimit: 1Gi
      name: devshm
  1. HOME 将临时文件和缓存指向 /tmp,该目录对运行时容器可写。
  2. USER 可防止 Python 的 getpass.getuser() 回退逻辑针对容器 UID 查询 /etc/passwd。这可避免在以非 root UID 运行且没有匹配 passwd 条目的镜像中发生启动失败,同时仍允许 torch_npu 正常自动加载。对于 TP>1 / HCCL 初始化,仍需验证镜像是否需要在 /etc/passwd 中存在真实的 UID 1000 条目。
  3. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 CPU limit。
  4. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 memory limit。
  5. runAsUser: 1000 使运行时容器与平台默认的 Modelcar UID 以及常见的 Ascend 设备权限模型保持一致。

2. InferenceService 示例所需变更

上面的 ClusterServingRuntime 中已经设置了 HOME 和 USER 环境变量,因此所有使用该运行时的服务都会继承它们。发布 vLLM-ascend 推理服务时,应根据 Modelcar 权限模式选择相应的 InferenceService 安全上下文。

对于非 root 模式,请添加 fsGroup: 1000 和 supplementalGroups: [1000],以便 服务能够通过预期的组权限访问 Ascend 设备文件:

kind: InferenceService
apiVersion: serving.kserve.io/v1beta1
metadata:
  name: qwen35
  namespace: demo
  annotations:
    aml-model-repo: Qwen3.5-0.8B
    modelFormat: transformers
    serving.kserve.io/deploymentMode: Standard
  labels:
    aml.cpaas.io/runtime-type: vllm
spec:
  predictor:
    model:
      modelFormat:
        name: transformers
      protocolVersion: v2
      resources:
        limits:
          cpu: "4"
          huawei.com/Ascend910B4: "1"
          memory: 16Gi
        requests:
          cpu: "2"
          memory: 8Gi
      runtime: aml-vllm-ascend-0.18.0rc1
      storageUri: pvc://qwen35/Qwen3.5-0.8B
    securityContext:
      fsGroup: 1000
      seccompProfile:
        type: RuntimeDefault
      supplementalGroups:
        - 1000
  1. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 CPU limit。
  2. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 memory limit。
  3. fsGroup: 1000 会使挂载文件继承组 1000,有助于将文件权限与允许访问 Ascend 设备的组保持一致。
  4. supplementalGroups: [1000] 会将容器进程添加到组 1000,从而可以按预期的组权限访问 Ascend 设备及相关挂载文件。

对于 root 模式的 Modelcar 部署,不需要 UID 1000 组设置。 将 InferenceService 安全上下文保持为最小配置:

spec:
  predictor:
    model:
      runtime: aml-vllm-ascend-0.18.0rc1
      storageUri: oci://<registry>/<repository>:<tag>
    securityContext:
      seccompProfile:
        type: RuntimeDefault

MindIE(Ascend NPU)

MindIE 专为 Huawei Ascend 硬件设计。它在资源管理和元数据方面的配置差异较大。

此示例已在 Ascend 310P 上验证。它也应适用于其他 Ascend NPU 型号,但你应根据实际环境调整镜像、资源配置 以及相关版本字段。

1.ClusterServingRuntime

# This is a sample YAML for Ascend NPU runtime
kind: ClusterServingRuntime
apiVersion: serving.kserve.io/v1alpha1
metadata:
  annotations:
    cpaas.io/display-name: mindie-2.2RC1
  labels:
    cpaas.io/accelerator-type: npu
    cpaas.io/cann-version: 8.3.0
    cpaas.io/runtime-class: mindie
  name: mindie-2.2rc1-310p
spec:
  containers:
    - command:
        - bash
        - -c
        - |
          REAL_SCRIPT=$(echo "$RAW_SCRIPT" | sed 's/__LT__/\x3c/g')
          echo "$REAL_SCRIPT" > /tmp/startup.sh
          chmod +x /tmp/startup.sh
          
          if [ ! -d "${MODEL_PATH}" ]; then
              MODEL_DIR="/mnt/models"
              echo "[WARNING] Model directory ${MODEL_PATH} not found, using ${MODEL_DIR} instead"
              MODEL_PATH="${MODEL_DIR}"
          fi

          CONFIG_FILE="${MODEL_PATH}/config.json"
          echo "Checking for file: ${CONFIG_FILE}"

          ls -ld "${MODEL_PATH}"
          chmod -R 755 "${MODEL_PATH}"
          echo "Fixing MODEL_PATH permission..."
          ls -ld "${MODEL_PATH}"

          /tmp/startup.sh --model-name "${MODEL_NAME}" --model-path "${MODEL_PATH}" --ip "${MY_POD_IP}"
      env:
        - name: RAW_SCRIPT
          value: |
            #!/bin/bash
            #
            #  Copyright 2024 Huawei Technologies Co., Ltd
            #
            #  Licensed under the Apache License, Version 2.0 (the "License");
            #  you may not use this file except in compliance with the License.
            #  You may obtain a copy of the License at
            #
            #  http://www.apache.org/licenses/LICENSE-2.0
            #
            #  Unless required by applicable law or agreed to in writing, software
            #  distributed under the License is distributed on an "AS IS" BASIS,
            #  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
            #  See the License for the specific language governing permissions and
            #  limitations under the License.
            #  ============================================================================
            #

            ##
            # Script Instruction
            ##
            ### Name:
            ### run_mindie.sh - Use to Start MindIE Service given a specific model
            ###
            ### Usage:
            ###   bash run_mindie.sh --model-name xxx --model-path /path/to/model
            ###
            ### Required:
            ###   --model-name             :Given a model name to identify MindIE Service.
            ###   --model-path             :Given a model path which contain necessary files such as yaml/conf.json/tokenizer/vocab etc.
            ### Options:
            ###   --help                   :Show this message.
            ###   --ip                     :The IP address bound to the MindIE Server business plane RESTful interface,default value: 127.0.0.1.
            ###   --port                   :The port bound to the MindIE Server business plane RESTful interface,default value: 1025.
            ###   --management-ip          :The IP address bound to the MindIE Server management plane RESTful interface,default value: 127.0.0.2.
            ###   --management-port        :The port bound to the MindIE Server management plane RESTful interface,default value: 1026.
            ###   --metrics-port           :The port bound to the performance indicator monitoring interface,default value: 1027.
            ###   --max-seq-len            :Maximum sequence length,default value: 2560.
            ###   --max-iter-times         :The global maximum output length of the model,default value: 512.
            ###   --max-input-token-len    :The maximum length of the token id,default value: 2048.
            ###   --max-prefill-tokens     :Each time prefill occurs, the total number of input tokens in the current batch,default value: 8192
            ###   --truncation             :Whether to perform parameter rationalization check interception,default value: false.
            ###   --template-type          :Reasoning type,default value: "Standard"
            ###   --max-preempt-count      :The upper limit of the maximum number of preemptible requests in each batch,default value: 0.
            ###   --support-select-batch   :Batch selection strategy,default value: false.
            ###   --npu-mem-size           :This can be used to apply for the upper limit of the KV Cache size in the NPU,default value: 8.
            ###   --max-prefill-batch-size :The maximum prefill batch size,default value: 50.
            ###   --world-size             :Enable several cards for inference.
            ###                             1. If it is not set, the parallel config in the YAML file is obtained by default. Set worldsize = dp*mp*pp.
            ###                             2. If set, modify the parallel config in the YAML file. set parallel config: dp:1 mp:worldSize pp:1
            ###   --ms-sched-host          :MS Scheduler IP address,default value: 127.0.0.1.
            ###   --ms-sched-port          :MS Scheduler port,default value: 8119.
            ###   For more details about config description, please check MindIE homepage: https://www.hiascend.com/document/detail/zh/mindie/10RC3/mindiellm/llmdev/mindie_llm0004.html
            help() {
              awk -F'### ' '/^###/ { print $2 }' "$0"
            }

            if [[ $# == 0 ]] || [[ "$1" == "--help" ]]; then
              help
              exit 1
            fi

            ##
            # Get device info
            ##
            total_count=$(npu-smi info -l | grep "Total Count" | awk -F ':' '{print $2}' | xargs)

            if [[ -z "$total_count" ]]; then
                echo "Error: Unable to retrieve device info. Please check if npu-smi is available for current user (id 1001), or if you are specifying an occupied device."
                exit 1
            fi

            echo "$total_count device(s) detected!"

            ##
            # Set toolkit envs
            ##
            echo "Setting toolkit envs..."
            if [[ -f "/usr/local/Ascend/ascend-toolkit/set_env.sh" ]];then
                            source /usr/local/Ascend/ascend-toolkit/set_env.sh
                    else
                            echo "ascend-toolkit package is incomplete please check it."
                            exit 1
                    fi
            echo "Toolkit envs set succeeded!"

            ##
            # Set MindIE envs
            ##
            echo "Setting MindIE envs..."
            if [[ -f "/usr/local/Ascend/mindie/set_env.sh" ]];then
                            source /usr/local/Ascend/mindie/set_env.sh
                    else
                            echo "mindie package is incomplete please check it."
                            exit 1
                    fi
            echo "MindIE envs set succeeded!"

            ##
            # Default MS envs
            ##

            # Set PYTHONPATH
            MF_SCRIPTS_ROOT=$(realpath "$(dirname "$0")")
            export PYTHONPATH=$MF_SCRIPTS_ROOT/../:$PYTHONPATH

            ##
            # Receive args and modify config.json
            ##
            export MIES_INSTALL_PATH=/usr/local/Ascend/mindie/latest/mindie-service
            CONFIG_FILE=${MIES_INSTALL_PATH}/conf/config.json
            echo "MindIE Service config path:$CONFIG_FILE"
            #default config
            BACKEND_TYPE="atb"
            MAX_SEQ_LEN=2560
            MAX_PREFILL_TOKENS=8192
            MAX_ITER_TIMES=512
            MAX_INPUT_TOKEN_LEN=2048
            TRUNCATION=false
            HTTPS_ENABLED=false
            MULTI_NODES_INFER_ENABLED=false
            NPU_MEM_SIZE=8
            MAX_PREFILL_BATCH_SIZE=50
            TEMPLATE_TYPE="Standard"
            MAX_PREEMPT_COUNT=0
            SUPPORT_SELECT_BATCH=false
            IP_ADDRESS="127.0.0.1"
            PORT=8080
            MANAGEMENT_IP_ADDRESS="127.0.0.2"
            MANAGEMENT_PORT=1026
            METRICS_PORT=1027

            #modify config
            while [[ "$#" -gt 0 ]]; do
                case $1 in
                    --model-path) MODEL_WEIGHT_PATH="$2"; shift ;;
                    --model-name) MODEL_NAME="$2"; shift ;;
                    --max-seq-len) MAX_SEQ_LEN="$2"; shift ;;
                    --max-iter-times) MAX_ITER_TIMES="$2"; shift ;;
                    --max-input-token-len) MAX_INPUT_TOKEN_LEN="$2"; shift ;;
                    --max-prefill-tokens) MAX_PREFILL_TOKENS="$2"; shift ;;
                    --truncation) TRUNCATION="$2"; shift ;;
                    --world-size) WORLD_SIZE="$2"; shift ;;
                    --template-type) TEMPLATE_TYPE="$2"; shift ;;
                    --max-preempt-count) MAX_PREEMPT_COUNT="$2"; shift ;;
                    --support-select-batch) SUPPORT_SELECT_BATCH="$2"; shift ;;
                    --npu-mem-size) NPU_MEM_SIZE="$2"; shift ;;
                    --max-prefill-batch-size) MAX_PREFILL_BATCH_SIZE="$2"; shift ;;
                    --ip) IP_ADDRESS="$2"; shift ;;
                    --port) PORT="$2"; shift ;;
                    --management-ip) MANAGEMENT_IP_ADDRESS="$2"; shift ;;
                    --management-port) MANAGEMENT_PORT="$2"; shift ;;
                    --metrics-port) METRICS_PORT="$2"; shift ;;
                    --ms-sched-host) ENV_MS_SCHED_HOST="$2"; shift ;;
                    --ms-sched-port) ENV_MS_SCHED_PORT="$2"; shift ;;
                    *)
                        echo "Unknown parameter: $1"
                        echo "Please check your inputs."
                        exit 1
                        ;;
                esac
                shift
            done

            if [ -z "$MODEL_WEIGHT_PATH" ] || [ -z "$MODEL_NAME" ]; then
                echo "Error: Both --model-path and --model-name are required."
                exit 1
            fi
            MODEL_NAME=${MODEL_NAME:-$(basename "$MODEL_WEIGHT_PATH")}
            echo "MODEL_NAME is set to: $MODEL_NAME"

            WORLD_SIZE=$total_count
            NPU_DEVICE_IDS=$(seq -s, 0 $(($WORLD_SIZE - 1)))

            #validate config
            if [[ "$BACKEND_TYPE" != "atb" ]]; then
                echo "Error: BACKEND must be 'atb'. Current value: $BACKEND_TYPE"
                exit 1
            fi

            if [[ ! "$IP_ADDRESS" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] ||
              [[ ! "$MANAGEMENT_IP_ADDRESS" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
                echo "Error: IP_ADDRESS and MANAGEMENT_IP_ADDRESS must be valid IP addresses. Current values: IP_ADDRESS=$IP_ADDRESS, MANAGEMENT_IP_ADDRESS=$MANAGEMENT_IP_ADDRESS"
                exit 1
            fi

            if [[ ! "$PORT" =~ ^[0-9]+$ ]] || (( PORT __LT__ 1025 || PORT > 65535 )) ||
              [[ ! "$MANAGEMENT_PORT" =~ ^[0-9]+$ ]] || (( MANAGEMENT_PORT __LT__ 1025 || MANAGEMENT_PORT > 65535 )); then
                echo "Error: PORT and MANAGEMENT_PORT must be integers between 1025 and 65535. Current values: PORT=$PORT, MANAGEMENT_PORT=$MANAGEMENT_PORT"
                exit 1
            fi

            if [ "$MAX_PREFILL_TOKENS" -lt "$MAX_SEQ_LEN" ]; then
                MAX_PREFILL_TOKENS=$MAX_SEQ_LEN
                echo "MAX_PREFILL_TOKENS was less than MAX_SEQ_LEN. Setting MAX_PREFILL_TOKENS to $MAX_SEQ_LEN"
            fi

            MODEL_CONFIG_FILE="${MODEL_WEIGHT_PATH}/config.json"
            if [ ! -f "$MODEL_CONFIG_FILE" ]; then
                echo "Error: config.json file not found in $MODEL_WEIGHT_PATH."
                exit 1
            fi
            chmod 600 "$MODEL_CONFIG_FILE"
            #update config file
            chmod u+w ${MIES_INSTALL_PATH}/conf/
            sed -i "s/\"backendType\"\s*:\s*\"[^\"]*\"/\"backendType\": \"$BACKEND_TYPE\"/" $CONFIG_FILE
            sed -i "s/\"modelName\"\s*:\s*\"[^\"]*\"/\"modelName\": \"$MODEL_NAME\"/" $CONFIG_FILE
            sed -i "s|\"modelWeightPath\"\s*:\s*\"[^\"]*\"|\"modelWeightPath\": \"$MODEL_WEIGHT_PATH\"|" $CONFIG_FILE
            sed -i "s/\"maxSeqLen\"\s*:\s*[0-9]*/\"maxSeqLen\": $MAX_SEQ_LEN/" "$CONFIG_FILE"
            sed -i "s/\"maxPrefillTokens\"\s*:\s*[0-9]*/\"maxPrefillTokens\": $MAX_PREFILL_TOKENS/" "$CONFIG_FILE"
            sed -i "s/\"maxIterTimes\"\s*:\s*[0-9]*/\"maxIterTimes\": $MAX_ITER_TIMES/" "$CONFIG_FILE"
            sed -i "s/\"maxInputTokenLen\"\s*:\s*[0-9]*/\"maxInputTokenLen\": $MAX_INPUT_TOKEN_LEN/" "$CONFIG_FILE"
            sed -i "s/\"truncation\"\s*:\s*[a-z]*/\"truncation\": $TRUNCATION/" "$CONFIG_FILE"
            sed -i "s|\(\"npuDeviceIds\"\s*:\s*\[\[\)[^]]*\(]]\)|\1$NPU_DEVICE_IDS\2|" "$CONFIG_FILE"
            sed -i "s/\"worldSize\"\s*:\s*[0-9]*/\"worldSize\": $WORLD_SIZE/" "$CONFIG_FILE"
            sed -i "s/\"httpsEnabled\"\s*:\s*[a-z]*/\"httpsEnabled\": $HTTPS_ENABLED/" "$CONFIG_FILE"
            sed -i "s/\"templateType\"\s*:\s*\"[^\"]*\"/\"templateType\": \"$TEMPLATE_TYPE\"/" $CONFIG_FILE
            sed -i "s/\"maxPreemptCount\"\s*:\s*[0-9]*/\"maxPreemptCount\": $MAX_PREEMPT_COUNT/" $CONFIG_FILE
            sed -i "s/\"supportSelectBatch\"\s*:\s*[a-z]*/\"supportSelectBatch\": $SUPPORT_SELECT_BATCH/" $CONFIG_FILE
            sed -i "s/\"multiNodesInferEnabled\"\s*:\s*[a-z]*/\"multiNodesInferEnabled\": $MULTI_NODES_INFER_ENABLED/" "$CONFIG_FILE"
            sed -i "s/\"maxPrefillBatchSize\"\s*:\s*[0-9]*/\"maxPrefillBatchSize\": $MAX_PREFILL_BATCH_SIZE/" "$CONFIG_FILE"
            sed -i "s/\"ipAddress\"\s*:\s*\"[^\"]*\"/\"ipAddress\": \"$IP_ADDRESS\"/" "$CONFIG_FILE"
            sed -i "s/\"port\"\s*:\s*[0-9]*/\"port\": $PORT/" "$CONFIG_FILE"
            sed -i "s/\"managementIpAddress\"\s*:\s*\"[^\"]*\"/\"managementIpAddress\": \"$MANAGEMENT_IP_ADDRESS\"/" "$CONFIG_FILE"
            sed -i "s/\"managementPort\"\s*:\s*[0-9]*/\"managementPort\": $MANAGEMENT_PORT/" "$CONFIG_FILE"
            sed -i "s/\"metricsPort\"\s*:\s*[0-9]*/\"metricsPort\": $METRICS_PORT/" $CONFIG_FILE
            sed -i "s/\"npuMemSize\"\s*:\s*-*[0-9]*/\"npuMemSize\": $NPU_MEM_SIZE/" "$CONFIG_FILE"

            ##
            # Start service
            ##
            echo "Current configurations are displayed as follows:"
            cat $CONFIG_FILE
            npu-smi info -m > ~/device_info

            ${MIES_INSTALL_PATH}/bin/mindieservice_daemon
        - name: MODEL_NAME
          value: '{{ index .Annotations "aml-model-repo" }}'
        - name: MODEL_PATH
          value: /mnt/models/{{ index .Annotations "aml-model-repo" }}
        - name: MY_POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
      image: swr.cn-south-1.myhuaweicloud.com/ascendhub/mindie:2.2.RC1-300I-Duo-py311-openeuler24.03-lts
      name: kserve-container
      resources:
        limits:
          cpu: 2
          memory: 6Gi
        requests:
          cpu: 2
          memory: 6Gi
      volumeMounts:
        - mountPath: /dev/shm
          name: dshm
      startupProbe:
        failureThreshold: 60
        httpGet:
          path: /v1/models
          port: 8080
          scheme: HTTP
        periodSeconds: 10
        timeoutSeconds: 180
  supportedModelFormats:
    - name: transformers
      version: "1"
  volumes:
    - emptyDir:
        medium: Memory
        sizeLimit: 8Gi
      name: dshm
      
  1. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 CPU limit。
  2. 请根据环境中的实际模型大小、运行时引擎、硬件类型和预期工作负载设置 memory limit。

2.InferenceService 的必需注解

与其他运行时不同,MindIE 在最终发布步骤中必须在 InferenceService 元数据中包含以下注解。这样可以确保 平台调度器正确将 NPU 硬件绑定到该服务。

配置键值目的
storage.kserve.io/readonly"false"启用对模型存储卷的写入访问。

3. 用户权限(Root 访问)

由于 Ascend 驱动和硬件抽象层的要求,MindIE 镜像必须以 root 用户运行。请确保你的 ClusterServingRuntime 或 InferenceService 安全上下文已相应配置:

注意:上面的 MindIE ClusterServingRuntime YAML 示例未指定 securityContext,这意味着容器将使用镜像的默认设置运行(通常为 root)。与显式设置 runAsNonRoot: true 和 runAsUser: 1000 的 MLServer 不同,MindIE 需要 root 权限才能访问 NPU 硬件。

运行时配置对比

在继续之前,请参考下表了解不同运行时的具体要求:

运行时目标硬件支持的框架特殊要求
XinferenceCPU / NVIDIA GPUtransformers, pytorch必须设置 MODEL_FAMILY 环境变量
MLServerCPU / NVIDIA GPUsklearn, xgboost, mlflow标准配置
TritonNVIDIA GPUtriton (TensorFlow, PyTorch, ONNX, etc.)标准配置
vLLM-ascendHuawei Ascend NPU(已在 910B4 上验证)transformers必须在 ClusterServingRuntime 中设置 HOME 和 USER,为 Ascend 910 单卡或多卡部署选择合适的 Modelcar 权限模式,并且仅在非 root 模式下添加 UID 1000 组设置
MindIEHuawei Ascend NPU(已在 310P 上验证)mindspore, transformers必须将所需的 NPU 注解添加到 InferenceService