AI模型推理服务在Knative中最佳配置实践

Knative和AI结合提供了快速部署、高弹性和低成本的技术优势,适用于需要频繁调整计算资源的AI应用场景,例如模型推理等。您可以通过Knative Pod部署AI模型推理任务,配置自动扩缩容、灵活分配GPU资源等功能,提高AI推理服务能力和GPU资源利用率。

加速模型部署

为了保证Knative Serving部署的AI模型推理服务实现快速的伸缩能力,应避免将AI模型打包到容器镜像中。当AI模型和容器镜像打包在一起时,容器的大小会显著地增大,从而减缓容器的部署速度。此外,当AI模型被打包在容器内时,AI模型的版本将与容器镜像版本绑定,增加了版本控制的复杂性。

为避免上述问题,推荐您将AI模型的数据加载到对象存储OSS、文件存储NAS等外部介质中,并通过PVC挂载到Knative服务Pod中。同时,可使用分布式数据集编排和加速引擎Fluid来加速对AI模型的读取和加载过程(参见JindoFS加速OSS文件访问EFC加速NAS或CPFS文件访问等相关文档),实现大模型的秒级加载。

前提条件

步骤一:定义数据集

当您将AI模型存放在对象存储OSS时,可以使用Dataset来声明OSS上的数据集,并使用JindoRuntime执行缓存任务。

展开查看示例YAML

apiVersion: v1
kind: Secret
metadata:
  name: access-key
stringData:
  fs.oss.accessKeyId: your_ak_id   # 请替换为访问OSS所用的access key ID。
  fs.oss.accessKeySecret: your_ak_skrt   # 请替换为访问OSS所用的access key secret。
---
apiVersion: data.fluid.io/v1alpha1
kind: Dataset
metadata:
  name: oss-data
spec:
  mounts:
  - mountPoint: "oss://{Bucket}/{path-to-model}" # 请替换为模型所在的OSS Bucket以及存储路径。
    name: xxx
    path: "{path-to-model}" # 根据程序加载模型的路径,替换该处的模型存储路径。
    options:
      fs.oss.endpoint: "oss-cn-beijing.aliyuncs.com"  # 请替换为实际的OSS endpoint地址。
    encryptOptions:
      - name: fs.oss.accessKeyId
        valueFrom:
          secretKeyRef:
            name: access-key
            key: fs.oss.accessKeyId
      - name: fs.oss.accessKeySecret
        valueFrom:
          secretKeyRef:
            name: access-key
            key: fs.oss.accessKeySecret
  accessModes:
    - ReadOnlyMany
---
apiVersion: data.fluid.io/v1alpha1
kind: JindoRuntime
metadata:
  name: oss-data
spec:
  replicas: 2
  tieredstore:
    levels:
      - mediumtype: SSD
        volumeType: emptyDir
        path: /mnt/ssd0/cache
        quota: 100Gi
        high: "0.95"
        low: "0.7"
  fuse:
    properties:
      fs.jindofsx.data.cache.enable: "true"
    args:
      - -okernel_cache
      - -oro
      - -oattr_timeout=7200
      - -oentry_timeout=7200
      - -ometrics_port=9089
    cleanPolicy: OnDemand

步骤二:挂载PVC实现加速效果

声明Dataset和JindoRuntime后,系统会自动创建同名PVC,将PVC挂载到Knative服务中即可实现加速效果。

展开查看示例YAML

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: sd-backend
spec:
  template:
    spec:
      containers:
        - image: <YOUR-IMAGE>   # 请将<YOUR-IMAGE>替换为您的镜像名称。
          name: image-name
          ports:
            - containerPort: xxx
              protocol: TCP
          volumeMounts:
            - mountPath: /data/models  # 根据程序加载模型的路径,替换moutPath。
              name: data-volume
      volumes:
        - name: data-volume
          persistentVolumeClaim:
            claimName: oss-data   # 挂载和Dataset同名的PVC。

步骤三:通过镜像缓存实现秒级加载

除AI模型本身之外,也需要考虑容器镜像大小对Knative服务的影响。运行AI模型的服务容器往往需要打包CUDA、pytorch-gpu等一系列依赖,导致其容器镜像大小显著增加。当使用ECI Pod部署Knative AI模型推理服务时,建议使用ImageCache来加速镜像的拉取过程,ImageCache能够为ECI Pod提供镜像缓存功能,通过提前缓存镜像来显著加速镜像拉取时间、实现秒级镜像拉取,参见使用ImageCache加速创建ECI Pod

apiVersion: eci.alibabacloud.com/v1
kind: ImageCache
metadata:
  name: imagecache-ai-model
  annotations:
    k8s.aliyun.com/eci-image-cache: "true" # 开启镜像缓存复用。
spec:
  images:
  - <YOUR-IMAGE>
  imageCacheSize:
    25 # 镜像缓存大小,单位GiB。
  retentionDays:
    7 # 镜像缓存保留时间。

优雅关闭容器

为了确保正在进行的请求不会突然终止,容器应在收到SIGTERM信号时启动正常关闭。

  • 收到SIGTERM信号后,如果应用程序使用HTTP探测,则设置容器为非就绪状态,防止将新的请求转发给正在关闭的容器。

  • 在关闭过程中,Knative的queue-proxy可能仍在将请求转发到容器。建议将timeoutSeconds参数应该设置为最长预期处理请求响应时间的1.2倍左右,以确保所有请求可以在容器终止之前完成。例如,如果最长的请求响应时间需要10秒,建议将timeoutSeconds参数设置为12秒。

    apiVersion: serving.knative.dev/v1
    kind: Service
    metadata:
      name: helloworld-go
      namespace: default
    spec:
      template:
        spec:
          timeoutSeconds: 12

并发参数配置

通过正确配置并发参数,您可以显著提升Knative服务的性能和响应速度。这些设置可以帮助您的应用程序更有效地处理高并发请求负载,从而提高整体的服务质量和用户体验。

  • 并发硬限制:硬限制是强制上限。如果并发达到硬限制,多余的请求将被缓冲,并且必须等待,直到有足够的可用资源来执行请求。

  • 并发软限制:软限制是有针对性的限制,而不是严格的约束。在类似请求突然爆发的情况下,可能会超过该值。

  • 目标使用率:相较于直接修改target,调整目标使用率autoscaling.knative.dev/target-utilization-percentage提供了更直观的并发级别管理方式。将此配置接近 100% 可以实现最高效的并发处理。

更多详细说明及实例,请参见并发数配置介绍

自动弹性扩缩容

在对自动弹性进行调整之前,首先需定义优化目标,例如减少延迟、最大限度地降低成本或适应尖峰流量模式。然后测量Pod在预期场景下启动所需的时间。例如,部署单个Pod可能与同时部署多个Pod具有不同的方差和平均启动时间。这些指标为自动弹性配置提供了基准。

更多参数信息及配置说明,请参见基于流量请求数实现服务自动扩缩容

扩缩容模式

自动弹性有两种模式,稳定模式(Stable Mode)和恐慌模式(Panic Mode),每种模式都有单独的窗口。稳定模式用于一般操作,而恐慌模式默认情况下具有更短的窗口,用于在流量突发时快速扩容Pod。

稳定模式窗口

通常为了保持稳定的扩缩容行为,稳定窗口应该比平均Pod启动时间长,推荐设置为平均时间的2倍。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/window: "40s"

恐慌模式窗口

恐慌窗口是稳定窗口的百分比。其主要功能是管理突然的、意外的流量高峰。

重要

此配置容易导致过度扩容,尤其当Pod启动时间较长时。请谨慎配置。

  • 如果稳定窗口设置为30秒,恐慌窗口配置为10%,则系统将使用3秒的数据来判断是否进入恐慌缩放模式。如果Pod通常需要30秒才能启动,则系统可能会在新Pod仍在上线时继续扩容,从而可能导致过度扩容Pod。

  • 在调整其他参数并确保缩放行为稳定之前,避免对恐慌窗口进行微调,尤其是当考虑到恐慌窗口的值短于或等同于Pod启动时间的情形。若稳定窗口为平均Pod启动时间的两倍,可以考虑将恐慌窗口值设定为50%,以此作为平衡正常和高峰流量模式的起点。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld
  namespace: default
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/panic-window-percentage: "20.0"

恐慌模式阈值是传入流量与服务容量的比率。根据流量的尖峰和可接受的延迟水平来调整此值。初始值为200%或更高,直到缩放行为稳定。服务运行一段时间之后,可以考虑进一步调整。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld
  namespace: default
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/panic-threshold-percentage: "150.0"

弹性速率

为了更好地响应流量模式,可以通过调整扩容和缩容速率来控制系统的弹性。默认配置通常能够满足大部分需求,但在特定场景下可能需要进行相应的调整。在调整速率之前需要观察当前情况,并优先在测试环境中进行更改和验证,以评估其影响。如果遇到扩缩容问题,请检查与扩容和缩容速率相关的参数,如稳定窗口和恐慌窗口等。

扩容速率

除非遇到特定问题,否则扩容速率通常不需要修改。例如,如果同时对多个Pod进行扩容,可能由于需等待资源的分配,导致启动时间延长,则需要调整扩容速率。如果观察到频繁扩缩,可能还需要考虑调整其他相关参数,如稳定窗口或者恐慌窗口大小,以优化伸缩行为。

apiVersion: v1
kind: ConfigMap
metadata:
  name: config-autoscaler
  namespace: knative-serving
data:
  max-scale-up-rate: "500.0"

缩容速率

缩容速率最初应设置为平均Pod启动时间或更长。默认值通常足以满足大多数使用情况,但如果重点在于优化成本,可以考虑调高速率,以便在流量高峰后更快地缩容。

缩容率是一个乘数,例如,值N/2将允许系统在弹性周期结束时将Pod数量缩减至当前数量的一半。对于Pod数量较少的服务,较低的缩容率有助于保持Pod数量更加平滑,避免频繁地伸缩造成的波动,从而提高系统稳定性。

apiVersion: v1
kind: ConfigMap
metadata:
  name: config-autoscaler
  namespace: knative-serving
data:
  max-scale-down-rate: "4.0"

延迟缩容

可以通过指定延迟缩容的时间窗口,避免新请求进入时频繁扩缩容,导致响应延迟的问题。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld-go
  namespace: default
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/scale-down-delay: "2m" // 表示延迟 2 分钟缩容

扩缩容边界

这些参数与业务的规模和可用资源高度相关。

下限

autoscaling.knative.dev/min-scale控制每个修订版应具有的最小副本数。Knative将保持不少于此数量的副本。对于那些使用率不高但需要保持始终可用的服务,将该值设置为1可以确保至少有一个副本始终运行。对于可能会有大量突发流量的服务,应将此值设定高于1以提高应对能力。如果希望服务能够在无流量时缩减到零副本,可将该值设置为0。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld
  namespace: default
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/min-scale: "0"

上限

autoscaling.knative.dev/max-scale不应超过可用资源。使用此参数主要限制最大资源成本。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld
  namespace: default
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/max-scale: "3"

初始扩容数

配置初始规模,建议配置为现有Pod数量的1.2倍左右,以便新版本可以充分处理现有流量。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld-go
  namespace: default
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/initial-scale: "3"

探针配置

您可以通过配置存活探针(Liveness Probe)和就绪探针(Readiness Probe)监测Knative服务的健康和可用性。相较于 Kubernetes 的探针策略,Knative 采用更频繁的积极探测,以最大限度减少冷启动时间,加速 Pod 启动到运行的过程。

  • Liveness Probe:存活探针用于监视容器的健康状况。如果容器处于失败状态或在容器运行中服务未成功启动,存活探针将重新启动容器。

  • Readiness Probe:配置就绪探针可以有效管理应用的自动扩缩容过程,确保只有准备好的实例可以接收流量,从而提升系统的可靠性和响应效率。

    • 在加载所有组件并且容器完全准备好之前,TCP探针不会启动监听端口。

    • 在端点能够处理请求之前,HTTP探测不应将服务标记为就绪。

    • 调整探测间隔periodSeconds时,请保持较短的间隔。理想情况下,该时间应小于默认值10秒。

更多详细说明及最佳实践,请参见在Knative中配置端口探测

按需自动缩容至零以节约资源

Knative的突出功能之一是能够在服务不使用时将Pod缩容至零。

对于需要管理多个模型的用户,推荐为每个独立的模型创建单独的Knative服务,以避免在一个服务内部集成复杂的业务逻辑。

  • 借助Knative的缩容到零的功能,可以做到长时间未被访问的模型不会产生资源成本。

  • 当客户端第一次访问模型服务时,Knative能够实现从零状态开始的自动扩容。

更多详细说明及最佳实践,请参见配置保留实例

基于多版本设置合理的灰度节奏

Knative优势之一是可以确保不间断的服务可用性,特别是在推出新版本时。当部署新代码或对服务进行更改时Knative会保持旧版本运行,直到新版本准备就绪。

可以通过流量百分比阈值进行配置。流量百分比是一个关键参数,它决定Knative何时应将流量从旧版本切换到新版本。该百分比可以低至10%,也可以高达100%,取决于具体需求。对于不需要旧服务的非生产环境,可以将流量百分比设置为100%。当新版本准备就绪时,会立即关闭旧版本。

在Pod可用性有限的情况下进行大规模操作时,需要控制旧版本的缩容速度。如果某个服务在500个Pod上运行,而只有300个额外的Pod可用,那么设置过大的流量百分比可能会导致服务部分不可用。

更多详细说明及最佳实践,请参见基于流量灰度发布服务