1 个不稳定版本

0.1.0 2021 年 7 月 25 日

#319科学

MIT 许可协议

89KB
1K SLoC

Node Crunch

License

允许在多个节点间分配计算任务。(关键词:数值计算、科学计算、HPC)

目录

特性

  • 100% 安全的 Rust。
  • 易于使用的 API。
  • 如果一个节点崩溃,服务器和其他节点仍然可以继续工作。(内部使用心跳消息。)
  • 在运行用户应用程序时,可以动态地添加更多节点以加快计算速度。
  • 节点可以是不同操作系统和硬件架构的混合。如果它能够编译,它就可以运行。

注意 1: 它仍在开发中,API 可能会更改。

注意 2: 服务器和节点之间的通信未加密,也没有身份验证,因此仅在受信任的环境中使用!(例如,节点可以通过 SSH 或 VPN 连接到服务器。您必须相应地设置此配置。)

简介

Node Crunch 是一个 crate,允许用户轻松编写分布式代码。代码分为两部分:一部分用于服务器,另一部分用于节点。

这反映在必须实现的两个 trait 上,以便 Node Crunch 能够工作

Overview

  1. NCSever trait。它包含服务器功能。在这里,数据被分割成节点以进行计算,然后从所有节点收集数据。该 trait 有五个必须相应实现的功能

    1.1 initial_data() 当节点首次接触服务器时会被调用。在这里,服务器内部分配一个新的唯一节点ID,并将其与可选的初始数据一起发送回节点。这仅在需要在主计算开始之前将初始数据发送到每个节点时才需要实现。

    1.2 prepare_data_for_node() 初始化节点后,节点将再次联系服务器以获取一些需要处理的数据。必须实现此方法以准备将发送到节点的数据。

    1.3 process_data_from_node() 当节点完成数据处理后,它将结果发送回服务器。必须由服务器代码实现此方法。

    1.4 heartbeat_timeout() 每个节点都会向服务器发送一个心跳消息。如果一个节点在发送此心跳消息失败时,则会调用此方法。必须在服务器代码中实现此方法,并且在这里应该将节点标记为离线。

    1.5 finish_job() 所有数据都已处理完毕后,将调用此方法。例如,可以在此方法中实现将所有结果写入磁盘等。

  2. NCNode 特性。这包含每个节点的功能。在这里,大部分处理由节点代码完成。特性有两个必须相应实现的方法。

    2.1 set_initial_data() 当节点首次接触服务器时,将新的节点ID和一些可选的初始数据一起提供给节点。只有在需要一些初始数据时才需要实现此方法。

    2.2 process_data_from_server() 在这里,主要的处理由节点代码完成。此方法接收需要处理的数据,并返回处理后的数据。

启动节点和服务器

Overview

节点从服务器接收数据并执行计算,然后将结果发送回服务器

Overview

所有计算完成后,服务器将退出

Overview

节点向服务器发送心跳消息

Overview

将为用户定义的数据结构实现这两个特性,这些数据结构被传递给服务器和节点启动器。

服务器模板

以下是服务器的简单模板

struct MyServer {
    // ...
}

impl NCServer for MyStruct {
    // The method initial_data() doesn't have to be implemented if no initial data is needed.

    fn prepare_data_for_node(&mut self, node_id: NodeID) -> Result<NCJobStatus, NCError> {
        // ...
    }

    fn process_data_from_node(&mut self, node_id: NodeID, node_data: &[u8]) -> Result<(), NCError> {
        // ...
    }

    fn heartbeat_timeout(&mut self, nodes: Vec<NodeID>) {
        // ...
    }

    fn finish_job(&mut self) {
        // ...
    }
}

let server = MyServer {
    // ...
}

// The configuration is shared between server and node.
let configuration = NCConfiguration {
    // ...
    ..Default::default()
};

let mut server_starter = NCServerStarter::new(configuration);

match server_starter.start(server) {
    Ok(_) => {
        info!("Calculation finished");
    }
    Err(e) => {
        error!("An error occurred: {}", e);
    }
}

节点模板

以下是节点的简单模板

struct MyNode {
    // ...
}

impl NCNode for MyNode {
    // The method set_initial_data() doesn't have to be implemented if no initial data is needed.

    fn process_data_from_server(&mut self, data: &[u8]) -> Result<Vec<u8>, NCError> {
        // ...
    }
}

// The configuration is shared between server and node.
let configuration = NCConfiguration {
    // ...
    ..Default::default()
};

let node = MyNode{};
let mut node_starter = NCNodeStarter::new(configuration);

match node_starter.start(node) {
    Ok(_) => {
        info!("Calculation finished");
    }
    Err(e) => {
        error!("An error occurred: {}", e);
    }
}

如何启动应用程序

手动启动

通常有一个二进制文件用于服务器和节点代码。您只需指定使用哪种模式,例如通过命令行。例如,我们使用“-s”开关来指定服务器模式。由于只有一个服务器和许多节点,因此节点模式应该是默认模式。

./myapp -s & # option "-s" means run in server mode.

./myapp & # start one task in node mode, this is used more often so no need to specify this mode.

./myapp & # start another task in node mode.

./myapp --ip ip_of_server & # from a different computer.

在主函数中进行简单的检查即可

fn main() {
    // Read settings from command line or configuration file
    let options = get_option();

    if options.server {
        run_server(options);
    } else {
        run_node(options);
    }
}

使用 SLURM / sbatch

如果您使用的是HPC(高性能集群),则将通过作业调度器运行新的作业。最常用的一种(至少在TOP500列表中)是SLURM(简单的Linux资源管理工具)。

首先在一台已知IP地址的计算机上启动服务器(让我们称其为ip_of_server

./myapp -s & # again here "-s" means run in server mode

然后确保二进制文件在所有计算节点的同一文件夹/路径中可用。现在您可以使用作业调度器启动多个作业,在这种情况下启动8个作业

for i in {1..8}; do sbatch run_single.sbatch; done

批处理文件“run_single.sbatch”可能看起来像这样,我们使用命令行选项“--ip”来指定服务器的IP地址(上面的ip_of_server

#!/bin/bash -l
## Example run script for Node Crunch with SLURM

## General configuration options
#SBATCH -J Node_Crunch
#SBATCH -o node_crunch.%j.%N.out
#SBATCH -e node_crunch.%j.%N_Err.out
#SBATCH [email protected]
#SBATCH --mail-type=ALL

## Machine and CPU configuration
## Number of tasks per job:
#SBATCH -n 1
## Number of nodes:
#SBATCH -N 1
## How long will this job run at maximum, here for example 8 hours:
#SBATCH --time=08:00:00
## How much memory will this job consume at maximum, here for example 128 MB
#SLURM --mem-per-cpu=128

# Ensure that all the binaries are available on all the cluster nodes at the same place.
# Usually this is done in the cluster setup via NFS or some other distributed
# filesystem already.

# change this to the actual ip address of the system where the server is running.
myapp --ip ip_of_server

现在让我们假设您的代码运行良好,但完成它需要很长时间。您应该怎么办?您可以再次登录到同一个集群(或不同的集群),并启动更多节点以帮助加速计算

for i in {1..32}; do sbatch run_single.sbatch; done

这次我们刚刚启动了额外的32个任务,因此总共有8 + 32 = 40个任务。

使用 PBS / Torque / qsub

另一种常用的作业调度程序是PBS(可移植批处理系统)或Torque。同样,请确保二进制文件在所有计算节点上可用。然后您可以启动多个作业(在这个例子中是8个)

for i in {1..8}; do qsub run_single.qsub; done

这里文件"run_single.qsub"可能看起来像这样

#!/bin/bash -l
## Example run script for Node Crunch with SLURM
#PBS -N Node_Crunch
#PBS -o ${PBS_JOBNAME}.out_${PBS_JOBID}
#PBS -j oe
## Mailing information a(bort),b(egin),e(nd)
#PBS -m abe
#PBS -M [email protected]

## Machine and CPU configuration
## Number of tasks per job:
#PBS -l nodes=1:ppn=1
## How long will this job run at maximum, here for example 8 hours:
#PBS -l walltime=8:00:00
## How much memory will this job consume at maximum, here for example 128 MB
#PBS -l pmem=128mb

# Ensure that all the binaries are available on all the cluster nodes at the same place.
# Usually this is done in the cluster setup via NFS or some other distributed
# filesystem already.

# change this to the actual ip address of the system where the server is running.
myapp --ip ip_of_server

同样,您可以在任何时候启动额外的作业来加速计算

完整的工作示例

使用这两个特性一开始看起来很复杂,但有一些示例展示了如何在“现实世界”应用中使用它

x 的比较?

MPI

MPI(消息传递接口)是分布式计算的金标准。它经过实战测试,高度优化,并支持C、C++和Fortran。还有其他编程语言的绑定。Node Crunch 主要与 Rust 一起使用,仍在开发中。它可以调用 C / C++ / Fortran 代码,请参阅 Fortran 示例。编写正确的 MPI 代码很困难,如果其中一个节点崩溃,所有其他节点(包括主节点)都将终止。另一方面,Node Crunch 不在乎其中一个节点是否崩溃。心跳系统会检测到其中一个节点不再可用,服务器将继续使用其他节点。Rust 使得编写正确的代码变得非常容易,用户不需要担心同步和其他事情,可以完全专注于计算部分。在 MPI 中,节点数量在整个应用开始后保持不变,而 Node Crunch 在运行时可以根据需要添加更多节点。使用 MPI 时,当您想在集群上运行应用程序时,您对 CPU / 架构 / 操作系统的选择有限。与 Node Crunch 不同,您可以根据需要混合不同的 OS、CPUs 和作业调度程序。您只需为每个想要使用的 OS / 硬件编译节点代码即可。

BOINC

BOINC(伯克利开放网络计算基础设施)也用于分布式计算,经过充分测试且性能出色。科学家们使用它通过提供可以在普通 PC、Mac 等上作为屏幕保护程序运行的桌面客户端来大规模扩展他们的计算。它利用了计算机当前未使用的资源进行数值计算。最著名(也是第一个)的应用程序是 SETI@home 客户端。Node Crunch 小得多,但设置起来也容易得多 ;-)

待办事项

您可能已经注意到,传递的数据是以 &[u8]Vec<u8> 的形式传递的。在将来,这将是对应于用户定义数据结构的泛型参数。目前,用户必须对传递到和从特征方法返回的所有数据进行反序列化和序列化。但在 nc_utils 模块中提供了辅助函数。

下一个版本需要完成的其他事情

  • 当前服务器与节点之间的通信未压缩、未加密且没有认证。也许我会切换到一个可以完成这些功能的小型Web框架,例如warp。Web框架可能有些过度,但为服务器添加一个不错的Web GUI会非常简单。还可以了解一下QUIC协议。

  • 该包不使用任何异步代码(tokioasync-std等)。计算通常比I/O更受CPU限制,但总有例外。因此,目前使用的基于线程的策略可能在某些情况下是限制因素。

  • 需要提供更多的辅助函数和数据结构。当有更多用例时,它们会出现,如果它们有意义,我会很高兴将它们包含在内。

  • 当然,重构和添加更多更好的文档总是好的;-)

常见问题解答

  • “Node Crunch”这个名字是从哪里来的?是“计算节点”和“数值压榨”。
  • 你会添加功能x吗?这取决于它是否有意义并且能帮助其他用户。
  • 我可以在Node Crunch中使用Rayon和/或GPGPU吗?当然可以,没问题。看看使用Rayon的mandel2示例(TODO:添加GPU示例)。
  • 我可以用Node Crunch使用我的C/C++/Fortran/...代码吗?从理论上讲可以,但你需要做一些工作(TODO:添加示例)。

许可协议

此软件包采用MIT许可证。

依赖项

~1.8–2.7MB
~52K SLoC