共计 19973 个字符,预计需要花费 50 分钟才能阅读完成。
在外卖广告 CTR 场景下,深度学习模型正在从简略 DNN 小模型过渡到千亿参数简单模型。基于该背景,本文将重点针对大规模深度模型在全链路带来的挑战,从在线时延、离线效率两个方面开展,论述外卖广告在大规模深度模型上的工程实践经验,心愿能为读者提供思路上的借鉴。
导语
随着美团外卖业务一直倒退,外卖广告引擎团队在多个畛域进行了工程上的摸索和实际,也获得了一些成绩。咱们将以连载的形式进行分享,内容次要包含:① 业务平台化的实际;② 大规模深度学习模型工程实际;③ 近线计算的摸索与实际;④ 大规模索引构建与在线检索服务实际;⑤ 机制工程平台化实际。
不久前,咱们已发表过业务平台化的实际(详情请参阅《美团外卖广告平台化的摸索与实际》一文)。本文为连载文章的第二篇,咱们将重点针对大规模深度模型在全链路层面带来的挑战,从在线时延、离线效率两个方面进行开展,论述广告在大规模深度模型上的工程实际,心愿能为大家带来一些帮忙或者启发。
1 背景
在搜寻、举荐、广告(下简称搜推广)等互联网外围业务场景下,对用户行为进行数据挖掘及趣味建模,为用户提供优质的服务,曾经成为改善用户体验、晋升营收的要害因素。近几年,针对搜推广业务,深度学习模型凭借数据红利和硬件技术红利,在业界得以宽泛落地,同时在 CTR 场景,业界逐渐从简略 DNN 小模型过渡到数万亿参数的 Embedding 大模型甚至超大模型。
外卖广告业务线次要经验了“LR 浅层模型(树模型)”->“深度学习模型”->“大规模深度学习模型”的演化过程。整个演变趋势从以人工特色为主的简略模型,逐渐向以数据为外围的简单深度学习模型进行过渡。而大模型的应用,大幅提高了模型的表达能力,更精准地实现了供需侧的匹配,为后续业务倒退提供了更多的可能性。但随着模型、数据规模的一直变大,咱们发现效率跟它们存在如下的关系:
依据上图所示,在数据规模、模型规模增长的状况下,所对应的“时长”变得会越来越长。这个“时长”对应到离线层面,体现在效率上;对应到在线层面,就体现在 Latency 上。而咱们的工作就是围绕这个“时长”的优化来发展。
2 剖析
相比一般小模型,大模型的外围问题在于:随着数据量、模型规模减少数十倍甚至百倍,整体链路上的存储、通信、计算等都将面临新的挑战,进而影响算法离线的迭代效率。如何冲破在线时延束缚等一系列问题?咱们先从全链路进行剖析,如下所示:
“时长”变长,次要会体现在以下几个方面:
- 在线时延:特色层面,在线申请不变的状况下,特色量的减少,带来的 IO、特色计算耗时减少等问题尤为突出,须要在特色算子解析编译、特色抽取外部任务调度、网络 I / O 传等方面重塑。在模型层面,模型历经百 M / G 到几百 G 的变动,在存储上带来了 2 个数量级的回升。此外,单模型的计算量也呈现了数量级的上涨(FLOPs 从百万到当初千万),单纯的靠 CPU,解决不了微小算力的需要,建设 CPU+GPU+Hierarchical Cache 推理架构来撑持大规模深度学习推理势在必行。
- 离线效率:随着样本、特色的数倍减少,样本构建,模型训练的工夫会被大大拉长,甚至会变得不可承受。如何在无限的资源下,解决海量样本构建、模型训练是零碎的首要问题。在数据层面,业界个别从两个层面去解决,一方面一直优化批处理过程中掣肘的点,另一方面把数据“化批为流”,由集中式转到摊派式,极大晋升数据的就绪工夫。在训练层面,通过硬件 GPU 并联合架构层面的优化,来达到减速目标。其次,算法翻新往往都是通过人来驱动,新数据如何疾速匹配模型,新模型如何疾速被其余业务利用,如果说将 N 集体放在 N 条业务线上独立地做同一个优化,演变成一个人在一个业务线的优化,同时播送适配到 N 个业务线,将会有 N - 1 个人力释放出来做新的翻新,这将会极大地缩短翻新的周期,尤其是在整个模型规模变大后,不可避免地会减少人工迭代的老本,实现从“人找特色 / 模型”到“特色 / 模型找人”的深度转换,缩小“反复翻新”,从而达到模型、数据智能化的匹配。
- Pipeline 其余问题:机器学习 Pipeline 并不是在大规模深度学习模型链路里才有,但随着大模型的铺开,将会有新的挑战,比方:①零碎流程如何反对全量、增量上线部署;②模型的回滚时长,把事件做正确的时长,以及事件做错后的复原时长。简而言之,会在开发、测试、部署、监测、回滚等方面产生新的诉求。
本文重点从在线时延(模型推理、特色服务 )、离线效率( 样本构建、数据筹备)等两个方面来开展,逐渐论述广告在大规模深度模型上的工程实际。如何去优化“时长”等相干问题,咱们会在后续篇章介进行分享,敬请期待。
3 模型推理
在模型推理层面,外卖广告历经了三个版本,从 1.0 时代,反对小众规模的 DNN 模型为代表,到 2.0 时代,高效、低代码反对多业务迭代,再到现在的 3.0 时代,逐渐面向深度学习 DNN 算力以及大规模存储的需要。次要演进趋势如下图所示:
面向大模型推理场景,3.0 架构解决的两个外围问题是:“存储问题”和“性能问题”。当然,面向 N 个百 G + 模型如何迭代,运算量数十倍减少时在线稳定性如何保障,Pipeline 如何加固等等,也是工程面临的挑战。上面咱们将重点介绍模型推理 3.0 架构是如何通过“分布式”来解决大模型存储问题,以及如何通过 CPU/GPU 减速来解决性能、吞吐问题。
3.1 分布式
大模型的参数次要分为两局部:Sparse 参数和 Dense 参数。
- Sparse 参数:参数量级很大,个别在亿级别,甚至十亿 / 百亿级别,这会导致存储空间占用较大,通常在百 G 级别,甚至 T 级别。其特点:① 单机加载艰难:在单机模式下,Sparse 参数需全副加载到机器内存中,导致内存重大吃紧,影响稳定性和迭代效率;② 读取稠密:每次推理计算,只需读取局部参数,比方 User 全量参数在 2 亿级别,但每次推理申请只需读取 1 个 User 参数。
- Dense 参数:参数规模不大,模型全连贯个别在 2~3 层,参数量级在百万 / 千万级别。特点:① 单机可加载:Dense 参数占用在几十兆左右,单机内存可失常加载,比方:输出层为 2000,全连贯层为[1024, 512, 256],总参数为:2000 1024 + 1024 512 + 512 * 256 + 256 = 2703616,共 270w 个参数,内存占用在百兆内;②全量读取:每次推理计算,须要读取全量参数。
因而,解决大模型参数规模增长的要害是将 Sparse 参数由单机存储革新为分布式存储,革新的形式包含两局部:① 模型网络结构转换;② Sparse 参数导出。
3.1.1 模型网络结构转换
业界对于分布式参数的获取形式大抵分为两种:内部服务提前获取参数并传给预估服务;预估服务外部通过革新 TF(TensorFlow)算子来从分布式存储获取参数。为了缩小架构革新老本和升高对现有模型构造的侵入性,咱们抉择通过革新 TF 算子的形式来获取分布式参数。
失常状况下,TF 模型会应用原生算子进行 Sparse 参数的读取,其中外围算子是 GatherV2 算子,算子的输出次要有两局部:1)须要查问的 ID 列表;2)存储 Sparse 参数的 Embedding 表。算子的作用是从 Embedding 表中读取 ID 列表索引对应的 Embedding 数据并返回,实质上是一个 Hash 查问的过程。其中,Embedding 表存储的 Sparse 参数,其在单机模型中全副存储在单机内存中。
革新 TF 算子实质上是对模型网络结构的革新,革新的外围点次要包含两局部:① 网络图重构;② 自定义分布式算子。
1. 网络图重构:革新模型网络结构,将原生 TF 算子替换为自定义分布式算子,同时进行原生 Embedding 表的固化。
- 分布式算子替换:遍历模型网络,将须要替换的 GatherV2 算子替换为自定义分布式算子 MtGatherV2,同时批改上下游节点的 Input/Output。
- 原生 Embedding 表固化:原生 Embedding 表固化为占位符,既能保留模型网络结构残缺,又能防止 Sparse 参数对单机内存的占用。
2. 自定义分布式算子:革新依据 ID 列表查问 Embedding 流程,从本地 Embedding 表中查问,革新为从分布式 KV 中查问。
- 申请查问:将输出 ID 进行去重以升高查问量,并通过分片的形式并发查问二级缓存(本地 Cache + 远端 KV)获取 Embedding 向量。
- 模型治理:保护对模型 Embedding Meta 注册、卸载流程,以及对 Cache 的创立、销毁性能。
- 模型部署:触发模型资源信息的加载,以及对 Embedding 数据并行导入 KV 的流程。
3.1.2 Sparse 参数导出
- 分片并行导出:解析模型的 Checkpoint 文件,获取 Embedding 表对应的 Part 信息,并依据 Part 进行划分,将每个 Part 文件通过多个 Worker 节点并行导出到 HDFS 上。
- 导入 KV:提前预调配多个 Bucket,Bucket 会存储模型版本等信息,便于在线路由查问。同时模型的 Embedding 数据也会存储到 Bucket 中,按分片并行形式导入到 KV 中。
整体流程如下图所示,咱们通过离线分布式模型构造转换、近线数据一致性保障、在线热点数据缓存等伎俩,保障了百 G 大模型的失常迭代需要。
能够看到,分布式借助的存储是内部 KV 能力,后续会替换为更加高效、灵便、易治理的 Embedding Service。
3.2 CPU 减速
抛开模型自身的优化伎俩外,常见的 CPU 减速伎俩次要有两种:① 指令集优化,比方应用 AVX2、AVX512 指令集;② 应用减速库(TVM、OpenVINO)。
- 指令集优化:如果应用 TensorFlow 模型,在编译 TensorFlow 框架代码时,间接在编译选项里退出指令集优化项即可。实践证明引入 AVX2、AVX512 指令集优化成果显著,在线推理服务吞吐晋升 30%+。
- 减速库优化:减速库通过对网络模型构造进行优化交融,以达到推理减速成果。业界罕用的减速库有 TVM、OpenVINO 等,其中 TVM 反对跨平台,通用性较好。OpenVINO 面向 Intel 厂商硬件进行针对性优化,通用性个别,但减速成果较好。
上面,将会重点介绍咱们应用 OpenVINO 进行 CPU 减速的一些实践经验。OpenVINO 是 Intel 推出的一套基于深度学习的计算减速优化框架,反对机器学习模型的压缩优化、减速计算等性能。OpenVINO 的减速原理简略概括为两局部:线性算子交融和数据精度校准。
- 线性算子交融:OpenVINO 通过模型优化器,将模型网络中的多层算子进行对立线性交融,以升高算子调度开销和算子间的数据访存开销,比方将 Conv+BN+Relu 三个算子合并成一个 CBR 构造算子。
- 数据精度校准:模型通过离线训练后,因为在推理的过程中不须要反向流传,齐全能够适当升高数据精度,比方降为 FP16 或 INT8 的精度,从而使得内存占用更小,推理提早更低。
CPU 减速通常是针对固定 Batch 的候选队列进行减速推理,但在搜推广场景中,候选队列往往都是动静的。这就意味着在模型推理之前,须要减少 Batch 匹配的操作,行将申请的动静 Batch 候选队列映射到一个离它最近的 Batch 模型上,但这需构建 N 个匹配模型,导致 N 倍的内存占用。而以后模型体积已达百 G 规模,内存重大吃紧。因而,选取正当的网络结构用于减速是须要思考的重点问题。下图是整体的运行架构:
- 网络散布:CTR 模型网络结构整体形象为三局部:Embedding 层、Attention 层和 MLP 层,其中 Embedding 层用于数据获取,Attention 层蕴含较多逻辑运算和轻量级的网络计算,MLP 层则为密集网络计算。
- 减速网络抉择:OpenVINO 针对纯网络计算的减速成果较好,能够很好地利用于 MLP 层。另外,模型大部分数据存储在 Embedding 层中,MLP 层占内存只有几十兆左右。如果针对 MLP 层网络划分出多个 Batch,模型内存占用在优化前(Embedding+Attention+MLP)≈ 优化后(Embedding+Attention+MLP×Batch 个数),对于内存占用的影响较小。因而,咱们最终选取 MLP 层网络作为模型减速网络。
目前,基于 OpenVINO 的 CPU 减速计划曾经在生产环境获得不错成果:CPU 与基线持平时,服务吞吐晋升 40%,均匀时延降落 15%。如果大家想在 CPU 层面做些减速的话,OpenVINO 是个不错的抉择。
3.3 GPU 减速
一方面,随着业务的倒退,业务状态越来越丰盛,流量越来越高,模型变宽变深,算力的耗费急剧减少;另一方面,广告场景次要应用 DNN 模型,波及大量稠密特色 Embedding 和神经网络浮点运算。作为访存和计算密集型的线上服务,在保障可用性的前提下,还要满足低提早、高吞吐的要求,对单机算力也是一种挑战。这些算力资源需要和空间的矛盾,如果解决不好,会极大限度业务的倒退:在模型加宽加深前,纯 CPU 推理服务可能提供可观的吞吐,然而在模型加宽加深后,计算复杂度回升,为了保障高可用性,须要耗费大量机器资源,导致大模型无奈大规模利用于线上。
目前,业界比拟通用的解决办法是利用 GPU 来解决这个问题,GPU 自身比拟实用于计算密集型工作。应用 GPU 须要解决如下挑战:如何在保障可用性、低提早的前提下,尽可能做到高吞吐,同时还须要思考易用性和通用性。为此,咱们也在 GPU 上做了大量实际工作,比方 TensorFlow-GPU、TensorFlow-TensorRT、TensorRT 等,为了兼顾 TF 的灵活性以及 TensorRT 的减速成果,咱们采纳 TensorFlow+TensorRT 独立两阶段的架构设计。
3.3.1 减速剖析
- 异构计算:咱们的思路跟 CPU 减速比拟统一,200G 的深度学习 CTR 模型不能间接全放入到 GPU 里,访存密集型算子实用(比方 Embedding 相干操作)CPU,计算密集型算子(比方 MLP)实用 GPU。
- GPU 应用须要关注的几个点:① 内存与显存的频繁交互;② 时延与吞吐;③ 扩展性与性能优化的 Trade Off;④ GPU Utilization。
- 推理引擎的抉择:业界罕用推理减速引擎有 TensorRT、TVM、XLA、ONNXRuntime 等,因为 TensorRT 在算子优化相比其余引擎更加深刻,同时能够通过自定义 plugin 的形式实现任意算子,具备很强的扩展性。而且 TensorRT 反对常见学习平台(Caffe、PyTorch、TensorFlow 等)的模型,其周边越来越欠缺(模型转换工具 onnx-tensorrt、性能剖析工具 nsys 等),因而在 GPU 侧的减速引擎应用 TensorRT。
- 模型剖析:CTR 模型网络结构整体形象为三局部:Embedding 层、Attention 层和 MLP 层,其中 Embedding 层用于数据获取,适宜 CPU;Attention 层蕴含较多逻辑运算和轻量级的网络计算,MLP 层则重网络计算,而这些计算能够并行进行,适宜 GPU,能够充分利用 GPU Core(Cuda Core、Tensor Core),进步并行度。
3.3.2 优化指标
深度学习推理阶段对算力和时延具备很高的要求,如果将训练好的神经网络间接部署到推理端,很有可能呈现算力有余无奈运行或者推理工夫较长等问题。因而,咱们须要对训练好的神经网络进行肯定的优化。业界神经网络模型优化的个别思路,能够从模型压缩、不同网络层合并、稠密化、采纳低精度数据类型等不同方面进行优化,甚至还须要依据硬件个性进行针对性优化。为此,咱们次要围绕以下两个指标进行优化:
- 延时和资源束缚下的吞吐:当 register、Cache 等共享资源不须要竞争时,进步并发可无效进步资源利用率(CPU、GPU 等利用率),但随之可能带来申请延时的上涨。因为在线零碎的延时限度十分刻薄,所以不能只通过资源利用率这一指标简略换算在线零碎的吞吐下限,须要在延时束缚下联合资源下限进行综合评估。当零碎延时较低,资源(Memory/CPU/GPU 等)利用率是制约因素时,可通过模型优化升高资源利用率;当系统资源利用率均较低,延时是制约因素时,可通过交融优化和引擎优化来升高延时。通过联合以上各种优化伎俩可无效晋升零碎服务的综合能力,进而达到晋升零碎吞吐的目标。
- 计算量束缚下的计算密度:CPU/GPU 异构零碎下,模型推理性能次要受数据拷贝效率和计算效率影响,它们别离由访存密集型算子和计算密集型算子决定,而数据拷贝效率受 PCIe 数据传输、CPU/GPU 内存读写等效率的影响,计算效率受各种计算单元 CPU Core、CUDA Core、Tensor Core 等计算效率的影响。随着 GPU 等硬件的疾速倒退,计算密集型算子的解决能力同步疾速进步,导致访存密集型算子妨碍零碎服务能力晋升的景象越来越突出,因而缩小访存密集型算子,晋升计算密度对系统服务能力也变得越来越重要,即在模型计算量变动不大的状况下,缩小数据拷贝和 kernel launch 等。比方通过模型优化和交融优化来缩小算子变换(比方 Cast/Unsqueeze/Concat 等算子)的应用,应用 CUDA Graph 缩小 kernel launch 等。
上面将围绕以上两个指标,具体介绍咱们在 模型优化 、 交融优化 和引擎优化 所做的一些工作。
3.3.3 模型优化
计算与传输去重:推理时同一 Batch 只蕴含一个用户信息,因而在进行 inference 之前能够将用户信息从 Batch Size 降为 1,真正须要 inference 时再进行开展,升高数据的传输拷贝以及反复计算开销。如下图,inference 前能够只查问一次 User 类特色信息,并在只有用户相干的子网络中进行裁剪,待须要计算关联时再开展。
- 自动化过程:找到反复计算的结点(红色结点),如果该结点的所有叶子结点都是反复计算结点,则该结点也是反复计算结点,由叶子结点逐层向上查找所有反复结点,直到结点遍历查找完,找到所有红白结点的连接线,插入 User 特色扩大结点,对 User 特色进行开展。
- 数据精度优化:因为模型训练时须要反向流传更新梯度,对数据精度要求较高;而模型推理时,只进行前向推理不须要更新梯度,所以在保障成果的前提下,应用 FP16 或混合精度进行优化,节俭内存空间,缩小传输开销,晋升推理性能和吞吐。
- 计算下推:CTR 模型构造次要由 Embedding、Attention 和 MLP 三层形成,Embedding 层偏数据获取,Attention 有局部偏逻辑,局部偏计算,为了充沛压迫 GPU 的后劲,将 CTR 模型构造中 Attention 和 MLP 大部分计算逻辑由 CPU 下沉到 GPU 进行计算,整体吞吐失去大幅晋升。
3.3.4 交融优化
在线模型 inference 时,每一层的运算操作都是由 GPU 实现,实际上是 CPU 通过启动不同的 CUDA kernel 来实现计算,CUDA kernel 计算张量的速度十分快,然而往往大量的工夫是节约在 CUDA kernel 的启动和对每一层输出 / 输入张量的读写操作上,这造成了内存带宽的瓶颈和 GPU 资源的节约。这里咱们将次要介绍 TensorRT 局部 主动优化 以及 手工优化 两块工作。
1. 主动优化:TensorRT 是一个高性能的深度学习 inference 优化器,能够为深度学习利用提供低提早、高吞吐的推理部署。TensorRT 可用于对超大规模模型、嵌入式平台或主动驾驶平台进行推理减速。TensorRT 现已能反对 TensorFlow、Caffe、MXNet、PyTorch 等简直所有的深度学习框架,将 TensorRT 和 NVIDIA 的 GPU 联合起来,能在简直所有的框架中进行疾速和高效的部署推理。而且有些优化不须要用户过多参加,比方局部 Layer Fusion、Kernel Auto-Tuning 等。
- Layer Fusion:TensorRT 通过对层间的横向或纵向合并,使网络层的数量大大减少,简略说就是通过交融一些计算 op 或者去掉一些多余 op,来缩小数据流通次数、显存的频繁应用以及调度的开销。比方常见网络结构 Convolution And ElementWise Operation 交融、CBR 交融等,下图是整个网络结构中的局部子图交融前后结构图,FusedNewOP 在交融过程中可能会波及多种 Tactic,比方 CudnnMLPFC、CudnnMLPMM、CudaMLP 等,最终会依据时长抉择一个最优的 Tactic 作为交融后的构造。通过交融操作,使得网络层数缩小、数据通道变短;雷同构造合并,使数据通道变宽;达到更加高效利用 GPU 资源的目标。
- Kernel Auto-Tuning:网络模型在 inference 时,是调用 GPU 的 CUDA kernel 进行计算。TensorRT 能够针对不同的网络模型、显卡构造、SM 数量、内核频率等进行 CUDA kernel 调整,抉择不同的优化策略和计算形式,寻找适宜以后的最优计算形式,以保障以后模型在特定平台上取得最优的性能。上图是优化次要思维,每一个 op 会有多种 kernel 优化策略(cuDNN、cuBLAS 等),依据以后架构从所有优化策略中过滤低效 kernel,同时抉择最优 kernel,最终造成新的 Network。
2. 手工优化:家喻户晓,GPU 适宜计算密集型的算子,对于其余类型算子(轻量级计算算子,逻辑运算算子等)不太敌对。应用 GPU 计算时,每次运算个别要通过几个流程:CPU 在 GPU 上调配显存 -> CPU 把数据发送给 GPU -> CPU 启动 CUDA kernel -> CPU 把数据取回 -> CPU 开释 GPU 显存。为了缩小调度、kernel launch 以及访存等开销,须要进行网络交融。因为 CTR 大模型构造灵便多变,网络交融伎俩很难对立,只能具体问题具体分析。比方在垂直方向,Cast、Unsqueeze 和 Less 交融,TensorRT 外部 Conv、BN 和 Relu 交融;在程度方向,同维度的输出算子进行交融。为此,咱们基于线上理论业务场景,应用 NVIDIA 相干性能剖析工具(NVIDIA Nsight Systems、NVIDIA Nsight Compute 等)进行具体问题的剖析。把这些性能剖析工具集成到线上 inference 环境中,取得 inference 过程中的 GPU Profing 文件。通过 Profing 文件,咱们能够清晰的看到 inference 过程,咱们发现整个 inference 中局部算子 kernel launch bound 景象重大,而且局部算子之间 gap 间隙较大,存在优化空间,如下图所示:
为此,基于性能剖析工具和转换后的模型对整个 Network 剖析,找出 TensorRT 曾经优化的局部,而后对 Network 中其余能够优化的子结构进行网络交融,同时还要保障这样的子结构在整个 Network 占有肯定的比例,保障交融后计算密度可能有肯定水平的回升。至于采纳什么样的网络交融伎俩,依据具体的场景进行灵活运用即可,如下图是咱们交融前后的子结构图比照:
3.3.5 引擎优化
- 多模型:因为外卖广告中用户申请规模不确定,广告时多时少,为此加载多个模型,每个模型对应不同输出的 Batch,将输出规模分桶归类划分,并将其 padding 到多个固定 Batch,同时对应到相应的模型进行 inference。
- Multi-contexts 和 Multi-streams:对每一个 Batch 的模型,应用多 context 和多 stream,不仅能够防止模型期待同一 context 的开销,而且能够充分利用多 stream 的并发性,实现 stream 间的 overlap,同时为了更好的解决资源竞争的问题,引入 CAS。如下图所示,单 stream 变成多 stream:
- Dynamic Shape:为了应答输出 Batch 不定场景下,不必要的数据 padding,同时缩小模型数量升高显存等资源的节约,引入 Dynamic Shape,模型依据理论输出数据进行 inference,缩小数据 padding 和不必要的计算资源节约,最终达到性能优化和吞吐晋升的目标。
- CUDA Graph:古代 GPU 每个 operation(kernel 运行等)所破费的工夫至多是微秒级别,而且,将每个 operation 提交给 GPU 也会产生一些开销(微秒级别)。理论 inference 时,常常须要执行大量的 kernel operation,这些 operation 每一个都独自提交到 GPU 并独立计算,如果能够把所有提交启动的开销汇总到一起,应该会带来性能的整体晋升。CUDA Graph 能够实现这样的性能,它将整个计算流程定义为一个图而不是单个操作的列表,而后通过提供一种由单个 CPU 操作来启动图上的多个 GPU 操作的办法缩小 kernel 提交启动的开销。CUDA Graph 核心思想是缩小 kernel launch 的次数,通过在推理前后 capture graph,依据推理的须要进行 update graph,后续推理时不再须要一次一次的 kernel launch,只须要 graph launch,最终达到缩小 kernel launch 次数的目标。如下图所示,一次 inference 执行 4 次 kernel 相干操作,通过应用 CUDA Graph 能够清晰看到优化成果。
- 多级 PS:为了进一步开掘 GPU 减速引擎性能,对 Embedding 数据的查问操作可通过多级 PS 的形式进行:GPU 显存 Cache->CPU 内存 Cache-> 本地 SSD/ 分布式 KV。其中,热点数据可缓存在 GPU 显存中,并通过数据热点的迁徙、降职和淘汰等机制对缓存数据进行动静更新,充沛开掘 GPU 的并行算力和访存能力进行高效查问。经离线测试,GPU Cache 查问性能相比 CPU Cache 晋升 10 倍 +;对于 GPU Cache 未命中数据,可通过拜访 CPU Cache 进行查问,两级 Cache 可满足 90%+ 的数据拜访;对于长尾申请,则须要通过拜访分布式 KV 进行数据获取。具体构造如下:
3.3.6 Pipeline
模型从离线训练到最终在线加载,整个流程繁琐易出错,而且模型在不同 GPU 卡、不同 TensorRT 和 CUDA 版本上无奈通用,这给模型转换带来了更多出错的可能性。因而,为了晋升模型迭代的整体效率,咱们在 Pipeline 方面进行了相干能力建设,如下图所示:
Pipeline 建设包含两局部:离线侧模型拆分转换流程,以及在线侧模型部署流程:
- 离线侧:只需提供模型拆分节点,平台会主动将原始 TF 模型拆分成 Embedding 子模型和计算图子模型,其中 Embedding 子模型通过分布式转换器进行分布式算子替换和 Embedding 导入工作;计算图子模型则依据抉择的硬件环境(GPU 型号、TensorRT 版本、CUDA 版本)进行 TensorRT 模型的转换和编译优化工作,最终将两个子模型的转换后果存储到 S3 中,用于后续的模型部署上线。整个流程都是平台主动实现,无需应用方感知执行细节。
- 在线测:只需抉择模型部署硬件环境(与模型转换的环境保持一致),平台会依据环境配置,进行模型的自适应推送加载,一键实现模型的部署上线。
Pipeline 通过配置化、一键化能力的建设,极大晋升了模型迭代效率,帮忙算法和工程同学可能更加专一的做好本职工作。下图是在 GPU 实际中相比纯 CPU 推理获得的整体收益:
4 特色服务 CodeGen 优化
特色抽取是模型计算的前置阶段,无论是传统的 LR 模型还是日趋风行的深度学习模型,都须要通过特色抽取来失去输出。在之前的博客美团外卖特色平台的建设与实际中,形容了咱们基于模型特色自描述 MFDL,将特色计算流程配置化,尽量保障了在线预估和离线训练时样本的一致性。随着业务疾速迭代,模型特色数量一直减少,特地是大模型引入了大量的离散特色,导致计算量有了成倍的增长。为此,咱们对特色抽取层做了一些优化,在吞吐和耗时上都获得了显著的收益。
4.1 全流程 CodeGen 优化
DSL 是对特色解决逻辑的形容。在晚期的特色计算实现中,每个模型配置的 DSL 都会被解释执行。解释执行的长处是实现简略,通过良好的设计便能取得较好的实现,比方罕用的迭代器模式;毛病是执行性能较低,在实现层面为了通用性防止不了增加很多的分支跳转和类型转换等。实际上,对于一个固定版本的模型配置来说,它所有的模型特色转换规则都是固定的,不会随申请而变动。极其状况下,基于这些已知的信息,能够对每个模型特色各自进行 Hard Code,从而达到最极致的性能。
显然,模型特色配置变幻无穷,不可能针对每个模型去人工编码。于是便有了 CodeGen 的想法,在编译期为每一个配置主动生成一套专有的代码。CodeGen 并不是一项具体的技术或框架,而是一种思维,实现从形象描述语言到具体执行语言的转换过程。其实在业界,计算密集型场景下应用 CodeGen 来减速计算已是罕用做法。如 Apache Spark 通过 CodeGen 来优化 SparkSql 执行性能,从 1.x 的 ExpressionCodeGen 减速表达式运算到 2.x 引入的 WholeStageCodeGen 进行全阶段的减速,都获得了非常明显的性能收益。在机器学习畛域,一些 TF 模型减速框架,如 TensorFlow XLA 和 TVM,也是基于 CodeGen 思维,将 Tensor 节点编译成对立的中间层 IR,基于 IR 联合本地环境进行调度优化,从而达到运行时模型计算减速的目标。
借鉴了 Spark 的 WholeStageCodeGen,咱们的指标是将整个特色计算 DSL 编译造成一个可执行办法,从而缩小代码运行时的性能损耗。整个编译过程能够分为:前端(FrontEnd),优化器(Optimizer)和后端(BackEnd)。前端次要负责解析指标 DSL,将源码转化为 AST 或 IR;优化器则是在前端的根底上,对失去的中间代码进行优化,使代码更加高效;后端则是将曾经优化的中间代码转化为针对各自平台的本地代码。具体实现如下:
- 前端:每个模型对应一张节点 DAG 图,一一解析每个特色计算 DSL,生成 AST,并将 AST 节点增加到图中。
- 优化器:针对 DAG 节点进行优化,比方公共算子提取、常量折叠等。
- 后端:将通过优化后的图编译成字节码。
通过优化之后,对节点 DAG 图的翻译,即后端代码实现,决定了最终的性能。这其中的一个难点,同时也是不能间接应用已有开源表达式引擎的起因:特色计算 DSL 并非是一个纯计算型表达式。它能够通过读取算子和转换算子的组合来形容特色的获取和处理过程:
- 读取算子:从存储系统获取特色的过程,是个 IO 型工作。比方查问近程 KV 零碎。
- 转换算子:特色获取到本地之后对特色进行转换,是个计算密集型工作。比方对特征值做 Hash。
所以在理论实现中,须要思考不同类型工作的调度,尽可能进步机器资源利用率,优化流程整体耗时。联合对业界的调研以及本身实际,进行了以下三种实现:
- 基于工作类型划分 Stage:将整个流程划分成获取和计算两种 Stage,Stage 外部分片并行处理,上一个 Stage 实现后再执行下一个 Stage。这是咱们晚期应用的计划,实现简略,能够基于不同的工作类型抉择不同的分片大小,比方 IO 型工作能够应用更大的分片。但毛病也很显著,会造成不同 Stage 的长尾叠加,每个 Stage 的长尾都会影响整个流程的耗时。
- 基于流水线划分 Stage:为了缩小不同 Stage 的长尾叠加,能够先将数据分片,为每个特色读取分片增加回调,在 IO 工作实现后回调计算工作,使整个流程像流水线一样平滑。分片调度能够让上一个 Stage 就绪更早的分片提前进入下一个 Stage,缩小等待时间,从而缩小整体申请耗时长尾。但毛病就是对立的分片大小不能充沛进步每个 Stage 的利用率,较小的分片会给 IO 型工作带来更多的网络耗费,较大的分片会加剧计算型工作的耗时。
- 基于 SEDA(Staged Event-Driven Architecture)形式:阶段式事件驱动形式应用队列来隔离获取 Stage 和计算 Stage,每个 Stage 调配有独立的线程池和批处理解决队列,每次生产 N(batching factor)个元素。这样既可能实现每个 Stage 独自抉择分片大小,同时事件驱动模型也能够让流程放弃平滑。这是咱们目前正在摸索的形式。
CodeGen 计划也并非完满,动静生成的代码升高了代码可读性,减少了调试老本,但以 CodeGen 作为适配层,也为更深刻的优化关上了空间。基于 CodeGen 和异步非阻塞的实现,在线上取到了不错的收益,一方面缩小了特色计算的耗时,另一方面也显著的升高了 CPU 负载,进步了零碎吞吐。将来咱们会持续施展 CodeGen 的劣势,在后端编译过程中进行针对性的优化,如摸索联合硬件指令(如 SIMD)或异构计算(如 GPU)来做更深层次的优化。
4.2 传输优化
在线预估服务整体上是双层架构,特色抽取层负责模型路由和特色计算,模型计算层负责模型计算。原有的零碎流程是将特色计算后的后果拼接成 M(预测的 Batch Size)× N(样本宽度)的矩阵,再通过序列化传输到计算层。之所以这么做,一方面出于历史起因,晚期很多非 DNN 的简略模型的输出格局是个矩阵,通过路由层拼接后,计算层能够间接应用,无需转换;另一方面,数组格局比拟紧凑,能够节俭网络传输耗时。
然而随着模型迭代倒退,DNN 模型逐步成为支流,基于矩阵传输的弊病也非常明显:
- 扩展性差:数据格式对立,不兼容非数值类型的特征值。
- 传输性能损耗:基于矩阵格局,须要对特色做对齐,比方 Query/User 维度须要被拷贝对齐到每个 Item 上,增大了申请计算层的网络传输数据量。
为了解决以上问题,优化后的流程在传输层之上退出一层转换层,用来依据 MDFL 的配置将计算的模型特色转换成须要的格局,比方 Tensor、矩阵或离线应用的 CSV 格局等。
理论线上大多数模型都是 TF 模型,为了进一步节俭传输耗费,平台设计了 Tensor Sequence 格局来存储每个 Tensor 矩阵:其中,r_flag 用来标记是否是 item 类特色,length 示意 item 特色的长度,值为 M(Item 个数)× NF(特色长度),data 用来存储理论的特征值,对于 Item 特色将 M 个特征值扁平化存储,对于申请类特色则间接填充。基于紧凑型 Tensor Sequence 格局使数据结构更加紧凑,缩小网络传输数据量。优化后的传输格局在线上也获得不错的成果,路由层调用计算层的申请大小降落了 50%+,网络传输耗时显著降落。
4.3 高维 ID 特色编码
离散特色和序列特色能够对立为 Sparse 特色,特色解决阶段会把原始特色通过 Hash 解决,变为 ID 类特色。在面对千亿级别维度的特色,基于字符串拼接再 Hash 的过程,在表白空间和性能上,都无奈满足要求。基于对业界的调研,咱们设计和利用了基于 Slot 编码的形式特色编码格局:
其中,feature_hash 为原始特征值通过 Hash 后的值。整型特色能够间接填充,非整型特色或穿插特色先通过 Hash 后再填充,超过 44 位则截断。基于 Slot 编码方案上线后,不仅晋升了在线特色计算的性能,同时也为模型成果的带来了显著晋升。
5 样本构建
5.1 流式样本
业界为了解决线上线下一致性的问题,个别都会在线 dump 实时打分应用的特色数据,称为特色快照;而不是通过简略离线 Label 拼接,特色回填的形式来构建样本,因为这种形式会带来较大的数据不统一。
架构原始的形式如下图所示:
这种计划随着特色规模越来越大、迭代场景越来越简单,突出的问题就是在线特色抽取服务压力大,其次是整个数据流收集老本太高。此样本收集计划存在以下问题:
- 就绪工夫长:在现有资源限度下,跑那么大数据简直要在 T + 2 能力将样本数据就绪,影响算法模型迭代。
- 资源消耗大:现有样本收集形式是将所有申请计算特色后与曝光、点击进行拼接,因为对未曝光 Item 进行了特色计算、数据落表,导致存储的数据量较大,消耗大量资源。
5.1.1 常见的计划
为了解决下面的问题,业界常见有两个计划:① Flink 实时流解决;② KV 缓存二次解决。具体流程如下图所示:
- 流式拼接计划:借助流式解决框架(Flink、Storm 等)低提早的流解决能力,间接读取曝光 / 点击实时流,与特色快照流数据在内存中进行关联(Join)解决;学生成流式训练样本,再转存为模型离线训练样本。其中流式样本和离线样本别离存储在不同的存储引擎中,反对不同类型的模型训练形式。此计划的问题:在数据流动环节的数据量仍然很大,占用较多的音讯流资源(比方 Kafka);Flink 资源耗费过大,如果每秒百 G 的数据量,做窗口 Join 则须要 30 分钟×60×100G 的内存资源。
- KV 缓存计划:把特色抽取的所有特色快照写入 KV 存储(如 Redis)缓存 N 分钟,业务零碎通过音讯机制,把候选队列中的 Item 传入到实时计算零碎(Flink 或者生产利用),此时的 Item 的量会比之前申请的 Item 量少很多,这样再将这些 Item 特色从特色快照缓存中取出,数据通过音讯流输入,反对流式训练。这种办法借助了外存,不论随着特色还是流量减少,Flink 资源可控,而且运行更加稳固。但突出的问题还是须要较大的内存来缓存大批量数据。
5.1.2 改良优化
从缩小有效计算的角度登程,申请的数据并不会都曝光。而策略对曝光后的数据有更强的需要,因而将天级解决前置到流解决,能够极大晋升数据就绪工夫。其次,从数据内容登程,特色蕴含申请级变更的数据与天级变更的数据,链路灵便拆散两者解决,能够极大晋升资源的利用,下图是具体的计划:
1. 数据拆分:解决数据传输量大问题(特色快照流大问题),预测的 Label 与实时数据一一 Match,离线数据能够通过回流的时候二次拜访,这样能够极大升高链路数据流的大小。
- 样本流中只有上下文 + 实时特色,减少读取数据流稳定性,同时因为只须要存储实时特色,Kafka 硬盘存储降落 10+ 倍。
2. 延时生产 Join 形式:解决占用内存大问题。
- 曝光流作为支流,写入到 HBase 中,同时为了后续能让其余流在 HBase 中 Join 上曝光,将 RowKey 写入 Redis;后续流通过 RowKey 写入 HBase,曝光与点击、特色的拼接借助外存实现,保障数据量增大后零碎能稳固运行。
- 样本流延时生产,后盾服务的样本流往往会比曝光流先到,为了能 Join 上 99%+ 的曝光数据,样本流期待窗口统计至多要 N 分钟以上;实现形式是将窗口期的数据全副压在 Kafka 的磁盘上,利用磁盘的程序读性能,省略掉了窗口期内须要缓存数据量的大量内存。
3. 特色补录拼样本:通过 Label 的 Join,此处补录的特色申请量不到在线的 20%;样本提早读取,与曝光做拼接后过滤出有曝光模型服务申请(Context+ 实时特色),再补录全副离线特色,拼成残缺样本数据,写入 HBase。
5.2 结构化存储
随着业务迭代,特色快照中的特色数量越来越大,使得整体特色快照在单业务场景下达到几十 TB 级别 / 天;从存储上看,多天单业务的特色快照就曾经 PB 级别,快达到广告算法存储阈值,存储压力大 ;从计算角度上看,应用原有的计算流程,因为计算引擎(Spark)的资源限度(应用到了 shuffle,shuffle write 阶段数据会落盘,如果分配内存有余,会呈现屡次落盘和外排序),须要与本身数据等大小的内存和较多的计算 CU 能力无效的实现计算, 占用内存高。样本构建流程外围流程如下图所示:
在补录特色时,存在以下问题:
- 数据冗余:补录特色的离线表个别为全量数据,条数在亿级别,样本构建用到的条数约为当日 DAU 的数量即千万级别,因而补录的特色表数据在参加计算时存在冗余数据。
- Join 程序:补录特色的计算过程即维度特色补全,存在屡次 Join 计算,因而 Join 计算的性能和 Join 的表的程序有很大关系,如上图所示,如果左表为几十 TB 级别的大表,那么之后的 shuffle 计算过程都会产生大量的网络 IO、磁盘 IO。
为了解决样本构建效率慢的问题,短期先从数据结构化治理,具体过程如下图所示:
- 结构化拆分:数据拆分成 Context 数据和结构化存储的维度数据代替混合存储。解决 Label 样本拼接新特色过程中携带大量冗余数据问题;并且做结构化存储后,针对离线特色,失去了很大的存储压缩。
- 高效过滤前置。数据过滤提前到 Join 前,缩小参加特色计算的数据量,能够无效升高网络 IO。在拼接过程中,补录特色的 Hive 表一般来说是全量表,数据条数个别为月活量,而理论拼接过程中应用的数据条数约为日活量,因而存在较大的数据冗余,有效的数据会带来额定的 IO 和计算。优化形式为预计算应用的维度 Key,并生成相应的布隆过滤器,在数据读取的时候应用布隆过滤器进行过滤,能够极大升高补录过程中冗余数据传输和冗余计算。
- 高性能 Join。应用高效的策略去编排 Join 程序,晋升特色补录环节的效率和资源占用。在特色拼接过程中,会存在多张表的 Join 操作,Join 的先后顺序也会极大影响拼接性能。如上图所示,如果拼接的左表数据量较大时,那么整体性能就会差。能够应用哈夫曼算法的思维,把每个表看作一个节点,对应的数据量量看成是他的权重,表之间的 Join 计算量能够简略类比两个节点的权重相加。因而,能够将此问题形象成结构哈夫曼树,哈夫曼树的结构过程即为最优的 Join 程序。
数据离线存储资源节俭达 80%+,样本构建效率晋升 200%+,以后整个样本数据也正在进行基于数据湖的实际,进一步晋升数据效率。
6 数据筹备
平台积攒了大量的特色、样本和模型等有价值的内容,心愿通过对这些数据资产进行复用,帮忙策略人员更好的进行业务迭代,获得更好的业务收益。特色优化占了算法人员晋升模型成果的所有办法中 40% 的工夫,但传统的特色开掘的工作形式存在着破费工夫长、开掘效率低、特色反复开掘等问题,所以平台心愿在特色维度赋能业务。
如果有自动化的试验流程去验证任意特色的成果,并将最终成果指标举荐给用户,无疑会帮忙策略同学节俭大量的工夫。当整个链路建设实现,后续只须要输出不同的特色候选集,即可输入相应成果指标。为此平台建设了特色、样本的“加”、“减”、“乘”、“除”智能机制。
6.1 做“加法”
特色举荐基于模型测试的办法,将特色复用到其余业务线现有模型,结构出新的样本和模型;比照新模型和 Base 模型的离线成果,获取新特色的收益,主动推送给相干的业务负责人。具体特色举荐流程如下图所示:
- 特色感知:通过上线墙或业务间存量形式触发特色举荐,这些特色曾经过肯定验证,能够保障特色举荐的成功率。
- 样本生产:样本生产时通过配置文件抽取特色,流程主动将新增特色加到配置文件中,而后进行新样本数据的生产。获取到新特色后,解析这些特色依赖的原始特色、维度、和 UDF 算子等,将新特色配置和依赖的原始数据交融到基线模型的原有配置文件中,结构出新的特色配置文件。主动进行新样本构建,样本构建时通过特色名称在特色仓库中抽取相干特色,并调用配置好的 UDF 进行特色计算,样本构建的时间段可配置。
- 模型训练:主动对模型构造和样本格局配置进行革新,而后进行模型训练,应用 TensorFlow 作为模型训练框架,应用 tfrecord 格局作为样本输出,将新特色依照数值类和 ID 类别离放到 A 和 B 两个组中,ID 类特色进行查表操作,而后对立追加到现有特色前面,不须要批改模型构造便可接管新的样本进行模型训练。
- 主动配置新模型训练参数:包含训练日期、样本门路、模型超参等,划分出训练集和测试集,主动进行新模型的训练。
- 模型评测:调用评估接口失去离线指标,比照新老模型评测后果,并预留单特色评估后果,打散某些特色后,给出单特色贡献度。将评估后果对立发送给用户。
6.2 做“减法”
特色举荐在广告外部落地并获得了肯定收益后,咱们在特色赋能层面做一些新的摸索。随着模型的一直优化,特色收缩的速度十分快,模型服务耗费资源急剧回升,剔除冗余特色,为模型“瘦身”势在必行。因而,平台建设了一套端到端的特色筛选工具。
- 特色打分:通过 WOE(Weight Of Evidence, 证据权重)等多种评估算法给出模型的所有特色评分,打分较高特色的品质较高,评估准确率高。
- 成果验证:训练好模型后,按打分排序,分批次对特色进行剔除。具体通过采纳特色打散的办法,比照原模型和打散后模型评估后果,相差较大低于阈值后完结评估,给出能够剔除的特色。
- 端到端计划:用户配置好试验参数和指标阈值后,无需人为干预,即可给出可删除的特色以及删除特色后模型的离线评估后果。
最终,在外部模型下线 40% 的特色后,业务指标降落依然管制在正当的阈值内。
6.3 做“乘法”
为了失去更好的模型成果,广告外部曾经开始做一些新的摸索,包含大模型、实时化、特色库等。这些摸索背地都有一个要害指标:须要更多、更好的数据让模型更智能、更高效。从广告现状登程,提出样本库(Data Bank)建设,实现把内部更多品种、更大规模的数据拿进来,利用于现有业务。具体如下图所示:
咱们建设了一套通用的样本共享平台,在这个平台上,能够借用其余业务线来产生增量样本。并且也搭建通用的 Embedding 共享架构,实现业务的以大带小。上面以广告业务线复用非广告样本为例,具体做法如下:
- 扩样本:基于 Flink 流式解决框架,建设了高扩大样本库 DataBank,业务 A 很不便复用业务 B、业务 C 的曝光、点击等 Label 数据去做试验。尤其是为小业务线,裁减了大量的价值数据,这种做法相比离线补录 Join,一致性会更强,特色平台提供了在线、离线一致性保障。
- 做共享:在样本就绪后,一个很典型的利用场景就是迁徙学习。另外,也搭建 Embedding 共享的数据通路(不强依赖“扩样本”流程),所有业务线能够基于大的 Embedding 训练,每个业务方也能够 update 这个 Embedding,在线通过建设 Embedding 版本机制,供多个业务线应用。
举例来说,通过将非广告样本复用到广告内一个业务,使样本数量减少了几倍,联合迁徙学习算法,离线 AUC 晋升千分之四,上线后 CPM 晋升百分之一。
此外,咱们也在建设广告样本主题库,将各业务生成的样本数据进行对立治理(对立元数据),面向用户透出对立样本主题分类,疾速注册、查找、复用,面向底层对立存储,节约存储、计算资源,缩小数据 Join,进步时效性。
6.4 做“除法”
通过特色“减法”能够剔除一些无正向作用的特色,但通过观察发现模型中还存在很多价值很小的特色。所以更进一步咱们能够通过价值、老本两方面综合思考,在全链路基于老本的束缚下价值最大,筛选出那些投入产出比拟低特色,升高资源耗费。这个在老本束缚上来求解的过程定义为做“除法”,整体流程如下图所示。
在离线维度,咱们建设了一套特色价值评估零碎,给出特色的老本和价值,在线推理时能够通过特色价值信息进行流量降级、特色弹性计算等操作,做“除法”关键步骤如下:
- 问题形象:如果咱们能失去每个特色的价值得分,又能够拿到特色的老本(存储、通信、计算加工),那么问题就转换成了在已知模型构造、固定资源老本下,如何让特色的价值最大化。
- 老本束缚下的价值评估:基于模型的特色集,平台首先进行老本和价值的统计汇总;老本包含了离线老本和在线老本,基于训练好的评判模型,得出特色的综合排序。
- 分场景建模:能够依据不同的资源状况,抉择不同的特色集,进行建模。在无限的资源下,抉择价值最大的模型在线 Work。另外,能够针对比拟大的特色集建模,在流量低峰启用,晋升资源利用率的同时给业务带来更大收益。还有一种利用场景是流量降级,推理服务监控在线资源的耗费,一旦资源计算达到瓶颈,切换到降级模型。
7 总结与瞻望
以上是咱们在大规模深度学习工程上的反“增”实际,去助力业务降本提效。将来咱们还会继续在以下方面进行摸索、实际:
- 全链路 GPU 化:在推理层面,通过 GPU 的切换,撑持更简单业务迭代的同时,整体老本也极大的升高,前面会在样本构建、特色服务上进行 GPU 化革新,并协同推动离线训练层面的降级。
- 样本数据湖:通过数据湖的 Schema Evolution、Patch Update 等个性构建更大规模的样本仓库,对业务方进行低成本、高价值的数据透出。
- Pipeline:算法全生命周期迭代过程中,很多环节的调试,链路信息都不够“串联”,以及离线、在线、成果指标的视角都比拟割裂,基于全链路的标准化、可观测大势所趋,并且这是后续链路智能化弹性调配的根底。当初业界比拟火的 MLOps、云原生都有较多的借鉴思路。
- 数据、模型智能匹配:上文提到在模型构造固定前提下,主动为模型加、减特色,同理在模型层面,固定肯定特色输出前提下,去主动嵌入一些新的模型构造。以及在将来,咱们也将基于业务畛域,通过平台的特色、模型体系,自动化地实现数据、模型的匹配。
8 本文作者
亚劼、英亮、陈龙、成杰、登峰、东奎、仝晔、思敏、乐彬等,均来自美团外卖技术团队。
9 招聘信息
美团外卖广告工程团队长期招聘后盾高级工程师 / 技术专家,负责广告多个方向(举荐 / 搜寻 / 召回 / 预估 / 翻新)的零碎研发工作,坐标北京。欢送感兴趣的同学退出咱们。可投简历至:zouyajie@meituan.com(邮件主题请注明 — 美团外卖广告工程团队)。
浏览美团技术团队更多技术文章合集
前端 | 算法 | 后端 | 数据 | 平安 | 运维 | iOS | Android | 测试
| 在公众号菜单栏对话框回复【2021 年货】、【2020 年货】、【2019 年货】、【2018 年货】、【2017 年货】等关键词,可查看美团技术团队历年技术文章合集。
| 本文系美团技术团队出品,著作权归属美团。欢送出于分享和交换等非商业目标转载或应用本文内容,敬请注明“内容转载自美团技术团队”。本文未经许可,不得进行商业性转载或者应用。任何商用行为,请发送邮件至 tech@meituan.com 申请受权。