Engineering at Meta

PrivacyLib: Privacy Controls at the Data Access Boundary

8.5内容质量
PrivacyLib: Privacy Controls at the Data Access Boundary

TL;DR · AI 摘要

Meta推出的PrivacyLib通过统一库实现数据访问隐私控制,提升效率和可扩展性。

核心要点

  • PrivacyLib每秒执行数十亿次隐私检查,模块化设计使新需求部署时间从月级缩短至周级
  • 通过传播代理上下文实现AI系统数据访问追踪,确保数据仅流向授权路径
  • 替代传统系统级隐私审计,将隐私控制嵌入基础设施实现自动化验证

结构提纲

按章节快速跳转。

  1. 介绍Meta隐私基础设施PAI的演进需求及PrivacyLib的诞生背景。

  2. 说明旧有系统级隐私审计的低效性及难以扩展的问题。

  3. 阐述PrivacyLib作为统一库的模块化集成方案及运行机制。

  4. 披露PrivacyLib每秒处理能力及模块化部署效率提升数据。

  5. 解释PrivacyLib如何通过上下文传播实现动态数据访问监控。

  6. 总结PrivacyLib对Meta隐私控制体系的效率与可靠性提升。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • PrivacyLib架构
    • 核心机制
      • 模块化检查
      • 上下文传播
    • 实施优势
      • 部署效率提升
      • 性能指标
    • 应用场景
      • AI代理监控
      • 数据流控制

金句 / Highlights

值得收藏与分享的关键句。

#PrivacyLib#数据隐私#Meta#基础设施#AI安全
打开原文

PrivacyLib:数据访问边界处的隐私控制 - Meta 的工程实践

发布日期

2026年10月27日

分类

AI研究

机器学习应用

安全与隐私

PrivacyLib:数据访问边界处的隐私控制

.entry-meta

.entry-header

作者

Sai Nagabhairava

Hannes Roth

Na Chen

Peter Prelich

Jonathan Bergeron

  • 这次对Meta隐私感知基础设施(PAI)的深入探讨聚焦于PrivacyLib。
  • 当数据在Meta多样化的数据和处理系统中流动时,必须满足隐私要求。历史上,支持新要求意味着需要在每个系统中分别实现相同的执行逻辑。PrivacyLib通过在Meta数据和处理系统的客户端内部运行的通用库来替代这一做法。
  • 系统只需与PrivacyLib集成一次,新隐私功能作为可复用该集成的模块进行插接。PrivacyLib每秒执行数十亿次检查,且每个模块的变更都可以进行模拟和逐步部署。
  • 对于内部AI代理,其数据访问路径可能无法预先知晓,但支持路径上的工具调用会将代理上下文传递给PrivacyLib。传播的上下文将每次访问与会话关联,使模块能够监控数据使用并阻止未经授权的操作。

在Meta,许多隐私要求最终归结为同一个问题:我们能否证明数据仅流向被允许的位置?在Meta的规模下,我们开发了隐私感知基础设施(PAI),通过基础设施本身来回答这个问题,而不是仅仅依赖可能随着系统变化而脱节的手动隐私审计。PAI在Meta基础设施中嵌入了高效可靠的隐私构造,使产品能够快速持续演进。

在之前的博客中,我们讨论了数据理解、数据血缘和政策区域(我们的信息流控制技术)在PAI中的作用。现在,我们将深入探讨PrivacyLib——一个在数据访问点运行于数百个数据和处理系统客户端的通用库。

PrivacyLib在更广泛的PAI工作流最终阶段发挥关键作用:持续执行和监控数据流。

本文重点介绍PrivacyLib在PAI工作流最终阶段的作用。

在PrivacyLib出现之前,我们需要为每个系统客户端单独集成隐私执行逻辑来满足每个要求。采用这种方法,跨系统实现新要求可能需要数月时间。每个新要求(无论是目的限制还是数据流发现)都需要相同的能力:覆盖广泛系统;在数周而非数月内部署;在每次数据访问时运行而不造成延迟问题;在不中断系统集群的情况下部署执行变更。随着时间推移,这种做法在需求和系统演进过程中变得越来越难以扩展。

PrivacyLib将系统集成与需求实现分离。我们只需为每个系统集成一次。当某个需求可以在数据访问点满足时,我们将其作为跨已集成系统的共享PrivacyLib模块实现,这样新需求不再需要在每个系统中单独实现。

模块分为两类:

  • 执行模块可以阻止数据访问。例如,政策区域执行目的限制。
  • 监控模块仅记录数据访问行为。例如,隐私探针收集用于发现数据流的运行时信号。

随着内部AI代理成为访问数据的新方式,我们还新增了代理监控模块,用于观察与代理相关的数据访问事件,帮助保护代理可访问的数据。已集成PrivacyLib的系统无需额外集成工作即可使用该模块,这就是一次性集成的价值:每个新的隐私功能以模块形式提供,而非作为独立项目。

PrivacyLib消除了各系统中重复的实现方式。

Meta规模下PrivacyLib的工程挑战与解决方案

PrivacyLib在Meta的广泛数据访问路径中运行:

  • 超过百种用于访问数据服务的库,从键值存储到AI功能。
  • 数以千计的生产服务,部署了不同版本的库。
  • 每日访问的数百万资产。
  • 每秒数十亿次的检查操作。

在如此规模下运行会带来三个工程挑战:

| 挑战 | 解决方案 | |------|---------| | 覆盖范围: | 系统可能包含多种客户端库和遗留集成,接受直接网络调用,并向后端存储提供直接数据库访问。所有这些都需要处理,以确保无论访问路径如何,要求始终有效。 | 我们在两个层面进行集成:类型化客户端库(PrivacyLib拥有完整的请求上下文),以及剩余RPC路径的较低网络边界以实现全面覆盖。每个集成系统将其原生API映射到PrivacyLib的通用操作,并提供配置模块所需的输入。一旦这些组件就位,相同的模块即可在多个系统中启用,无需单独的系统特定集成。 | | 规模: | 每秒数十亿次检查,即使每次检查的计算成本很小,整个集群的累积影响也会变得显著。热路径中的每个服务都需要承担这一成本。 | 我们在构建时将要求编译为最简可评估形式,大多数检查通过进程内的缓存回答,从而避免网络调用。 | | 可靠性: | 隐私逻辑运行在关键系统的热路径中,由按自身时间表发布更新的团队负责。变更必须能够独立测试并自主回滚,因为全集群回滚不能作为恢复路径。 | 执行逻辑作为拥有该功能的服务的一部分进行部署,因此变更通过该服务自身的流水线到达生产环境,而非通过全集群配置推送。 |

实践案例:保护消息元数据

为了说明PrivacyLib在Meta规模下的运作方式,让我们通过一个需要遵守的隐私要求来演示如何处理消息元数据。

用户通过Messenger等应用交换消息,该应用支持端到端加密。当Messenger根据适用法律路由和传递这些消息时,其后端系统会处理非内容消息数据(即消息元数据),例如消息发送或送达时间等信息,以提升产品性能、检测滥用行为并保护用户免受有害行为的影响。

这些后端服务中有很多使用 Logger,这是一个在服务进程中运行的客户端库,用于将元数据写入数据仓库表。在写入记录之前,Logger 内部的 PrivacyLib 集成会调用 Policy Zones(一个执行模块),检查元数据是否可以流向目标表。此检查分为两个部分:做出决策所需的输入条件,以及在运行时如何实际评估检查。在分析这两个部分后,我们将探讨 PrivacyLib 如何跨系统扩展,以及变更如何在不影响路径中服务的情况下部署。

PrivacyLib 在消息后端服务的 Logger 客户端内部运行。它将请求上下文与生成的目标元数据结合,调用为写入配置的模块,并将执行决策结果返回给 Logger。

注解消息元数据

每个 Logger 目标表都有一个 LoggerConfig,用于描述写入其中的记录。MetadataTraceLoggerConfig 为消息传递追踪定义了此类配置,并标记为包含消息元数据:

code
final class MetadataTraceLoggerConfig extends LoggerConfigBase {
  use MessagingTrafficMetadataPolicyTrait;
  public function configure(
    LoggerFieldDefiner $fd,
  ): LoggerFieldDefiner {
    $this->annotateWithTableLevelPolicy();
    // Field and destination definitions
  }
}

日志目标可以有多个写入器,因此描述其输出数据的注解属于目标本身,而不是每个写入该目标的服务。如果每个服务单独声明注解,声明可能会不一致,或者新写入器可能完全省略注解。在 LoggerConfig 中一次性声明注解,可以为所有写入器提供相同的数据描述。

对于此目标,MessagingTrafficMetadataPolicyTrait 提供了 MESSAGING_TRAFFIC_METADATA 表级注解,而 annotateWithTableLevelPolicy() 方法将其附加到目标。

LoggerConfig 是 Logger 自己的模式,每个系统以自己的格式描述数据。为了给 PrivacyLib 提供统一的消费形式,Logger 的代码生成流水线将配置转换为兼容 PrivacyLib 的构件。生成的 MetadataTraceLoggerConfigPrivacyLibArtifact 派生自 MetadataTraceLoggerConfig,并携带目标标识和消息注解:

code
/**
 * @codegen-source MetadataTraceLoggerConfig
 */
class MetadataTraceLoggerConfigPrivacyLibArtifact
  extends LoggerInputPrivacyLibArtifact {
  
  final public static function getAssetIDImpl(
  ): LoggerInputAssetID {
    return LoggerInputAssetID::get(
      'MetadataTraceLoggerConfig',
    );
  }

  protected static function getPurposePolicyVars(
  ): TPurposePolicyVars {
    return PurposePolicy::makeVars(
      TPurposePolicyName::MESSAGING_TRAFFIC_METADATA,
    );
  }

  // Other generated fields omitted.
}

上述生成的类由 Logger 的 Hack 客户端(Meta 的类型化 PHP 语言)使用。对于其 C++ 和 Python 客户端,Logger 会将目标标识和注解发布到中央目录。PrivacyLib 使用这些信息生成特定语言的构件。在下一节的 C++ 示例中,PrivacyLib 使用 LoggerConfig 名称加载目标的 C++ 构件。

执行检查

上一节的制品描述了一个 Logger 目标。PrivacyLib 还需要知道 Logger 写操作的含义以及哪些模块应该运行。Logger 的 Hack 集成定义了针对所有目标的统一规则:

code
$config->libraryConfig()->enablePrivacyLib(
  PrivacyLibOperation::write()
    ->input(
      PrivacyLibInput::instance(
        nameof ILoggerInputPrivacyLibPayloadProvider,
      ),
    )
    ->enforcements(
      PrivacyLibWriteEnforcement::zones(
        ZoneDataType::LOGGER,
      )
        ->withPropagation()
        ->enabled(),
      PrivacyLibWriteEnforcement::telemetryLogging()->enabled(),
      // 其他模块
    ),
);

PrivacyLibOperation::write() 声明了 Logger 的 PrivacyLib 写操作,enforcements() 列出了在该操作上运行的模块。策略区域模块可能影响结果,而遥测日志模块会记录评估过程但不影响结果。在此处添加模块无需修改单个目标或 Logger 调用点。ZoneDataType::LOGGER 告诉策略区域模块它正在检查一个 Logger 目标。

在 C++ 服务中,Logger 构造 PrivacyLib 请求的方式如下:

code
const auto assetList =
      std::vector<std::string>{configName};

PrivacyLibBuilder builder(
      "LOGGER_WRITE_CLIENT",
      assetList,
      LoggerWriteAPIEnumHelper::apiToHashedString(
          PolicyEnforcedLoggerWriteAPI::log),
      Caller::LOGGER,
      getLoggerWriteImplementation());

auto result =
      PrivacyLibRequest(std::move(builder)).dispatch();

PrivacyLib 使用 configName 加载目标的制品。PolicyEnforcedLoggerWriteAPI::log 的哈希值将 Logger 的 API 映射到 PrivacyLib 的写操作。其余的构建器参数标识 Logger 为调用方并提供执行写操作的实现。dispatch() 调用配置的模块并记录其结果。对于策略区域模块,PrivacyLib 提供来自生成制品的目标注解和当前处理上下文。在此示例中,策略区域模块评估两个条件:

  • 当前上下文表示允许使用消息元数据。
  • 目标制品携带 MESSAGING_TRAFFIC_METADATA 注解。

当启用策略时,如果任一条件失败,策略区域模块将拒绝写操作。PrivacyLib 将执行模块的结果合并为一个响应。拒绝会阻止写操作在 Logger 将记录发送到下游之前执行。观察模块会发出遥测数据但不影响响应。

深入分析 Logger 写操作上的模块评估。

跨系统扩展隐私检查

PrivacyLib 通过通用执行模型、轻量级评估和遥测,以及对绕过集成客户端的请求的覆盖,使隐私检查能够跨异构系统扩展。

跨系统的通用执行模型

PrivacyLib 的集成覆盖了 API 差异极大的系统:键值存储、图数据库、ML 管道、文件系统和 Logger 等日志系统。每个集成将其访问点映射到五个通用操作之一,PrivacyLib 在每个操作上都会调用其模块:

  • 读取:在获取数据时运行。
  • 写入:在存储数据时运行。
  • RPC:在调用其他服务时运行。
  • 请求入口:在服务开始处理请求时运行一次。
  • 内联执行:将特定计算封装在更大的请求中,例如评估一个特性。在此计算过程中产生的数据访问会使用其内联隐私上下文进行评估。

模块是针对这五种操作进行编写的,而不是针对每个系统的原生 API。读取、写入和 RPC 操作也有后操作变体,因此观察模块可以在访问决策完成后运行。一旦系统提供了映射关系,所有兼容的模块都可以复用它,这就是为什么在已经集成的系统上启用额外模块只需几天时间。

PrivacyLib 运行在数据和处理系统的客户端中,因为客户端已经拥有模块所需的终点身份和请求上下文。将评估保留在同一进程中可以避免在每次访问时跨进程边界传输这些输入。这种设计还带来了两个优势:

  • 更丰富的上下文:PrivacyLib 可以在相关信息仍然可用时使用调用者的身份、端点和用途。如果在数据存储上执行检查,这些信息通常会丢失。
  • 故障隔离:有缺陷的更改仅限于运行该代码版本的服务,从而将共享数据存储保留在故障边界之外。

保持检查的计算成本低廉

由于内联评估会在每次数据访问时运行,因此必须在计算上高效。每个请求的缓存可以消除请求内部的重复检查,而每个主机的缓存可以保留频繁使用的配置和结果。这避免了早期设计所需的远程配置获取(仅此一项就比内联路径慢数千倍)。结果是,PrivacyLib 能够在低每次检查延迟的情况下,每秒在集群中执行数十亿次检查,同时仅消耗 Meta 总计算容量的一小部分。

在 PrivacyLib 的规模下,遥测数据也必须保持轻量。PrivacyLib 记录每个模块的状态,采样延迟并记录错误,这使我们能够全面了解生产环境中执行情况的表现。如果记录每次调用的完整细节,接收这些事件的系统将被淹没,因此目标是去重重复事件,同时保持罕见事件的可见性。

两种机制协同工作以实现这一目标:每个主机上的布隆过滤器以固定速率对高流量模式进行下采样,而细粒度的采样率会根据流量变化进行调整。

覆盖直接 RPC 路径

并非所有调用者都通过 PrivacyLib 集成的客户端库访问数据;有些会发出原始 RPC 请求。RPC 层仍然可以检查这些请求,但它看到的上下文比客户端少得多。PrivacyLib 通过加密签名令牌弥合了这一差距:当客户端检查成功时,PrivacyLib 会将令牌附加到请求中,ServiceRouter 在较低边界对支持的 RPC 流量验证该令牌,确认请求已经通过了更丰富的客户端检查。

当请求到达 RPC 层时没有令牌,ServiceRouter 会回退到基于网络负载中可见上下文的更粗粒度检查。由于该上下文有限,下层会采用最严格的解释,选择误报而非遗漏。两者结合,两个层级覆盖了通过 Meta 管理的客户端和受支持 RPC 路径的访问。超出这些边界的路径不被此机制覆盖,需要自己的 PrivacyLib 集成。

共同作用的结果是,评估尽可能保持本地化且计算成本低廉,在本地评估不可用时则采用更粗粒度的后备方案,同时模块可在不同系统间统一运行。剩下的难题是更复杂的操作问题:如何在不破坏实时流量或导致错误访问决策的前提下调整执行行为。

安全推出执行更改

隐私要求会随时间变化。目的地注解或执行模块的更新可能改变写入操作的允许状态。PrivacyLib 为这两类更改都提供了安全的推出路径。

更改注解

添加或删除注解会修改 LoggerConfig 中的两行代码:导入 MessagingTrafficMetadataPolicyTrait 的声明,以及调用 annotateWithTableLevelPolicy() 的语句。由于目的地可能有多个写入者,这两行代码的修改可能同时阻止多个服务的写入操作。

因此,代码审查的核心是向作者和审查者展示修改的实际影响。在代码审查期间,日志的代码生成管道会更新目的地的 PrivacyLib 软件包。随后模拟会将观测到的数据流同时与新旧两个软件包进行对比,任何结果发生变化的数据流都会被报告。审查者需要共同查看源代码修改、生成的软件包和模拟结果,决策依据是修改效果的实证而非仅阅读代码变更。如果模拟报告了意外变更,审查者会在发布前阻止该软件包。一旦获得批准,软件包会通过各服务的常规构建和部署流程分发到服务端。这在变更部署前提供了两种可见性:一是模拟报告的决策变更,二是生成的代码明确展示将部署的具体内容。同时提供了控制手段,因为每个服务自身的部署流程决定变更何时到达生产环境。这也限定了影响范围:部署后发现的问题仅限于运行该构建的服务。

缺点在于变更不会立即覆盖整个集群,导致变更传播速度慢于全局配置更新。实践中,需求负责人会使用显式跟踪机制在截止日期前更新范围内服务,而需要更快更新的服务可使用紧急部署流程。

模拟在发布前揭示决策变更,而独立服务部署控制时间以限制生产影响。

模块推出

注解变更仅影响一个日志目的地。添加到日志写入路径的模块会为每个日志目的地运行。因此,我们会在全局启用模块前先部署该模块。

以 Policy Zones 的推出为例,我们首先在日志的 PrivacyLib 写操作配置中将 Policy Zones 模块设置为 rollingOut() 状态:

code
PrivacyLibWriteEnforcement::zones(ZoneDataType::LOGGER)
    ->rollingOut()

关键推出标识符 logger.input.write.zones 确定了 Logger 输入、写入操作与 Policy Zones 模块的组合。rollingOut() 将模块执行置于运行时控制之下。我们通过分阶段将该控制从 0% 提升至 100%,同时监控拒绝、错误和延迟情况。若有效写入开始失败,无需重建或重新部署服务即可降低百分比。由于 Telemetry Logging 模块配置独立,其仍持续运行。

当 Policy Zones 模块在 Logger 流量中稳定后,我们将配置从 rollingOut() 改为 enabled()。此时 Logger 集成在不参考推出百分比的情况下直接运行模块。

部署后,工程师可通过运行时信号增减或停止模块推出,无需重建或重新部署。

上述两种变更路径在不同阶段均设有防护机制。若模拟显示出现意外流量变化,我们会在发布前暂停注解工件。若模块在推出后行为异常,可通过运行时控制降低其推出百分比。

这些安全机制改变了 Meta 满足隐私要求的实现方式。PrivacyLib 现在运行在数百个数据和处理系统的客户端内部,每秒执行数十亿次检查,同时仅消耗 Meta 计算资源的一小部分。基于它的要求(如通过 Policy Zones 实现的目的限制、通过 Privacy Probes 实现的数据流发现)可复用这些集成,而非重新构建。因此,启用已覆盖系统的模块仅需数天,而非过去定制集成所需的数月。当变更出现故障时,失败会限制在运行已部署变更的服务内部。我们现在可以基于这一基础构建新一代工作负载:AI 代理。

AI 代理的 PrivacyLib 控制

AI 代理比传统服务更难分析,因为其行为具有动态性,访问的数据在运行时确定。因此,我们必须提前设计代理安全机制,不仅要对构建时预期代理访问的内容实施控制(如 Policy Zones),还需对运行时代理尝试访问的内容实施控制。在启用这些控制后,可在任何数据返回前阻止非法访问。

代理通过调用工具执行任务。单个工具调用可能以嵌套方式跨越多个服务才能到达数据。为应对这一新挑战,我们构建了 AgentLib——基于 PrivacyLib 设计原则的通用运行时,用于监控和管理内部代理的工具调用。在支持的路径上,AgentLib 将每个工具调用与代理会话关联,下游客户端和服务会传播该上下文,直到调用到达集成 PrivacyLib 的系统。在该数据访问边界,PrivacyLib 将上下文、请求操作和目标注解结合,再传递给各个模块进行最终决策。

在 PrivacyLib 中,代理可观测性模块会记录代理会话和工具调用标识符,同时记录目标地址、客户端 API、集成点以及任何错误信息。工程师可以通过会话查询这些事件,以调查工具调用所涉及的服务和目标地址。在引入代理可观测性模块时,PrivacyLib 已有的广泛覆盖能力使我们能够直接在已集成该库的系统中进行监控,而无需手动将模块集成到数百个系统中。即使借助 AI 开发,这也是一项艰巨的任务。

为强制执行某些策略,我们未对现有策略区域模块进行任何修改,因为它依赖于相同的输入进行策略执行。在启用此控制的路径上,系统会在允许操作前考虑目标地址注解和代理上下文。当策略区域模块阻止某个操作时,PrivacyLib 会将该结果沿调用链回传至代理环境。即使代理选择了一个开发者未预料到的工具,策略区域模块仍会执行此检查。访问决策的判定与代理是否按预期行为无关。

AgentLib 将内部代理执行的受支持工具调用与代理上下文相关联,这些信息会在数据访问边界由服务传递给 PrivacyLib。PrivacyLib 利用这些上下文信息进行监控和策略执行。

构建和运营 PrivacyLib 的经验教训

在 Meta 的系统中构建和运营 PrivacyLib 让我们学到了几个重要的经验。

为不断演进的系统和需求进行设计:在 PrivacyLib 出现之前,我们为满足每个新需求而单独在每个调用点添加隐私控制。随着时间推移,Meta 不断构建新的数据和处理系统,服务处理数据的方式也不断演进,隐私需求持续扩展。每次变更都增加了集成工作量,而新的访问路径有时出现的速度甚至超过了我们的覆盖速度。教训是应在稳定的数据访问边界进行集成,定义系统间共享的少量操作,并将注解与数据本身保持一致。模块可以复用相同的集成,长期减少工程工作量。

可靠性赢得采用:早期阶段,一个无效配置导致整个集群的进程崩溃,破坏了我们自身的安全上线原则。其他事故则源于注解变更虽然能成功编译,但改变了访问决策并导致产品故障。这些事故表明,只有当服务所有者能够在发布前看到变更对其流量的影响,并能从有问题的变更中恢复而无需全局回滚时,他们才会在关键数据访问路径上信任 PrivacyLib。

覆盖范围包含部署:当新需求需要对模块、支持的策略和注解进行变更时,旧服务需要部署包含更新后的 PrivacyLib 功能的版本。我们已确保有显式的清单、跟踪和上线机制来协调这些部署,使需求负责人能够通过仅更新范围内的服务来安全地满足截止期限。

未来展望

这些经验正在塑造 PrivacyLib 的下一步发展方向,随着 AI 将数据访问推向越来越新颖的环境和调用路径。我们继续从两个方面增强 PrivacyLib 的能力:

  • AI训练:用于训练的数据通过多种环境进入我们的系统。其中大部分数据在不同地方被生成或转换。我们正在将PrivacyLib风格的方法扩展到该领域,使相同的保障措施能够随之扩展。有关Meta如何处理AI训练数据的更多信息,请参见《AI at Meta:关于训练数据的透明度》。
  • 内部AI代理:在Meta内部用于工程和生产力的代理系统中,观察尝试访问数据的行为并阻止未授权的访问只是一个起点,而非完整的解决方案。接下来的挑战更为复杂:在行为发生时评估内部代理的行为,以及推理这些代理之间的通信关系,其中一次数据访问可能与最初请求者相隔多个代理层级。

致谢

作者希望感谢许多现职和曾任职于Meta的员工对PrivacyLib开发所做出的重要贡献。特别地,我们谨向(按字母顺序排列)Adwait Sathe、Andrew Vaslas、Darrien Glasser、Diana Marsala、Hanzhi Zhang、Kousik Nandy、Matthew Hillman、Matthieu Martin、Michael Dietz、Nathan Maue、Ramy Wassef、Sen Luo、Spencer Baumgardner、Vince Yang和Zhong Hu表示特别感谢。同时,我们也衷心感谢本文的所有审阅者(按字母顺序排列):Aleksandar Ilic、Alex Kagan、Alex Ponomarenko、Arash Nikkar、Avtar Brar、Chloe Lu、Chris Wiltz、Emil Vazquez、Emily Van Deuren、Rituraj Kirti、Sushaant Mujoo和Wenlong Dong。我们特别感谢Jonathan Bergeron监督整个工作并提供所有指导和宝贵反馈,以及Chloe Lu协调所需的资源和支持以促成这篇博文的发布。