对多核/多线程架构的支持

目录

定义

主板
也称为母板。
LDom
局部性域或 NUMA 域。可能等同于主板或插槽。
插槽/核心/线程
图 1 说明了插槽、核心和线程的概念,如 Slurm 的多核/多线程支持文档中所定义。
CPU
根据系统配置,这可以是核心或线程。

图 1:插槽、核心和线程的定义
亲和性
绑定到特定逻辑处理器的状态。
亲和性掩码
一个位掩码,其中索引对应于逻辑处理器。 最低有效位对应于系统上的第一个逻辑处理器编号,而最高有效位对应于系统上的最后一个逻辑处理器编号。 在给定位置的 '1' 表示进程可以在相关的逻辑处理器上运行。
Fat Masks
具有多个位设置的亲和性掩码,允许进程在多个逻辑处理器上运行。

srun 标志概述

定义了许多标志,以便用户能够更好地利用此架构,通过明确指定其应用程序所需的插槽、核心和线程数量。表 1 总结了这些选项。

低级(显式绑定)
--cpu-bind=... 显式进程亲和性绑定和控制选项
高级(自动掩码生成)
--sockets-per-node=S 分配给作业的节点中的插槽数量(最小)
--cores-per-socket=C 分配给作业的插槽中的核心数量(最小)
--threads-per-core=T 分配给作业的核心中的线程最小数量。在任务布局中,使用指定的每核心最大线程数。
-B S[:C[:T]] --sockets-per-node、--cores-per_cpu、--threads-per_core 的组合快捷选项
任务分配选项
-m / --distribution 分配方式:任意 | 块 | 循环 | plane=x | [block|cyclic]:[block|cyclic|fcyclic]
内存作为可消耗资源
--mem=mem 作业所需的每个节点的实际内存量。
--mem-per-cpu=mem 作业所需的每个分配 CPU 的实际内存量。
任务调用控制
--cpus-per-task=CPUs 每个任务所需的 CPU 数量
--ntasks-per-node=ntasks 每个节点上调用的任务数量 --ntasks-per-socket=ntasks 每个插槽上调用的任务数量 --ntasks-per-core=ntasks 每个核心上调用的任务数量 --overcommit 允许每个 CPU 超过一个任务
应用提示
--hint=compute_bound 使用每个插槽中的所有核心
--hint=memory_bound 每个插槽中仅使用一个核心 --hint=[no]multithread [不]使用具有内核多线程的额外线程
保留给系统使用的资源
--core-spec=cores 为系统使用保留的核心数量
--thread-spec=threads 为系统使用保留的线程数量

表 1:支持多核/多线程环境的 srun 标志

重要的是要注意,许多这些标志只有在进程与特定 CPU 具有某种亲和性时才有意义(可选地)内存。 不一致的选项通常会导致错误。 任务亲和性通过 slurm.conf 文件中的 TaskPlugin 参数进行配置。 根据系统架构和可用软件,TaskPlugin 有几种选项,除了 "task/none" 之外的任何选项都将任务绑定到 CPU。 如果通过configurator.html 生成 slurm.conf,请参见 "任务启动" 部分。

低级 --cpu-bind=... - 显式绑定接口

以下 srun 标志提供了一个低级核心绑定接口:

--cpu-bind=        绑定任务到 CPU
    q[uiet]         在任务运行之前静默绑定(默认)
    v[erbose]       在任务运行之前详细报告绑定
    no[ne]          不将任务绑定到 CPU(默认)
    rank            按任务排名绑定
    map_cpu:<list>  为每个任务指定 CPU ID 绑定
                    其中 <list><cpuid1>,<cpuid2>,...<cpuidN>
    mask_cpu:<list> 为每个任务指定 CPU ID 绑定掩码
                    其中 <list><mask1>,<mask2>,...<maskN>
    rank_ldom       按排名将任务绑定到 NUMA
                    局部性域中的 CPU
    map_ldom:<list> 为每个任务指定 NUMA 局部性域 ID
                    其中 <list><ldom1>,<ldom2>,...<ldomN>
    rank_ldom       按排名将任务绑定到 NUMA
                    局部性域中的 CPU,其中 <list><ldom1>,<ldom2>,...<ldomN>
    mask_ldom:<list> 为每个任务指定 NUMA 局部性域 ID 掩码
                    其中 <list><ldom1>,<ldom2>,...<ldomN>
    ldoms           自动生成的掩码绑定到 NUMA 局部性
                    域
    sockets         自动生成的掩码绑定到插槽
    cores           自动生成的掩码绑定到核心
    threads         自动生成的掩码绑定到线程
    help            显示此帮助信息

亲和性可以设置为特定的逻辑处理器(插槽、核心、线程)或比逻辑处理器的最低级别更粗糙的粒度(核心或线程)。 在后者的情况下,允许进程在特定插槽或核心内利用多个处理器。

示例:

    • srun -n 8 -N 4 --cpu-bind=mask_cpu:0x1,0x4 a.out
    • srun -n 8 -N 4 --cpu-bind=mask_cpu:0x3,0xD a.out

另请参见 'srun --cpu-bind=help' 和 'man srun'

高级 -B S[:C[:T]] - 自动掩码生成接口

我们已更新节点选择基础设施,允许以更细粒度选择逻辑处理器。用户可以请求特定数量的节点、插槽、核心和线程:

-B, --extra-node-info=S[:C[:T]]            扩展为:
  --sockets-per-node=S   每个节点分配的插槽数量
  --cores-per-socket=C   每个插槽分配的核心数量
  --threads-per-core=T   每个核心分配的线程数量
                每个字段可以是 'min' 或通配符 '*'

     请求的总 CPU = (节点) x (S x C x T)

示例:

    • srun -n 8 -N 4 -B 2:1 a.out
    • srun -n 8 -N 4 -B 2 a.out
      注意:将上述与之前相应的 --cpu-bind=... 示例进行比较
    • srun -n 16 -N 4 a.out
    • srun -n 16 -N 4 -B 2:2:1 a.out
    • srun -n 16 -N 4 -B 2:2:1 a.out
            或
    • srun -n 16 -N 4 --sockets-per-node=2 --cores-per-socket=2 --threads-per-core=1 a.out
    • srun -n 16 -N 2-4 -B '1:*:1' a.out
    • srun -n 16 -N 4-2 -B '2:*:1' a.out
    • srun -n 16 -N 4-4 -B '1:1' a.out

注意:

  • 在命令行中添加 --cpu-bind=no 将导致进程不绑定到逻辑处理器。
  • 在命令行中添加 --cpu-bind=verbose(或将 CPU_BIND 环境变量设置为 "verbose")将导致每个任务报告正在使用的亲和性掩码
  • 当使用 -B 时,绑定默认开启。在多核/多线程系统上的默认绑定相当于 -B 选项中列举的资源级别。

另请参见 'srun --help' 和 'man srun'

任务分配选项:对 -m / --distribution 的扩展

-m / --distribution 选项用于在节点之间分配进程,已扩展为描述逻辑处理器最低级别内的分配。 可用的分配包括:
任意 | 块 | 循环 | plane=x | [block|cyclic]:[block|cyclic|fcyclic]

平面分配 (plane=x) 导致块:循环的分配,块大小等于 x。 在以下内容中,我们使用 "逻辑处理器的最低级别" 来描述插槽、核心或线程,具体取决于架构。 该分配将集群划分为平面(包括每个节点上最低级别逻辑处理器的数量),然后首先在每个平面内调度,然后跨平面调度。

对于二维分配([block|cyclic]:[block|cyclic|fcyclic]),第二个分配(在 ":" 之后)允许用户为节点内的进程指定分配方法,并适用于最低级别的逻辑处理器(插槽、核心或线程,具体取决于架构)。 当任务需要多个 CPU 时,cyclic 将作为一组分配所有这些 CPU(即如果可能的话在同一个插槽内),而 fcyclic 将以循环方式分配每个 CPU 跨插槽。

当使用高级标志时,绑定会自动启用,只要任务/亲和性插件已启用。要在作业级别禁用绑定,请使用 --cpu-bind=no。

分配标志可以与其他开关组合:

    • srun -n 16 -N 4 -B '2:*:1' -m block:cyclic --cpu-bind=socket a.out
    • srun -n 16 -N 4 -B '2:*:1' -m plane=2 --cpu-bind=core a.out
    • srun -n 16 -N 4 -B '2:*:1' -m plane=2 a.out

在多核/多线程系统上的默认分配相当于 -m block:cyclic 和 --cpu-bind=thread。

另请参见 'srun --help'

内存作为可消耗资源

--mem 标志指定作业每个节点所需的最大内存量(以 MB 为单位)。此标志用于支持内存作为可消耗资源的分配策略。

--mem=MB      每个节点所需的最大实际内存量
              由作业要求。

此标志允许调度程序在特定节点上共同分配作业,前提是它们的总内存需求不超过节点上的总内存量。

为了将内存用作可消耗资源,必须首先在 slurm.conf 中启用 select/cons_tres 插件:

SelectType=select/cons_tres     # 启用可消耗资源
SelectTypeParameters=CR_Memory  # 内存作为可消耗资源

将内存作为可消耗资源通常与 CPU、插槽或核心可消耗资源结合使用,使用 SelectTypeParameters 值:CR_CPU_Memory、CR_Socket_Memory 或 CR_Core_Memory

如果通过 configurator.html 生成 slurm.conf,请参见 "资源选择" 部分。

另请参见 'srun --help' 和 'man srun'

任务调用作为逻辑处理器的函数

--ntasks-per-{node,socket,core}=ntasks 标志允许用户请求在每个节点、插槽或核心上调用不超过 ntasks 的任务。 这类似于使用 --cpus-per-task=ncpus,但不需要知道每个节点上实际的 CPU 数量。在某些情况下,能够请求在每个节点、插槽或核心上调用不超过特定数量的 ntasks 更为方便。这包括提交一个应用程序,其中只有一个 "任务/排名" 应分配给每个节点,同时允许作业利用节点中存在的所有并行性,或者将单个设置/清理/监控作业提交到预先存在的分配的每个节点,作为更大作业脚本中的一步。 现在可以通过以下标志指定:

--ntasks-per-node=n    每个节点上调用的任务数量
--ntasks-per-socket=n  每个插槽上调用的任务数量
--ntasks-per-core=n    每个核心上调用的任务数量

例如,给定一个包含两个插槽的节点的集群,每个插槽包含两个核心,以下命令说明了这些标志的行为:

% srun -n 4 hostname
hydra12
hydra12
hydra12
hydra12
% srun -n 4 --ntasks-per-node=1 hostname
hydra12
hydra13
hydra14
hydra15
% srun -n 4 --ntasks-per-node=2 hostname
hydra12
hydra12
hydra13
hydra13
% srun -n 4 --ntasks-per-socket=1 hostname
hydra12
hydra12
hydra13
hydra13
% srun -n 4 --ntasks-per-core=1 hostname
hydra12
hydra12
hydra12
hydra12

另请参见 'srun --help' 和 'man srun'

应用提示

不同的应用程序将具有不同级别的资源需求。一些应用程序往往是计算密集型的,但几乎不需要进程间通信。一些应用程序将是内存绑定的,在耗尽计算能力之前饱和处理器的内存带宽。其他应用程序将高度通信密集,导致进程在等待来自其他进程的消息时阻塞。具有这些不同属性的应用程序在多核系统上运行良好,只要映射正确。

对于计算密集型应用程序,通常会使用多核系统中的所有核心。对于内存绑定的应用程序,仅在每个插槽中使用单个核心将导致每个核心的内存带宽最高。对于通信密集型应用程序,使用内核内多线程(例如超线程、SMT 或 TMT)也可能提高性能。 以下命令行标志可用于将这些类型的应用提示传达给 Slurm 多核支持:

--hint=             根据应用提示绑定任务
    compute_bound   使用每个插槽中的所有核心
    memory_bound    每个插槽中仅使用一个核心
    [no]multithread [不]使用具有内核多线程的额外线程
    help            显示此帮助信息

例如,给定一个包含两个插槽的节点的集群,每个插槽包含两个核心,以下命令说明了这些标志的行为。在详细的 --cpu-bind 输出中,任务被描述为 'hostname, task Global_ID Local_ID [PID]':

% srun -n 4 --hint=compute_bound --cpu-bind=verbose sleep 1
cpu-bind=MASK - hydra12, task  0  0 [15425]: mask 0x1 set
cpu-bind=MASK - hydra12, task  1  1 [15426]: mask 0x4 set
cpu-bind=MASK - hydra12, task  2  2 [15427]: mask 0x2 set
cpu-bind=MASK - hydra12, task  3  3 [15428]: mask 0x8 set

% srun -n 4 --hint=memory_bound --cpu-bind=verbose sleep 1
cpu-bind=MASK - hydra12, task  0  0 [15550]: mask 0x1 set
cpu-bind=MASK - hydra12, task  1  1 [15551]: mask 0x4 set
cpu-bind=MASK - hydra13, task  2  0 [14974]: mask 0x1 set
cpu-bind=MASK - hydra13, task  3  1 [14975]: mask 0x4 set

另请参见 'srun --hint=help' 和 'man srun'

高层 srun 标志背后的动机

允许用户使用更高层次的 srun 标志而不是 --cpu-bind 的动机在于后者可能难以使用。提出的高层标志比 --cpu-bind 更易于使用,因为:

  • 使用高层标志时,亲和性掩码生成会自动发生。
  • --cpu-bind 标志的长度和复杂性与 -B 和 --distribution 标志组合的长度相比,使高层标志更易于使用。

如下面的示例所示,使用高层标志指定不同布局要简单得多,因为用户不必重新计算掩码或 CPU ID。这种方法比重新排列掩码或映射简单得多。

给定一个 32 进程的作业和一个四节点、双插槽、双核心的集群,我们希望在四个节点之间使用块分配,然后在节点内跨物理处理器使用循环分配。下面我们展示如何使用 1) 高层标志和 2) --cpubind 获得所需的布局。

高层标志

使用 Slurm 的高层标志,用户可以通过以下任一提交获得上述布局,因为 --distribution=block:cyclic 是默认的分配方法。

$ srun -n 32 -N 4 -B 4:2 --distribution=block:cyclic a.out
      或
$ srun -n 32 -N 4 -B 4:2 a.out

核心显示为 c0 和 c1,处理器显示为 p0 到 p3。结果任务 ID 为:

c0c1
p0 0 4
p2 2 6
c0c1
p1 1 5
p3 3 7

任务 ID 的计算和分配对用户是透明的。用户不必担心核心编号(第节 将进程固定到核心)或设置任何 CPU 亲和性。默认情况下,使用多核支持标志时将设置 CPU 亲和性。

低级标志 --cpu-bind

使用 Slurm 的 --cpu-bind 标志,用户必须计算 CPU ID 或掩码,并确保他们理解系统上的核心编号。当所有节点的核心编号不同时,会出现另一个问题。 --cpu-bind 选项仅允许用户为所有节点指定单个掩码。使用 Slurm 高层标志消除了这一限制,因为 Slurm 将为每个请求的节点正确生成适当的掩码。

在一个四个双插槽双核心节点集群中,核心块编号

核心显示为 c0 和 c1,处理器显示为 p0 到 p3。块编号中节点内的 CPU ID 为: (此信息可从系统上的 /proc/cpuinfo 文件中获得)

c0c1
p0 0 1
p2 4 5
c0c1
p1 2 3
p3 6 7

 导致用户需要计算的处理器/核心和任务 ID 的以下映射:

处理器/核心的映射
c0c1
p0 0x01 0x02
p2 0x10 0x20
c0c1
p1 0x04 0x08
p3 0x40 0x80

任务 ID
c0c1
p0 0 4
p2 2 6
c0c1
p1 1 5
p3 3 7

上述映射和任务 ID 可以转换为以下命令:

$ srun -n 32 -N 4 --cpu-bind=mask_cpu:1,4,10,40,2,8,20,80 a.out
      或
$ srun -n 32 -N 4 --cpu-bind=map_cpu:0,2,4,6,1,3,5,7 a.out

同一集群,但其核心编号为循环而不是块

在一个核心编号为循环的系统中,srun 命令的正确掩码参数看起来像:(这将在具有核心块编号的系统上实现与上述命令相同的布局。)

$ srun -n 32 -N 4 --cpu-bind=map_cpu:0,1,2,3,4,5,6,7 a.out

在具有循环核心编号的系统上使用块 map_cpu

如果用户在指定 map_cpu 列表之前未检查其系统的核心编号,从而未意识到系统具有循环核心编号而不是块编号,则他们将无法获得预期的布局。例如,如果他们决定重用上述命令:

$ srun -n 32 -N 4 --cpu-bind=map_cpu:0,2,4,6,1,3,5,7 a.out

他们会得到以下意外的任务 ID 布局:

c0c1
p0 0 2
p2 1 3
c0c1
p1 4 6
p3 5 7

因为在循环编号中节点内的处理器 ID 为:

c0c1
p0 0 4
p2 2 6
c0c1
p1 1 5
p3 3 7

重要的结论是,使用 --cpu-bind 标志并不简单,并且假设用户是专家。

对 sinfo/squeue/scontrol 的扩展

还对其他 Slurm 工具进行了若干扩展,以便更轻松地与多核/多线程系统进行交互。

sinfo

sinfo 节点列表的长版本 (-l) 已扩展为显示每个节点的插槽、核心和线程。例如:

% sinfo -N
NODELIST     NODES PARTITION STATE
hydra[12-15]     4    parts* idle

% sinfo -lN
Thu Sep 14 17:47:13 2006
NODELIST     NODES PARTITION       STATE CPUS    S:C:T MEMORY TMP_DISK WEIGHT FEATURES REASON
hydra[12-15]     4    parts*        idle   8+ 2+:4+:1+   2007    41447      1   (null) none

% sinfo -lNe
Thu Sep 14 17:47:18 2006
NODELIST     NODES PARTITION       STATE CPUS    S:C:T MEMORY TMP_DISK WEIGHT FEATURES REASON

hydra[12-14]     3    parts*        idle    8    2:4:1   2007    41447      1   (null) none
hydra15          1    parts*        idle   64    8:4:2   2007    41447      1   (null) none

对于用户指定的输出格式 (-o/--format) 和排序 (-S/--sort),以下标识符可用:

%X  每个节点的插槽数量
%Y  每个插槽的核心数量
%Z  每个核心的线程数量
%z  扩展处理器信息:每个节点的插槽、核心、线程数量(S:C:T)

例如:

% sinfo -o '%9P %4c %8z %8X %8Y %8Z'
PARTITION CPUS S:C:T    SOCKETS  CORES    THREADS
parts*    4    2:2:1    2        2        1

另请参见 'sinfo --help' 和 'man sinfo'

squeue

对于用户指定的输出格式 (-o/--format) 和排序 (-S/--sort),以下标识符可用:

%m  作业请求的内存大小(以 MB 为单位)
%H  每个节点请求的插槽数量
%I  每个插槽请求的核心数量
%J  每个核心请求的线程数量
%z  扩展处理器信息:每个节点请求的插槽、核心、线程数量(S:C:T)

以下是运行 7 个副本后的示例 squeue 输出:

      % srun -n 4 -B 2:2:1 --mem=1024 sleep 100 &
% squeue -o '%.5i %.2t %.4M %.5D %7H %6I %7J %6z %R'
JOBID ST TIME NODES SOCKETS CORES THREADS S:C:T NODELIST(REASON)
   17 PD 0:00     1 2       2     1       2:2:1 (资源)
   18 PD 0:00     1 2       2     1       2:2:1 (资源)
   19 PD 0:00     1 2       2     1       2:2:1 (资源)
   13  R 1:27     1 2       2     1       2:2:1 hydra12
   14  R 1:26     1 2       2     1       2:2:1 hydra13
   15  R 1:26     1 2       2     1       2:2:1 hydra14
   16  R 1:26     1 2       2     1       2:2:1 hydra15

squeue 命令还可以显示作业的内存大小,例如:

% sbatch --mem=123 tmp
提交批处理作业 24

$ squeue -o "%.5i %.2t %.4M %.5D %m"
JOBID ST TIME NODES MIN_MEMORY
  24   R 0:05     1 123

另请参见 'squeue --help' 和 'man squeue'

scontrol

可以使用 scontrol 调整以下作业设置:

请求的分配:
  ReqSockets=<count>  设置作业所需的插槽数量
  ReqCores=<count>    设置作业所需的核心数量
  ReqThreads=<count>  设置作业所需的线程数量

例如:

# scontrol update JobID=17 ReqThreads=2
# scontrol update JobID=18 ReqCores=4
# scontrol update JobID=19 ReqSockets=8

% squeue -o '%.5i %.2t %.4M %.5D %9c %7H %6I %8J'
JOBID ST TIME NODES MIN_PROCS SOCKETS CORES THREADS
   17 PD 0:00     1 1         4       2     1
   18 PD 0:00     1 1         8       4     2
   19 PD 0:00     1 1         4       2     1
   13  R 1:35     1 0         0       0     0
   14  R 1:34     1 0         0       0     0
   15  R 1:34     1 0         0       0     0
   16  R 1:34     1 0         0       0

'scontrol show job' 命令可用于显示每个节点分配的 CPU 数量以及请求和约束中指定的插槽、核心和线程。

% srun -N 2 -B 2:1 sleep 100 &
% scontrol show job 20
JobId=20 UserId=(30352) GroupId=users(1051)
   Name=sleep
   Priority=4294901749 Partition=parts BatchFlag=0
   AllocNode:Sid=hydra16:3892 TimeLimit=UNLIMITED
   JobState=RUNNING StartTime=09/25-17:17:30 EndTime=NONE
   NodeList=hydra[12-14] NodeListIndices=0,2,-1
   AllocCPUs=1,2,1
   NumCPUs=4 ReqNodes=2 ReqS:C:T=2:1:*
   OverSubscribe=0 Contiguous=0 CPUs/task=0
   MinCPUs=0 MinMemory=0 MinTmpDisk=0 Features=(null)
   Dependency=0 Account=(null) Reason=None Network=(null)
   ReqNodeList=(null) ReqNodeListIndices=-1
   ExcNodeList=(null) ExcNodeListIndices=-1
   SubmitTime=09/25-17:17:30 SuspendTime=None PreSusTime=0

另请参见 'scontrol --help' 和 'man scontrol'

slurm.conf 中的配置设置

有几个 slurm.conf 设置可用于控制上述多核功能。

除了下面的描述外,如果通过 configurator.html 生成 slurm.conf,请参见 "任务启动" 和 "资源选择" 部分。

如前所述,为了设置亲和性,必须首先在 slurm.conf 中启用任务/亲和性插件:

TaskPlugin=task/affinity          # 启用任务亲和性

此设置是任务启动特定参数的一部分:

# o 定义任务启动特定参数
#
#    "TaskProlog" : 定义每个任务开始执行之前作为用户执行的程序。
#    "TaskEpilog" : 定义每个任务终止后作为用户执行的程序。
#    "TaskPlugin" : 定义任务启动插件。这可以用于提供节点内的资源管理(例如,将任务固定到特定处理器)。允许的值为:
#      "task/affinity" : CPU 亲和性支持
#      "task/cgroup"   : 使用 Linux cgroup 将任务绑定到资源
#      "task/none"     : 无任务启动操作,默认值
#
# 示例:
#
# TaskProlog=/usr/local/slurm/etc/task_prolog # 默认值为无
# TaskEpilog=/usr/local/slurm/etc/task_epilog # 默认值为无
# TaskPlugin=task/affinity                    # 默认值为 task/none

在 slurm.conf 中声明节点硬件配置:

NodeName=dualcore[01-16] CoresPerSocket=2 ThreadsPerCore=1

有关各种节点配置选项的更完整描述,请参见 slurm.conf 手册页。

最后修改于 2023 年 5 月 19 日