赞
踩
最近需要寻找一种更友好的方式来存储我们的模型。
我们曾经在使用 ormb 时遇见了问题,由于我们的模型有的非常大(数十 GB),在使用 ormb 时将会面临:
此外,我们还正在开发新的KubeGems算法商店,需要一个能够和算法商店对接的模型仓库服务,以便于仓库中的模型能够在算法商店中更好的展示。
为此,需要寻找新的替代方案。需要能够满足:
Huggingface 使用了 git + lfs 模型进行模型托管,将小文件以及代码使用 git 进行版本管理,将模型或其他大文件存放至 git lfs。
这种方式增加了一个 git 服务,对于大流量时,git 服务会成为瓶颈。
对于私有化部署,需要引入一个 git server 和对 git servre 的多租户配置,对于我们来说确实不够精简。
而且使用 git 方式,模型数据会在两个地方存放,增加维护成本。
一众云厂商的解决方案是将模型存储到了对象存储,华为 ModelArts ,百度 BML,阿里 PAI,腾讯 TI-ONE,Amazon SageMaker 等。
对于公有云来说,提供 ML 解决方案同时将数据都放在对象存储中是最好的方式。
但在私有云中,虽然也用对象存储,但我们没有 ML 的配套方案。若让用户将模型直接存储在对象存储中,将难以进行版本控制。
我们需要提供一套管理机制,放在用户和对象存储之间。
借鉴 ormb 的方式,可以将模型存储在 OCI registry 中。为了解决之前遇到的问题,需要对现有 ormb 的打包流程进行优化,将 big layer 进行拆分,增加文件复用。
这又带来新问题,对于使用者来说,必须要使用修改后的 ormb 才能正常下载模型。
改都改了不如彻底一点,基于 OCI 编写一个新的程序来帮助我们解决这个问题。对,就这么干!
以 OCI 作为服务端,OCI 服务端后端存储对接到 S3。OCI 仓库不使用 harbor,选择了更轻量的 docker registry v2。
将模型使用合适的方法分层然后 push 到 OCI 仓库,下载时再将模型拉下来合并还原。
非常好,我们的数据经过了 本地->OCI->S3 并存储起来了。但是,如果流量再大一点呢,OCI 或许是新的瓶颈。
那能不能 本地->S3 呢?这样岂不是又快又好了。
上面说到在直接使用对象存储时我们面临的问题为难以进行版本控制,且 s3 的 key 需要分发到客户端,更难以进行权限控制。
这里借鉴 git lfs 提供的思路,将文件直接从 git 直接上传到 git lfs server,而 git server 仅做了协调。
于是一个新的结构产生了:
这个协调者负责沟通用户和 S3,并包含了鉴权等,核心流程为:
这和 git lfs 非常像, git lfs 也有基于 S3 的实现, 但是我们不需要引入一个完整的 git server(带来了额外的复杂度)。
好了,总体思路确定后,开始完善每个流程的细节,这个新的东西称为 modelx 。
先解决如何存储数据,先看存储部分 server 端接口:
参考 OCI 我们 server 端仅包含三种核心对象:
name | description |
---|---|
index | 全局索引,用于寻找所有 manifest |
manifest | 版本描述文件,记录版本包含的 blob 文件 |
blob | 数据文件,实际存储数据的类型 |
所以一个 manifest 示例为:
schemaVersion: mediaType: config: - name: modelx.yaml digest: sha256:xxxxxxxx... mediaType: "?" size: 45 blobs: - name: file-a.bin digest: sha256:xxxxxxxx... mediaType: "" size: 18723 - name: file-b.bin digest: sha256:xxxxxxxx... mediaType: "?" size: 10086 annotaitons: description: "some description"
服务端接口:
method | path | description |
---|---|---|
GET | / | 获取全局索引 |
GET | /{repository}/{name}/index | 获取索引 |
GET | /{repository}/{name}/manifests/{tag} | 获取特定版本描述文件 |
DELETE | /{repository}/{name}/manifests/{tag} | 删除特定版本描述文件 |
HEAD | /{repository}/{name}/blobs/{digest} | 判断数据文件是否存在 |
GET | /{repository}/{name}/blobs/{digest} | 获取特定版本数据文件 |
PUT | /{repository}/{name}/blobs/{digest} | 上传特定版本数据文件 |
似曾相识,对,这和 OCI registry 的接口非常像,但是更简单了。
上传流程:
这里有一个隐形约定:客户端在上传 manifest 之前,确保已经上传了所有 blob。
上传 manifest 意味着客户端承诺上传了 manifest 中所有的部分,以便于其他客户端可以发现并能够下载完整的数据。
下载流程:
我们实现了一个简单的文件服务器,这对我们来说已经可以用了。
这就是一个简单的文件服务器,数据还是流过了 modelx, 那如何实现直接本地直接上传到 S3 流程呢?
这里借助了 302 状态码,当客户端上传 blob 时,可能收到 302 响应,
此时 Location Header 会包含重定向的 URI,客户端需要重新将 blob 上传至该地址。下载时也使用相同逻辑。
在使用S3作为存储后端时,我们使用到了s3 presign urls,能够对特定object生成临时 url 来上传和下载,这非常关键。
为了支持非 http 协议存储,客户端需要在收到 302 响应后根据具体地址使用不同的方式处理上传。
http(s)://
开头的重定向地址,此时客户端继续使用 http 协议上传至该地址。s3://
开头的 presign 的 S3 地址,则此时则需要客户端转为使用 s3 client 上传 blob 到该地址。这基本上是一个简单高效的,可索引的,版本化的文件存储服务。不仅可以用于存储模型,甚至可以推广到存储镜像,charts 等。
我们在研究了OCI destribution 的协议后,发现OCI协议在上传接口上无法做到能够让客户端直接与存储服务器交互。
总是需要在最终的存储服务器前增加一个适应层。
详细的可以参看 OCI Distribution Specification。
OCI 中无法获得模型文件列表,从而无法仅下载指定文件。
在已有的服务端实现中,可以看到 modelx 服务端仅负责文件存储,对于 manifest 中实际包含哪些 blob,还是由客户端决定。
我们的最终目的是用于存储模型,面临的模型可能有超大单文件以及海量小文件的场景。
除了解决如何将模型存储起来,还需要解决如何管理多个模型版本,模型下载(增量下载)。
在上一节的 manifest 中,每一个 blob 都包含了 mediaType 字段,以表示该文件的类型。可以从这里进行扩展。
一个 modelx.yaml 示例(非最终版):
config:
inputs: {}
outputs: {}
description: Awesome text generator
framework: pytorch
maintainers:
- maintainer
tags:
- modelx
- demo
task: text-generation
modelFiles:
- torch.bin
- tf_output.h5
有了上面的实现,认证可以插入到服务端几个接口中,对请求进行拦截。
目前modelx实现了基于 OIDC 的认证方案,modelx 仅需要填写一个外部 OIDC Identity Provider 地址即可使用。
最终来说,我们实现了一个简单的、 高性能的、可扩展的模型存储服务。
结合了 OCI、git-lfs 和 对象存储的优势,并解决了我们在模型管理遇到的问题。
未来 modelx 依旧还有许多事情要做,欢迎大家参与到 modelx 以及 kubegems 社区中来。
如果你想快速体验一下 modelx 可以看看Setup.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。