From 38c6fa78b2c2e61e1bf5754512b054ec912a33f7 Mon Sep 17 00:00:00 2001 From: Peter Boyle Date: Sat, 3 Oct 2026 10:17:15 -0400 Subject: [PATCH] Split updates --- Grid/lattice/Lattice_transfer.h | 586 +++++++++++++-------------- tests/solver/Test_dwf_mrhs_cg.cc | 14 +- tests/solver/Test_dwf_mrhs_cg_mpi.cc | 14 +- tests/solver/Test_mobius_bcg.cc | 14 +- tests/solver/Test_split_grid.cc | 14 +- 5 files changed, 300 insertions(+), 342 deletions(-) diff --git a/Grid/lattice/Lattice_transfer.h b/Grid/lattice/Lattice_transfer.h index d485827cc..6b4e82a71 100644 --- a/Grid/lattice/Lattice_transfer.h +++ b/Grid/lattice/Lattice_transfer.h @@ -1550,212 +1550,277 @@ void precisionChange(Lattice &out, const Lattice &in){ //////////////////////////////////////////////////////////////////////////////// // Communicate between grids //////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////////////// -// SIMPLE CASE: -/////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +// Grid_split / Grid_unsplit // -// Mesh of nodes (2x2) ; subdivide to 1x1 subdivisions +// The split grid divides the full grid's processor layout Pf into partitions of layout +// Ps; ratio R = Pf/Ps per dimension, nvector = prod R partitions. A rank at full processor +// coordinate c belongs to partition q = c/Ps at split coordinate s = c%Ps, and partition q +// holds vector index lex(q,R) (GridSplitVectorIndex). Local volumes satisfy +// Ls = Lf * R per dimension. // -// Lex ord: -// N0 va0 vb0 vc0 vd0 N1 va1 vb1 vc1 vd1 -// N2 va2 vb2 vc2 vd2 N3 va3 vb3 vc3 vd3 +// Every full-grid rank's local block of field v lies wholly inside one split-grid rank's +// local volume, in the sub-box at offset o = c%R (units of Lf), on the rank of partition +// q(v) at split coordinate c/R. The redistribution is therefore nvector whole-block +// point-to-point exchanges, nothing finer, and no data moves twice. Step k (a coordinate in +// the R box) pairs every rank with one sender and one receiver: // -// Ratio = full[dim] / split[dim] +// send field q = (c%R + k)%R to full coordinate q*Ps + c/R +// recv sub-box o = (c/Ps - k)%R from full coordinate (c%Ps)*R + o // -// For each dimension do an all to all; get Nvec -> Nvec / ratio -// Ldim -> Ldim * ratio -// LocalVol -> LocalVol * ratio -// full AllToAll(0) -// N0 va0 vb0 va1 vb1 N1 vc0 vd0 vc1 vd1 -// N2 va2 vb2 va3 vb3 N3 vc2 vd2 vc3 vd3 +// Within a step the send map is a permutation of the ranks and the receive map is its +// inverse, so each step is one symmetric SendToRecvFrom per rank; a rank paired with +// itself copies locally. Grid_unsplit runs the same steps with the roles exchanged. // -// REARRANGE -// N0 va01 vb01 N1 vc01 vd01 -// N2 va23 vb23 N3 vc23 vd23 -// -// full AllToAll(1) // Not what is wanted. FIXME -// N0 va01 va23 N1 vc01 vc23 -// N2 vb01 vb23 N3 vd01 vd23 -// -// REARRANGE -// N0 va0123 N1 vc0123 -// N2 vb0123 N3 vd0123 -// -// Must also rearrange data to get into the NEW lex order of grid at each stage. Some kind of "insert/extract". -// NB: Easiest to programme if keep in lex order. -/* - * Let chunk = (fvol*nvec)/sP be size of a chunk. ( Divide lexico vol * nvec into fP/sP = M chunks ) - * - * 2nd A2A (over sP nodes; subdivide the fP into sP chunks of M) - * - * node 0 1st chunk of node 0M..(1M-1); 2nd chunk of node 0M..(1M-1).. data chunk x M x sP = fL / sP * M * sP = fL * M growth - * node 1 1st chunk of node 1M..(2M-1); 2nd chunk of node 1M..(2M-1).. - * node 2 1st chunk of node 2M..(3M-1); 2nd chunk of node 2M..(3M-1).. - * node 3 1st chunk of node 3M..(3M-1); 2nd chunk of node 2M..(3M-1).. - * etc... - */ +// Data stays on the device. A block on the full grid is the local volume in lexicographic +// order; the split grid's local volume is staged box-major (sub-box index, then +// lexicographic within the sub-box), so every exchanged block is contiguous. Packing and +// unpacking are accelerator kernels. Buffers are deviceVectors: the staged split volume +// plus one block. Without ACCELERATOR_AWARE_MPI each block is staged through host memory. +///////////////////////////////////////////////////////////////////////////////////////////// + +// Scalar sites of in, written to buf in box-major order: sub-boxes of extent bdims taken +// lexicographically, sites lexicographically within each. bdims = local dimensions gives +// plain lexicographic order. +template +void GridSplitUnvectorise(const Lattice &in,typename vobj::scalar_object *buf,const Coordinate &bdims) +{ + GridBase *grid = in.Grid(); + int nd = grid->_ndimension; + Coordinate rdims = grid->_rdimensions; + Coordinate simd = grid->_simd_layout; + Coordinate nbox(nd); + int64_t bvol = 1; + for(int d=0;d_ldimensions[d] % bdims[d]) == 0 ); + nbox[d] = grid->_ldimensions[d] / bdims[d]; + bvol *= bdims[d]; + } + const int Nsimd = grid->Nsimd(); + autoView( in_v , in, AcceleratorRead); + accelerator_for(ss, grid->oSites(), 1, { + Coordinate ocoor(nd); + Coordinate icoor(nd); + Coordinate bcoor(nd); + Coordinate wcoor(nd); + Lexicographic::CoorFromIndex(ocoor,ss,rdims); + for(int lane=0;lane +void GridSplitVectorise(const typename vobj::scalar_object *buf,Lattice &out,const Coordinate &bdims) +{ + GridBase *grid = out.Grid(); + int nd = grid->_ndimension; + Coordinate rdims = grid->_rdimensions; + Coordinate simd = grid->_simd_layout; + Coordinate nbox(nd); + int64_t bvol = 1; + for(int d=0;d_ldimensions[d] % bdims[d]) == 0 ); + nbox[d] = grid->_ldimensions[d] / bdims[d]; + bvol *= bdims[d]; + } + const int Nsimd = grid->Nsimd(); + autoView( out_v , out, AcceleratorWriteDiscard); + accelerator_for(ss, grid->oSites(), 1, { + Coordinate ocoor(nd); + Coordinate icoor(nd); + Coordinate bcoor(nd); + Coordinate wcoor(nd); + Lexicographic::CoorFromIndex(ocoor,ss,rdims); + for(int lane=0;lane Field; + std::vector FieldRank; + std::vector Box; + std::vector BoxRank; + + GridSplitSchedule(GridBase *_full_grid,GridBase *split_grid) : full_grid(_full_grid) + { + nd = full_grid->_ndimension; + GRID_ASSERT(split_grid->_ndimension == nd); + ratio.resize(nd); + fldims = full_grid->_ldimensions; + Coordinate sprocs = split_grid->_processors; + Coordinate fcoor = full_grid->_processor_coor; + nvector = 1; + for(int d=0;d_gdimensions[d] == split_grid->_gdimensions[d]); + GRID_ASSERT(full_grid->_fdimensions[d] == split_grid->_fdimensions[d]); + GRID_ASSERT( (full_grid->_processors[d] % sprocs[d]) == 0 ); + ratio[d] = full_grid->_processors[d] / sprocs[d]; + GRID_ASSERT(split_grid->_ldimensions[d] == fldims[d]*ratio[d]); + nvector *= ratio[d]; + } + GRID_ASSERT(nvector*split_grid->_Nprocessors == full_grid->_Nprocessors); + + Field.resize(nvector); + FieldRank.resize(nvector); + Box.resize(nvector); + BoxRank.resize(nvector); + for(int k=0;kRankFromProcessorCoor(dest); + BoxRank[k] = full_grid->RankFromProcessorCoor(from); + } + } + + // One step's exchange; returns the bytes that left this rank + uint64_t Exchange(void *xmit,int dest,void *recv,int from,uint64_t bytes) + { + int me = full_grid->ThisRank(); + // The send map is a permutation and the receive map its inverse: self pairs with self + GRID_ASSERT( (dest == me) == (from == me) ); + if ( dest == me ) { + acceleratorCopyDeviceToDevice(xmit,recv,bytes); + return 0; + } + GRID_ASSERT( (bytes % sizeof(int32_t)) == 0 ); + GRID_ASSERT( bytes/sizeof(int32_t) < (1ULL<<31) ); // SendToRecvFrom counts int32 words +#ifdef ACCELERATOR_AWARE_MPI + full_grid->SendToRecvFrom(xmit,dest,recv,from,bytes); +#else + std::vector hxmit(bytes); + std::vector hrecv(bytes); + acceleratorCopyFromDevice(xmit,&hxmit[0],bytes); + full_grid->SendToRecvFrom(&hxmit[0],dest,&hrecv[0],from,bytes); + acceleratorCopyToDevice(&hrecv[0],recv,bytes); +#endif + return bytes; + } +}; + +// Common body: full[v] may alias one another (the single field overload) template -void Grid_split(std::vector > & full,Lattice & split) +void Grid_split(const std::vector *> &full,Lattice &split) { typedef typename Vobj::scalar_object Sobj; - int full_vecs = full.size(); - - GRID_ASSERT(full_vecs>=1); - - GridBase * full_grid = full[0].Grid(); + GRID_ASSERT(full.size()>=1); + GridBase *full_grid = full[0]->Grid(); GridBase *split_grid = split.Grid(); - int ndim = full_grid->_ndimension; - int full_nproc = full_grid->_Nprocessors; - int split_nproc =split_grid->_Nprocessors; - - //////////////////////////////// - // Checkerboard management - //////////////////////////////// - int cb = full[0].Checkerboard(); + int cb = full[0]->Checkerboard(); split.Checkerboard() = cb; - - ////////////////////////////// - // Checks - ////////////////////////////// - GRID_ASSERT(full_grid->_ndimension==split_grid->_ndimension); - for(int n=0;n_gdimensions[d]==split.Grid()->_gdimensions[d]); - GRID_ASSERT(full[n].Grid()->_fdimensions[d]==split.Grid()->_fdimensions[d]); - } + for(int n=0;nGrid() == full_grid); + GRID_ASSERT(full[n]->Checkerboard() == cb); } - int nvector =full_nproc/split_nproc; - GRID_ASSERT(nvector*split_nproc==full_nproc); - GRID_ASSERT(nvector == full_vecs); + double t_start = usecond(); + GridSplitSchedule sched(full_grid,split_grid); + GRID_ASSERT(sched.nvector == full.size()); - Coordinate ratio(ndim); - for(int d=0;d_processors[d]/ split_grid->_processors[d]; - } - - // Coarse phase timers, reported on GridLogPerformance - double t_start = usecond(); - double t_d2h = 0; - double t_unvec = 0; - double t_a2a = 0; - double t_reorder = 0; - double t_vec = 0; - double t0; - - uint64_t lsites = full_grid->lSites(); - uint64_t sz = lsites * nvector; - std::vector tmpdata(sz); - std::vector alldata(sz); - std::vector scalardata(lsites); + uint64_t fvol = full_grid->lSites(); + uint64_t bytes = fvol*sizeof(Sobj); + deviceVector staged(split_grid->lSites()); + deviceVector block(fvol); + Sobj *staged_p = &staged[0]; + Sobj *block_p = &block[0]; double t_alloc = usecond() - t_start; - for(int v=0;v_ldimensions); + double t1 = usecond(); + offrank += sched.Exchange((void *)block_p,sched.FieldRank[k], + (void *)&staged_p[sched.Box[k]*fvol],sched.BoxRank[k],bytes); + double t2 = usecond(); + t_pack += t1-t0; + t_comm += t2-t1; } - int nvec = nvector; // Counts down to 1 as we collapse dims - Coordinate ldims = full_grid->_ldimensions; + double t0 = usecond(); + GridSplitVectorise(staged_p,split,sched.fldims); + double t_unpack = usecond() - t0; + double t_total = usecond() - t_start; - for(int d=ndim-1;d>=0;d--){ - - if ( ratio[d] != 1 ) { - - t0 = usecond(); - full_grid ->AllToAll(d,alldata,tmpdata); - if ( split_grid->_processors[d] > 1 ) { - alldata=tmpdata; - split_grid->AllToAll(d,alldata,tmpdata); - } - t_a2a += usecond() - t0; - t0 = usecond(); - - auto rdims = ldims; - auto M = ratio[d]; - auto rsites= lsites*M;// increases rsites by M - nvec /= M; // Reduce nvec by subdivision factor - rdims[d] *= M; // increase local dim by same factor - - int sP = split_grid->_processors[d]; - int fP = full_grid->_processors[d]; - - int fvol = lsites; - - int chunk = (nvec*fvol)/sP; GRID_ASSERT(chunk*sP == nvec*fvol); - - // Loop over reordered data post A2A - thread_for(c, chunk, { - Coordinate coor(ndim); - for(int m=0;m +void Grid_split(std::vector > & full,Lattice & split) +{ + std::vector *> full_p(full.size()); + for(int n=0;n void Grid_split(Lattice &full,Lattice & split) { int nvector = full.Grid()->_Nprocessors / split.Grid()->_Nprocessors; - std::vector > full_v(nvector,full.Grid()); - for(int n=0;n *> full_p(nvector,&full); + Grid_split(full_p,split); } template @@ -1763,154 +1828,55 @@ void Grid_unsplit(std::vector > & full,Lattice & split) { typedef typename Vobj::scalar_object Sobj; - int full_vecs = full.size(); - - GRID_ASSERT(full_vecs>=1); - - GridBase * full_grid = full[0].Grid(); + GRID_ASSERT(full.size()>=1); + GridBase *full_grid = full[0].Grid(); GridBase *split_grid = split.Grid(); - int ndim = full_grid->_ndimension; - int full_nproc = full_grid->_Nprocessors; - int split_nproc =split_grid->_Nprocessors; - - //////////////////////////////// - // Checkerboard management - //////////////////////////////// int cb = full[0].Checkerboard(); split.Checkerboard() = cb; - - ////////////////////////////// - // Checks - ////////////////////////////// - GRID_ASSERT(full_grid->_ndimension==split_grid->_ndimension); - for(int n=0;n_gdimensions[d]==split.Grid()->_gdimensions[d]); - GRID_ASSERT(full[n].Grid()->_fdimensions[d]==split.Grid()->_fdimensions[d]); - } } - int nvector =full_nproc/split_nproc; - GRID_ASSERT(nvector*split_nproc==full_nproc); - GRID_ASSERT(nvector == full_vecs); + double t_start = usecond(); + GridSplitSchedule sched(full_grid,split_grid); + GRID_ASSERT(sched.nvector == full.size()); - Coordinate ratio(ndim); - for(int d=0;d_processors[d]/ split_grid->_processors[d]; - } - - // Coarse phase timers, reported on GridLogPerformance - double t_start = usecond(); - double t_a2a = 0; - double t_reorder = 0; - double t_vec = 0; - double t0; - - uint64_t lsites = full_grid->lSites(); - uint64_t sz = lsites * nvector; - std::vector tmpdata(sz); - std::vector alldata(sz); - std::vector scalardata(lsites); + uint64_t fvol = full_grid->lSites(); + uint64_t bytes = fvol*sizeof(Sobj); + deviceVector staged(split_grid->lSites()); + deviceVector block(fvol); + Sobj *staged_p = &staged[0]; + Sobj *block_p = &block[0]; double t_alloc = usecond() - t_start; - // Open and close a host view first so any device to host copy is timed on its own - t0 = usecond(); - { - autoView(split_v, split, CpuRead); + double t0 = usecond(); + GridSplitUnvectorise(split,staged_p,sched.fldims); + double t_pack = usecond() - t0; + + double t_comm = 0; + double t_unpack = 0; + uint64_t offrank = 0; + for(int k=0;k_ldimensions); + double t3 = usecond(); + t_comm += t2-t1; + t_unpack += t3-t2; } - double t_d2h = usecond() - t0; - t0 = usecond(); - unvectorizeToLexOrdArray(alldata,split); - double t_unvec = usecond() - t0; - - ///////////////////////////////////////////////////////////////// - // Start from split grid and work towards full grid - ///////////////////////////////////////////////////////////////// - - int nvec = 1; - uint64_t rsites = split_grid->lSites(); - Coordinate rdims = split_grid->_ldimensions; - - for(int d=0;d_processors[d]; - int fP = full_grid->_processors[d]; - - auto ldims = rdims; ldims[d] /= M; // Decrease local dims by same factor - auto lsites= rsites/M; // Decreases rsites by M - - int fvol = lsites; - int chunk = (nvec*fvol)/sP; GRID_ASSERT(chunk*sP == nvec*fvol); - - t0 = usecond(); - { - // Loop over reordered data post A2A - thread_for(c, chunk,{ - Coordinate coor(ndim); - for(int m=0;m_processors[d] > 1 ) { - split_grid->AllToAll(d,tmpdata,alldata); - tmpdata=alldata; - } - full_grid ->AllToAll(d,tmpdata,alldata); - t_a2a += usecond() - t0; - rdims[d]/= M; - rsites /= M; - nvec *= M; // Increase nvec by subdivision factor - } - } - - t0 = usecond(); - lsites = full_grid->lSites(); - for(int v=0;v> mpi_split[k]; - } - break; + if ( GridCmdOptionExists(argv,argv+argc,"--split") ) { + std::vector split; + GridCmdOptionIntVector(GridCmdOptionPayload(argv,argv+argc,"--split"),split); + GRID_ASSERT(split.size() == mpi_layout.size()); + for(int k=0;k> mpi_split[k]; - } - break; + if ( GridCmdOptionExists(argv,argv+argc,"--split") ) { + std::vector split; + GridCmdOptionIntVector(GridCmdOptionPayload(argv,argv+argc,"--split"),split); + GRID_ASSERT(split.size() == mpi_layout.size()); + for(int k=0;k> mpi_split[k]; - } - break; + if ( GridCmdOptionExists(argv,argv+argc,"--split") ) { + std::vector split; + GridCmdOptionIntVector(GridCmdOptionPayload(argv,argv+argc,"--split"),split); + GRID_ASSERT(split.size() == mpi_layout.size()); + for(int k=0;k> mpi_split[k]; - } - break; + if ( GridCmdOptionExists(argv,argv+argc,"--split") ) { + std::vector split; + GridCmdOptionIntVector(GridCmdOptionPayload(argv,argv+argc,"--split"),split); + GRID_ASSERT(split.size() == mpi_layout.size()); + for(int k=0;k