0

要約すると、私の質問は、Scalapack (BLACS) の 2 つの異なるプロセス グリッド上の 2 つのブロック周期的に分散されたマトリックス間でマトリックス コピーを実装する方法についてです。これを pdgemr2d_ で実装しようとしています。これは、同じプロセス グリッド上の 2 つのマトリックス間でコピーする場合に頻繁に使用します。

以下は、私が直面している問題の状態に関するかなり技術的な議論です。私はそれを基本的な問題に突き止めましたが、解決策があるようには見えません...ただし、Scalapack は私がしようとしているタイプの操作が可能であると具体的に述べているため、あるに違いありません。これの適切な例はどこにもありません。

C の MPI で実行される Scalapack の 1x1 計算グリッドの初期化は、通常、次のようになります。

[...]
int NUM_TASKS, RANK;

int MPI_STARTUP = MPI_Init (&argc, &argv);
if (MPI_STARTUP != MPI_SUCCESS)
  MPI_Abort (MPI_COMM_WORLD, MPI_STARTUP);

MPI_Comm_size (MPI_COMM_WORLD, &NUM_TASKS);
MPI_Comm_rank (MPI_COMM_WORLD, &RANK);

int CONTEXT;

Cblacs_pinfo (&RANK, &NUM_TASKS);
Cblacs_get (-1, 0, &CONTEXT);
Cblacs_gridinit (&CONTEXT, "Row", 1, 1);
Cblacs_gridinfo (CONTEXT, &NPROW, &NPCOL, &MYPROW, &MYPCOL);
[...]

このコードは、MPI が認識しているプロセッサの数に関係なく、1x1 グリッドを生成します (グリッドのサイズ {1, 1} が Cblacs_gridinit に渡されます)。ここで、CONTEXT は Scalapack 関数に対して、作業中のグリッドを示します (同時に複数を使用することができ、Cblacs_get によって生成されます)。Cblacs_gridinfo は、NPROW と NPCOL をプロセッサーの行と列の数 (この場合は {1, 1}) として設定します。MYPROW と MYPCOL は、どのグリッド ブロックが属するかを各プロセッサに示します。この場合、1x1 グリッドでは、1 つのプロセッサのみが参加し、そのグリッド ID は {0, 0} です。

単純なブロック巡回分散 100x100 行列の行列記述子の初期化も、通常は単純です。

int info;
int desc[9];
int block_size = 32;
int zero = 0; int one = 1;
int num_rows = 100; int num_cols = 100;

int num_cols_local = numroc_ (&num_cols, &block_size, &mypcol, &zero, &npcol);
int num_cols_local_protect = MAX (1, num_cols_local);
int num_rows_local = numroc_ (&num_rows, &block_size, &myprow, &zero, &nprow);
int num_rows_local_protect = MAX (1, num_rows_local);
descinit_ (desc, &num_rows, &num_cols, &block_size, &block_size, &zero, &zero, \
  &CONTEXT, &num_rows_local_protect, &info);

/* now allocate array with per-processor size num_cols_local_protect * num_rows_local_protect */

(一部のプロセッサでは num_cols_local または num_rows_local が負の整数として正確に返されるため、「保護」変数が必要な理由については後で説明します。)

上記のほとんどは、descinit_ に渡される &zeros を除いて一目瞭然です。これは、行列の最初の行が分散されるプロセッサの行と、最初の列が分散されるプロセッサの列を示します。これらの値は、descinit_ 関数で使用される場合、非常に明示的な境界を持ちます。Fortran関数自体から、

[...]
ELSE IF( IRSRC.LT.0 .OR. IRSRC.GE.NPROW ) THEN
  INFO = -6
ELSE IF( ICSRC.LT.0 .OR. ICSRC.GE.NPCOL ) THEN
  INFO = -7
[...]

{0,0} は単一のグリッド ブロックの適切なインデックスであるため、ここでは IRSRC と ICSRC をゼロとして渡します。グリッドがはるかに大きい場合でも、最初のプロセッサ ブロックが最初の行と列の値を格納する可能性が高いため、{0,0} を渡す可能性があります。

1 つのプロセッサで実行すると、これは非常にうまく機能します。NPROW、NPCOL、MYPROW、および MYPCOL の唯一のプロセッサである RANK 0 の値は、それぞれ 1、1、0、および 0 です。この場合の CONTEXT は 0 であり、非負であることは、それが参照するグリッドがこの RANK でアクティブであることを示します。これらの値は、1x1 プロセス グリッドの存在を示し、最初のプロセッサが正しいことを示し、RANK 0 に属する正しいプロセス グリッド ブロックを示します。この場合、それが唯一のブロックです。

ただし、2 つのプロセッサで実行すると、問題が発生するため、正式にはそうすべきではありません。1 番目と 2 番目の RANK には、CONTEXT、NPROW、NPCOL、MYPROW、および MYCOL があります。

RANK 0: 0, 1, 1, 0, 0
RANK 1: -1, -1, -1, -1, -1

すべての値は負です。最も重要なのは、RANK 1 の CONTEXT が負であることです。これは、この RANK が 1x1 プロセッサ グリッドに参加していないことを示しています。今すぐ descinit_ を呼び出すと、すべてのプロセッサですぐに問題になります。descinit_ から Fortran コードを参照すると、次のようになります (明確にするために上記を繰り返します)。

[...]
ELSE IF( IRSRC.LT.0 .OR. IRSRC.GE.NPROW ) THEN
  INFO = -6
ELSE IF( ICSRC.LT.0 .OR. ICSRC.GE.NPCOL ) THEN
  INFO = -7
[...]

これらの制限は、各プロセッサがグリッドに参加している限り意味があります。このようなグリッド ブロックは存在しないため、インデックスを負にしたり、プロセス グリッド内の行または列の総数以上にすることはできません。

RANK 1 では、IRSRC はゼロとして渡されますが、NPROW と NPCOL はグリッドの初期化から -1 として返されるため、descinit_ は常に失敗します。

上記のすべては、マトリックス記述子の初期化とその後のすべての操作を現在のグリッドに参加しているプロセッサに制限するだけで、エレガントではありませんが簡単に克服できます。何かのようなもの:

if (CONTEXT > -1) {
[...]

ただし、プロセッサ グリッドは 1 つだけではなく 2 つあり、pdgemr2d_ 関数を使用して通信する必要があります。この関数の目的は、あるグリッドの分散行列 A のサブセットを別のグリッドの分散行列 B にコピーすることです。グリッドは互いに関連している必要はなく、部分的または完全にばらばらでもかまいません。これは簡単な操作です。たとえば、コンテキスト CONTEXT_A を持つプロセッサ グリッドからコンテキスト CONTEXT_B を持つプロセッサ グリッドに行列全体をコピーしたいとします。各コンテキストのマトリックスの記述子は、desc_A および desc_B として与えられます。

pdgemr2d_ (&num_rows, &num_cols, matrix_A, &one, &one, \
  desc_A, matrix_B, &one, &one, desc_B, &CONTEXT_B);

これもかなり自明です。いずれかのコンテキストがグリッド メンバーを持つすべてのプロセッサで実行する必要があります。私の場合、CONTEXT_A には MPI が認識しているすべてのプロセッサにまたがるグリッドがあり、CONTEXT_B は 1x1 のシングル プロセッサ グリッドです。

pdgemr2d_ には、少なくとも CONTEXT_A と CONTEXT_B の両方に含まれるすべてのプロセッサを含むコンテキスト識別子を指定する必要があります。また、CONTEXT_A または CONTEXT_B に属さないプロセッサについては、要素 desc_A[CTXT] または desc_B[CTXT] をそれぞれ - に設定する必要があります。そのプロセッサで 1。

Cblacs_gridinit によって返される CONTEXT 値は、そのコンテキストのグリッドに参加していないプロセッサでは -1 であるため、理論上、descinit_ はこれをエレガントに行います。ただし、descinit_ は、NPROW と NPCOL の負の値に関する上記の制限により、グリッドに参加していないプロセッサでは正しい行列記述子を生成しません。

適切な分離グリッド通信を行うには、いずれかのコンテキストに参加するすべてのプロセッサでこのような行列記述子を定義する必要があります。

明らかに、pdgemr2d_ は、コード内の関数の説明が具体的に述べているように、これを克服できない欠陥として記述することはできません。

PDGEMR2D は、A の部分行列を B の部分行列にコピーします。A と B は、異なる分布を持つことができます。異なるプロセッサ グリッド上に配置することも、異なるブロック サイズを使用することも、コピーする領域の開始を A の異なる場所にすることもできます。とB.

ご協力いただきありがとうございます。これはかなり専門的な質問であることは承知しています。

4

1 に答える 1

2

PDGEMR2D の使用方法を理解するのに同様の困難があり、ここで結論を共有したいと思いました。

つまり、複数のコンテキストをセットアップしようとしている場合は、提供されている DESCINIT サブルーチンを使用しないでください。そのエラー チェックでは、初期化される配列記述子のコンテキストにすべてのプロセスが参加していると想定していますが、PDGEMR2D を使用しようとしている場合はそうではありません。

長さ 9 の整数配列であるため、DESCINIT を使用せずに独自の記述子を簡単に初期化できます。最初の 8 つのフィールド (dtype、ctxt、m、n、mb、nb、csrc、および rsrc) はグローバルであり、すべてのプロセスで同じ値。9 番目のフィールド (lld) のみがローカルであり、その値は、配列が定義されているコンテキストのメンバーではないプロセスでは無視されます。

ScaLAPACK ソース (オンライン バージョンはこちら)のサンプル プログラム pdgemrdrv.c は、この問題を理解しようとしていたときに役に立ちました。多くの不必要な複雑さが含まれていますが、次の重要な点を推測できます。

  1. 配列記述子はグローバルに初期化する必要があります (送信コンテキストと受信コンテキストの両方で)。
  2. 配列のメモリは、配列が存在するコンテキストにのみ割り当てる必要があります。
  3. PDGEMR2D はグローバルに (送信コンテキストと受信コンテキストの両方で) 呼び出す必要があります。送信コンテキストまたは受信コンテキストのいずれかのメンバーであるプロセスのみが実際に参加します。

お役に立てれば。乾杯。

于 2012-02-20T20:20:47.757 に答える