更好的阅读体验 👉 TensorChord Blog
在训练和调试深度学习模型时,经常需要在数据服务器、训练服务器、本地调试环境等等之间来回穿梭。这就是目前每一个算法工程师的工作现状。
而这个过程充满了痛苦,我们不得不与各种各样的工具和系统搏斗:一行行查 Manual 写出来的 Bash 脚本、花样繁多的 Dockerfile 、Kubernetes YAML 配置。当然,一定少不了最让人头秃的 CUDA 相关问题了。
A: 你的代码在我这里跑不起来啊,报错 CUDA 版本不一致
B: 啊?在我这里是好的啊
envd 希望能够让 AI/ML 领域的环境配置不再让人困扰。envd 为使用者提供了基于 python 的构建语言。你甚至不需要专门学习就能利用 envd 来完成一些复杂的环境构建过程。
def build():
base(os="ubuntu20.04", language="python")
install.cuda(version="11.2", cudnn="8")
install.python_packages(name=[
"torch"
])
其次 envd 仍在支持更多的运行时,其中包括 Kubernetes 、远端服务器、本地 Docker 或 Podman 等。确保使用者可以在任何地方都可以自信地进行模型训练和推理。这一切都是建立在不需要了解 Kubernetes 等基础设施的基础上。作为基础设施领域的工程师,我们坚信,使用 Kubernetes 体验最好的方式就是你永远不需要了解它。
最后,在 AI/ML 的场景下,envd 的构建速度比 Docker (Dockerfile v1) 快 6 倍。更快的构建帮助使用者更快地迭代模型。
让我们回到 envd 出生的那天。2022 年初,我和我的朋友金晶聊起了我们在 AI/ML 领域遇到的令人讨厌的问题。
问题非常多,但是要说最烦人的,毫无疑问是与训练开发环境相关的一系列问题。作为 AI 基础设施的工程师,我最常被我所服务的算法工程师们问到的问题包括但不限于:
docker commit
了一下提交了一个不到 10MB 的依赖,你一下把我的镜像搞大了 2GB ?于是我们就此陷入了对各自遇到的稀奇古怪的环境问题的吐槽中去了。讨论结束后,我们一起脑洞了一个好的体验应该是什么样子的。这就是 envd 的由来。
我们认为,目前体验糟糕最主要的原因,在于目前 AI 基础设施领域的产品或项目,都是面向基础设施的( Infrastructure oriented )。它们更像是为基础设施工程师设计的,而不是为终端用户:算法工程师和数据科学家们设计的。所以这些产品都以资源利用率、GPU 共享、完善的运维支持等为卖点。
但,我们作为基础设施工程师和算法工程师在技术背景和使用习惯等方面都有着显著且不可忽视的差异。这些差异都使得算法工程师们并不能很好地使用那些原本就是面向基础设施工程师设计的产品,比如 Kubernetes 。
当然,我们也坚定地认为不应该让算法工程师们学习如何使用 Kubernetes 。Kubernetes 能够做绝大多数你想做的事情,你甚至还能利用 Kubernetes 管理喷气式飞机。但是它就像是 Cloud 时代的 Linux kernel ,没有道理让每一个工程师都了解内核的工作原理吧。
如果把问题聚焦在 AI/ML 领域,那么更进一步,Docker 也可以被更简单的抽象。我们相信也没有算法工程师想了解 containerd 、runc 、buildkit 、OCI spec 这些底层晦涩的知识。
所以,envd 希望能够将这些目前已经在工业界广泛使用的基础设施,面向 AI/ML 领域进一步抽象,为算法工程师和数据科学家团队们提供更加简单易用的工具和产品。envd 的愿景是使用者再也不需要关心基础设施,只需要通过 TensorFlow 、PyTorch 、Jax 等框架开发模型即可。
为此,envd 引入了基于 python 的构建文件 build.envd
。它不再像 Kubernetes 的 YAML 配置或者是 Dockerfile 一样,而是围绕 AI/ML 的模型开发与推理来进行。envd 构建出来的仍然是兼容 OCI spec 的镜像。因此使用者可以像使用普通的镜像一样使用 envd。
下面是一个非常简单的示例。展示了如何构建一个 PyTorch 的 GPU 环境。
为了能够在 Kubernetes 、远端服务器等不同的环境下使用 envd 构建的环境,我们维护了一个非常轻量级的 sshd 的实现,并内置于其中。因此用户可以通过 ssh 协议连接到环境进行开发和调试。当然,你可以可以通过 envd 提供的语法在环境中安装需要的 vscode extension ,或者是配置使用 Jupyter 。
def build():
base(os="ubuntu20.04", language="python3")
install.vscode_extensions([
"ms-python.python",
])
# Configure jupyter notebooks.
config.jupyter()
# Configure zsh.
shell("zsh")
因为聚焦在 AI/ML 领域,因此我们对 Docker 和 buildkit 的使用进行了针对性的优化。使得在这一场景下的 envd 构建速度比 Docker (300Mbps 带宽网络环境下的 Dockerfile v1) 快 6 倍。
这得益于 envd 在各个层次上的 cache 。举个例子来说明,在 Docker 中如果 Dockerfile 前面的命令缓存失效了,那么后续的命令都要重新执行,也包括 pip install
命令。它需要重新下载。
而 envd 会在多次构建间维护 pip index 的 cache ,使得后续的构建不需要再重新下载 wheel ,只需要使用已经被缓存的包即可。
envd | Docker |
|
|
除了各个层级的 cache 之外,envd 的构建过程是自动并行的。比如,使用 apt-get install
安装系统依赖和 pip install
安装 python 依赖时是可以并行执行的,而不需要如同 Dockerfile 的实现一样,需要等待 apt-get install
执行完再执行后续的构建过程。
envd 不只是面向个人使用者设计的,它更是要解决算法团队在环境管理上的问题。
在一个团队里,通常大家会基于相似的基础配置进行修改的方式配置环境。在之前只能通过口耳相传的 Dockerfile 进行。而在 envd 中可以定义 python 函数来完成。
下面例子中的两个函数 configure_streamlit
和 configure_mnist
可以被其他的使用者复用。这些构建配置,在团队内以 envd 中定义的新函数的形式积累下来,形成 envd Hub (目前这一功能仍在设计中),这就是团队的环境管理知识库。以后再也不需要维护“祖传”的 Dockerfile 啦。
def build():
base(os="ubuntu20.04", language="python3")
configure_mnist()
configure_streamlit(8501)
def configure_streamlit(port):
install.python_packages([
"streamlit",
"streamlit_drawable_canvas",
])
runtime.expose(envd_port=port, host_port=port, service="streamlit")
runtime.daemon(commands=[
["streamlit", "run", "~/streamlit-mnist/app.py"]
])
def configure_mnist():
install.apt_packages([
"libgl1",
])
install.python_packages([
"tensorflow",
"numpy",
"opencv-python",
"matplotlib",
])
envd 目前仍处于非常早期的阶段,我们只是踏出了第一步,帮助算法工程师和数据科学家团队关注于 AI/ML 业务,而非基础设施。在近期 envd 会着眼于更好的团队需求支持,提供更加完善的 Kubernetes 运行时。
欢迎大家保持关注!
🍻
1
zsj950618 2022-09-08 20:12:50 +08:00 via Android 1
> apt-get install 安装系统依赖和 pip install 安装 python 依赖时是可以并行执行的
不一定,pip install 时需要的一些头文件,可能是从 apt install 的。 |
2
gaocegege OP 嗯对,如果不冲突的话是可以的。所以我们也在设计语法让用户自己决定是不是需要并行。
有冲突就需要 fallback 到串行。 |
3
gaocegege OP |
4
zsj950618 2022-09-08 20:15:46 +08:00 via Android 1
而且例子里的 install.apt_packages libgl1 。。。什么鬼,为什么 libgl1 需要手动安装?所有的 libx 都不应该手动安装,应该是被某个其他包依赖了,自动安装才对。否则怎么知道是兼容的?
|
5
gaocegege OP |
6
houshuu 2022-09-08 21:42:23 +08:00 3
感觉这个出发点挺好的, docker 编译确实经常挺费时间的, cache 这个挺吸引人.
平时本地开发喜欢用 python 自带的 venv, 也有环境间的 pip cache, 不过有时候系统库版本不兼容只能上容器, 这下终于是把优点结合起来了. |
7
hsfzxjy 2022-09-08 21:48:14 +08:00 via Android 1
opencv 不应该依赖 libg1 ,如果要在无头环境使用,应该装 opencv-python-headless 而不是 opencv-python ,这是官方推荐做法
|
8
gaocegege OP @hsfzxjy 这样,学习了
这个例子是参考了 2017 年的一个 mnist streamlit 的 demo ,可能有点非最佳实践 |
10
hsfzxjy 2022-09-08 23:20:22 +08:00
如果要编译 CUDA 算子,如何指定 devel 版本的镜像以使用 nvcc?
|
12
hsfzxjy 2022-09-08 23:49:59 +08:00 1
分享一个竞品 https://github.com/replicate/cog ,我觉得它提到了一个很常见的痛点:
> No more CUDA hell. Cog knows which CUDA/cuDNN/PyTorch/Tensorflow/Python combos are compatible and will set it all up correctly for you. 即一些包需要特定版本组合的环境下才能正常工作,并且这些知识通常鲜为人知,用一般的工具也难以梳理出其中的依赖。能把这些自动处理好无疑会有更大的帮助。 |
13
hsfzxjy 2022-09-08 23:51:21 +08:00
@gaocegege #11 比如 nvidia/cuda:11.7.1-devel-ubuntu20.04 带有 devel 的
|
14
hsfzxjy 2022-09-09 00:03:08 +08:00 1
还有一类问题是 在 build 镜像阶段装包需要采用与 本地调试 /容器内调试 时不同的指令。
比如如果在 build 镜像阶段想要编译一个 torch extension ,那你需要类似这样指定特殊的环境变量 TORCH_CUDA_ARCH_LIST="3.5 5.2 6.0 6.1 7.0+PTX 8.0" TORCH_NVCC_FLAGS="-Xfatbin -compress-all"。否则会报找不到驱动的错误,因为 build 镜像阶段不会挂载 GPU 驱动。 我见过很多人在这个问题上卡了很长的时间,因为这个问题在 本地调试 /容器内调试 时均不会出现,只有在 build 镜像时才出现,同时理解这个问题需要足够的底层知识储备。 类似这样的问题还存在不少。我觉得如果有可能的话,可以把这类 heuristics 都收集起来集成在这个项目内部,这会在特定时候帮用户大忙。 |
15
gaocegege OP @hsfzxjy 默认用 install.cuda 配置的就是带 devel 的,不过现在只支持 11.6 和 11.2 ,也可以用
base(image="") 自定义 base image |
16
gaocegege OP @hsfzxjy 嗯嗯,cog 是在内存里维护了一个 map ,把每个 pytorch tf 版本对应的 cuda 版本记在了里面。我们后面也有计划提供一个 func ,来让使用者指定是否要自动推理合适的 cuda 版本
|
17
hsfzxjy 2022-09-09 00:51:36 +08:00
个人还是非常希望这类项目能成功的,但可能需要和一些既有算法背景又有底层背景的人多交流,才能知道真正的痛点在什么地方。这一类用户的痛点显然能覆盖只有算法背景的人的痛点,并且是更为本质的。
我认为最头疼的是当前整个生态中各种上层软件和底层软件乃至硬件强耦合,从而有“只有特定版本组合的库能跑”、“我要重新编译这个库”、“我编译时遇到各种坑”等一系列问题。如果有人想在中间加一层,使得上层用户无需关注底层细节,这个中间层必须尽可能梳理好各种各样的耦合关系。否则用户一定会在某个时候想越过中间层去调整底层的细节,此时中间层反而是个累赘。 比如像我比较熟悉和习惯构建容器,在我看来这就是借 Python 语法实现的一个 DSL ,并没有解决我太多的痛点,反而在遇到问题时这个 DSL 会显得特别蹩脚。当然我可能不是你们的目标用户~ |
18
gaocegege OP @hsfzxjy 谢谢建议
嗯对于这样的 heuristics ,我们是希望在支持了 envd Hub 之后,由用户提供不同的 build func ,envd 本身提供的原语需要能够支持用户利用它写出各种各样的 build func 。 对于你说的这个例子,一个类似于这样的 build func 可以被定义: ```python def build_torch_extension(): os.environ("TORCH_CUDA_ARCH_LIST", "3.5 5.2 6.0 6.1 7.0+PTX 8.0" TORCH_NVCC_FLAGS="-Xfatbin -compress-all") ... ``` 在使用的时候可以: ```python load("build_torch_extension") def build(): build_torch_extension() ``` 作为一个帮助解决 dirty work 的项目,我们希望能够通过扩展性来解决各种不同场景的问题,因为不同场景的 dirty work 都不太一样,我们也很难把所有的 heuristics 都集成在项目里 当然最简单的框架和 CUDA 的版本映射,这个倒是不复杂。 |
19
gaocegege OP @hsfzxjy
> 比如像我比较熟悉和习惯构建容器,在我看来这就是借 Python 语法实现的一个 DSL ,并没有解决我太多的痛点,反而在遇到问题时这个 DSL 会显得特别蹩脚。当然我可能不是你们的目标用户~ 谢谢建议,如果你有兴趣的话,我们可以约一次线上的时间交流一下。关于你日常工作中在构建时遇到的问题,和现在你一般都是如何解决的。这对我们非常有帮助~ |