作者:亚劼 英亮 陈龙等
随着美团外卖业务不断发展,外卖广告引擎团队在多个领域进行了工程上的探索和实践,也取得了一些成果。我们将以连载的方式进行分享,内容主要包括:① 业务平台化的实践;② 大规模深度学习模型工程实践;③ 近线计算的探索与实践;④ 大规模索引构建与在线检索服务实践;⑤ 机制工程平台化实践。不久前,我们已发表过业务平台化的实践(详情请参阅《美团外卖广告平台化的探索与实践》一文)。本文为连载文章的第二篇,我们将重点针对大规模深度模型在全链路层面带来的挑战,从在线时延、离线效率两个方面进行展开,阐述广告在大规模深度模型上的工程实践,希望能为大家带来一些帮助或者启发。
在搜索、推荐、广告(下简称搜推广)等互联网核心业务场景下,进行数据挖掘及兴趣建模,为用户提供优质的服务,已经成为改善用户体验的关键要素。近几年,针对搜推广业务,深度学习模型凭借数据红利和硬件技术红利,在业界得以广泛落地,同时在CTR场景,业界逐步从简单DNN小模型过渡到数万亿参数的Embedding大模型甚至超大模型。外卖广告业务线主要经历了“LR浅层模型(树模型)” -> “深度学习模型” -> “大规模深度学习模型”的演化过程。整个演化趋势从以人工特征为主的简单模型,逐步向以数据为核心的复杂深度学习模型进行过渡。而大模型的使用,大幅提高了模型的表达能力,更精准地实现了供需侧的匹配,为后续业务发展提供了更多的可能性。但随着模型、数据规模的不断变大,我们发现效率跟它们存在如下的关系:
根据上图所示,在数据规模、模型规模增长的情况下,所对应的“时长”变得会越来越长。这个“时长”对应到离线层面,体现在效率上;对应到在线层面,就体现在Latency上。而我们的工作就是围绕这个“时长”的优化来开展。
相比普通小模型,大模型的核心问题在于:随着数据量、模型规模增加数十倍甚至百倍,整体链路上的存储、通信、计算等都将面临新的挑战,进而影响算法离线的迭代效率。如何突破在线时延约束等一系列问题?我们先从全链路进行分析,如下所示:

“时长”变长,主要会体现在以下几个方面:
本文重点从在线时延(模型推理、特征服务)、离线效率(样本构建、数据准备)等两个方面来展开,逐步阐述广告在大规模深度模型上的工程实践。如何去优化“时长”等相关问题,我们会在后续篇章介进行分享,敬请期待。
在模型推理层面,外卖广告历经了三个版本,从1.0时代,支持小众规模的DNN模型为代表,到2.0时代,高效、低代码支持多业务迭代,再到如今的3.0时代,逐步面向深度学习DNN算力以及大规模存储的需求。主要演进趋势如下图所示:
面向大模型推理场景,3.0架构解决的两个核心问题是:“存储问题”和“性能问题”。当然,面向N个百G+模型如何迭代,运算量数十倍增加时在线稳定性如何保障,Pipeline如何加固等等,也是工程面临的挑战。下面我们将重点介绍模型推理3.0架构是如何通过“分布式”来解决大模型存储问题,以及如何通过CPU/GPU加速来解决性能、吞吐问题。
大模型的参数主要分为两部分:Sparse参数和Dense参数。
因此,解决大模型参数规模增长的关键是将Sparse参数由单机存储改造为分布式存储,改造的方式包括两部分:① 模型网络结构转换;② Sparse参数导出。
业界对于分布式参数的获取方式大致分为两种:外部服务提前获取参数并传给预估服务;预估服务内部通过改造TF(TensorFlow)算子来从分布式存储获取参数。为了减少架构改造成本和降低对现有模型结构的侵入性,我们选择通过改造TF算子的方式来获取分布式参数。
正常情况下,TF模型会使用原生算子进行Sparse参数的读取,其中核心算子是GatherV2算子,算子的输入主要有两部分:① 需要查询的ID列表;② 存储Sparse参数的Embedding表。
算子的作用是从Embedding表中读取ID列表索引对应的Embedding数据并返回,本质上是一个Hash查询的过程。其中,Embedding表存储的Sparse参数,其在单机模型中全部存储在单机内存中。
改造TF算子本质上是对模型网络结构的改造,改造的核心点主要包括两部分:① 网络图重构;② 自定义分布式算子。
1. 网络图重构:改造模型网络结构,将原生TF算子替换为自定义分布式算子,同时进行原生Embedding表的固化。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜
2. 自定义分布式算子:改造根据ID列表查询Embedding流程,从本地Embedding表中查询,改造为从分布式KV中查询。
整体流程如下图所示,我们通过离线分布式模型结构转换、近线数据一致性保证、在线热点数据缓存等手段,保障了百G大模型的正常迭代需求。

可以看到,分布式借助的存储是外部KV能力,后续会替换为更加高效、灵活、易管理的Embedding Service。
抛开模型本身的优化手段外,常见的CPU加速手段主要有两种:① 指令集优化,比如使用AVX2、AVX512指令集;② 使用加速库(TVM、OpenVINO)。
下面,将会重点介绍我们使用OpenVINO进行CPU加速的一些实践经验。OpenVINO是Intel推出的一套基于深度学习的计算加速优化框架,支持机器学习模型的压缩优化、加速计算等功能。OpenVINO的加速原理简单概括为两部分:线性算子融合和数据精度校准。
CPU加速通常是针对固定Batch的候选队列进行加速推理,但在搜推广场景中,候选队列往往都是动态的。这就意味着在模型推理之前,需要增加Batch匹配的操作,即将请求的动态Batch候选队列映射到一个离它最近的Batch模型上,但这需构建N个匹配模型,导致N倍的内存占用。而当前模型体积已达百G规模,内存严重吃紧。因此,选取合理的网络结构用于加速是需要考虑的重点问题。下图是整体的运行架构:
目前,基于OpenVINO的CPU加速方案已经在生产环境取得不错效果:CPU与基线持平时,服务吞吐提升40%,平均时延下降15%。如果大家想在CPU层面做些加速的话,OpenVINO是个不错的选择。
一方面,随着业务的发展,业务形态越来越丰富,流量越来越高,模型变宽变深,算力的消耗急剧增加;另一方面,广告场景主要使用DNN模型,涉及大量稀疏特征Embedding和神经网络浮点运算。作为访存和计算密集型的线上服务,在保证可用性的前提下,还要满足低延迟、高吞吐的要求,对单机算力也是一种挑战。这些算力资源需求和空间的矛盾,如果解决不好,会极大限制业务的发展:在模型加宽加深前,纯CPU 推理服务能够提供可观的吞吐,但是在模型加宽加深后,计算复杂度上升,为了保证高可用性,需要消耗大量机器资源,导致大模型无法大规模应用于线上。目前,业界比较通用的解决办法是利用GPU来解决这个问题,GPU本身比较适用于计算密集型任务。使用GPU需要解决如下挑战:如何在保证可用性、低延迟的前提下,尽可能做到高吞吐,同时还需要考虑易用性和通用性。为此,我们也在GPU上做了大量实践工作,比如TensorFlow-GPU、TensorFlow-TensorRT、TensorRT等,为了兼顾TF的灵活性以及TensorRT的加速效果,我们采用TensorFlow+TensorRT独立两阶段的架构设计。
深度学习推理阶段对算力和时延具有很高的要求,如果将训练好的神经网络直接部署到推理端,很有可能出现算力不足无法运行或者推理时间较长等问题。因此,我们需要对训练好的神经网络进行一定的优化。业界神经网络模型优化的一般思路,可以从模型压缩、不同网络层合并、稀疏化、采用低精度数据类型等不同方面进行优化,甚至还需要根据硬件特性进行针对性优化。为此,我们主要围绕以下两个目标进行优化:
下面将围绕以上两个目标,具体介绍我们在模型优化、融合优化和引擎优化所做的一些工作。
1. 计算与传输去重:推理时同一Batch只包含一个用户信息,因此在进行inference之前可以将用户信息从Batch Size降为1,真正需要inference时再进行展开,降低数据的传输拷贝以及重复计算开销。如下图,inference前可以只查询一次User类特征信息,并在只有用户相关的子网络中进行裁剪,待需要计算关联时再展开。

2. 数据精度优化:由于模型训练时需要反向传播更新梯度,对数据精度要求较高;而模型推理时,只进行前向推理不需要更新梯度,所以在保证效果的前提下,使用FP16或混合精度进行优化,节省内存空间,减少传输开销,提升推理性能和吞吐。
3. 计算下推:CTR模型结构主要由Embedding、Attention和MLP三层构成,Embedding层偏数据获取,Attention有部分偏逻辑,部分偏计算,为了充分压榨GPU的潜力,将CTR模型结构中Attention和MLP大部分计算逻辑由CPU下沉到GPU进行计算,整体吞吐得到大幅提升。
在线模型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等。

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占有一定的比例,保证融合后计算密度能够有一定程度的上升。至于采用什么样的网络融合手段,根据具体的场景进行灵活运用即可,如下图是我们融合前后的子结构图对比:




模型从离线训练到最终在线加载,整个流程繁琐易出错,而且模型在不同GPU卡、不同TensorRT和CUDA版本上无法通用,这给模型转换带来了更多出错的可能性。因此,为了提升模型迭代的整体效率,我们在Pipeline方面进行了相关能力建设,如下图所示:

Pipeline建设包括两部分:离线侧模型拆分转换流程,以及在线侧模型部署流程:
Pipeline通过配置化、一键化能力的建设,极大提升了模型迭代效率,帮助算法和工程同学能够更加专注的做好本职工作。下图是在GPU实践中相比纯CPU推理取得的整体收益:

特征抽取是模型计算的前置阶段,无论是传统的LR模型还是日趋流行的深度学习模型,都需要通过特征抽取来得到输入。在之前的博客美团外卖特征平台的建设与实践中,描述了我们基于模型特征自描述MFDL,将特征计算流程配置化,尽量保证了在线预估和离线训练时样本的一致性。随着业务快速迭代,模型特征数量不断增加,特别是大模型引入了大量的离散特征,导致计算量有了成倍的增长。为此,我们对特征抽取层做了一些优化,在吞吐和耗时上都取得了显著的收益。
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并非是一个纯计算型表达式。它可以通过读取算子和转换算子的组合来描述特征的获取和处理过程:
所以在实际实现中,需要考虑不同类型任务的调度,尽可能提高机器资源利用率,优化流程整体耗时。结合对业界的调研以及自身实践,进行了以下三种实现:

CodeGen方案也并非完美,动态生成的代码降低了代码可读性,增加了调试成本,但以CodeGen作为适配层,也为更深入的优化打开了空间。基于CodeGen和异步非阻塞的实现,在线上取到了不错的收益,一方面减少了特征计算的耗时,另一方面也明显的降低了CPU负载,提高了系统吞吐。未来我们会继续发挥CodeGen的优势,在后端编译过程中进行针对性的优化,如探索结合硬件指令(如SIMD)或异构计算(如GPU)来做更深层次的优化。
在线预估服务整体上是双层架构,特征抽取层负责模型路由和特征计算,模型计算层负责模型计算。原有的系统流程是将特征计算后的结果拼接成M(预测的Batch Size) × N(样本宽度)的矩阵,再经过序列化传输到计算层。之所以这么做,一方面出于历史原因,早期很多非DNN的简单模型的输入格式是个矩阵,经过路由层拼接后,计算层可以直接使用,无需转换;另一方面,数组格式比较紧凑,可以节省网络传输耗时。
然而随着模型迭代发展,DNN模型逐渐成为主流,基于矩阵传输的弊端也非常明显:
为了解决以上问题,优化后的流程在传输层之上加入一层转换层,用来根据MDFL的配置将计算的模型特征转换成需要的格式,比如Tensor、矩阵或离线使用的CSV格式等。
实际线上大多数模型都是TF模型,为了进一步节省传输消耗,平台设计了Tensor Sequence格式来存储每个Tensor矩阵:其中,r_flag用来标记是否是item类特征,length表示item特征的长度,值为M(Item个数)×NF(特征长度),data用来存储实际的特征值,对于Item特征将M个特征值扁平化存储,对于请求类特征则直接填充。基于紧凑型Tensor Sequence格式使数据结构更加紧凑,减少网络传输数据量。优化后的传输格式在线上也取得不错的效果,路由层调用计算层的请求大小下降了50%+,网络传输耗时明显下降。
离散特征和序列特征可以统一为Sparse特征,特征处理阶段会把原始特征经过Hash处理,变为ID类特征。在面对千亿级别维度的特征,基于字符串拼接再Hash的过程,在表达空间和性能上,都无法满足要求。基于对业界的调研,我们设计和应用了基于Slot编码的方式特征编码格式:

其中,feature_hash为原始特征值经过Hash后的值。整型特征可以直接填充,非整型特征或交叉特征先经过Hash后再填充,超过44位则截断。基于Slot编码方案上线后,不仅提升了在线特征计算的性能,同时也为模型效果的带来了明显提升。
业界为了解决线上线下一致性的问题,一般都会在线dump实时打分使用的特征数据,称为特征快照;而不是通过简单离线Label拼接,特征回填的方式来构建样本,因为这种方式会带来较大的数据不一致。架构原始的方式如下图所示:

这种方案随着特征规模越来越大、迭代场景越来越复杂,突出的问题就是在线特征抽取服务压力大,其次是整个数据流收集成本太高。此样本收集方案存在以下问题:
为了解决上面的问题,业界常见有两个方案:①Flink实时流处理;②KV缓存二次处理。具体流程如下图所示:

从减少无效计算的角度出发,请求的数据并不会都曝光。而策略对曝光后的数据有更强的需求,因此将天级处理前置到流处理,可以极大提升数据就绪时间。其次,从数据内容出发,特征包含请求级变更的数据与天级变更的数据,链路灵活分离两者处理,可以极大提升资源的利用,下图是具体的方案:

1. 数据拆分:解决数据传输量大问题(特征快照流大问题),预测的Label与实时数据一一Match,离线数据可以通过回流的时候二次访问,这样可以极大降低链路数据流的大小。
2. 延时消费Join方式:解决占用内存大问题。
3. 特征补录拼样本:通过Label的Join,此处补录的特征请求量不到在线的20%;样本延迟读取,与曝光做拼接后过滤出有曝光模型服务请求(Context+实时特征),再补录全部离线特征,拼成完整样本数据,写入HBase。
随着业务迭代,特征快照中的特征数量越来越大,使得整体特征快照在单业务场景下达到几十TB级别/天;从存储上看,多天单业务的特征快照就已经PB级别,快到达广告算法存储阈值,存储压力大;从计算角度上看,使用原有的计算流程,由于计算引擎(Spark)的资源限制(使用到了shuffle,shuffle write阶段数据会落盘,如果分配内存不足,会出现多次落盘和外排序),需要与自身数据等大小的内存和较多的计算CU才能有效的完成计算,占用内存高。样本构建流程核心流程如下图所示:

在补录特征时,存在以下问题:
为了解决样本构建效率慢的问题,短期先从数据结构化治理,详细过程如下图所示:

数据离线存储资源节省达80%+,样本构建效率提升200%+,当前整个样本数据也正在进行基于数据湖的实践,进一步提升数据效率。
平台积累了大量的特征、样本和模型等有价值的内容,希望通过对这些数据资产进行复用,帮助策略人员更好的进行业务迭代,取得更好的业务收益。特征优化占了算法人员提升模型效果的所有方法中40%的时间,但传统的特征挖掘的工作方式存在着花费时间长、挖掘效率低、特征重复挖掘等问题,所以平台希望在特征维度赋能业务。如果有自动化的实验流程去验证任意特征的效果,并将最终效果指标推荐给用户,无疑会帮助策略同学节省大量的时间。当整个链路建设完成,后续只需要输入不同的特征候选集,即可输出相应效果指标。为此平台建设了特征、样本的“加”、“减”、“乘”、“除”智能机制。
特征推荐基于模型测试的方法,将特征复用到其他业务线现有模型,构造出新的样本和模型;对比新模型和Base模型的离线效果,获取新特征的收益,自动推送给相关的业务负责人。具体特征推荐流程如下图所示:


特征推荐在广告内部落地并取得了一定收益后,我们在特征赋能层面做一些新的探索。随着模型的不断优化,特征膨胀的速度非常快,模型服务消耗资源急剧上升,剔除冗余特征,为模型“瘦身”势在必行。因此,平台建设了一套端到端的特征筛选工具。

最终,在内部模型下线40%的特征后,业务指标下降仍然控制在合理的阈值内。
为了得到更好的模型效果,广告内部已经开始做一些新的探索,包括大模型、实时化、特征库等。这些探索背后都有一个关键目标:需要更多、更好的数据让模型更智能、更高效。从广告现状出发,提出样本库(Data Bank)建设,实现把外部更多种类、更大规模的数据拿进来,应用于现有业务。具体如下图所示:

我们建立了一套通用的样本共享平台,在这个平台上,可以借用其他业务线来产生增量样本。并且也搭建通用的Embedding共享架构,实现业务的以大带小。下面以广告业务线复用非广告样本为例,具体做法如下:
举例来说,通过将非广告样本复用到广告内一个业务,使样本数量增加了几倍,结合迁移学习算法,离线AUC提升千分之四,上线后CPM提升百分之一。此外,我们也在建设广告样本主题库,将各业务生成的样本数据进行统一管理(统一元数据),面向用户透出统一样本主题分类,快速注册、查找、复用,面向底层统一存储,节约存储、计算资源,减少数据Join,提高时效性。
通过特征“减法”可以剔除一些无正向作用的特征,但通过观察发现模型中还存在很多价值很小的特征。所以更进一步我们可以通过价值、成本两方面综合考虑,在全链路基于成本的约束下价值最大,筛选出那些投入产出比较低特征,降低资源消耗。这个在成本约束下去求解的过程定义为做“除法”,整体流程如下图所示。

在离线维度,我们建立了一套特征价值评估系统,给出特征的成本和价值,在线推理时可以通过特征价值信息进行流量降级、特征弹性计算等操作,做“除法”关键步骤如下:
以上是我们在大规模深度学习工程上的反“增”实践,去助力业务降本提效。未来我们还会持续在以下方面进行探索、实践:
亚劼、英亮、陈龙、成杰、登峰、东奎、仝晔、思敏、乐彬等,均来自美团外卖技术团队。
以上就是外卖广告大规模深度学习模型工程实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号