我用GPT-2开发了一个AI写作应用,并总结了这些经验教训

writeup.ai 是一款用于自动写作的开源文本机器人,主要基于OpenAI的GPT-2,同时搭配以下一系列经过调优的模型:

  • 法律法规
  • 文案与声明
  • 歌词
  • 哈利·波特
  • 权力的游戏
  • 学术研究摘要

这一次,我们的主要目标是构建一套能够快速交付OpenAI GPT-2 Medium(一套用于生成文本的机器学习模型),并同时支持10到20款面向重度用户的应用程序。

目标概述:

  • 尝试在NLP(自然语言处理)场景中训练机器学习模型。我借此掌握了大量关于模型部署的知识。
  • 我最初预计整个周期大概为一个月,但最终总计投入三个月时间。
  • 工程难度不易估算——特别是对我这种过度自信的白痴来说~
  • 遗憾的是,我对模型训练了解得不多,所以估算自然更加困难。
  • 需要使用大量开源训练脚本(nsheppard)。我发现gwern的GPT2指南特别适用于本篇文章中的场景。另外,我还想向大家推荐Max的gpt-2-simple repo,也是份很棒的快速入门资料。
  • Wrieup.ai大体开源,我也添加了相关链接以进一步解释自己的尝试中经历的错误/失败。另外,我也添加了指向GitHub的代码链接。

链接:

背景介绍:

  • 我个人曾拥有多年的React、Django以及Flask Web应用开发经历。
  • 我在机器学习以及MLOps(机器学习DevOps)方面是个纯新手,因此请大家以平和的心态看待我的这段旅程~

读者敬启:

  • 你需要拥有一定的Web开发背景积累,我也会在文章中主动提供链接以帮助大家理解相关术语。
  • 最好能掌握一些机器学习方面的基础知识。

备注:

  • 这里我会尽可能简明扼要地进行表述。
  • 文章内会先给出全称,之后使用缩写,例如机器学习(ML)->ML。
  • 在大多数情况下,这里的模型是指机器学习模型,因此不再写成“ML模型”的形式。
  • 供应链锁定确实是个问题。我非常喜欢Google Cloud Platform(GCP),也完全不打算改旗易帜,因此本文中的某些建议也以使用GCP为前提。
  • 根据以往使用AWS的体验来看,GCP上的ML资源部署与扩展体验都要好一些。
  • 欢迎大家通过邮件、推文以及评论等方式与我交流。

技术架构:

前端

后端

微服务

语言

Javascript (ES6)

Python 3.6

Python 3.6

Web框架

ReactJS

Django (2.2) / Django-Rest-Framework

Stripped Django (2.2) / Django-Rest-Framework

托管平台

Netlify

GCP

GCP

部署流程

Netlify会根据push自动build。

startup-scripts / Ansible

startup-scripts / Ansible

创建工具

create-react-app

cookiecutter-django

cookiecutter-django

设计

Material UI

无;使用REST / WebSockets

无;使用REST / WebSockets

重要误国

slatejs – 文本编辑器

Django Channels创建WebSockets

PyTorch, TensorFlow, pytorch-transformers

  • 前端(ReactJS)加入后端(Django)中的WebSocket,并通过WebSocket实现与后端的通信。 前端代码 | 后端代码
  • 后端对前端请求进行解析与序列化,并将消息(文本、算法、设置等)打包并通过WebSocket通道发送至Google Load Balancer。 后端代码
  • 负载均衡器中继至适当的微服务(small、medium、large、哈利·波特、法律法规等)。
  • 微服务会定期使用建议的词汇对Websocket进行实时更新,从而产生“流”效果。
  • 前端从微服务处接收更新后的WebSocket消息。
  • 各个ML模型(small、medium、large、哈利·波特、法律法规、学术研究等)都属于独立的微服务,并根据使用情况进行自动规模伸缩。
  • 我尝试了无数次迭代以提高速度水平。
  • 一般来讲,我不太喜欢微服务架构(因为会增加额外的复杂性)。但必须承认,虽然为此付出了大量精力,但微服务架构确实在性能提升方面发挥着不可替代的作用。
  • 微服务的请求与计算成本,同传统的后端服务器存在巨大差别。传统的Web服务器能够轻松实现每秒500至5000条请求;但在运行1gb模型的实例当中,每秒50项请求(每项生成50到100个单词)的负载就足以让设备崩溃。(*)
  • 后端与微服务采用Python 3.6编写,并使用Django(DRF)支持后端。在微服务架构中,我使用不同的Django版本支持各独立实例。
  • 所有微服务实例均附带有GPU或者Cascade Lake CPU用以运行ML模型。后文将具体阐述。
  • 后端与微服务托管在Google Cloud Platform之上。
  • Google Load Balancer负责将所有流量路由至微服务。该负载均衡器基于“/gpt2-medium, /gtp2-medium-hp”等URL后缀,同时亦可运行健康状态检查以识别CUDA崩溃问题。

(*) - 最简单的判断方法:如果你需要刻意证明自己的用例适合微服务架构,则往往代表其没必要使用微服务方法。

在秘鲁利马的三周:

  • 我在秘鲁利马度过了三周假期,但主要内容仍然是认真编码,行程只是作为工作之余的调剂。
  • 在旅程快结束时,几位好友帮我进行了beta测试。结果嘛……又慢又容易报错。
  • 旅程当中,我把80%的时间花在了在共享办公场地里编码身上。
  • 我用了两周时间处理后端与DevOps,最后一周弄前端。
  • 随着复杂度的提升,我后来又重写了DevOps。
  • 在旅行结束时,前端已经能够通过POST请求与中继至微服务架构的后端进行通信。
  • 速度不好,质量也不高,但在看到第一消息顺利完成从前端到后端,再到微服务的端到端传播之后,我仍然感到激动万分!

MVP版本

在利马期间完成的工作:

  • 一套规模合理的前端。其拥有一款简单的文本编辑器,以及可通过微服务填充的决策选项。
  • 后端可以创建WebSocket以建立与前端的通信。在第一次迭代中,后端原本通过POST请求与微服务通信,而后将消息中继至WebSocket。我当时特别希望让微服务简单一点,不要涉及WebSocket。
  • 通过Ansible进行自动部署(后来被重构/迁移至Google Startup脚本当中)。
  • 错误:启动过早! 事后来看,我应该在构建4到5周之后再进行启动。毕竟到那时候,这才是一套真正能够正常工作的MVP。但我太紧张了,怕这东西折腾半天没个结果,有点讽刺~

随拍:凌晨两点加上空荡荡的共享办公场所,在这个神奇的地方,一切皆有可能。

90/90原则:

代码中前90%的部分占据总体开发周期的90%。但余下10%的代码部分将占用同样长度的开发周期。——贝尔实验室Tom Cargill

  • 工程师们不擅长估算项目周期。
  • 严重低估了机器学习DevOps(也有人将其称为「MLOps」)的难度。
  • 我也低估了特征渐变的管理难度。
  • 我发现在Docker容器上运行微服务,以及扩展并安装能够与模型相匹配的CUDA驱动程序非常困难,远超预期。

为了让这套机器学习微服务架构在Docker容器上正常运行,我不得不:

  • 使用一套已经安装有CUDA的定制化TensorFlow启动镜像。
  • 向谷歌方面递交了一份特别申请以安装nvidia-drivers(特殊情况,并不代表所有特定镜像)。
  • 在GCP实例上安装Docker。
  • 利用nvidia覆盖docker默认运行时(以降低docker与docker-compose的使用难度)。
  • 确保docker不会删除我的配置变更或者还原配置。
  • 对GitHub与GCP进行同步;根据push内容构建Docker镜像。
  • 对Google Cloud Storage与ML模型进行同步。我发现指向GCP存储与实例的读取速度非常快。
  • 从Google Cloud Build处pull预构建完成的Docker镜像。
  • 从云存储桶处pull ML模型,同时在Docker中挂载不同的独立文件夹。
  • 祈祷所有要求(TensorFlow/PyTorch)都能正常安装。
  • 保存磁盘镜像/快照,以便实例能够利用镜像快速冷启动。
  • 其他传统DevOps工作(git、监控、启动docker容器等等)。
  • 现在回想起来,这些工作都可以通过更简单的方式实现;但当时我有点手忙脚乱,所以没有规划好时间。

以上提到的步骤都需要完全自动化,否则无法正常扩展。都2019年了,在自动化部署流程中手动编写bash脚本确实挺傻的,但不这样就没法使用谷歌startup-scripts的自动规模伸缩功能。Kubernetes当然也能完成任务,但我智商不够,用不明白K8s~谷歌strtup-scripts会在机器启动时运行一个shell脚本,。另外,我发现在这种实例需要自动规模伸缩的情况下,很难使用Ansible。

提示: 一定要用startup-script-url! 它能通知实例利用自定义的存储桶URL运行脚本。这种方式比将脚本复制/粘贴至GCP的CLI/UI中更好。当然,大家还需要对启动脚本进行不少小幅修改。

  • 设置后端非常简单。这是我第一次使用Django Channels,基本思路就是为Django-Channels配置WebSockets属性。
  • 由于特征在不断变化,因此前端耗费了我不少时间。我一直在添加一个又一个特征,希望尽可能改善实际效果。
  • 我的微服务架构最早是用Flask编写(也是听了大家的建议)。但后来我观察了一下基准测试,并发现完全可以使用django-rest-framework获得相同的性能。对我来说,把所有内容都保存在django-rest-fraemwork当中要轻松得多(毕竟我对Django更熟悉)。
  • 微服务的优化需要不少时间。我试过好几款不同的显卡、CPU、内存以及镜像配置,这一点稍后再说。

令人震惊的事实:

  • 直到两个月之前,TensorFlow镜像中默认使用的仍然是Python 2.7版本。
  • PyTorch的docker镜像使用conda。
  • 必须覆盖之后,才能在Docker上使用nvidia运行时。
  • ML开源代码示例很多,大家可以根据需要随意添加使用。
  • 谷歌的TensorFlow Docker镜像优化效果超好,甚至能让PyTorch获得比官方镜像更快的运行速度。这可能是因为上游PyTorch镜像当中存在bug,我也不太清楚。
  • 从Docker容器(TensorFlow/PyTorch)处pull来的builds可能用不了,主要原因就是ML发展速度太快了,大家习惯就好。

提示1:尽量不要手动安装CUDA。使用预先安装完成的谷歌引导镜像才是正确选择。

提示2:请务必记录好CUDA版本/其他配置。这能确保谷歌云与你的CUDA版本及其他要求良好匹配。我遇到过不少由于CUDA版本以及框架版本冲突引发的bug,大家需要尽量避免。

总结:在了解了配置的运作方式之后(包括引导镜像、Docker镜像、Docker配置以及CUDA),剩下的工作就简单多了。可惜有很多问题我是遇上之后才知道的……

推理与其他专业术语解释

ML社区传代使用大量令人望而生畏的技术术语。我用了几个月时间学习数学,之后情况才有所好转。

总体来讲,我非常佩服那些数学水平高超的人,同时也深深为自己的愚钝感到惭愧……

术语

定义

16位

通过降低精度的方式提高训练速度。

算法

一个几乎可以指代一切的通用词汇。大家看过的研究论文越多,对这个字眼也就越熟悉,特别是在《科学》、《自然》这个级别的期刊当中。典型的表达包括“物价弟离一种新颖的算法……”

APEX

这东西需要安装在docker容器当中,却一般不需要运行。用于nvidia的16位训练。

conda

负责安装所有Python依赖项。但在正式发布之后,经常会出现在自己的机器上跑得好好的,到别人机器上却错误频发的情况。

融合 (converged)

训练完成,更多训练也无法进一步提升效果。

CUDA

类似于指向nvidia显卡的步话机,负责告知其如何运行我们写出的充满bug的代码。很少能得到正确安装。

嵌入 (embedding)

模型的内部输入表示。

推理

对“运行时或者运行”的时髦表达。“我们的推理运行时距离融合结束还有5秒”——>运行需要5秒钟。

潜在空间(latent space)

模型认为这些变量彼此相关且具有实际意义,但我们无法理解个中缘由。

nvidia-smi

一套用于监控显卡当前资源使用量的CLI。

SOTA

代表最先进之意。这个字眼一般用于形容规模极为巨大的模型,或者是把别人的模型拿来利用加入更多训练数据、更多层并进行更多次迭代。

transformers

我智商不够,解释不清楚。感兴趣的朋友可以点击此处看看jay的说法。

TPU

谷歌的定制化显卡,但并不完全属于英伟达显卡的即插即用型替代方案。需要修改代码才能运行,可以很好地服务于拥有较高内存需求的架构(例如transformers)。

python 2.7

这个版本已经比较陈旧了,但目前仍在超过七成的机器学习研究当中得到应用。

sudo pip install

属于我们永远不该做,但却在很多repo中反复见到的操作。

单元测试(unit tests)

在机器学习源代码中极少出现的作法。

英伟达最新的十款显卡

大家可以把这些显卡的性能记录成参考表的形式,而后圈出那些支持16位的型号,同时剔除听起来像是来自《终结者》电影的型号。

提示:这当然是一份戏谑性质的术语表啦,实际上机器学习领域有着诸多新表达/专业术语。我建议大家创建并更新一套你自己的术语表。

推理分析:GPU相关术语解释

  • 在Web上运行机器学习模型时,大家只有两种硬件选项:GPU(显卡)或者CPU。
  • 优势:GPU速度更快,性能一般可达CPU的5到10倍。缺点:成本更高,会增加部署复杂度。
  • 很多机器学习任务只需要花费大约一秒钟时间(例如图像分类)。这些任务比较适用于CPU。
  • 大部分用户并不会注意到异步任务当中0.05秒与0.5秒之间的运行时间差别。你的见面应该快速加载,但对任务结果则可以采用懒加载方式。
  • 在CPU上运行gpt-2 medium模型(1.2至1.5 GB)速度较慢。CPU平均每秒只能生成3到7个单词,非常影响使用体验。
  • Google Cloud上提供的选项包括Cascade Lake(最新一代至强CPU,针对机器学习进行了优化)、K80、V100以及P100。
  • 这些基准并非科学基准,而更像是那种写在餐巾纸上的快速排序小心得……总之仅供参考。

芯片

速度(每秒单词数)(1)

内存

Cascade Lake (CPU) (2)

8-11

26 gb (实例)

K80 (GPU)

12-24

11 gb

P100 (GPU)

32-64

16 gb

V100 (GPU)

32-64

16 gb

  • (1) - 这一结果来自运行多个PyTorch实例的场景。之所以这样做,是为了消除CPU/GPU之间的运行阻塞问题。例如,在能够消除CPU/GPU阻塞的前提下,在同一台安装有GPU的设备上,双PyTorch实例的单词生成量可达单一PyTorch实例的1.5倍。使用单一PyTorch应用的运行实例可能每秒会产生15个单词,但在双PyTorch应用程序场景下其可能每秒各生成10个单词。
  • (2) - 这里我出现了重大失误,我没有安装最新的MKL-DNN驱动程序。我不清楚影响大不大,大家可能会在实际测试中获得显著的性能提升,也可能没啥变化。
  • 随着文本输入量的增加,内存容量越大效果越好。
  • 就单周期成本来看,Cascade Lakes与GPU具有相同的成本效益。根据我的感觉,Cascade Lakes的实际使用体验只略低于“足够快”——换言之,Cascade Lakes生成单词的速度没有我想象的快。
  • 我发现,当一次性生成的单词数量低于50个时,K80与P100的使用体验基本相当。
  • 除了GPT-2 Large之外,我最终决定主要使用Cascade Lakes与K80。成本嘛,成本是个大问题。

提示1:大家可以为大部分负载选取抢占式运行,成本将直接缩减至二分之一。除了产品发布阶段,其余时段我都会选择抢占式运行。

提示2:如果使用抢占式方法,则谷歌会每24小时强制实例进行一次重启。请在凌晨2点创建实例,从而尽可能降低对访问者的影响。

提示3:Cascade Lakes绝对是最好的权衡选项。

注意:这些“基准”结果仅适用于推理(即实时运行模型)。大部分训练任务都在GPU上完成。

Thomson的望远镜加工新手原则

“先做四英寸镜,再做六英寸镜,速度要快于单做一块六英寸镜。”——Programming Pearls, Communications of the ACM,1985年9月

  • 先从简单的部分开始:API端点从gpt2-medium处生成单词。别着急,先从任务同步、Flask使用以及单端点场景开始。
  • 添加前端。需要查询API端点。别着急,重复请求可能会导致API崩溃。
  • 为API端点添加作为网守的后端。
  • 将Flask端点重写为Django-DRF形式。
  • 在后端当中集成django-channels以处理WebSockets。添加redis-cache以检查重复请求,而后再将请求转发至微服务架构。
  • 变更前端以通过WebSockets进行通信。
  • 重写来自Ansible的部署脚本,以符合Google Cloud的启动脚本范式。
  • 整合微服务以通过WebSockes进行通信,即允许“流传输”。
  • 训练并添加更多微服务(small、medium、large、法律法规、写作、哈利·波特、歌词、企业、xlnet等)。
  • 从简单端点起步,逐渐提高复杂程度。

优势:在经历以上流程之后,我个人在ML部署方面获得了全方位的提升。

缺点:与GCP核心产品(特别是存储、云构建、自动规模伸缩以及镜像)紧密耦合。无论是从技术层面还是策略层面来看,与单一服务供应商紧密耦合都不是件好事。

提示:如果与GCP产品紧密耦合,则可加快构建速度。在使用startup-scripts之后,一切操作都简单了很多。

总结:如果我一开始就意识到最终架构有多么复杂(我本人对DevOps确实所知甚少),那我可能直接就被吓倒了。由于缺少充足的计划,我根本不清楚自己将会面临怎样的风险。在所有错误当中,最值得反省的就是:我本应该先利用一套简单的架构进行应用程序构建,而后逐步提高复杂度并进行重构——这能让我少走很多弯路。

部署难题汇总

注意:GCP与Docker都具有镜像概念。为了避免混淆,我在文章中将GCP镜像全部称呼为引导镜像。

一般来说,使用Docker容器有助于简化部署、服务配置以及代码可重复性。

但在机器学习中使用Docker相当困难,具体问题包括:

  • 镜像往往变得出奇的大。官方TensorFlow Docker镜像往往高达500 mb甚至1.5 gb。
  • 大部分GCP机器学习引导镜像并不包含Docker/Compose。
  • 大部分包含Docker的引导镜像不提供CUDA。
  • 如果各位够胆从零开始安装TensorFlow与CUDA,请受我一拜~
  • 最好的办法是找一套相对符合要求的启动镜像,然后从二者中选择安装难度相对较低的一种(CUDA或者Docker)。在大多数情况下,Docker加相关工具的安装难度,要略低于CUDA。
  • 大部分模型的体积都超过1 gb,超出源代码控制范围。我们需要在启动/部署时配合能够实现大型模型同步的脚本。
  • 经常忘记使用命令把nvidia运行时导入Docker。
  • DevOps中的反馈循环比传统编程慢得多。单单是发现错误并做出修改这个流程,就能轻松耗掉10来分钟。如果使用谷歌的滚动部署,那周期还会更长。

优势:一旦容器设置完成,其运行健壮性还是相当靠谱的。

缺点:Docker会令部署变得更为复杂。合理的意见:既然如此,为什么不引入Kubernetes?回答是,我脑子不行,用不明白Kubernetes。

提示1:一定要谨慎,确保将正在运行的每一条shell命令都添加到Quiver日志(或者其他记录机制)当中。我们可能需要多次复制并粘贴命令,而后自动执行其中大部分操作。如果单靠脑袋硬记命令顺序,那么整个流程将很难实现自动化。

提示2:以绝对路径的形式运行/保存各项命令,以避免覆盖错误的目录。例如"rsync /path1 /path2"是正确的,"rsync path1 path2"是错误的……真的很容易出问题。

提示3:如果你熟悉Ansible,可以利用Ansible在目录上重新运行谷歌startup-scripts,其速度要远高于GCP rolling-deploys。

- name: Deploy to Open
  # startup scripts does most of the hard work, but make sure 
  # you're only deploying to things that finished from startup scripts
  hosts: open_django:&finished_startup
  gather_facts: true
  become: true
  post_tasks:
    - name: Run Startup Script
      shell: |
        google_metadata_script_runner --script-type startup --debug
      args:
        chdir: /
      become: yes

提示4:其他一些耗时的工作

  1. 哪些模型应该保存在哪个存储桶中。建议大家明确区分哪些云存储桶用于训练,哪些用于生产。
  2. 明确实例中的存储桶/目录应如何同步。
  3. 如果可能,请让实例与docker容器的安装目录处于同一位置。例如实例的/models应对应docker容器的/models路径。
  4. 将正确的rsync命令写入存储桶。再次强调,一定要使用rsync(而非cp)!在效率方面,重新启动要比通过cp提取同一文件高得多。

提示5:对PyTorch (torch.cuda.is_available) 或者TensorFlow (tf.test.is_gpu_available)进行快速自动检查,可以省去每次启动后确保Docker使用nvidia的麻烦。

总结:在这方面,很多Web工程师可能都会像我这样,在部署训练完成的ML应用程序时遇到困难。

发现瓶颈——显存不足问题

  • 对传统Web服务器的负载进行监控一般比较简单。所有GCP页面都会列出CPU的使用率百分比。在内存方面,命令开头就会快速提示当前程序正在仅代表和多少内存。谷歌的StackDriver能够将内存使用量自动转发至Google Cloud。
  • DevOps长期以来一直关注对CPU、内存、磁盘以及网络资源使用量的监控。
  • 但是,超频达人肯定更关注GPU。自从AlexNet诞生以来(或者说社区学会利用GPU处理ML负载以来),一直没有靠谱的生产级GPU监控工具。
  • 要正确监控GPU使用情况,我们必须使用nvidia-smi,设定隔多久输出一次监控结果,编写脚本以供Prometheus读取,而后将结果传递给StackDriver。简而言之,大家需要编写一项专门的微服务来监控其他微服务。
  • 使用期间,CPU与GPU的使用率均呈线性上升。在测试中,我发现极少数vCPU利用率坐飙升至80%至100%,而系统只会根据CPU使用率进行自动规模伸缩。另外,即使添加再多的vCPU以及CPU,也无法缓解GPU的高利用率状况。
  • GPU显存不足时可能引发问题。当用户传递较长的提示符(大于200个单词)时,PyTorch会发生异常,其中包含大量显存泄漏情况。为了解决这个问题,我跟踪了PyTorch异常并强制释放了未使用的显存。之所以nvidia-smi未能起效,是因为显存使用统计信息为非实时且不够精确(IIRC只显示某一进程的峰值内存使用情况)。

训练模型

  • 我在gpt2-medium的P100上对其他模型进行了调优。训练迭代(周期)的范围从权力的游戏(GoT)与哈利·波特(HP)的6万次……到学术研究(20万份论文摘要)的60万次。
  • 使用TensorFlow 1.13进行训练。
  • 训练时间从几小时(6万轮)到数天(60万轮)不等。
  • 交叉熵损失介于2到3之间。过度训练后指标失效。
  • 对nsheppard的gpt2 repo进行分叉,通过少量修改以加快大型数据集的启动速度。
  • 在掌握了ML术语之后,,大家就可以按照gwern的教程进行操作(虽然还是相当困难)。
  • 使用梯度检查点来处理显存问题。在未发生显存问题的单一GPU上无法调整gpt2-large(7.74亿个参数,1.5 gb)。
  • 数据集范围的查找与清洁工作难度不算太高,但相当枯燥乏味。
  • 再次强调,数据清洁工作约占整体工作量的80%。
  • 从Kaggle以及谷歌等处获取数据集。在清洁过程中,最耗时的工作包括数据集异常、处理换行符(\r、\n\以及回车等)、unicode检测与语言检测等。
  • Gwern使用大量bash/命令行清洁他使用的莎士比亚语料库。我建议大家使用Python,这样更易于在不同数据集上实现代码复用。
  • 无法在Docker当中正确进行16位训练(apex)。英伟达基准(存在一定营销成分)显示,16位模式可以将训练周期缩短至二分之一(甚至更低)。我没能成功,也不打算在这方面投入过多精力。
  • 在训练完成后,利用huggingface脚本将模型转换为PyTorch形式。好在pytorch-transformers的部署过程非常简单。
  • 我本来希望避免对哈利·波特语料库进行过度训练,但事后看来,过度训练还是要比训练不足好些。在对小型数据集进行过度训练/训练不足平衡时,得到的结果可能有所不同。

提示1:在准备好原始训练数据集后,请记得复制一份。千万不要修改原始数据集。将修改后的输出结果复制到单独的文件夹中。另外,修改后的数据集与原始数据集一定要存放在独立的文件夹内,以免发生错误/混淆。

提示2:如果大家发现自己需要一段时间专门清洁特定的数据集,先别急着动手,不妨找找有没有类似的可用数据集。这里说的就是哈利·波特数据集!

提示3:一定要试试tmux!使用tmux,我们可以轻松在远程设备上开始训练,而且完全不用担心意外退出。

提示4:使用Quiver保存所有命令。手动输入真的很容易发生错误。

运行模型

  • 使用PyTorch。Pytorch-transformers会为模型创建便捷的API调用站点,类似于huggingface中的run_gpt2.py示例。接下来进行大规模重构。
  • 在PyTorch加载GPT-2模型速度很难(大约需要1到2分钟)。
  • 为了缩短加载时间,在微服务启动时,WSGI会加载适当的模型(gpt2-small、medium、large等)并将PyTorch实例存储为一个单项。
    • 这一条是专门答复网友发来的,关于如何提高响应速度的邮件。
  • 所有后续请求都使用单项PyTorch实例。
  • 根据模型大小对配置中的WSGI进程运行数量做出限制。WSGI进程数量过多会导致显存不足,太少则会导致GPU利用率过低。
  • 当PyTorch显存不足以捕捉异常;释放显存泄漏。
def get_process_prompt_response(request, validated_data):
    try:
        output = generate_sequences_from_prompt(**validated_data)
    except RuntimeError as exc:
        if "out of memory" in str(exc):
            logger.exception(
                f"Ran Out of Memory When Running {validated_data}. Clearing Cache."
            )
            torch.cuda.empty_cache()
            oom_response = get_oom_response(validated_data)
            return oom_response
    response = serialize_sequences_to_response(
        output,
        validated_data["prompt"],
        validated_data["cache_key"],
        WebsocketMessageTypes.COMPLETED_RESPONSE,
        completed=validated_data["length"],
        length=validated_data["length"],
    )
    # 在所有响应上清除缓存(可能有点极端,大家谨慎使用)
    torch.cuda.empty_cache()
    return response
  • 95%的请求时间用于预测对数。其他时间则用于进行前端->后端->负载均衡器路由以及反序列化。
  • 每生成五个单词,微服务都会利用更新文本对WebSocket进行更新。
  • 向后端添加缓存以应对重复请求。
  • 为了简化来自不同实例的相同响应,我为全部请求设定了seed。

其他部署改进、蒸馏与思路

  • TensorFlow提供TensorFlow Serve,PyTorch也提供TorchScript,可用于将模型转换为生产级水平。其收益包括合理的速度改进(redditor引用提高了30%),以及在无Python设备上轻松部署等。我对某些模型上的PyTorch转换过程进行了跟踪,但发现速度优势并不太明显,反而引入了不少复杂性因素。
  • 过去几个月以来,模型蒸馏(在大小与运行时降低一半的前提下,保持90%到95%的准确率)引起了人们的关注。Huggingface对gpt2-small进行蒸馏后,体积减少了33%,而速度则提升到2倍。
  • 最近一篇关于极限语言模型压缩的论文,将BERT压缩了60倍!如果能够将其应用于GPT2,相信将具有巨大的现实意义。
  • 虽然存在一些反模式,但PyTorch与TensorFlow确实拥有非常出色的效果,使我能够更快地诊断问题并尝试潜在的解决方案。
  • 我最初集成了XLNet,但却得不到与GPT2一样强大的生成输出结果。我也尝试过让它提供单一词汇建议(类似于掩盖语言模型),但找不到能够与之匹配的合适写作用例/UI。

其他重要发现

  • 有时候需要重复之前提到的步骤。
  • Sentry 拥有强大的错误报告能力。但在配合ASGI(Django-Channels)时,其使用难度会有所提升。
  • Quiver
  • tmux——利用它保持远程会话开启。另一种办法嘛,当然就是一直盯着屏幕呗。
  • django-rest-framework的使用感受很棒,感觉就像是作弊码。
  • Netlify非常适合部署。

工作中的倦怠情绪

如何克服自己的心态波动……

  • 用头撞墙,发泄情绪~
  • 在大概2个月到2个半月时,逐渐陷入倦怠状态。
  • 感觉非常纠结,认为应该尽快启动,但又怕启动结果不好。强迫症般地认为自己错过了某些重要特征。
  • 跟好朋友的交流能极大缓解自己的紧张情绪(感觉James C的开导)。
  • 自我施压过度。在此期间,我很少给家人打电话,事实证明这是个错误的决定。后来,我发现哪怕只是跟妈妈聊聊家常,都能很好地帮自己平复心态。
  • 终于完成了,我感到相当自豪。无论是否有意,我还是在过程中学到了很多关于ML部署的知识,相信它们将在下一个项目中为我提供助力。

鸣谢

  • 感谢OpenAI的GPT2以及HuggingFace的pytorch-transformers。
  • 感谢 GCP积分机制,否则我真的租不起这么多实例。可能有个人喜好的影响,份量我觉得GCP在整体指标(包括存储桶、网络以及易用性)方面比AWS更好。
  • 帮助我进行beta测试并提供宝贵反馈的好友。感谢Christine Li、James Stewart、Kate Aksay、Zoltan Szalas、Steffan Linssen以及Harini Babu——排名当然不分先后!
  • 也要感谢各位Redditor/ProductHunt用户做出的贡献,感谢大家的反馈以及整理出的重要写作提示。

原文链接:

https://senrigan.io/blog/how-writeupai-runs-behind-the-scenes/

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/0tUIRWQUjaip2MtKgpYM

扫码关注云+社区

领取腾讯云代金券