Skip to content

Commit 355f9c8

Browse files
authored
Merge pull request #565 from ValeevGroup/evaleev/feature/summa-3d-grid
summa: 3-d (proc_h) process grid for batched general products
2 parents d38b314 + bf43a86 commit 355f9c8

5 files changed

Lines changed: 506 additions & 89 deletions

File tree

src/TiledArray/dist_eval/contraction_eval.h

Lines changed: 132 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,65 @@ class Summa
127127
const ordinal_type
128128
right_stride_local_; ///< stride for local right row iterators
129129

130+
// 3-d (h-grouped) grid information. The world's first
131+
// proc_h_ * proc_h_stride ranks are partitioned into proc_h_ contiguous
132+
// groups; slab h belongs to group h % proc_h_, and each group runs its
133+
// own 2-d SUMMA grid (proc_grid_ is this rank's GROUP-LOCAL grid,
134+
// constructed over the group's rank interval). proc_h_ == 1 is the
135+
// ordinary shared-grid batched contraction.
136+
const ordinal_type proc_h_; ///< Number of slab (h) groups
137+
const ordinal_type
138+
proc_h_stride_; ///< World ranks per slab group (the
139+
///< group of slab h spans world ranks
140+
///< [(h % proc_h_) * proc_h_stride_, ...))
141+
const ordinal_type first_slab_; ///< This rank's group's first slab (== its
142+
///< group index), or nh_ if this rank is
143+
///< in no group (idle for this eval)
144+
const ordinal_type my_slabs_; ///< Number of slabs of this rank's group
145+
146+
/// \return the world rank that owns result tile \p i: the within-group
147+
/// owner (from the group-local process grid) shifted by the world-rank
148+
/// offset of the group that owns \p i's slab. For proc_h_ == 1 the offset
149+
/// is 0 and this is the ordinary cyclic owner.
150+
ProcessID result_tile_owner(const ordinal_type i) const {
151+
const ordinal_type source_index = DistEvalImpl_::perm_index_to_source(i);
152+
// owner is independent of slab index *within a group*
153+
const ordinal_type slab_index = source_index % result_slab_size_;
154+
const ordinal_type tile_row = slab_index / proc_grid_.cols();
155+
const ordinal_type tile_col = slab_index % proc_grid_.cols();
156+
const ordinal_type proc_row = tile_row % proc_grid_.proc_rows();
157+
const ordinal_type proc_col = tile_col % proc_grid_.proc_cols();
158+
const ProcessID within_group = proc_row * proc_grid_.proc_cols() + proc_col;
159+
// shift by the offset of the group that owns this tile's slab
160+
const ordinal_type slab = source_index / result_slab_size_;
161+
const ordinal_type group = (proc_h_ > 1ul) ? (slab % proc_h_) : 0ul;
162+
return ProcessID(group * proc_h_stride_) + within_group;
163+
}
164+
130165
/// \return the slab index of SUMMA step \p s
131166
ordinal_type step_h(const ordinal_type s) const { return s / k_; }
132167
/// \return the within-slab inner-dimension index of SUMMA step \p s
133168
ordinal_type step_k(const ordinal_type s) const { return s % k_; }
134169

170+
/// \return the smallest SUMMA step >= \p s that belongs to one of this
171+
/// rank's group's slabs, or nsteps_ if there is none
172+
ordinal_type next_step(ordinal_type s) const {
173+
if (proc_h_ == 1ul) return std::min(s, nsteps_);
174+
if (first_slab_ >= nh_) return nsteps_; // not in any group
175+
while (s < nsteps_ && (step_h(s) % proc_h_) != first_slab_)
176+
s = (step_h(s) + 1ul) * k_; // jump to the start of the next slab
177+
return std::min(s, nsteps_);
178+
}
179+
180+
/// \return this rank's group-local ordinal of slab \p h (which must
181+
/// belong to this rank's group)
182+
ordinal_type slab_ord(const ordinal_type h) const {
183+
return (h - first_slab_) / proc_h_;
184+
}
185+
186+
/// \return the number of SUMMA steps of this rank's group's slabs
187+
ordinal_type my_steps() const { return my_slabs_ * k_; }
188+
135189
typedef Future<typename right_type::eval_type>
136190
right_future; ///< Future to a right-hand argument tile
137191
typedef Future<typename left_type::eval_type>
@@ -225,7 +279,7 @@ class Summa
225279
/// \param end The end of the row or column range
226280
/// \param stride The row or column index stride
227281
/// \param k The broadcast group index
228-
/// \param max_group_size The maximum number of processes in the result
282+
/// \param max_proc_h_stride The maximum number of processes in the result
229283
/// group, which is equal to the number of process in this process row or
230284
/// column as defined by \c proc_grid_.
231285
/// \param key_offset The key that will be used to identify the process group
@@ -238,21 +292,21 @@ class Summa
238292
const std::vector<bool>& process_mask,
239293
ordinal_type index, const ordinal_type end,
240294
const ordinal_type stride,
241-
const ordinal_type max_group_size,
295+
const ordinal_type max_proc_h_stride,
242296
const ordinal_type k, const ordinal_type key_offset,
243297
const ProcMap& proc_map) const {
244298
// Generate the list of processes in rank_row
245-
std::vector<ProcessID> proc_list(max_group_size, -1);
299+
std::vector<ProcessID> proc_list(max_proc_h_stride, -1);
246300

247301
// Flag the root processes of the broadcast, which may not be included
248302
// by shape.
249-
ordinal_type p = k % max_group_size;
303+
ordinal_type p = k % max_proc_h_stride;
250304
proc_list[p] = proc_map(p);
251305
ordinal_type count = 1ul;
252306

253307
// Flag all processes that have non-zero tiles
254-
for (p = 0ul; (index < end) && (count < max_group_size);
255-
index += stride, p = (p + 1u) % max_group_size) {
308+
for (p = 0ul; (index < end) && (count < max_proc_h_stride);
309+
index += stride, p = (p + 1u) % max_proc_h_stride) {
256310
if ((proc_list[p] != -1) || (shape.is_zero(index)) || !process_mask.at(p))
257311
continue;
258312

@@ -734,7 +788,7 @@ class Summa
734788
// broadcast root (i.e. within-slab k congruent to rank_col mod Pcols)
735789
const ordinal_type Pcols = proc_grid_.proc_cols();
736790

737-
for (; s < end; ++s) {
791+
for (s = next_step(s); s < end; s = next_step(s + 1ul)) {
738792
const ordinal_type k = step_k(s);
739793
if (k % Pcols != static_cast<ordinal_type>(proc_grid_.rank_col()))
740794
continue;
@@ -787,7 +841,7 @@ class Summa
787841
// broadcast root (i.e. within-slab k congruent to rank_row mod Prows)
788842
const ordinal_type Prows = proc_grid_.proc_rows();
789843

790-
for (; s < end; ++s) {
844+
for (s = next_step(s); s < end; s = next_step(s + 1ul)) {
791845
const ordinal_type k = step_k(s);
792846
if (k % Prows != static_cast<ordinal_type>(proc_grid_.rank_row()))
793847
continue;
@@ -847,9 +901,9 @@ class Summa
847901
/// \return The first step, greater than or equal to \c s with non-zero
848902
/// tiles, or \c nsteps_ if none is found.
849903
ordinal_type iterate_row(ordinal_type s) const {
850-
// Iterate over steps until a non-zero tile is found or the end of the
851-
// matrix is reached.
852-
for (; s < nsteps_; ++s) {
904+
// Iterate over this rank's group's steps until a non-zero tile is found
905+
// or the end of the matrix is reached.
906+
for (s = next_step(s); s < nsteps_; s = next_step(s + 1ul)) {
853907
// Search for non-zero tiles in row k of slab h of right
854908
ordinal_type i =
855909
step_h(s) * right_slab_size_ + step_k(s) * proc_grid_.cols();
@@ -871,9 +925,9 @@ class Summa
871925
/// \return The first step, greater than or equal to \c s, that contains
872926
/// a non-zero tile. If no non-zero tile is not found, return \c nsteps_.
873927
ordinal_type iterate_col(ordinal_type s) const {
874-
// Iterate over steps until a non-zero tile is found or the end of the
875-
// matrix is reached.
876-
for (; s < nsteps_; ++s) {
928+
// Iterate over this rank's group's steps until a non-zero tile is found
929+
// or the end of the matrix is reached.
930+
for (s = next_step(s); s < nsteps_; s = next_step(s + 1ul)) {
877931
// Search column k of slab h for non-zero tiles
878932
const ordinal_type base = step_h(s) * left_slab_size_;
879933
for (ordinal_type i = base + left_start_local_ + step_k(s);
@@ -963,11 +1017,17 @@ class Summa
9631017
return tile_count;
9641018
} else {
9651019
// Construct static broadcast groups for dense arguments
966-
// (key space [0, 2*nsteps_) is reserved for the sparse per-step groups)
967-
const madness::DistributedID col_did(DistEvalImpl_::id(), 2ul * nsteps_);
1020+
// (key space [0, 2*nsteps_) is reserved for the sparse per-step groups,
1021+
// whose keys h*k_ and h*k_+nsteps_ are disjoint across h-groups; the
1022+
// two static keys are offset PAST that range and made group-unique so
1023+
// that two different groups' single-grid static groups never claim the
1024+
// same DistributedID with inconsistent membership)
1025+
const std::size_t static_key_base = 2ul * nsteps_ + 2ul * first_slab_;
1026+
const madness::DistributedID col_did(DistEvalImpl_::id(),
1027+
static_key_base);
9681028
col_group_ = proc_grid_.make_col_group(col_did);
9691029
const madness::DistributedID row_did(DistEvalImpl_::id(),
970-
2ul * nsteps_ + 1ul);
1030+
static_key_base + 1ul);
9711031
row_group_ = proc_grid_.make_row_group(row_did);
9721032

9731033
#ifdef TILEDARRAY_ENABLE_SUMMA_TRACE_INITIALIZE
@@ -985,12 +1045,12 @@ class Summa
9851045
#endif // TILEDARRAY_ENABLE_SUMMA_TRACE_INITIALIZE
9861046

9871047
// Allocate memory for the reduce pair tasks (one per local result tile
988-
// per slab).
1048+
// per slab of this rank's group).
9891049
std::allocator<ReducePairTask<op_type>> alloc;
990-
reduce_tasks_ = alloc.allocate(nh_ * proc_grid_.local_size());
1050+
reduce_tasks_ = alloc.allocate(my_slabs_ * proc_grid_.local_size());
9911051

9921052
// Iterate over all local tiles
993-
const ordinal_type n = nh_ * proc_grid_.local_size();
1053+
const ordinal_type n = my_slabs_ * proc_grid_.local_size();
9941054
for (ordinal_type t = 0ul; t < n; ++t) {
9951055
// Initialize the reduction task
9961056
ReducePairTask<op_type>* MADNESS_RESTRICT const reduce_task =
@@ -1019,9 +1079,9 @@ class Summa
10191079
if (k_ == 0) return 0;
10201080

10211081
// Allocate memory for the reduce pair tasks (one per local result tile
1022-
// per slab).
1082+
// per slab of this rank's group).
10231083
std::allocator<ReducePairTask<op_type>> alloc;
1024-
reduce_tasks_ = alloc.allocate(nh_ * proc_grid_.local_size());
1084+
reduce_tasks_ = alloc.allocate(my_slabs_ * proc_grid_.local_size());
10251085

10261086
// Initialize iteration variables
10271087
const ordinal_type col_stride = // The stride to iterate down a column
@@ -1030,10 +1090,11 @@ class Summa
10301090
proc_grid_.proc_cols();
10311091

10321092
// Iterate over all local tiles, slab by slab (the block-cyclic phase
1033-
// restarts at every slab: the owner of tile (h,i,j) does not depend on h)
1093+
// restarts at every slab: within a group, the owner of tile (h,i,j)
1094+
// does not depend on h)
10341095
ordinal_type tile_count = 0ul;
10351096
ReducePairTask<op_type>* MADNESS_RESTRICT reduce_task = reduce_tasks_;
1036-
for (ordinal_type h = 0ul; h < nh_; ++h) {
1097+
for (ordinal_type h = first_slab_; h < nh_; h += proc_h_) {
10371098
const ordinal_type slab_base = h * result_slab_size_;
10381099
ordinal_type row_start =
10391100
slab_base + proc_grid_.rank_row() * proc_grid_.cols();
@@ -1103,7 +1164,7 @@ class Summa
11031164

11041165
// Iterate over all local tiles, slab by slab
11051166
ReducePairTask<op_type>* reduce_task = reduce_tasks_;
1106-
for (ordinal_type h = 0ul; h < nh_; ++h) {
1167+
for (ordinal_type h = first_slab_; h < nh_; h += proc_h_) {
11071168
const ordinal_type slab_base = h * result_slab_size_;
11081169
ordinal_type row_start =
11091170
slab_base + proc_grid_.rank_row() * proc_grid_.cols();
@@ -1126,7 +1187,7 @@ class Summa
11261187

11271188
// Deallocate the memory for the reduce pair tasks.
11281189
std::allocator<ReducePairTask<op_type>>().deallocate(
1129-
reduce_tasks_, nh_ * proc_grid_.local_size());
1190+
reduce_tasks_, my_slabs_ * proc_grid_.local_size());
11301191
}
11311192

11321193
/// Set the result tiles and destroy reduce tasks
@@ -1145,7 +1206,7 @@ class Summa
11451206

11461207
// Iterate over all local tiles, slab by slab
11471208
ReducePairTask<op_type>* reduce_task = reduce_tasks_;
1148-
for (ordinal_type h = 0ul; h < nh_; ++h) {
1209+
for (ordinal_type h = first_slab_; h < nh_; h += proc_h_) {
11491210
const ordinal_type slab_base = h * result_slab_size_;
11501211
ordinal_type row_start =
11511212
slab_base + proc_grid_.rank_row() * proc_grid_.cols();
@@ -1177,7 +1238,7 @@ class Summa
11771238
}
11781239
// Deallocate the memory for the reduce pair tasks.
11791240
std::allocator<ReducePairTask<op_type>>().deallocate(
1180-
reduce_tasks_, nh_ * proc_grid_.local_size());
1241+
reduce_tasks_, my_slabs_ * proc_grid_.local_size());
11811242

11821243
#ifdef TILEDARRAY_ENABLE_SUMMA_TRACE_FINALIZE
11831244
ss << "}\n";
@@ -1229,9 +1290,10 @@ class Summa
12291290
const std::vector<col_datum>& col,
12301291
const std::vector<row_datum>& row,
12311292
madness::TaskInterface* const task) {
1232-
// The reduce tasks of slab h occupy
1233-
// [h * local_size, (h+1) * local_size)
1234-
const ordinal_type slab_offset = step_h(s) * proc_grid_.local_size();
1293+
// The reduce tasks of this group's slab h occupy
1294+
// [slab_ord(h) * local_size, (slab_ord(h)+1) * local_size)
1295+
const ordinal_type slab_offset =
1296+
slab_ord(step_h(s)) * proc_grid_.local_size();
12351297

12361298
// Iterate over the row
12371299
for (ordinal_type i = 0ul; i < col.size(); ++i) {
@@ -1266,9 +1328,10 @@ class Summa
12661328
const std::vector<col_datum>& col,
12671329
const std::vector<row_datum>& row,
12681330
madness::TaskInterface* const task) {
1269-
// The reduce tasks of slab h occupy
1270-
// [h * local_size, (h+1) * local_size)
1271-
const ordinal_type slab_offset = step_h(s) * proc_grid_.local_size();
1331+
// The reduce tasks of this group's slab h occupy
1332+
// [slab_ord(h) * local_size, (slab_ord(h)+1) * local_size)
1333+
const ordinal_type slab_offset =
1334+
slab_ord(step_h(s)) * proc_grid_.local_size();
12721335

12731336
// Iterate over the row
12741337
for (ordinal_type i = 0ul; i < col.size(); ++i) {
@@ -1452,8 +1515,12 @@ class Summa
14521515

14531516
template <typename Derived>
14541517
void make_next_step_tasks(Derived* task, ordinal_type depth) {
1455-
// Set the depth to be no greater than the maximum number steps
1456-
if (depth > owner_->nsteps_) depth = owner_->nsteps_;
1518+
// Set the depth to be no greater than the number of SUMMA steps this
1519+
// rank's group actually executes. In the 2-d (proc_h_ == 1) case this is
1520+
// nsteps_ (my_slabs_ == nh_); in the 3-d (proc_h_ > 1) case my_steps() <
1521+
// nsteps_, and clamping to nsteps_ would pre-spawn surplus step tasks
1522+
// that all resolve to the terminating step (k_ == nsteps_).
1523+
if (depth > owner_->my_steps()) depth = owner_->my_steps();
14571524

14581525
// Spawn n=depth step tasks
14591526
for (; depth > 0ul; --depth) {
@@ -1540,13 +1607,14 @@ class Summa
15401607
public:
15411608
DenseStepTask(const std::shared_ptr<Summa_>& owner,
15421609
const ordinal_type depth)
1543-
: StepTask(owner, owner->nsteps_ + 1ul), k_(0) {
1610+
: StepTask(owner, owner->my_steps() + 1ul), k_(owner->next_step(0ul)) {
15441611
StepTask::make_next_step_tasks(this, depth);
1545-
StepTask::spawn_get_row_col_tasks(k_);
1612+
if (k_ < owner_->nsteps_) StepTask::spawn_get_row_col_tasks(k_);
15461613
}
15471614

15481615
DenseStepTask(DenseStepTask* const parent, const int ndep)
1549-
: StepTask(parent, ndep), k_(parent->k_ + 1ul) {
1616+
: StepTask(parent, ndep),
1617+
k_(parent->owner_->next_step(parent->k_ + 1ul)) {
15501618
// Spawn tasks to get k-th row and column tiles
15511619
if (k_ < owner_->nsteps_) StepTask::spawn_get_row_col_tasks(k_);
15521620
}
@@ -1671,7 +1739,8 @@ class Summa
16711739
const trange_type trange, const shape_type& shape,
16721740
const std::shared_ptr<const pmap_interface>& pmap, const Perm& perm,
16731741
const op_type& op, const ordinal_type k, const ProcGrid& proc_grid,
1674-
const ordinal_type nh = 1ul)
1742+
const ordinal_type nh = 1ul, const ordinal_type proc_h = 1ul,
1743+
const ordinal_type proc_h_stride = 0ul)
16751744
: DistEvalImpl_(world, trange, shape, pmap, outer(perm)),
16761745
left_(left),
16771746
right_(right),
@@ -1685,6 +1754,11 @@ class Summa
16851754
left_slab_size_(left.size() / nh),
16861755
right_slab_size_(right.size() / nh),
16871756
result_slab_size_(proc_grid.rows() * proc_grid.cols()),
1757+
proc_h_(proc_h),
1758+
proc_h_stride_(proc_h_stride),
1759+
first_slab_(compute_first_slab(world, nh, proc_h, proc_h_stride)),
1760+
my_slabs_(first_slab_ < nh ? (nh - first_slab_ + proc_h - 1ul) / proc_h
1761+
: 0ul),
16881762
reduce_tasks_(NULL),
16891763
left_start_local_(proc_grid_.rank_row() * k),
16901764
left_end_(left.size() / nh),
@@ -1703,6 +1777,19 @@ class Summa
17031777
TA_ASSERT(nh_ > 0);
17041778
TA_ASSERT(left.size() % nh_ == 0);
17051779
TA_ASSERT(right.size() % nh_ == 0);
1780+
TA_ASSERT(proc_h_ > 0);
1781+
TA_ASSERT(proc_h_ == 1ul || proc_h_stride > 0ul);
1782+
TA_ASSERT(proc_h_ <= nh_);
1783+
}
1784+
1785+
/// \return this rank's group's first slab (== its group index), or
1786+
/// \p nh if this rank is outside the grouped rank interval
1787+
static ordinal_type compute_first_slab(World& world, const ordinal_type nh,
1788+
const ordinal_type proc_h,
1789+
const ordinal_type proc_h_stride) {
1790+
if (proc_h == 1ul) return 0ul;
1791+
const auto rank = ordinal_type(world.rank());
1792+
return (rank < proc_h * proc_h_stride) ? (rank / proc_h_stride) : nh;
17061793
}
17071794

17081795
virtual ~Summa() {}
@@ -1717,18 +1804,11 @@ class Summa
17171804
TA_ASSERT(TensorImpl_::is_local(i));
17181805
TA_ASSERT(!TensorImpl_::is_zero(i));
17191806

1720-
const ordinal_type source_index = DistEvalImpl_::perm_index_to_source(i);
1721-
1722-
// Compute tile coordinate in tile grid (the owner of a tile is
1723-
// independent of its slab index)
1724-
const ordinal_type slab_index = source_index % result_slab_size_;
1725-
const ordinal_type tile_row = slab_index / proc_grid_.cols();
1726-
const ordinal_type tile_col = slab_index % proc_grid_.cols();
1727-
// Compute process coordinate of tile in the process grid
1728-
const ordinal_type proc_row = tile_row % proc_grid_.proc_rows();
1729-
const ordinal_type proc_col = tile_col % proc_grid_.proc_cols();
1730-
// Compute the process that owns tile
1731-
const ProcessID source = proc_row * proc_grid_.proc_cols() + proc_col;
1807+
// The process that owns tile i: the within-group cyclic owner shifted by
1808+
// the world-rank offset of the tile's slab group (see
1809+
// result_tile_owner). For proc_h_ == 1 this is the ordinary cyclic
1810+
// owner over the whole world.
1811+
const ProcessID source = result_tile_owner(i);
17321812

17331813
const madness::DistributedID key(DistEvalImpl_::id(), i);
17341814
return TensorImpl_::world().gop.template recv<value_type>(source, key);

0 commit comments

Comments
 (0)