神经网络的通用和可扩展并行化

Posted 雨夜的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了神经网络的通用和可扩展并行化相关的知识,希望对你有一定的参考价值。

缩放神经网络,无论是使用的训练数据量、模型大小还是使用的计算,对于提高许多现实世界机器学习应用程序的模型质量至关重要,例如计算机视觉、语言理解和神经机器翻译. 反过来,这又促使最近的研究仔细审查在缩放神经模型的成功中起关键作用的因素。尽管增加模型容量可能是提高模型质量的合理方法,但这样做会带来许多必须克服的系统和软件工程挑战。例如,为了训练超过加速器内存容量的大型模型,有必要在多个加速器之间划分权重和模型计算。这种并行化过程会增加网络通信开销,并可能导致设备利用率不足。此外,给定的并行化算法通常需要大量的工程工作,可能不适用于不同的模型架构。

为了解决这些扩展挑战,我们提出了“ GSPMD:ML 计算图的通用和可扩展并行化”,其中我们描述了一个基于XLA 编译器的开源自动并行化系统。GSPMD 能够扩展大多数深度学习网络架构,并且已经应用于许多深度学习模型,例如GShard-M4、LaMDA、BigSSL、ViT和MetNet-2,从而在跨领域取得最先进的结果几个域。GSPMD 还被集成到多个 ML 框架中,包括TensorFlow和JAX,它们使用 XLA 作为共享编译器。

概述

GSPMD 将 ML 模型编程任务与并行化挑战分开。它允许模型开发人员编写程序,就好像它们在具有非常高内存和计算能力的单个设备上运行一样——用户只需要将几行注释代码添加到模型代码中的关键张量子集中,以指示如何分割张量。例如,要训练一个大型模型并行的Transformer,一个人可能只需要注释少于 10 个张量(少于整个计算图中所有张量的 1%),每个张量一行附加代码。然后 GSPMD 运行编译器通道,确定整个图的并行化计划,并将其转换为数学上等效的并行化计算,可以在每个设备上执行。这使用户可以专注于模型构建而不是并行化实现,并且可以轻松移植现有的单设备程序以在更大范围内运行。

模型编程和并行性的分离还允许开发人员最大限度地减少代码重复。使用 GSPMD,开发人员可以针对不同的用例采用不同的并行算法,而无需重新实现模型。例如,支持 GShard-M4 和 LaMDA 模型的模型代码可以应用各种适用于具有相同模型实现的不同模型和集群大小的并行化策略。同样,通过应用 GSPMD,BigSSL 大型语音模型可以与以前的较小模型共享相同的实现。

通用性和灵活性

由于不同的模型架构可能更适合不同的并行化策略,因此 GSPMD 旨在支持适用于不同用例的多种并行算法。例如,对于适合单个加速器内存的较小模型,数据并行是首选,其中设备使用不同的输入数据训练相同的模型。相比之下,大于单个加速器内存容量的模型更适合流水线算法(如GPipe采用的算法),该算法将模型划分为多个、连续的阶段或运算符级并行(例如,Mesh-TensorFlow),其中模型中的单个计算运算符被拆分为更小的并行运算符。

GSPMD 支持上述所有具有统一抽象和实现的并行化算法。此外,GSPMD 支持嵌套的并行模式。例如,它可用于将模型划分为单独的流水线阶段,每个流水线阶段都可以使用运算符级并行性进一步划分。

GSPMD 还允许性能专家专注于最能利用硬件的算法,而不是涉及大量跨设备通信的实现,从而促进了并行算法的创新。例如,对于大型 Transformer 模型,我们发现了一种新颖的算子级并行算法,可以在设备的 2D 网格上划分张量的多个维度。它随着训练设备的数量线性减少峰值加速器内存使用量,同时由于其在多个维度上的平衡数据分布,保持加速器计算的高利用率。

为了说明这一点,请考虑已按上述方式注释的 Transformer 模型中的简化前馈层。为了对完全分区的输入数据执行第一个矩阵乘法,GSPMD 应用MPI风格的AllGather通信算子与来自另一个设备的分区数据进行部分合并。然后它在本地执行矩阵乘法并产生一个分区结果。在第二次矩阵乘法之前,GSPMD 在右侧输入添加另一个 AllGather,并在本地执行矩阵乘法,产生随后需要组合和分区的中间结果。为此,GSPMD 添加了一个 MPI 风格的ReduceScatter累积和划分这些中间结果的通信算子。虽然在每个阶段使用 AllGather 算子生成的张量都大于原始分区大小,但它们是短暂的,使用后会释放相应的内存缓冲区,这不会影响训练中的峰值内存使用。

具有嵌套并行性的 Transformer 示例

作为针对不同并行性模式的共享、健壮机制,GSPMD 允许用户方便地在模型不同部分的模式之间切换。这对于可能具有不同性能特征的不同组件的模型特别有价值,例如,处理图像和音频的多模态模型。考虑一个具有Transformer编码器-解码器架构的模型,它有一个嵌入层、一个带有Mixture-of-Expert层的编码器堆栈、一个带有密集前馈层的解码器堆栈和一个最终的softmax层。在 GSPMD 中,可以通过简单的配置实现对每一层分别处理的几种并行模式的复杂组合。

在下图中,我们展示了组织为逻辑 4x4 网格的 16 个设备的分区策略。蓝色表示沿第一个网格维度X 进行分区,黄色表示沿第二个网格维度Y 进行分区。X和Y被重新用于不同的模型组件以实现不同的并行模式。例如,X维用于嵌入层和 softmax 层中的数据并行,但用于编码器和解码器中的流水线并行。该ÿ尺寸也被用来以不同的方式来划分词汇,分批或模型专家尺寸。

计算效率

GSPMD 在大型模型训练中提供行业领先的性能。并行模型需要额外的通信来协调多个设备来进行计算。因此,可以通过检查花费在通信开销上的时间比例来估计并行模型的效率——利用率越高,花费在通信上的时间越少越好。在最近的MLPerf性能基准测试集中,一个类似 BERT 的仅编码器模型具有约 5000 亿个参数,我们在 2048 TPU-V4 上应用 GSPMD 进行并行化芯片产生了极具竞争力的结果(见下表),利用了 TPU-V4 提供的高达 63% 的峰值 FLOPS。我们还在下表中提供了一些具有代表性的大型模型的效率基准。这些示例模型配置在Lingvo 框架中开源,并附有在 Google Cloud 上运行它们的说明。更多的基准测试结果可以在我们论文的实验部分找到。

模范家庭 参数计数 已激活模型的百分比* 专家人数** 层数 TPU数量 FLOPS 利用率

密集解码器 (LaMDA) 137B 100% 1 64 1024 TPUv3 56.5%

密集编码器 (MLPerf-Bert) 480B 100% 1 64 2048 TPUv4 63%

稀疏激活编码器-解码器 (GShard-M4) 577B 0.25% 2048 32 1024 TPUv3 46.8%

稀疏激活解码器 1.2T 8% 64 64 1024 TPUv3 53.8%

结论

许多有用的机器学习应用程序(例如 NLP、语音识别、机器翻译和自动驾驶)的持续发展和成功取决于尽可能实现最高准确度。

工作流中容器化的依赖注入!Activiti集成CDI实现工作流的可配置型和可扩展型

Activiti工作流集成CDI简介

  • activiti-cdi模块提供activiti的可配置型和cdi扩展
  • activiti-cdi的特性:

    • 支持 @BusinessProcessScoped beans, 绑定到流程实例的cdi bean
    • 流程为cdi bean支持自定义EL处理器
    • 使用注解为流程实例提供声明式控制
    • Activiti可以挂接在cdi事件总线上
    • 支持Java EE和Java SE, 支持Spring
    • 支持单元测试
  • 要在maven项目中使用activiti-cdi,需要添加依赖:

    <dependency>
          <groupId>org.activiti</groupId>
          <artifactId>activiti-cdi</artifactId>
          <version>5.8</version>
    </dependency>
  • activiti-cdi 5.6以上的版本会自动加入activiti-entin和spring

    设置activiti-cdi

  • Activiti cdi可以安装在不同环境中

    查找流程引擎

  • cdi扩展需要访问到ProcessEngine, 为了实现此功能:

    • 使用org.activiti.cdi.spi.ProcessEngineLookup接口在运行期间进行查找
    • cdi模块使用默认的名为org.activiti.cdi.impl.LocalProcessEngineLookup的实现,使用ProcessEngines这个工具类来查找ProcessEngine
    • 默认配置下,使用ProcessEngines#NAME_DEFAULT来查找ProcessEngine.这个类可能是使用自定义名称的子类
    • ==注意:== 需要把activiti.cfg.xml放在classpath下
  • Activiti cdi使用java.util.ServiceLoader SPI处理org.activiti.cdi.spi.ProcessEngineLookup的实例

    • 为了提供接口的自定义实现,需要创建一个文本文件,名为META-INF/services/org.activiti.cdi.spi.ProcessEngineLookup, 在文件中需要指定实现的全类名
    • 如果你没有提供自定义的org.activiti.cdi.spi.ProcessEngineLookup实现,activiti会使用默认的LocalProcessEngineLookup实现,需要做的就是把activiti.cfg.xml放到classpath下

      配置Process Engine

  • 实际的配置依赖于选用的ProcessEngineLookup策略
  • 在这里主要结合LocalProcessEngineLookup讨论可用的配置,要求在classpath下提供一个spring的activiti.cfg.xml
  • Activiti提供了不同的ProcessEngineConfiguration实现,主要是依赖实际使用的事务管理策略
  • activiti-cdi模块对事务的要求不严格,意味着任何事务管理策略都可以使用,即便是spring事务抽象层
  • cdi模块提供两种自定义ProcessEngineConfiguration实现:

    • org.activiti.cdi.CdiJtaProcessEngineConfiguration: activiti的JtaProcessEngineConfiguration的子类,用于在activiti使用JTA管理的事务环境
    • org.activiti.cdi.CdiStandaloneProcessEngineConfiguration: activiti的StandaloneProcessEngineConfiguration的子类,用于在activiti使用简单JDBC事务环境
  • JBoss7下的activiti.cfg.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
          <!-- lookup the JTA-Transaction manager -->
          <bean id="transactionManager" class="org.springframework.jndi.JndiObjectFactoryBean">
                  <property name="jndiName" value="java:jboss/TransactionManager"></property>
                  <property name="resourceRef" value="true" />
          </bean>
    
          <!-- process engine configuration -->
          <bean id="processEngineConfiguration"
                  class="org.activiti.cdi.CdiJtaProcessEngineConfiguration">
                  <!-- lookup the default Jboss datasource -->
                  <property name="dataSourceJndiName" value="java:jboss/datasources/ExampleDS" />
                  <property name="databaseType" value="h2" />
                  <property name="transactionManager" ref="transactionManager" />
                  <!-- using externally managed transactions -->
                  <property name="transactionsExternallyManaged" value="true" />
                  <property name="databaseSchemaUpdate" value="true" />
          </bean>
    </beans>
  • 在Glassfish 3.1.1,假设配置好名为jdbc/activiti的datasource:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
          <!-- lookup the JTA-Transaction manager -->
          <bean id="transactionManager" class="org.springframework.jndi.JndiObjectFactoryBean">
                  <property name="jndiName" value="java:appserver/TransactionManager"></property>
                  <property name="resourceRef" value="true" />
          </bean>
    
          <!-- process engine configuration -->
          <bean id="processEngineConfiguration"
                  class="org.activiti.cdi.CdiJtaProcessEngineConfiguration">
                  <property name="dataSourceJndiName" value="jdbc/activiti" />
                  <property name="transactionManager" ref="transactionManager" />
                  <!-- using externally managed transactions -->
                  <property name="transactionsExternallyManaged" value="true" />
                  <property name="databaseSchemaUpdate" value="true" />
          </bean>
    </beans>
  • 注意: 上面的配置要引入spring-context模块依赖

    <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>3.0.3.RELEASE</version>
    </dependency>

    发布流程

  • 可以使用标准的activiti-api发布流程-RepositoryService
  • activiti-cdi也提供了自动发布classpath下processes.xml中列出的流程的方式
  • processes.xml:

    <?xml version="1.0" encoding="utf-8" ?>
    <!-- list the processes to be deployed -->
    <processes>
          <process resource="diagrams/myProcess.bpmn20.xml" />
          <process resource="diagrams/myOtherProcess.bpmn20.xml" />
    </processes>

    基于CDI环境的流程执行

  • BPMN业务流程通常是一个长时间运行的操作,包含了用户和系统任务的操作
  • 运行过程中,流程会分成多个单独的工作单元,由用户和应用逻辑执行
  • 在activiti-cdi中,流程实例可以分配到cdi环境中,关联展现成一个工作单元:

    • 这是非常有用的,如果工作单元太复杂:比如如果实现的用户任务是不同形式的复杂顺序,可以在这个操作中保持non-process-scoped状态
    • 默认配置下,流程实例分配到broadest激活环境,就会启动交互,如果交互环境没有激活,就会返回到请求中

      与流程实例进行关联交互

  • 处理 @BusinessProcessScoped beans, 或注入流程变量时,实现了激活的cdi环境与流程实例的关联
  • Activiti-cdi提供了org.activiti.cdi.BusinessProcess bean来控制关联:

    • startProcessByXx(...): 对应activiti的RuntimeService中的相关方法,允许启动和随后向关联的业务流程
    • resumeProcessById(String processInstanceId): 允许通过提供的Id来关联流程实例
    • resumeTaskById(String taskId): 允许通过提供的Id来关联任务,也可以扩展关联流程实例
  • 一个工作单元完成后 ,completeTask() 方法可以调用来解除流程实例和会话或请求的关联.这会通知activiti当前任务已经完成,并让流程实例继续执行
  • BusinessProcess bean是 @Named bean, 意思是导出的方法可以通过表达式语言调用:

    • 比如在JSF页面中.下面的JSF 2 代码启动一个新的交互,分配给一个用户任务实例,Id作为一个请求参数传递:

      <f:metadata>
      <f:viewParam name="taskId" />
      <f:event type="preRenderView" listener="#{businessProcess.startTask(taskId, true)}" />
      </f:metadata>

      声明式流程控制

  • Activiti-cdi允许通过注解声明启动流程实例和完成任务
  • @org.activiti.cdi.annotation.StartProcess注解允许通过key或name启动流程实例.流程实例会在注解的方法返回之后启动:

    @StartProcess("authorizeBusinessTripRequest")
    public String submitRequest(BusinessTripRequest request) {
          // do some work
          return "success";
    }
  • 根据activiti的配置,注解方法的代码和启动流程实例会在同一个事务中执行 .@org.activiti.cdi.annotation.CompleteTask事务的使用方式相同:

    @CompleteTask(endConversation=false)
    public String authorizeBusinessTrip() {
          // do some work
          return "success";
    }

    @CompleteTask注解可以结束当前会话.默认行为会在activiti返回后结束会话.可以禁用结束会话的功能

    在流程中引用bean

  • Activiti-cdi使用自定义解析器把CDI bean暴露到activiti El中,可以在流程中引用这些bean:

    <userTask id="authorizeBusinessTrip" name="Authorize Business Trip"
                          activiti:assignee="#{authorizingManager.account.username}" />
  • authorizingManager可以是生产者方法提供的bean:

    @Inject @ProcessVariable Object businessTripRequesterUsername;
    
    @Produces
    @Named
    public Employee authorizingManager() {
          TypedQuery<Employee> query = entityManager.createQuery("SELECT e FROM Employee e WHERE e.account.username=\'"
                  + businessTripRequesterUsername + "\'", Employee.class);
          Employee employee = query.getSingleResult();
          return employee.getManager();

    使用@BusinessProcessScoped beans

  • 使用activiti-cdi,bean的生命周期可以绑定到流程实例上:

    • 可以提供一个自定义的环境实现,命名为BusinessProcessContext.
    • BusinessProcessScoped bean的实例会作为流程变量保存到当前流程实例中
    • BusinessProcessScoped bean需要是PassivationCapable,比如序列化
  • 使用流程作用域bean的示例如下:

    @Named
    @BusinessProcessScoped
    public class BusinessTripRequest implements Serializable {
          private static final long serialVersionUID = 1L;
          private String startDate;
          private String endDate;
          // ...
    }
  • 有时,需要使用流程作用域bean,没有与流程实例关联:

    • 比如启动流程之前.如果当前流程实例没有激活 ,BusinessProcessScoped bean实例会暂时保存在局部作用域里:

      • 会话
      • 请求
      • 依赖环境
  • 如果作用域后来与业务流程实例关联了,bean实例会刷新到流程实例里

    注入流程变量

  • 流程变量可以实现用于注入
  • Activiti-CDI支持以下注入流程变量的方式:

    • @BusinessProcessScoped使用 @Inject [附加修饰] 类型 属性名实现类型安全的流程变量的注入
    • 使用@ProcessVariable(name)修饰符实现对类型不安全的流程变量的注入

      @Inject @ProcessVariable Object accountNumber;
      @Inject @ProcessVariable("accountNumber") Object account
  • 为了通过EL引用流程变量, 可以使用如下方式:

    • @Named @BusinessProcessScoped beans可以直接引用
    • 其他流程变量可以使用ProcessVariables bean来使用

      #{processVariables[\'accountNumber\']}

      接收流程事件

  • Activiti可以挂在CDI的事件总线上,就可以使用标准CDI事件机制来监听流程事件
  • 为了启用activiti的CDI事件支持,需要在配置中启用对应的解析监听器:

    <property name="postBpmnParseHandlers">
          <list>
                  <bean class="org.activiti.cdi.impl.event.CdiEventSupportBpmnParseHandler" />
          </list>
    </property>
  • 这样activiti就配置成了使用CDI事件总线发布事件
  • 在CDI bean中处理事件的方式:

    • 使用@Observes注解声明特定的事件监听器
    • 事件监听是类型安全的
    • 流程事件类型是org.activiti.cdi.BusinessProcessEvent
  • 一个简单事件监听方法示例:

    public void onProcessEvent(@Observes BusinessProcessEvent businessProcessEvent) {
          // handle event
    }
  • 监听器可以监听所有事件.如果想限制监听器接收的事件类型,可以添加修饰注解:

    • @BusinessProcess: 限制指定流程定义的事件

      • @Observes @BusinessProcess("billingProcess")
    • @StartActivity: 限制指定进入环节的事件

      • @Observes @StartActivity("shipGoods")
    • @EndActivity: 限制指定结束环节的事件

      • @Observes @EndActivity("shipGoods")
    • @TakeTransition: 限制指定连线的事件
  • 修饰命名可以自由组合:

    • 为了接收shipmentProcess流程中所有离开shipGoods环节的事件:

      public void beforeShippingGoods(@Observes @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
          // handle event
      }
  • 默认配置下,事件监听器是同步调用,并在同一个事务环境中
  • CDI事务性监听器可以控制监听器什么时候处理事件:

    • 可以保证监听器只在事件中的事务成功之后才处理

      public void onShipmentSuceeded(@Observes(during=TransactionPhase.AFTER_SUCCESS) @BusinessProcess("shippingProcess") @EndActivity("shipGoods") BusinessProcessEvent evt) {
          // send email to customer.
      }

      Activiti CDI中的更多功能

  • 流程引擎和服务都可以注入: Inject ProcessEngine,RepositoryService,TaskService,...
  • 当前流程实例和任务可以注入: @Inject ProcessInstance, Task
  • 当前业务标识可以注入: @Inject @BusinessKey String businessKey
  • 当前流程实例id可以注入: @Inject @ProcessInstanceId String pid

以上是关于神经网络的通用和可扩展并行化的主要内容,如果未能解决你的问题,请参考以下文章

工作流中容器化的依赖注入!Activiti集成CDI实现工作流的可配置型和可扩展型

为啥在更多 CPU/内核上的并行化在 Python 中的扩展性如此之差?

icon尺寸通用设计规范:

深度学习的并行化策略

转:Linux网络IO并行化技术概览

通用应用程序的今日扩展