作者:陈裘凯(求索)
前言
KubeDL 是阿里开源的基于 Kubernetes 的 AI 工作负载治理框架,取自 ”Kubernetes-Deep-Learning” 的缩写,心愿可能依靠阿里巴巴的场景,将大规模机器学习作业调度与治理的教训反哺社区。目前 KubeDL 曾经进入 CNCF Sandbox 我的项目孵化,咱们会一直摸索云原生 AI 场景中的最佳实际,助力算法科学家们简略高效地实现翻新落地。
在最新的 KubeDL Release 0.4.0 版本中,咱们带来了模型版本治理(ModelVersion)的能力,AI 科学家们能够像治理镜像一样轻松地对模型版本进行追踪,打标及存储。更重要的是,在经典的机器学习流水线中,“训练”与“推理”两个阶段绝对独立,算法科学家视角中的“训练 -> 模型 -> 推理”流水线不足断层,而“模型”作为两者的两头产物正好可能充当那个“承前启后”的角色。
Github:https://github.com/kubedl-io/kubedl
网站:https://kubedl.io/model/intro/
模型治理现状
模型文件是分布式训练的产物,是通过充沛迭代与搜寻后保留的算法精髓,在工业界算法模型曾经成为了贵重的数字资产。通常不同的分布式框架会输入不同格局的模型文件,如 Tensorflow 训练作业通常输入 CheckPoint(.ckpt)、GraphDef(.pb)、SavedModel 等格局,而 PyTorch 则通常以 .pth 后缀,不同的框架会在加载模型时解析其中承载的运行时的数据流图、运行参数及其权重等信息,对于文件系统来说,它们都是一个(或一组)非凡格局的文件,就像 JPEG 和 PNG 格局的图像文件一样。
因而典型的治理形式就是把它们当作文件,托管在对立的对象存储中(如阿里云 OSS 和 AWS S3),每个租户 / 团队调配一个目录,各自成员再把模型文件存储在本人对应的子目录中,由 SRE 来对立进行读写权限的管控:
这种治理形式的优缺点都很显著:
- 益处是保留了用户的 API 应用习惯,在训练代码中将本人的目录指定为输入门路,之后将云存储的对应目录 mount 到推理服务的容器内加载模型即可;
- 但这对 SRE 提出了较高的要求,不合理的读写权限受权及误操作,可能造成文件权限泄露,甚至大面积的误删除;同时基于文件的治理形式不易实现模型的版本治理,通常要求用户本身依据文件名来标记,或是下层平台本人承当版本治理的复杂度;此外,模型文件与算法代码 / 训练参数的对应关系也无奈间接映射,甚至同个文件在屡次训练中会被屡次覆写,难以追溯历史;
基于以上现状,KubeDL 充沛联合了 Docker 镜像治理的劣势,引入了一套 Image-Based 的镜像治理 API,让分布式训练和推理服务联合得更严密天然,同时也极大简化了模型治理的复杂度。
从镜像登程
镜像(Image)是 Docker 的灵魂,也是容器时代的外围基础设施。镜像自身即分层的不可变文件系统,模型文件人造地能够作为其中的一个独立镜像层,两者联合的还会迸发出其余火花:
- 用户不必再面向文件治理模型,而是间接应用 KubeDL 提供的 ModelVersion API 即可,训练与推理服务之间通过 ModelVersion API 桥接;
- 与镜像一样,能够对模型打 Tag 实现版本追溯,并推送到对立的镜像 Registry 存储,通过 Registry 进行鉴权,同时镜像 Registry 的存储后端还能够替换成用户本人的 OSS/S3,用户能够平滑过渡;
- 模型镜像一旦构建结束,即成为只读的模板,无奈再被笼罩及篡写,践行 Serverless“不可变基础设施”的理念;
- 镜像层(Layer)通过压缩算法及哈希去重,缩小模型文件存储的老本并放慢了散发的效率;
在“模型镜像化”的根底上,还能够充沛联合开源的镜像治理组件,最大化镜像带来的劣势:
- 大规模的推理服务扩容场景中,能够通过 Dragonfly 来减速镜像散发效率,面对流量突发型场景时能够疾速弹出无状态的推理服务实例,同时防止了挂载云存储卷可能呈现的大规模实例并发读时的限流问题;
- 日常的推理服务部署,也能够通过 OpenKruise 中的 ImagePullJob 来提前对节点上的模型镜像进行预热,晋升扩容公布的效率。
Model 与 ModelVersion
KubeDL 模型治理引入了 2 个资源对象:Model 及 ModelVersion,Model 代表某个具体的模型,ModelVersion 则示意该模型迭代过程中的一个具体版本,一组 ModelVersion 从同一个 Model 派生而来。以下是示例:
apiVersion: model.kubedl.io/v1alpha1
kind: ModelVersion
metadata:
name: my-mv
namespace: default
spec:
# The model name for the model version
modelName: model1
# The entity (user or training job) that creates the model
createdBy: user1
# The image repo to push the generated model
imageRepo: modelhub/resnet
imageTag: v0.1
# The storage will be mounted at /kubedl-model inside the training container.
# Therefore, the training code should export the model at /kubedl-model path.
storage:
# The local storage to store the model
localStorage:
# The local host path to export the model
path: /foo
# The node where the chief worker run to export the model
nodeName: kind-control-plane
# The remote NAS to store the model
nfs:
# The NFS server address
server: ***.cn-beijing.nas.aliyuncs.com
# The path under which the model is stored
path: /foo
# The mounted path inside the container
mountPath: /kubedl/models
---
apiVersion: model.kubedl.io/v1alpha1
kind: Model
metadata:
name: model1
spec:
description: "this is my model"
status:
latestVersion:
imageName: modelhub/resnet:v1c072
modelVersion: mv-3
Model 资源自身只对应某类模型的形容,并追踪最新的版本的模型及其镜像名告知给用户,用户次要通过 ModelVersion 来自定义模型的配置:
- modelName: 用来指向对应的模型名称;
- createBy: 创立该 ModelVersion 的实体,用来追溯上游的生产者,通常是一个分布式训练作业;
- imageRepo: 镜像 Registry 的地址,构建实现模型镜像后将镜像推送到该地址;
- storage: 模型文件的存储载体,以后咱们反对了 NAS,AWSEfs 和 LocalStorage 三种存储介质,将来会反对更多支流的存储形式。以上的例子中展现了两种模型输入的形式(本地存储卷和 NAS 存储卷),个别只容许指定一种存储形式。
当 KubeDL 监听到 ModelVersion 的创立时,便会触发模型构建的工作流:
- 监听 ModelVersion 事件,发动一次模型构建;
- 依据 storage 的类型创立出对应的 PV 与 PVC 并期待 volume 就绪;
- 创立出 Model Builder 进行用户态的镜像构建,Model Builder 咱们采纳了 kaniko 的计划,其构建的过程与镜像格局,和规范的 Docker 完全一致,只不过这一切都在用户态产生,不依赖任何宿主机的 Docker Daemon;
- Builder 会从 volume 的对应门路中拷贝出模型文件(能够是单文件也能够是一个目录),并将其作为独立的镜像层来构建出一个残缺的 Model Image;
- 把产出的 Model Image 推送到 ModelVersion 对象中指定的镜像 Registry 仓库;
- 完结整个构建过程;
至此,该 ModelVersion 对应版本的模型便固化在了镜像仓库中,能够分发给后续的推理服务进行生产。
从训练到模型
尽管 ModelVersion 反对独立创立并发动构建,但咱们更冀望在分布式训练作业胜利完结后主动触发模型的构建,人造串联成一个流水线。
KubeDL 反对这种提交形式,以 TFJob 作业为例,在发动分布式训练时即指定好模型文件的输入门路和推送的仓库地址,当作业胜利执行结束时就会主动创立出一个 ModelVersion 对象,并将 createdBy 指向上游的作业名,而当作业执行失败或提前终止时并不会触发 ModelVersion 的创立。
以下是一个分布式 mnist 训练的例子,其将模型文件输入到本地节点的 /models/model-example-v1
门路,当顺利运行完结后即触发模型的构建:
apiVersion: "training.kubedl.io/v1alpha1"
kind: "TFJob"
metadata:
name: "tf-mnist-estimator"
spec:
cleanPodPolicy: None
# modelVersion defines the location where the model is stored.
modelVersion:
modelName: mnist-model-demo
# The dockerhub repo to push the generated image
imageRepo: simoncqk/models
storage:
localStorage:
path: /models/model-example-v1
mountPath: /kubedl-model
nodeName: kind-control-plane
tfReplicaSpecs:
Worker:
replicas: 3
restartPolicy: Never
template:
spec:
containers:
- name: tensorflow
image: kubedl/tf-mnist-estimator-api:v0.1
imagePullPolicy: Always
command:
- "python"
- "/keras_model_to_estimator.py"
- "/tmp/tfkeras_example/" # model checkpoint dir
- "/kubedl-model" # export dir for the saved_model format
% kubectl get tfjob
NAME STATE AGE MAX-LIFETIME MODEL-VERSION
tf-mnist-estimator Succeeded 10min mnist-model-demo-e7d65
% kubectl get modelversion
NAME MODEL IMAGE CREATED-BY FINISH-TIME
mnist-model-demo-e7d65 tf-mnist-model-example simoncqk/models:v19a00 tf-mnist-estimator 2021-09-19T15:20:42Z
% kubectl get po
NAME READY STATUS RESTARTS AGE
image-build-tf-mnist-estimator-v19a00 0/1 Completed 0 9min
通过这种机制,还能够将其余“仅当作业执行胜利才会输入的 Artifacts 文件”一起固化到镜像中,并在后续的阶段中应用。
从模型到推理
有了后面的根底,在部署推理服务时间接援用已构建好的 ModelVersion,便能加载对应模型并间接对外提供推理服务。至此,算法模型生命周期(代码 -> 训练 -> 模型 -> 部署上线)各阶段通过模型相干的 API 联结了起来。
通过 KubeDL 提供的 Inference 资源对象部署一个推理服务时,只需在某个 predictor 模板中填充对应的 ModelVersion 名,Inference Controller 在创立 predictor 时会注入一个 Model Loader,它会拉取承载了模型文件的镜像到本地,并通过容器间共享 Volume 的形式把模型文件挂载到主容器中,实现模型的加载。如上文所述,与 OpenKruise 的 ImagePullJob 相结合咱们能很不便地实现模型镜像预热,来为模型的加载提速。为了用户感知的一致性,推理服务的模型挂载门路与分布式训练作业的模型输入门路默认是统一的。
apiVersion: serving.kubedl.io/v1alpha1
kind: Inference
metadata:
name: hello-inference
spec:
framework: TFServing
predictors:
- name: model-predictor
# model built in previous stage.
modelVersion: mnist-model-demo-abcde
replicas: 3
batching:
batchSize: 32
template:
spec:
containers:
- name: tensorflow
args:
- --port=9000
- --rest_api_port=8500
- --model_name=mnist
- --model_base_path=/kubedl-model/
command:
- /usr/bin/tensorflow_model_server
image: tensorflow/serving:1.11.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9000
- containerPort: 8500
resources:
limits:
cpu: 2048m
memory: 2Gi
requests:
cpu: 1024m
memory: 1Gi
对于一个残缺的推理服务,可能同时 Serve 多个不同模型版本的 predictor,比方在常见搜寻举荐的场景中,冀望以 A/B Testing 试验来同时对比屡次模型迭代的成果,通过 Inference+ModelVersion 能够很容易做到。咱们对不同的 predictor 援用不同版本的模型,并调配正当权重的流量,即可达到一个推理服务下同时 Serve 不同版本的模型并灰度比拟成果的目标:
apiVersion: serving.kubedl.io/v1alpha1
kind: Inference
metadata:
name: hello-inference-multi-versions
spec:
framework: TFServing
predictors:
- name: model-a-predictor-1
modelVersion: model-a-version1
replicas: 3
trafficWeight: 30 # 30% traffic will be routed to this predictor.
batching:
batchSize: 32
template:
spec:
containers:
- name: tensorflow
// ...
- name: model-a-predictor-2
modelVersion: model-version2
replicas: 3
trafficWeight: 50 # 50% traffic will be roted to this predictor.
batching:
batchSize: 32
template:
spec:
containers:
- name: tensorflow
// ...
- name: model-a-predictor-3
modelVersion: model-version3
replicas: 3
trafficWeight: 20 # 20% traffic will be roted to this predictor.
batching:
batchSize: 32
template:
spec:
containers:
- name: tensorflow
// ...
总结
KubeDL 通过引入 Model 和 ModelVersion 两种资源对象,与规范的容器镜像相结合实现了模型构建,打标与版本追溯,不可变存储与散发等性能,解放了粗放型的模型文件管理模式,镜像化还能够与其余优良的开源社区相结合,实现镜像散发减速,模型镜像预热等性能,晋升模型部署的效率。同时,模型治理 API 的引入很好地连贯了分布式训练与推理服务两个本来割裂的阶段,显著晋升了机器学习流水线的自动化水平,以及算法科学家上线模型、试验比照的体验和效率。咱们欢送更多的用户试用 KubeDL,并向咱们提出贵重的意见,也期待有更多的开发者关注以及参加 KubeDL 社区的建设!
KubeDL Github 地址:
https://github.com/kubedl-io/kubedl
戳 此处 ,立刻理解 KubeDL 我的项目!