Skip to content

Commit ecccb24

Browse files
committed
Merge commit '84411a6c' into zhihao/feature/retile_SUMMA
# Conflicts: # src/TiledArray/array_impl.h
2 parents eb3a244 + 84411a6 commit ecccb24

3 files changed

Lines changed: 124 additions & 9 deletions

File tree

src/TiledArray/array_impl.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,21 +1041,27 @@ std::shared_ptr<ArrayImpl<Tile, Policy>> make_with_new_trange(
10411041
};
10421042
for (const auto target_ord : *target_array.pmap()) {
10431043
if (target_array.is_zero(target_ord)) continue;
1044-
// build each target tile in one pass: a single source lookup per cell
1045-
// sizes it and fills it together (no separate all-ranges walk).
1044+
// build each target tile up-front in a single contiguous arena page:
1045+
// arena_outer_init's range pre-walk sizes every cell, and we cache the
1046+
// source cell it found per ordinal so the fill loop reuses it instead of
1047+
// repeating the (cached, but non-trivial) source lookup. Every non-null
1048+
// cell is fully overwritten below, so the slab need not be zero-init'd.
10461049
const auto outer_range = target_trange.make_tile_range(target_ord);
10471050
const std::size_t n = outer_range.volume();
10481051
using InnerRange = typename Tile::value_type::range_type;
1052+
std::vector<const typename Tile::value_type*> src_cells(n);
10491053
auto range_fn = [&](std::size_t o) -> InnerRange {
10501054
const auto* sc = source_cell_at(outer_range.idx(o));
1055+
src_cells[o] = sc;
10511056
return (!sc || sc->empty()) ? InnerRange{} : sc->range();
10521057
};
1053-
Tile tile = arena_outer_init<Tile>(outer_range, 1, range_fn);
1058+
Tile tile =
1059+
arena_outer_init<Tile>(outer_range, 1, range_fn, kArenaCachelineAlign,
1060+
/*zero_init=*/false);
10541061
for (std::size_t o = 0; o < n; ++o) {
10551062
auto& cell = tile.data()[o];
10561063
if (cell.empty()) continue; // deliberately-null cell
1057-
const auto* sc = source_cell_at(outer_range.idx(o));
1058-
const auto* s = sc->data();
1064+
const auto* s = src_cells[o]->data();
10591065
auto* d = cell.data();
10601066
for (std::size_t p = 0; p < cell.size(); ++p) d[p] = s[p];
10611067
}

src/TiledArray/dist_eval/contraction_eval.h

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,24 +2070,46 @@ class Summa
20702070

20712071
ProcessID get_row_group_root(const ordinal_type k,
20722072
const madness::Group& row_group) const {
2073+
// The broadcast root is the process in this process row that owns the
2074+
// column-k source tiles, i.e. grid position (rank_row, k % proc_cols).
20732075
ProcessID group_root = k % proc_grid_.proc_cols();
20742076
if (!right_.shape().is_dense() &&
20752077
row_group.size() < static_cast<ProcessID>(proc_grid_.proc_cols())) {
2076-
const ProcessID world_root =
2077-
proc_grid_.rank_row() * proc_grid_.proc_cols() + group_root;
2078+
// The group has been pruned (it omits ranks that own no nonzero tiles
2079+
// for this column), so the root's group-local rank no longer equals its
2080+
// process-column index and must be looked up by its world rank. Use
2081+
// ProcGrid::map_col, which is exactly how make_group inserted the root
2082+
// into the group (it always includes the geometric root). Computing the
2083+
// world rank by hand here is a latent bug: it must account for the
2084+
// grid's rank offset (nonzero for the h-grouped 3-d batched grid), which
2085+
// map_col supplies. Without it Group::rank() fails to find the root and
2086+
// returns -1, aborting the broadcast.
2087+
const ProcessID world_root = proc_grid_.map_col(group_root);
20782088
group_root = row_group.rank(world_root);
2089+
TA_ASSERT(group_root >= 0);
20792090
}
20802091
return group_root;
20812092
}
20822093

20832094
ProcessID get_col_group_root(const ordinal_type k,
20842095
const madness::Group& col_group) const {
2096+
// The broadcast root is the process in this process column that owns the
2097+
// row-k source tiles, i.e. grid position (k % proc_rows, rank_col).
20852098
ProcessID group_root = k % proc_grid_.proc_rows();
20862099
if (!left_.shape().is_dense() &&
20872100
col_group.size() < static_cast<ProcessID>(proc_grid_.proc_rows())) {
2088-
const ProcessID world_root =
2089-
group_root * proc_grid_.proc_cols() + proc_grid_.rank_col();
2101+
// The group has been pruned (it omits ranks that own no nonzero tiles
2102+
// for this row), so the root's group-local rank no longer equals its
2103+
// process-row index and must be looked up by its world rank. Use
2104+
// ProcGrid::map_row, which is exactly how make_group inserted the root
2105+
// into the group (it always includes the geometric root). Computing the
2106+
// world rank by hand here is a latent bug: it must account for the
2107+
// grid's rank offset (nonzero for the h-grouped 3-d batched grid), which
2108+
// map_row supplies. Without it Group::rank() fails to find the root and
2109+
// returns -1, aborting the broadcast.
2110+
const ProcessID world_root = proc_grid_.map_row(group_root);
20902111
group_root = col_group.rank(world_root);
2112+
TA_ASSERT(group_root >= 0);
20912113
}
20922114
return group_root;
20932115
}

tests/proc_grid.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,93 @@ BOOST_AUTO_TEST_CASE(make_groups) {
193193
}
194194
}
195195

196+
// Regression test for the SUMMA broadcast-root computation
197+
// (Summa::get_row_group_root / get_col_group_root in
198+
// dist_eval/contraction_eval.h).
199+
//
200+
// When the right (resp. left) operand is block-sparse the SUMMA row (resp.
201+
// column) broadcast group is pruned to the ranks that own nonzero source
202+
// tiles, so its size drops below proc_cols (resp. proc_rows). In that case the
203+
// broadcast root's group-local rank no longer equals its process-column
204+
// (-row) index and must be looked up from its *world* rank. make_group always
205+
// inserts the geometric root into the group via ProcGrid::map_col / map_row,
206+
// whose returned world rank includes the grid's rank_offset_ -- the world rank
207+
// of the grid's first process. rank_offset_ is nonzero for the h-grouped 3-d
208+
// batched SUMMA grid (every slab group beyond group 0). The old root code
209+
// reconstructed the world rank by hand as rank_row*proc_cols + group_root
210+
// (resp. group_root*proc_cols + rank_col), *omitting rank_offset_*, so for an
211+
// offset grid Group::rank() failed to find the root and returned -1, tripping
212+
// MADNESS_ASSERT(group_root >= 0) in WorldGopInterface::bcast and aborting the
213+
// process.
214+
//
215+
// The fix recomputes the root's world rank with ProcGrid::map_col / map_row,
216+
// which is exactly the value make_group inserts. This test validates that
217+
// primitive: on an offset grid map_col / map_row must include the offset, so
218+
// the broadcast root the fix now uses is the value present in the group, while
219+
// the old offset-less formula would have missed it (differing by exactly the
220+
// nonzero offset). A full end-to-end SUMMA repro requires >= 4 ranks (proc_h_
221+
// > 1 needs P >= 2*proc_rows*proc_cols and a pruned group needs proc_cols or
222+
// proc_rows >= 2), beyond this suite's np<=2 runs.
223+
BOOST_AUTO_TEST_CASE(summa_bcast_root_offset) {
224+
// Baseline (np-independent): on an unoffset grid the offset-aware maps agree
225+
// with the bare geometric formula the old root code used. The test ctor
226+
// permits an arbitrary (test_rank, test_nprocs) regardless of world size and
227+
// leaves rank_offset_ == 0.
228+
{
229+
TiledArray::detail::ProcGrid grid(*GlobalFixture::world, /*test_rank=*/5u,
230+
/*test_nprocs=*/12u, /*rows=*/8,
231+
/*cols=*/8,
232+
/*row_size=*/64, /*col_size=*/64);
233+
BOOST_REQUIRE_GT(grid.proc_cols(), 0u);
234+
BOOST_REQUIRE_GT(grid.proc_rows(), 0u);
235+
for (std::size_t c = 0; c < grid.proc_cols(); ++c)
236+
BOOST_CHECK_EQUAL(
237+
grid.map_col(c),
238+
grid.rank_row() * ProcessID(grid.proc_cols()) + ProcessID(c));
239+
for (std::size_t r = 0; r < grid.proc_rows(); ++r)
240+
BOOST_CHECK_EQUAL(
241+
grid.map_row(r),
242+
grid.rank_col() + ProcessID(r) * ProcessID(grid.proc_cols()));
243+
}
244+
245+
// Offset grid: this is the regression. Construct a process grid whose first
246+
// process is at a nonzero world rank (as the h-grouped batched SUMMA does
247+
// for slab groups beyond group 0) and verify the maps the fix now uses
248+
// include the offset -- so the broadcast root is the in-group world rank,
249+
// not the offset-less one the old code computed (which would be -1 once the
250+
// group is pruned). Requires the world to be large enough to host an offset
251+
// sub-grid (rank_subset_t ctor asserts rank_offset + nprocs <= world.size()).
252+
const auto world_size = GlobalFixture::world->size();
253+
if (world_size >= 2) {
254+
const std::size_t nprocs = world_size - (world_size / 2); // upper ~half
255+
const ProcessID offset = world_size - ProcessID(nprocs); // nonzero offset
256+
BOOST_REQUIRE_GT(offset, 0);
257+
TiledArray::detail::ProcGrid grid(*GlobalFixture::world,
258+
TiledArray::detail::rank_subset, offset,
259+
nprocs, /*rows=*/8,
260+
/*cols=*/8, /*row_size=*/64,
261+
/*col_size=*/64);
262+
// Only ranks inside the sub-grid have a valid (rank_row, rank_col).
263+
if (GlobalFixture::world->rank() >= offset) {
264+
for (std::size_t c = 0; c < grid.proc_cols(); ++c) {
265+
const ProcessID offset_less =
266+
grid.rank_row() * ProcessID(grid.proc_cols()) + ProcessID(c);
267+
// map_col == the value make_group inserts as the row-broadcast root
268+
BOOST_CHECK_EQUAL(grid.map_col(c), offset_less + offset);
269+
// ... which differs from the old offset-less root computation
270+
BOOST_CHECK_NE(grid.map_col(c), offset_less);
271+
}
272+
for (std::size_t r = 0; r < grid.proc_rows(); ++r) {
273+
const ProcessID offset_less =
274+
grid.rank_col() + ProcessID(r) * ProcessID(grid.proc_cols());
275+
// map_row == the value make_group inserts as the col-broadcast root
276+
BOOST_CHECK_EQUAL(grid.map_row(r), offset_less + offset);
277+
BOOST_CHECK_NE(grid.map_row(r), offset_less);
278+
}
279+
}
280+
}
281+
}
282+
196283
#if 0
197284
// This test case us used to evaluate distribute statistics. This unit test
198285
// should only be enabled when changes are made to the ProcGrid algorithm, and

0 commit comments

Comments
 (0)