mod_phys_lmdz_mpi_transfert MPI transfer 核心

输入范围

LMDZ.COMMON-6.3\LMDZ.COMMON\libf\phy_common\mod_phys_lmdz_mpi_transfert.F90

本页范围

本页展开 mod_phys_lmdz_mpi_transfert.F90(1953 个物理行,END MODULE 在第 1952 行)的全部 8 个泛型接口、94 个 specific procedures 和 18 个内部引擎子程序。mpi-data-transfer 是组页概览;phys-transfer-para-wrapper 覆盖上层合并层;本页专注 MPI 底层实现细节。

模块结构

MODULE mod_phys_lmdz_mpi_transfert
CONTAINS
  ! 94 specific procedures + 18 _gen engine subroutines
END MODULE

模块不 USE 其他 transfer 模块。每个 specific procedure 按需 USE mod_phys_lmdz_mpi_datais_mpi_root/is_using_mpi/mpi_master/COMM_LMDZ_PHY 等)或 USE mod_grid_phy_lmdzklon_glo/klon_mpi/nbp_lon/jj_nb/ii_begin/is_north_pole_dyn/is_south_pole_dyn)。

8 个泛型接口总览

接口 过程数 类型 最大维度 MPI 调用 引擎
bcast_mpi 22 c, i/i1-i6, r/r1-r6, l/l1-l6 6D MPI_BCAST cgen/igen/rgen/lgen
scatter_mpi 12 i/i1-i3, r/r1-r3, l/l1-l3 4D MPI_SCATTERV igen/rgen/lgen
gather_mpi 12 i/i1-i3, r/r1-r3, l/l1-l3 4D MPI_GATHERV igen/rgen/lgen
scatter2D_mpi 12 i/i1-i3, r/r1-r3, l/l1-l3 5D→4D 组合
gather2D_mpi 12 i/i1-i3, r/r1-r3, l/l1-l3 4D→5D 组合
reduce_sum_mpi 10 i/i1-i4, r/r1-r4 4D MPI_REDUCE igen/rgen
grid1dTo2d_mpi 12 i/i1-i3, r/r1-r3, l/l1-l3 5D 无(纯本地) igen/rgen/lgen
grid2dTo1d_mpi 12 i/i1-i3, r/r1-r3, l/l1-l3 5D 无(纯本地) igen/rgen/lgen

总计 94 个 specific procedures + 18 个 _gen 引擎子程序

类型覆盖差异

维度后缀约定

_i = scalar integer,_i1 = 1D,_i2 = 2D,_i3 = 3D。bcast 额外支持 _i4/_i5/_i6(到 6D)。reduce_sum 额外支持 _i4(到 4D)。_c 仅 character scalar。

bcast_mpi 实现

标量包装模式

标量过程(bcast_mpi_i/bcast_mpi_r/bcast_mpi_l)将标量值复制到 var_tmp(1) 后调用引擎,广播后再复制回来:

SUBROUTINE bcast_mpi_i(var)
  USE mod_phys_lmdz_mpi_data, ONLY : is_mpi_root
  INTEGER,INTENT(INOUT) :: Var
  INTEGER               :: var_tmp(1)
  IF (is_mpi_root) var_tmp(1)=var
  CALL bcast_mpi_igen(Var_tmp,1)
  var=var_tmp(1)
END SUBROUTINE

数组过程(bcast_mpi_i1bcast_mpi_i6)直接传 Varsize(Var) 给引擎,利用 Fortran 数组内存连续性。

引擎子程序

SUBROUTINE bcast_mpi_igen(var,nb)
  USE mod_phys_lmdz_mpi_data
#ifdef CPP_MPI
  INCLUDE 'mpif.h'
#endif
  IF (.not.is_using_mpi) RETURN
#ifdef CPP_MPI
  CALL MPI_BCAST(Var,nb,MPI_INTEGER,mpi_master,COMM_LMDZ_PHY,ierr)
#endif
END SUBROUTINE
引擎 MPI datatype 额外说明
bcast_mpi_cgen MPI_CHARACTER 参数 nb=len(Var1)
bcast_mpi_igen MPI_INTEGER
bcast_mpi_rgen MPI_REAL_LMDZ 自定义类型,初始化为 MPI_REAL8
bcast_mpi_lgen MPI_LOGICAL

所有引擎:root = mpi_master,comm = COMM_LMDZ_PHYIF (.not.is_using_mpi) RETURN 快速跳过。

scatter_mpi 实现

引擎结构

scatter 引擎(scatter_mpi_igen/rgen/lgen)三步:

  1. root 打包IF (is_mpi_root) 遍历所有 rank,用 klon_mpi_para_nb/begin/endVarIn(klon_glo,dimsize) 抽取连续块到 VarTmp(dimsize*klon_glo),同时计算 counts(rank)=nb*dimsizedispls(rank)=Index-1
  2. MPI 调用MPI_SCATTERV(VarTmp,counts,displs,MPI_*,VarOut,klon_mpi*dimsize,MPI_*,mpi_master,COMM_LMDZ_PHY,ierr)
  3. 非 MPI 退化IF (.not.is_using_mpi) 在打包前直接 VarOut(:,:)=VarIn(:,:) + RETURN

dimsize 计算

specific procedure 将额外维度展平为 dimsize

后缀 dimsize
_i(scalar) 1
_i1(1D extra) Size(VarOut,2)
_i2(2D extra) Size(VarOut,2)*Size(VarOut,3)
_i3(3D extra) Size(VarOut,2)*Size(VarOut,3)*Size(VarOut,4)

scatter 从 VarOut 额外维度取 dimsize(因为 root 端 VarInklon_glo 首维,各 rank 端 VarOutklon_mpi 首维)。

VarTmp 临时数组

VarTmp 声明为 DIMENSION(dimsize*klon_glo),是栈上自动数组。打包循环按 i=1..dimsize 外层、rank 内层的方式,将全局列连续拷贝到按 rank 排列的 buffer 中。

gather_mpi 实现

引擎结构

gather 引擎(gather_mpi_igen/rgen/lgen)三步,顺序与 scatter 不同:

  1. root 计算 displs/countsIF (is_mpi_root) 预计算 counts/displs(所有 rank 都参与 GATHERV,但只有 root 的 displs/counts 有意义)。
  2. 非 MPI 退化IF (.not.is_using_mpi)VarOut(:,:)=VarIn(:,:) + RETURN。注意 gather_rgen/gather_lgen 中此检查位于 displs 计算之后,与 scatter_*gengather_igen 不同(后者先检查非 MPI 再打包)。
  3. MPI 调用 + root 解包MPI_GATHERV(VarIn,klon_mpi*dimsize,MPI_*,VarTmp,counts,displs,MPI_*,mpi_master,COMM_LMDZ_PHY,ierr),root 再将 VarTmpklon_mpi_para_begin/end 解包到 VarOut(klon_glo,dimsize)

dimsize 计算

gather 从 VarIn 额外维度取 dimsizeSize(VarIn,2) 等),与 scatter 取 VarOut 相反。这是因为 gather 方向是 local→global,VarIn 在各 rank 端有 klon_mpi 首维。

scatter2D_mpi / gather2D_mpi 组合

这两个接口不直接调用 MPI 原语,而是组合 grid 转换和 1D scatter/gather:

scatter2D_mpi 流程

VarIn(nbp_lon*jj_nb, ...)   -- grid2dTo1d_glo -->   Var_tmp(klon_glo, ...)   -- scatter_mpi -->   VarOut(klon_mpi, ...)
  1. 分配栈临时数组 Var_tmp(klon_glo, ...),额外维度从 VarOut 取。
  2. 调用 grid2dTo1d_glo(VarIn, Var_tmp)mod_grid_phy_lmdz),将 2D 经纬网格全局字段转为 1D 列表示。
  3. 调用 scatter_mpi(Var_tmp, VarOut) 分发到各 rank。

gather2D_mpi 流程

VarIn(klon_mpi, ...)   -- gather_mpi -->   Var_tmp(klon_glo, ...)   -- grid1dTo2d_glo -->   VarOut(nbp_lon*jj_nb, ...)
  1. 分配栈临时数组 Var_tmp(klon_glo, ...),额外维度从 VarOut 取(size(VarOut,3) 等)。
  2. 调用 gather_mpi(VarIn, Var_tmp) 收集到 root。
  3. 调用 grid1dTo2d_glo(Var_tmp, VarOut) 将 1D 列转回 2D 经纬网格。

grid1dTo2d_glo/grid2dTo1d_glo 来自 mod_grid_phy_lmdz,操作的是全局列数 klon_glo 和全局 2D 网格 nbp_lon*nbp_lat(注意与 _mpi 版本的区别:_glonbp_lat_mpijj_nb)。

reduce_sum_mpi 实现

标量包装

标量过程(reduce_sum_mpi_i/reduce_sum_mpi_r)使用 VarIn_tmp(1)/VarOut_tmp(1) 包装标量为数组:

SUBROUTINE reduce_sum_mpi_i(VarIn, VarOut)
  INTEGER,INTENT(IN)  :: VarIn
  INTEGER,INTENT(OUT) :: VarOut
  INTEGER             :: VarIn_tmp(1), VarOut_tmp(1)
  VarIn_tmp(1)=VarIn
  CALL reduce_sum_mpi_igen(VarIn_tmp,VarOut_tmp,1)
  VarOut=VarOut_tmp(1)
END SUBROUTINE

引擎

SUBROUTINE reduce_sum_mpi_igen(VarIn,VarOut,nb)
  IF (.not.is_using_mpi) THEN
    VarOut(:)=VarIn(:)
    RETURN
  ENDIF
#ifdef CPP_MPI
  CALL MPI_REDUCE(VarIn,VarOut,nb,MPI_INTEGER,MPI_SUM,mpi_master,COMM_LMDZ_PHY,ierr)
#endif
END SUBROUTINE

grid1dTo2d_mpi / grid2dTo1d_mpi 实现

这两个接口是纯本地网格转换,不涉及 MPI 通信,没有 IF (.not.is_using_mpi) 守卫。

grid1dTo2d_mpi 引擎

将本 rank 的 1D 列数组 VarIn(klon_mpi,dimsize) 映射到局部 2D 块 VarOut(nbp_lon*jj_nb,dimsize)

  1. 极点行清零VarOut(1:nbp_lon,:)=0(北极行),VarOut(nbp_lon*(jj_nb-1)+1:nbp_lon*jj_nb,:)=0(南极行)。logical 引擎用 .FALSE.
  2. 计算偏移offset=ii_begin;若 is_north_pole_dynOffset=nbp_lon(跳过北极复制行)。
  3. 主体复制VarOut(ij+offset-1,i)=VarIn(ij,i)
  4. 北极处理IF (is_north_pole_dyn) → 将 VarIn(1,i) 复制到 VarOut(1:nbp_lon,i)(北极所有经度共享同一值)。
  5. 南极处理IF (is_south_pole_dyn) → 将 VarIn(klon_mpi,i) 复制到 VarOut(nbp_lon*(jj_nb-1)+1:nbp_lon*jj_nb,i)

grid2dTo1d_mpi 引擎

反向操作,将局部 2D 块 VarIn(nbp_lon*jj_nb,dimsize) 提取为 1D 列 VarOut(klon_mpi,dimsize)

  1. 计算偏移offset=ii_begin;若 is_north_pole_dynoffset=nbp_lon
  2. 主体提取VarOut(ij,i)=VarIn(ij+offset-1,i)
  3. 北极处理IF (is_north_pole_dyn)VarOut(1,i)=VarIn(1,i)(从 2D 北极行首元素读取)。

注意:grid2dTo1d_mpi 不处理南极——南极值已在主体提取中通过 offset 正确读取。

_mpi_glo 版本对比

属性 grid1dTo2d_mpi grid1dTo2d_glo(mod_grid_phy_lmdz)
1D 首维 klon_mpi(本 rank) klon_glo(全局)
2D 首维 nbp_lon*jj_nb(本 rank 局部) nbp_lon*nbp_lat(全局)
偏移来源 ii_begin + is_north_pole_dyn 固定 nbp_lon
极点条件 条件(is_north/south_pole_dyn 无条件(总是复制两极)
用途 scatter2D_mpi/gather2D_mpi 组合 全局字段↔︎1D 列转换

CPP_MPI 双重守卫

所有引擎子程序使用编译期 + 运行期双重守卫:

#ifdef CPP_MPI
  INCLUDE 'mpif.h'
#endif
  IF (.not.is_using_mpi) RETURN
#ifdef CPP_MPI
  CALL MPI_BCAST(...)
#endif
场景 CPP_MPI 定义 is_using_mpi 行为
MPI 构建 + MPI 运行 .TRUE. 执行 MPI 调用
MPI 构建 + 单进程 .TRUE. 执行 MPI 调用(mpi_size=1)
非 MPI 构建 .FALSE. 所有引擎空操作或直拷

is_using_mpiinit_phys_lmdz_mpi_data 中根据 CPP_MPI 宏设置:定义时 .TRUE.,否则 .FALSE.

MPI_REAL_LMDZ

INTEGER,SAVE :: MPI_REAL_LMDZ   ! MPI_REAL8

init_phys_lmdz_mpi_data 中初始化为 MPI_REAL8。所有 real 类型的 MPI 调用使用 MPI_REAL_LMDZ 而非直接 MPI_REALMPI_DOUBLE_PRECISION,提供精度抽象层。若需切换单/双精度,只需修改此处赋值。

依赖数据汇总

来源模块 符号 用途
mod_phys_lmdz_mpi_data is_using_mpi 运行时 MPI 开关
mod_phys_lmdz_mpi_data is_mpi_root root 判断
mod_phys_lmdz_mpi_data mpi_master MPI root rank
mod_phys_lmdz_mpi_data COMM_LMDZ_PHY 物理侧 communicator
mod_phys_lmdz_mpi_data MPI_REAL_LMDZ real datatype
mod_phys_lmdz_mpi_data mpi_size 进程数
mod_phys_lmdz_mpi_data klon_mpi_para_nb/begin/end 各 rank 列分布表
mod_phys_lmdz_mpi_data klon_mpi 本 rank 列数
mod_phys_lmdz_mpi_data ii_begin 本 rank 经向起始
mod_phys_lmdz_mpi_data is_north_pole_dyn 本 rank 拥有北极
mod_phys_lmdz_mpi_data is_south_pole_dyn 本 rank 拥有南极
mod_grid_phy_lmdz klon_glo 全局列数
mod_grid_phy_lmdz nbp_lon 经度点数
mod_grid_phy_lmdz jj_nb 本 rank 纬度行数

Mars 运行参与度

条件经过:Mars 物理侧使用并行 transfer 时经过。MPI 构建 + mpi_size>1 时执行 MPI 调用;
非 MPI 构建或单进程时由 .not.is_using_mpi 短路或走 _para 上层退化。

Mars 物理过程通过 phys-transfer-para-wrapperbcast/scatter/gather 泛型间接调用本模块。直接调用 *_mpi 接口的场景主要出现在 _para wrapper 内部的 !$OMP MASTER 区域。

相关页面