#memory-region #distributed-computing #distributed #run-time #hpc #distributed-applications #data-processing

lamellar

Lamellar是一个用于HPC系统的异步任务运行时,由RUST开发

12个版本

0.6.1 2024年2月16日
0.6.0 2023年12月1日
0.5.0 2023年1月19日
0.5.0-rc.22022年12月23日
0.2.2 2020年9月5日

#96 in 异步

Download history 2/week @ 2024-06-29 13/week @ 2024-07-20 68/week @ 2024-07-27

81 次每月下载

自定义许可证

2MB
33K SLoC

Lamellar - Rust HPC运行时

Lamellar是研究Rust系统编程语言在HPC领域的适用性,作为C和C++的替代品,重点关注PGAS方法。

一些术语

在下面的readme和API文档(https://docs.rs/lamellar/latest/lamellar/)中,我们反复使用了一些术语,以下是对这些术语及其简要描述的说明

  • PE - 处理单元,通常是多线程进程,对于那些熟悉MPI的人来说,它对应于Rank。
    • 通常你会在你的系统上的每个物理CPU插槽上创建1个PE,但每个CPU上可以有多个PE也是有效的
    • 在某些情况下,可能会使用Node(表示计算节点)而不是PE,在这种情况下,它们可以互换使用
  • World - 代表你的分布式计算系统的抽象
    • 由N个PE组成,所有PE都可以相互通信
  • Team - 世界中存在的PE的子集
  • AM - 简称Active Message
  • Collective Operation - 通常意味着所有PE(与给定的分布式对象相关联)必须明确参与操作,否则会发生死锁。
    • 例如,屏障、构建新的分布式对象
  • One-sided Operation - 通常意味着只有调用PE需要成功完成操作。
    • 例如,访问本地数据,等待本地工作完成

功能

层状结构提供了多种不同的通信模式和编程模型,以下是简要概述:

主动消息

层状结构允许在分布式环境中向远程PE发送和执行用户定义的主动消息。用户首先为他们的数据结构实现运行时导出特质(LamellarAM),然后在实现上调用一个过程宏 #[lamellar::am]。过程宏生成所有必要的代码,以启用远程执行主动消息。更多详细信息请参阅 主动消息 模块文档。

达尔斯(分布式弧)

层状结构提供了一个名为 DarcArc 的分布式扩展。达尔斯在分布式环境中提供对内部对象的安全共享访问,确保生命周期和读写访问得到正确执行。更多详细信息请参阅 Darc 模块文档。

PGAS 抽象

层状结构也通过多个接口提供 PGAS 功能。

LamellarArrays(分布式数组)

第一个是分布式数组的抽象,允许进行分布式迭代和元素的数据并行处理。更多详细信息请参阅 LamellarArray 模块文档。

低级内存区域

第二个是构建可从远程PE读取和写入的内存区域的低级(不安全)接口。请注意,除非您非常熟悉/自信于低级分布式内存(即使如此,也强烈建议您使用 LamellarArrays 接口)。更多详细信息请参阅 内存区域 模块文档。

网络后端

层状结构依赖于称为 Lamellae 的网络提供者来在整个系统中传输数据。目前存在三种这样的 Lamellae:

  • local - 用于单 PE(单个系统、单个进程)开发(这是默认设置),
  • shmem - 用于多 PE(单个系统、多进程)开发,对于模拟分布式环境很有用(通过共享内存通信)
  • rofi - 用于多 PE(多系统、多进程)分布式开发,基于 Rust OpenFabrics 接口传输层(ROFI)(https://github.com/pnnl/rofi)。
    • 默认情况下,Rofi 的支持被禁用,因为使用它依赖于 Rofi C 库和 libfabrics 库,这些库可能没有安装在你的系统上。
    • 可以通过向你的 Cargo.toml 文件中的 lamellar 条目添加 features = ["enable-rofi"] 来启用它

层状结构的长期目标是,您可以使用 local 后端进行开发,然后当您准备好运行分布式时,无需更改代码即可切换到 rofi 后端。目前情况相反,如果它使用 rofi 编译和运行,则在没有更改的情况下,它将在使用 localshmem 时编译和运行。

有关使用每个 Lamellae 后端的附加信息,请参阅下面的 运行层状应用程序 部分

示例

我们的仓库还提供了许多示例,突出了运行时的各种功能: https://github.com/pnnl/lamellar-runtime/tree/master/examples

此外,我们正在编译一套基准测试(其中一些具有多个实现),这可能也有助于您参考: https://github.com/pnnl/lamellar-benchmarks/

以下是一些突出lamellar一些功能的简单示例,更深入的示例可以在各种功能的文档中找到。

选择一个Lamellae并构建lamellar世界实例

您可以在运行时选择要使用的后端,如下所示

use lamellar::Backend;
fn main(){
 let mut world = lamellar::LamellarWorldBuilder::new()
        .with_lamellae( Default::default() ) //if "enable-rofi" feature is active default is rofi, otherwise  default is `Local`
        //.with_lamellae( Backend::Rofi ) //explicity set the lamellae backend to rofi,
        //.with_lamellae( Backend::Local ) //explicity set the lamellae backend to local
        //.with_lamellae( Backend::Shmem ) //explicity set the lamellae backend to use shared memory
        .build();
}

或通过设置以下环境变量: LAMELLAE_BACKEND="lamellae" 其中lamellae是以下之一: localshmemrofi

创建和执行一个注册的主动消息

请参阅主动消息文档以获取更多细节和示例

use lamellar::active_messaging::prelude::*;

#[AmData(Debug, Clone)] // `AmData` is a macro used in place of `derive` 
struct HelloWorld { //the "input data" we are sending with our active message
    my_pe: usize, // "pe" is processing element == a node
}

#[lamellar::am] // at a highlevel registers this LamellarAM implemenatation with the runtime for remote execution
impl LamellarAM for HelloWorld {
    async fn exec(&self) {
        println!(
            "Hello pe {:?} of {:?}, I'm pe {:?}",
            lamellar::current_pe, 
            lamellar::num_pes,
            self.my_pe
        );
    }
}

fn main(){
    let mut world = lamellar::LamellarWorldBuilder::new().build();
    let my_pe = world.my_pe();
    let num_pes = world.num_pes();
    let am = HelloWorld { my_pe: my_pe };
    for pe in 0..num_pes{
        world.exec_am_pe(pe,am.clone()); // explicitly launch on each PE
    }
    world.wait_all(); // wait for all active messages to finish
    world.barrier();  // synchronize with other PEs
    let request = world.exec_am_all(am.clone()); //also possible to execute on every PE with a single call
    world.block_on(request); //both exec_am_all and exec_am_pe return futures that can be used to wait for completion and access any returned result
}

创建、初始化和遍历一个分布式数组

请参阅LamellarArray文档以获取更多细节和示例

use lamellar::array::prelude::*;

fn main(){
    let world = lamellar::LamellarWorldBuilder::new().build();
    let my_pe = world.my_pe();
    let block_array = AtomicArray::<usize>::new(&world, 1000, Distribution::Block); //we also support Cyclic distribution.
    block_array.dist_iter_mut().enumerate().for_each(move |(i,elem)| elem.store(i) ); //simultaneosuly initialize array accross all pes, each pe only updates its local data
    block_array.wait_all();
    block_array.barrier();
    if my_pe == 0{
        for (i,elem) in block_array.onesided_iter().into_iter().enumerate(){ //iterate through entire array on pe 0 (automatically transfering remote data)
            println!("i: {} = {})",i,elem);
        }
    }
}

在主动消息中使用Darc

请参阅Darc文档以获取更多细节和示例

use lamellar::active_messaging::prelude::*;
use std::sync::atomic::{AtomicUsize,Ordering};

#[AmData(Debug, Clone)] // `AmData` is a macro used in place of `derive` 
struct DarcAm { //the "input data" we are sending with our active message
    cnt: Darc<AtomicUsize>, // count how many times each PE executes an active message
}

#[lamellar::am] // at a highlevel registers this LamellarAM implemenatation with the runtime for remote execution
impl LamellarAM for DarcAm {
    async fn exec(&self) {
        self.cnt.fetch_add(1,Ordering::SeqCst);
    }
}

fn main(){
    let mut world = lamellar::LamellarWorldBuilder::new().build();
    let my_pe = world.my_pe();
    let num_pes = world.num_pes();
    let cnt = Darc::new(&world, AtomicUsize::new());
    for pe in 0..num_pes{
        world.exec_am_pe(pe,DarcAm{cnt: cnt.clone()}); // explicitly launch on each PE
    }
    world.exec_am_all(am.clone()); //also possible to execute on every PE with a single call
    cnt.fetch_add(1,Ordering::SeqCst); //this is valid as well!
    world.wait_all(); // wait for all active messages to finish
    world.barrier();  // synchronize with other PEs
    assert_eq!(cnt.load(Ordering::SeqCst),num_pes*2 + 1);
}

使用Lamellar

Lamellar可以在单节点工作站以及分布式HPC系统上运行。对于工作站,只需将以下内容复制到Cargo.toml文件的依赖项部分

lamellar= "0.6.1"

如果您计划在分布式HPC系统内使用,请将以下内容复制到Cargo.toml文件

lamellar= {version= "0.6.1",features= ["enable-rofi"]}

注意:从Lamellar 0.6.1版本开始,不再需要手动安装Libfabric,构建过程现在将尝试自动为您构建libfabric。如果此过程失败,您仍然可以通过OFI_DIR环境变量传递手动libfabric安装。

对于这两种环境,按照正常方式构建您的应用程序

cargobuild (--release)

运行Lamellar应用程序

有几种方式可以运行Lamellar应用程序,这主要取决于您想使用的lamellae。

local(单进程、单系统)

  1. 直接启动可执行文件
    • cargo运行 --发布

shmem(多进程、单系统)

  1. 获取lamellar_run.sh
  2. 使用lamellar_run.sh启动您的应用程序
    • ./lamellar_run -N=2 -T=10 <appname>
      • N要启动的PE(进程)数量(默认=1)
      • T每个PE的线程数(默认 = 核心数/PE数)
      • 假设<appname>可执行文件位于./target/release/<appname>

rofi(多进程、多系统)

  1. 在集群上分配计算节点
    • salloc-N2
  2. 使用集群启动器启动应用程序
    • srun-N2 -mpi=pmi2./目标/发布/<appname>
      • pmi2库用于获取分配的节点信息,并帮助设置初始握手

环境变量

Lamellar公开了一些环境变量,可以在运行时用于控制应用程序的执行

  • LAMELLAR_THREADS - Lamellar PE内使用的工线程数量
    • export LAMELLAR_THREADS=10
  • LAMELLAE_BACKEND - 执行期间使用的后端。注意,如果在世界构建器中显式设置了后端,则此变量将被忽略。
    • 可能的值
      • 本地
      • shmem
      • rofi
  • LAMELLAR_MEM_SIZE - 指定运行时 "RDMAable" 内存池的初始大小。默认为1GB
    • export LAMELLAR_MEM_SIZE=$((20*1024*1024*1024)) 20GB 内存池
    • 内部,Lamellar 使用 RDMAable 内存池来存储运行时数据结构(例如 DarcsOneSidedMemoryRegion等),聚合缓冲区和消息队列。根据需要,系统将动态分配额外的内存池。这可能是一项相当昂贵的操作(因为该操作是在所有 PE 上同步进行的),因此运行时将在执行结束时打印一条消息,说明分配了多少额外的池。
      • 如果您发现正在动态分配新的内存池,请尝试将 LAMELLAR_MEM_SIZE 设置为更大的值
    • 注意:在单个系统上运行多个 PE 时,池的总分配内存将等于 LAMELLAR_MEM_SIZE * 进程数

新闻

  • 2023年2月:alpha版本 -- v0.6.1
  • 2023年11月:alpha版本 -- v0.6
  • 2023年1月:alpha版本 -- v0.5
  • 2022年3月:alpha版本 -- v0.4
  • 2021年4月:alpha版本 -- v0.3
  • 2020年9月:添加对 "local" lamellae 的支持,为 crates.io 发布做准备 -- v0.2.1
  • 2020年7月:第二个 alpha 版本 -- v0.2
  • 2020年2月:第一个 alpha 版本 -- v0.1

构建要求

  • Cargo.toml 中列出的 crate

可选:如果您想在分布式 HPC 环境中运行 Lamellar,则需要以下依赖项:rofi lamellae 通过在 cargo.toml 或构建时命令行中添加 "enable-rofi" 来启用。例如,使用 cargo build --features enable-rofi 构建 Rofi。可以将 Rofi 从源代码构建,然后设置 ROFI_DIR 环境变量为 Rofi 安装目录,或者让 rofi-sys crate 自动构建它。

在发布时,Lamellar 已与以下外部软件包进行测试

GCC CLANG ROFI OFI IB VERBS MPI SLURM
7.1.0 8.0.1 0.1.0 1.20 1.13 mvapich2/2.3a 17.02.7

构建软件包

以下假设根目录为 ${ROOT}

  1. 将 Lamellar 下载到 ${ROOT}/lamellar-runtime

cd${ROOT} &&git clone https://github.com/pnnl/lamellar-runtime

  1. 选择要使用的 Lamellae

    • 在 Cargo.toml 中添加 "enable-rofi" 功能,如果想要使用 rofi(或将 --features enable-rofi 传递给 cargo build 命令),否则将仅构建对本地和 shmem 后端的支持。
  2. 编译 Lamellar 库和测试可执行文件(可以将功能标志传递给命令行,而不是在 cargo.toml 中指定)

cargobuild (--release) (--featuresenable-rofi)

executables located at ./target/debug(release)/test
  1. 编译示例

cargobuild --examples(--release) (--featuresenable-rofi)

executables located at ./target/debug(release)/examples/

注意:我们执行显式构建,而不是 cargo run --examples,因为这些示例旨在在分布式环境中运行(请参阅下面的测试部分)。

历史

  • 版本 0.6.1
    • 清理基于锁的数据结构的 API
    • N 方传播屏障
    • 解决AM可见性问题
    • 更好的错误信息
    • 更新Rofi lamellae以利用rofi v0.3
    • 针对Darcs的各种修复
  • 版本0.6
    • LamellarArrays
      • 额外的迭代器方法
        • 计数
        • 求和
        • reduce
      • 额外的逐元素操作
        • 余数
        • xor
        • shl, shr
      • 后端操作批量改进
      • 可变大小的数组索引
      • GlobalLockArray的初始实现
      • 'ArrayOps'特质以启用用户定义的元素类型
    • AM组 - AM的运行时提供的聚合
      • 通用的'AmGroup'
      • 'TypedAmGroup'
        • 'static'组成员
    • 杂项
      • 添加LAMELLLAR_DEADLOCK_TIMEOUT以帮助处理停滞的应用程序
      • 在检测到恐慌和关键失败时改进错误处理和退出
      • 后端线程改进
      • LamellarEnv特质用于访问有关当前lamellar环境的各种信息
      • 额外的示例
      • 更新文档
  • 版本0.5
    • 大幅改进的文档(即现在有了 ;)
    • 将API“异步化” - 大多数远程操作现在返回Futures
    • LamellarArrays
      • 额外的单侧迭代器、局部迭代器、分布式迭代器
      • 额外的逐元素操作
      • 针对“调度器”的For Each
      • 后端优化
    • AM任务组
    • AM后端更新
    • 用于跟踪的钩子
  • 版本0.4
    • 分布式Arcs(Darcs:分布式原子引用计数对象)
    • LamellarArrays
      • UnsafeArray、AtomicArray、LocalLockArray、ReadOnlyArray、LocalOnlyArray
      • 分布式迭代
      • 局部迭代
    • SHMEM后端
    • 动态内部RDMA内存池
  • 版本0.3.0
    • 递归主动消息
    • 子队支持
    • 支持自定义团队架构(Examples/team_examples/custom_team_arch.rs)
    • LamellarArray的初始支持(基于分布式数组的Am聚合)
    • 与Rofi 0.2集成
    • 重写了示例
  • 版本0.2.2
    • 在readme中提供示例
  • 版本0.2.1
    • 将本地lamellae作为默认lamellae
    • 功能守卫rofi lamellae,以便lamellar可以在没有libfabrics和ROFI的系统上构建
    • 添加了一个用于执行分布式DFT的示例代理应用程序
  • 版本0.2
    • 新的用户界面API
    • 注册了Active Messages(启用稳定的rust)
    • Remote Closures功能受保护,可使用nightly rust
    • 重新设计内部lamellae组织
    • 对world和teams(PE的子组)的初始支持
  • 版本0.1
    • 基本的init/finit功能
    • 远程闭包执行
    • 基本的内存管理(堆和数据段)
    • 基本的远程内存区域支持(put/get)
    • ROFI Lamellae(远程闭包执行、远程内存区域)
    • 套接字Lamellae(远程闭包执行、对远程内存区域的有限支持)
    • 简单的示例

注意

状态

Lamellar仍在开发中,因此并非所有预期的功能都已实现。

联系方式

当前团队成员

Ryan Friese - [email protected]
Roberto Gioiosa - [email protected] Erdal Mutlu - [email protected]
Joseph Cottam - [email protected] Greg Roek - [email protected]

过去团队成员

Mark Raugas - [email protected]

许可

本项目采用BSD许可证 - 有关详细信息,请参阅LICENSE.md文件。

致谢

本工作得到太平洋西北国家实验室(PNNL)高性能数据分析(HPDA)计划的支持,PNNL是Battelle运营的多项目DOE实验室。

依赖项

~10-45MB
~716K SLoC