← 文章 / AI技术
Meta 工程博客 6小时前 · 2026-11-04 02:41:17 · 7 阅读

F3 Lightning:Meta 如何大规模加速广告机器学习特征上线

  • F3 是 Meta 用于创建、编译和部署广告排序模型底层机器学习特征的框架,单次请求可能需要超过 20,000 个特征。我们首次在 介绍了 F3
  • F3 通过将所有特征编译为单个合并图来降低执行计算成本,但该图同时也作为发布单元,因此单个故障或无效定义会阻塞整个发布管道。
  • 是构建在 F3 之上的基础设施层,通过将特征发布与共享执行解耦来解决此挑战,使每个特征能够独立编译、验证、部署和回滚。
  • 最棘手的问题在于保留 F3 的效率提升。早期的单特征发布原型因独立特征不再共享计算而增加了 CPU 使用率。我们旨在通过四项优化来缩小这一差距:稳定的计算标识符、单一共享执行计划、跨所有特征的一个共享内存缓冲区,以及用于直接数组访问的密集索引映射。
  • 比之前的 F3 性能更优,且基础设施计算成本没有增加。这种更短的发布周期也允许在相同时间内进行更多的模型迭代。

生产级机器学习系统很少直接消费原始数据。在模型做出预测之前,其特征服务系统可能需要读取存储信号、将事件过滤到时间窗口内、对数值进行计数或求和、归一化,以及组合中间结果。

完成此项工作的最简单设计是将每个特征视为独立的函数。这易于理解,但在大规模下会变得低效,因为特征在计算上并非相互独立。七天点击数和七天曝光数可能读取相同的事件源并应用相同的时间窗口过滤。点击率特征随后可能复用这两个结果。如果单独运行每个特征,将在每次请求中重复该过滤操作及其他所有共享步骤。

F3 采用了不同的方法,将特征提取表示为有向无环图 (DAG)。每个节点执行一个工作单元,如读取输入、应用过滤器或计算聚合。一条边意味着一个计算依赖于另一个计算的结果。该图的输出是模型消费的特征值。

工程师用高级语言各自独立编写每个特征。F3 编译器会把每个定义转换成一个小图,再把某个服务用到的所有特征的图合并成一张合并图(Figure 1)。当多个特征共享同一个操作时(比如上面的七天过滤器),编译器会把重复部分合并为单个节点。这样一来,这部分共享计算每个请求只执行一次,而不是每个需要它的特征各执行一次。运行时,特征提取就是对这张合并图的一次遍历。

Figure 1: F3 把所有特征整合到一张合并图中。

共享图让每次发布变成“全有或全无”

F3 的合并图高效地共享了计算,但有个重大隐患:图本身就是发布单元,一个有问题的定义就可能拖住合并图中的所有其他特征。

Figure 2 展示了这个难题:七个独立编写的特征 A 到 G 中,五个定义没问题,但 D 和 G 各有一个互不相关的错误。“Attempt 1” 构建在 D 上失败,七个特征全部无法发布。修复 D 后,“Attempt 2” 构建又卡在 G 上再次失败。直到 “Attempt 3” 中两个定义都修好,完整的合并图才成功上线。

Figure 2: 一个特征失败会阻塞整个发布。

随着 F3 规模扩大,失败越来越频繁。F3 最初只服务几百个特征,那个规模下合并顺利,发布也基本一帆风顺。如今特征数量已超过 20,000,而且不同类型的特征需要各自不同的检查,新特征和已修改特征也要按不同的测试标准把关。已经被模型消费的特征必须先通过模型相关测试才能发布,而这些测试计算开销太大,只能在发布流程的后期阶段运行。由于合并图是作为一个整体发布的,图中的其他特征也得跟着一起等。

F3 Lightning:让每个特征独立发布,同时共享计算

F3 上线后,团队对合并计算图进行了多轮优化。这些改动曾短暂恢复了发布速度,但随着新特性不断接入,由于所有特性仍打包在同一个合并图中,效率优势逐渐被侵蚀。因此,F3 Lightning 的目标是让每个特性都能独立发布,同时保持 F3 原有的执行效率。 如下图所示,F3 Lightning 通过两项关键变更实现这一目标:
  • 部署粒度:每个特性都是一个可独立发布的配置单元。其验证、上线和回滚过程互不干扰,不会影响其他特性。
  • 效率持平:F3 的高效源于合并计算图,因此若为每个特性分配独立的发布流程,便失去了这一效率来源。F3 Lightning 在执行机制上实施了多项关键优化,从而在保留按特性独立发布的同时,实现与 F3 相当的效率。
图 3:F3 Lightning 将发布边界与执行边界分离。

部署粒度:F3 Lightning 如何隔离特性发布

隔离机制始于编译器阶段:每个特性被单独编译为一个完整的配置。该配置包含提取此特性所需的全部节点,且按序排列,无需依赖相邻特性。例如,若特性 C 出现异常,特性 A 仍可正常上线,因为两者在配置层面完全独立,互不关联。
图 4:有缺陷的特性不再阻塞正常特性。
由于特性在发布时不再被合并,每个特性均可按照自己的节奏独立调度上线。发布流程因此从“全有或全无”的批量模式,转变为有序的优先级队列。变更的优先级由其所解锁的价值决定:尚未被任何模型使用的新特性,只需通过标准验证即可快速上线;而修改已被在线模型使用的特性,则需经过更严格的测试。
图 5:特性依据影响力和风险度从优先级队列中选取。

效率持平:F3 Lightning 如何维持 F3 的服务效率

最初的原型只做到了部署独立。功能虽然可以独立发布,但执行层面不再共享,导致原型的 CPU 消耗远高于传统 F3。每个独立发布的配置都会以独立的图进入服务进程并被单独执行,F3 通过图合并省掉的每一次读取、过滤和聚合操作都被重复执行了一遍。

解决方案是恢复共享执行,但不再恢复合并。F3 Lightning 在启动时不会重建合并后的图,而是获取当前生效的功能配置,把它们共享的工作整合起来,编排成单一的执行顺序。要恢复到原来的性能水平,需要四个步骤:先满足一个编译器前提,再做三项启动优化。

步骤 1:稳定的标识符让共享计算可被识别

要想对执行去重,运行时必须能识别两份独立编译的配置中是否包含相同的计算。节点编号做不到这一点,因为编号是每个功能图内部局部的。同一个七天过滤器可能在一个配置里是节点 2,在另一个配置里是节点 7,而且只要插入一个节点,它之后的所有编号都会改变。传统 F3 从未遇到这个问题,因为它一次编译就能看到所有功能,可以全局分配标识符。

F3 Lightning 的做法是让编译器根据计算本身(而不是它在图中的位置)来生成标识符:依据操作类型、参数以及输入的标识符(见图 6)。由于每个输入的标识符已经涵盖了它上游的全部内容,这个标识符就能代表生成该结果的整条计算链。当输入顺序不影响结果时,会对输入按标准顺序排列。聚合操作还会纳入定义其行为的属性,比如时间窗口和分组键。这样一来,只要两个功能在相同的输入上定义了相同的操作,即使它们的编译和发布时间相隔数周,也会得到相同的标识符。一旦某个计算的含义发生变化,它的标识符也会随之改变。

图 6:相同的计算获得相同的标识符。

步骤 2:跨所有功能的统一执行计划

服务启动时,F3 Lightning 会为所有活跃特征构建一份统一的执行计划,并借助拓扑排序确保每个节点都排列在其依赖节点之后。若多个特征中存在标识符相同的节点,计划只保留一份副本,并将所有消费方连接到该节点,保证无论被多少个特征使用,计算都只执行一次。

合并去重确保了每份计算只跑一次,但计划本身仍是一张图,执行器需针对每个请求逐指针遍历。因此,同一个启动流程会将其展开为有序列表,让每个请求沿列表做一次线性前向执行,而非遍历图结构。

共享同一份计划带来一个风险:单个计算失败可能影响所有特征。为规避此问题,若某计算失败或其输入不可用,F3 Lightning 会将其下游所有节点标记为未就绪,执行器会直接跳过这些任务。这样,故障只会波及依赖该节点的下游,独立分支则继续运行。

第三步:引入共享内存缓冲区,去重还不够

前两步完成后,虽有计算不再重复执行,但 CPU 占用率仍高于传统 F3。残留的计算开销源于内存布局:每个配置各自维护请求期数据容器,导致中间结果散落在不同的内存分配中。按计划顺序遍历意味着在各容器间跳跃,每次跳跃都会引发缓存缺失。

F3 Lightning 用每个请求一份共享缓冲区取代了按特征划分的容器。启动时,各特征的引用会重新绑定到该缓冲区的对应槽位。

单一缓冲区带来三方面改进:

局部性:计划中相邻步骤如今会访问相邻内存,前一步读取的值通常仍在缓存中。这恢复了合并图原本就具备的顺序访问模式,同时消除了每个请求为各特征单独分配内存的开销。

就绪状态统一追踪:一个与值分离存储的向量取代了各特征的就绪跟踪。执行器检查值是否就绪时,无需读取值本身。标记必须独立,因为空列表也是有效结果,不能用“空”同时表示“尚未计算”。

不重复拷贝:大多数特征值是列表和映射(map),消费者不再各自拿到一份拷贝。共享槽位只保存对生产者结果的引用,因此被 20 个特征共用的计算只需执行一次、存储一次。标量这类单值仍然直接拷贝,因为拷贝一个小值比追踪指针更划算。这样做是安全的,因为每个 buffer 只存活于单次请求内,而且执行顺序保证每个结果的生命周期覆盖其所有读取者。

图 8:所有特征共享一块内存 buffer。

第 4 步:稠密索引映射消除最后的逐次访问开销

值已经集中存放,但查找本身依然开销不小。每次访问都要解析标识符:哈希 key、探测 bucket、再跟进指针。以 Meta 的线上服务规模来看,这种查找方式在计算上并不高效。

最终方案沿用了第 2、3 步的思路:把工作挪到启动时一次性完成。F3 Lightning 为每个唯一标识符分配一个连续整数槽位,并把所有配置中的引用重写为指向这些槽位,标识符只在初始化阶段使用。之后读取结果就是对共享 buffer 的直接数组索引,请求路径上不再有哈希和查找表。

借助 F3 Lightning,同类抽取特征的上生产速度比传统 F3 快约 3.6 倍,且基础设施计算成本并未增加。更短的发布周期意味着模型可以在同样时间内更频繁地迭代和重训。构建 F3 Lightning 的过程中,我们还收获了三条经验:

部署和执行的边界应该分开:如果同一份产物既决定组件如何运行、又决定它们何时上线,那么任何一处出问题都会阻塞其他所有变更。把这两件事拆开后,效率和发布速度之间就不再需要取舍。

请求之间不变的工作应放在启动阶段,而不是请求路径上:有三件事对每个请求都完全一样——识别可共享的计算、决定执行顺序、确定每个结果的存放位置。F3 Lightning 现在只在服务启动时做一次,因此独立发布不会给单个请求带来任何额外计算开销。

两种设计可以以相同顺序执行相同的操作,但在 CPU 计算开销上可能存在显著差异:在 F3 Lightning 中,我们消除了所有重复计算,但 CPU 使用率仍高于传统 F3。每个特征各自保留一份共享结果的副本,分散在内存中。将这些副本整合为每个结果仅保留一份,才真正弥合了差距。这是项目中不太直观但普适性最强的结论。在现代 CPU 上,结果驻留的位置和副本数量,其影响程度不亚于实际执行的计算量。

致谢

我们要感谢 Sourabh Bansal, Ping Chen, Weitao Chen, Benjamin Dummitt, Junhua Gu, Henry Hu, Jing Huo, Joffrey Liu, Rocky Liu, Weixi Ma, Shuby Mao, Sandeep Pandey, Mihir Rastogi, Tim Sombuntham, Matt Steiner, Vivienne Sung, Ritwik Tewari, Vivek Trehan, Arnaud Venet, Daniel Wang, Ziheng Wang, Gautam Wilkins, Fan Yang, Qiao Yang, Jie Zheng,以及团队全体成员。他们参与开发了 F3——Meta 广告机器学习特征计算与服务平台——及其上层能力 F3 Lightning,并推动其生产化落地。

分享此内容:

原始来源: Meta 工程博客

评论 (0)