From 3a3a20b8c9cfbc65f34a43912a622870256ecf01 Mon Sep 17 00:00:00 2001 From: Peter Boyle Date: Tue, 29 Sep 2026 13:46:09 -0400 Subject: [PATCH] Updates --- Grid/algorithms/multigrid/BlockCyclic.h | 3 +- .../GeneralCoarsenedMatrixMultiRHS.h | 1272 +++++++++++++++++ MPI_benchmark/simple_reproducer_noncache.cc | 214 +++ ...Example_pvdagm_3level_DenseCoarseMatrix.cc | 990 +++++++++++++ .../Example_pvdagm_3level_FourierSmoother.cc | 1145 +++++++++++++++ systems/WorkArounds.txt | 85 +- tests/debug/Test_blockcyclic.cc | 2 +- tests/debug/Test_coarse.cc | 261 ++++ tests/debug/Test_coarse_coarsen.cc | 279 ++++ tests/debug/Test_mrhs_deflation.cc | 89 ++ tests/debug/Test_sloppy_dagger.cc | 207 +++ 11 files changed, 4506 insertions(+), 41 deletions(-) create mode 100644 Grid/algorithms/multigrid/GeneralCoarsenedMatrixMultiRHS.h create mode 100644 MPI_benchmark/simple_reproducer_noncache.cc create mode 100644 examples/Example_pvdagm_3level_DenseCoarseMatrix.cc create mode 100644 examples/Example_pvdagm_3level_FourierSmoother.cc create mode 100644 tests/debug/Test_coarse.cc create mode 100644 tests/debug/Test_coarse_coarsen.cc create mode 100644 tests/debug/Test_mrhs_deflation.cc create mode 100644 tests/debug/Test_sloppy_dagger.cc diff --git a/Grid/algorithms/multigrid/BlockCyclic.h b/Grid/algorithms/multigrid/BlockCyclic.h index c27f53768..eda021195 100644 --- a/Grid/algorithms/multigrid/BlockCyclic.h +++ b/Grid/algorithms/multigrid/BlockCyclic.h @@ -39,8 +39,7 @@ typedef ComplexD DenseInverseScalar; // BlockCyclicLayout: the index arithmetic of a 2D block-cyclic distribution // of an N x N matrix over a Pr x Pc logical process grid with block size nb. // -// This is stage 1 of the 2D distributed dense inverse -// (documentation/DistributedDenseInverse2D.tex). It is deliberately +// This is stage 1 of the 2D distributed dense inverse. It is deliberately // COMMUNICATOR-FREE: every mapping is a static pure function of // (N, nb, Pr, Pc), so the whole layout is exhaustively unit-testable on one // rank with no MPI in the loop (Test_blockcyclic). A thin instance layer diff --git a/Grid/algorithms/multigrid/GeneralCoarsenedMatrixMultiRHS.h b/Grid/algorithms/multigrid/GeneralCoarsenedMatrixMultiRHS.h new file mode 100644 index 000000000..54c0d67b5 --- /dev/null +++ b/Grid/algorithms/multigrid/GeneralCoarsenedMatrixMultiRHS.h @@ -0,0 +1,1272 @@ +/************************************************************************************* + + Grid physics library, www.github.com/paboyle/Grid + + Source file: ./lib/algorithms/GeneralCoarsenedMatrixMultiRHS.h + + Copyright (C) 2015 + +Author: Peter Boyle + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + See the full license in the file "LICENSE" in the top level distribution directory +*************************************************************************************/ +/* END LEGAL */ +#pragma once + + +NAMESPACE_BEGIN(Grid); + + +// Fine Object == (per site) type of fine field +// nbasis == number of deflation vectors +template +class MultiGeneralCoarsenedOperator : public SparseMatrixBase > > { +public: + typedef typename CComplex::scalar_object SComplex; + // The BLAS scalar of this level follows the coefficient precision: + // ComplexF for an fp32 coarse space, ComplexD for fp64. + typedef typename GridTypeMapper::scalar_type CoarseBLASScalar; + typedef MultiGeneralCoarsenedOperator MultiGeneralCoarseOp; + + typedef iVector siteVector; + typedef iMatrix siteMatrix; + typedef iVector calcVector; + typedef iMatrix calcMatrix; + typedef Lattice > CoarseComplexField; + typedef Lattice CoarseVector; + typedef Lattice > CoarseMatrix; + typedef iMatrix Cobj; + typedef iVector Cvec; + typedef Lattice< CComplex > CoarseScalar; // used for inner products on fine field + typedef Lattice FineField; + typedef CoarseVector Field; + + // Block operations on the fine vectors carry the fine layout, which need + // not be the coarse one + typedef decltype(innerProduct(Fobj(),Fobj())) FineInner; + typedef Lattice FineComplexField; + typedef Lattice BlockComplexField; + + //////////////////// + // Data members + // + // Nrhs independent: the D dimensional coarse grid, the geometry, the padded + // cell that supplies the stencil grid, the stencil, and the matrix elements. + // + // Nrhs dependent: the D+1 grid, its padded cell, and the BLAS B/C buffers + // with their pointer tables. Owned by SetNRHS(). + //////////////////// + GridCartesian * _CoarseGrid; // D dimensional + NonLocalStencilGeometry geom; + NonLocalStencilGeometry geom_srhs; + PaddedCell CellD; // D dimensional, supplies stencil grid + GeneralLocalStencil Stencil; // D dimensional + + int _Nrhs; + GridCartesian * _CoarseGridMulti; // D+1 dimensional, SetNRHS + PaddedCell * CellMulti; // D+1 dimensional, SetNRHS + + deviceVector BLAS_B; + deviceVector BLAS_C; + // The matrix, ONE buffer, site major: a site's npoint blocks are contiguous. + // Both organisations read it through their pointer tables, the folded one a + // site at a time and the unfolded one a point at a time with a stride of the + // whole stencil, so nothing is duplicated. Writing or reading one point + // goes through a single-point scratch. + deviceVector BLAS_Amat; // [site][point] + deviceVector BLAS_Ascr; // one point, for I/O + + std::vector > BLAS_AP; + std::vector > BLAS_BP; + deviceVector BLAS_CP; + + /////////////////////////////////////////////////////////////////////////// + // Stencil legs carried in the BATCH dimension, LegGroup at a time. + // + // One call per stencil point batches over the local coarse volume alone, + // which at the production point is 1024 sites. That is too few for the + // single-precision kernel: it then runs at half the bandwidth the double + // one reaches and takes the same time, so an fp32 coarse space buys + // nothing in the operator. The same shape at four times the batch runs + // 1.7x faster in fp32 than fp64, so the fix is more batch, not fewer + // bytes. Grouping G legs multiplies the batch by G and divides the launch + // count by G, at the price of G partial outputs summed at the end. + // + // G must divide npoint; 3 divides both the 33 point (next-to-nearest) and + // 81 point (full box) stencils. G=1 is the ungrouped form. The only + // numerical difference is the ORDER of the sum over stencil points. + /////////////////////////////////////////////////////////////////////////// + // The group need not divide npoint: the last call carries the remainder, + // with its own smaller tables. The batch a call presents is LegGroup times + // the local coarse volume, so the group that saturates the kernel depends + // on the decomposition, and the default is chosen from it rather than + // fixed. TargetBatch is where the measured single-precision curve flattens + // (530 GB/s at 1024, 695 at 9216, 728 at 33792 for 60x12x60 on MI250X). + // + // 9216 is nine times the production local coarse volume, so the default + // lands on nine legs per call: 81 = 9x9 for the full-box stencil and + // 33 = 9+9+9+6 for the next-to-nearest one, which is the grouping the + // earlier HDCG coarse operator used. + static const int TargetBatch = 9216; + + int LegGroup = 1; + std::vector > BLAS_APg; // per group + std::vector > BLAS_BPg; + deviceVector BLAS_CPg; // LegGroup*sites + deviceVector BLAS_CPr; // remainder*sites + deviceVector BLAS_Cg; // LegGroup partials + + int LegGroups(void) const { return geom.npoint/LegGroup; } // full + int LegRemainder(void) const { return geom.npoint%LegGroup; } + + /////////////////////////////////////////////////////////////////////////// + // Matrix pointer table for the grouped call. Group g holds legs + // g*LegGroup .. and the batch index runs site fastest within a leg. The + // last group is short when LegGroup does not divide npoint. + /////////////////////////////////////////////////////////////////////////// + void BuildGroupedA(void) + { + int32_t sites = _CoarseGrid->lSites(); + int ng = LegGroups() + (LegRemainder()?1:0); + BLAS_APg.resize(ng); + for(int g=0;g BLAS_BK; // [site][rhs][leg][nbasis] + std::vector > BLAS_AKP; // per chunk, per site + std::vector > BLAS_BKP; // per chunk, per site + deviceVector BLAS_BPflat; // [point][site] neighbours + + int KChunks(void) const { return (geom.npoint + KFold - 1)/KFold; } + int KLegs(int g) const { return std::min(KFold, geom.npoint - g*KFold); } + + /////////////////////////////////////////////////////////////////////////// + // Repack the matrix so a site's stencil points are contiguous: the folded + // GEMM reads each site's block as one column-major nbasis x (legs*nbasis) + // matrix, which is also a single sequential stream rather than npoint + // interleaved ones. Costs one pass over the matrix, once per solve setup. + /////////////////////////////////////////////////////////////////////////// + void PackFoldedMatrix(void) + { + // Nothing to pack: the matrix is already site major. Only the pointer + // table is built, one entry per site per chunk, at the chunk's first leg. + int32_t sites = _CoarseGrid->lSites(); + const int np = geom.npoint; + BLAS_AKP.resize(KChunks()); + for(int g=0;g geom.npoint ) K = geom.npoint; + return K; + } + // Legs per call that brings the batch up to where the kernel saturates. + int AutoLegGroup(void) const + { + int32_t sites = _CoarseGrid->lSites(); + int G = (TargetBatch + sites - 1)/sites; + if ( G < 1 ) G = 1; + if ( G > geom.npoint ) G = geom.npoint; + return G; + } + + /////////////////////////////////////////////////////////////////////////// + // The fold along K gathers the neighbours, which costs nbasis*nrhs per leg + // per site: 16 MB of traffic per apply at one right hand side and 195 at + // twelve, against a matrix of 973. Measured at the production point it is + // worth 7.6% at one right hand side and costs 7.6% at twelve, so the fold + // is used only for a small batch, and a large one carries its legs in the + // batch dimension instead. + /////////////////////////////////////////////////////////////////////////// + static const int FoldNrhsMax = 4; + + void ChooseOrganisation(int nrhs) + { + if ( nrhs <= FoldNrhsMax ) { + KFold = AutoKFold(); + } else { + KFold = 0; + } + LegGroup = AutoLegGroup(); + if ( KFold ) { + PackFoldedMatrix(); + } else { + BuildGroupedA(); + } + } + + /////////////////////// + // Interface + /////////////////////// + GridBase * Grid(void) { CheckGridSet(); return _CoarseGridMulti; }; + GridCartesian * CoarseGrid(void) { CheckGridSet(); return _CoarseGridMulti; }; + GridCartesian * CoarseGridD(void) { return _CoarseGrid; }; // lower dimensional grid + int Nrhs(void) { CheckGridSet(); return _Nrhs; }; + + void CheckGridSet(void) + { + if ( _CoarseGridMulti == nullptr ) { + std::cout << GridLogError + << "MultiGeneralCoarsenedOperator: the multiRHS grid has not been set." + << std::endl; + std::cout << GridLogError + << " Call SetGrid(CoarseGridMulti) with the D+1 dimensional grid your" + << std::endl; + std::cout << GridLogError + << " coarse vectors live on, before Grid(), Nrhs() or M()." + << std::endl; + GRID_ASSERT(_CoarseGridMulti != nullptr); + } + } + + ////////////////////////////////////////////////////////////////////////// + // Accessors. Note Grid() is the D+1 multiRHS grid, so a consumer wanting + // the space the matrix elements live on must ask for CoarseGridD(). + ////////////////////////////////////////////////////////////////////////// + // Resident device memory this operator holds (matrix elements and the + // Nrhs-dependent BLAS buffers). Not evictable. + uint64_t DeviceBytes(void) + { + uint64_t b = (uint64_t)(BLAS_B.capacity()+BLAS_C.capacity()+BLAS_Cg.capacity())*sizeof(calcVector) + + (uint64_t)BLAS_BK.capacity()*sizeof(CoarseBLASScalar); + b += (uint64_t)(BLAS_Amat.capacity()+BLAS_Ascr.capacity())*sizeof(calcMatrix); + return b; + } + + NonLocalStencilGeometry & Geometry(void) { return geom_srhs; }; + + // One stencil point in or out of the site-major buffer. + void MatrixPointIn(int p,const deviceVector &src) + { + int32_t sites = _CoarseGrid->lSites(); + const int np = geom.npoint; + calcMatrix *dst = &BLAS_Amat[0]; + const calcMatrix *sp = &src[0]; + accelerator_for(ss,sites,1,{ dst[(uint64_t)ss*np+p] = sp[ss]; }); + } + void MatrixPointOut(int p,deviceVector &dst) + { + int32_t sites = _CoarseGrid->lSites(); + const int np = geom.npoint; + const calcMatrix *sp = &BLAS_Amat[0]; + dst.resize(sites); + calcMatrix *dp = &dst[0]; + accelerator_for(ss,sites,1,{ dp[ss] = sp[(uint64_t)ss*np+p]; }); + } + void ExtractMatrix(int p,CoarseMatrix &A) + { + MatrixPointOut(p,BLAS_Ascr); + BLAStoGrid(A,BLAS_Ascr); + } + + // I/O on the operator matrices, via the BLAS layout array. The parameter is + // a vector over the geometry points; the body indexes A[p]. + void SetMatrix (int p,std::vector & A) + { + GRID_ASSERT(A.size()==geom_srhs.npoint); + GridtoBLAS(A[p],BLAS_Ascr); + MatrixPointIn(p,BLAS_Ascr); + } + void GetMatrix (int p,std::vector & A) + { + GRID_ASSERT(A.size()==geom_srhs.npoint); + MatrixPointOut(p,BLAS_Ascr); + BLAStoGrid(A[p],BLAS_Ascr); + } + + /////////////////////////////////////////////////////////////////////////// + // Constructor takes the D dimensional coarse grid. Everything built here + // is independent of Nrhs, in particular the matrix elements, which must + // survive a change of Nrhs untouched. + /////////////////////////////////////////////////////////////////////////// + MultiGeneralCoarsenedOperator(NonLocalStencilGeometry &_geom,GridCartesian *CoarseGrid) : + _CoarseGrid(CoarseGrid), + geom_srhs(_geom), + geom(CoarseGrid,_geom.hops,_geom.skip), + CellD(geom.Depth(),CoarseGrid), + Stencil(CellD.grids.back(),geom.shifts), // D dimensional padded cell stencil + _Nrhs(-1), + _CoarseGridMulti(nullptr), + CellMulti(nullptr) + { + int32_t unpadded_sites = _CoarseGrid->lSites(); + + ///////////////////////////////////////////////// + // Matrix elements and their pointer table + ///////////////////////////////////////////////// + BLAS_Amat.resize((uint64_t)unpadded_sites*geom.npoint); + BLAS_Ascr.resize(unpadded_sites); + BLAS_AP.resize(geom.npoint); + for(int p=0;p_ndimension; + + GRID_ASSERT(CoarseGridMulti->_ndimension == nd+1); + GRID_ASSERT(CoarseGridMulti->_processors[0] == 1); // rhs is not distributed + for(int d=0;d_fdimensions[d+1] == _CoarseGrid->_fdimensions[d]); + GRID_ASSERT(CoarseGridMulti->_processors [d+1] == _CoarseGrid->_processors [d]); + GRID_ASSERT(CoarseGridMulti->_simd_layout[d+1] == _CoarseGrid->_simd_layout[d]); + } + + _CoarseGridMulti = CoarseGridMulti; + _Nrhs = CoarseGridMulti->_fdimensions[0]; + GRID_ASSERT(_Nrhs>=1); + + int nrhs = _Nrhs; + + CellMulti = new PaddedCell(geom.Depth(),_CoarseGridMulti); + + int32_t padded_sites = CellD.grids.back()->lSites(); // D dimensional + int32_t unpadded_sites = _CoarseGrid->lSites(); // D dimensional + + // The neighbour offset multiplication by nrhs is exact only if the D+1 + // padded volume is nrhs copies of the D dimensional one. Check it. + GRID_ASSERT(CellMulti->grids.back()->lSites() == nrhs*padded_sites); + GRID_ASSERT(_CoarseGridMulti->lSites() == nrhs*unpadded_sites); + + ///////////////////////////////////////////////// + // Choose the organisation FIRST: every table below is sized from + // LegGroup or KFold, and is indexed with them in the apply, so the two + // must be decided before anything is allocated. + ///////////////////////////////////////////////// + ChooseOrganisation(nrhs); + std::cout << GridLogMessage << "MultiGeneralCoarsenedOperator: Nrhs " << nrhs + << ", coarse apply " + << ( KFold ? "folds "+std::to_string(KFold)+" legs along K (K = " + +std::to_string(KFold*nbasis)+", "+std::to_string(KChunks())+" chunk(s))" + : "carries "+std::to_string(LegGroup)+" legs in the batch" ) + << std::endl; + + ///////////////////////////////////////////////// + // Device data vector storage + ///////////////////////////////////////////////// + BLAS_B.resize(nrhs *padded_sites); // includes ghost zone + BLAS_C.resize(nrhs *unpadded_sites); // no ghost zone + BLAS_BP.resize(geom.npoint); + for(int p=0;p lSite, D dim + nbr = nbr*nrhs; // D -> D+1, rhs innermost + GRID_ASSERT(nbr void GridtoBLAS(const Lattice &from,deviceVector &to) + { + typedef typename vobj::scalar_object sobj; + typedef typename vobj::scalar_type scalar_type; + typedef typename vobj::vector_type vector_type; + + GridBase *Fg = from.Grid(); + GRID_ASSERT(!Fg->_isCheckerBoarded); + int nd = Fg->_ndimension; + + to.resize(Fg->lSites()); + + Coordinate LocalLatt = Fg->LocalDimensions(); + size_t nsite = 1; + for(int i=0;i_ostride; + Coordinate f_istride = Fg->_istride; + Coordinate f_rdimensions = Fg->_rdimensions; + + autoView(from_v,from,AcceleratorRead); + auto to_v = &to[0]; + + const int words=sizeof(vobj)/sizeof(vector_type); + accelerator_for(idx,nsite,1,{ + + Coordinate from_coor, base; + Lexicographic::CoorFromIndex(base,idx,LocalLatt); + for(int i=0;i void BLAStoGrid(Lattice &grid,deviceVector &in) + { + typedef typename vobj::scalar_object sobj; + typedef typename vobj::scalar_type scalar_type; + typedef typename vobj::vector_type vector_type; + + GridBase *Tg = grid.Grid(); + GRID_ASSERT(!Tg->_isCheckerBoarded); + int nd = Tg->_ndimension; + + GRID_ASSERT(in.size()==Tg->lSites()); + + Coordinate LocalLatt = Tg->LocalDimensions(); + size_t nsite = 1; + for(int i=0;i_ostride; + Coordinate t_istride = Tg->_istride; + Coordinate t_rdimensions = Tg->_rdimensions; + + autoView(to_v,grid,AcceleratorWrite); + auto from_v = &in[0]; + + const int words=sizeof(vobj)/sizeof(vector_type); + accelerator_for(idx,nsite,1,{ + + Coordinate to_coor, base; + Lexicographic::CoorFromIndex(base,idx,LocalLatt); + for(int i=0;i + // = \sum_{l in ball} e^{iqk.delta_l} A_ji^{b.b+l} + // = M_{kl} A_ji^{b.b+l} + // + // Where q_k = delta_k . (2*M_PI/global_nb[mu]) + // Then A{ji}^{b,b+l} = M^{-1}_{lm} ComputeProj_{m,b,i,j} + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + // Probe momenta. The stencil DISPLACEMENTS are the +-1 box; the momenta + // used to separate them are free, and are chosen here to span the + // Brillouin zone: k_mu = K_mu * shift_mu with K_mu ~ L_mu/3, so a phase + // is ~2pi/3 per unit displacement rather than 2pi/L_mu. + // + // This is what conditions the extraction. With K_mu = 1 every entry of + // the phase matrix tends to 1 as the coarse lattice grows, the matrix + // tends to rank one, and its inverse amplifies any error in the measured + // projections: at 24.24.16.32 the condition number is 2.7e5, so fp32 + // projections give coarse matrix elements wrong by ~1e-3. Spread momenta + // bring it to ~20, independent of the lattice size. + // + // K_mu is stepped away from L_mu/2, where +k and -k alias into the same + // momentum and the matrix is singular. + /////////////////////////////////////////////////////////////////////////// + void CoarsenMomenta(GridBase *CoarseGrid,Coordinate &K) + { + Coordinate clatt = CoarseGrid->GlobalDimensions(); + int Nd = CoarseGrid->Nd(); + K.resize(Nd); + for(int mu=0;mu=3) ? (int)std::lround(L/3.0) : 1; + if ( k < 1 ) k = 1; + if ( (L>2) && ((2*k)%L == 0) ) k = k-1; // +k and -k must differ + if ( k < 1 ) k = 1; + K[mu] = k; + } + } + + void CoarsenFourierMatrix(GridBase *CoarseGrid,Eigen::MatrixXcd &invMkl) + { + const int npoint = geom_srhs.npoint; + Coordinate clatt = CoarseGrid->GlobalDimensions(); + int Nd = CoarseGrid->Nd(); + Coordinate K; CoarsenMomenta(CoarseGrid,K); + + Eigen::MatrixXcd Mkl = Eigen::MatrixXcd::Zero(npoint,npoint); + ComplexD ci(0.0,1.0); + for(int k=0;k svd(Mkl); + RealD cond = svd.singularValues()(0)/svd.singularValues()(npoint-1); + std::cout << GridLogMessage << "CoarsenOperator: probe momenta "<_ndimension; + latt.resize(nd); simd.resize(nd); mpi.resize(nd); + for(int d=0;d_fdimensions[d]; + simd[d] = grid->_simd_layout[d]; + mpi [d] = CoarseGrid->_processors[d]; + } + } + + // D+1 coarse grid holding the batch, rhs innermost and unvectorised + void CoarsenBatchGridLayout(GridBase *CoarseGrid,int batch, + Coordinate &latt,Coordinate &simd,Coordinate &mpi) + { + latt.resize(1,batch); simd.resize(1,1); mpi.resize(1,1); + latt[0]=batch; simd[0]=1; mpi[0]=1; + for(int d=0;d_ndimension;d++){ + latt.push_back(CoarseGrid->_fdimensions[d]); + simd.push_back(CoarseGrid->_simd_layout[d]); + mpi .push_back(CoarseGrid->_processors[d]); + } + } + + /////////////////////////////////////////////////////////////////////////// + // The Fourier inverse needs the phase in the coarse layout and the basis + // phasing needs it in the fine layout; each is built from its own + // coordinates rather than transferred. + /////////////////////////////////////////////////////////////////////////// + void CoarsenPhases(GridBase *grid,GridBase *CoarseGrid,GridCartesian *BlockGrid, + std::vector &pha, + std::vector &phaF) + { + const int npoint = geom_srhs.npoint; + Coordinate clatt = CoarseGrid->GlobalDimensions(); + int Nd = CoarseGrid->Nd(); + Coordinate K; CoarsenMomenta(CoarseGrid,K); + ComplexD ci(0.0,1.0); + + // The fine-side scratch carries the FINE level's precision, which need + // not be the coarse one (fp64 fine, fp32 coarse at L1). + typedef typename GridTypeMapper::scalar_type FineScalar; + FineComplexField one(grid); one=FineScalar(1.0); + FineComplexField zz(grid); zz = Zero(); + BlockComplexField pha_blk (BlockGrid); + BlockComplexField blk_coor(BlockGrid); + + for(int p=0;p &pha, + CoarseComplexField &phaB, + CoarseVector &TmpProj, + std::vector &_A, + GridBase *CoarseGrid) + { + typedef typename CComplex::scalar_type SComplex; + const int npoint = geom_srhs.npoint; + + for(int b=0;boSites(); + for(int k=0;k > &linop, + GridCartesian *FineGridMulti, + std::vector &Subspace, + GridBase *CoarseGrid) + { + MultiRHSBlockProject > Projector; + CoarsenOperator(linop,FineGridMulti,Subspace,CoarseGrid,Projector); + } + template + void CoarsenOperator(LinearOperatorBase > &linop, + GridCartesian *FineGridMulti, + std::vector &Subspace, + GridBase *CoarseGrid, + Projector_t &Projector) + { + RealD tproj=0.0, tmat=0.0, tphase=0.0, tphaseBZ=0.0, tslice=0.0, tinv=0.0; + + std::cout << GridLogMessage<< "GeneralCoarsenMatrixMrhs (multiRHS fine operator)"<< std::endl; + + GRID_ASSERT(Subspace.size()==nbasis); + GridBase *grid = Subspace[0].Grid(); + + GRID_ASSERT(FineGridMulti->_ndimension == grid->_ndimension+1); + GRID_ASSERT(FineGridMulti->_processors[0] == 1); + for(int d=0;d_ndimension;d++){ + GRID_ASSERT(FineGridMulti->_fdimensions[d+1] == grid->_fdimensions[d]); + GRID_ASSERT(FineGridMulti->_processors [d+1] == grid->_processors [d]); + } + int batch = FineGridMulti->_fdimensions[0]; + + Coordinate blatt,bsimd,bmpi; + CoarsenBlockGridLayout(grid,CoarseGrid,blatt,bsimd,bmpi); + GridCartesian BlockGrid(blatt,bsimd,bmpi); + + BlockComplexField InnerProd(&BlockGrid); + blockOrthogonalise(InnerProd,Subspace); + + // the caller owns it; import the basis we have just orthonormalised + Projector.Allocate(nbasis,grid,CoarseGrid); + Projector.ImportBasis(Subspace); + + const int npoint = geom_srhs.npoint; + + Eigen::MatrixXcd invMkl; + CoarsenFourierMatrix(CoarseGrid,invMkl); + + FineField phaV(grid); + std::vector phaF(npoint,grid); + std::vector pha (npoint,CoarseGrid); + + tphase=-usecond(); + CoarsenPhases(grid,CoarseGrid,&BlockGrid,pha,phaF); + tphase+=usecond(); + + std::vector _A; + _A.resize(npoint,CoarseGrid); + for(int k=0;k > &linop, + std::vector &Subspace, + GridBase *CoarseGrid, + int batch) + { + MultiRHSBlockProject > Projector; + CoarsenOperator(linop,Subspace,CoarseGrid,batch,Projector); + } + template + void CoarsenOperator(LinearOperatorBase > &linop, + std::vector &Subspace, + GridBase *CoarseGrid, + int batch, + Projector_t &Projector) + { + RealD tproj=0.0, tmat=0.0, tphase=0.0, tphaseBZ=0.0, tslice=0.0, tinv=0.0; + + std::cout << GridLogMessage<< "GeneralCoarsenMatrixMrhs (single RHS fine operator)"<< std::endl; + + GRID_ASSERT(Subspace.size()==nbasis); + GRID_ASSERT(batch>=1); + GridBase *grid = Subspace[0].Grid(); + + Coordinate blatt,bsimd,bmpi; + CoarsenBlockGridLayout(grid,CoarseGrid,blatt,bsimd,bmpi); + GridCartesian BlockGrid(blatt,bsimd,bmpi); + + BlockComplexField InnerProd(&BlockGrid); + blockOrthogonalise(InnerProd,Subspace); + + // the caller owns it; import the basis we have just orthonormalised + Projector.Allocate(nbasis,grid,CoarseGrid); + Projector.ImportBasis(Subspace); + + const int npoint = geom_srhs.npoint; + + Eigen::MatrixXcd invMkl; + CoarsenFourierMatrix(CoarseGrid,invMkl); + + FineField phaV(grid); + std::vector phaF(npoint,grid); + std::vector pha (npoint,CoarseGrid); + + tphase=-usecond(); + CoarsenPhases(grid,CoarseGrid,&BlockGrid,pha,phaF); + tphase+=usecond(); + + std::vector _A; + _A.resize(npoint,CoarseGrid); + for(int k=0;k MphaV(batch,grid); + + for(int i0=0;i0M(in,out); + } + void M (const CoarseVector &in, CoarseVector &out) + { + // std::cout << GridLogMessage << "New Mrhs coarse"<ExchangePeriodic(in); }(); //padded input + t_exch+=usecond(); + + int npoint = geom.npoint; + typedef calcMatrix* Aview; + typedef LatticeView Vview; + + const int Nsimd = CComplex::Nsimd(); + + int64_t nrhs =pin.Grid()->GlobalDimensions()[0]; + GRID_ASSERT(nrhs>=1); + + RealD flops,bytes; + int64_t osites=in.Grid()->oSites(); // unpadded + int64_t unpadded_vol = CoarseGrid()->lSites()/nrhs; + + flops = 1.0* npoint * nbasis * nbasis * 8.0 * osites * CComplex::Nsimd(); + bytes = 1.0*osites*sizeof(siteMatrix)*npoint/pin.Grid()->GlobalDimensions()[0] + + 2.0*osites*sizeof(siteVector)*npoint; + + + t_GtoB=-usecond(); + { GRID_TRACE("CoarseGridToBLAS"); + GridtoBLAS(pin,BLAS_B); + } + t_GtoB+=usecond(); + + GridBLAS BLAS; + + t_mult=-usecond(); + { GRID_TRACE("CoarseStencilGEMM"); + // The scalar type selects the GEMM: Cgemm for an fp32 coarse space, + // Zgemm for fp64. + if ( KFold ) { + // Gather one chunk of neighbour vectors per site, then one GEMM whose + // contraction runs over the chunk's legs, accumulating into C. No + // partial results, so nothing to combine afterwards. + const int nb = nbasis; + const int64_t ns = (int64_t)_CoarseGrid->lSites(); // D dimensional: CoarseGrid() is D+1 + GRID_ASSERT( (int64_t)BLAS_BPflat.size() == (int64_t)geom.npoint*ns ); + CoarseBLASScalar * const bk = &BLAS_BK[0]; + CoarseBLASScalar * const * const bp = &BLAS_BPflat[0]; + for(int g=0;g 1 ) { GRID_TRACE("CoarseLegSum"); + // Sum the LegGroup partial results into the single output buffer. + // Flat over scalars: a calcVector is nbasis of them and the partials + // are contiguous, one whole C volume after another. + int64_t nscalar = (int64_t)BLAS_C.size()*nbasis; // one whole C volume + const int G = LegGroup; + CoarseBLASScalar *dst = (CoarseBLASScalar *)&BLAS_C[0]; + CoarseBLASScalar *src = (CoarseBLASScalar *)&BLAS_Cg[0]; + accelerator_for(i,nscalar,1,{ + CoarseBLASScalar sum = src[i]; + for(int l=1;l &out){assert(0);}; +}; + +NAMESPACE_END(Grid); diff --git a/MPI_benchmark/simple_reproducer_noncache.cc b/MPI_benchmark/simple_reproducer_noncache.cc new file mode 100644 index 000000000..5c3aa37ad --- /dev/null +++ b/MPI_benchmark/simple_reproducer_noncache.cc @@ -0,0 +1,214 @@ +// Minimal reproducer for the CXI uncached-registration defect. +// +// Hypothesis: with the libfabric MR cache disabled, a device pointer at a non-zero offset +// into a hipMalloc'd allocation is registered as the whole allocation and the offset is +// discarded, so a send transmits from the base and a receive lands at the base. +// +// Each test therefore prints, for both the data and its landing place, what a correct +// implementation must give, what the hypothesis predicts instead, and what was observed. +// +// hipcc -O2 -o simple_reproducer_noncache simple_reproducer_noncache.cc \ +// -I$MPICH_DIR/include -L$MPICH_DIR/lib -lmpi -L$MPICH_DIR/gtl/lib -lmpi_gtl_hsa +// +// MPICH_GPU_SUPPORT_ENABLED=1 FI_MR_CACHE_MAX_COUNT=0 \ +// srun -N2 -n2 --ntasks-per-node=1 ./simple_reproducer_noncache + +#include +#include +#include +#include +#include +#include + +// The message must be large enough to go rendezvous: an eager message is staged through a host +// bounce buffer with hipMemcpy, which honours the offset, and never registers the user pointer. +static const int NWORDS = 262144; // 2 MB buffers +static int NMSG = 16384; // 128 KB messages (argv[1], in words) +static int OFF = 16384; // offending offset in words (argv[2]); clearest at >= NMSG, + // which keeps the intended and predicted windows disjoint + +static int rank, size, peer; +static uint64_t *A, *B; +static std::vector h(NWORDS); + +#define HIP(cmd) do { hipError_t e=(cmd); if ( e != hipSuccess ) { \ + printf("hip error %s at line %d\n",hipGetErrorString(e),__LINE__); \ + MPI_Abort(MPI_COMM_WORLD,1); } } while(0) + +static uint64_t word(uint64_t tag,int r,int n) +{ + return (tag<<60) | ((uint64_t)r<<32) | (uint64_t)n; +} + +static const char *verdict(int observed,int correct,int predicted) +{ + if ( observed == correct ) return "CORRECT"; + if ( observed == predicted ) return "WRONG, as predicted"; + return "WRONG, and not as predicted"; +} + +static void refresh(void) +{ + for(int n=0;n>32) & 0xfffffff)); +} + +// Name a word: still the receive pattern, or a word of the neighbour's send buffer. +static const char *describe(uint64_t w,int idx) +{ + static char s[64]; + if ( w == word(0xb,rank,idx) ) snprintf(s,sizeof(s),"untouched"); + else if ( (w>>60) == 0xa ) snprintf(s,sizeof(s),"A[%d] of rank %d", + (int)(w & 0xffffffff),(int)((w>>32) & 0xfffffff)); + else snprintf(s,sizeof(s),"unrecognised"); + return s; +} + +static void report(const char *name,int sendoff,int recvoff) +{ + HIP(hipMemcpy(h.data(),B,NWORDS*sizeof(uint64_t),hipMemcpyDeviceToHost)); + + int first=-1, last=-1; + for(int n=0;n recv &B[%d]\n",rank,name,sendoff,recvoff); + if ( first < 0 ) { printf("rank %d nothing was written\n",rank); return; } + + uint64_t w = h[first]; + int src = (int)(w & 0xffffffff); + int sr = (int)((w>>32) & 0xfffffff); + + // The hypothesis predicts both offsets are discarded: the bytes at A[0], landing at B[0]. + printf("rank %d data : want A[%d] predict A[0] got A[%d] from rank %d %s\n", + rank,sendoff,src,sr,verdict(src,sendoff,0)); + printf("rank %d place : want B[%d] predict B[0] got B[%d] %s\n", + rank,recvoff,first,verdict(first,recvoff,0)); + + if ( src == sendoff && first == recvoff ) return; + + // Both candidate landing sites in our receive buffer, and both candidate source words in the + // neighbour's send buffer, which the fill pattern fixes by construction. + printf("rank %d Boff %d\n",rank,recvoff); + printf("rank %d B[0] = 0x%016llx %s\n", + rank,(unsigned long long)h[0],describe(h[0],0)); + if ( recvoff != 0 ) + printf("rank %d B[%d]\t= 0x%016llx %s\n", + rank,recvoff,(unsigned long long)h[recvoff],describe(h[recvoff],recvoff)); + printf("rank %d Neighbour rank %d send buffer holds\n",rank,peer); + printf("rank %d A[0]\t= 0x%016llx\n", + rank,(unsigned long long)word(0xa,peer,0)); + if ( sendoff != 0 ) + printf("rank %d A[%d]\t= 0x%016llx\n", + rank,sendoff,(unsigned long long)word(0xa,peer,sendoff)); + + window("intended",recvoff); + if ( recvoff != 0 ) window("predicted",0); + if ( first < recvoff || last >= recvoff+NMSG ) { + if ( first != 0 || last != NMSG-1 ) { + printf("rank %d stray : modified words span B[%d..%d], outside both windows\n", + rank,first,last); + } + } + + int bad=0; + for(int i=0;i 1 ) NMSG = atoi(argv[1]); + if ( argc > 2 ) OFF = atoi(argv[2]); + if ( NMSG < 1 || OFF < 0 || OFF+NMSG > NWORDS ) { + if ( rank == 0 ) printf("message %d and offset %d words do not fit in %d\n",NMSG,OFF,NWORDS); + MPI_Finalize(); + return 1; + } + + int ndev; + HIP(hipGetDeviceCount(&ndev)); + if ( ndev < 1 ) { + printf("rank %d sees no GPU\n",rank); + MPI_Abort(MPI_COMM_WORLD,1); + } + HIP(hipSetDevice(rank%ndev)); + HIP(hipMalloc(&A,NWORDS*sizeof(uint64_t))); + HIP(hipMalloc(&B,NWORDS*sizeof(uint64_t))); + + ordered(print_setup); + + const int offsets[4][2] = { {0,0}, {OFF,0}, {0,OFF}, {OFF,OFF} }; + const char *names[4] = { "test 1","test 2","test 3","test 4" }; + + for(int t=0;t<4;t++) { + refresh(); + exchange(offsets[t][0],offsets[t][1]); + test_name = names[t]; test_sendoff = offsets[t][0]; test_recvoff = offsets[t][1]; + ordered(print_report); + } + + HIP(hipFree(A)); + HIP(hipFree(B)); + MPI_Finalize(); + return 0; +} diff --git a/examples/Example_pvdagm_3level_DenseCoarseMatrix.cc b/examples/Example_pvdagm_3level_DenseCoarseMatrix.cc new file mode 100644 index 000000000..7b6ce5b18 --- /dev/null +++ b/examples/Example_pvdagm_3level_DenseCoarseMatrix.cc @@ -0,0 +1,990 @@ +/************************************************************************************* + + Grid physics library, www.github.com/paboyle/Grid + + Source file: ./examples/Example_pvdagm_3level_DenseCoarseMatrix.cc + + Copyright (C) 2026 + +Author: Peter Boyle + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + See the full license in the file "LICENSE" in the top level distribution directory +*************************************************************************************/ +/* END LEGAL */ + +// +// PVdagM three level multigrid on MultiGeneralCoarsenedOperator. +// +// STAGES ONE AND TWO: grids, types, subspace, and the L1 and L2 coarsenings. +// The dense bottom and the solves are not here yet. +// +// Differences from Example_pvdagm_mrhs_3level_DenseCoarseMatrix.cc: +// +// * The coarse space is UNVECTORISED (sComplexD). The fine space stays +// vectorised. MultiRHSBlockProject carries the mixed layout. +// +// * One operator, not two. The deprecated path needed +// DeprecatedGeneralCoarsenedMatrix to coarsen and +// DeprecatedMultiGeneralCoarsenedMatrix to apply, bridged by CopyMatrix. +// This operator does both, and single versus multiRHS is SetGrid on the +// same object with the matrix elements built once. +// +// * Nrhs is unconstrained. The deprecated path required nrhs % vComplex::Nsimd() == 0 because +// its multiRHS grid carried the SIMD in the rhs direction. +// +// * CoarsenOperator takes the subspace vectors, not an Aggregation. It block +// orthonormalises them IN PLACE -- the vectors are far too large to copy +// defensively -- so rawNull is taken first and the RAW vectors are what +// define the L2 null space. Do not insert an Orthogonalise() anywhere: +// projecting a block-orthonormal vector onto its own block-orthonormalised +// aggregation gives e_k, and the near null content is silently gone. The +// || - I||_F guard below is what catches that. +// +// Env: LATT LS MASS NBASIS(compile time) NRHS BLOCK BLOCK2 COARSEN_BATCH +// HOT_START CONFIG SUBSPACE_FILE DEPRECATED_CHECK MRHS_COARSEN +// + +#include +#include // std::sort, for the runtime-environment dump in ParseEnvironment +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace Grid; + +// Compile time so it can be cut down for laptop runs: -DNBASIS=8 +#ifndef NBASIS +#define NBASIS 60 +#endif + +RealD mass = 0.00078; +int Nrhs = 12; +int Ls = 24; +int CoarsenBatch = 9; +std::vector lat_size({48,48,48,96}); + +// Solver tuning. PRINCIPLE (PB, 2026-08-24): the defaults ARE the current +// optimum, so an unset environment reproduces the best banked result; they +// are updated as and when a better point is found, and every change is +// dated here. Environment variables of the same names override for sweeps. +// +// Current optimum: 2026-08-24, slurm-5335492 F4, 48^3x96 Ls=24 on 288 GCDs, +// 17.2 s/RHS at Nrhs=4, 32.2 s at Nrhs=1 (exact-halo FINAL ~1e-8 pending +// the exact-outer rerun). Smoother mmax == order (full GCR history); +// PB's mmax=1 trial gave 72 vs ~60 outer iterations and was slower. +RealD FineSmootherShift = 0.1; +int FineSmootherOrder = 6; +int FineSmootherMmax = 6; +RealD CoarseSmootherShift = 0.1; +int PowerIterations = 0; // >0: power-iterate the smoother operators before the solves (spectral edge) +// Smoother implementation per level (Smoothers.h): +// gcr : the adaptive PGCR (default, as always) +// replay : run the PGCR with a coefficient recorder for the first +// PolyRecordIters outer steps, then switch to GCRReplaySmoother +// (same polynomial, no inner products) -- 1402.2585 p.13 revisited +// cheb : ChebyshevNonHermitianSmoother, 1/x on [ChebLo,ChebHi], order +// = the GCR step count of that level +std::string FineSmootherMode = "gcr"; +std::string CoarseSmootherMode = "gcr"; +int PolyRecordIters = 4; +int PolyRecordStart = 0; // outer step at which recording begins (0: from the first step) +std::string PolyRecordSelect = "last"; // which recorded call to replay: last|first|mean (mean is the bad one) +int PolyRefresh = 0; // >0: every PolyRefresh outer steps, one adaptive step re-records the polynomial (HDCG: every 10) +int PolyVerbose = 0; // 1: fixed-polynomial smoothers print |r_m|/|r_0| per call +RealD FineChebLo = 3.0, FineChebHi = 137.0; // from the harvested polynomial and PowerIterations edge +RealD CoarseChebLo = 8.0, CoarseChebHi = 45.0; +int CoarseSmootherNstep = 2; +int CoarseSmootherMmax = 2; +RealD CoarseSolverTol = 0.05; +int CoarseSolverOrder = 200; +int CoarseSolverMmax = 16; +RealD OuterTol = 1.0e-8; +int OuterMmax = 4; +int OuterNstep = 8; + +// "It's legal to get the same answer faster, not to get a less correct +// answer." (PB, 2026-08-24) +// +// Halo-precision POLICY: reduced-precision (fp32 wire) +// halos belong in the PRECONDITIONER -- the smoother, the V-cycle's own +// residuals, and the coarsening -- and NEVER in the outer Krylov. The +// outer operator's applications define what "converged" means; making them +// sloppy turns the stopping criterion into a statement about the wrong +// operator (measured: solver stops at computed 9.8e-9 while the true +// residual is 3.4e-8). Exactness costs one exact fine matvec per outer +// iteration, a few percent of the solve. +// +// Stencil::SloppyComms is a free runtime setter (Stencil.h:303), so the +// policy is implemented by SCOPED toggling: SetFineSloppy(1) on entering +// the preconditioner / coarsening, SetFineSloppy(0) on leaving. The +// operators default to EXACT. FineSloppyComms therefore now means +// "sloppy inside the preconditioner"; =0 makes everything exact. +int FineSloppyComms = 1; +// SmootherCoeffLog=1 : print the GCR step lengths a_k and orthogonalisation +// coefficients b_kj of BOTH smoothers every call -- the harvest for a fixed +// polynomial smoother (stable coefficients => stationary p(A), no reductions). +int SmootherCoeffLog = 0; +std::function SetFineSloppy = [](int){}; + +void ParseEnvironment(void) +{ + if(getenv("MASS")) mass = atof(getenv("MASS")); + if(getenv("NRHS")) Nrhs = atoi(getenv("NRHS")); + if(getenv("LS")) Ls = atoi(getenv("LS")); + if(getenv("COARSEN_BATCH")) CoarsenBatch= atoi(getenv("COARSEN_BATCH")); + if(getenv("FineSmootherShift")) FineSmootherShift = atof(getenv("FineSmootherShift")); + if(getenv("FineSmootherOrder")) FineSmootherOrder = atoi(getenv("FineSmootherOrder")); + if(getenv("FineSmootherMmax")) FineSmootherMmax = atoi(getenv("FineSmootherMmax")); + if(getenv("CoarseSmootherShift"))CoarseSmootherShift= atof(getenv("CoarseSmootherShift")); + if(getenv("PowerIterations")) PowerIterations = atoi(getenv("PowerIterations")); + if(getenv("FineSmootherMode")) FineSmootherMode = getenv("FineSmootherMode"); + if(getenv("CoarseSmootherMode")) CoarseSmootherMode = getenv("CoarseSmootherMode"); + if(getenv("PolyRecordIters")) PolyRecordIters = atoi(getenv("PolyRecordIters")); + if(getenv("PolyRecordStart")) PolyRecordStart = atoi(getenv("PolyRecordStart")); + if(getenv("PolyRecordSelect")) PolyRecordSelect = getenv("PolyRecordSelect"); + if(getenv("PolyRefresh")) PolyRefresh = atoi(getenv("PolyRefresh")); + if(getenv("PolyVerbose")) PolyVerbose = atoi(getenv("PolyVerbose")); + if(getenv("FineChebLo")) FineChebLo = atof(getenv("FineChebLo")); + if(getenv("FineChebHi")) FineChebHi = atof(getenv("FineChebHi")); + if(getenv("CoarseChebLo")) CoarseChebLo = atof(getenv("CoarseChebLo")); + if(getenv("CoarseChebHi")) CoarseChebHi = atof(getenv("CoarseChebHi")); + if(getenv("CoarseSmootherNstep"))CoarseSmootherNstep= atoi(getenv("CoarseSmootherNstep")); + if(getenv("CoarseSmootherMmax")) CoarseSmootherMmax = atoi(getenv("CoarseSmootherMmax")); + if(getenv("CoarseSolverTol")) CoarseSolverTol = atof(getenv("CoarseSolverTol")); + if(getenv("CoarseSolverOrder")) CoarseSolverOrder = atoi(getenv("CoarseSolverOrder")); + if(getenv("CoarseSolverMmax")) CoarseSolverMmax = atoi(getenv("CoarseSolverMmax")); + if(getenv("OuterTol")) OuterTol = atof(getenv("OuterTol")); + if(getenv("OuterMmax")) OuterMmax = atoi(getenv("OuterMmax")); + if(getenv("FineSloppyComms")) FineSloppyComms = atoi(getenv("FineSloppyComms")); + if(getenv("SmootherCoeffLog")) SmootherCoeffLog = atoi(getenv("SmootherCoeffLog")); + if(getenv("OuterNstep")) OuterNstep = atoi(getenv("OuterNstep")); + if(getenv("LATT")){ + Coordinate l; + GridCmdOptionIntVector(std::string(getenv("LATT")),l); + GRID_ASSERT(l.size()==4); + for(int d=0;d<4;d++) lat_size[d]=l[d]; + } + + std::cout << GridLogMessage << "PARAM: LATT " + << lat_size[0]<<"."< hits; + for(char **e = environ; e && *e; e++){ + std::string s(*e); + for(auto p : prefixes){ + if( s.compare(0,strlen(p),p)==0 ){ + if(s.size()>200) s = s.substr(0,197)+"..."; // paths can be enormous + hits.push_back(s); + break; + } + } + } + std::sort(hits.begin(),hits.end()); + std::cout << GridLogMessage << "PARAM: ---- runtime environment: "< +void saveSubspace(std::vector &subspace, std::string const fname){ +#ifdef HAVE_LIME + Grid::emptyUserRecord record; + Grid::ScidacWriter SW(subspace[0].Grid()->IsBoss()); + SW.open(fname); + for (int k = 0; k < (int)subspace.size(); k++) SW.writeScidacFieldRecord(subspace[k], record); + SW.close(); +#endif +} +template +void loadSubspace(std::vector &subspace, std::string const fname){ +#ifdef HAVE_LIME + Grid::emptyUserRecord record; + Grid::ScidacReader SR; + SR.open(fname); + for (int k = 0; k < (int)subspace.size(); k++) SR.readScidacFieldRecord(subspace[k], record); + SR.close(); +#endif +} + +////////////////////////////////////////////////////////////////////// +// A = PV^dag M (non-Hermitian) +////////////////////////////////////////////////////////////////////// +template +class PVdagMLinearOperator : public LinearOperatorBase { + Matrix &_Mat; Matrix &_PV; +public: + PVdagMLinearOperator(Matrix &Mat,Matrix &PV): _Mat(Mat),_PV(PV) {}; + void OpDiag (const Field &in, Field &out) { assert(0); } + void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); } + void OpDirAll (const Field &in, std::vector &out){ assert(0); }; + void Op (const Field &in, Field &out){ Field tmp(in.Grid()); _Mat.M(in,tmp); _PV.Mdag(tmp,out); } + void AdjOp (const Field &in, Field &out){ Field tmp(in.Grid()); _PV.M(in,tmp); _Mat.Mdag(tmp,out); } + void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ HermOp(in,out); ComplexD d=innerProduct(in,out); n1=real(d); n2=norm2(out); } + void HermOp(const Field &in, Field &out){ Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); } +}; + +////////////////////////////////////////////////////////////////////// +// || - I||_F over a set of coarse vectors. Small means the raw near null +// content survived the projection; see GramGuard for where a leak lands. +////////////////////////////////////////////////////////////////////// +template +RealD GramDefect(std::vector &v) +{ + RealD s2=0.0; + for(int i=0;i<(int)v.size();i++){ + for(int j=0;j<(int)v.size();j++){ + ComplexD sij=TensorRemove(innerProduct(v[i],v[j])); + ComplexD d=sij-(i==j?ComplexD(1.0):ComplexD(0.0)); + s2+=real(d)*real(d)+imag(d)*imag(d); + } + } + return std::sqrt(s2); +} + +// On a leak every image collapses to the block unit e_k, the Gram becomes +// N*I, and the defect lands at (N-1)*sqrt(nbasis) -- orders above the ~0.2 +// of a content preserving projection. Trip well below that so a mis-set +// threshold costs a log line rather than the run. +template +void GramGuard(const std::string &name,std::vector &v,GridBase *grid) +{ + RealD defect = GramDefect(v); + RealD N = (RealD)grid->gSites(); + RealD leak = (N-1.0)*std::sqrt((RealD)v.size()); + RealD trip = std::sqrt(N); + std::cout << GridLogMessage << "GUARD: ||<"< - I||_F = " << defect + << " (e_k leak would be " << leak << ", trip at " << trip << ")" << std::endl; + GRID_ASSERT( defect < trip ); +} + + +////////////////////////////////////////////////////////////////////// +// Shifted variants for the smoothers +////////////////////////////////////////////////////////////////////// +template +class ShiftedPVdagMLinearOperator : public LinearOperatorBase { + Matrix &_Mat; Matrix &_PV; +public: + RealD shift; + ShiftedPVdagMLinearOperator(RealD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){}; + void OpDiag (const Field &in, Field &out) { assert(0); } + void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); } + void OpDirAll (const Field &in, std::vector &out){ assert(0); }; + void Op (const Field &in, Field &out){ Field tmp(in.Grid()); _Mat.M(in,tmp); _PV.Mdag(tmp,out); out = out + shift*in; } + void AdjOp (const Field &in, Field &out){ Field tmp(in.Grid()); _PV.M(tmp,out); _Mat.Mdag(in,tmp); out = out + shift*in; } + void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); } + void HermOp(const Field &in, Field &out){ Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); } +}; + +template +class ShiftedLinearOperator : public LinearOperatorBase { + LinearOperatorBase &_Op; RealD shift; +public: + ShiftedLinearOperator(RealD _shift, LinearOperatorBase &Op) : _Op(Op), shift(_shift) {} + void OpDiag (const Field &in, Field &out) { assert(0); } + void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); } + void OpDirAll (const Field &in, std::vector &out) { assert(0); } + void Op (const Field &in, Field &out) { _Op.Op(in,out); out = out + shift*in; } + void AdjOp (const Field &in, Field &out) { _Op.AdjOp(in,out); out = out + shift*in; } + void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); } + void HermOp (const Field &in, Field &out) { Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); } +}; + +////////////////////////////////////////////////////////////////////// +// Power iteration on a (non-Hermitian) operator: the spectral edge the +// smoother polynomial must not exceed. Reports +// step 0 : |A v|/|v| on a RANDOM unit v -- a one-sample lower bound on +// sigma_max(A). If this and the converged value agree, the +// operator is near-normal and the spectral picture (R_m(lambda) +// on the spectrum) is trustworthy; if not, the field of values +// sets the safe interval and the spectrum understates it. +// step k : |A v_k|/|v_k| -> |lambda_max| as v_k -> the dominant +// eigenvector; the complex Rayleigh quotient gives its +// phase (real => on the axis). A non-converging oscillation +// means a complex-conjugate pair of equal modulus at the top. +// Uses Op(), not HermOp(): this is the operator the smoother sees. +////////////////////////////////////////////////////////////////////// +template +void PowerIteration(const std::string &name, LinearOperatorBase &Op, GridBase *grid, int iters) +{ + GRID_TRACE("PowerIteration"); + GridParallelRNG RNG(grid); RNG.SeedFixedIntegers(std::vector({7,11,13,17})); + Field v(grid), Av(grid); + gaussian(RNG,v); + RealD nv = std::sqrt(norm2(v)); v = v*(1.0/nv); + RealD ratio=0.0, ratio0=0.0; ComplexD rq(0.0); + for(int i=0;iL2 blocking; banked optimum 2026-08-24 (env BLOCK overrides) + if ( getenv("BLOCK") ){ GridCmdOptionIntVector(std::string(getenv("BLOCK")),Block); GRID_ASSERT(Block.size()==4); } + for(int d=0;d<4;d++){ GRID_ASSERT(lat_size[d]%Block[d]==0); clatt[d]=lat_size[d]/Block[d]; } + std::cout << GridLogMessage << "Block " << Block << " coarse lattice " << clatt << std::endl; + + ////////////////////////////////////////////////////////////////////// + // The coarse space is unvectorised. The 5D coarse grid is built here + // rather than through SpaceTimeGrid so the SIMD layout is ours. + ////////////////////////////////////////////////////////////////////// + Coordinate c5latt({1,clatt[0],clatt[1],clatt[2],clatt[3]}); + Coordinate c5simd({1,1,1,1,1}); + Coordinate c5mpi ({1,mpi[0],mpi[1],mpi[2],mpi[3]}); + GridCartesian *Coarse5d = new GridCartesian(c5latt,c5simd,c5mpi); + + // 6D coarse multiRHS grid: rhs is dim 0, undistributed and unvectorised. + // No divisibility constraint on nrhs, unlike the deprecated operators. + Coordinate cmlatt({nrhs,1,clatt[0],clatt[1],clatt[2],clatt[3]}); + Coordinate cmsimd({1,1,1,1,1,1}); + Coordinate cmmpi ({1,1,mpi[0],mpi[1],mpi[2],mpi[3]}); + GridCartesian *CoarseMrhs = new GridCartesian(cmlatt,cmsimd,cmmpi); + + // 6D coarse grid at the coarsening batch, used only while CoarsenOperator + // runs. The matrix elements survive the change back to nrhs. + Coordinate cblatt({batch,1,clatt[0],clatt[1],clatt[2],clatt[3]}); + GridCartesian *CoarseBatch = new GridCartesian(cblatt,cmsimd,cmmpi); + + // 6D fine grid carrying the coarsening batch: fine SIMD layout preserved + Coordinate fmlatt({batch,Ls,lat_size[0],lat_size[1],lat_size[2],lat_size[3]}); + Coordinate fmsimd({1,1,fsimd[0],fsimd[1],fsimd[2],fsimd[3]}); + Coordinate fmmpi ({1,1,mpi[0],mpi[1],mpi[2],mpi[3]}); + GridCartesian *FineMrhs = new GridCartesian(fmlatt,fmsimd,fmmpi); + + std::cout << GridLogMessage << "Nsimd fine " << FGrid->Nsimd() + << " coarse " << Coarse5d->Nsimd() << std::endl; + + GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers({1,2,3,4}); + GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers({5,6,7,8}); + + ////////////////////////////////////////////////////////////////////// + // Gauge field + ////////////////////////////////////////////////////////////////////// + LatticeGaugeField Umu(UGrid); + if ( getenv("HOT_START") ) { + std::cout << GridLogMessage << "Hot start gauge field" << std::endl; + SU::HotConfiguration(RNG4,Umu); + } else { + std::string file("/ccs/home/poare/ckpoint_lat.1000"); + if ( getenv("CONFIG") ) file = std::string(getenv("CONFIG")); + std::cout << GridLogMessage << "Reading gauge field " << file << std::endl; + FieldMetaData header; + NerscIO::readConfiguration(Umu,header,file); + } + + MobiusFermionD Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5,b,c); + MobiusFermionD Dpv (Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,1.0, M5,b,c); + + // PVdagM and ShiftedPVdagM are thin wrappers over these same two objects, + // so this one callback controls every fine halo in the program. Default + // EXACT; the preconditioner and the coarsening turn sloppiness on for + // their own scope only (policy note at FineSloppyComms). + SetFineSloppy = [&Ddwf,&Dpv](int sloppy){ + Ddwf.SloppyComms(sloppy); + Dpv .SloppyComms(sloppy); + }; + SetFineSloppy(0); + std::cout << GridLogMessage << "Fine halo policy: preconditioner+coarsening " + << (FineSloppyComms ? "SLOPPY (fp32 wire)" : "exact") + << ", outer Krylov EXACT" << std::endl; + + typedef PVdagMLinearOperator PVdagM_t; + typedef ShiftedPVdagMLinearOperator ShiftedPVdagM_t; + PVdagM_t PVdagM(Ddwf,Dpv); + + ////////////////////////////////////////////////////////////////////// + // Level 1 types: unvectorised coarse scalar + ////////////////////////////////////////////////////////////////////// + typedef sTComplexD CComplexS; + typedef MultiGeneralCoarsenedOperator CoarseOperator; + typedef CoarseOperator::CoarseVector CoarseVector; + typedef Aggregation Subspace; + + NextToNearestStencilGeometry5D geom(Coarse5d); + + ////////////////////////////////////////////////////////////////////// + // Subspace: load RAW (no Orthogonalise!), or generate. + // + // The Aggregation is scaffolding for CreateSubspaceGCR only. That runs + // entirely on the fine grid and ends in GlobalOrthonormalise, which is a + // whole-lattice Gram-Schmidt, so the coarse grid it holds is never + // dereferenced and may be the unvectorised one. + ////////////////////////////////////////////////////////////////////// + std::string subspace_file = "subspace_nb" + std::to_string(nbasis) + ".scidac"; + if ( getenv("SUBSPACE_FILE") ) subspace_file = std::string(getenv("SUBSPACE_FILE")); + uint64_t file_exists=0; + if ( UGrid->IsBoss() ){ std::ifstream f(subspace_file); file_exists=f.good()?1:0; } + UGrid->GlobalSum(file_exists); + + const int cb=0; + Subspace AggregatesGCR(Coarse5d,FGrid,cb); + if ( file_exists ){ + std::cout << GridLogMessage << "*** Loading subspace from disk (kept RAW) ***" << std::endl; + loadSubspace(AggregatesGCR.subspace, subspace_file); + } else { + std::cout << GridLogMessage << "*** GCR subspace generation ***" << std::endl; + AggregatesGCR.CreateSubspaceGCR(RNG5,PVdagM,nbasis); + saveSubspace(AggregatesGCR.subspace, subspace_file); + } + + // RAW copy BEFORE CoarsenOperator block-orthonormalises in place. + std::vector rawNull(nbasis,FGrid); + for(int k=0;k MrhsPVdagM(PVdagM,FGrid,batch); + CoarseOpPV.CoarsenOperator(MrhsPVdagM,FineMrhs,AggregatesGCR.subspace,Coarse5d); + } else { + // PVdagM is single RHS: apply it directly, batch on the coarse side. + CoarseOpPV.CoarsenOperator(PVdagM,AggregatesGCR.subspace,Coarse5d,batch); + } + SetFineSloppy(0); + + // (moved here 2026-08-28: it needs AggregatesGCR.subspace, which is freed right after + // the projector imports it below -- the 60 fine vectors are 20 GB of host memory per + // rank and 60 LRU-eligible device fields competing with the solver's working set) + ////////////////////////////////////////////////////////////////////// + // Optional cross check of the coarse matrix elements against the + // deprecated path, which needs a vectorised coarse space. Block Gram-Schmidt + // is idempotent, so the deprecated path may re-orthonormalise the same vectors in place + // without a second copy of the subspace. + ////////////////////////////////////////////////////////////////////// + if ( getenv("DEPRECATED_CHECK") ) { + + typedef DeprecatedGeneralCoarsenedMatrix LittleDiracOperator; + typedef DeprecatedMultiGeneralCoarsenedMatrix MrhsLittleDiracOperator; + typedef Aggregation SubspaceV; + + Coordinate v5latt({1,clatt[0],clatt[1],clatt[2],clatt[3]}); + Coordinate v5simd({1,fsimd[0],fsimd[1],fsimd[2],fsimd[3]}); + GridCartesian *Coarse5dV = new GridCartesian(v5latt,v5simd,c5mpi); + + int nrhs_dep = vComplex::Nsimd(); + Coordinate vmlatt({nrhs_dep,1,clatt[0],clatt[1],clatt[2],clatt[3]}); + Coordinate vmsimd({vComplex::Nsimd(),1,1,1,1,1}); + GridCartesian *CoarseMrhsV = new GridCartesian(vmlatt,vmsimd,cmmpi); + + NextToNearestStencilGeometry5D geomV(Coarse5dV); + + SubspaceV AggV(Coarse5dV,FGrid,cb); + for(int k=0;k a2; + for(int p=0;p h1(sites),h2(sites); + acceleratorCopyFromDevice(&mrhsDep.BLAS_A[p][0],&h1[0],sites*sizeof(calcMatrix)); + acceleratorCopyFromDevice(&a2[0], &h2[0],sites*sizeof(calcMatrix)); + ComplexD *w1=(ComplexD *)&h1[0]; + ComplexD *w2=(ComplexD *)&h2[0]; + int64_t words = sites*sizeof(calcMatrix)/sizeof(ComplexD); + for(int64_t i=0;i L3. + // + // The fine operator here is the L1 operator, which is natively multiRHS, so the + // multiRHS driver applies with no promotion adapter: its D+1 grid IS the + // batch grid the L1 operator is currently set to. + ////////////////////////////////////////////////////////////////////// + Coordinate cclatt = clatt; + Coordinate Block2({4,4,2,4}); // L2->L3 blocking; banked optimum 2026-08-24 (env BLOCK2 overrides) + if ( getenv("BLOCK2") ){ GridCmdOptionIntVector(std::string(getenv("BLOCK2")),Block2); GRID_ASSERT(Block2.size()==4); } + for(int d=0;d<4;d++){ GRID_ASSERT(clatt[d]%Block2[d]==0); cclatt[d]=clatt[d]/Block2[d]; } + std::cout << GridLogMessage << "Block2 " << Block2 << " coarse-coarse lattice " << cclatt << std::endl; + + Coordinate cc5latt({1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]}); + GridCartesian *CoarseCoarse5d = new GridCartesian(cc5latt,c5simd,c5mpi); + + Coordinate ccmlatt({nrhs,1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]}); + GridCartesian *CoarseCoarseMrhs = new GridCartesian(ccmlatt,cmsimd,cmmpi); + + Coordinate ccblatt({batch,1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]}); + GridCartesian *CoarseCoarseBatch = new GridCartesian(ccblatt,cmsimd,cmmpi); + + // Coarsening deepens the tensor nest by one iScalar + typedef CoarseVector::vector_object CoarseSiteObj; + typedef iScalar CComplexS2; + typedef MultiGeneralCoarsenedOperator CoarseCoarseOperator; + typedef CoarseCoarseOperator::CoarseVector CoarseCoarseVector; + + NextToNearestStencilGeometry5D geom2(CoarseCoarse5d); + + // RAW copy of the coarse null vectors, for the same reason as rawNull: + // the L2 CoarsenOperator block-orthonormalises its subspace in place, and + // the L3 basis must be defined by the vectors that still carry content. + std::vector rawPsi(nbasis,Coarse5d); + for(int k=0;k LinOpCoarse(CoarseOpPV); + + std::cout << GridLogMessage << "*** L2 CoarsenOperator, batch "< MrhsProjectorL2; + MrhsProjectorL2.Allocate(nbasis,Coarse5d,CoarseCoarse5d); + MrhsProjectorL2.ImportBasis(psi_coarse); // block orthonormal basis + + { + std::vector psi_cc(nbasis,CoarseCoarse5d); + MrhsProjectorL2.blockProject(rawPsi,psi_cc); // RAW vectors in + + GramGuard("psi_cc",psi_cc,CoarseCoarse5d); + } + rawPsi.clear(); rawPsi.shrink_to_fit(); + + ////////////////////////////////////////////////////////////////////// + // Both coarse operators apply on their solve grids + ////////////////////////////////////////////////////////////////////// + { + GridParallelRNG cRNG(Coarse5d); cRNG.SeedFixedIntegers({3,4,5,6}); + CoarseVector cin(CoarseMrhs), cout_(CoarseMrhs); + random(cRNG,cin); + CoarseOpPV.M(cin,cout_); + std::cout << GridLogMessage << "L1 apply |in|^2 = " << norm2(cin) + << " |M in|^2 = " << norm2(cout_) << std::endl; + GRID_ASSERT( norm2(cout_) > 0.0 ); + + GridParallelRNG ccRNG(CoarseCoarse5d); ccRNG.SeedFixedIntegers({7,8,9,10}); + CoarseCoarseVector ccin(CoarseCoarseMrhs), ccout(CoarseCoarseMrhs); + random(ccRNG,ccin); + CoarseOpL2.M(ccin,ccout); + std::cout << GridLogMessage << "L2 apply |in|^2 = " << norm2(ccin) + << " |M in|^2 = " << norm2(ccout) << std::endl; + GRID_ASSERT( norm2(ccout) > 0.0 ); + } + + ////////////////////////////////////////////////////////////////////// + // STAGE THREE (part one): the dense bottom on L2. + // + // DenseCoarseMatrix is bilingual: it takes the elements through + // Geometry()/ExtractMatrix(), so the L2 operator serves directly. It does + // detect that a multiRHS op cannot apply on the D dimensional grid and + // skips its own certificate and VERIFY, so the equivalent check is done + // here instead, driving the L2 operator at Nrhs 1 through a slice. + ////////////////////////////////////////////////////////////////////// + typedef DenseCoarseMatrix DenseCC_t; + std::unique_ptr DenseCC; + + if ( getenv("DENSE_CC")==nullptr || atoi(getenv("DENSE_CC")) ) { + + std::cout << GridLogMessage << "*** L3 dense bottom: import from the L2 operator ***" << std::endl; + DenseCC.reset(new DenseCC_t(CoarseCoarse5d)); + DenseCC->Import(CoarseOpL2); + + //////////////////////////////////////////////////////////////////// + // ||A Ainv x - x|| / ||x||, the check Import could not run itself + //////////////////////////////////////////////////////////////////// + Coordinate cc1latt({1,1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]}); + GridCartesian *CoarseCoarseOne = new GridCartesian(cc1latt,cmsimd,cmmpi); + + CoarseOpL2.SetGrid(CoarseCoarseOne); + + CoarseCoarseVector x(CoarseCoarse5d),y(CoarseCoarse5d),z(CoarseCoarse5d); + GridParallelRNG dRNG(CoarseCoarse5d); dRNG.SeedFixedIntegers({11,12,13,14}); + random(dRNG,x); + + (*DenseCC)(x,y); // y = Ainv x + + CoarseCoarseVector y1(CoarseCoarseOne),z1(CoarseCoarseOne); + InsertSliceFast(y,y1,0,0); + CoarseOpL2.M(y1,z1); // z = A y + ExtractSliceFast(z,z1,0,0); + + z = z - x; + RealD rel = std::sqrt(norm2(z)/norm2(x)); + std::cout << GridLogMessage << "L3 dense: ||A Ainv x - x||/||x|| = " << rel << std::endl; + GRID_ASSERT( rel < 1.0e-2 ); + + CoarseOpL2.SetGrid(CoarseCoarseMrhs); + delete CoarseCoarseOne; + } + + ////////////////////////////////////////////////////////////////////// + // STAGE THREE (part two): the solves. + // + // Both operators are driven from the SAME objects at whatever Nrhs is + // asked for -- the matrix elements were built once and survive SetGrid -- + // so single RHS and multiRHS are the same code path with a different grid. + ////////////////////////////////////////////////////////////////////// + GRID_ASSERT(DenseCC != nullptr); // the PGCR bottom is not ported yet + + typedef PrecGeneralisedConjugateResidualNonHermitian FineSmoother_t; + + ShiftedPVdagM_t ShiftedPVdagM(FineSmootherShift,Ddwf,Dpv); + TrivialPrecon simple_fine; + TrivialPrecon simpleC; + + auto RunSolve = [&](int nr) + { + std::cout << GridLogMessage << "**********************************************" << std::endl; + std::cout << GridLogMessage << " THREE-level solve, Nrhs = " << nr << std::endl; + // Device-memory budget BEFORE the solve (2026-08-28: NRHS=6 died in hipMalloc at the + // first fine-smoother history allocation -- reported as an asynchronous "memory + // access fault" unless AMD_SERIALIZE_KERNEL/COPY made it a clean OOM). The outer + // mRHS GCR holds src, sol, r, Az and OuterMmax x (p,q) fine fields PER RHS; the fine + // smoother adds FineSmootherMmax x (p,q) once. The MemoryManager LRU cap + // (--device-mem) must be BELOW what is physically left after the non-LRU allocations + // (comms buffers, dense slab, stencil buffers), or the device fills before anything + // is evicted. Print the estimate, the LRU state, and the device's own free count. + { + uint64_t fieldBytes = (uint64_t)FGrid->lSites()*sizeof(typename LatticeFermionD::scalar_object); + double outerGB = (double)nr*(4 + 2*OuterMmax)*fieldBytes/1.0e9; + double smthGB = (double)(2*FineSmootherMmax + 4)*fieldBytes/1.0e9; + std::cout << GridLogMessage << "Device budget: fine field " << fieldBytes/1.0e6 << " MB; outer GCR history " + << nr << " x (4 + 2 x " << OuterMmax << ") fields = " << outerGB << " GB; fine smoother history + temps ~ " + << smthGB << " GB; MemoryManager device LRU " << MemoryManager::DeviceCacheBytes()/1.0e9 << " GB now, cap " + << MemoryManager::DeviceMaxBytes/1.0e9 << " GB" << std::endl; + // Empty the device LRU: every setup-era Lattice copy (coarse null vectors, + // coarsening temporaries) goes back to host, so the solve's working set + // starts from a clean device and the cap applies to it alone. + MemoryManager::EvictAll(); + // ...and release the allocation caches' held blocks (setup-era deviceVector + // scratch that is "free" to the caller but not to hipMalloc). + MemoryManager::DropCache(); + MemoryManager::PrintBytes(); + acceleratorMem(); + } + std::cout << GridLogMessage << "**********************************************" << std::endl; + + Coordinate cml({nr,1,clatt[0],clatt[1],clatt[2],clatt[3]}); + Coordinate ccml({nr,1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]}); + GridCartesian *CMrhs = new GridCartesian(cml, cmsimd,cmmpi); + GridCartesian *CCMrhs = new GridCartesian(ccml,cmsimd,cmmpi); + + CoarseOpPV.SetGrid(CMrhs); + CoarseOpL2.SetGrid(CCMrhs); + + NonHermitianLinearOperator LinOpC (CoarseOpPV); + NonHermitianLinearOperator LinOpCC(CoarseOpL2); + + MrhsDenseCCSolve ccSolve(*DenseCC,nr); + + ShiftedLinearOperator ShiftedC(CoarseSmootherShift, LinOpC); + if ( PowerIterations > 0 ) { + // Spectral edges the smoother polynomials must respect (see + // scripts/gcr_polynomial.py: |R_m|>1 beyond the edge = amplification). + PowerIteration ("CoarseSmootherOp(shift="+std::to_string(CoarseSmootherShift)+")", ShiftedC, CMrhs, PowerIterations); + PowerIteration ("CoarseOp(unshifted)", LinOpC, CMrhs, PowerIterations); + PowerIteration("FineSmootherOp(shift="+std::to_string(FineSmootherShift)+")", ShiftedPVdagM, Ddwf.FermionGrid(), PowerIterations); + } + PrecGeneralisedConjugateResidualNonHermitian + CoarseSmootherGCR(0.01,1,ShiftedC,simpleC,CoarseSmootherMmax,CoarseSmootherNstep); + CoarseSmootherGCR.Level(2); CoarseSmootherGCR.Name("Csmoother"); CoarseSmootherGCR.SetZeroGuess(1); + + SwitchableSmoother CoarseSmootherSlot(CoarseSmootherGCR,"Csmoother GCR"); + MrhsCoarseThreeLevelPrec + L2to3Precon(LinOpC, CoarseSmootherSlot, MrhsProjectorL2, ccSolve, + Coarse5d, CoarseCoarse5d, CCMrhs, nr); + + PrecGeneralisedConjugateResidualNonHermitian + L2PGCR(CoarseSolverTol, CoarseSolverOrder/16, LinOpC, L2to3Precon, CoarseSolverMmax, 16); + L2PGCR.Level(2); L2PGCR.Name("Couter"); L2PGCR.SetZeroGuess(1); + + FineSmoother_t SmootherGCR(0.0,1,ShiftedPVdagM,simple_fine,FineSmootherMmax,FineSmootherOrder); + SmootherGCR.Level(1); SmootherGCR.Name("Fsmoother"); SmootherGCR.SetZeroGuess(1); + SmootherGCR.LogCoefficients(SmootherCoeffLog); + CoarseSmootherGCR.LogCoefficients(SmootherCoeffLog); + + SwitchableSmoother FineSmootherSlot(SmootherGCR,"Fsmoother GCR"); + MrhsTwoLevelMG > + ThreeLevelPrecon(PVdagM, FineSmootherSlot, MrhsProjector, L2PGCR, Coarse5d, CMrhs); + ThreeLevelPrecon.SetSloppy = SetFineSloppy; + ThreeLevelPrecon.SloppyComms = FineSloppyComms; + + MrhsPGCRNonHermitian + L1PGCR(OuterTol,1000,PVdagM,ThreeLevelPrecon,OuterMmax,OuterNstep); + L1PGCR.Level(1); L1PGCR.Name("Fouter"); L1PGCR.SetZeroGuess(1); + + ////////////////////////////////////////////////////////////////////// + // Smoother modes. Objects live for the duration of this RunSolve. + ////////////////////////////////////////////////////////////////////// + std::unique_ptr > FineCheb; + std::unique_ptr > CoarseCheb; + std::unique_ptr > FineReplay; + std::unique_ptr > CoarseReplay; + GCRCoefficients recF, recC; + { + GCRCoefficients::Select sel = GCRCoefficients::Last; + if ( PolyRecordSelect=="first" ) sel = GCRCoefficients::First; + if ( PolyRecordSelect=="mean" ) sel = GCRCoefficients::Mean; + recF.select = sel; recC.select = sel; + } + if ( FineSmootherMode == "cheb" ) { + FineCheb.reset(new ChebyshevNonHermitianSmoother(FineChebLo,FineChebHi,FineSmootherOrder,ShiftedPVdagM)); + FineCheb->Verbose = PolyVerbose; FineCheb->name = "Fsmoother"; + FineSmootherSlot.Set(*FineCheb,"Fsmoother Chebyshev"); + } + if ( CoarseSmootherMode == "cheb" ) { + CoarseCheb.reset(new ChebyshevNonHermitianSmoother(CoarseChebLo,CoarseChebHi,CoarseSmootherNstep,ShiftedC)); + CoarseCheb->Verbose = PolyVerbose; CoarseCheb->name = "Csmoother"; + CoarseSmootherSlot.Set(*CoarseCheb,"Csmoother Chebyshev"); + } + // Recording window [PolyRecordStart, PolyRecordStart+PolyRecordIters). + // M3 (2026-08-26): the GCR polynomial changes fast over the first outer + // steps (per-call |r|/|r0| 0.0034 -> 0.017 over steps 1-4) and the MEAN + // of those is a poor smoother (replay 0.033-0.049 per call); record a + // settled window instead. + if ( PolyRecordStart == 0 ) { + if ( FineSmootherMode == "replay" ) SmootherGCR.SetCoefficientRecorder(&recF); + if ( CoarseSmootherMode == "replay" ) CoarseSmootherGCR.SetCoefficientRecorder(&recC); + } + // Record -> replay, with optional periodic re-recording ("re-record, not + // fade away": HDCG refreshed its polynomial every 10 steps, tracking the + // evolving spectral content of the residual). Schedule on outer steps: + // [PolyRecordStart, +PolyRecordIters) adaptive GCR, recording + // then replay of the selected recorded call; + // if PolyRefresh>0: every PolyRefresh steps, ONE adaptive recording + // step, then replay of that call. + auto BuildReplays = [&](void){ + if ( FineSmootherMode == "replay" ) { + SmootherGCR.SetCoefficientRecorder(nullptr); + recF.Flush(); recF.Report("Fsmoother"); + FineReplay.reset(new GCRReplaySmoother(ShiftedPVdagM,recF)); + FineReplay->Verbose = PolyVerbose; FineReplay->name = "Fsmoother"; + FineSmootherSlot.Set(*FineReplay,"Fsmoother replay"); + SmootherGCR.ReleaseHistory(); + } + if ( CoarseSmootherMode == "replay" ) { + CoarseSmootherGCR.SetCoefficientRecorder(nullptr); + recC.Flush(); recC.Report("Csmoother"); + CoarseReplay.reset(new GCRReplaySmoother(ShiftedC,recC)); + CoarseReplay->Verbose = PolyVerbose; CoarseReplay->name = "Csmoother"; + CoarseSmootherSlot.Set(*CoarseReplay,"Csmoother replay"); + CoarseSmootherGCR.ReleaseHistory(); + } + }; + auto StartRecording = [&](int step){ + if ( FineSmootherMode == "replay" ) { { auto sel=recF.select; recF = GCRCoefficients(); recF.select=sel; } SmootherGCR.SetCoefficientRecorder(&recF); FineSmootherSlot.Set(SmootherGCR,"Fsmoother GCR (recording)"); } + if ( CoarseSmootherMode == "replay" ) { { auto sel=recC.select; recC = GCRCoefficients(); recC.select=sel; } CoarseSmootherGCR.SetCoefficientRecorder(&recC); CoarseSmootherSlot.Set(CoarseSmootherGCR,"Csmoother GCR (recording)"); } + std::cout << GridLogMessage << "Smoother coefficient recording starts at outer step " << step << std::endl; + }; + int switchStep = PolyRecordStart + PolyRecordIters; + L1PGCR.OnStep = [&](int step){ + if ( FineSmootherMode != "replay" && CoarseSmootherMode != "replay" ) return; + if ( step == PolyRecordStart && PolyRecordStart > 0 ) StartRecording(step); + if ( step == switchStep ) { BuildReplays(); return; } + if ( PolyRefresh > 0 && step > switchStep ) { + int since = step - switchStep; + if ( since % PolyRefresh == 0 ) { StartRecording(step); } // one adaptive, recorded step + if ( since % PolyRefresh == 1 ) { BuildReplays(); } // then replay it + } + }; + std::cout << GridLogMessage << "Smoother modes: fine " << FineSmootherMode << " coarse " << CoarseSmootherMode + << (FineSmootherMode=="replay"||CoarseSmootherMode=="replay" ? " (record outer steps "+std::to_string(PolyRecordStart)+".."+std::to_string(PolyRecordStart+PolyRecordIters)+")" : "") + << std::endl; + + std::vector src(nr,FGrid), sol(nr,FGrid); + for(int r=0;r + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + See the full license in the file "LICENSE" in the top level distribution directory +*************************************************************************************/ +/* END LEGAL */ + +// +// PVdagM three level multigrid on MultiGeneralCoarsenedOperator. +// +// STAGES ONE AND TWO: grids, types, subspace, and the L1 and L2 coarsenings. +// The dense bottom and the solves are not here yet. +// +// Differences from Example_pvdagm_mrhs_3level_DenseCoarseMatrix.cc: +// +// * The coarse space is UNVECTORISED (sComplexD). The fine space stays +// vectorised. MultiRHSBlockProject carries the mixed layout. +// +// * One operator, not two. The deprecated path needed +// DeprecatedGeneralCoarsenedMatrix to coarsen and +// DeprecatedMultiGeneralCoarsenedMatrix to apply, bridged by CopyMatrix. +// This operator does both, and single versus multiRHS is SetGrid on the +// same object with the matrix elements built once. +// +// * Nrhs is unconstrained. The deprecated path required nrhs % vComplex::Nsimd() == 0 because +// its multiRHS grid carried the SIMD in the rhs direction. +// +// * CoarsenOperator takes the subspace vectors, not an Aggregation. It block +// orthonormalises them IN PLACE -- the vectors are far too large to copy +// defensively -- so rawNull is taken first and the RAW vectors are what +// define the L2 null space. Do not insert an Orthogonalise() anywhere: +// projecting a block-orthonormal vector onto its own block-orthonormalised +// aggregation gives e_k, and the near null content is silently gone. The +// || - I||_F guard below is what catches that. +// +// Env: LATT LS MASS NBASIS(compile time) NRHS BLOCK BLOCK2 COARSEN_BATCH +// HOT_START CONFIG SUBSPACE_FILE DEPRECATED_CHECK MRHS_COARSEN +// + +#include +#include +#include // std::sort, for the runtime-environment dump in ParseEnvironment +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace Grid; + +// Compile time so it can be cut down for laptop runs: -DNBASIS=8 +#ifndef NBASIS +#define NBASIS 60 +#endif + +RealD mass = 0.00078; +int Nrhs = 12; +int Ls = 24; +int CoarsenBatch = 9; +std::vector lat_size({48,48,48,96}); + +// Solver tuning. PRINCIPLE (PB, 2026-08-24): the defaults ARE the current +// optimum, so an unset environment reproduces the best banked result; they +// are updated as and when a better point is found, and every change is +// dated here. Environment variables of the same names override for sweeps. +// +// Current optimum: 2026-08-24, slurm-5335492 F4, 48^3x96 Ls=24 on 288 GCDs, +// 17.2 s/RHS at Nrhs=4, 32.2 s at Nrhs=1 (exact-halo FINAL ~1e-8 pending +// the exact-outer rerun). Smoother mmax == order (full GCR history); +// PB's mmax=1 trial gave 72 vs ~60 outer iterations and was slower. +RealD FineSmootherShift = 0.1; +int FineSmootherOrder = 6; +int FineSmootherMmax = 6; +RealD CoarseSmootherShift = 0.1; +int PowerIterations = 0; // >0: power-iterate the smoother operators before the solves (spectral edge) +// Smoother implementation per level (Smoothers.h): +// gcr : the adaptive PGCR (default, as always) +// replay : run the PGCR with a coefficient recorder for the first +// PolyRecordIters outer steps, then switch to GCRReplaySmoother +// (same polynomial, no inner products) -- 1402.2585 p.13 revisited +// cheb : ChebyshevNonHermitianSmoother, 1/x on [ChebLo,ChebHi], order +// = the GCR step count of that level +std::string FineSmootherMode = "gcr"; +std::string CoarseSmootherMode = "gcr"; +int PolyRecordIters = 4; +int PolyRecordStart = 0; // outer step at which recording begins (0: from the first step) +std::string PolyRecordSelect = "last"; // which recorded call to replay: last|first|mean (mean is the bad one) +int PolyRefresh = 0; // >0: every PolyRefresh outer steps, one adaptive step re-records the polynomial (HDCG: every 10) +int PolyVerbose = 0; // 1: fixed-polynomial smoothers print |r_m|/|r_0| per call +RealD FineChebLo = 3.0, FineChebHi = 137.0; // from the harvested polynomial and PowerIterations edge +RealD CoarseChebLo = 8.0, CoarseChebHi = 45.0; +// Landau frame + Fourier fine smoother (FineSmootherMode "fourier"): +// the configuration is rotated to the Landau frame at load; the smoother is the +// free Mobius PVdagM inverse, F(1)^-dag then F(m_s), by 4D FFT (FreeMobius5D). +// FourierSmootherMass caps the free IR gain (structural at large Ls: the free +// operator's own e^{-alpha Ls} cap vanishes, so m_s must supply it). +RealD GaugeFixAlpha = 0.05; // stable window 0.02-0.05 (Grid FA normalisation) +RealD GaugeFixTol = 1.0e-5; // smoothness saturates here (tol scan 2026-09-01) +RealD FourierSmootherMass = 0.1; +RealD SmootherGainCap = 0.0; // >0: cap free-block singular values (IR gain limiter); 0 = off +int RotateSubspace = 1; // rotate a LOADED subspace into the frame (legacy files) +int CoarseSmootherNstep = 2; +int CoarseSmootherMmax = 2; +RealD CoarseSolverTol = 0.05; +int CoarseSolverOrder = 200; +int CoarseSolverMmax = 16; +RealD OuterTol = 1.0e-8; +int OuterMmax = 4; +int OuterNstep = 8; + +// "It's legal to get the same answer faster, not to get a less correct +// answer." (PB, 2026-08-24) +// +// Halo-precision POLICY: reduced-precision (fp32 wire) +// halos belong in the PRECONDITIONER -- the smoother, the V-cycle's own +// residuals, and the coarsening -- and NEVER in the outer Krylov. The +// outer operator's applications define what "converged" means; making them +// sloppy turns the stopping criterion into a statement about the wrong +// operator (measured: solver stops at computed 9.8e-9 while the true +// residual is 3.4e-8). Exactness costs one exact fine matvec per outer +// iteration, a few percent of the solve. +// +// Stencil::SloppyComms is a free runtime setter (Stencil.h:303), so the +// policy is implemented by SCOPED toggling: SetFineSloppy(1) on entering +// the preconditioner / coarsening, SetFineSloppy(0) on leaving. The +// operators default to EXACT. FineSloppyComms therefore now means +// "sloppy inside the preconditioner"; =0 makes everything exact. +int FineSloppyComms = 1; +// SmootherCoeffLog=1 : print the GCR step lengths a_k and orthogonalisation +// coefficients b_kj of BOTH smoothers every call -- the harvest for a fixed +// polynomial smoother (stable coefficients => stationary p(A), no reductions). +int SmootherCoeffLog = 0; +std::function SetFineSloppy = [](int){}; + +void ParseEnvironment(void) +{ + if(getenv("MASS")) mass = atof(getenv("MASS")); + if(getenv("NRHS")) Nrhs = atoi(getenv("NRHS")); + if(getenv("LS")) Ls = atoi(getenv("LS")); + if(getenv("COARSEN_BATCH")) CoarsenBatch= atoi(getenv("COARSEN_BATCH")); + if(getenv("FineSmootherShift")) FineSmootherShift = atof(getenv("FineSmootherShift")); + if(getenv("FineSmootherOrder")) FineSmootherOrder = atoi(getenv("FineSmootherOrder")); + if(getenv("FineSmootherMmax")) FineSmootherMmax = atoi(getenv("FineSmootherMmax")); + if(getenv("CoarseSmootherShift"))CoarseSmootherShift= atof(getenv("CoarseSmootherShift")); + if(getenv("PowerIterations")) PowerIterations = atoi(getenv("PowerIterations")); + if(getenv("FineSmootherMode")) FineSmootherMode = getenv("FineSmootherMode"); + if(getenv("CoarseSmootherMode")) CoarseSmootherMode = getenv("CoarseSmootherMode"); + if(getenv("PolyRecordIters")) PolyRecordIters = atoi(getenv("PolyRecordIters")); + if(getenv("PolyRecordStart")) PolyRecordStart = atoi(getenv("PolyRecordStart")); + if(getenv("PolyRecordSelect")) PolyRecordSelect = getenv("PolyRecordSelect"); + if(getenv("PolyRefresh")) PolyRefresh = atoi(getenv("PolyRefresh")); + if(getenv("PolyVerbose")) PolyVerbose = atoi(getenv("PolyVerbose")); + if(getenv("GaugeFixAlpha")) GaugeFixAlpha = atof(getenv("GaugeFixAlpha")); + if(getenv("GaugeFixTol")) GaugeFixTol = atof(getenv("GaugeFixTol")); + if(getenv("FourierSmootherMass")) FourierSmootherMass = atof(getenv("FourierSmootherMass")); + if(getenv("SmootherGainCap")) SmootherGainCap = atof(getenv("SmootherGainCap")); + if(getenv("RotateSubspace")) RotateSubspace = atoi(getenv("RotateSubspace")); + if(getenv("FineChebLo")) FineChebLo = atof(getenv("FineChebLo")); + if(getenv("FineChebHi")) FineChebHi = atof(getenv("FineChebHi")); + if(getenv("CoarseChebLo")) CoarseChebLo = atof(getenv("CoarseChebLo")); + if(getenv("CoarseChebHi")) CoarseChebHi = atof(getenv("CoarseChebHi")); + if(getenv("CoarseSmootherNstep"))CoarseSmootherNstep= atoi(getenv("CoarseSmootherNstep")); + if(getenv("CoarseSmootherMmax")) CoarseSmootherMmax = atoi(getenv("CoarseSmootherMmax")); + if(getenv("CoarseSolverTol")) CoarseSolverTol = atof(getenv("CoarseSolverTol")); + if(getenv("CoarseSolverOrder")) CoarseSolverOrder = atoi(getenv("CoarseSolverOrder")); + if(getenv("CoarseSolverMmax")) CoarseSolverMmax = atoi(getenv("CoarseSolverMmax")); + if(getenv("OuterTol")) OuterTol = atof(getenv("OuterTol")); + if(getenv("OuterMmax")) OuterMmax = atoi(getenv("OuterMmax")); + if(getenv("FineSloppyComms")) FineSloppyComms = atoi(getenv("FineSloppyComms")); + if(getenv("SmootherCoeffLog")) SmootherCoeffLog = atoi(getenv("SmootherCoeffLog")); + if(getenv("OuterNstep")) OuterNstep = atoi(getenv("OuterNstep")); + if(getenv("LATT")){ + Coordinate l; + GridCmdOptionIntVector(std::string(getenv("LATT")),l); + GRID_ASSERT(l.size()==4); + for(int d=0;d<4;d++) lat_size[d]=l[d]; + } + + std::cout << GridLogMessage << "PARAM: LATT " + << lat_size[0]<<"."< hits; + for(char **e = environ; e && *e; e++){ + std::string s(*e); + for(auto p : prefixes){ + if( s.compare(0,strlen(p),p)==0 ){ + if(s.size()>200) s = s.substr(0,197)+"..."; // paths can be enormous + hits.push_back(s); + break; + } + } + } + std::sort(hits.begin(),hits.end()); + std::cout << GridLogMessage << "PARAM: ---- runtime environment: "<_fdimensions[0]; + LatticeFermionD ferm4((GridCartesian *)UGrid); + for(int s=0;s &F, double Gmax) +{ + if ( Gmax <= 0.0 ) return; + int n5 = 4*F.Ls; + size_t nslot = F.Minv_dev.size()/((size_t)n5*n5); + for(size_t slot=0;slot(z.real(),z.imag()); + } + Eigen::JacobiSVD svd(B,Eigen::ComputeFullU|Eigen::ComputeFullV); + Eigen::VectorXd s = svd.singularValues(); + for(int i=0;iGmax) s(i)=Gmax; + Eigen::MatrixXcd Bc = svd.matrixU()*s.asDiagonal()*svd.matrixV().adjoint(); + for(int r=0;r z = Bc(r,cc); + F.Minv_dev[base+(size_t)r*n5+cc] = Grid::Complex(z.real(),z.imag()); + } + } +} + +void DaggerBlocks(FreeMobius5DInverse &F) +{ + int n5 = 4*F.Ls; + for(int i=0;i<(int)F.Minv.size();i++) F.Minv[i] = F.Minv[i].adjoint().eval(); + size_t nslot = F.Minv_dev.size()/((size_t)n5*n5); + for(size_t slot=0;slot { +public: + using LinearFunction::operator(); + FreeMobius5DInverse &Fm; // F(m_s) + FreeMobius5DInverse &F1dag; // F(1) with daggered blocks + FourierPVdagMSmoother(FreeMobius5DInverse &_Fm, + FreeMobius5DInverse &_F1dag) : Fm(_Fm), F1dag(_F1dag) {} + void operator()(const LatticeFermionD &in,LatticeFermionD &out){ + // Outer operator A = PV^dag M (PVdagMLinearOperator::Op = _Mat.M then _PV.Mdag). + // A^-1 = M^-1 (PV^dag)^-1 = F(m) . F(1)^-dag: apply F(1)^-dag first, then F(m). + LatticeFermionD tmp(in.Grid()); + F1dag(in,tmp); + Fm(tmp,out); + } +}; + +template +void saveSubspace(std::vector &subspace, std::string const fname){ +#ifdef HAVE_LIME + Grid::emptyUserRecord record; + Grid::ScidacWriter SW(subspace[0].Grid()->IsBoss()); + SW.open(fname); + for (int k = 0; k < (int)subspace.size(); k++) SW.writeScidacFieldRecord(subspace[k], record); + SW.close(); +#endif +} +template +void loadSubspace(std::vector &subspace, std::string const fname){ +#ifdef HAVE_LIME + Grid::emptyUserRecord record; + Grid::ScidacReader SR; + SR.open(fname); + for (int k = 0; k < (int)subspace.size(); k++) SR.readScidacFieldRecord(subspace[k], record); + SR.close(); +#endif +} + +////////////////////////////////////////////////////////////////////// +// A = PV^dag M (non-Hermitian) +////////////////////////////////////////////////////////////////////// +template +class PVdagMLinearOperator : public LinearOperatorBase { + Matrix &_Mat; Matrix &_PV; +public: + PVdagMLinearOperator(Matrix &Mat,Matrix &PV): _Mat(Mat),_PV(PV) {}; + void OpDiag (const Field &in, Field &out) { assert(0); } + void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); } + void OpDirAll (const Field &in, std::vector &out){ assert(0); }; + void Op (const Field &in, Field &out){ Field tmp(in.Grid()); _Mat.M(in,tmp); _PV.Mdag(tmp,out); } + void AdjOp (const Field &in, Field &out){ Field tmp(in.Grid()); _PV.M(in,tmp); _Mat.Mdag(tmp,out); } + void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ HermOp(in,out); ComplexD d=innerProduct(in,out); n1=real(d); n2=norm2(out); } + void HermOp(const Field &in, Field &out){ Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); } +}; + +////////////////////////////////////////////////////////////////////// +// || - I||_F over a set of coarse vectors. Small means the raw near null +// content survived the projection; see GramGuard for where a leak lands. +////////////////////////////////////////////////////////////////////// +template +RealD GramDefect(std::vector &v) +{ + RealD s2=0.0; + for(int i=0;i<(int)v.size();i++){ + for(int j=0;j<(int)v.size();j++){ + ComplexD sij=TensorRemove(innerProduct(v[i],v[j])); + ComplexD d=sij-(i==j?ComplexD(1.0):ComplexD(0.0)); + s2+=real(d)*real(d)+imag(d)*imag(d); + } + } + return std::sqrt(s2); +} + +// On a leak every image collapses to the block unit e_k, the Gram becomes +// N*I, and the defect lands at (N-1)*sqrt(nbasis) -- orders above the ~0.2 +// of a content preserving projection. Trip well below that so a mis-set +// threshold costs a log line rather than the run. +template +void GramGuard(const std::string &name,std::vector &v,GridBase *grid) +{ + RealD defect = GramDefect(v); + RealD N = (RealD)grid->gSites(); + RealD leak = (N-1.0)*std::sqrt((RealD)v.size()); + RealD trip = std::sqrt(N); + std::cout << GridLogMessage << "GUARD: ||<"< - I||_F = " << defect + << " (e_k leak would be " << leak << ", trip at " << trip << ")" << std::endl; + GRID_ASSERT( defect < trip ); +} + + +////////////////////////////////////////////////////////////////////// +// Shifted variants for the smoothers +////////////////////////////////////////////////////////////////////// +template +class ShiftedPVdagMLinearOperator : public LinearOperatorBase { + Matrix &_Mat; Matrix &_PV; +public: + RealD shift; + ShiftedPVdagMLinearOperator(RealD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){}; + void OpDiag (const Field &in, Field &out) { assert(0); } + void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); } + void OpDirAll (const Field &in, std::vector &out){ assert(0); }; + void Op (const Field &in, Field &out){ Field tmp(in.Grid()); _Mat.M(in,tmp); _PV.Mdag(tmp,out); out = out + shift*in; } + void AdjOp (const Field &in, Field &out){ Field tmp(in.Grid()); _PV.M(tmp,out); _Mat.Mdag(in,tmp); out = out + shift*in; } + void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); } + void HermOp(const Field &in, Field &out){ Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); } +}; + +template +class ShiftedLinearOperator : public LinearOperatorBase { + LinearOperatorBase &_Op; RealD shift; +public: + ShiftedLinearOperator(RealD _shift, LinearOperatorBase &Op) : _Op(Op), shift(_shift) {} + void OpDiag (const Field &in, Field &out) { assert(0); } + void OpDir (const Field &in, Field &out,int dir,int disp) { assert(0); } + void OpDirAll (const Field &in, std::vector &out) { assert(0); } + void Op (const Field &in, Field &out) { _Op.Op(in,out); out = out + shift*in; } + void AdjOp (const Field &in, Field &out) { _Op.AdjOp(in,out); out = out + shift*in; } + void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ assert(0); } + void HermOp (const Field &in, Field &out) { Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); } +}; + +////////////////////////////////////////////////////////////////////// +// Power iteration on a (non-Hermitian) operator: the spectral edge the +// smoother polynomial must not exceed. Reports +// step 0 : |A v|/|v| on a RANDOM unit v -- a one-sample lower bound on +// sigma_max(A). If this and the converged value agree, the +// operator is near-normal and the spectral picture (R_m(lambda) +// on the spectrum) is trustworthy; if not, the field of values +// sets the safe interval and the spectrum understates it. +// step k : |A v_k|/|v_k| -> |lambda_max| as v_k -> the dominant +// eigenvector; the complex Rayleigh quotient gives its +// phase (real => on the axis). A non-converging oscillation +// means a complex-conjugate pair of equal modulus at the top. +// Uses Op(), not HermOp(): this is the operator the smoother sees. +////////////////////////////////////////////////////////////////////// +template +void PowerIteration(const std::string &name, LinearOperatorBase &Op, GridBase *grid, int iters) +{ + GRID_TRACE("PowerIteration"); + GridParallelRNG RNG(grid); RNG.SeedFixedIntegers(std::vector({7,11,13,17})); + Field v(grid), Av(grid); + gaussian(RNG,v); + RealD nv = std::sqrt(norm2(v)); v = v*(1.0/nv); + RealD ratio=0.0, ratio0=0.0; ComplexD rq(0.0); + for(int i=0;iL2 blocking; banked optimum 2026-08-24 (env BLOCK overrides) + if ( getenv("BLOCK") ){ GridCmdOptionIntVector(std::string(getenv("BLOCK")),Block); GRID_ASSERT(Block.size()==4); } + for(int d=0;d<4;d++){ GRID_ASSERT(lat_size[d]%Block[d]==0); clatt[d]=lat_size[d]/Block[d]; } + std::cout << GridLogMessage << "Block " << Block << " coarse lattice " << clatt << std::endl; + + ////////////////////////////////////////////////////////////////////// + // The coarse space is unvectorised. The 5D coarse grid is built here + // rather than through SpaceTimeGrid so the SIMD layout is ours. + ////////////////////////////////////////////////////////////////////// + Coordinate c5latt({1,clatt[0],clatt[1],clatt[2],clatt[3]}); + Coordinate c5simd({1,1,1,1,1}); + Coordinate c5mpi ({1,mpi[0],mpi[1],mpi[2],mpi[3]}); + GridCartesian *Coarse5d = new GridCartesian(c5latt,c5simd,c5mpi); + + // 6D coarse multiRHS grid: rhs is dim 0, undistributed and unvectorised. + // No divisibility constraint on nrhs, unlike the deprecated operators. + Coordinate cmlatt({nrhs,1,clatt[0],clatt[1],clatt[2],clatt[3]}); + Coordinate cmsimd({1,1,1,1,1,1}); + Coordinate cmmpi ({1,1,mpi[0],mpi[1],mpi[2],mpi[3]}); + GridCartesian *CoarseMrhs = new GridCartesian(cmlatt,cmsimd,cmmpi); + + // 6D coarse grid at the coarsening batch, used only while CoarsenOperator + // runs. The matrix elements survive the change back to nrhs. + Coordinate cblatt({batch,1,clatt[0],clatt[1],clatt[2],clatt[3]}); + GridCartesian *CoarseBatch = new GridCartesian(cblatt,cmsimd,cmmpi); + + // 6D fine grid carrying the coarsening batch: fine SIMD layout preserved + Coordinate fmlatt({batch,Ls,lat_size[0],lat_size[1],lat_size[2],lat_size[3]}); + Coordinate fmsimd({1,1,fsimd[0],fsimd[1],fsimd[2],fsimd[3]}); + Coordinate fmmpi ({1,1,mpi[0],mpi[1],mpi[2],mpi[3]}); + GridCartesian *FineMrhs = new GridCartesian(fmlatt,fmsimd,fmmpi); + + std::cout << GridLogMessage << "Nsimd fine " << FGrid->Nsimd() + << " coarse " << Coarse5d->Nsimd() << std::endl; + + GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers({1,2,3,4}); + GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers({5,6,7,8}); + + ////////////////////////////////////////////////////////////////////// + // Gauge field + ////////////////////////////////////////////////////////////////////// + LatticeGaugeField Umu(UGrid); + if ( getenv("HOT_START") ) { + std::cout << GridLogMessage << "Hot start gauge field" << std::endl; + SU::HotConfiguration(RNG4,Umu); + } else { + std::string file("/ccs/home/poare/ckpoint_lat.1000"); + if ( getenv("CONFIG") ) file = std::string(getenv("CONFIG")); + std::cout << GridLogMessage << "Reading gauge field " << file << std::endl; + FieldMetaData header; + NerscIO::readConfiguration(Umu,header,file); + } + + ////////////////////////////////////////////////////////////////////// + // Landau frame. Fix a copy, project Omega back onto the group, rotate + // Umu in place: everything downstream runs in the smooth frame. The + // plaquette is gauge invariant (checked); Omega is retained to rotate a + // subspace that was generated in the original frame. + ////////////////////////////////////////////////////////////////////// + LatticeColourMatrixD Omega(UGrid); + { + LatticeGaugeField Ufix(UGrid); Ufix = Umu; + FourierAcceleratedGaugeFixer::SteepestDescentGaugeFix( + Ufix,Omega,GaugeFixAlpha,10000,GaugeFixTol,GaugeFixTol,true,-1,false); + Omega = ProjectOnGroup(Omega); + RealD plaq0 = WilsonLoops::avgPlaquette(Umu); + SU::GaugeTransform(Umu,Omega); + RealD plaq1 = WilsonLoops::avgPlaquette(Umu); + std::cout << GridLogMessage << "Landau frame: plaquette " << plaq0 << " -> " << plaq1 + << " (invariant); 1-linkTrace/Nc = " + << 1.0-WilsonLoops::linkTrace(Umu) << std::endl; + GRID_ASSERT(fabs(plaq1-plaq0) < 1.0e-10); + } + + MobiusFermionD Ddwf(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5,b,c); + MobiusFermionD Dpv (Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,1.0, M5,b,c); + + // PVdagM and ShiftedPVdagM are thin wrappers over these same two objects, + // so this one callback controls every fine halo in the program. Default + // EXACT; the preconditioner and the coarsening turn sloppiness on for + // their own scope only (policy note at FineSloppyComms). + SetFineSloppy = [&Ddwf,&Dpv](int sloppy){ + Ddwf.SloppyComms(sloppy); + Dpv .SloppyComms(sloppy); + }; + SetFineSloppy(0); + std::cout << GridLogMessage << "Fine halo policy: preconditioner+coarsening " + << (FineSloppyComms ? "SLOPPY (fp32 wire)" : "exact") + << ", outer Krylov EXACT" << std::endl; + + typedef PVdagMLinearOperator PVdagM_t; + typedef ShiftedPVdagMLinearOperator ShiftedPVdagM_t; + PVdagM_t PVdagM(Ddwf,Dpv); + + ////////////////////////////////////////////////////////////////////// + // Level 1 types: unvectorised coarse scalar + ////////////////////////////////////////////////////////////////////// + typedef sTComplexD CComplexS; + typedef MultiGeneralCoarsenedOperator CoarseOperator; + typedef CoarseOperator::CoarseVector CoarseVector; + typedef Aggregation Subspace; + + NextToNearestStencilGeometry5D geom(Coarse5d); + + ////////////////////////////////////////////////////////////////////// + // Subspace: load RAW (no Orthogonalise!), or generate. + // + // The Aggregation is scaffolding for CreateSubspaceGCR only. That runs + // entirely on the fine grid and ends in GlobalOrthonormalise, which is a + // whole-lattice Gram-Schmidt, so the coarse grid it holds is never + // dereferenced and may be the unvectorised one. + ////////////////////////////////////////////////////////////////////// + std::string subspace_file = "subspace_nb" + std::to_string(nbasis) + ".scidac"; + if ( getenv("SUBSPACE_FILE") ) subspace_file = std::string(getenv("SUBSPACE_FILE")); + uint64_t file_exists=0; + if ( UGrid->IsBoss() ){ std::ifstream f(subspace_file); file_exists=f.good()?1:0; } + UGrid->GlobalSum(file_exists); + + const int cb=0; + Subspace AggregatesGCR(Coarse5d,FGrid,cb); + if ( file_exists ){ + std::cout << GridLogMessage << "*** Loading subspace from disk (kept RAW) ***" << std::endl; + loadSubspace(AggregatesGCR.subspace, subspace_file); + if ( RotateSubspace ) { + // Legacy file, generated in the original frame: the near-null space is + // gauge covariant, so rotating by Omega gives the exact frame basis. + std::cout << GridLogMessage << "*** Rotating loaded subspace into the Landau frame ***" << std::endl; + for(int k=0;k rawNull(nbasis,FGrid); + for(int k=0;k MrhsPVdagM(PVdagM,FGrid,batch); + CoarseOpPV.CoarsenOperator(MrhsPVdagM,FineMrhs,AggregatesGCR.subspace,Coarse5d); + } else { + // PVdagM is single RHS: apply it directly, batch on the coarse side. + CoarseOpPV.CoarsenOperator(PVdagM,AggregatesGCR.subspace,Coarse5d,batch); + } + SetFineSloppy(0); + + // (moved here 2026-08-28: it needs AggregatesGCR.subspace, which is freed right after + // the projector imports it below -- the 60 fine vectors are 20 GB of host memory per + // rank and 60 LRU-eligible device fields competing with the solver's working set) + ////////////////////////////////////////////////////////////////////// + // Optional cross check of the coarse matrix elements against the + // deprecated path, which needs a vectorised coarse space. Block Gram-Schmidt + // is idempotent, so the deprecated path may re-orthonormalise the same vectors in place + // without a second copy of the subspace. + ////////////////////////////////////////////////////////////////////// + if ( getenv("DEPRECATED_CHECK") ) { + + typedef DeprecatedGeneralCoarsenedMatrix LittleDiracOperator; + typedef DeprecatedMultiGeneralCoarsenedMatrix MrhsLittleDiracOperator; + typedef Aggregation SubspaceV; + + Coordinate v5latt({1,clatt[0],clatt[1],clatt[2],clatt[3]}); + Coordinate v5simd({1,fsimd[0],fsimd[1],fsimd[2],fsimd[3]}); + GridCartesian *Coarse5dV = new GridCartesian(v5latt,v5simd,c5mpi); + + int nrhs_dep = vComplex::Nsimd(); + Coordinate vmlatt({nrhs_dep,1,clatt[0],clatt[1],clatt[2],clatt[3]}); + Coordinate vmsimd({vComplex::Nsimd(),1,1,1,1,1}); + GridCartesian *CoarseMrhsV = new GridCartesian(vmlatt,vmsimd,cmmpi); + + NextToNearestStencilGeometry5D geomV(Coarse5dV); + + SubspaceV AggV(Coarse5dV,FGrid,cb); + for(int k=0;k a2; + for(int p=0;p h1(sites),h2(sites); + acceleratorCopyFromDevice(&mrhsDep.BLAS_A[p][0],&h1[0],sites*sizeof(calcMatrix)); + acceleratorCopyFromDevice(&a2[0], &h2[0],sites*sizeof(calcMatrix)); + ComplexD *w1=(ComplexD *)&h1[0]; + ComplexD *w2=(ComplexD *)&h2[0]; + int64_t words = sites*sizeof(calcMatrix)/sizeof(ComplexD); + for(int64_t i=0;i L3. + // + // The fine operator here is the L1 operator, which is natively multiRHS, so the + // multiRHS driver applies with no promotion adapter: its D+1 grid IS the + // batch grid the L1 operator is currently set to. + ////////////////////////////////////////////////////////////////////// + Coordinate cclatt = clatt; + Coordinate Block2({4,4,2,4}); // L2->L3 blocking; banked optimum 2026-08-24 (env BLOCK2 overrides) + if ( getenv("BLOCK2") ){ GridCmdOptionIntVector(std::string(getenv("BLOCK2")),Block2); GRID_ASSERT(Block2.size()==4); } + for(int d=0;d<4;d++){ GRID_ASSERT(clatt[d]%Block2[d]==0); cclatt[d]=clatt[d]/Block2[d]; } + std::cout << GridLogMessage << "Block2 " << Block2 << " coarse-coarse lattice " << cclatt << std::endl; + + Coordinate cc5latt({1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]}); + GridCartesian *CoarseCoarse5d = new GridCartesian(cc5latt,c5simd,c5mpi); + + Coordinate ccmlatt({nrhs,1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]}); + GridCartesian *CoarseCoarseMrhs = new GridCartesian(ccmlatt,cmsimd,cmmpi); + + Coordinate ccblatt({batch,1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]}); + GridCartesian *CoarseCoarseBatch = new GridCartesian(ccblatt,cmsimd,cmmpi); + + // Coarsening deepens the tensor nest by one iScalar + typedef CoarseVector::vector_object CoarseSiteObj; + typedef iScalar CComplexS2; + typedef MultiGeneralCoarsenedOperator CoarseCoarseOperator; + typedef CoarseCoarseOperator::CoarseVector CoarseCoarseVector; + + NextToNearestStencilGeometry5D geom2(CoarseCoarse5d); + + // RAW copy of the coarse null vectors, for the same reason as rawNull: + // the L2 CoarsenOperator block-orthonormalises its subspace in place, and + // the L3 basis must be defined by the vectors that still carry content. + std::vector rawPsi(nbasis,Coarse5d); + for(int k=0;k LinOpCoarse(CoarseOpPV); + + std::cout << GridLogMessage << "*** L2 CoarsenOperator, batch "< MrhsProjectorL2; + MrhsProjectorL2.Allocate(nbasis,Coarse5d,CoarseCoarse5d); + MrhsProjectorL2.ImportBasis(psi_coarse); // block orthonormal basis + + { + std::vector psi_cc(nbasis,CoarseCoarse5d); + MrhsProjectorL2.blockProject(rawPsi,psi_cc); // RAW vectors in + + GramGuard("psi_cc",psi_cc,CoarseCoarse5d); + } + rawPsi.clear(); rawPsi.shrink_to_fit(); + + ////////////////////////////////////////////////////////////////////// + // Both coarse operators apply on their solve grids + ////////////////////////////////////////////////////////////////////// + { + GridParallelRNG cRNG(Coarse5d); cRNG.SeedFixedIntegers({3,4,5,6}); + CoarseVector cin(CoarseMrhs), cout_(CoarseMrhs); + random(cRNG,cin); + CoarseOpPV.M(cin,cout_); + std::cout << GridLogMessage << "L1 apply |in|^2 = " << norm2(cin) + << " |M in|^2 = " << norm2(cout_) << std::endl; + GRID_ASSERT( norm2(cout_) > 0.0 ); + + GridParallelRNG ccRNG(CoarseCoarse5d); ccRNG.SeedFixedIntegers({7,8,9,10}); + CoarseCoarseVector ccin(CoarseCoarseMrhs), ccout(CoarseCoarseMrhs); + random(ccRNG,ccin); + CoarseOpL2.M(ccin,ccout); + std::cout << GridLogMessage << "L2 apply |in|^2 = " << norm2(ccin) + << " |M in|^2 = " << norm2(ccout) << std::endl; + GRID_ASSERT( norm2(ccout) > 0.0 ); + } + + ////////////////////////////////////////////////////////////////////// + // STAGE THREE (part one): the dense bottom on L2. + // + // DenseCoarseMatrix is bilingual: it takes the elements through + // Geometry()/ExtractMatrix(), so the L2 operator serves directly. It does + // detect that a multiRHS op cannot apply on the D dimensional grid and + // skips its own certificate and VERIFY, so the equivalent check is done + // here instead, driving the L2 operator at Nrhs 1 through a slice. + ////////////////////////////////////////////////////////////////////// + typedef DenseCoarseMatrix DenseCC_t; + std::unique_ptr DenseCC; + + if ( getenv("DENSE_CC")==nullptr || atoi(getenv("DENSE_CC")) ) { + + std::cout << GridLogMessage << "*** L3 dense bottom: import from the L2 operator ***" << std::endl; + DenseCC.reset(new DenseCC_t(CoarseCoarse5d)); + DenseCC->Import(CoarseOpL2); + + //////////////////////////////////////////////////////////////////// + // ||A Ainv x - x|| / ||x||, the check Import could not run itself + //////////////////////////////////////////////////////////////////// + Coordinate cc1latt({1,1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]}); + GridCartesian *CoarseCoarseOne = new GridCartesian(cc1latt,cmsimd,cmmpi); + + CoarseOpL2.SetGrid(CoarseCoarseOne); + + CoarseCoarseVector x(CoarseCoarse5d),y(CoarseCoarse5d),z(CoarseCoarse5d); + GridParallelRNG dRNG(CoarseCoarse5d); dRNG.SeedFixedIntegers({11,12,13,14}); + random(dRNG,x); + + (*DenseCC)(x,y); // y = Ainv x + + CoarseCoarseVector y1(CoarseCoarseOne),z1(CoarseCoarseOne); + InsertSliceFast(y,y1,0,0); + CoarseOpL2.M(y1,z1); // z = A y + ExtractSliceFast(z,z1,0,0); + + z = z - x; + RealD rel = std::sqrt(norm2(z)/norm2(x)); + std::cout << GridLogMessage << "L3 dense: ||A Ainv x - x||/||x|| = " << rel << std::endl; + GRID_ASSERT( rel < 1.0e-2 ); + + CoarseOpL2.SetGrid(CoarseCoarseMrhs); + delete CoarseCoarseOne; + } + + ////////////////////////////////////////////////////////////////////// + // STAGE THREE (part two): the solves. + // + // Both operators are driven from the SAME objects at whatever Nrhs is + // asked for -- the matrix elements were built once and survive SetGrid -- + // so single RHS and multiRHS are the same code path with a different grid. + ////////////////////////////////////////////////////////////////////// + GRID_ASSERT(DenseCC != nullptr); // the PGCR bottom is not ported yet + + typedef PrecGeneralisedConjugateResidualNonHermitian FineSmoother_t; + + ShiftedPVdagM_t ShiftedPVdagM(FineSmootherShift,Ddwf,Dpv); + TrivialPrecon simple_fine; + TrivialPrecon simpleC; + + auto RunSolve = [&](int nr) + { + std::cout << GridLogMessage << "**********************************************" << std::endl; + std::cout << GridLogMessage << " THREE-level solve, Nrhs = " << nr << std::endl; + // Device-memory budget BEFORE the solve (2026-08-28: NRHS=6 died in hipMalloc at the + // first fine-smoother history allocation -- reported as an asynchronous "memory + // access fault" unless AMD_SERIALIZE_KERNEL/COPY made it a clean OOM). The outer + // mRHS GCR holds src, sol, r, Az and OuterMmax x (p,q) fine fields PER RHS; the fine + // smoother adds FineSmootherMmax x (p,q) once. The MemoryManager LRU cap + // (--device-mem) must be BELOW what is physically left after the non-LRU allocations + // (comms buffers, dense slab, stencil buffers), or the device fills before anything + // is evicted. Print the estimate, the LRU state, and the device's own free count. + { + uint64_t fieldBytes = (uint64_t)FGrid->lSites()*sizeof(typename LatticeFermionD::scalar_object); + double outerGB = (double)nr*(4 + 2*OuterMmax)*fieldBytes/1.0e9; + double smthGB = (double)(2*FineSmootherMmax + 4)*fieldBytes/1.0e9; + std::cout << GridLogMessage << "Device budget: fine field " << fieldBytes/1.0e6 << " MB; outer GCR history " + << nr << " x (4 + 2 x " << OuterMmax << ") fields = " << outerGB << " GB; fine smoother history + temps ~ " + << smthGB << " GB; MemoryManager device LRU " << MemoryManager::DeviceCacheBytes()/1.0e9 << " GB now, cap " + << MemoryManager::DeviceMaxBytes/1.0e9 << " GB" << std::endl; + // Empty the device LRU: every setup-era Lattice copy (coarse null vectors, + // coarsening temporaries) goes back to host, so the solve's working set + // starts from a clean device and the cap applies to it alone. + MemoryManager::EvictAll(); + // ...and release the allocation caches' held blocks (setup-era deviceVector + // scratch that is "free" to the caller but not to hipMalloc). + MemoryManager::DropCache(); + MemoryManager::PrintBytes(); + acceleratorMem(); + } + std::cout << GridLogMessage << "**********************************************" << std::endl; + + Coordinate cml({nr,1,clatt[0],clatt[1],clatt[2],clatt[3]}); + Coordinate ccml({nr,1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]}); + GridCartesian *CMrhs = new GridCartesian(cml, cmsimd,cmmpi); + GridCartesian *CCMrhs = new GridCartesian(ccml,cmsimd,cmmpi); + + CoarseOpPV.SetGrid(CMrhs); + CoarseOpL2.SetGrid(CCMrhs); + + NonHermitianLinearOperator LinOpC (CoarseOpPV); + NonHermitianLinearOperator LinOpCC(CoarseOpL2); + + MrhsDenseCCSolve ccSolve(*DenseCC,nr); + + ShiftedLinearOperator ShiftedC(CoarseSmootherShift, LinOpC); + if ( PowerIterations > 0 ) { + // Spectral edges the smoother polynomials must respect (see + // scripts/gcr_polynomial.py: |R_m|>1 beyond the edge = amplification). + PowerIteration ("CoarseSmootherOp(shift="+std::to_string(CoarseSmootherShift)+")", ShiftedC, CMrhs, PowerIterations); + PowerIteration ("CoarseOp(unshifted)", LinOpC, CMrhs, PowerIterations); + PowerIteration("FineSmootherOp(shift="+std::to_string(FineSmootherShift)+")", ShiftedPVdagM, Ddwf.FermionGrid(), PowerIterations); + } + PrecGeneralisedConjugateResidualNonHermitian + CoarseSmootherGCR(0.01,1,ShiftedC,simpleC,CoarseSmootherMmax,CoarseSmootherNstep); + CoarseSmootherGCR.Level(2); CoarseSmootherGCR.Name("Csmoother"); CoarseSmootherGCR.SetZeroGuess(1); + + SwitchableSmoother CoarseSmootherSlot(CoarseSmootherGCR,"Csmoother GCR"); + MrhsCoarseThreeLevelPrec + L2to3Precon(LinOpC, CoarseSmootherSlot, MrhsProjectorL2, ccSolve, + Coarse5d, CoarseCoarse5d, CCMrhs, nr); + + PrecGeneralisedConjugateResidualNonHermitian + L2PGCR(CoarseSolverTol, CoarseSolverOrder/16, LinOpC, L2to3Precon, CoarseSolverMmax, 16); + L2PGCR.Level(2); L2PGCR.Name("Couter"); L2PGCR.SetZeroGuess(1); + + FineSmoother_t SmootherGCR(0.0,1,ShiftedPVdagM,simple_fine,FineSmootherMmax,FineSmootherOrder); + SmootherGCR.Level(1); SmootherGCR.Name("Fsmoother"); SmootherGCR.SetZeroGuess(1); + SmootherGCR.LogCoefficients(SmootherCoeffLog); + CoarseSmootherGCR.LogCoefficients(SmootherCoeffLog); + + SwitchableSmoother FineSmootherSlot(SmootherGCR,"Fsmoother GCR"); + MrhsTwoLevelMG > + ThreeLevelPrecon(PVdagM, FineSmootherSlot, MrhsProjector, L2PGCR, Coarse5d, CMrhs); + ThreeLevelPrecon.SetSloppy = SetFineSloppy; + ThreeLevelPrecon.SloppyComms = FineSloppyComms; + + MrhsPGCRNonHermitian + L1PGCR(OuterTol,1000,PVdagM,ThreeLevelPrecon,OuterMmax,OuterNstep); + L1PGCR.Level(1); L1PGCR.Name("Fouter"); L1PGCR.SetZeroGuess(1); + + ////////////////////////////////////////////////////////////////////// + // Smoother modes. Objects live for the duration of this RunSolve. + ////////////////////////////////////////////////////////////////////// + std::unique_ptr > FineCheb; + std::unique_ptr > CoarseCheb; + std::unique_ptr > FineReplay; + std::unique_ptr > CoarseReplay; + GCRCoefficients recF, recC; + { + GCRCoefficients::Select sel = GCRCoefficients::Last; + if ( PolyRecordSelect=="first" ) sel = GCRCoefficients::First; + if ( PolyRecordSelect=="mean" ) sel = GCRCoefficients::Mean; + recF.select = sel; recC.select = sel; + } + std::unique_ptr > Fms; + std::unique_ptr > F1dag; + std::unique_ptr FineFourier; + std::unique_ptr FineFourierGCR; // fgcr: Fourier as the preconditioner of a low-order fine GCR ("2-deep" smoother) + if ( FineSmootherMode == "fourier" || FineSmootherMode == "fgcr" ) { + // Free PVdagM inverse in the Landau frame (config already rotated). + // Block build is (4Ls)^2 per local momentum in memory; Ls=24 at large + // local volume is heavy -- fine on the laptop harness. + std::vector pbc = {1,1,1,1}; + Fms.reset (new FreeMobius5DInverse(FGrid,Ls,M5,b,c,FourierSmootherMass,pbc)); + F1dag.reset(new FreeMobius5DInverse(FGrid,Ls,M5,b,c,1.0,pbc)); + DaggerBlocks(*F1dag); + GainCap(*Fms, SmootherGainCap); + GainCap(*F1dag,SmootherGainCap); + FineFourier.reset(new FourierPVdagMSmoother(*Fms,*F1dag)); + if ( FineSmootherMode == "fourier" ) { + FineSmootherSlot.Set(*FineFourier,"Fsmoother Fourier"); + } else { + // fgcr: identical to the "gcr" fine smoother but with the Fourier free-inverse + // as the preconditioner instead of simple_fine. A low-order (FineSmootherOrder) + // GCR on ShiftedPVdagM, Fourier-preconditioned -- the outer GCR absorbs the + // non-normal transient that makes raw Fourier amplify (cf. (iii): S0 alone rho=13 + // but S0-as-preconditioner converged FGMRES in 181). Prints its residuals. + FineFourierGCR.reset(new FineSmoother_t(0.0,1,ShiftedPVdagM,*FineFourier,FineSmootherMmax,FineSmootherOrder)); + FineFourierGCR->Level(1); FineFourierGCR->Name("Fsmoother-FGCR"); FineFourierGCR->SetZeroGuess(1); + FineSmootherSlot.Set(*FineFourierGCR,"Fsmoother FourierGCR"); + } + } + if ( FineSmootherMode == "cheb" ) { + FineCheb.reset(new ChebyshevNonHermitianSmoother(FineChebLo,FineChebHi,FineSmootherOrder,ShiftedPVdagM)); + FineCheb->Verbose = PolyVerbose; FineCheb->name = "Fsmoother"; + FineSmootherSlot.Set(*FineCheb,"Fsmoother Chebyshev"); + } + if ( CoarseSmootherMode == "cheb" ) { + CoarseCheb.reset(new ChebyshevNonHermitianSmoother(CoarseChebLo,CoarseChebHi,CoarseSmootherNstep,ShiftedC)); + CoarseCheb->Verbose = PolyVerbose; CoarseCheb->name = "Csmoother"; + CoarseSmootherSlot.Set(*CoarseCheb,"Csmoother Chebyshev"); + } + // Recording window [PolyRecordStart, PolyRecordStart+PolyRecordIters). + // M3 (2026-08-26): the GCR polynomial changes fast over the first outer + // steps (per-call |r|/|r0| 0.0034 -> 0.017 over steps 1-4) and the MEAN + // of those is a poor smoother (replay 0.033-0.049 per call); record a + // settled window instead. + if ( PolyRecordStart == 0 ) { + if ( FineSmootherMode == "replay" ) SmootherGCR.SetCoefficientRecorder(&recF); + if ( CoarseSmootherMode == "replay" ) CoarseSmootherGCR.SetCoefficientRecorder(&recC); + } + // Record -> replay, with optional periodic re-recording ("re-record, not + // fade away": HDCG refreshed its polynomial every 10 steps, tracking the + // evolving spectral content of the residual). Schedule on outer steps: + // [PolyRecordStart, +PolyRecordIters) adaptive GCR, recording + // then replay of the selected recorded call; + // if PolyRefresh>0: every PolyRefresh steps, ONE adaptive recording + // step, then replay of that call. + auto BuildReplays = [&](void){ + if ( FineSmootherMode == "replay" ) { + SmootherGCR.SetCoefficientRecorder(nullptr); + recF.Flush(); recF.Report("Fsmoother"); + FineReplay.reset(new GCRReplaySmoother(ShiftedPVdagM,recF)); + FineReplay->Verbose = PolyVerbose; FineReplay->name = "Fsmoother"; + FineSmootherSlot.Set(*FineReplay,"Fsmoother replay"); + SmootherGCR.ReleaseHistory(); + } + if ( CoarseSmootherMode == "replay" ) { + CoarseSmootherGCR.SetCoefficientRecorder(nullptr); + recC.Flush(); recC.Report("Csmoother"); + CoarseReplay.reset(new GCRReplaySmoother(ShiftedC,recC)); + CoarseReplay->Verbose = PolyVerbose; CoarseReplay->name = "Csmoother"; + CoarseSmootherSlot.Set(*CoarseReplay,"Csmoother replay"); + CoarseSmootherGCR.ReleaseHistory(); + } + }; + auto StartRecording = [&](int step){ + if ( FineSmootherMode == "replay" ) { { auto sel=recF.select; recF = GCRCoefficients(); recF.select=sel; } SmootherGCR.SetCoefficientRecorder(&recF); FineSmootherSlot.Set(SmootherGCR,"Fsmoother GCR (recording)"); } + if ( CoarseSmootherMode == "replay" ) { { auto sel=recC.select; recC = GCRCoefficients(); recC.select=sel; } CoarseSmootherGCR.SetCoefficientRecorder(&recC); CoarseSmootherSlot.Set(CoarseSmootherGCR,"Csmoother GCR (recording)"); } + std::cout << GridLogMessage << "Smoother coefficient recording starts at outer step " << step << std::endl; + }; + int switchStep = PolyRecordStart + PolyRecordIters; + L1PGCR.OnStep = [&](int step){ + if ( FineSmootherMode != "replay" && CoarseSmootherMode != "replay" ) return; + if ( step == PolyRecordStart && PolyRecordStart > 0 ) StartRecording(step); + if ( step == switchStep ) { BuildReplays(); return; } + if ( PolyRefresh > 0 && step > switchStep ) { + int since = step - switchStep; + if ( since % PolyRefresh == 0 ) { StartRecording(step); } // one adaptive, recorded step + if ( since % PolyRefresh == 1 ) { BuildReplays(); } // then replay it + } + }; + std::cout << GridLogMessage << "Smoother modes: fine " << FineSmootherMode << " coarse " << CoarseSmootherMode + << (FineSmootherMode=="replay"||CoarseSmootherMode=="replay" ? " (record outer steps "+std::to_string(PolyRecordStart)+".."+std::to_string(PolyRecordStart+PolyRecordIters)+")" : "") + << std::endl; + + std::vector src(nr,FGrid), sol(nr,FGrid); + for(int r=0;r=12, EvictAll/DropCache). Translations cached by -the provider go stale. -Symptom (ALPS/CSCS, aarch64 Grace/H200, cray-mpich 8.1.32, libfabric 1.22): the +https://github.com/ofiwg/libfabric/issues/11451 + +SEGFAULT in first MPI_Comm_dup on more than one node, after MPI_Init + +Cause: symptom (ALPS/CSCS, aarch64 Grace/H200, cray-mpich 8.1.32, libfabric 1.22): the memhooks monitor intercepts munmap by PATCHING THE PLT at MPI_Init (ofi_memhooks_start -> ofi_write_patch with a garbage data_size); the write overruns -munmap@plt into the NEXT PLT entry (MPI_Comm_dup in Grid's binary), leaving -`br x15` where an adrp belongs -> segfault on first MPI_Comm_dup. Diagnosed with -a hardware watchpoint on the PLT entry during MPI_Init; see the issue for the +munmap@plt into the NEXT PLT entry (MPI_Comm_dup in Grid's binary) +leaving +`br x15` where an adrp belongs -> + +Diagnosed with a hardware watchpoint on the PLT entry during MPI_Init; see the issue for the gdb transcript. Reproduce on one node with MPICH_SINGLE_HOST_ENABLED=0. -Fix (either): export FI_MR_CACHE_MONITOR=kdreg2 (kernel-driven invalidation; OLCF's - own recommendation, NOT the default) - export FI_MR_CACHE_MAX_COUNT=0 (no registration cache at all) + +Fix (either): + export FI_MR_CACHE_MONITOR=kdreg2 +OR + export FI_MR_CACHE_MONITOR=disabled + export FI_MR_CACHE_MAX_COUNT=0 + On ALPS this is a CORRECTNESS requirement for MPI on Slingshot, not a tuning. + + + +============================================================================ +Frontier +============================================================================ + +a) FI_MR_CACHE_MONITOR=kdreg2 leads to Runtime MPI errors with "NO_TRANSLATION" + +Symptom (Frontier, x86, Cray MPICH 8.1.x, ROCm 7.2): device-buffer MPI fails with + MPI_Waitall ... MPIDI_OFI_handle_cq_error: OFI poll failed + (ofi_events.c:MPIDI_OFI_handle_cq_error: Input/output error - NO_TRANSLATION) +on a LIVE, never-freed hipMalloc buffer + + +b) export FI_MR_CACHE_MONITOR=disabled + export FI_MR_CACHE_MAX_COUNT=0 + +Leads to INCORRECT RESULTS + +https://github.com/ofiwg/libfabric/issues/12773 + +https://github.com/ofiwg/libfabric/issues/12775 + +Fix: + +export FI_HMEM_ROCR_USE_DMABUF=0 + Every systems/Frontier job and sourceme now sets kdreg2 on OLCF's recommendation; whether it resolves the Frontier NO_TRANSLATION is the pending test above. + diff --git a/tests/debug/Test_blockcyclic.cc b/tests/debug/Test_blockcyclic.cc index e5b0d04ff..86fa8708b 100644 --- a/tests/debug/Test_blockcyclic.cc +++ b/tests/debug/Test_blockcyclic.cc @@ -20,7 +20,7 @@ Author: Peter Boyle ////////////////////////////////////////////////////////////////////////////// // Regression gate for BlockCyclicLayout -- stage 1 of the 2D distributed -// dense inverse (documentation/DistributedDenseInverse2D.tex). +// dense inverse. // // The layout is pure index arithmetic, so this test is EXHAUSTIVE rather // than statistical: every stage sweeps a battery of (N, nb, Pr, Pc) diff --git a/tests/debug/Test_coarse.cc b/tests/debug/Test_coarse.cc new file mode 100644 index 000000000..362cbc465 --- /dev/null +++ b/tests/debug/Test_coarse.cc @@ -0,0 +1,261 @@ +/************************************************************************************* + + Grid physics library, www.github.com/paboyle/Grid + + Source file: ./tests/debug/Test_coarse.cc + + Copyright (C) 2026 + +Author: Peter Boyle + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + See the full license in the file "LICENSE" in the top level distribution directory +*************************************************************************************/ +/* END LEGAL */ + +// +// MultiGeneralCoarsenedOperator against the existing mrhs coarse operator. +// +// The reference (the deprecated operator) is constructed on the D+1 grid; the +// operator under test on the D dimensional grid, with SetGrid() adopting the +// caller owned D+1 grid and building its padded cell and neighbour table from +// the D dimensional stencil, with the Nrhs factor multiplied in. +// +// Both are given identical matrix elements, so any difference in the apply is +// the restructured neighbour table. The same geometry object is passed to +// both: the reference adds one to skip for the rhs direction, the operator +// under test uses it as is on the D dimensional grid, so both describe the +// same stencil over the D dimensions. +// +#include + +using namespace Grid; + +const int nbasis = 8; + +typedef vSpinColourVector FineObj; +typedef sTComplexD CComplexT; // unvectorised coarse space + +typedef DeprecatedMultiGeneralCoarsenedMatrix RefOperator; +typedef MultiGeneralCoarsenedOperator TestOperator; + +//////////////////////////////////////////////////////////////////////// +// Identical random matrix elements into both operators +//////////////////////////////////////////////////////////////////////// +template +void SeedMatrixElements(OpA &A,OpB &B,int npoint,GridSerialRNG &sRNG) +{ + typedef typename OpA::calcMatrix calcMatrix; + + deviceVector bufA,bufB; + for(int p=0;p host(sites); + + ComplexD *w = (ComplexD *)&host[0]; + int64_t words = sites*sizeof(calcMatrix)/sizeof(ComplexD); + for(int64_t i=0;i +RealD MatrixChecksum(Op &O,int npoint) +{ + typedef typename Op::calcMatrix calcMatrix; + RealD sum=0.0; + deviceVector buf; + for(int p=0;p host(sites); + acceleratorCopyFromDevice(&buf[0],&host[0],sites*sizeof(calcMatrix)); + ComplexD *w = (ComplexD *)&host[0]; + int64_t words = sites*sizeof(calcMatrix)/sizeof(ComplexD); + for(int64_t i=0;iNsimd() << std::endl; + + //////////////////////////////////////////////// + // One geometry object for both, and one D+1 grid + // owned here and shared by both operators: fields + // conform only across a shared grid object. + //////////////////////////////////////////////// + NextToNearestStencilGeometry4D geom(CoarseD); + + Coordinate mlatt(1,nrhs), msimd(1,1), mmpi(1,1); + for(int d=0;dNsimd() << std::endl; + + std::cout << GridLogMessage << "npoint ref " << OpRef.geom.npoint + << " npoint test " << OpTest.geom.npoint << std::endl; + GRID_ASSERT(OpRef.geom.npoint == OpTest.geom.npoint); + + int npoint = OpRef.geom.npoint; + + //////////////////////////////////////////////// + // Identical matrix elements + //////////////////////////////////////////////// + GridSerialRNG sRNG; sRNG.SeedFixedIntegers(std::vector({7,8,9,10})); + SeedMatrixElements(OpRef,OpTest,npoint,sRNG); + + RealD ckRef = MatrixChecksum(OpRef,npoint); + RealD ckTest = MatrixChecksum(OpTest,npoint); + std::cout << GridLogMessage << "matrix element checksum ref " << ckRef + << " test " << ckTest << std::endl; + GRID_ASSERT( ckRef == ckTest ); + + //////////////////////////////////////////////// + // Same input, compare the applies + //////////////////////////////////////////////// + typedef RefOperator::CoarseVector CoarseVector; + // RNG on the D dimensional grid fills any D+1 field: the rhs direction is + // undistributed and divides cleanly. One RNG serves every Nrhs. + GridParallelRNG pRNG(CoarseD); pRNG.SeedFixedIntegers(std::vector({1,2,3,4})); + + CoarseVector in (CoarseMulti); random(pRNG,in); + CoarseVector out1(CoarseMulti); + CoarseVector out2(CoarseMulti); + CoarseVector err (CoarseMulti); + + OpRef.M(in,out1); + OpTest.M(in,out2); + + err = out1 - out2; + std::cout << GridLogMessage << "|ref out|^2 = " << norm2(out1) + << " |test out|^2 = " << norm2(out2) << std::endl; + std::cout << GridLogMessage << "|ref - test|^2 = " << norm2(err) << std::endl; + GRID_ASSERT( norm2(out1) > 0.0 ); + GRID_ASSERT( norm2(err) == 0.0 ); + + //////////////////////////////////////////////// + // SetGrid is idempotent on pointer identity + //////////////////////////////////////////////// + OpTest.SetGrid(CoarseMulti); + GRID_ASSERT( MatrixChecksum(OpTest,npoint) == ckTest ); + OpTest.M(in,out2); + err = out1 - out2; + GRID_ASSERT( norm2(err) == 0.0 ); + std::cout << GridLogMessage << "SetGrid idempotent on identity" << std::endl; + + //////////////////////////////////////////////// + // Move to a different Nrhs and back. The matrix + // elements are Nrhs independent and must survive + // both the release and the rebuild. + //////////////////////////////////////////////// + // Nrhs 1 is the single RHS case through the multiRHS path, and each slice + // of the Nrhs 4 apply must come back unchanged. + OpTest.M(in,out2); + for(int nr=2;nr>=1;nr--){ + Coordinate latt2(1,nr), simd2(1,1), mpi2(1,1); + for(int d=0;d 2 -> 1 -> release -> 4, |ref - test|^2 = " + << norm2(err) << std::endl; + GRID_ASSERT( norm2(err) == 0.0 ); + + std::cout << GridLogMessage << "Test_coarse: ALL PASS" << std::endl; + + Grid_finalize(); +} diff --git a/tests/debug/Test_coarse_coarsen.cc b/tests/debug/Test_coarse_coarsen.cc new file mode 100644 index 000000000..28fa50c9f --- /dev/null +++ b/tests/debug/Test_coarse_coarsen.cc @@ -0,0 +1,279 @@ +/************************************************************************************* + + Grid physics library, www.github.com/paboyle/Grid + + Source file: ./tests/debug/Test_coarse_coarsen.cc + + Copyright (C) 2026 + +Author: Peter Boyle + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + See the full license in the file "LICENSE" in the top level distribution directory +*************************************************************************************/ +/* END LEGAL */ + +// +// Coarsen the same fine operator two ways and compare the matrix elements. +// +// ref : DeprecatedMultiGeneralCoarsenedMatrix::CoarsenOperator, vectorised +// coarse space matched to the fine SIMD layout, single RHS fine +// applications +// test : MultiGeneralCoarsenedOperator::CoarsenOperator on the D+1 grid, +// unvectorised sComplexD coarse space, the batch of phased basis +// vectors carried in the rhs direction and applied through +// MrhsPromotedOperator +// +// BLAS_A is written by GridtoBLAS in lSite order, which does not depend on +// the SIMD layout, so the two are directly comparable. +// +#include + +using namespace Grid; + +const int nbasis = 8; +const int batch = 9; + +typedef vSpinColourVector FineObj; +typedef vTComplex CComplexV; // vectorised coarse space, reference +typedef sTComplexD CComplexS; // unvectorised coarse space, under test + +typedef DeprecatedMultiGeneralCoarsenedMatrix RefOperator; +typedef MultiGeneralCoarsenedOperator TestOperator; + +template +void ReadMatrix(Op &O,int npoint,std::vector > &host) +{ + typedef typename Op::calcMatrix calcMatrix; + host.resize(npoint); + // MatrixPointOut carries one stencil point in lSite order whichever internal + // layout the operator uses. + deviceVector buf; + for(int p=0;pNsimd() + << " coarse ref "<Nsimd() + << " coarse test "<Nsimd()<({1,2,3,4})); + LatticeGaugeFieldD Umu(FineGrid); SU::HotConfiguration(pRNG,Umu); + + RealD mass = 0.1; + WilsonFermionD Dw(Umu,*FineGrid,*FrbGrid,mass); + MdagMLinearOperator HermOp(Dw); + + //////////////////////////////////////////////// + // One random subspace, two copies: CoarsenOperator orthogonalises in place + //////////////////////////////////////////////// + Aggregation Subspace(CoarseV,FineGrid,0); + std::vector subspace(nbasis,FineGrid); + for(int i=0;i MrhsHermOp(HermOp,FineGrid,batch); + + std::cout << GridLogMessage << "test CoarsenOperator (D+1)" << std::endl; + OpTest.CoarsenOperator(MrhsHermOp,FineGridMulti,subspace,CoarseS); + + //////////////////////////////////////////////// + // Compare matrix elements + //////////////////////////////////////////////// + int npoint = OpRef.geom.npoint; + GRID_ASSERT(npoint == OpTest.geom.npoint); + typedef RefOperator::calcMatrix calcMatrix; + std::vector > A1,A2; + ReadMatrix(OpRef,npoint,A1); + ReadMatrix(OpTest,npoint,A2); + + RealD num=0.0, den=0.0; + for(int p=0;p > A3; + ReadMatrix(OpTestSrhs,npoint,A3); + + RealD nums=0.0; + for(int p=0;p + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + See the full license in the file "LICENSE" in the top level distribution + directory +*************************************************************************************/ +/* END LEGAL */ +#include + +// MultiRHSDeflation: the D+1 multiRHS interface (one permutation pass each +// way) must reproduce the vector-of-D-fields interface bit for bit up to +// the summation order of the GEMMs. Random "eigenvectors" and values: the +// deflation formula G = E (E^dag R)/lambda does not care that they are not +// eigenpairs of anything. + +using namespace Grid; + +int main (int argc, char ** argv) +{ + Grid_init(&argc,&argv); + + const int nbasis = 8; + const int nev = 12; + const int nrhs = 5; + + typedef iVector siteVector; + typedef Lattice CoarseVector; + + // Unvectorised D and D+1 coarse grids, as MGCoarseGrids builds them + Coordinate latt({1,4,4,4,4}); + Coordinate simd({1,1,1,1,1}); + Coordinate mpi = GridDefaultMpi(); + Coordinate mpi5({1,mpi[0],mpi[1],mpi[2],mpi[3]}); + GridCartesian Coarse5d(latt,simd,mpi5); + + Coordinate latt6({nrhs,1,4,4,4,4}); + Coordinate simd6({1,1,1,1,1,1}); + Coordinate mpi6({1,1,mpi[0],mpi[1],mpi[2],mpi[3]}); + GridCartesian CoarseMrhs(latt6,simd6,mpi6); + + GridParallelRNG RNG(&Coarse5d); RNG.SeedFixedIntegers(std::vector({1,2,3,4})); + + std::vector evec(nev,&Coarse5d); + std::vector eval(nev); + for(int e=0;e src(nrhs,&Coarse5d), guess(nrhs,&Coarse5d); + for(int r=0;r Deflator; + Deflator.ImportEigenBasis(evec,eval); + + // Reference: the vector interface + Deflator.DeflateSources(src,guess); + + // The D+1 interface on the same sources + CoarseVector src_mrhs(&CoarseMrhs), guess_mrhs(&CoarseMrhs); + for(int r=0;r + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + See the full license in the file "LICENSE" in the top level distribution + directory +*************************************************************************************/ +/* END LEGAL */ + +// +// Isolate a sloppy-comms dagger defect: on a CPU/GEN multi-rank build the +// sloppy dagger halo can be wrong by ~5% of norm2, deterministically, which +// Benchmark_dwf's sloppy pass catches as a failed dagger Cshift check. +// +// The sloppy compressed-buffer pool (StencilBuffer::DeviceCommBuf) is a +// SHARED STATIC across all stencil objects, so scenarios contaminate each +// other inside one process: this program runs exactly ONE scenario per +// invocation, selected with --seq : +// +// fresh-dag sloppy dagger is the operator's first call +// nodag-dag sloppy nodag x3 (one source), then sloppy dagger (same source) +// nodag-dag-fresh nodag x3 (fresh source each), then dagger (fresh source) +// nodag-dag-newsrc nodag x3 (one source), then dagger (different source) +// dag-nodag-dag dagger, nodag x3, dagger (dagger-first history) +// +// Every call is checked against a separate never-sloppy reference +// operator (exact Dhop == the Cshift construction at 1e-31, certified by +// Benchmark_dwf's exact pass). The last dagger's error is fingerprinted +// by slice along each MPI-decomposed direction. +// +// OMP_NUM_THREADS=1 mpirun -n 2 ./Test_sloppy_dagger --grid 8.8.8.16 \ +// --mpi 1.1.1.2 --seq nodag-dag +// +#include + +using namespace std; +using namespace Grid; + +int main (int argc, char ** argv) +{ + Grid_init(&argc,&argv); + + std::string seq("nodag-dag"); + if( GridCmdOptionExists(argv,argv+argc,"--seq") ) + seq = GridCmdOptionPayload(argv,argv+argc,"--seq"); + + const int Ls=8; + + GridCartesian * UGrid = SpaceTimeGrid::makeFourDimGrid(GridDefaultLatt(), + GridDefaultSimd(Nd,vComplex::Nsimd()), + GridDefaultMpi()); + GridRedBlackCartesian * UrbGrid = SpaceTimeGrid::makeFourDimRedBlackGrid(UGrid); + GridCartesian * FGrid = SpaceTimeGrid::makeFiveDimGrid(Ls,UGrid); + GridRedBlackCartesian * FrbGrid = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGrid); + + GridParallelRNG RNG4(UGrid); RNG4.SeedFixedIntegers({1,2,3,4}); + GridParallelRNG RNG5(FGrid); RNG5.SeedFixedIntegers({5,6,7,8}); + + LatticeGaugeField Umu(UGrid); + SU::HotConfiguration(RNG4,Umu); + + RealD mass=0.1, M5=1.8; + DomainWallFermionD Dref(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5); // never sloppy + DomainWallFermionD Dw (Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5); // under test + Dw.SloppyComms(1); + + Coordinate mpi = GridDefaultMpi(); + LatticeFermionD src(FGrid), exact(FGrid), sloppy(FGrid), err(FGrid); + + // One call: fresh or reused source, dag or not; always checked. + LatticeFermionD srcA(FGrid); gaussian(RNG5,srcA); + auto call = [&](int dag, int fresh, const char *tag){ + if ( fresh ) gaussian(RNG5,src); else src = srcA; + Dref.Dhop(src,exact,dag); + Dw.Dhop(src,sloppy,dag); + err = sloppy - exact; + std::cout << GridLogMessage << "SEQ[" << seq << "] " << tag + << (dag?" DAG ":" NODAG ") << " norm2(err) " << norm2(err) << std::endl; + }; + // First bad site: coordinate + exact-vs-sloppy hex words (PB: look for + // expected/actual mismatching in the high-order half word). + auto firstbad = [&](LatticeFermionD &ex, LatticeFermionD &sl){ + typedef LatticeFermionD::scalar_object sobj; + Coordinate gdims = FGrid->GlobalDimensions(); + int64_t gsites = FGrid->gSites(); + for(int64_t g=0; g 1.0e-4 ) { + std::cout << GridLogMessage << "FIRSTBAD site " << gcoor + << " word " << w << " (spin "<<(w/6)%4<<" col "<<(w/2)%3<<" reim "<<(w%2)<<")" + << " exact " << std::hex << we[w] + << " sloppy " << ws[w] << std::dec << std::endl; + for(int w2=0;w2<8&&w2 sn; + sliceNorm(sn,err,mu+1); + std::cout << GridLogMessage << "SEQ[" << seq << "] err by slice of dim " << mu << ":"; + for(int t=0;t<(int)sn.size();t++) std::cout << " " << sn[t]; + std::cout << std::endl; + } + }; + + if ( seq == "fresh-dag" ) { + call(1,0,"call0"); + } else if ( seq == "nodag-dag" ) { + call(0,0,"call0"); call(0,0,"call1"); call(0,0,"call2"); + call(1,0,"call3"); + } else if ( seq == "nodag-dag-fresh" ) { + call(0,1,"call0"); call(0,1,"call1"); call(0,1,"call2"); + call(1,1,"call3"); + } else if ( seq == "nodag-dag-newsrc" ) { + call(0,0,"call0"); call(0,0,"call1"); call(0,0,"call2"); + call(1,1,"call3"); + } else if ( seq == "dag-nodag-dag" ) { + call(1,0,"call0"); + call(0,0,"call1"); call(0,0,"call2"); call(0,0,"call3"); + call(1,0,"call4"); + } else if ( seq == "noleave" ) { + // NO exact-operator call between sloppy calls: references precomputed. + LatticeFermionD refnodag(FGrid), refdag(FGrid); + Dref.Dhop(srcA,refnodag,DaggerNo); + Dref.Dhop(srcA,refdag,DaggerYes); + for(int i=0;i<2;i++){ + Dw.Dhop(srcA,sloppy,DaggerNo); + err = sloppy - refnodag; + std::cout << GridLogMessage << "SEQ[noleave] call" << i << " NODAG norm2(err) " << norm2(err) << std::endl; + if ( i==0 ) firstbad(refnodag,sloppy); + } + Dw.Dhop(srcA,sloppy,DaggerYes); + err = sloppy - refdag; + std::cout << GridLogMessage << "SEQ[noleave] call3 DAG norm2(err) " << norm2(err) << std::endl; + } else if ( seq == "twoop" ) { + // A SECOND sloppy operator shares the static pool; sequence runs on it + // with interleaved exact references (as the honest runs had). + DomainWallFermionD Dw2(Umu,*FGrid,*FrbGrid,*UGrid,*UrbGrid,mass,M5); + Dw2.SloppyComms(1); + Dw.Dhop(srcA,sloppy,DaggerNo); // prime the FIRST sloppy op's state + for(int i=0;i<3;i++){ + Dref.Dhop(srcA,exact,DaggerNo); + Dw2.Dhop(srcA,sloppy,DaggerNo); + err = sloppy - exact; + std::cout << GridLogMessage << "SEQ[twoop] call" << i << " NODAG norm2(err) " << norm2(err) << std::endl; + } + Dref.Dhop(srcA,exact,DaggerYes); + Dw2.Dhop(srcA,sloppy,DaggerYes); + err = sloppy - exact; + std::cout << GridLogMessage << "SEQ[twoop] call3 DAG norm2(err) " << norm2(err) << std::endl; + } else if ( seq == "self-exact" ) { + // Exact call on the SAME object (Dw), opposite sense, then sloppy: + // distinguishes per-object from shared state. + LatticeFermionD refnodag(FGrid); + Dref.Dhop(srcA,refnodag,DaggerNo); // reference only, early + Dw.SloppyComms(0); + Dw.Dhop(srcA,sloppy,DaggerYes); // exact DAG on Dw itself + Dw.SloppyComms(1); + Dw.Dhop(srcA,sloppy,DaggerNo); // sloppy NODAG: sense mismatch + err = sloppy - refnodag; + std::cout << GridLogMessage << "SEQ[self-exact] sloppy NODAG after own exact DAG: " << norm2(err) << std::endl; + } else if ( seq == "cold" ) { + // NO exact Dhop anywhere before the sloppy calls. + LatticeFermionD outn(FGrid), outd(FGrid); + Dw.Dhop(srcA,outn,DaggerNo); + Dw.Dhop(srcA,outd,DaggerYes); + LatticeFermionD refnodag(FGrid), refdag(FGrid); + Dref.Dhop(srcA,refnodag,DaggerNo); + Dref.Dhop(srcA,refdag,DaggerYes); + err = outn - refnodag; + std::cout << GridLogMessage << "SEQ[cold] first-ever sloppy NODAG: " << norm2(err) << std::endl; + err = outd - refdag; + std::cout << GridLogMessage << "SEQ[cold] then sloppy DAG: " << norm2(err) << std::endl; + } else { + std::cout << GridLogMessage << "unknown --seq " << seq << std::endl; + } + fingerprint(); + + Grid_finalize(); +}