Compare commits

..
486 Commits
Author SHA1 Message Date
Peter Boyle 2043072d9c Merge pull request #493 from jdmaia/hip_stencil_padding
Fix alignment on StencilEntry struct for HIP
2026-09-11 17:32:30 -04:00
Julio Maia 487ec8d410 Fix alignment on StencilEntry struct for HIP
CUDA neatly aligns the StencilEntry struct to 16-bytes, which is not used on HIP.
Mapping the StencilEntry struct to 16-bytes on HIP yields around 5% better performance
on MI355X-class GPUs.
2026-09-11 10:56:02 -05:00
Peter Boyle 75bb6bdb11 Update for bug report 2026-09-09 18:12:52 -04:00
Peter Boyle a2ef8b39d3 New script for ORNL to try 2026-09-09 15:13:12 -04:00
Peter Boyle 03495bfcf8 Simplify 2026-09-09 14:51:40 -04:00
Peter Boyle 7f6b0f409c Better commenting 2026-09-09 14:39:56 -04:00
Peter Boyle ec682693c1 Rename to be more familial with PowerMethod 2026-09-09 14:20:24 -04:00
Peter Boyle f8f93c1443 Non herm case 2026-09-09 14:12:49 -04:00
Peter Boyle fbfb93af64 MG better organisation 2026-09-09 14:11:55 -04:00
Peter Boyle 0ac6b72783 Claude's reorg to clean up parameters 2026-09-08 15:53:52 -04:00
Peter Boyle e8a82fa683 Multigrid clean up phase, and fix shell variables for libfabric bug work around on Frontier 2026-09-08 15:52:15 -04:00
Peter Boyle a07545adc4 Preparing for multigrid parameter consolidation and clean up of code, rationalise the different variants. 2026-09-06 14:18:00 -04:00
Peter Boyle 482f3cbaa2 Real part comparisons 2026-09-05 08:05:03 -04:00
Peter Boyle a28f7ad531 Grids in FermionOperator 2026-09-03 23:18:06 -04:00
Peter Boyle 953a401366 Use PlannedFFT in momentum space propagator. 2026-09-03 20:59:42 -04:00
Peter Boyle 59f4a3729a FFT improvement by ~2x 2026-09-03 18:13:02 -04:00
Peter Boyle 357ede3664 Improved FFT -- 1.5-2x when there are 2-6 ranks in a given axis of the cartesian communicator.
Barrel shift -> all to all (x2) and distributed FFT work fully load balanced without redundant work.
There is little more I can do now on FFT. Comms dominated and running distributed work dividing bandwidth optimal RingAllToAll

/ccs/home/paboyle/ParallelIO/systems/Frontier/tests/core/Test_fft_prop --mpi 3.6.4.4 --grid 48.48.48.96 --accelerator-threads 8 --shm 4096 --shm-mpi 1 --device-mem
32000 --log Error,Warning,Message,Performance

*************************************************
 Benchmarking FFT of LatticeFermionD on plane wave
*************************************************
Grid : Performance : 0.501524 s :  FFT took     0.001311 s (transpose P=3)
Grid : Performance : 0.501531 s :  FFT pack     5.9e-05 s
Grid : Performance : 0.501533 s :  FFT alltoall 0.000828 s
Grid : Performance : 0.501534 s :  FFT reorder  0.000204 s
Grid : Performance : 0.501535 s :  FFT kernels  1e-05 s
Grid : Performance : 0.501536 s :  FFT unpack   5e-05 s
Grid : Performance : 0.509992 s :  FFT took     0.001829 s (transpose P=6)
Grid : Performance : 0.510000 s :  FFT pack     6e-05 s
Grid : Performance : 0.510002 s :  FFT alltoall 0.001436 s
Grid : Performance : 0.510003 s :  FFT reorder  0.000202 s
Grid : Performance : 0.510005 s :  FFT kernels  9e-06 s
Grid : Performance : 0.510006 s :  FFT unpack   5.3e-05 s
Grid : Performance : 0.517690 s :  FFT took     0.001599 s (transpose P=4)
Grid : Performance : 0.517698 s :  FFT pack     6e-05 s
Grid : Performance : 0.517700 s :  FFT alltoall 0.001258 s
Grid : Performance : 0.517701 s :  FFT reorder  0.0002 s
Grid : Performance : 0.517702 s :  FFT kernels  9e-06 s
Grid : Performance : 0.517703 s :  FFT unpack   4.9e-05 s
Grid : Performance : 0.524858 s :  FFT took     0.001561 s (transpose P=4)
Grid : Performance : 0.524865 s :  FFT pack     5.8e-05 s
Grid : Performance : 0.524867 s :  FFT alltoall 0.001213 s
Grid : Performance : 0.524868 s :  FFT reorder  0.000209 s
Grid : Performance : 0.524869 s :  FFT kernels  8e-06 s
Grid : Performance : 0.524870 s :  FFT unpack   4.9e-05 s
*************************************************
 FFT of [48 48 48 96] LatticeFermionD took 0.030916 s
*************************************************
2026-09-03 17:33:32 -04:00
Peter Boyle e767694b82 Better memory tracking 2026-08-30 20:16:31 -04:00
Peter Boyle 4c7953c2d1 Better memory logging 2026-08-30 20:16:17 -04:00
Peter Boyle 6c4634bc90 Command line arg 2026-08-30 10:42:10 -04:00
Peter Boyle f0a2c0c465 Changes to investigate lib fabric memory region cache fail on Frontier 2026-08-30 10:41:39 -04:00
Peter Boyle 01f504ca4b Updated test job 2026-08-29 09:08:15 -04:00
Peter Boyle 15e00edda2 POssible ROCM bug addressing 2026-08-29 01:15:12 -04:00
Peter Boyle 21b53c06d1 FI investigatins 2026-08-28 23:31:54 -04:00
Peter Boyle e03797e882 More FI_MR related 2026-08-28 22:01:36 -04:00
Peter Boyle 43c6573ca4 Memory manager update to drop cache 2026-08-28 17:03:41 -04:00
Peter Boyle f367bc8bce Solving memory pressure in NRHS=12 solver 2026-08-28 13:52:45 -04:00
Peter Boyle 63c2cdb712 Debug ulimit as core files driving me crazy 2026-08-28 11:20:43 -04:00
Peter Boyle 9b4c388585 Update for latt parameter; claude's predeliction for using shell variables rather than "grid" style command line args is getting annoying 2026-08-28 10:04:36 -04:00
Peter Boyle 576cebd56a Job for debug 2026-08-28 09:03:56 -04:00
Peter Boyle 86da22fb78 Debug page fault 2026-08-28 08:52:11 -04:00
Peter Boyle 27e32b1633 Stop mucking about with THREAD_MULTIPLE 2026-08-28 07:06:45 -04:00
Peter Boyle 2264afda84 Allreduce benchmarking 2026-08-27 22:11:06 -04:00
Peter Boyle 792d2a8258 Masked/orthog dim CartesianRingAllReduce and CartesianRingAllGather 2026-08-27 17:10:49 -04:00
Peter Boyle 7abc19dc03 Allow subsets of dimensions for spatial only sums 2026-08-27 17:03:14 -04:00
Peter Boyle 074bddd48b Probing environment effects on dense inversion time 2026-08-27 13:56:02 -04:00
Peter Boyle 6cea81bc2c More probing why test harness vs. slate was a little faster running Grid's dense inverse than normal environment 2026-08-27 13:42:18 -04:00
Peter Boyle 68b9fa86d7 Faster leaf inverse 2026-08-27 12:53:26 -04:00
Peter Boyle d44964d923 Faster dense inverse, I hope 2026-08-27 12:13:46 -04:00
Peter Boyle 52998539bc Bigger leaf operations should spead up distributed inverse 2026-08-27 12:08:37 -04:00
Peter Boyle 88acdb8172 More perf diagnostics 2026-08-27 11:22:35 -04:00
Peter Boyle 0857f3aa82 Mor ecomms probing 2026-08-27 09:26:24 -04:00
Peter BoyleandClaude Fable 5 0979371c45 SUMMA telemetry: ring time histogram by message size
Per log2-size bucket: messages, GB, seconds inside SendToRecvFrom, GB/s,
% of ring time.  Decomposes the 2 GB/s/rank average (probe: 11-20 GB/s
at 8 MB) into latency-bound small messages vs slow large ones vs wait.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RAhdQHrkzKmzfxpxW1whLn
2026-08-27 02:02:30 -04:00
Peter BoyleandClaude Fable 5 96071aa492 Dense allgather: stage/scatter x by global-site order, not rank-major block
DENSE_DEVICE_SUM=4 assumed each rank's rows of x were the contiguous
[me*nrows,...) block; x and the slab columns are in global-site order
(hX[myGsite*nbasis+b]), the contiguous block is the rank-major index.
Coincide on one rank only -> Frontier VERIFY 0.9965.  Stage via myGsite,
scatter through the inverse of BuildRankMajorMap.  4-rank laptop VERIFY
7.38e-07, identical to the allreduce path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RAhdQHrkzKmzfxpxW1whLn
2026-08-27 02:02:01 -04:00
Peter Boyle e5f1553232 Better reduction 2026-08-26 23:36:31 -04:00
Peter Boyle 06c6011229 Timestamp improvement 2026-08-26 21:56:04 -04:00
Peter Boyle cbe97cc40d Smoother search improvement 2026-08-26 21:37:36 -04:00
Peter Boyle dc1ae3185a Ring allgather too. Let's nail the Dense CoarseCoarse 2026-08-26 21:05:36 -04:00
Peter Boyle 8e4a304626 Less aggressive L5 2026-08-26 20:17:19 -04:00
Peter Boyle 5372dce0c2 Rerecord, not fade away ! 2026-08-26 20:15:14 -04:00
Peter Boyle bc67e2c584 abs thrust issue 2026-08-26 17:58:18 -04:00
Peter Boyle e15099eb7b Both changes to improve the buffer persistence in teh dense distributed inverse and to add verbosity to polynomial smoothers 2026-08-26 17:48:03 -04:00
Peter Boyle 5f8f396e9b More jobs for testing Mgrid 2026-08-26 14:22:46 -04:00
Peter Boyle 841e59e8c0 GCR coefficient recording and playback 2026-08-26 14:22:00 -04:00
Peter Boyle b55ca3d50d Consolidate polynomial smoothers + logging time breakdown in Dense Coarsest invers 2026-08-26 14:20:50 -04:00
Peter Boyle d7c9076228 Power method added 2026-08-26 06:54:20 -04:00
Peter Boyle 7a292bc4c6 Slate compile fix 2026-08-25 21:39:26 -04:00
Peter Boyle 9213f6e533 Improvements for mulktigrid. + Schur setup improvements 2026-08-25 21:37:12 -04:00
Peter Boyle 5685203b7b Frontier jobs 2026-08-25 19:18:16 -04:00
Peter Boyle 22dda4a8ed Update 2026-08-25 19:17:59 -04:00
Peter Boyle eb22145f49 Updated slate 2026-08-25 17:27:37 -04:00
Peter Boyle 5d92f56bed omp issues combined with hipcc 2026-08-25 15:45:49 -04:00
Peter Boyle d42d1855c2 SLATE build updates 2026-08-25 15:33:07 -04:00
Peter Boyle 0a81a41729 Slate changes and warn suppression 2026-08-25 15:29:21 -04:00
Peter Boyle 229f4808c7 Ring all reduce first cut 2026-08-25 12:53:24 -04:00
Peter Boyle 167753aa3f Adding ring all reduce for large vectors 2026-08-25 12:07:11 -04:00
Peter Boyle dbbd5dba35 Potentially faster GCR linalg 2026-08-25 07:29:02 -04:00
Peter Boyle 33757c3c94 FOrmat warn avoidance 2026-08-24 20:32:54 -04:00
Peter Boyle 7438e56f94 More controls 2026-08-24 19:42:38 -04:00
Peter Boyle 47416230b1 Verbose on finalize improvement 2026-08-24 19:42:38 -04:00
Peter Boyle 6a854ebd0d Print trace for teh GCR step 2026-08-24 19:42:38 -04:00
Peter Boyle 8c6bcddbab Coefficent of GCR printing for poly recording 2026-08-24 19:40:27 -04:00
Peter Boyle effa31e761 Drop redundant set zero's and optimise persistent history vectors for smoothers 2026-08-24 15:48:26 -04:00
Peter Boyle 7792d7a1a7 Verbose prettier 2026-08-24 11:00:16 -04:00
Peter Boyle 8261dc39d9 More tracing 2026-08-24 10:56:16 -04:00
Peter Boyle 9ed7049736 COMpile fixes 2026-08-23 22:58:06 -04:00
Peter Boyle 5a70066e5b Claude build fixeds 2026-08-23 21:16:56 -04:00
Peter Boyle 6be8b478c3 Claude 2D block cyclic inverse work 2026-08-23 20:42:21 -04:00
Peter Boyle 34a1220035 Claude 2D block cyclic inverse 2026-08-23 20:41:46 -04:00
Peter Boyle c97cfe9f5b This fails 2026-08-23 12:14:58 -04:00
Peter Boyle 01858754ed Unhappy test looking for work aroudn to avoid allgather 2026-08-23 12:14:23 -04:00
Peter Boyle c388dd98d6 More verbose phasing on finish 2026-08-23 11:00:43 -04:00
Peter Boyle 38344e035f Don't print Finalize until made collective 2026-08-23 10:56:36 -04:00
Peter Boyle 14d2f2c06e Improvements 2026-08-23 02:38:23 -04:00
Peter Boyle 06fa2fc160 use shm buffers 2026-08-22 17:18:12 -04:00
Peter Boyle a9c22d787a Benchmark comms with the exact sequence in Stencil as it is now. 2026-08-22 17:14:10 -04:00
Peter Boyle 1e287b6b30 Changes to dense inverse AND improved timing on comms 2026-08-22 11:43:13 -04:00
Peter Boyle 152e9ecf3b Bandwidth measures inside stencil 2026-08-22 10:51:44 -04:00
Peter Boyle 2e0c19c915 Allgather test 2026-08-21 21:25:18 -04:00
Peter Boyle 4d6931620b Gather based distributed dense inverse 2026-08-21 12:34:01 -04:00
Peter Boyle d62fc91996 Better ROCTX tracing 2026-08-20 11:46:33 -04:00
Peter Boyle 3ed05dbbb1 Verbose trasing 2026-08-20 11:46:33 -04:00
Peter Boyle 97c9178785 Ready for faster 3 level solve with dense coarse and BLAS on SRHS !
Exciting
2026-08-20 11:44:26 -04:00
Peter Boyle 2fde4f3e50 Trace hooks 2026-08-20 07:38:28 -04:00
Peter Boyle b31547c903 no compile fix 2026-08-19 23:23:18 -04:00
Peter Boyle 2d56323012 No compile on Frontier 2026-08-19 23:06:38 -04:00
Peter Boyle 63fefb8ea5 new tests for lex lattice 2026-08-19 20:33:10 -04:00
Peter Boyle e4ec5d0009 FOr pvdagm preconditioners if they work 2026-08-19 20:17:56 -04:00
Peter Boyle a5e4ce3e48 Better comment 2026-08-19 20:15:21 -04:00
Peter Boyle 2900ce33b5 New files, including v2 multiRHS coarse op 2026-08-19 20:05:48 -04:00
Peter Boyle ea5bf89955 Failed experiment in the style of Saul Cohens non-red black CG approach to mgrid. 2026-08-19 20:04:29 -04:00
Peter Boyle 109b77ea75 Drop D2 field types 2026-08-19 20:03:40 -04:00
Peter Boyle 333315569c Benchmark lex lattices -- what is the benefit of Grid's SIMDization layout 2026-08-19 20:02:58 -04:00
Peter Boyle 995e149256 Scalar "vector" type to provide standard lexicographic local lattice layouts 2026-08-19 20:02:01 -04:00
Peter Boyle 971b0f3546 Deprecate doubled vector as much as possible.
Arm complex instructions on M3/M4 NEON v8.3 and simplify A64FX code paths/broaden.
Grid_vector_types and Simd.h mainly reorg and prep for sComplexD and sComplexF alternat Nsimd=1 types
2026-08-19 20:00:26 -04:00
Peter Boyle ab9603b28d Support for lexLattice in coarse space and eliminating layout changes in
multigrid
2026-08-19 19:58:35 -04:00
Peter Boyle adfd5853cc lex layout arrays support in fermion instantiations 2026-08-19 19:57:26 -04:00
Peter Boyle cc54c90ddc sComplex simd, also commit coarse space as lex lattice tests 2026-08-19 19:54:18 -04:00
Peter Boyle 6a4071cdc2 New lex lattice subdirs 2026-08-19 19:52:33 -04:00
Peter Boyle e92be14df8 Recurse to lexLattice 2026-08-19 19:50:56 -04:00
Peter Boyle e9be1a91b3 Speed up on mac arm with --enable-simd=NEONv8.3 2026-08-19 19:49:59 -04:00
Peter Boyle 7db3b96d2d Deprecate D2 fields 2026-08-19 19:49:26 -04:00
Peter Boyle 429ad2a71a sComplex and lexLattice prep work 2026-08-19 19:42:56 -04:00
Peter Boyle 0b1598e9ef Annoying warn on printf fix 2026-08-19 19:41:43 -04:00
Peter Boyle 8d04b91932 Clean up whitespace. Gauge group needs a Gimpl or some other policy parameter.
Perhaps rename iGaugeGroupd and pass EITHER vComplexD, vComplexF OR sCompexD / sComplexF
Use a typedef to restore the original Gauge group
2026-08-19 19:39:42 -04:00
Peter Boyle 665094d94b Lex gauge actions. 2026-08-19 19:38:48 -04:00
Peter Boyle 919d126af1 Remove D2 lattice fields; remove WilsonCompressor based half precision project; add sComplex lexLattice layouts
Fix staggered instantiations for new types
2026-08-19 19:37:29 -04:00
Peter Boyle abd5df1729 Discourage use of D2 lattices -- it was low performance.
Introduce scalar lexLattice layout
2026-08-19 19:35:59 -04:00
Peter Boyle 3243ad91e8 Fix race condition during read after write due to missing barrier.
Enable both lexLattice (scalar order) and Lattice (vector or partial SOA order)
2026-08-19 19:34:54 -04:00
Peter Boyle c10d451f4d Enable interafaces with both coarse and fine being either single higher dim multiRHS lattice OR vector of lower dim fields 2026-08-19 19:33:55 -04:00
Peter Boyle 9c4a61850e Features for better BLAS based coarse grids, for both single and multi RHS.
Use sComplex and lexLattice in future to avoid layout changes and copies
2026-08-19 19:32:36 -04:00
Peter Boyle 5e3fe24412 Recursive Schur complement based distributed matrix inverse by Claude after discussing plan and instructions from me 2026-08-19 19:31:46 -04:00
Peter Boyle 7b81ca6b52 Changes to make Nsimd=1 sComplex lexLattices functional for "where" statements 2026-08-19 19:30:34 -04:00
Peter Boyle 8c9ea4cde2 Json "" warning remove 2026-08-19 19:29:27 -04:00
Peter Boyle 0ac72cb6a3 Don't use host as it sucks. 2026-08-15 12:24:49 -04:00
Peter Boyle c4b089cf15 More timing updates 2026-08-15 09:51:56 -04:00
Peter Boyle 85e1bbf4bc Added gather benchmark to cross refer to the 'lazy' analogue of summing a vector with many zeroes in regions not owned by THIS rank 2026-08-15 09:51:12 -04:00
Peter Boyle 032f111c8b Instrument as not faster 2026-08-15 07:42:18 -04:00
Peter Boyle e38528888a Submission scripts for distributed schur coarse grid inverse 2026-08-14 18:28:46 -04:00
Peter Boyle 7c9a6d354e Updates for thrust 2026-08-14 18:21:45 -04:00
Peter Boyle 3ec90803ac no compil patches 2026-08-14 18:21:13 -04:00
Peter Boyle c22473f15d First test of distributed schur recursive inverse 2026-08-14 17:53:35 -04:00
Peter Boyle 55c064de28 Batch blas test extension 2026-08-14 17:53:35 -04:00
Peter Boyle 06ce057920 Bug fix for CPU build 2026-08-14 17:53:35 -04:00
Peter Boyle 76b4bd6d12 Distributed inverse support 2026-08-14 17:53:35 -04:00
Peter Boyle b5541aab55 Schur Inverse support 2026-08-14 17:53:35 -04:00
Peter Boyle a7160ac513 Support for distributed Schur inverse 2026-08-14 17:53:35 -04:00
Peter Boyle 02d0301c9f Scaling perf 2026-08-13 23:46:33 -04:00
Peter Boyle a6cdf20c18 IO tests 2026-08-13 20:08:56 -04:00
Peter Boyle ad9a413892 Update 2026-08-13 20:08:25 -04:00
Peter Boyle 1fddd2c29b Updates to IO regression example for MPICH issue 2026-08-13 18:54:27 -04:00
Peter Boyle 2f75067569 Some I/O simple benchmarks 2026-08-13 18:54:26 -04:00
Peter Boyle 6e8a00f215 Simplest starting point 2026-08-13 16:57:10 -04:00
Peter Boyle 702773e5fb BatchedBlas.h updates. May cause some churn. Trying to avoid scalar pointer copies to device. 2026-08-13 14:55:03 -04:00
Peter Boyle 4dfbd850ff Buf fix on DenseCoarseMatrix.h, coefficient caching optimisation on BatchGEMM 2026-08-13 13:26:42 -04:00
Peter Boyle b039e659af 4D pseudo fermion 2026-08-12 12:51:55 -04:00
Peter Boyle d16d44dda0 Prepare for reduced Ls during evolution forces using 4D pseudo fermion.
Worth an effort to understand if the different stochastic estimator is cleaner.
2026-08-12 12:51:01 -04:00
Peter Boyle 1c19389ba6 Two flavour boson term 2026-08-12 12:50:32 -04:00
Peter Boyle 02fdff674c Test the PVdagM left prec pseudofermion formulation 2026-08-12 12:49:59 -04:00
Peter Boyle fd8b6a23a6 PvDagM or other left prec precon two flavour ratio 2026-08-12 12:49:21 -04:00
Peter Boyle 9e3a51d078 Use the new DenseCoarseInverse class 2026-08-12 12:48:19 -04:00
Peter Boyle 6f7a2ad7c7 Dense coarse inverse wrapper class, bundling up a bunch of useful work 2026-08-12 12:47:40 -04:00
Peter Boyle 499d656949 Batch blas test 2026-08-12 12:47:16 -04:00
Peter Boyle ba68f09026 Batch inverse 2026-08-12 12:46:56 -04:00
Peter Boyle 3bdeeb73ef Update 2026-08-12 12:44:56 -04:00
Peter Boyle 19868a800f Blas optimise change 2026-08-12 12:43:40 -04:00
Peter Boyle df908ee872 3 level dense 2026-08-11 17:50:12 -04:00
Peter Boyle 84715ff4b9 Default to fast path 2026-08-11 17:39:12 -04:00
Peter Boyle 5792195073 Check in codes used at time of Lattice conference for PVdagM multigrid 2026-08-11 16:35:42 -04:00
Peter Boyle fb5662a449 Setup on Frontier 2026-08-11 16:35:42 -04:00
Peter Boyle 6b2ad3db80 Inverse improvements. Need to make this properly recursive 2026-08-11 16:35:42 -04:00
Peter Boyle f1a969f0c3 Evict all optoin 2026-08-11 16:35:42 -04:00
Peter Boyle f18320a152 GeneralCoarsenedMatrix speed up -- same optimisations for fast
calculation used by GeneralCoarsenedMatrixMultiRHS
2026-08-11 16:35:42 -04:00
Peter Boyle bfcde59199 Memory manager improvements 2026-08-11 16:35:42 -04:00
Peter Boyle f23e9e70cd Global orthogonalise routine 2026-08-11 16:35:42 -04:00
Peter Boyle 06fcd31da0 Verbose options 2026-08-11 16:35:42 -04:00
Peter Boyle 7e4fe99b1e Logging update 2026-08-11 16:35:42 -04:00
Peter Boyle 159ea3d64d Better logging performance 2026-08-11 16:35:42 -04:00
Peter Boyle 1f55c13bc4 Test script for Frontier on I/O 2026-08-11 13:37:50 -04:00
Peter Boyle d68b111d06 Switch on the faster IO be default 2026-08-11 13:36:50 -04:00
Peter Boyle 41f5a02204 IO updates for AllToAllV aggregation of contiguous chunks.
May finally clean up the poor MPI2 IO performance issue that has been persistent.
2026-08-11 13:33:04 -04:00
Peter Boyle 7647576863 Sourceme's for frontier 2026-06-26 11:50:44 -04:00
Peter Boyle f11ba18df2 Remove accelerator_inline on CPU only code 2026-06-17 20:47:15 +02:00
Peter Boyle cf8587e401 Update booster compiule 2026-06-17 20:46:53 +02:00
Peter Boyle 7dd35ef749 Make disable accelerator aware mpi compile for CUDA 2026-06-17 20:46:14 +02:00
Peter Boyle 41e570ddce Annoying old CPU perfmon code should be removed or deprecated as not
worth maintaining
2026-06-17 20:45:32 +02:00
Peter Boyle a452131b50 Print improvement 2026-06-17 20:45:04 +02:00
Peter Boyle 4e49ca55ab Merge branch 'develop' of https://github.com/paboyle/Grid into develop 2026-06-16 11:20:12 -07:00
Peter Boyle c3f4474401 Adding Mattia's memory leak test 2026-06-16 11:19:36 -07:00
Peter Boyle 3d3eff86f3 Modify move assignment operator to be noexcept
Add noexcept specifier to move assignment operator.
2026-06-11 09:44:24 -04:00
Peter Boyle fc9f154ac1 Modify Lattice move constructor to be noexcept
Add noexcept specifier to move constructor for Lattice class.
2026-06-11 09:40:06 -04:00
Peter Boyle 4aa0bca4dc Change sum operation to use gpucub mistake in PR from Chris
Updated the sum operation definition for GPU reduction to use gpucub instead of cub.
2026-06-01 14:12:25 -04:00
Peter Boyle 905da6f083 Merge branch 'feature/reduction-reorganisation' into develop 2026-05-27 21:01:30 -04:00
Peter Boyle 86c7f29183 Config command update 2026-05-27 16:19:33 -04:00
Peter Boyle b0c99f876e Configure on mac update 2026-05-27 16:16:55 -04:00
Peter Boyle bf5fcdc860 Ease of use for std::complex interchangable with thrust 2026-05-27 16:05:37 -04:00
Peter Boyle b58a1508fa Perlmutter cuda version update 2026-05-21 13:25:13 -07:00
Peter Boyle 4d527e81fa Remove hip specific files 2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 7803580aa6 Lattice_reduction_gpu: demote timing logs to Debug, disable by default
skills/mpi-heterogeneous: add Bug Class 4 for Frontier GTL/libamdhip64 ABI mismatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 32654db366 Test_planned_fft: fix PlannedFFT template parameter to use ::vector_object
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 cd340cfab3 tests: add Test_planned_fft exercising PlannedFFT<vobj>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 f32866b2ff tests/fft: remove PlanDestroy calls (FFT handles plans per-call)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 1cd1dc091e FFT: add FFTbase, PlannedFFT; factor FFT_dim_execute free function
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 0493656e86 debug: add Test_hipfft_repro — reproducer for hipFFT PARSE_ERROR on ROCm 7
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 66fd504c4d tests/debug: add G=4 to hipfft fail reproducer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 be4dd2b52f tests/debug: test hipMemset variant before cache is populated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 707d059766 tests/debug: extend hipfft fail reproducer with hipMemset and sync variants
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 f08c755ae6 FFT: use host stack buffer in PlanCreate, not deviceVector
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 dbbfdd4e4b tests/debug: add minimal hipfft ordering bug fail/pass pair
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 f967fb40bf tests/debug: test plan-before-malloc vs malloc-before-plan ordering
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 74e0f846cb tests/debug: extend hipfft reproducer with Grid-realistic howmany and exec tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 303a4d26e5 tests/debug: add minimal hipfft plan-creation reproducer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter Boyle 119888653c FFT HIP: use hipfftCreate+hipfftMakePlanMany instead of hipfftPlanMany 2026-05-21 12:34:30 -04:00
Peter Boyle a9f42c08f9 FFT: pass nullptr for inembed/onembed in hipfftPlanMany to avoid HIPFFT_PARSE_ERROR 2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 e79adc9d31 FFT: cache plans per vobj type across calls
Plans are created lazily on the first FFT_dim call and reused for all
subsequent calls on the same FFT object.  PlanCreate<vobj>() can be
called explicitly to pre-warm the cache.  PlanDestroy() must be called
before switching to a different vobj type; the destructor cleans up any
live plans automatically.

Update Test_fft.cc and Test_fftf.cc to call PlanDestroy() between the
LatticeComplex and LatticeSpinMatrix sections that reuse the same FFT object.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 5a9056cd93 Accelerator: lower default accelerator_threads from 16 to 8
Benchmark_dwf_fp32 on MI250X GCD: 1.7 TF/s at nt=8, ~300 GF/s at nt=16.
With Nsimd=8 (fp32, GEN_SIMD_WIDTH=64B), nt=8 gives exactly 64 threads =
one full AMD wavefront. Higher values double register demand per block and
hit a register-pressure cliff for stencil kernels.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter Boyle 012c36ab5a Accelerator: raise default accelerator_threads from 2 to 16 2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 5c4574f9aa skills: add gpu-memory-performance.md
Documents the acceleratorThreads() default=2 trap, LambdaApply thread
mapping, coalescedRead/Write idiom, when to use __global__ vs
accelerator_for, and fused vs staged HBM access patterns.

Includes observed MI250X numbers from LatticePropagatorD reduction
(50 → 297 → 546 GB/s progression).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 a424775884 sumD_gpu_reduce_words: fuse pack+reduce into single packReduceKernel
Replace the two-kernel pack+reduce sequence with a single fused kernel
packReduceKernel<R> that reads R words of each vobj at offset 'base'
and accumulates directly into iVector<iScalar<scalarD>,R>, eliminating
the intermediate bundle buffer entirely.

HBM access per word-group drops from 3x (pack-read + pack-write +
reduce-read) to 1x.  Thread count comes from getNumBlocksAndThreads
(warpSize..256) rather than acceleratorThreads(), so occupancy is
correct regardless of the --accelerator-threads setting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter Boyle d6b1388741 Modified repack 2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 796c6cae4e Enable GRID_REDUCTION_TIMING unconditionally
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 1a8064d6d9 Lattice_reduction_gpu: add GRID_REDUCTION_TIMING instrumentation
Uncomment #define GRID_REDUCTION_TIMING to enable per-phase timing output:

  sumD_gpu_reduce_words: pack time (accelerator_for) per R and base
  sumD_gpu_small:        reduceKernel+barrier time and D2H time separately
  sumD_gpu_large:        total wall time across all word groups

This lets us identify whether the large-type bottleneck is in the pack
kernel, the shared-memory reduction kernel, the barrier, or the D2H.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 43648924c3 sumD_gpu_large: radix-12 word-bundle reduction replacing radix-1
Replace the word-by-word loop (one kernel launch per scalar word) with
sumD_gpu_reduce_words<R> which packs R consecutive vector_type words per
site into iVector<iScalar<vector>,R>, then calls the existing sumD_gpu_small
shared-memory kernel once for the whole bundle.

Dispatch: radix-12 first, radix-4 for the remainder < 12, radix-1 for
any final < 4 words.  For LatticePropagator (144 words = 12x12), this
reduces the kernel-launch count from 144 to 12 -- a 12x reduction.

Bundle::Nsimd() inherits from vector_type so sumD_gpu_small handles SIMD
lane extraction and double-precision promotion identically to the scalar
word case.  sizeof(Bundle::scalar_objectD) = R*16 <= 192 B; well within
sharedMemPerBlock on all supported devices.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 bf2140e74d Lattice_reduction_sycl: fix double-precision accumulation in sumD_gpu_tensor
Accumulate in sobjD throughout rather than accumulating in sobj and
converting the final sum. For float fields this matters: summing N floats
then casting loses O(N*eps_float) relative precision vs accumulating in
double from the start.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 a1119266c1 Revert to hand-rolled reduction; drop Lattice_reduction_gpu_cub.h
Remove the CUB/hipCUB direction entirely. Restore Lattice_reduction_gpu.h,
Lattice_reduction_sycl.h, and Lattice_reduction.h to the state before the
CUB rewrite (commit 969b0a39), recovering the original primary function names
(sumD_gpu_small, sumD_gpu_large, sumD_gpu, sum_gpu, sum_gpu_large) and the
hand-rolled shared-memory reduction kernel.

Delete Lattice_reduction_gpu_cub.h. Update Test_reduction to remove the
old/new comparison sections that depended on sum_gpu_old.

The lesson: CUB DeviceReduce is slower than the hand-rolled kernel for small
types, and the smem sizing problem for the extraction pass has no clean
solution within the accelerator_for abstraction. The right improvement is
a higher radix (12 then 4) in sumD_gpu_large, applied directly to the
existing hand-rolled kernel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 a0f00c0eca sumD_gpu_direct: revert to per-lane write; CUB handles Nsimd*osites inputs
Benchmarking showed the shared-memory lane-summation approach (843d6497)
was slower than writing each SIMD lane individually and letting CUB reduce
the full nlanes = osites*Nsimd array. CUB's device reduce is more efficient
over the larger input than the smem overhead + serialised lane-0 summation.
The smem approach also required overriding acceleratorThreads() to avoid
the block-size sizing problem. Restore the simpler per-lane path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 d358954a84 sumD_gpu_direct: shared-memory lane reduction with acceleratorThreads(1)
Set acceleratorThreads to 1 before the extraction kernel so that
dim3(nsimd,1,1) blocks give exactly one site group per block and
__shared__ sobjD smem[nsimd] is correctly sized without depending on
the runtime acceleratorThreads() value. threadIdx.x (acceleratorSIMTlane)
indexes the SIMD lane for coalesced reads; lane 0 sums smem[0..nsimd-1]
and writes one sobjD per site. CUB then reduces osites elements instead
of osites*nsimd, reducing both store traffic and CUB work by Nsimd.
acceleratorSynchronise() (warp-level) suffices since nsimd < warpSize.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 aee00bdfb5 sumD_gpu_direct: one thread per SIMD lane using extractLane
Replaces one thread per outer site calling Reduce() (sequential Nsimd-wide
loop) with one thread per lane calling extractLane() — O(1) per thread.
CUB now reduces over osites*Nsimd elements. Avoids serial lane reduction
but leaves the per-lane sobjD store stride as a known remaining concern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 cf324b0fa1 Lattice_reduction_gpu_cub: define GRID_REDUCTION_TIMING in header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 b314dc224d Lattice_reduction_gpu_cub: add GRID_REDUCTION_TIMING instrumentation
Guards accelerator_for and CUB DeviceReduce calls in sumD_gpu_direct
and sumD_gpu_large with #ifdef GRID_REDUCTION_TIMING to isolate where
time is spent in each path. Large path accumulates across all groups
and prints totals with words/nfull/rem context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 1bbd62498e Lattice_reduction_gpu_cub: replace WordBundle4 with iVector<iScalar<scalarD>,4>
WordBundle4 was redundant with Grid's existing tensor infrastructure.
iVector<iScalar<scalarD>,4> already provides accelerator_inline operator+,
zeroit(), and sycl::is_device_copyable — no new type needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 f3c3b1c04b Test_reduction: add timing benchmark for new vs old reduction paths
Reports us/call and GB/s for sum_gpu (CUB/sycl::reduction) and
sum_gpu_old (hand-rolled shared-memory) for each field type, with
5-call warmup and 100-call timed loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 069f98b253 skills: HPC battle-hardening skill files for GPU+MPI correctness
Six skill files encoding expertise for making codebases robust on
problematic HPC systems, covering: correctness verification
(double-run, fingerprinting, flight recorder), hang diagnosis,
GPU runtime correctness (premature barrier, infinite poll),
MPI correctness on heterogeneous systems (device buffer aliasing,
AARCH64 PLT corruption, deterministic reductions),
compiler validation, and communication/computation overlap pipeline
design.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 dfd0503eae Test_reduction: use separate float and double grids
Float fields require a grid constructed with vComplexF::Nsimd(); using
a double grid causes grid->_gsites to undercount the sites in float
vobjF, making the constant-field expected value wrong.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 c629b2e87e Rename scalarNorm2 to squaredSum in Test_reduction.cc
The function computes |sum|^2 — the squared magnitude of an aggregate sum —
not a norm. squaredSum makes clear that squaring is applied to the sum, not
to individual site values before summing, distinguishing it from sumOfSquares
(the squared L2 norm).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 7c8462abd1 Fix Zero() used on thrust::complex in WordBundle4 initialisation
Grid's Zero() sentinel is not assignable to thrust::complex<double>;
use scalarD(0) instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 95a6a0bde7 Reinstate large/small dispatch in CUB reduction path; radix-4 word-bundle for large types
rocPRIM's DeviceReduce requires warpSize(64) threads each holding one element in shared
memory, so sizeof(T)*64 must fit in sharedMemPerBlock.  LatticePropagator::scalar_objectD
is 2304 bytes (64*2304 = 147 KB), exceeding the budget and triggering a compile-time
static_assert in limit_block_size.

Introduce sumD_gpu_direct (the original direct-CUB path, safe for small types) and a new
sumD_gpu_large that groups the vobj's vector_type words in bundles of 4, reducing each
bundle as WordBundle4<scalarD> (64 bytes, 64*64 = 4 KB — always within budget).  If
words % 4 != 0, the final partial bundle is zero-padded.  sumD_gpu dispatches at compile
time via if constexpr on sizeof(sobjD) > 512.

For LatticePropagator (144 words) this gives 36 CUB launches instead of 144.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 bba328fac5 Add Test_reduction to tests/debug
Tests the new CUB/hipCUB/SYCL lattice reduction (sum_gpu) against the
preserved hand-rolled implementation (sum_gpu_old) for LatticeComplexF/D,
LatticeColourMatrixF/D and LatticePropagatorF/D.

Part a) gaussian random field: checks that old and new agree to within
float/double roundoff tolerance.
Part b) constant field (= 1.0, identity-matrix init): verifies
innerProduct(sum, sum) = Ncomp * V^2 where Ncomp counts the nonzero
diagonal scalar components per site (1 / Nc / Ns*Nc respectively).

Make.inc is auto-generated by scripts/filelist on bootstrap and is not
tracked; the new .cc file is all that is needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter BoyleandClaude Sonnet 4.6 41362349f3 Rewrite lattice GPU reduction to use CUB, hipCUB, and SYCL reduction
Replace hand-rolled shared-memory reduction kernels (reduceBlock/reduceBlocks/
reduceKernel) and the global device variable retirementCount with a unified
CUB/hipCUB DeviceReduce::Reduce path for CUDA/HIP and sycl::reduction for SYCL.
No small/large split is needed: both CUB and sycl::reduction handle arbitrary
object sizes internally.

Old implementations preserved as sum_gpu_old / sumD_gpu_old etc. in the
original files for regression testing on GPU hardware.

Also add CLAUDE.md with build, test, and architecture guidance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:34:30 -04:00
Peter Boyle 12e3499b6d Updated rocm 7 compile for ORNL 2026-05-21 12:28:42 -04:00
Peter Boyle 9576011011 Changed setup for ROCM 7, nasty LD_LIBRARY_PATH issues were committing
evils
2026-05-21 12:28:04 -04:00
Peter Boyle 155b34c1aa File list lost 2026-05-21 12:06:01 -04:00
Peter BoyleandClaude Sonnet 4.6 982ffe9ebe Lattice_reduction_gpu: demote timing logs to Debug, disable by default
skills/mpi-heterogeneous: add Bug Class 4 for Frontier GTL/libamdhip64 ABI mismatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 12:05:36 -04:00
Peter BoyleandClaude Sonnet 4.6 0251ecaeab Test_planned_fft: fix PlannedFFT template parameter to use ::vector_object
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 18:13:38 -04:00
Peter BoyleandClaude Sonnet 4.6 372a27d645 tests: add Test_planned_fft exercising PlannedFFT<vobj>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:59:24 -04:00
Peter BoyleandClaude Sonnet 4.6 72b4a061f3 tests/fft: remove PlanDestroy calls (FFT handles plans per-call)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:54:41 -04:00
Peter BoyleandClaude Sonnet 4.6 29198efabe FFT: add FFTbase, PlannedFFT; factor FFT_dim_execute free function
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:53:17 -04:00
Peter BoyleandClaude Sonnet 4.6 50aa51f93a debug: add Test_hipfft_repro — reproducer for hipFFT PARSE_ERROR on ROCm 7
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:27:27 -04:00
Peter BoyleandClaude Sonnet 4.6 79ccc81a86 tests/debug: add G=4 to hipfft fail reproducer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:21:52 -04:00
Peter BoyleandClaude Sonnet 4.6 3f0fdbb597 tests/debug: test hipMemset variant before cache is populated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:10:16 -04:00
Peter BoyleandClaude Sonnet 4.6 ea57bd8f03 tests/debug: extend hipfft fail reproducer with hipMemset and sync variants
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:02:02 -04:00
Peter BoyleandClaude Sonnet 4.6 bdba5b8403 FFT: use host stack buffer in PlanCreate, not deviceVector
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:49:06 -04:00
Peter BoyleandClaude Sonnet 4.6 58cc6ca9c0 tests/debug: add minimal hipfft ordering bug fail/pass pair
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:48:23 -04:00
Peter BoyleandClaude Sonnet 4.6 e5996b440d tests/debug: test plan-before-malloc vs malloc-before-plan ordering
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:40:17 -04:00
Peter BoyleandClaude Sonnet 4.6 ad9d03fd85 tests/debug: extend hipfft reproducer with Grid-realistic howmany and exec tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 19:19:59 -04:00
Peter BoyleandClaude Sonnet 4.6 4de160ce20 tests/debug: add minimal hipfft plan-creation reproducer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 17:52:59 -04:00
Peter Boyle fc8c8ce6e7 FFT HIP: use hipfftCreate+hipfftMakePlanMany instead of hipfftPlanMany 2026-05-19 17:29:28 -04:00
Peter Boyle ddbb7f07c8 FFT: pass nullptr for inembed/onembed in hipfftPlanMany to avoid HIPFFT_PARSE_ERROR 2026-05-19 17:15:21 -04:00
Peter Boyle a5a04929fb Merge pull request #492 from giltirn/develop
Fixes to support CUDA > 13
2026-05-19 15:26:58 -04:00
Peter BoyleandClaude Sonnet 4.6 1e29c59bcc FFT: cache plans per vobj type across calls
Plans are created lazily on the first FFT_dim call and reused for all
subsequent calls on the same FFT object.  PlanCreate<vobj>() can be
called explicitly to pre-warm the cache.  PlanDestroy() must be called
before switching to a different vobj type; the destructor cleans up any
live plans automatically.

Update Test_fft.cc and Test_fftf.cc to call PlanDestroy() between the
LatticeComplex and LatticeSpinMatrix sections that reuse the same FFT object.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 15:12:10 -04:00
Peter BoyleandClaude Sonnet 4.6 b6abdc3845 Accelerator: lower default accelerator_threads from 16 to 8
Benchmark_dwf_fp32 on MI250X GCD: 1.7 TF/s at nt=8, ~300 GF/s at nt=16.
With Nsimd=8 (fp32, GEN_SIMD_WIDTH=64B), nt=8 gives exactly 64 threads =
one full AMD wavefront. Higher values double register demand per block and
hit a register-pressure cliff for stencil kernels.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 13:41:03 -04:00
Christopher Kelly 77b8657fcc Fixes to support CUDA > 13. Specifically, the CUDA header is no longer accidentally included within Grid's namespace, and the breaking change to cub::Sum() -> ::cuda::std::plus<>{} in CUDA-13 has been worked around 2026-05-19 12:22:14 -04:00
Peter Boyle 2fadd8bb62 Accelerator: raise default accelerator_threads from 2 to 16 2026-05-19 10:15:53 -04:00
Peter BoyleandClaude Sonnet 4.6 60df2dd5d0 skills: add gpu-memory-performance.md
Documents the acceleratorThreads() default=2 trap, LambdaApply thread
mapping, coalescedRead/Write idiom, when to use __global__ vs
accelerator_for, and fused vs staged HBM access patterns.

Includes observed MI250X numbers from LatticePropagatorD reduction
(50 → 297 → 546 GB/s progression).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 10:03:32 -04:00
Peter BoyleandClaude Sonnet 4.6 66b529b345 sumD_gpu_reduce_words: fuse pack+reduce into single packReduceKernel
Replace the two-kernel pack+reduce sequence with a single fused kernel
packReduceKernel<R> that reads R words of each vobj at offset 'base'
and accumulates directly into iVector<iScalar<scalarD>,R>, eliminating
the intermediate bundle buffer entirely.

HBM access per word-group drops from 3x (pack-read + pack-write +
reduce-read) to 1x.  Thread count comes from getNumBlocksAndThreads
(warpSize..256) rather than acceleratorThreads(), so occupancy is
correct regardless of the --accelerator-threads setting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 09:46:43 -04:00
Peter Boyle 1304172a93 Modified repack 2026-05-19 08:53:13 -04:00
Peter BoyleandClaude Sonnet 4.6 1315d4604d Enable GRID_REDUCTION_TIMING unconditionally
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:14:00 -04:00
Peter BoyleandClaude Sonnet 4.6 a31af31328 Lattice_reduction_gpu: add GRID_REDUCTION_TIMING instrumentation
Uncomment #define GRID_REDUCTION_TIMING to enable per-phase timing output:

  sumD_gpu_reduce_words: pack time (accelerator_for) per R and base
  sumD_gpu_small:        reduceKernel+barrier time and D2H time separately
  sumD_gpu_large:        total wall time across all word groups

This lets us identify whether the large-type bottleneck is in the pack
kernel, the shared-memory reduction kernel, the barrier, or the D2H.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:13:30 -04:00
Peter BoyleandClaude Sonnet 4.6 26c3c7d8f9 sumD_gpu_large: radix-12 word-bundle reduction replacing radix-1
Replace the word-by-word loop (one kernel launch per scalar word) with
sumD_gpu_reduce_words<R> which packs R consecutive vector_type words per
site into iVector<iScalar<vector>,R>, then calls the existing sumD_gpu_small
shared-memory kernel once for the whole bundle.

Dispatch: radix-12 first, radix-4 for the remainder < 12, radix-1 for
any final < 4 words.  For LatticePropagator (144 words = 12x12), this
reduces the kernel-launch count from 144 to 12 -- a 12x reduction.

Bundle::Nsimd() inherits from vector_type so sumD_gpu_small handles SIMD
lane extraction and double-precision promotion identically to the scalar
word case.  sizeof(Bundle::scalar_objectD) = R*16 <= 192 B; well within
sharedMemPerBlock on all supported devices.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:56:45 -04:00
Peter BoyleandClaude Sonnet 4.6 0650d7c7eb Lattice_reduction_sycl: fix double-precision accumulation in sumD_gpu_tensor
Accumulate in sobjD throughout rather than accumulating in sobj and
converting the final sum. For float fields this matters: summing N floats
then casting loses O(N*eps_float) relative precision vs accumulating in
double from the start.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:53:40 -04:00
Peter BoyleandClaude Sonnet 4.6 068f95ad2d Revert to hand-rolled reduction; drop Lattice_reduction_gpu_cub.h
Remove the CUB/hipCUB direction entirely. Restore Lattice_reduction_gpu.h,
Lattice_reduction_sycl.h, and Lattice_reduction.h to the state before the
CUB rewrite (commit 969b0a39), recovering the original primary function names
(sumD_gpu_small, sumD_gpu_large, sumD_gpu, sum_gpu, sum_gpu_large) and the
hand-rolled shared-memory reduction kernel.

Delete Lattice_reduction_gpu_cub.h. Update Test_reduction to remove the
old/new comparison sections that depended on sum_gpu_old.

The lesson: CUB DeviceReduce is slower than the hand-rolled kernel for small
types, and the smem sizing problem for the extraction pass has no clean
solution within the accelerator_for abstraction. The right improvement is
a higher radix (12 then 4) in sumD_gpu_large, applied directly to the
existing hand-rolled kernel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:52:18 -04:00
Peter BoyleandClaude Sonnet 4.6 f4fbf7c9ca sumD_gpu_direct: revert to per-lane write; CUB handles Nsimd*osites inputs
Benchmarking showed the shared-memory lane-summation approach (843d6497)
was slower than writing each SIMD lane individually and letting CUB reduce
the full nlanes = osites*Nsimd array. CUB's device reduce is more efficient
over the larger input than the smem overhead + serialised lane-0 summation.
The smem approach also required overriding acceleratorThreads() to avoid
the block-size sizing problem. Restore the simpler per-lane path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:23:15 -04:00
Peter BoyleandClaude Sonnet 4.6 843d6497b2 sumD_gpu_direct: shared-memory lane reduction with acceleratorThreads(1)
Set acceleratorThreads to 1 before the extraction kernel so that
dim3(nsimd,1,1) blocks give exactly one site group per block and
__shared__ sobjD smem[nsimd] is correctly sized without depending on
the runtime acceleratorThreads() value. threadIdx.x (acceleratorSIMTlane)
indexes the SIMD lane for coalesced reads; lane 0 sums smem[0..nsimd-1]
and writes one sobjD per site. CUB then reduces osites elements instead
of osites*nsimd, reducing both store traffic and CUB work by Nsimd.
acceleratorSynchronise() (warp-level) suffices since nsimd < warpSize.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:08:10 -04:00
Peter BoyleandClaude Sonnet 4.6 747c167658 sumD_gpu_direct: one thread per SIMD lane using extractLane
Replaces one thread per outer site calling Reduce() (sequential Nsimd-wide
loop) with one thread per lane calling extractLane() — O(1) per thread.
CUB now reduces over osites*Nsimd elements. Avoids serial lane reduction
but leaves the per-lane sobjD store stride as a known remaining concern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 16:21:50 -04:00
Peter BoyleandClaude Sonnet 4.6 fca2c5dba0 Lattice_reduction_gpu_cub: define GRID_REDUCTION_TIMING in header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 14:54:08 -04:00
Peter BoyleandClaude Sonnet 4.6 e12bc7f07c Lattice_reduction_gpu_cub: add GRID_REDUCTION_TIMING instrumentation
Guards accelerator_for and CUB DeviceReduce calls in sumD_gpu_direct
and sumD_gpu_large with #ifdef GRID_REDUCTION_TIMING to isolate where
time is spent in each path. Large path accumulates across all groups
and prints totals with words/nfull/rem context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 14:23:44 -04:00
Peter BoyleandClaude Sonnet 4.6 dc6ae51cab Lattice_reduction_gpu_cub: replace WordBundle4 with iVector<iScalar<scalarD>,4>
WordBundle4 was redundant with Grid's existing tensor infrastructure.
iVector<iScalar<scalarD>,4> already provides accelerator_inline operator+,
zeroit(), and sycl::is_device_copyable — no new type needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:55:28 -04:00
Peter BoyleandClaude Sonnet 4.6 baa70d8ec9 Test_reduction: add timing benchmark for new vs old reduction paths
Reports us/call and GB/s for sum_gpu (CUB/sycl::reduction) and
sum_gpu_old (hand-rolled shared-memory) for each field type, with
5-call warmup and 100-call timed loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:31:13 -04:00
Peter BoyleandClaude Sonnet 4.6 c93b338bdd skills: HPC battle-hardening skill files for GPU+MPI correctness
Six skill files encoding expertise for making codebases robust on
problematic HPC systems, covering: correctness verification
(double-run, fingerprinting, flight recorder), hang diagnosis,
GPU runtime correctness (premature barrier, infinite poll),
MPI correctness on heterogeneous systems (device buffer aliasing,
AARCH64 PLT corruption, deterministic reductions),
compiler validation, and communication/computation overlap pipeline
design.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:10:44 -04:00
Peter BoyleandClaude Sonnet 4.6 c0472aa0ec Test_reduction: use separate float and double grids
Float fields require a grid constructed with vComplexF::Nsimd(); using
a double grid causes grid->_gsites to undercount the sites in float
vobjF, making the constant-field expected value wrong.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:09:35 -04:00
Peter BoyleandClaude Sonnet 4.6 09552cfd73 Rename scalarNorm2 to squaredSum in Test_reduction.cc
The function computes |sum|^2 — the squared magnitude of an aggregate sum —
not a norm. squaredSum makes clear that squaring is applied to the sum, not
to individual site values before summing, distinguishing it from sumOfSquares
(the squared L2 norm).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 23:15:11 -04:00
Peter BoyleandClaude Sonnet 4.6 003fec509c Fix Zero() used on thrust::complex in WordBundle4 initialisation
Grid's Zero() sentinel is not assignable to thrust::complex<double>;
use scalarD(0) instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 18:10:17 -04:00
Peter BoyleandClaude Sonnet 4.6 773a82d87f Reinstate large/small dispatch in CUB reduction path; radix-4 word-bundle for large types
rocPRIM's DeviceReduce requires warpSize(64) threads each holding one element in shared
memory, so sizeof(T)*64 must fit in sharedMemPerBlock.  LatticePropagator::scalar_objectD
is 2304 bytes (64*2304 = 147 KB), exceeding the budget and triggering a compile-time
static_assert in limit_block_size.

Introduce sumD_gpu_direct (the original direct-CUB path, safe for small types) and a new
sumD_gpu_large that groups the vobj's vector_type words in bundles of 4, reducing each
bundle as WordBundle4<scalarD> (64 bytes, 64*64 = 4 KB — always within budget).  If
words % 4 != 0, the final partial bundle is zero-padded.  sumD_gpu dispatches at compile
time via if constexpr on sizeof(sobjD) > 512.

For LatticePropagator (144 words) this gives 36 CUB launches instead of 144.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 16:55:58 -04:00
Peter BoyleandClaude Sonnet 4.6 286c29d6fb Add Test_reduction to tests/debug
Tests the new CUB/hipCUB/SYCL lattice reduction (sum_gpu) against the
preserved hand-rolled implementation (sum_gpu_old) for LatticeComplexF/D,
LatticeColourMatrixF/D and LatticePropagatorF/D.

Part a) gaussian random field: checks that old and new agree to within
float/double roundoff tolerance.
Part b) constant field (= 1.0, identity-matrix init): verifies
innerProduct(sum, sum) = Ncomp * V^2 where Ncomp counts the nonzero
diagonal scalar components per site (1 / Nc / Ns*Nc respectively).

Make.inc is auto-generated by scripts/filelist on bootstrap and is not
tracked; the new .cc file is all that is needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 14:31:33 -04:00
Peter BoyleandClaude Sonnet 4.6 969b0a3922 Rewrite lattice GPU reduction to use CUB, hipCUB, and SYCL reduction
Replace hand-rolled shared-memory reduction kernels (reduceBlock/reduceBlocks/
reduceKernel) and the global device variable retirementCount with a unified
CUB/hipCUB DeviceReduce::Reduce path for CUDA/HIP and sycl::reduction for SYCL.
No small/large split is needed: both CUB and sycl::reduction handle arbitrary
object sizes internally.

Old implementations preserved as sum_gpu_old / sumD_gpu_old etc. in the
original files for regression testing on GPU hardware.

Also add CLAUDE.md with build, test, and architecture guidance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 13:41:56 -04:00
Peter Boyle f8b2eacf99 File list issue (Ed Bennets pull request?) 2026-05-15 12:57:42 -04:00
Peter Boyle 6140ac6864 Hip Happy 2026-05-15 12:13:01 -04:00
Peter Boyle c6c2834e03 Hip Happy 2026-05-15 11:30:29 -04:00
Peter Boyle 856545a1db Support ROCM 7.0.2 2026-05-15 11:30:29 -04:00
Peter Boyle e2d607f6c7 Merge pull request #490 from jdmaia/hip-guard-acceleratorfor2dNB
[HIP] Including kernel launch parameter guard on accelerator_for2dNB
2026-05-06 14:51:30 -04:00
Julio Maia 66da4e0657 Including guard on accelerator_for2dNB against invalid kernel configurations if GRID_HIP 2026-05-06 13:26:33 -05:00
Peter Boyle b37390bb5a 4 node usqcd run 2026-04-27 14:40:11 -07:00
Peter Boyle 829dc8cceb 32 node 2026-04-27 14:38:02 -07:00
Peter Boyle 13cc2c39f5 FOM run 2026-04-27 14:20:49 -07:00
Peter Boyle 66ea3b271c Merge branch 'develop' of https://github.com/paboyle/Grid into develop 2026-04-27 13:55:52 -07:00
Peter Boyle d293b58a20 384 node baseline run 2026-04-27 13:54:40 -07:00
Peter Boyle ce093b2bf3 rdtsc 2026-04-27 13:54:06 -07:00
Peter Boyle e4404efe5a Perlmutter compile update 2026-04-27 13:53:28 -07:00
Peter Boyle 5ce270f1de Adding Claude related files 2026-04-21 10:41:18 -04:00
Peter Boyle af43b067a0 New CLAUDE controllable visualiser 2026-04-10 11:23:25 -04:00
Quadro 34b44d1fee New file for animation in MD time direction 2026-04-02 13:55:38 -04:00
Peter Boyle 595ceaac37 Include grid header and make the ENABLE correct 2026-03-11 17:24:44 -04:00
Peter Boyle daf5834e8e Fixing incorrect PR about disable fermion instantiations 2026-03-11 17:05:46 -04:00
Peter Boyle 0d8658a039 Optimised 2026-03-05 06:06:32 -05:00
Peter Boyle 095e004d01 Setup change GCR 2026-03-05 06:06:32 -05:00
Peter Boyle 0acabee7f6 Modest change 2026-03-05 06:06:32 -05:00
Peter Boyle 76fbcffb60 Improvement to 16^3 hdcg 2026-03-05 06:06:32 -05:00
Peter Boyle a0a62d7ead Merge pull request #478 from vataspro/PolyakovUpstream
Spatial Polyakov Loop implementation
2026-02-24 20:45:42 -05:00
Peter Boyle c5038ea6a5 Merge pull request #483 from cmcknigh/bugfix/rocm7-rocblas-type-refactor
Adding a version check to handle rocBlas type refactor
2026-02-24 20:45:03 -05:00
Peter Boyle a5120903eb Merge pull request #486 from RChrHill/fix/sp4-fp32
Define Sp4 ProjectOnGeneralGroup for generic vtype
2026-02-24 20:44:08 -05:00
Peter Boyle 00b286a08a Merge pull request #488 from RChrHill/feature/additional-ET-traces
Add ET support for Lattice spin- and colour-traces
2026-02-24 20:43:45 -05:00
Peter Boyle 24a9759353 Merge pull request #485 from edbennett/skip-fermion-instantiations
Be able to skip compiling fermion instantiations altogether
2026-02-24 20:43:20 -05:00
edbennett 1b56f6f46d be able to skip compiling fermion instantiations altogether 2026-02-24 23:52:18 +00:00
Peter Boyle 2a8084d569 Subspace setup 2026-02-13 17:26:11 -05:00
Peter Boyle 6ff29f9d4f Alternate multigrids 2026-02-13 17:25:45 -05:00
RChHill c4d3e79193 Add ET support for Lattice spin- and colour-traces 2026-01-29 14:46:52 +00:00
Peter Boyle 7cd3f21e6b preserving a bunch of experiments on setup and g5 subspace doubling 2026-01-06 05:57:39 -05:00
paboyle 4a0aaf0786 Fix issue with Aurora compilers 2025-11-21 21:41:13 +00:00
paboyle 9c3835524c Fix compile warn 2025-11-21 21:41:12 +00:00
paboyle 549351bb8a Stag verbose clean up 2025-11-20 18:22:57 +00:00
RChHill b650b89682 Define Sp4 ProjectOnGeneralGroup for generic vtype 2025-11-19 13:26:52 +00:00
Peter Boyle 74e6b19f83 Looks like the reuse of xfers in staggered has bugs or corner cases depending on volume 2025-11-17 22:29:06 -05:00
Peter Boyle 2e684028de Improvements 2025-11-14 18:12:27 -05:00
paboyle c54d87a472 Aurora compile fix for new compiler 2025-11-06 18:17:33 +00:00
Allen McKnight 4304245c1b Merge branch 'develop' into bugfix/rocm7-rocblas-type-refactor 2025-11-04 08:50:11 -06:00
Peter Boyle 6165931afa Update GridStd.h 2025-10-03 14:35:37 -04:00
Your Name 1d1fd3bcaf adding a version check to handle rocblas type change 2025-10-02 15:24:24 -05:00
paboyle 23581333e6 link cufft 2025-08-21 22:25:55 +01:00
paboyle e5fa3d887f Compile on CUDA 2025-08-21 22:10:27 +01:00
paboyle 583fa7bb0a FFTW guarded after CUDA adn HIP 2025-08-21 22:00:12 +01:00
Peter Boyle fe0db53842 FFT offload to GPU and MUCH faster comms.
40x speed up on Frontier
2025-08-21 16:45:38 -04:00
Peter Boyle 76c0ada1e1 Benchmark for En Hung 2025-08-21 16:45:38 -04:00
Peter Boyle 92f49e9194 Merge pull request #482 from g-simonetti/wflow_sp2n_paboyle
Fixed Wilson flow for Nc not equal to 3
2025-08-21 09:10:25 -04:00
Peter Boyle 44c8057b5f Merge pull request #481 from vataspro/sp-reps-fix
Only compile higher fermion representations for symplectic gauge group when requested via configure flag
2025-08-20 12:57:28 -04:00
Alexis Provatas 0ad837f595 Fix Sp representations compilation 2025-08-20 17:48:39 +01:00
Peter Boyle bd2103c746 Merge pull request #480 from vataspro/fix-no-comms
Fix enable-comms=none
2025-08-20 12:26:47 -04:00
Alexis Provatas 9c18d2ddb0 Fix StencilSendToRecvFromBegin to agree with base 2025-08-20 17:17:06 +01:00
g-simonetti 1245a8c151 num_colours added to class S 2025-08-20 16:27:34 +01:00
g-simonetti 07113dc8ba Changed beta=3 to beta=Nc with comments 2025-08-20 16:18:34 +01:00
paboyle a3420e6fa9 Update for grid view logging 2025-08-14 21:29:20 +00:00
paboyle 732836d9f8 Missed one 2025-08-14 20:25:54 +00:00
paboyle 87658f7b53 ASSERT tripped in Shuhei's branch 2025-08-14 20:08:54 +00:00
paboyle e7f51e5fb1 Timer pointers for hadrons compat.
Reluctantly, this interface is silly to pass timers around.
2025-08-11 21:11:36 +01:00
Peter Boyle 1ce5f70dd1 Update GridStd.h 2025-08-11 12:20:54 -04:00
Peter Boyle 473635f401 Update BinaryIO.h 2025-08-11 11:06:06 -04:00
paboyle 5adf2657dd Updated to compile and run fast on CUDA 2025-08-10 00:00:13 +01:00
paboyle 82cfff2990 A2A meson field BLAS based momentum project 2025-08-07 15:51:15 +00:00
paboyle 4397b1c442 Debugged momentum projection for A2A Meson Field 2025-08-07 15:51:01 +00:00
paboyle 9e6a4a4737 Assertion updates to macros (mostly) with backtrace.
WIlson flow to include options for DBW2, Iwasaki, Symanzik.
View logging for data assurance
2025-08-07 15:48:38 +00:00
paboyle 41f344bbd3 Merge with Christoph GPT checksum debug 2025-07-15 03:06:09 +00:00
paboyle a77cd50b2f Update comms logging in Cshift 2025-07-11 14:36:10 +00:00
paboyle 73af020f98 improved 2025-06-27 06:08:54 +00:00
paboyle bffb83c46e std::cout<<GridLogMessage<<"Debug:"<<std::endl;
std::cout<<GridLogMessage<<"  --dylib-map     : print dynamic library map, useful for interpreting signal backtraces "<<std::endl;
    std::cout<<GridLogMessage<<"  --heartbeat     : periodic itimer wakeup (interrupts stuck system calls!) "<<std::endl;
    std::cout<<GridLogMessage<<"  --signal-delay n : pause for n seconds after signal handling (useful to get ALL nodes in stuck state) "<<std::endl;
    std::cout<<GridLogMessage<<"  --debug-stdout  : print stdout from EVERY node to file Grid.stdout/err.rank "<<std::endl;
    std::cout<<GridLogMessage<<"  --debug-signals : catch sigsegv and print a blame report, handle SIGHUP with a backtrace to stderr"<<std::endl;
    std::cout<<GridLogMessage<<"  --debug-heartbeat : periodically report backtrace "<<std::endl;

--dylib-map : Grid prints its dylib regions
--heartbeat : itimer based / SIGALRM wake up which seems to make Aurora
more stable
--debug-heartbeat : periodically report to stderr where we are in code

Now have libunwind option (configure: --with-unwind=<prefix>) to give an
Asynch-Signal safe backtrace. Avoid glibc backtrace due to mallocs.
2025-06-27 06:08:54 +00:00
paboyle 7031f37350 Use libunwind for backtrace as it is signal asynch safe 2025-06-27 06:08:54 +00:00
paboyle 829dd74cb2 Verbose change 2025-06-27 06:08:54 +00:00
paboyle 66e671985d P2P 2025-06-27 06:08:54 +00:00
paboyle 5afcbcf0f3 Cshift uses flight recorder 2025-06-27 06:08:54 +00:00
paboyle 9730579312 Simplify and verbose 2025-06-27 06:08:51 +00:00
paboyle bfae14d035 More flight logging 2025-06-27 06:07:34 +00:00
paboyle b78fc73d19 Better signal handler 2025-06-27 06:07:34 +00:00
Peter Boyle 709f8ae76c Update README 2025-06-26 23:06:11 -04:00
Peter Boyle 7aa06329d0 Update for new stencil compression options 2025-06-17 18:06:19 +02:00
Peter Boyle 9d6a38c44c Compressed comms options as Sloppy 2025-06-17 16:43:53 +02:00
Peter Boyle 6ec5cee368 Preparing for compressed comms 2025-06-17 16:38:10 +02:00
Peter Boyle f2e9a68825 Simplify 2025-06-13 17:32:05 +02:00
Peter Boyle d88750e6b6 Sloppy + non-sloppy 2025-06-13 16:42:01 +02:00
Peter Boyle 821358eda7 Remove partial dirichlet. Favour intro reduced prec comms options 2025-06-13 05:08:45 +02:00
Peter Boyle fce6e1f135 Kill core files for quota reasons 2025-06-13 05:08:15 +02:00
Peter Boyle 8f0bb3e676 remove partial dirichlet 2025-06-13 05:07:56 +02:00
Peter Boyle 262c70d967 USe sloppy comms options 2025-06-13 05:07:23 +02:00
Peter Boyle da43ef7c2d REmove partial dirichlet option. It's going nowhere 2025-06-13 05:05:15 +02:00
Peter Boyle 7b60ab5df1 Warning suppress 2025-06-13 05:04:55 +02:00
Peter Boyle f6b961a64e Warning suppress 2025-06-13 05:04:47 +02:00
Peter Boyle f1ed988aa3 Interface to reduced precision comms 2025-06-13 05:04:12 +02:00
Peter Boyle eea51bb604 Suppress annoying warns 2025-06-13 05:03:36 +02:00
Peter Boyle 9203126aa5 Scripts 2025-06-11 15:30:16 +02:00
Peter Boyle f90ba4712a Update for Jupiter 2025-06-11 15:24:34 +02:00
Peter Boyle 3737a24096 Updated python output 2025-06-03 14:09:29 -04:00
paboyle d418f78352 Making running on Aurora more debuggable 2025-05-23 20:58:16 +00:00
paboyle 25163998a0 Makes SYCL compiler happy 2025-05-23 20:57:11 +00:00
Peter Boyle dc546aaa4b Updated config options for BNL cluster 2025-05-13 18:44:47 -04:00
Peter Boyle 5364d580c9 Output chirality, eigenvector density files and python source lego plot 2025-05-13 18:44:47 -04:00
Peter Boyle 2a9a6347e3 Do not require Grid format RNGs and also to the 5Li reporting 2025-05-13 18:44:47 -04:00
Peter Boyle cfdb56f314 Run measurements at t=0 too 2025-05-13 18:44:46 -04:00
Peter Boyle b517e88db3 Update README 2025-05-13 16:49:21 -04:00
paboyle bb317aba8d Lattice = for sycl 2025-05-13 12:50:58 +00:00
paboyle 644cc6647e JSON update 2025-05-13 12:50:58 +00:00
paboyle 72397ce23b SYCL interface change 2025-05-13 12:50:58 +00:00
Alexis Provatas c646d91527 Fix names, protect against bad index values, clean docstrings 2025-05-01 10:52:00 +01:00
Alexis Provatas a2b98d82e1 remove obsolete spatial polyakov observable file 2025-05-01 10:52:00 +01:00
Alexis Provatas 7b9415c088 Move observable logger to Polyakov Loop file and fix docstring 2025-05-01 10:52:00 +01:00
Alexis Provatas cb7110f492 Add Spatial Polyakov Loop observable 2025-05-01 10:52:00 +01:00
Alexis Provatas 0c7af66490 Create Spatial Polyakov Observable Module 2025-05-01 10:52:00 +01:00
Alexis Provatas 496d1b914a Generalise Polyakov loop and overload for temporal direction 2025-05-01 10:52:00 +01:00
Peter Boyle d60a80c098 Fixes and visualisation 2025-04-29 18:04:23 -04:00
Peter Boyle bb8b6d9d73 Fix 2025-04-29 18:04:04 -04:00
Peter Boyle 677b4cc5b0 Make all tests compile 2025-04-24 20:33:26 -04:00
Peter Boyle be565ffab6 update mac config command 2025-04-24 14:50:06 -04:00
Peter Boyle df6120e5f6 CPU compile oops fix 2025-04-24 14:50:06 -04:00
Peter Boyle 21de6f7da8 Merge pull request #477 from lehner/feature/wilson-clover-5d
Feature/wilson clover 5d
2025-04-24 14:44:48 -04:00
Peter Boyle dbe39f9ce0 Merge pull request #471 from edbennett/fix-wflow
Shave off rough edges in Wilson flow test
2025-04-24 14:40:31 -04:00
Peter Boyle ab3de50d5e Merge pull request #473 from UCL-ARC/gauge_action_deriv
WilsonGagueAction deriv
2025-04-24 14:39:10 -04:00
Peter Boyle c545bd2139 Merge pull request #465 from edbennett/allow-nonsu3-compilation
guard against trying to compile SU3-specific code when Nc ≠ 3
2025-04-24 14:35:51 -04:00
Peter Boyle 6a1c64fbdd Merge pull request #470 from paboyle/specflow
Spectral flow, DWF/Mobius kernel measurement
2025-04-24 14:34:33 -04:00
Peter Boyle b75809ed61 Update README 2025-04-24 14:27:22 -04:00
Peter Boyle ecaf228e5c Update README 2025-04-24 14:25:32 -04:00
Peter Boyle 6d015ae8fc Visualisation tools 2025-04-24 13:47:34 -04:00
Peter Boyle 233150d93f Bug fix for no accelerator aware MPI, thanks Shuhei for finding it. 2025-04-24 11:40:46 -04:00
Peter Boyle 7af8c77a52 Normalise 2025-04-24 11:37:39 -04:00
Chulwoo Jung a957e7bfa1 Adding DWF evec Chirality measurement 2025-04-22 22:17:51 +00:00
Chulwoo Jung cee4c8ce8c Merge branch 'develop' of https://github.com/paboyle/Grid into specflow 2025-04-18 19:55:36 +00:00
Christoph Lehner 96bf814d8c Add checkerboarding to 5D compact clover 2025-04-10 23:05:39 +02:00
Christoph Lehner 7ddc422788 CompactWilsonClover5D 2025-04-10 23:05:29 +02:00
Peter Boyle e652fc2825 Shared Memory test reenabled on every Grid object creation.
Const improvements in Accelerator.h
2025-04-07 11:51:40 -04:00
Peter Boyle a49fa3f8d0 ROCM 6.3.1 appears to work 2025-04-07 11:50:59 -04:00
Peter Boyle cd452a2f91 Slurm update 2025-04-04 18:40:20 -04:00
Peter Boyle 4f89f603ae Changes to add back shared memory test on GPU 2025-04-04 18:40:15 -04:00
Peter Boyle 11dc2c5e1d PVdagM initialise 2025-04-04 18:35:06 -04:00
Peter Boyle 6fec3c15ca Cleaner printing 2025-04-04 18:35:06 -04:00
Peter Boyle 938c47480f Updated compile on frontier.
Unsatisfactory hacsk
2025-04-04 18:35:06 -04:00
Peter Boyle 3811d19298 Fence 2025-04-04 18:35:06 -04:00
Peter Boyle 83a3ab6b6f Barrier -- not sure 100% this was needed 2025-04-04 18:35:05 -04:00
Peter Boyle d66a9af6a3 No compile fix 2025-04-04 18:35:05 -04:00
Peter Boyle adc90d3a86 NVLINK GET/PUT on cuda aware mpi 2025-04-04 18:35:05 -04:00
Peter Boyle ebbd015c5c Deprecate shared memory copy as direction matters on nvidia GPU 2025-04-04 18:35:05 -04:00
Peter Boyle 4ab73b36b2 Deprecate shared memory copy as direction matters on GPU 2025-04-04 18:35:05 -04:00
Peter Boyle 130e07a422 Non hermitian support 2025-04-04 18:35:05 -04:00
Peter Boyle 8f47bb367e Shifted non herm 2025-04-04 18:35:05 -04:00
Peter Boyle 0c3cb60135 Script update 2025-04-04 18:35:05 -04:00
Peter Boyle 9eae8fca5d Size outut 2025-04-04 18:35:05 -04:00
Peter Boyle 882a217074 Example of Useful prerequisite installs with spack 2025-03-26 11:28:53 -04:00
Mashy Green e465fce201 Merge remote-tracking branch 'upstream/develop' into gauge_action_deriv 2025-03-24 10:12:42 +00:00
Mashy Green d41542c64b reverted sp2n test wilsonfundfermiongauge to original 2025-03-24 08:29:15 +00:00
Peter Boyle 199818bd6c Merge pull request #475 from lehner/feature-aurora
Sync with GPT on Aurora
2025-03-13 08:55:55 -04:00
Christoph Lehner fe66c7ca30 verbosity 2025-03-13 12:49:36 +00:00
Christoph Lehner e9177e4af3 Blas compatibility 2025-03-13 08:48:23 +00:00
Christoph Lehner d15a6c5933 Merge branch 'develop' of https://github.com/paboyle/Grid into feature-aurora 2025-03-13 07:29:55 +00:00
paboyle 25ab9325e7 Use hostVector but remove construct resize 2025-03-11 15:02:32 +00:00
paboyle 19f9378b98 Should work on Aurora nowb 2025-03-11 13:50:43 +00:00
Mashy Green 785bc7a14f Adding staple zeroing fix 2025-03-10 12:29:04 +00:00
Mashy Green 1a1fe85428 Merge remote-tracking branch 'upstream' into gauge_action_deriv 2025-03-10 08:37:36 +00:00
Mashy Green 0000d2e558 Merge branch 'develop' into gauge_action_deriv 2025-03-10 08:35:57 +00:00
Christoph Lehner 9ffd1ed4ce Merged 2025-03-08 15:30:08 +00:00
Peter Boyle 3d014864e2 Makinig LLVM happy 2025-03-06 14:19:25 -05:00
paboyle 1d22841811 Working on aurora, GPT issue turned up is fixed 2025-03-06 03:20:18 +00:00
Peter Boyle a1cdda833f Update WorkArounds.txt 2025-03-05 14:04:23 -05:00
Peter Boyle ad6db92690 Update WorkArounds.txt 2025-03-05 14:00:26 -05:00
Peter Boyle e8ff9d8e50 Update WorkArounds.txt 2025-03-05 14:00:04 -05:00
Peter Boyle 795769c636 Update WorkArounds.txt 2025-03-05 13:50:41 -05:00
Peter Boyle 267a39d943 Update WorkArounds.txt 2025-03-05 13:49:43 -05:00
Peter Boyle 3624bd3d22 Update WorkArounds.txt 2025-03-05 13:45:09 -05:00
Peter Boyle bc12dbbb38 Update WorkArounds.txt 2025-03-05 12:48:56 -05:00
Peter Boyle eb8a008a8f Create WorkArounds.txt 2025-03-05 12:41:59 -05:00
paboyle c4d9aa1a21 Config command that makes GPT happier 2025-02-27 20:12:49 +00:00
paboyle 6ae809ed40 Print not liked on GPT compile 2025-02-27 20:12:49 +00:00
Peter Boyle 311e2aab3f Update Accelerator.h 2025-02-26 11:42:52 -05:00
paboyle 438dfbdb83 Only throw if there is a pending list entry in CommsComplete 2025-02-25 16:57:27 +00:00
paboyle b2ce760cf4 Verbose issue with GPT 2025-02-25 16:55:23 +00:00
Muhammad AsifandMashy Green b1ba209696 Latest upstream with np-su3 patch and modified Sp_WilsonFunfFermionGauge test to be small (#22)
Co-authored-by: Mashy Green <mashy@me.com>

merging no-su3 patch
2025-02-24 11:38:42 +00:00
Muhammad Asif cb3e529b1e Merge branch 'paboyle:develop' into develop 2025-02-24 11:29:09 +00:00
Mashy Green 717f647418 added the WilsonFlow patch from upstream PR #471 2025-02-24 08:41:31 +00:00
Mashy Green 98e7418187 Merge remote-tracking branch 'upstream/develop' into gauge_action_deriv 2025-02-24 08:33:05 +00:00
Mashy GreenandMashy Green fe05bf48b1 Improvements to WilsonGaugeAction deriv function (#16)
* patched version + modifications to deriv -> staple in qcd/gauge

* Cleaning up and aligning variable naming between action deriv versions

* Removing the regresion test files that were also in this branch for a clean PR

* Reverting whitespace changes

* Fixing after revering too much!

---------

Co-authored-by: Mashy Green <mashy@me.com>
2025-02-17 18:52:04 +00:00
Mashy Green d2dd8f54e2 Fixing after revering too much! 2025-02-17 17:32:27 +00:00
Mashy Green 7726ee4b16 Reverting whitespace changes 2025-02-17 17:16:28 +00:00
paboyle ba9bbe0221 Bounce MPI through host 2025-02-12 19:34:59 +00:00
paboyle 4c3dd82d84 CSHIFT with bounce throuhgh Host memory on MPI packets 2025-02-12 19:09:53 +00:00
paboyle 44e911b5b7 Comment change 2025-02-12 17:37:55 +00:00
paboyle a7a16df9d0 GET not put has kinder barrier sequence for NVLINK type access as when
GET is done, I can use it without barrier. Moves a barrier to a nicer
place, overlapped with DtoH DMA
2025-02-12 14:59:28 +00:00
paboyle 382e0abefd Was issueing a double fence -- the gather also fences 2025-02-12 14:57:28 +00:00
paboyle 6fdefe5b90 Barrier sequencing if doing "GET" not "PUT" is different.
This is somewhat better timing for Barriers
2025-02-12 14:55:20 +00:00
paboyle 4788dd8e2e More states in packet progression for GPU non aware MPI 2025-02-12 14:53:57 +00:00
paboyle 1cc5f221f3 GET not put ordering is better as I know when I've got all MY data 2025-02-12 14:53:05 +00:00
paboyle 93251bfba0 GET not put for better ordering in the downstream dependent kernels -- I
know when I'm done, so we can move a barrier / handshake between ranks
intranode to a point off critical path
2025-02-12 14:50:21 +00:00
paboyle 18b79508b8 New line better for pretty print 2025-02-12 14:49:48 +00:00
paboyle 4de5ed1613 Remove vector view. The std::vector will not inform Memory manager of
deletion and so a stale entry could be left. It is not and should not be
used.
2025-02-12 14:48:46 +00:00
paboyle 0baaddbe98 Pipeline mode commit on Aurora. 5+ TF/s on 16^3x32 per tile at 384
nodes.
More concurrency/fine grained scheduling is possible.
2025-02-04 19:27:26 +00:00
edbennett 8729c46169 add clover energy density measurement to default WilsonFlow measurements 2025-02-03 14:27:55 +00:00
edbennett 09f81fe7c3 don't force energy density measurement to be every wilson flow iteration 2025-02-03 14:27:45 +00:00
edbennett 1876e5b7c0 correct tests/smearing/WilsonFlow to use non-adaptive flow and use correct interface 2025-02-03 14:27:29 +00:00
Mashy Green 355ec76257 Merge pull request #18 from UCL-ARC/bugfix/nvtx
Bugfix/nvtx
2025-02-03 11:05:42 +00:00
paboyle b50fb34e71 Perf on Aurora 2025-02-01 18:39:34 +00:00
paboyle de84d730ff Fastest run config on Aurora to date 2025-02-01 18:08:40 +00:00
Peter Boyle c74d11e3d7 PVdagM MG 2025-02-01 11:04:13 -05:00
Christoph Lehner 84cab5e6e7 no comms and log cleanup 2025-02-01 16:37:21 +01:00
Mashy Green 4f17c8d081 Merge branch 'paboyle:develop' into bugfix/nvtx 2025-01-29 13:10:12 +00:00
Mashy Green aaab753982 Reverting to older version of nvtx for Tursa support 2025-01-29 12:57:38 +00:00
Chulwoo Jung 570b72a47b Bugfix. Sorry! 2025-01-21 15:37:39 -05:00
Chulwoo Jung a5798a89ed Merge branch 'develop' into specflow 2025-01-21 12:13:24 -05:00
Chulwoo Jung f7e2f9a401 Checking in spectral flow and DWF/Mobius kernel eigenvalue measurement 2025-01-16 20:47:33 +00:00
Chulwoo Jung 2848a9b558 DWF Kernel lanczos working(?) 2025-01-16 01:29:56 +00:00
Mashy Green d4868991af Fixed wrong lib for NVTX in configure.ac and updated to nvtx3 2025-01-10 14:53:19 +00:00
Mashy Green e99d42404e Removing the regresion test files that were also in this branch for a clean PR 2024-12-16 16:31:22 +00:00
Mashy Green 3ba019c747 Cleaning up and aligning variable naming between action deriv versions 2024-12-03 15:23:00 +00:00
Mashy Green 47429218bb patched version + modifications to deriv -> staple in qcd/gauge 2024-11-27 16:29:22 +00:00
edbennett 8d305df0db guard against trying to compile SU3-specific code when Nc ≠ 3 2024-05-24 14:00:56 +01:00
600 changed files with 52372 additions and 6191 deletions
+24 -24
View File
@@ -120,7 +120,7 @@ inline void acceleratorMemSet(void *base,int value,size_t bytes) { cudaMemset(ba
cudaGetErrorString( err )); \
printf("File %s Line %d\n",__FILE__,__LINE__); \
fflush(stdout); \
if (acceleratorAbortOnGpuError) assert(err==cudaSuccess); \
if (acceleratorAbortOnGpuError) GRID_ASSERT(err==cudaSuccess); \
} \
}
@@ -168,7 +168,7 @@ public:
if ( (_Tp*)ptr == (_Tp *) NULL ) {
printf("Grid Device Allocator got NULL for %lu bytes\n",(unsigned long) bytes );
}
assert( ( (_Tp*)ptr != (_Tp *)NULL ) );
GRID_ASSERT( ( (_Tp*)ptr != (_Tp *)NULL ) );
return ptr;
}
@@ -276,11 +276,11 @@ public:
{
#ifdef GRID_HIP
auto err = hipDeviceSynchronize();
assert(err==hipSuccess);
GRID_ASSERT(err==hipSuccess);
#endif
#ifdef GRID_CUDA
auto err = cudaDeviceSynchronize();
assert(err==cudaSuccess);
GRID_ASSERT(err==cudaSuccess);
#endif
#ifdef GRID_SYCL
accelerator_barrier();
@@ -305,8 +305,8 @@ public:
{
RealD t2=usecond();
assert(OpA!=GridBLAS_OP_T); // Complex case expect no transpose
assert(OpB!=GridBLAS_OP_T);
GRID_ASSERT(OpA!=GridBLAS_OP_T); // Complex case expect no transpose
GRID_ASSERT(OpB!=GridBLAS_OP_T);
int lda = m; // m x k column major
int ldb = k; // k x n column major
@@ -341,7 +341,7 @@ public:
(hipblasDoubleComplex *) Bkn, ldb,
(hipblasDoubleComplex *) &beta_p[0],
(hipblasDoubleComplex *) Cmn, ldc);
assert(err==HIPBLAS_STATUS_SUCCESS);
GRID_ASSERT(err==HIPBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_CUDA
cublasOperation_t hOpA;
@@ -361,7 +361,7 @@ public:
(cuDoubleComplex *) Bkn, ldb,
(cuDoubleComplex *) &beta_p[0],
(cuDoubleComplex *) Cmn, ldc);
assert(err==CUBLAS_STATUS_SUCCESS);
GRID_ASSERT(err==CUBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_SYCL
int64_t m64=m;
@@ -433,8 +433,8 @@ public:
{
RealD t2=usecond();
assert(OpA!=GridBLAS_OP_T); // Complex case expect no transpose
assert(OpB!=GridBLAS_OP_T);
GRID_ASSERT(OpA!=GridBLAS_OP_T); // Complex case expect no transpose
GRID_ASSERT(OpB!=GridBLAS_OP_T);
int lda = m; // m x k column major
int ldb = k; // k x n column major
@@ -469,7 +469,7 @@ public:
(hipblasComplex *) Bkn, ldb,
(hipblasComplex *) &beta_p[0],
(hipblasComplex *) Cmn, ldc);
assert(err==HIPBLAS_STATUS_SUCCESS);
GRID_ASSERT(err==HIPBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_CUDA
cublasOperation_t hOpA;
@@ -489,7 +489,7 @@ public:
(cuComplex *) Bkn, ldb,
(cuComplex *) &beta_p[0],
(cuComplex *) Cmn, ldc);
assert(err==CUBLAS_STATUS_SUCCESS);
GRID_ASSERT(err==CUBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_SYCL
int64_t m64=m;
@@ -595,11 +595,11 @@ public:
{
RealD t2=usecond();
int32_t batchCount = Amk.size();
assert(Bkn.size()==batchCount);
assert(Cmn.size()==batchCount);
GRID_ASSERT(Bkn.size()==batchCount);
GRID_ASSERT(Cmn.size()==batchCount);
assert(OpA!=GridBLAS_OP_T); // Complex case expect no transpose
assert(OpB!=GridBLAS_OP_T);
GRID_ASSERT(OpA!=GridBLAS_OP_T); // Complex case expect no transpose
GRID_ASSERT(OpB!=GridBLAS_OP_T);
int lda = m; // m x k column major
int ldb = k; // k x n column major
@@ -636,7 +636,7 @@ public:
(hipblasDoubleComplex **)&Cmn[0], ldc,
batchCount);
// std::cout << " hipblas return code " <<(int)err<<std::endl;
assert(err==HIPBLAS_STATUS_SUCCESS);
GRID_ASSERT(err==HIPBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_CUDA
cublasOperation_t hOpA;
@@ -657,7 +657,7 @@ public:
(cuDoubleComplex *) &beta_p[0],
(cuDoubleComplex **)&Cmn[0], ldc,
batchCount);
assert(err==CUBLAS_STATUS_SUCCESS);
GRID_ASSERT(err==CUBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_SYCL
int64_t m64=m;
@@ -804,8 +804,8 @@ public:
RealD t2=usecond();
int32_t batchCount = Amk.size();
assert(OpA!=GridBLAS_OP_T); // Complex case expect no transpose
assert(OpB!=GridBLAS_OP_T);
GRID_ASSERT(OpA!=GridBLAS_OP_T); // Complex case expect no transpose
GRID_ASSERT(OpB!=GridBLAS_OP_T);
int lda = m; // m x k column major
int ldb = k; // k x n column major
@@ -821,8 +821,8 @@ public:
acceleratorCopyToDevice((void *)&beta ,(void *)&beta_p[0],sizeof(ComplexF));
RealD t0=usecond();
assert(Bkn.size()==batchCount);
assert(Cmn.size()==batchCount);
GRID_ASSERT(Bkn.size()==batchCount);
GRID_ASSERT(Cmn.size()==batchCount);
#ifdef GRID_HIP
hipblasOperation_t hOpA;
hipblasOperation_t hOpB;
@@ -843,7 +843,7 @@ public:
(hipblasComplex **)&Cmn[0], ldc,
batchCount);
assert(err==HIPBLAS_STATUS_SUCCESS);
GRID_ASSERT(err==HIPBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_CUDA
cublasOperation_t hOpA;
@@ -864,7 +864,7 @@ public:
(cuComplex *) &beta_p[0],
(cuComplex **)&Cmn[0], ldc,
batchCount);
assert(err==CUBLAS_STATUS_SUCCESS);
GRID_ASSERT(err==CUBLAS_STATUS_SUCCESS);
#endif
#ifdef GRID_SYCL
int64_t m64=m;
+185
View File
@@ -0,0 +1,185 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What This Is
Grid is a data-parallel C++ library for lattice QCD. It provides SIMD-vectorised lattice containers, MPI-based domain decomposition, GPU acceleration (CUDA/HIP/SYCL), and a full suite of QCD algorithms including HMC.
## Build
Uses GNU Autotools. The bootstrap step only needs to run once (or after `configure.ac` changes).
```bash
./bootstrap.sh # downloads Eigen 3.4.0, generates configure
mkdir build && cd build
../configure [options]
make -j$(nproc)
make check # run root-level tests
make install
```
Key configure options:
| Option | Common values |
|--------|---------------|
| `--enable-simd=` | `AVX2`, `AVX512`, `KNL`, `A64FX`, `NEONv8`, `GPU` |
| `--enable-comms=` | `mpi-auto`, `mpi3-auto`, `none` |
| `--enable-accelerator=` | `cuda`, `hip`, `sycl` |
| `--enable-shm=` | `shmopen`, `hugetlbfs`, `nvlink` |
| `--enable-Nc=` | `3` (default), `2`, `4`, `5` |
| `--with-gmp=`, `--with-mpfr=`, `--with-fftw=`, `--with-lime=` | paths to libs |
| `--enable-hdf5`, `--enable-mkl`, `--enable-lapack` | optional features |
GPU builds additionally need `--enable-gen-simd-width=64` (sets 512-bit SIMD width for GPU warp/wavefront sizing) and `--enable-unified=no --enable-shm=nvlink` for multi-GPU runs.
To speed up compilation, `--disable-fermion-reps --disable-gparity` skips instantiating G-parity and higher-representation fermion operators.
Platform recipes from `README.md`:
- **KNL**: `--enable-simd=KNL --enable-comms=mpi3-auto --enable-mkl`
- **Skylake/Haswell**: `--enable-simd=AVX512` or `AVX2` + `--enable-comms=mpi3-auto`
- **AMD EPYC**: `--enable-simd=AVX2 --enable-comms=mpi3`
- **A64FX (Fugaku)**: `--enable-simd=A64FX --enable-comms=mpi3 --enable-shm=shmget` (see `SVE_README.txt`)
Complete, working `configure` invocations for specific HPC systems (Frontier/ROCm, Perlmutter/CUDA, Summit, SDCC-A100, etc.) live in `systems/<platform>/config-command`. These are the canonical references for production builds.
Required external libs: GMP, MPFR, OpenSSL, zlib.
### Use `systems/` for real machines
`systems/<machine>/` holds the known-good build for each production platform (`Frontier`, `Aurora`, `Perlmutter`, `Summit`, `Tursa`, `Lumi`, `Booster`, `Crusher`, `SDCC-*`, `mac-arm`, …). Each contains a `config-command` (the exact `../../configure` invocation) and a `sourceme.sh` (module loads and env). **Prefer copying/adapting these over hand-rolling configure flags** — they encode compiler workarounds, `LDFLAGS`, and shared-memory settings that are easy to get wrong. `systems/WorkArounds.txt` records known vendor bugs.
Note the GPU builds use `--enable-simd=GPU --enable-gen-simd-width=64`, so `Nsimd` is *not* 1 on device (it is `64/sizeof(scalar)`).
### Regenerating `Make.inc` — required after adding or deleting source files
`Make.inc` files are generated, not tracked in git (`.gitignore`d). `scripts/filelist` walks `Grid/`, `tests/*`, `benchmarks/`, `examples/`, and `HMC/` and writes the file lists and per-test `bin_PROGRAMS` rules. Every new `.cc`/`.h` in `Grid/`, and every new `Test_*.cc` / `Benchmark_*.cc` / `Example_*.cc`, is invisible to the build until you run:
```bash
./scripts/filelist # from the source root, then re-run configure/make
```
`bootstrap.sh` runs it for you on the first setup.
## Running Tests and Benchmarks
```bash
# From build directory
make check # root-level tests (Test_simd, Test_cshift, etc.)
make -C tests/<subdir> tests # build tests in a subdirectory
make tests # build all tests across all subdirectories
./tests/core/Test_simd # run a single test binary directly
mpirun -n 4 ./tests/core/Test_cshift --grid 16.16.16.16 --mpi 1.1.1.4
```
`make check` is a thin smoke test — building a subdirectory with `make -C tests/<subdir> tests` and running the relevant binaries directly is the normal development loop. Test binaries take Grid's standard command-line arguments (`--grid`, `--mpi`, `--accelerator-threads`, `--threads`, `--debug-signals`, `--log`); see `Grid/util/Init.cc`.
Test subdirectories and their focus: `core` (SIMD, stencil, comms), `solver` (CG, GMRES, eigensolvers), `hmc` (MD integrators), `forces` (fermion forces), `lanczos`, `IO`, `smearing`, `sp2n`, `debug`.
Tests and benchmarks that need optional fermion representations are guarded by `disable_tests_without_instantiations.h` / `disable_benchmarks_without_instantiations.h`, so a `--disable-fermion-reps --disable-gparity` build silently compiles them to no-ops.
## Architecture
### Layer stack (bottom to top)
1. **SIMD layer** (`Grid/simd/`) — platform-specific intrinsics wrapped into `vRealF`, `vComplexD`, etc. The SIMD width and layout are compile-time constants controlled by `--enable-simd`.
2. **Tensor layer** (`Grid/tensors/`) — Lorentz/colour/spin tensor algebra built on top of SIMD types. `iMatrix`, `iVector`, `iScalar` templates compose into QCD types like `ColourMatrix`, `SpinColourVector`.
3. **Lattice layer** (`Grid/lattice/`) — `Lattice<T>` container: a site-local tensor replicated across a distributed Cartesian grid. All arithmetic is site-parallel and expression-template-fused.
4. **Cartesian/comms layer** (`Grid/cartesian/`, `Grid/communicator/`) — `GridCartesian` holds the MPI topology and local/global geometry. `Grid/cshift/` implements nearest-neighbour halo exchange; `Grid/stencil/` is the optimised multi-hop stencil used by Dirac operators.
5. **Algorithm layer** (`Grid/algorithms/`) — iterative solvers (CG, GMRES, BiCGSTAB, mixed-precision), eigensolvers (Lanczos, LAPACK), FFT, smearing, and multigrid.
6. **QCD layer** (`Grid/qcd/`) — gauge and fermion actions, HMC integrators, observables.
### QCD subsystem (`Grid/qcd/`)
- `action/fermion/` — Wilson, Clover, DWF (Mobius), Staggered, twisted-mass, G-parity variants
- `action/gauge/` — Wilson gauge, Symanzik, Iwasaki, DBW2, plaquette+rect
- `representations/` — Fundamental, Adjoint, Two-index, Sp(2n)
- `hmc/` — Leapfrog, OMF2/OMF4 integrators; pseudofermion refreshment; Metropolis accept/reject
- `smearing/` — APE, Stout, HEX, gradient flow
- `observables/` — Polyakov loop, plaquette, topological charge
### GPU acceleration and the view/memory-manager discipline
### Multigrid (`Grid/algorithms/multigrid/`)
Aggregation-based algebraic multigrid for Wilson-type fermions. Key files: `CoarsenedMatrix.h` (coarse operator), `GeneralCoarsenedMatrix.h` and `GeneralCoarsenedMatrixMultiRHS.h` (general coarsening supporting multi-RHS solves), `Aggregates.h` (near-null vector construction), `Geometry.h` (coarse-grid geometry). `MultiGrid.h` is the top-level include.
### GPU acceleration
GPU support is injected via macros in `Grid/threads/Accelerator.h``accelerator_for(i, n, nsimd, {...})`, `accelerator_forNB` (non-blocking, must be followed by `accelerator_barrier()`), `accelerator_for2dNB`, and `accelerator_inline`. On a CPU build these degrade to `thread_for` (OpenMP). Unified virtual memory is on by default (`--enable-unified=yes`); device-aware MPI (`--enable-accelerator-aware-mpi`) avoids device→host copies on transfers.
Lattice data is **not** directly addressable inside a kernel. You must open a view with the correct access mode so `Grid/allocator/MemoryManager.h` can move/mark the data:
```cpp
autoView(out_v, out, AcceleratorWriteDiscard); // RAII; closes at end of scope
autoView(in_v, in, AcceleratorRead);
accelerator_for(ss, grid->oSites(), Nsimd, {
coalescedWrite(out_v[ss], coalescedRead(in_v[ss]));
});
```
Modes are `AcceleratorRead/Write/WriteDiscard` and `CpuRead/Write/WriteDiscard`. Getting the mode wrong (e.g. `AcceleratorRead` on a field you write) produces stale-data bugs that only appear on GPU builds. Inside kernels use `coalescedRead`/`coalescedWrite` rather than raw `operator[]` — they map the SIMD lane onto `threadIdx.x` so accesses stay coalesced.
### Repo-local debugging skills (`skills/`)
`skills/` contains hard-won, Grid-specific playbooks written as invocable skill files. Consult them before debugging in these areas rather than reasoning from first principles:
| File | Covers |
|---|---|
| `gpu-memory-performance.md` | `acceleratorThreads()`, LambdaApply thread mapping, `coalescedRead` idiom, fused vs staged HBM access |
| `gpu-runtime-correctness.md` | GPU runtime returning early from sync, silent wrong answers |
| `communication-overlap.md` | 7-phase halo pipeline, per-packet events, host-staging vs GPU-direct RDMA |
| `mpi-heterogeneous.md` | `MPI_Sendrecv` device-buffer aliasing, deterministic reductions |
| `compiler-validation.md` | Isolating GPU compiler codegen bugs, minimal reproducers |
| `correctness-verification.md` | Double-run fingerprinting, per-packet checksums, flight recorder |
| `hang-diagnosis.md` | Diagnosing MPI/accelerator hangs |
The key loop macros (defined in `Grid/threads/Accelerator.h`) are:
- `accelerator_for(iter, num, nsimd, {...})` — maps to CUDA/HIP kernel or OpenMP loop; `nsimd` is the innermost SIMD lane count
- `accelerator_forNB(...)` — non-blocking variant (no implicit barrier)
- `accelerator_for2dNB(iter1, num1, iter2, num2, nsimd, {...})` — 2D kernel launch
- `thread_for(iter, num, {...})` — CPU OpenMP loop (never dispatches to GPU)
On CPU builds, `accelerator_for` aliases to `thread_for`.
### Solver patterns
`SchurRedBlack` (`Grid/algorithms/iterative/SchurRedBlack.h`) implements red-black (even/odd) preconditioning for fermion operators. Most production fermion solves use `SchurRedBlackDiagMooeeSolve` or similar wrappers that internally call a `ConjugateGradient` on the Schur complement.
Mixed-precision solvers (`ConjugateGradientMixedPrec`, `BiCGSTABMixedPrec`) drive a double-precision outer loop with single-precision inner solves.
### Memory and I/O
- `Grid/allocator/` — aligned/NUMA-aware allocators; caching allocator via `--enable-alloc-cache`
- `Grid/parallelIO/` — distributed parallel reader/writer for ILDG (via LIME), SciDAC, and native binary formats
- `Grid/serialisation/` — text, binary, HDF5, XML/JSON serialisation of arbitrary Grid objects
### Executables
- `HMC/` — production HMC driver programmes (e.g. `Mobius2p1f.cc`, `DWF_plus_DSDR_nf2plus1_Shamir_Gparity.cc`)
- `benchmarks/``Benchmark_dwf`, `Benchmark_ITT`, `Benchmark_comms`, `Benchmark_memory_bandwidth`, … used to qualify a new machine
- `examples/` — small, readable programmes (`Example_plaquette.cc`, `Example_Mobius_spectrum.cc`) that are the best starting point for learning the API
Each of these directories auto-builds every top-level `.cc` as its own binary via `scripts/filelist`.
Every programme is wrapped in `Grid_init(&argc, &argv)` / `Grid_finalize()` (`Grid/util/Init.h`).
## Key Conventions
- **C++17** is required throughout.
- Template structure: most classes are templated on `<_FImpl>` (fermion impl) or `<Gimpl>` (gauge impl), which encode the representation and precision. Instantiation is controlled by `--enable-fermion-instantiations`.
- **Tensor indices are positional, not labelled.** The `Grid/tensors/` arithmetic recurses structurally over the `iScalar`/`iVector`/`iMatrix` nest: each level defines only the {scalar,vector,matrix}² products at its own level, with element types resolved by automatic type deduction, so every colour/spin/lorentz combination composes from ~200 lines (versus the pre-C++11 QDP++/PETE approach of machine-generating every case). An index's meaning derives entirely from its nesting depth counted from the outside; `iScalar` is the identity/broadcast case at every level. Never insert or remove a nesting level casually — the multiplication tables contract by position.
- **Multigrid coarsening deepens the tensor nest by one level.** A coarse site vector is `iVector<CComplex,nbasis>`, and `innerProduct` on it returns `iScalar<CComplex>` — one level deeper than the fine block scalar. So the block-inner-product scalar type gains one `iScalar` wrapper per MG level (fine: `vTComplex`; level 2: `iScalar<vTComplex>`; see `examples/Example_pvdagm_3level.cc`). When calling `blockInnerProduct`/`blockZAXPY`/`blockOrthogonalise` on coarse fields, the coarse scalar type must match `decltype(innerProduct(siteVector(),siteVector()))` exactly; a wrong depth fails to compile (no viable `operator=` deep in the instantiation chain) rather than mis-contracting.
- **Grids are borrowed, never owned.** `conformable` is pointer identity, so every object that interoperates must hold the *same* `GridCartesian *`; a class that minted its own grid internally could never conform with anything else. Ownership is therefore not available, and lifetime is managed by scope discipline instead of reference counting: whoever creates a grid retains it beyond every object it handed a reference to. Anything *derived* from a grid inherits this — `~PaddedCell` dereferences its `unpadded_grid`, so a `PaddedCell` cannot even be **destroyed** after its parent grid, only used. Where a consumer must let go early, it offers an explicit hand-back (`MultiGeneralCoarsenedOperatorV2::ReleaseGrid()`) to be called *before* the grid is destroyed.
- The `RealD`/`RealF`/`ComplexD`/`ComplexF` typedefs are used everywhere; avoid raw `double`/`float`.
- Use `GRID_ASSERT(cond)` (defined in `Grid/GridStd.h`), not bare `assert` — it prints a Grid-formatted message and aborts cleanly under MPI.
- Logging is stream-based, not macro-based: `std::cout << GridLogMessage << ... << std::endl;`. Channels declared in `Grid/log/Log.h` include `GridLogError`, `GridLogWarning`, `GridLogDebug`, `GridLogPerformance`, `GridLogIterative`, `GridLogSolver`, `GridLogHMC`, `GridLogComms`, `GridLogMemory`, `GridLogDslash`, `GridLogIRL`, `GridLogMG`. A subset is switched on at runtime with e.g. `--log Error,Warning,Message,Performance,Iterative,Integrator,Debug,Colours` (names given without the `GridLog` prefix).
- Performance-critical paths use `GRID_TRACE(name)` from `Grid/perfmon/Tracing.h` (compiled out unless `--enable-tracing` selects a backend) and the `GridStopWatch` timers in `Grid/perfmon/Timer.h`.
- Reductions across MPI ranks go through `GridBase::GlobalSum` / `GlobalMax`; never reduce with bare MPI calls inside library code.
- Everything lives in `NAMESPACE_BEGIN(Grid)` / `NAMESPACE_END(Grid)` macros; follow the surrounding file rather than writing `namespace Grid { }`.
- British spelling is used in identifiers and comments (`colour`, `neighbour`, `serialisation`).
+2
View File
@@ -51,11 +51,13 @@ directory
#pragma nv_diag_suppress cast_to_qualified_type
//disables nvcc specific warning in many files
#pragma nv_diag_suppress esa_on_defaulted_function_ignored
#pragma nv_diag_suppress declared_but_not_referenced
#pragma nv_diag_suppress extra_semicolon
#else
//disables nvcc specific warning in json.hpp
#pragma diag_suppress unsigned_compare_with_zero
#pragma diag_suppress cast_to_qualified_type
#pragma diag_suppress declared_but_not_referenced
//disables nvcc specific warning in many files
#pragma diag_suppress esa_on_defaulted_function_ignored
#pragma diag_suppress extra_semicolon
+2 -1
View File
@@ -54,7 +54,8 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
#include <Grid/serialisation/Serialisation.h>
#include <Grid/util/Sha.h>
#include <Grid/communicator/Communicator.h>
#include <Grid/cartesian/Cartesian.h>
#include <Grid/communicator/RingAllReduce.h>
#include <Grid/cartesian/Cartesian.h>
#include <Grid/tensors/Tensors.h>
#include <Grid/lattice/Lattice.h>
#include <Grid/cshift/Cshift.h>
+39 -4
View File
@@ -1,9 +1,17 @@
#ifndef GRID_STD_H
#define GRID_STD_H
///////////////////
// Grid config
///////////////////
#include "Config.h"
///////////////////
// Std C++ dependencies
///////////////////
#define _NBACKTRACE (256)
extern void * Grid_backtrace_buffer[_NBACKTRACE];
#include <cassert>
#include <complex>
#include <memory>
@@ -15,7 +23,9 @@
#include <random>
#include <functional>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <stdio.h>
#include <signal.h>
@@ -23,11 +33,36 @@
#include <sys/time.h>
#include <chrono>
#include <zlib.h>
#ifdef HAVE_EXECINFO_H
#include <execinfo.h>
#endif
void GridAbort(void);
#define ASSLOG(A) ::write(STDERR_FILENO,A,::strlen(A));
#ifdef HAVE_EXECINFO_H
#define GRID_ASSERT(b) if(!(b)) { \
fflush(stdout); \
ASSLOG(" GRID_ASSERT failure: "); \
ASSLOG(__FILE__); \
ASSLOG(" : "); \
ASSLOG(#b); \
ASSLOG(" : "); \
int symbols = backtrace(Grid_backtrace_buffer,_NBACKTRACE); \
backtrace_symbols_fd(Grid_backtrace_buffer,symbols,STDERR_FILENO); \
GridAbort(); \
};
#else
#define GRID_ASSERT(b) if(!(b)) { \
ASSLOG(" GRID_ASSERT failure: "); \
ASSLOG(__FILE__); \
ASSLOG(" : "); \
ASSLOG(#b); \
ASSLOG(" : "); \
GridAbort(); \
};
#endif
///////////////////
// Grid config
///////////////////
#include "Config.h"
#ifdef TOFU
#undef GRID_COMMS_THREADS
+11 -7
View File
@@ -54,21 +54,25 @@ Version.h: version-cache
include Make.inc
include Eigen.inc
extra_sources+=$(WILS_FERMION_FILES)
extra_sources+=$(STAG_FERMION_FILES)
if BUILD_FERMION_INSTANTIATIONS
extra_sources+=$(WILS_FERMION_FILES)
extra_sources+=$(STAG_FERMION_FILES)
if BUILD_ZMOBIUS
extra_sources+=$(ZWILS_FERMION_FILES)
extra_sources+=$(ZWILS_FERMION_FILES)
endif
if BUILD_GPARITY
extra_sources+=$(GP_FERMION_FILES)
extra_sources+=$(GP_FERMION_FILES)
endif
if BUILD_FERMION_REPS
extra_sources+=$(ADJ_FERMION_FILES)
extra_sources+=$(TWOIND_FERMION_FILES)
extra_sources+=$(ADJ_FERMION_FILES)
extra_sources+=$(TWOIND_FERMION_FILES)
endif
if BUILD_SP
extra_sources+=$(SP_FERMION_FILES)
extra_sources+=$(SP_TWOIND_FERMION_FILES)
if BUILD_FERMION_REPS
extra_sources+=$(SP_TWOIND_FERMION_FILES)
endif
endif
endif
lib_LIBRARIES = libGrid.a
+1 -1
View File
@@ -29,8 +29,8 @@ directory
#pragma once
#include <type_traits>
#include <cassert>
#include <exception>
#include <cassert>
#define NAMESPACE_BEGIN(A) namespace A {
#define NAMESPACE_END(A) }
+2
View File
@@ -51,6 +51,8 @@ NAMESPACE_CHECK(approx);
#include <Grid/algorithms/deflation/MultiRHSBlockProject.h>
#include <Grid/algorithms/deflation/MultiRHSDeflation.h>
#include <Grid/algorithms/deflation/MultiRHSBlockCGLinalg.h>
// Not really deflation, but useful
#include <Grid/algorithms/blas/MomentumProject.h>
NAMESPACE_CHECK(deflation);
#include <Grid/algorithms/iterative/ConjugateGradient.h>
NAMESPACE_CHECK(ConjGrad);
+648 -241
View File
@@ -1,6 +1,6 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/Cshift.h
@@ -28,6 +28,15 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
#ifndef _GRID_FFT_H_
#define _GRID_FFT_H_
#ifdef GRID_CUDA
#include <cufft.h>
#endif
#ifdef GRID_HIP
#include <hipfft/hipfft.h>
#endif
#if !defined(GRID_CUDA) && !defined(GRID_HIP)
#ifdef HAVE_FFTW
#if defined(USE_MKL) || defined(GRID_SYCL)
#include <fftw/fftw3.h>
@@ -35,266 +44,664 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
#include <fftw3.h>
#endif
#endif
#endif
NAMESPACE_BEGIN(Grid);
template<class scalar> struct FFTW { };
#ifdef HAVE_FFTW
template<> struct FFTW<ComplexD> {
public:
typedef fftw_complex FFTW_scalar;
typedef fftw_plan FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, const int *n,int howmany,
FFTW_scalar *in, const int *inembed,
int istride, int idist,
FFTW_scalar *out, const int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
return ::fftw_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
}
static void fftw_flops(const FFTW_plan p,double *add, double *mul, double *fmas){
::fftw_flops(p,add,mul,fmas);
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out) {
::fftw_execute_dft(p,in,out);
}
inline static void fftw_destroy_plan(const FFTW_plan p) {
::fftw_destroy_plan(p);
}
};
template<> struct FFTW<ComplexF> {
public:
typedef fftwf_complex FFTW_scalar;
typedef fftwf_plan FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, const int *n,int howmany,
FFTW_scalar *in, const int *inembed,
int istride, int idist,
FFTW_scalar *out, const int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
return ::fftwf_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
}
static void fftw_flops(const FFTW_plan p,double *add, double *mul, double *fmas){
::fftwf_flops(p,add,mul,fmas);
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out) {
::fftwf_execute_dft(p,in,out);
}
inline static void fftw_destroy_plan(const FFTW_plan p) {
::fftwf_destroy_plan(p);
}
};
#endif
#ifndef FFTW_FORWARD
#define FFTW_FORWARD (-1)
#define FFTW_BACKWARD (+1)
#define FFTW_ESTIMATE (0)
#endif
class FFT {
private:
GridCartesian *vgrid;
GridCartesian *sgrid;
int Nd;
double flops;
double flops_call;
uint64_t usec;
Coordinate dimensions;
Coordinate processors;
Coordinate processor_coor;
template<class scalar> struct FFTW {
};
#ifdef GRID_HIP
template<> struct FFTW<ComplexD> {
public:
static const int forward=FFTW_FORWARD;
static const int backward=FFTW_BACKWARD;
double Flops(void) {return flops;}
double MFlops(void) {return flops/usec;}
double USec(void) {return (double)usec;}
FFT ( GridCartesian * grid ) :
vgrid(grid),
Nd(grid->_ndimension),
dimensions(grid->_fdimensions),
processors(grid->_processors),
processor_coor(grid->_processor_coor)
{
flops=0;
usec =0;
Coordinate layout(Nd,1);
sgrid = new GridCartesian(dimensions,layout,processors,*grid);
};
~FFT ( void) {
delete sgrid;
}
template<class vobj>
void FFT_dim_mask(Lattice<vobj> &result,const Lattice<vobj> &source,Coordinate mask,int sign){
conformable(result.Grid(),vgrid);
conformable(source.Grid(),vgrid);
Lattice<vobj> tmp(vgrid);
tmp = source;
for(int d=0;d<Nd;d++){
if( mask[d] ) {
FFT_dim(result,tmp,d,sign);
tmp=result;
}
}
}
template<class vobj>
void FFT_all_dim(Lattice<vobj> &result,const Lattice<vobj> &source,int sign){
Coordinate mask(Nd,1);
FFT_dim_mask(result,source,mask,sign);
}
template<class vobj>
void FFT_dim(Lattice<vobj> &result,const Lattice<vobj> &source,int dim, int sign){
#ifndef HAVE_FFTW
std::cerr << "FFTW is not compiled but is called"<<std::endl;
assert(0);
#else
conformable(result.Grid(),vgrid);
conformable(source.Grid(),vgrid);
int L = vgrid->_ldimensions[dim];
int G = vgrid->_fdimensions[dim];
Coordinate layout(Nd,1);
Coordinate pencil_gd(vgrid->_fdimensions);
pencil_gd[dim] = G*processors[dim];
// Pencil global vol LxLxGxLxL per node
GridCartesian pencil_g(pencil_gd,layout,processors,*vgrid);
// Construct pencils
typedef typename vobj::scalar_object sobj;
typedef typename sobj::scalar_type scalar;
Lattice<sobj> pgbuf(&pencil_g);
autoView(pgbuf_v , pgbuf, CpuWrite);
std::cout << "CPU view" << std::endl;
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
typedef typename FFTW<scalar>::FFTW_plan FFTW_plan;
int Ncomp = sizeof(sobj)/sizeof(scalar);
int Nlow = 1;
for(int d=0;d<dim;d++){
Nlow*=vgrid->_ldimensions[d];
}
int rank = 1; /* 1d transforms */
int n[] = {G}; /* 1d transforms of length G */
int howmany = Ncomp;
int odist,idist,istride,ostride;
idist = odist = 1; /* Distance between consecutive FT's */
istride = ostride = Ncomp*Nlow; /* distance between two elements in the same FT */
int *inembed = n, *onembed = n;
scalar div;
if ( sign == backward ) div = 1.0/G;
else if ( sign == forward ) div = 1.0;
else assert(0);
std::cout << GridLogPerformance<<"Making FFTW plan" << std::endl;
typedef hipfftDoubleComplex FFTW_scalar;
typedef hipfftHandle FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
FFTW_plan p;
auto rv = hipfftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,HIPFFT_Z2Z,howmany);
GRID_ASSERT(rv==HIPFFT_SUCCESS);
return p;
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
hipfftResult rv;
if ( sign == forward ) rv =hipfftExecZ2Z(p,in,out,HIPFFT_FORWARD);
else rv =hipfftExecZ2Z(p,in,out,HIPFFT_BACKWARD);
accelerator_barrier();
GRID_ASSERT(rv==HIPFFT_SUCCESS);
}
inline static void fftw_destroy_plan(const FFTW_plan p) { hipfftDestroy(p); }
};
template<> struct FFTW<ComplexF> {
public:
static const int forward=FFTW_FORWARD;
static const int backward=FFTW_BACKWARD;
typedef hipfftComplex FFTW_scalar;
typedef hipfftHandle FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
FFTW_plan p;
auto rv = hipfftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,HIPFFT_C2C,howmany);
GRID_ASSERT(rv==HIPFFT_SUCCESS);
return p;
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
hipfftResult rv;
if ( sign == forward ) rv =hipfftExecC2C(p,in,out,HIPFFT_FORWARD);
else rv =hipfftExecC2C(p,in,out,HIPFFT_BACKWARD);
accelerator_barrier();
GRID_ASSERT(rv==HIPFFT_SUCCESS);
}
inline static void fftw_destroy_plan(const FFTW_plan p) { hipfftDestroy(p); }
};
#endif
#ifdef GRID_CUDA
template<> struct FFTW<ComplexD> {
public:
static const int forward=FFTW_FORWARD;
static const int backward=FFTW_BACKWARD;
typedef cufftDoubleComplex FFTW_scalar;
typedef cufftHandle FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
FFTW_plan p;
cufftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,CUFFT_Z2Z,howmany);
return p;
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
if ( sign == forward ) cufftExecZ2Z(p,in,out,CUFFT_FORWARD);
else cufftExecZ2Z(p,in,out,CUFFT_INVERSE);
accelerator_barrier();
}
inline static void fftw_destroy_plan(const FFTW_plan p) { cufftDestroy(p); }
};
template<> struct FFTW<ComplexF> {
public:
static const int forward=FFTW_FORWARD;
static const int backward=FFTW_BACKWARD;
typedef cufftComplex FFTW_scalar;
typedef cufftHandle FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
FFTW_plan p;
cufftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,CUFFT_C2C,howmany);
return p;
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
if ( sign == forward ) cufftExecC2C(p,in,out,CUFFT_FORWARD);
else cufftExecC2C(p,in,out,CUFFT_INVERSE);
accelerator_barrier();
}
inline static void fftw_destroy_plan(const FFTW_plan p) { cufftDestroy(p); }
};
#endif
#if !defined(GRID_CUDA) && !defined(GRID_HIP)
#ifdef HAVE_FFTW
template<> struct FFTW<ComplexD> {
public:
typedef fftw_complex FFTW_scalar;
typedef fftw_plan FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
return ::fftw_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
::fftw_execute_dft(p,in,out);
}
inline static void fftw_destroy_plan(const FFTW_plan p) { ::fftw_destroy_plan(p); }
};
template<> struct FFTW<ComplexF> {
public:
typedef fftwf_complex FFTW_scalar;
typedef fftwf_plan FFTW_plan;
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
FFTW_scalar *in, int *inembed,
int istride, int idist,
FFTW_scalar *out, int *onembed,
int ostride, int odist,
int sign, unsigned flags) {
return ::fftwf_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
}
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
::fftwf_execute_dft(p,in,out);
}
inline static void fftw_destroy_plan(const FFTW_plan p) { ::fftwf_destroy_plan(p); }
};
#endif
#endif
struct FFTbase {
double flops;
double flops_call;
uint64_t usec;
GridCartesian *_grid;
static const int forward = FFTW_FORWARD;
static const int backward = FFTW_BACKWARD;
double Flops(void) { return flops; }
double MFlops(void) { return flops / usec; }
double USec(void) { return (double)usec; }
FFTbase(GridCartesian *grid) : _grid(grid), flops(0), flops_call(0), usec(0) {}
};
// Barrel-shift gather, FFT execute, and insert. Called by both FFT and PlannedFFT.
// The caller is responsible for plan acquisition and destruction.
template<class vobj>
static void FFT_dim_execute(
Lattice<vobj> &result,
const Lattice<vobj> &source,
int dim, int sign,
typename FFTW<typename vobj::scalar_type>::FFTW_plan p,
GridCartesian *grid,
double &flops, double &flops_call, uint64_t &usec)
{
typedef typename vobj::scalar_type scalar;
typedef typename vobj::scalar_object sobj;
typedef typename vobj::scalar_type scalar_type;
typedef typename vobj::vector_type vector_type;
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
#if 0
// ======================= ORIGINAL barrel-shift path =======================
// Preserved for reference. Superseded by the transpose / all-to-all path
// below (the single active path for ALL P): the barrel is a P-fold redundant
// all-gather -- every rank assembles and transforms all Nperp lines of length
// G, keeping only its own L points. The transpose partitions the Nperp
// perpendicular lines across the P ranks along dim, so each rank transforms
// only ceil(Nperp/P) lines and moves (P-1)/P of its data instead of P-1
// redundant copies. See CartesianRingAllToAll in communicator/RingAllReduce.h.
const int Ndim = grid->Nd();
int L = grid->_ldimensions[dim];
int G = grid->_fdimensions[dim];
int Ncomp = sizeof(sobj) / sizeof(scalar);
int64_t Nlow = 1, Nhigh = 1;
for (int d = 0; d < dim; d++) Nlow *= grid->_ldimensions[d];
for (int d = dim+1; d < Ndim; d++) Nhigh *= grid->_ldimensions[d];
int64_t Nperp = Nlow * Nhigh;
deviceVector<scalar> pgbuf(Nperp * Ncomp * G);
scalar *pgbuf_v = &pgbuf[0];
int howmany = Ncomp * Nperp;
scalar div;
if (sign == FFTW_BACKWARD) div = 1.0 / G;
else if (sign == FFTW_FORWARD) div = 1.0;
else GRID_ASSERT(0);
double t_pencil = 0, t_fft = 0, t_copy = 0, t_shift = 0;
double t_total = -usecond();
result = source;
int pc = grid->_processor_coor[dim];
const Coordinate ldims = grid->_ldimensions;
const Coordinate rdims = grid->_rdimensions;
const Coordinate sdims = grid->_simd_layout;
const Coordinate processors = grid->_processors;
Coordinate pgdims(Ndim);
pgdims[0] = G;
for (int d = 0, dd = 1; d < Ndim; d++)
if (d != dim) pgdims[dd++] = ldims[d];
int64_t pgvol = 1;
for (int d = 0; d < Ndim; d++) pgvol *= pgdims[d];
const int Nsimd = vobj::Nsimd();
t_pencil = -usecond();
for (int p_idx = 0; p_idx < processors[dim]; p_idx++) {
t_copy -= usecond();
autoView(r_v, result, AcceleratorRead);
accelerator_for(idx, grid->oSites(), vobj::Nsimd(), {
#ifdef GRID_SIMT
{
FFTW_scalar *in = (FFTW_scalar *)&pgbuf_v[0];
FFTW_scalar *out= (FFTW_scalar *)&pgbuf_v[0];
p = FFTW<scalar>::fftw_plan_many_dft(rank,n,howmany,
in,inembed,
istride,idist,
out,onembed,
ostride, odist,
sign,FFTW_ESTIMATE);
}
// Barrel shift and collect global pencil
std::cout << GridLogPerformance<<"Making pencil" << std::endl;
Coordinate lcoor(Nd), gcoor(Nd);
result = source;
int pc = processor_coor[dim];
for(int p=0;p<processors[dim];p++) {
{
autoView(r_v,result,CpuRead);
autoView(p_v,pgbuf,CpuWrite);
thread_for(idx, sgrid->lSites(),{
Coordinate cbuf(Nd);
sobj s;
sgrid->LocalIndexToLocalCoor(idx,cbuf);
peekLocalSite(s,r_v,cbuf);
cbuf[dim]+=((pc+p) % processors[dim])*L;
pokeLocalSite(s,p_v,cbuf);
});
}
if (p != processors[dim] - 1) {
result = Cshift(result,dim,L);
int lane = acceleratorSIMTlane(Nsimd);
#else
for (int lane = 0; lane < Nsimd; lane++) {
#endif
Coordinate icoor, ocoor, pgcoor;
Lexicographic::CoorFromIndex(icoor, lane, sdims);
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
pgcoor[0] = ocoor[dim] + icoor[dim]*rdims[dim] + ((pc+p_idx)%processors[dim])*L;
for (int d = 0, dd = 1; d < Ndim; d++)
if (d != dim) { pgcoor[dd] = ocoor[d] + icoor[d]*rdims[d]; dd++; }
int64_t pgidx;
Lexicographic::IndexFromCoor(pgcoor, pgidx, pgdims);
vector_type *from = (vector_type *)&r_v[idx];
scalar_type stmp;
for (int w = 0; w < Ncomp; w++) {
stmp = getlane(from[w], lane);
pgbuf_v[pgidx + w*pgvol] = stmp;
}
#ifdef GRID_SIMT
}
std::cout <<GridLogPerformance<< "Looping orthog" << std::endl;
// Loop over orthog coords
int NN=pencil_g.lSites();
GridStopWatch timer;
timer.Start();
thread_for( idx,NN,{
Coordinate cbuf(Nd);
pencil_g.LocalIndexToLocalCoor(idx, cbuf);
if ( cbuf[dim] == 0 ) { // restricts loop to plane at lcoor[dim]==0
FFTW_scalar *in = (FFTW_scalar *)&pgbuf_v[idx];
FFTW_scalar *out= (FFTW_scalar *)&pgbuf_v[idx];
FFTW<scalar>::fftw_execute_dft(p,in,out);
}
#else
}
#endif
});
timer.Stop();
// performance counting
double add,mul,fma;
FFTW<scalar>::fftw_flops(p,&add,&mul,&fma);
flops_call = add+mul+2.0*fma;
usec += timer.useconds();
flops+= flops_call*NN;
std::cout <<GridLogPerformance<< "Writing back results " << std::endl;
// writing out result
t_copy += usecond();
if (p_idx != processors[dim] - 1) {
Lattice<vobj> temp(grid);
t_shift -= usecond();
temp = Cshift(result, dim, L); result = temp;
t_shift += usecond();
}
}
t_pencil += usecond();
FFTW_scalar *in = (FFTW_scalar *)pgbuf_v;
FFTW_scalar *out = (FFTW_scalar *)pgbuf_v;
t_fft = -usecond();
FFTW<scalar>::fftw_execute_dft(p, in, out, sign);
t_fft += usecond();
flops_call = 5.0 * howmany * G * log2(G);
usec = t_fft;
flops = flops_call;
result = Zero();
double t_insert = -usecond();
{
autoView(r_v, result, AcceleratorWrite);
accelerator_for(idx, grid->oSites(), Nsimd, {
#ifdef GRID_SIMT
{
autoView(pgbuf_v,pgbuf,CpuRead);
autoView(result_v,result,CpuWrite);
thread_for(idx,sgrid->lSites(),{
Coordinate clbuf(Nd), cgbuf(Nd);
sobj s;
sgrid->LocalIndexToLocalCoor(idx,clbuf);
cgbuf = clbuf;
cgbuf[dim] = clbuf[dim]+L*pc;
peekLocalSite(s,pgbuf_v,cgbuf);
pokeLocalSite(s,result_v,clbuf);
int lane = acceleratorSIMTlane(Nsimd);
#else
for (int lane = 0; lane < Nsimd; lane++) {
#endif
Coordinate icoor(Ndim), ocoor(Ndim), pgcoor(Ndim);
Lexicographic::CoorFromIndex(icoor, lane, sdims);
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
pgcoor[0] = ocoor[dim] + icoor[dim]*rdims[dim] + pc*L;
for (int d = 0, dd = 1; d < Ndim; d++)
if (d != dim) { pgcoor[dd] = ocoor[d] + icoor[d]*rdims[d]; dd++; }
int64_t pgidx;
Lexicographic::IndexFromCoor(pgcoor, pgidx, pgdims);
vector_type *to = (vector_type *)&r_v[idx];
scalar_type stmp;
for (int w = 0; w < Ncomp; w++) {
stmp = pgbuf_v[pgidx + w*pgvol];
putlane(to[w], stmp, lane);
}
#ifdef GRID_SIMT
}
#else
}
#endif
});
}
result = result * div;
t_insert += usecond();
t_total += usecond();
std::cout << GridLogPerformance << " FFT took " << t_total/1.0e6 << " s" << std::endl;
std::cout << GridLogPerformance << " FFT pencil " << t_pencil/1.0e6 << " s" << std::endl;
std::cout << GridLogPerformance << " of which copy " << t_copy/1.0e6 << " s" << std::endl;
std::cout << GridLogPerformance << " of which shift" << t_shift/1.0e6 << " s" << std::endl;
std::cout << GridLogPerformance << " FFT kernels " << t_fft/1.0e6 << " s" << std::endl;
std::cout << GridLogPerformance << " FFT insert " << t_insert/1.0e6 << " s" << std::endl;
#endif
// ==================== transpose / all-to-all pencil FFT ====================
//
// P ranks lie along dim; each holds a local L-slab (L=ldim) of every global
// line (length G=fdim=L*P) and Nperp perpendicular lines. We partition the
// Nperp lines across the P ranks: the rank at coord `own` owns the lines with
// olin/Oloc == own, gathers their full G points via a cartesian all-to-all,
// transforms only Oloc = ceil(Nperp/P) of them, then scatters the result back
// with the inverse all-to-all.
//
// The owned-line count is CEIL-padded to a multiple of P (Oloc*P >= Nperp) so
// the all-to-all stays SYMMETRIC (one uniform chunk) for ANY (P,Nperp) -- in
// particular a P carrying a factor absent from Nperp, e.g. P=3 on the T axis
// of 128^3x288 whose perpendicular volume is a pure power of two. This keeps
// the transpose as total over decompositions as the barrel it replaces (no
// new geometry constraint), at a cost of <= (P-1) padded lines out of Nperp.
// Padding slots olin in [Nperp, Oloc*P) are never packed and never unpacked.
//
// The load-bearing identity: the all-to-all block index == cartesian coord
// along dim == which L-slab [c*L, c*L+L) of the global line -- so the block a
// rank receives carries the global-x tag needed to order the FFT input.
//
// Degenerate P: P=1 -> both all-to-alls are self-copies and the reorders are
// the identity (G=L), i.e. a pure local FFT. P=2 -> each all-to-all moves
// half a field, two of them one field, equal to the barrel's single Cshift.
{
const int Ndim = grid->Nd();
int L = grid->_ldimensions[dim];
int G = grid->_fdimensions[dim];
int Ncomp = sizeof(sobj) / sizeof(scalar);
int P = grid->_processors[dim];
int64_t Nperp = 1;
for (int d = 0; d < Ndim; d++)
if (d != dim) Nperp *= grid->_ldimensions[d];
int64_t Oloc = (Nperp + P - 1) / P; // ceil: owned (padded) lines/rank
int64_t chunk = (int64_t)L * Oloc * Ncomp; // one all-to-all block (uniform)
int64_t nbuf = (int64_t)P * chunk; // == Ncomp*Oloc*G, one field's worth
int64_t howmany_local = (int64_t)Ncomp * Oloc;
scalar div;
if (sign == FFTW_BACKWARD) div = 1.0 / G;
else if (sign == FFTW_FORWARD) div = 1.0;
else GRID_ASSERT(0);
double t_total = -usecond();
double t_pack = 0, t_a2a = 0, t_reorder = 0, t_fft = 0, t_unpack = 0;
deviceVector<scalar> sbuf(nbuf);
deviceVector<scalar> rbuf(nbuf);
deviceVector<scalar> pgbuf(nbuf); // FFTW pencil buffer, Ncomp*Oloc lines of G
scalar *sbuf_v = &sbuf[0];
scalar *rbuf_v = &rbuf[0];
scalar *pgbuf_v = &pgbuf[0];
// Pad slots (olin in [Nperp, Oloc*P)) are never packed, so they would carry
// garbage device memory into the FFT. Zero them so the padded lines stay
// finite -- but ONLY when padding is actually present. In the common
// Nperp % P == 0 case pack writes every sbuf entry bijectively, so skip the
// whole-buffer memset and its device sync entirely.
if ( (int64_t)Oloc*P != Nperp )
acceleratorMemSet(sbuf_v, 0, nbuf*sizeof(scalar));
const Coordinate ldims = grid->_ldimensions;
const Coordinate rdims = grid->_rdimensions;
const Coordinate sdims = grid->_simd_layout;
const int Nsimd = vobj::Nsimd();
// ---- 1. pack: source -> sbuf. block = owner coord; payload xloc + L*(slot + Oloc*w)
t_pack -= usecond();
{
autoView(s_v, source, AcceleratorRead);
accelerator_for(idx, grid->oSites(), Nsimd, {
#ifdef GRID_SIMT
{
int lane = acceleratorSIMTlane(Nsimd);
#else
for (int lane = 0; lane < Nsimd; lane++) {
#endif
Coordinate icoor(Ndim), ocoor(Ndim);
Lexicographic::CoorFromIndex(icoor, lane, sdims);
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
int64_t xloc = ocoor[dim] + icoor[dim]*rdims[dim];
int64_t olin = 0, str = 1;
for (int d = 0; d < Ndim; d++) {
if (d == dim) continue;
int64_t c = ocoor[d] + icoor[d]*rdims[d];
olin += str * c;
str *= ldims[d];
}
int64_t own = olin / Oloc;
int64_t slot = olin - own*Oloc;
vector_type *from = (vector_type *)&s_v[idx];
for (int w = 0; w < Ncomp; w++) {
scalar_type stmp = getlane(from[w], lane);
sbuf_v[ own*chunk + xloc + L*(slot + Oloc*w) ] = stmp;
}
#ifdef GRID_SIMT
}
#else
}
#endif
});
}
result = result*div;
std::cout <<GridLogPerformance<< "Destroying plan " << std::endl;
// destroying plan
FFTW<scalar>::fftw_destroy_plan(p);
t_pack += usecond();
// ---- 2. forward all-to-all: gather my owned lines' L-slabs from every rank
t_a2a -= usecond();
CartesianRingAllToAll(grid, sbuf_v, rbuf_v, (uint64_t)chunk, dim);
t_a2a += usecond();
// ---- 3. reorder rbuf -> pgbuf: contiguous G-lines (w,slot), xpos = src*L + xloc
t_reorder -= usecond();
accelerator_for(q, nbuf, 1, {
int64_t xpos = q % G;
int64_t t = q / G; // = w*Oloc + slot
int64_t slot = t % Oloc;
int64_t w = t / Oloc;
int64_t src = xpos / L;
int64_t xloc = xpos % L;
pgbuf_v[q] = rbuf_v[ src*chunk + xloc + L*(slot + Oloc*w) ];
});
t_reorder += usecond();
// ---- 4. FFT: Ncomp*Oloc contiguous lines of length G (istride 1, idist G)
{
FFTW_scalar *in = (FFTW_scalar *)pgbuf_v;
FFTW_scalar *out = (FFTW_scalar *)pgbuf_v;
t_fft -= usecond();
FFTW<scalar>::fftw_execute_dft(p, in, out, sign);
t_fft += usecond();
}
flops_call = 5.0 * (double)howmany_local * G * log2(G);
usec = (uint64_t)t_fft;
flops = flops_call;
// ---- 5a. reorder pgbuf -> sbuf: block = destination coord; xpos = dst*L + xloc
t_reorder -= usecond();
accelerator_for(j, nbuf, 1, {
int64_t dst = j / chunk;
int64_t r = j % chunk;
int64_t xloc = r % L;
int64_t u = r / L; // = slot + Oloc*w
int64_t slot = u % Oloc;
int64_t w = u / Oloc;
int64_t xpos = dst*L + xloc;
sbuf_v[j] = pgbuf_v[ w*Oloc*G + slot*G + xpos ];
});
t_reorder += usecond();
// ---- 5b. inverse all-to-all: scatter transformed L-slabs back
t_a2a -= usecond();
CartesianRingAllToAll(grid, sbuf_v, rbuf_v, (uint64_t)chunk, dim);
t_a2a += usecond();
// ---- 5c. unpack rbuf -> result (x div); block = owner coord of each line
t_unpack -= usecond();
{
autoView(r_v, result, AcceleratorWrite);
accelerator_for(idx, grid->oSites(), Nsimd, {
#ifdef GRID_SIMT
{
int lane = acceleratorSIMTlane(Nsimd);
#else
for (int lane = 0; lane < Nsimd; lane++) {
#endif
Coordinate icoor(Ndim), ocoor(Ndim);
Lexicographic::CoorFromIndex(icoor, lane, sdims);
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
int64_t xloc = ocoor[dim] + icoor[dim]*rdims[dim];
int64_t olin = 0, str = 1;
for (int d = 0; d < Ndim; d++) {
if (d == dim) continue;
int64_t c = ocoor[d] + icoor[d]*rdims[d];
olin += str * c;
str *= ldims[d];
}
int64_t own = olin / Oloc;
int64_t slot = olin - own*Oloc;
vector_type *to = (vector_type *)&r_v[idx];
for (int w = 0; w < Ncomp; w++) {
scalar_type stmp = div * rbuf_v[ own*chunk + xloc + L*(slot + Oloc*w) ];
putlane(to[w], stmp, lane);
}
#ifdef GRID_SIMT
}
#else
}
#endif
});
}
t_unpack += usecond();
t_total += usecond();
std::cout << GridLogPerformance << " FFT took " << t_total/1.0e6 << " s (transpose P=" << P << ")" << std::endl;
std::cout << GridLogPerformance << " FFT pack " << t_pack/1.0e6 << " s" << std::endl;
std::cout << GridLogPerformance << " FFT alltoall " << t_a2a/1.0e6 << " s" << std::endl;
std::cout << GridLogPerformance << " FFT reorder " << t_reorder/1.0e6 << " s" << std::endl;
std::cout << GridLogPerformance << " FFT kernels " << t_fft/1.0e6 << " s" << std::endl;
std::cout << GridLogPerformance << " FFT unpack " << t_unpack/1.0e6 << " s" << std::endl;
}
}
class FFT : public FFTbase {
public:
FFT(GridCartesian *grid) : FFTbase(grid) {}
~FFT() {}
template<class vobj>
void FFT_dim_mask(Lattice<vobj> &result, const Lattice<vobj> &source, Coordinate mask, int sign) {
const int Ndim = _grid->Nd();
Lattice<vobj> tmp = source;
for (int d = 0; d < Ndim; d++) {
if (mask[d]) {
FFT_dim(result, tmp, d, sign);
tmp = result;
}
}
}
template<class vobj>
void FFT_all_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int sign) {
Coordinate mask(_grid->Nd(), 1);
FFT_dim_mask(result, source, mask, sign);
}
template<class vobj>
void FFT_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int dim, int sign) {
GRID_ASSERT(source.Grid() == _grid);
GRID_ASSERT(result.Grid() == _grid);
conformable(result.Grid(), source.Grid());
typedef typename vobj::scalar_type scalar;
typedef typename vobj::scalar_object sobj;
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
typedef typename FFTW<scalar>::FFTW_plan FFTW_plan;
const int Ndim = _grid->Nd();
int G = _grid->_fdimensions[dim];
int Ncomp = sizeof(sobj) / sizeof(scalar);
int64_t Nperp = 1;
for (int d = 0; d < Ndim; d++)
if (d != dim) Nperp *= _grid->_ldimensions[d];
int P = _grid->_processors[dim];
int64_t Oloc = (Nperp + P - 1) / P; // ceil-padded owned lines/rank (see FFT_dim_execute)
int n[] = {G};
int howmany = Ncomp * (int)Oloc;
deviceVector<scalar> dummy(2);
FFTW_scalar *buf = (FFTW_scalar *)&dummy[0];
FFTW_plan p = FFTW<scalar>::fftw_plan_many_dft(1, n, howmany,
buf, n, 1, G,
buf, n, 1, G,
sign, FFTW_ESTIMATE);
FFT_dim_execute(result, source, dim, sign, p, _grid, flops, flops_call, usec);
FFTW<scalar>::fftw_destroy_plan(p);
}
};
template<class vobj>
class PlannedFFT : public FFTbase {
private:
typedef typename vobj::scalar_type scalar;
typedef typename vobj::scalar_object sobj;
typedef typename vobj::vector_type vector_type;
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
typedef typename FFTW<scalar>::FFTW_plan FFTW_plan;
std::vector<FFTW_plan> forward_plans;
std::vector<FFTW_plan> backward_plans;
void PlanCreate() {
const int Ndim = _grid->Nd();
forward_plans.resize(Ndim);
backward_plans.resize(Ndim);
for (int d = 0; d < Ndim; d++) {
int G = _grid->_fdimensions[d];
int Ncomp = sizeof(sobj) / sizeof(scalar);
int64_t Nperp = 1;
for (int dd = 0; dd < Ndim; dd++)
if (dd != d) Nperp *= _grid->_ldimensions[dd];
int P = _grid->_processors[d];
int64_t Oloc = (Nperp + P - 1) / P; // ceil-padded owned lines/rank (see FFT_dim_execute)
int howmany = Ncomp * (int)Oloc;
int n[] = {G};
deviceVector<scalar> dummy(2);
FFTW_scalar *buf = (FFTW_scalar *)&dummy[0];
forward_plans[d] = FFTW<scalar>::fftw_plan_many_dft(1, n, howmany, buf, n, 1, G, buf, n, 1, G, FFTW_FORWARD, FFTW_ESTIMATE);
backward_plans[d] = FFTW<scalar>::fftw_plan_many_dft(1, n, howmany, buf, n, 1, G, buf, n, 1, G, FFTW_BACKWARD, FFTW_ESTIMATE);
}
}
void PlanDestroy() {
for (auto p : forward_plans) FFTW<scalar>::fftw_destroy_plan(p);
for (auto p : backward_plans) FFTW<scalar>::fftw_destroy_plan(p);
forward_plans.clear();
backward_plans.clear();
}
public:
PlannedFFT(GridCartesian *grid) : FFTbase(grid) { PlanCreate(); }
~PlannedFFT() { PlanDestroy(); }
void FFT_dim_mask(Lattice<vobj> &result, const Lattice<vobj> &source, Coordinate mask, int sign) {
const int Ndim = _grid->Nd();
Lattice<vobj> tmp = source;
for (int d = 0; d < Ndim; d++) {
if (mask[d]) {
FFT_dim(result, tmp, d, sign);
tmp = result;
}
}
}
void FFT_all_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int sign) {
Coordinate mask(_grid->Nd(), 1);
FFT_dim_mask(result, source, mask, sign);
}
void FFT_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int dim, int sign) {
GRID_ASSERT(source.Grid() == _grid);
GRID_ASSERT(result.Grid() == _grid);
GRID_ASSERT((int)forward_plans.size() == _grid->Nd());
conformable(result.Grid(), source.Grid());
FFTW_plan p = (sign == forward ? forward_plans : backward_plans)[dim];
FFT_dim_execute(result, source, dim, sign, p, _grid, flops, flops_call, usec);
}
};
+55 -23
View File
@@ -64,7 +64,7 @@ public:
//
// I'm not entirely happy with implementation; to share the Schur code between herm and non-herm
// while still having a "OpAndNorm" in the abstract base I had to implement it in both cases
// with an assert trap in the non-herm. This isn't right; there must be a better C++ way to
// with an GRID_ASSERT trap in the non-herm. This isn't right; there must be a better C++ way to
// do it, but I fear it required multiple inheritance and mixed in abstract base classes
/////////////////////////////////////////////////////////////////////////////////////////////
@@ -148,22 +148,22 @@ public:
// Support for coarsening to a multigrid
void OpDiag (const Field &in, Field &out) {
_Mat.Mdiag(in,out);
assert(0);
GRID_ASSERT(0);
}
void OpDir (const Field &in, Field &out,int dir,int disp) {
_Mat.Mdir(in,out,dir,disp);
assert(0);
GRID_ASSERT(0);
}
void OpDirAll (const Field &in, std::vector<Field> &out){
assert(0);
GRID_ASSERT(0);
};
void Op (const Field &in, Field &out){
_Mat.M(in,out);
assert(0);
GRID_ASSERT(0);
}
void AdjOp (const Field &in, Field &out){
_Mat.Mdag(in,out);
assert(0);
GRID_ASSERT(0);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
HermOp(in,out);
@@ -188,13 +188,13 @@ public:
ShiftedHermOpLinearOperator(LinearOperatorBase<Field> &Mat,RealD shift): _Mat(Mat), _shift(shift){};
// Support for coarsening to a multigrid
void OpDiag (const Field &in, Field &out) {
assert(0);
GRID_ASSERT(0);
}
void OpDir (const Field &in, Field &out,int dir,int disp) {
assert(0);
GRID_ASSERT(0);
}
void OpDirAll (const Field &in, std::vector<Field> &out){
assert(0);
GRID_ASSERT(0);
};
void Op (const Field &in, Field &out){
HermOp(in,out);
@@ -271,10 +271,42 @@ public:
_Mat.Mdag(in,out);
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
assert(0);
GRID_ASSERT(0);
}
void HermOp(const Field &in, Field &out){
assert(0);
GRID_ASSERT(0);
}
};
template<class Matrix,class Field>
class ShiftedNonHermitianLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat;
RealD shift;
public:
ShiftedNonHermitianLinearOperator(Matrix &Mat,RealD shft): _Mat(Mat),shift(shft){};
// Support for coarsening to a multigrid
void OpDiag (const Field &in, Field &out) {
_Mat.Mdiag(in,out);
out = out + shift*in;
}
void OpDir (const Field &in, Field &out,int dir,int disp) {
_Mat.Mdir(in,out,dir,disp);
}
void OpDirAll (const Field &in, std::vector<Field> &out){
_Mat.MdirAll(in,out);
};
void Op (const Field &in, Field &out){
_Mat.M(in,out);
out = out + shift * in;
}
void AdjOp (const Field &in, Field &out){
_Mat.Mdag(in,out);
out = out + shift * in;
}
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
GRID_ASSERT(0);
}
void HermOp(const Field &in, Field &out){
GRID_ASSERT(0);
}
};
@@ -313,13 +345,13 @@ class SchurOperatorBase : public LinearOperatorBase<Field> {
}
// Support for coarsening to a multigrid
void OpDiag (const Field &in, Field &out) {
assert(0); // must coarsen the unpreconditioned system
GRID_ASSERT(0); // must coarsen the unpreconditioned system
}
void OpDir (const Field &in, Field &out,int dir,int disp) {
assert(0);
GRID_ASSERT(0);
}
void OpDirAll (const Field &in, std::vector<Field> &out){
assert(0);
GRID_ASSERT(0);
};
};
template<class Matrix,class Field>
@@ -415,10 +447,10 @@ class NonHermitianSchurOperatorBase : public LinearOperatorBase<Field>
MpcDag(tmp,out);
}
virtual void HermOpAndNorm(const Field& in, Field& out, RealD& n1, RealD& n2) {
assert(0);
GRID_ASSERT(0);
}
virtual void HermOp(const Field& in, Field& out) {
assert(0);
GRID_ASSERT(0);
}
void Op(const Field& in, Field& out) {
Mpc(in, out);
@@ -428,13 +460,13 @@ class NonHermitianSchurOperatorBase : public LinearOperatorBase<Field>
}
// Support for coarsening to a multigrid
void OpDiag(const Field& in, Field& out) {
assert(0); // must coarsen the unpreconditioned system
GRID_ASSERT(0); // must coarsen the unpreconditioned system
}
void OpDir(const Field& in, Field& out, int dir, int disp) {
assert(0);
GRID_ASSERT(0);
}
void OpDirAll(const Field& in, std::vector<Field>& out){
assert(0);
GRID_ASSERT(0);
};
};
@@ -548,7 +580,7 @@ class SchurStaggeredOperator : public SchurOperatorBase<Field> {
public:
SchurStaggeredOperator (Matrix &Mat): _Mat(Mat), tmp(_Mat.RedBlackGrid())
{
assert( _Mat.isTrivialEE() );
GRID_ASSERT( _Mat.isTrivialEE() );
mass = _Mat.Mass();
}
virtual void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
@@ -579,7 +611,7 @@ class SchurStaggeredOperator : public SchurOperatorBase<Field> {
Mpc(in,out);
}
virtual void MpcDagMpc(const Field &in, Field &out) {
assert(0);// Never need with staggered
GRID_ASSERT(0);// Never need with staggered
}
};
template<class Matrix,class Field> using SchurStagOperator = SchurStaggeredOperator<Matrix,Field>;
@@ -591,7 +623,7 @@ template<class Field> class OperatorFunction {
public:
virtual void operator() (LinearOperatorBase<Field> &Linop, const Field &in, Field &out) = 0;
virtual void operator() (LinearOperatorBase<Field> &Linop, const std::vector<Field> &in,std::vector<Field> &out) {
assert(in.size()==out.size());
GRID_ASSERT(in.size()==out.size());
for(int k=0;k<in.size();k++){
(*this)(Linop,in[k],out[k]);
}
@@ -605,7 +637,7 @@ public:
virtual void operator() (const std::vector<Field> &in, std::vector<Field> &out)
{
assert(in.size() == out.size());
GRID_ASSERT(in.size() == out.size());
for (unsigned int i = 0; i < in.size(); ++i)
{
+2
View File
@@ -269,7 +269,9 @@ public:
RealD xscale = 2.0/(hi-lo);
RealD mscale = -(hi+lo)/(hi-lo);
Linop.HermOp(T0,y);
grid->Barrier();
axpby(T1,xscale,mscale,y,in);
grid->Barrier();
// sum = .5 c[0] T0 + c[1] T1
// out = ()*T0 + Coeffs[1]*T1;
+3 -3
View File
@@ -121,7 +121,7 @@ double AlgRemez::generateApprox(int num_degree, int den_degree,
// Reallocate arrays, since degree has changed
if (num_degree != n || den_degree != d) allocate(num_degree,den_degree);
assert(a_len<=SUM_MAX);
GRID_ASSERT(a_len<=SUM_MAX);
step = new bigfloat[num_degree+den_degree+2];
@@ -151,9 +151,9 @@ double AlgRemez::generateApprox(int num_degree, int den_degree,
equations();
if (delta < tolerance) {
std::cout<<"Delta too small, try increasing precision\n";
assert(0);
GRID_ASSERT(0);
};
assert( delta>= tolerance);
GRID_ASSERT( delta>= tolerance);
search(step);
}
+1 -1
View File
@@ -134,7 +134,7 @@ class AlgRemez
virtual ~AlgRemez();
int getDegree(void){
assert(n==d);
GRID_ASSERT(n==d);
return n;
}
// Reset the bounds of the approximation
+8 -8
View File
@@ -28,11 +28,11 @@ void AlgRemezGeneral::setupPolyProperties(int num_degree, int den_degree, PolyTy
pow_n = num_degree;
pow_d = den_degree;
if(pow_n % 2 == 0 && num_type_in == PolyType::Odd) assert(0);
if(pow_n % 2 == 1 && num_type_in == PolyType::Even) assert(0);
if(pow_n % 2 == 0 && num_type_in == PolyType::Odd) GRID_ASSERT(0);
if(pow_n % 2 == 1 && num_type_in == PolyType::Even) GRID_ASSERT(0);
if(pow_d % 2 == 0 && den_type_in == PolyType::Odd) assert(0);
if(pow_d % 2 == 1 && den_type_in == PolyType::Even) assert(0);
if(pow_d % 2 == 0 && den_type_in == PolyType::Odd) GRID_ASSERT(0);
if(pow_d % 2 == 1 && den_type_in == PolyType::Even) GRID_ASSERT(0);
num_type = num_type_in;
den_type = den_type_in;
@@ -112,9 +112,9 @@ double AlgRemezGeneral::generateApprox(const int num_degree, const int den_degre
equations();
if (delta < tolerance) {
std::cout<<"Iteration " << iter-1 << " delta too small (" << delta << "<" << tolerance << "), try increasing precision\n";
assert(0);
GRID_ASSERT(0);
};
assert( delta>= tolerance );
GRID_ASSERT( delta>= tolerance );
search();
}
@@ -278,7 +278,7 @@ void AlgRemezGeneral::equations(){
if(num_pows[j] != -1){ *aa++ = z; t++; }
z *= x;
}
assert(t == n+1);
GRID_ASSERT(t == n+1);
z = (bigfloat)1l;
t = 0;
@@ -286,7 +286,7 @@ void AlgRemezGeneral::equations(){
if(den_pows[j] != -1){ *aa++ = -y * z; t++; }
z *= x;
}
assert(t == d);
GRID_ASSERT(t == d);
B[i] = y * z; // Right hand side vector
}
+1 -1
View File
@@ -106,7 +106,7 @@ class AlgRemezGeneral{
bigfloat (*f)(bigfloat x, void *data), void *data);
inline int getDegree(void) const{
assert(n==d);
GRID_ASSERT(n==d);
return n;
}
// Reset the bounds of the approximation
+1 -1
View File
@@ -74,7 +74,7 @@ bigfloat epsilonMobius(bigfloat x, void* data){
void computeZmobiusOmega(std::vector<ComplexD> &omega_out, const int Ls_out,
const std::vector<RealD> &omega_in, const int Ls_in,
const RealD lambda_bound){
assert(omega_in.size() == Ls_in);
GRID_ASSERT(omega_in.size() == Ls_in);
omega_out.resize(Ls_out);
//Use the Remez algorithm to generate the appropriate rational polynomial
File diff suppressed because it is too large Load Diff
+329
View File
@@ -0,0 +1,329 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: BatchedInverse.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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
#include <Grid/algorithms/blas/BatchedBlas.h>
#ifdef GRID_HIP
#include <rocsolver/rocsolver.h>
#endif
// GRID_CUDA: batched LU inversion lives in cuBLAS (getrfBatched/getriBatched);
// cublas_v2.h already included via BatchedBlas.h.
// GRID_SYCL: oneapi/mkl.hpp already included via BatchedBlas.h (lapack::getrf/getri).
NAMESPACE_BEGIN(Grid);
///////////////////////////////////////////////////////////////////////////////
// GridBLASInverse: cross-platform batched dense matrix inversion.
//
// HIGH LEVEL contract (deliberately NOT a getrf/getrs interface): invert a
// batch of dense N x N matrices IN PLACE,
//
// A[i] <- A[i]^{-1} i = 0 .. batchCount-1
//
// Layout: column major, lda = N, contiguous per batch element; pointer list
// exactly as GridBLAS::gemmBatched (deviceVector<T*> of device pointers).
// Each backend chooses HOW:
// HIP : rocSOLVER getrf_batched + getri_batched
// CUDA : cuBLAS getrfBatched + getriBatched (out-of-place getri; workspace
// hidden here, result copied back so the surface stays in-place)
// SYCL : oneMKL LAPACK getrf + getri per batch element (USM, in-order queue)
// CPU : Eigen PartialPivLU (the correctness oracle for all of the above)
//
// The int32 vendor-batched entry points bound N < 2^31 (asserted); the huge
// single-matrix ILP64 path (getrf_64 + blocked identity-getrs harvest, proven
// in the dense coarse-coarse setup at N=69120) migrates here as a batch==1
// large-N dispatch in a follow-up -- the recursive Schur leaves are the
// batched consumers this surface is shaped for.
//
// NB GPU-backend call signatures are written to vendor documentation but the
// air-gapped development loop compiles only the CPU/Eigen path; verify the
// rocSOLVER/cuBLAS/oneMKL calls against headers on first device compile.
// Semantics are locked by the CPU unit test (Test_batched_blas).
///////////////////////////////////////////////////////////////////////////////
class GridBLASInverse {
public:
#ifdef GRID_HIP
// rocSOLVER runs on a rocblas_handle (distinct type from hipblasHandle_t)
static rocblas_handle & Handle(void) {
static rocblas_handle h;
static int init = 0;
if ( !init ) {
auto st = rocblas_create_handle(&h);
GRID_ASSERT(st == rocblas_status_success);
init = 1;
}
return h;
}
#endif
#ifdef GRID_CUDA
// cuBLAS batched LU shares the GridBLAS handle
static cublasHandle_t & Handle(void) {
GridBLAS::Init();
return GridBLAS::gridblasHandle;
}
#endif
#ifdef GRID_SYCL
static sycl::queue * & Handle(void) {
GridBLAS::Init();
return GridBLAS::gridblasHandle;
}
#endif
GridBLASInverse() {};
~GridBLASInverse() {};
///////////////////////////////////////////////////////////////////////////
// SINGLE large matrix, in place: blocked LU + identity solve.
// HIP : rocsolver_zgetrf_64 (blocked, GEMM-based) then ONE zgetrs_64 with
// the N x N identity as RHS (trsm-based) into a scratch N x N, copied
// back. Extra device memory: N*N*16 B (1.2 GB at N=8640).
// other backends: falls through to inverseBatched(batch 1).
// Why: rocSOLVER getri_batched is a small-matrix routine -- measured 0.35 s
// at N=1920, 0.53 s at 4320, 1.84 s at 8640 (n^1.2-1.8, i.e. overhead
// bound) as the big-leaf inverse of the 2D Schur recursion, where 287
// ranks wait on it. The _64 getrf/getrs pair is the path proven in the
// 1-rank dense coarse-coarse setup at N=69120 (DenseCoarseMatrix.h).
// Same in-place, column-major, lda=N contract as inverseBatched.
// NB: written to rocSOLVER's documented z*_64 signatures; not compiled on
// HIP in the air-gapped loop -- verify on first hipcc build.
///////////////////////////////////////////////////////////////////////////
double lastGetrfUs = 0.0, lastGetrsUs = 0.0; // split timing of the last inverseLU (HIP path)
void inverseLU(int64_t N, ComplexD *A)
{
#ifdef GRID_HIP
rocblas_handle handle = Handle();
deviceVector<int64_t> ipiv((uint64_t)N);
deviceVector<int64_t> info(1);
double t0 = usecond();
auto st1 = rocsolver_zgetrf_64(handle, N, N, (rocblas_double_complex *)A, N, &ipiv[0], &info[0]);
GRID_ASSERT(st1 == rocblas_status_success);
accelerator_barrier();
lastGetrfUs = usecond()-t0;
int64_t info_h = -1; acceleratorCopyFromDevice(&info[0], &info_h, sizeof(int64_t));
GRID_ASSERT(info_h == 0);
deviceVector<ComplexD> X((uint64_t)N*N);
{ ComplexD *x = &X[0]; const int64_t NN = N;
accelerator_for(idx, (uint64_t)N*N, 1, { int64_t j = idx/NN, i = idx - j*NN; x[idx] = (i==j) ? ComplexD(1.0,0.0) : ComplexD(0.0,0.0); });
accelerator_barrier(); }
auto st2 = rocsolver_zgetrs_64(handle, rocblas_operation_none, N, N,
(rocblas_double_complex *)A, N, &ipiv[0],
(rocblas_double_complex *)&X[0], N);
GRID_ASSERT(st2 == rocblas_status_success);
accelerator_barrier();
lastGetrsUs = usecond()-t0-lastGetrfUs;
acceleratorCopyDeviceToDevice((void *)&X[0], (void *)A, (uint64_t)N*N*sizeof(ComplexD));
#else
deviceVector<ComplexD*> bp(1); std::vector<ComplexD*> ptr(1); ptr[0] = A;
acceleratorCopyToDevice(&ptr[0], &bp[0], sizeof(ComplexD*));
inverseBatched(N, bp);
#endif
}
void inverseBatched(int64_t N, deviceVector<ComplexF*> &Amat)
{
int32_t batchCount = Amat.size();
GRID_ASSERT(batchCount > 0);
#ifdef GRID_HIP
GRID_ASSERT( N < 2147483647L );
rocblas_int n = (rocblas_int)N;
rocblas_int lda = (rocblas_int)N;
deviceVector<rocblas_int> ipiv((uint64_t)batchCount*N);
deviceVector<rocblas_int> info(batchCount);
auto st1 = rocsolver_cgetrf_batched(Handle(), n, n,
(rocblas_float_complex *const *)&Amat[0], lda,
&ipiv[0], (rocblas_stride)N,
&info[0], batchCount);
GRID_ASSERT(st1 == rocblas_status_success);
auto st2 = rocsolver_cgetri_batched(Handle(), n,
(rocblas_float_complex *const *)&Amat[0], lda,
&ipiv[0], (rocblas_stride)N,
&info[0], batchCount);
GRID_ASSERT(st2 == rocblas_status_success);
accelerator_barrier();
std::vector<rocblas_int> info_h(batchCount);
acceleratorCopyFromDevice(&info[0],&info_h[0],batchCount*sizeof(rocblas_int));
for(int i=0;i<batchCount;i++) GRID_ASSERT(info_h[i]==0); // singular pivot => abort loudly
#endif
#ifdef GRID_CUDA
GRID_ASSERT( N < 2147483647L );
int n = (int)N;
deviceVector<int> ipiv((uint64_t)batchCount*N);
deviceVector<int> info(batchCount);
auto st1 = cublasCgetrfBatched(Handle(), n,
(cuComplex **)&Amat[0], n,
&ipiv[0], &info[0], batchCount);
GRID_ASSERT(st1 == CUBLAS_STATUS_SUCCESS);
// getri is OUT of place: hidden workspace keeps the surface in-place
deviceVector<ComplexF> work((uint64_t)batchCount*N*N);
deviceVector<ComplexF*> Cptr(batchCount);
std::vector<ComplexF*> Cptr_h(batchCount);
std::vector<ComplexF*> Aptr_h(batchCount);
for(int i=0;i<batchCount;i++) Cptr_h[i] = &work[(uint64_t)i*N*N];
acceleratorCopyToDevice(&Cptr_h[0],&Cptr[0],batchCount*sizeof(ComplexF*));
acceleratorCopyFromDevice(&Amat[0],&Aptr_h[0],batchCount*sizeof(ComplexF*));
auto st2 = cublasCgetriBatched(Handle(), n,
(const cuComplex *const *)&Amat[0], n,
&ipiv[0],
(cuComplex **)&Cptr[0], n,
&info[0], batchCount);
GRID_ASSERT(st2 == CUBLAS_STATUS_SUCCESS);
accelerator_barrier();
std::vector<int> info_h(batchCount);
acceleratorCopyFromDevice(&info[0],&info_h[0],batchCount*sizeof(int));
for(int i=0;i<batchCount;i++) GRID_ASSERT(info_h[i]==0);
for(int i=0;i<batchCount;i++)
acceleratorCopyDeviceToDevice(Cptr_h[i],Aptr_h[i],(uint64_t)N*N*sizeof(ComplexF));
#endif
#ifdef GRID_SYCL
// Per-element oneMKL LAPACK on the in-order queue; group API optimisation later.
sycl::queue *q = Handle();
std::vector<ComplexF*> Aptr_h(batchCount);
acceleratorCopyFromDevice(&Amat[0],&Aptr_h[0],batchCount*sizeof(ComplexF*));
int64_t lwf = oneapi::mkl::lapack::getrf_scratchpad_size<std::complex<float> >(*q,N,N,N);
int64_t lwi = oneapi::mkl::lapack::getri_scratchpad_size<std::complex<float> >(*q,N,N);
deviceVector<ComplexF> scratchf(lwf);
deviceVector<ComplexF> scratchi(lwi);
deviceVector<int64_t> ipiv(N);
for(int i=0;i<batchCount;i++){
oneapi::mkl::lapack::getrf(*q,N,N,(std::complex<float>*)Aptr_h[i],N,&ipiv[0],
(std::complex<float>*)&scratchf[0],lwf);
oneapi::mkl::lapack::getri(*q,N, (std::complex<float>*)Aptr_h[i],N,&ipiv[0],
(std::complex<float>*)&scratchi[0],lwi);
}
q->wait();
#endif
#if !defined(GRID_SYCL) && !defined(GRID_CUDA) && !defined(GRID_HIP)
// Reference implementation; the oracle the unit test locks semantics with.
thread_for (p, batchCount, {
Eigen::Map<Eigen::MatrixXcf> eA(Amat[p],N,N);
Eigen::PartialPivLU<Eigen::MatrixXcf> lu(eA);
eA = lu.inverse();
});
#endif
}
void inverseBatched(int64_t N, deviceVector<ComplexD*> &Amat)
{
int32_t batchCount = Amat.size();
GRID_ASSERT(batchCount > 0);
#ifdef GRID_HIP
GRID_ASSERT( N < 2147483647L );
rocblas_int n = (rocblas_int)N;
rocblas_int lda = (rocblas_int)N;
deviceVector<rocblas_int> ipiv((uint64_t)batchCount*N);
deviceVector<rocblas_int> info(batchCount);
auto st1 = rocsolver_zgetrf_batched(Handle(), n, n,
(rocblas_double_complex *const *)&Amat[0], lda,
&ipiv[0], (rocblas_stride)N,
&info[0], batchCount);
GRID_ASSERT(st1 == rocblas_status_success);
auto st2 = rocsolver_zgetri_batched(Handle(), n,
(rocblas_double_complex *const *)&Amat[0], lda,
&ipiv[0], (rocblas_stride)N,
&info[0], batchCount);
GRID_ASSERT(st2 == rocblas_status_success);
accelerator_barrier();
std::vector<rocblas_int> info_h(batchCount);
acceleratorCopyFromDevice(&info[0],&info_h[0],batchCount*sizeof(rocblas_int));
for(int i=0;i<batchCount;i++) GRID_ASSERT(info_h[i]==0);
#endif
#ifdef GRID_CUDA
GRID_ASSERT( N < 2147483647L );
int n = (int)N;
deviceVector<int> ipiv((uint64_t)batchCount*N);
deviceVector<int> info(batchCount);
auto st1 = cublasZgetrfBatched(Handle(), n,
(cuDoubleComplex **)&Amat[0], n,
&ipiv[0], &info[0], batchCount);
GRID_ASSERT(st1 == CUBLAS_STATUS_SUCCESS);
deviceVector<ComplexD> work((uint64_t)batchCount*N*N);
deviceVector<ComplexD*> Cptr(batchCount);
std::vector<ComplexD*> Cptr_h(batchCount);
std::vector<ComplexD*> Aptr_h(batchCount);
for(int i=0;i<batchCount;i++) Cptr_h[i] = &work[(uint64_t)i*N*N];
acceleratorCopyToDevice(&Cptr_h[0],&Cptr[0],batchCount*sizeof(ComplexD*));
acceleratorCopyFromDevice(&Amat[0],&Aptr_h[0],batchCount*sizeof(ComplexD*));
auto st2 = cublasZgetriBatched(Handle(), n,
(const cuDoubleComplex *const *)&Amat[0], n,
&ipiv[0],
(cuDoubleComplex **)&Cptr[0], n,
&info[0], batchCount);
GRID_ASSERT(st2 == CUBLAS_STATUS_SUCCESS);
accelerator_barrier();
std::vector<int> info_h(batchCount);
acceleratorCopyFromDevice(&info[0],&info_h[0],batchCount*sizeof(int));
for(int i=0;i<batchCount;i++) GRID_ASSERT(info_h[i]==0);
for(int i=0;i<batchCount;i++)
acceleratorCopyDeviceToDevice(Cptr_h[i],Aptr_h[i],(uint64_t)N*N*sizeof(ComplexD));
#endif
#ifdef GRID_SYCL
sycl::queue *q = Handle();
std::vector<ComplexD*> Aptr_h(batchCount);
acceleratorCopyFromDevice(&Amat[0],&Aptr_h[0],batchCount*sizeof(ComplexD*));
int64_t lwf = oneapi::mkl::lapack::getrf_scratchpad_size<std::complex<double> >(*q,N,N,N);
int64_t lwi = oneapi::mkl::lapack::getri_scratchpad_size<std::complex<double> >(*q,N,N);
deviceVector<ComplexD> scratchf(lwf);
deviceVector<ComplexD> scratchi(lwi);
deviceVector<int64_t> ipiv(N);
for(int i=0;i<batchCount;i++){
oneapi::mkl::lapack::getrf(*q,N,N,(std::complex<double>*)Aptr_h[i],N,&ipiv[0],
(std::complex<double>*)&scratchf[0],lwf);
oneapi::mkl::lapack::getri(*q,N, (std::complex<double>*)Aptr_h[i],N,&ipiv[0],
(std::complex<double>*)&scratchi[0],lwi);
}
q->wait();
#endif
#if !defined(GRID_SYCL) && !defined(GRID_CUDA) && !defined(GRID_HIP)
thread_for (p, batchCount, {
Eigen::Map<Eigen::MatrixXcd> eA(Amat[p],N,N);
Eigen::PartialPivLU<Eigen::MatrixXcd> lu(eA);
eA = lu.inverse();
});
#endif
}
};
NAMESPACE_END(Grid);
+300
View File
@@ -0,0 +1,300 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: MomentumProject.h
Copyright (C) 2025
Author: Peter Boyle <pboyle@bnl.gov>
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);
/*
MultiMomProject
Import vectors -> nxyz x (ncomponent x nt)
Import complex phases -> nmom x nxy
apply = via (possibly batched) GEMM
*/
template<class Field, class ComplexField>
class MomentumProject
{
public:
typedef typename Field::scalar_type scalar;
typedef typename Field::scalar_object scalar_object;
GridBase *grid;
uint64_t nmom;
uint64_t nxyz;
uint64_t nt;
uint64_t nbtw;
uint64_t words;
deviceVector<scalar> BLAS_V; //
deviceVector<scalar> BLAS_M; //
deviceVector<scalar> BLAS_P; //
MomentumProject(){};
~MomentumProject(){ Deallocate(); };
void Deallocate(void)
{
grid=nullptr;
nmom=0;
nxyz=0;
nt=0;
nbtw=0;
words=0;
BLAS_V.resize(0);
BLAS_M.resize(0);
BLAS_P.resize(0);
}
void Allocate(int _nmom,GridBase *_grid)
{
grid=_grid;
Coordinate ldims = grid->LocalDimensions();
nmom=_nmom;
nt = ldims[grid->Nd()-1];
nxyz = grid->lSites()/nt;
words = sizeof(scalar_object)/sizeof(scalar);
nbtw = nt * words;
BLAS_V.resize (nxyz * nt * words );
BLAS_M.resize (nmom * nxyz );
BLAS_P.resize (nmom * nt * words );
}
void ImportMomenta(const std::vector <ComplexField> &momenta)
{
GRID_ASSERT(momenta.size()==nmom);
// might as well just make the momenta here
typedef typename Field::vector_object vobj;
int nd = grid->_ndimension;
uint64_t sz = BLAS_M.size();
GRID_ASSERT(momenta.size()==nmom)
GRID_ASSERT(momenta[0].Grid()==grid);
GRID_ASSERT(sz = nxyz * nmom);
Coordinate rdimensions = grid->_rdimensions;
Coordinate ldims = grid->LocalDimensions();
int64_t osites = grid->oSites();
Coordinate simd = grid->_simd_layout;
const int Nsimd = vobj::Nsimd();
uint64_t lwords = words; // local variable for copy in to GPU
int64_t Nxyz = nxyz;
auto blasData_p = &BLAS_M[0];
for(int m=0;m<momenta.size();m++){
autoView( Data , momenta[m], AcceleratorRead);
auto Data_p = &Data[0];
accelerator_for(xyz,nxyz,1,{
//////////////////////////////////////////
// isite -- map lane within buffer to lane within lattice
////////////////////////////////////////////
Coordinate lcoor(nd,0);
Lexicographic::CoorFromIndex(lcoor,xyz,ldims);
Coordinate icoor(nd);
Coordinate ocoor(nd);
for (int d = 0; d < nd; d++) {
icoor[d] = lcoor[d]/rdimensions[d];
ocoor[d] = lcoor[d]%rdimensions[d];
}
int64_t osite;
int64_t isite;
Lexicographic::IndexFromCoor(ocoor,osite,rdimensions);
Lexicographic::IndexFromCoor(icoor,isite,simd);
// BLAS_M[nmom][slice_vol]
// Fortran Column major BLAS layout is M_xyz,mom
scalar data = extractLane(isite,Data[osite]);
uint64_t idx = xyz+m*Nxyz;
blasData_p[idx] = data;
});
}
}
void ImportVector(Field &vec)
{
typedef typename Field::vector_object vobj;
int nd = grid->_ndimension;
uint64_t sz = BLAS_V.size();
GRID_ASSERT(sz = nxyz * words * nt);
Coordinate rdimensions = grid->_rdimensions;
Coordinate ldims= grid->LocalDimensions();
int64_t osites = grid->oSites();
Coordinate simd = grid->_simd_layout;
const int Nsimd = vobj::Nsimd();
uint64_t lwords= words; // local variable for copy in to GPU
auto blasData_p = &BLAS_V[0];
autoView( Data , vec, AcceleratorRead);
auto Data_p = &Data[0];
int64_t nwords = words;// for capture
int64_t Nt = nt;// for capture
accelerator_for(sf,osites,Nsimd,{
#ifdef GRID_SIMT
{
int lane=acceleratorSIMTlane(Nsimd); // buffer lane
#else
for(int lane=0;lane<Nsimd;lane++) {
#endif
//////////////////////////////////////////
// isite -- map lane within buffer to lane within lattice
////////////////////////////////////////////
Coordinate lcoor(nd,0);
Coordinate icoor(nd);
Coordinate ocoor(nd);
Lexicographic::CoorFromIndex(icoor,lane,simd);
Lexicographic::CoorFromIndex(ocoor,sf,rdimensions);
int64_t l_xyz = 0;
for (int d = 0; d < nd; d++) {
lcoor[d] = rdimensions[d]*icoor[d] + ocoor[d];
}
uint64_t l_t = lcoor[nd-1];
Coordinate xyz_coor = lcoor;
xyz_coor[nd-1] =0;
Lexicographic::IndexFromCoor(xyz_coor,l_xyz,ldims);
scalar_object data = extractLane(lane,Data[sf]);
scalar *data_words = (scalar *) &data;
for(int w = 0 ; w < nwords; w++) {
// BLAS_V[slice_vol][nt][words]
// Fortran Column major BLAS layout is V_(t,w)_xyz
uint64_t idx = w+l_t*nwords + l_xyz * nwords * Nt;
blasData_p[idx] = data_words[w];
}
#ifdef GRID_SIMT
}
#else
}
#endif
});
}
void ExportMomentumProjection(std::vector<typename Field::scalar_object> &projection)
{
projection.resize(nmom*nt);
acceleratorCopyFromDevice(&BLAS_P[0],(scalar *)&projection[0],BLAS_P.size()*sizeof(scalar));
// Could decide on a layout late?
}
// Row major layout "C" order:
// BLAS_V[slice_vol][nt][words]
// BLAS_M[nmom][slice_vol]
// BLAS_P[nmom][nt][words]
//
// Fortran Column major BLAS layout is V_(w,t)_xyz
// Fortran Column major BLAS layout is M_xyz,mom
// Fortran Column major BLAS layout is P_(w,t),mom
//
// Projected
//
// P = (V * M)_(w,t),mom
//
void Project(Field &data,std::vector< typename Field::scalar_object > & projected_gdata)
{
double t_import=0;
double t_export=0;
double t_gemm =0;
double t_allreduce=0;
t_import-=usecond();
this->ImportVector(data);
std::vector< typename Field::scalar_object > projected_planes;
deviceVector<scalar *> Vd(1);
deviceVector<scalar *> Md(1);
deviceVector<scalar *> Pd(1);
scalar * Vh = & BLAS_V[0];
scalar * Mh = & BLAS_M[0];
scalar * Ph = & BLAS_P[0];
acceleratorPut(Vd[0],Vh);
acceleratorPut(Md[0],Mh);
acceleratorPut(Pd[0],Ph);
t_import+=usecond();
GridBLAS BLAS;
/////////////////////////////////////////
// P_im = VMmx . Vxi
/////////////////////////////////////////
t_gemm-=usecond();
BLAS.gemmBatched(GridBLAS_OP_N,GridBLAS_OP_N,
words*nt,nmom,nxyz,
scalar(1.0),
Vd,
Md,
scalar(0.0), // wipe out result
Pd);
BLAS.synchronise();
t_gemm+=usecond();
t_export-=usecond();
ExportMomentumProjection(projected_planes); // resizes
t_export+=usecond();
/////////////////////////////////
// Reduce across MPI ranks
/////////////////////////////////
int nd = grid->Nd();
int gt = grid->GlobalDimensions()[nd-1];
int lt = grid->LocalDimensions()[nd-1];
projected_gdata.resize(gt*nmom);
for(int t=0;t<gt*nmom;t++){ // global Nt array with zeroes for stuff not on this node
projected_gdata[t]=Zero();
}
for(int t=0;t<lt;t++){
for(int m=0;m<nmom;m++){
int st = grid->LocalStarts()[nd-1];
projected_gdata[t+st + gt*m] = projected_planes[t+lt*m];
}}
t_allreduce-=usecond();
grid->GlobalSumVector((scalar *)&projected_gdata[0],gt*nmom*words);
t_allreduce+=usecond();
std::cout << GridLogPerformance<<" MomentumProject t_import "<<t_import<<"us"<<std::endl;
std::cout << GridLogPerformance<<" MomentumProject t_export "<<t_export<<"us"<<std::endl;
std::cout << GridLogPerformance<<" MomentumProject t_gemm "<<t_gemm<<"us"<<std::endl;
std::cout << GridLogPerformance<<" MomentumProject t_reduce "<<t_allreduce<<"us"<<std::endl;
}
};
NAMESPACE_END(Grid);
+6 -7
View File
@@ -69,8 +69,8 @@ public:
DeflatedGuesser(const std::vector<Field> & _evec, const std::vector<RealD> & _eval, const unsigned int _N)
: evec(_evec), eval(_eval), N(_N)
{
assert(evec.size()==eval.size());
assert(N <= evec.size());
GRID_ASSERT(evec.size()==eval.size());
GRID_ASSERT(N <= evec.size());
}
virtual void operator()(const Field &src,Field &guess) {
@@ -141,11 +141,10 @@ public:
}
//postprocessing
std::cout << GridLogMessage << "Start BlockPromote for loop" << std::endl;
for (int j=0;j<Nsrc;j++)
{
std::cout << GridLogMessage << "BlockProject iter: " << j << std::endl;
blockPromote(guess_coarse[j],guess[j],subspace);
guess[j].Checkerboard() = src[j].Checkerboard();
for (int j=0;j<Nsrc;j++) {
std::cout << GridLogMessage << "BlockProject iter: " << j << std::endl;
blockPromote(guess_coarse[j],guess[j],subspace);
guess[j].Checkerboard() = src[j].Checkerboard();
}
};
@@ -160,7 +160,7 @@ public:
uint64_t words;
nrhs = X.size();
assert(X.size()==Y.size());
GRID_ASSERT(X.size()==Y.size());
conformable(X[0],Y[0]);
grid = X[0].Grid();
@@ -259,7 +259,7 @@ public:
uint64_t words;
nrhs = X.size();
assert(X.size()==Y.size());
GRID_ASSERT(X.size()==Y.size());
conformable(X[0],Y[0]);
grid = X[0].Grid();
@@ -267,7 +267,7 @@ public:
vol = grid->oSites()/rd0;
words = rd0*sizeof(vector_object)/sizeof(scalar);
int64_t vw = vol * words;
assert(vw == grid->lSites()*sizeof(scalar_object)/sizeof(scalar));
GRID_ASSERT(vw == grid->lSites()*sizeof(scalar_object)/sizeof(scalar));
RealD t0 = usecond();
BLAS_X.resize(nrhs * vw); // cost free if size doesn't change
+617 -77
View File
@@ -61,6 +61,17 @@ public:
uint64_t coarse_vol;
uint64_t words;
////////////////////////////////////////////////////////////////////////////
// Blocking geometry in full local coordinates. Addressing in lSites rather
// than (lane,oSite) lets the fine and coarse spaces carry different SIMD
// layouts, so an unvectorised coarse space may block a vectorised fine one.
////////////////////////////////////////////////////////////////////////////
Coordinate fine_ldimensions;
Coordinate coarse_ldimensions;
Coordinate block_ldimensions;
Coordinate fine_simd;
Coordinate coarse_simd;
// Row major layout "C" order:
// BLAS_V[coarse_vol][nbasis][block_vol][words]
// BLAS_F[coarse_vol][nrhs][block_vol][words]
@@ -120,35 +131,47 @@ public:
fine_vol = fine_grid->lSites();
coarse_vol = coarse_grid->lSites();
block_vol = fine_vol/coarse_vol;
words = sizeof(scalar_object)/sizeof(scalar);
int nd = coarse_grid->_ndimension;
GRID_ASSERT(fine_grid->_ndimension == nd);
fine_ldimensions.resize(nd);
coarse_ldimensions.resize(nd);
block_ldimensions.resize(nd);
fine_simd = fine_grid->_simd_layout;
coarse_simd = coarse_grid->_simd_layout;
for(int d=0;d<nd;d++){
GRID_ASSERT(fine_grid->_processors[d] == coarse_grid->_processors[d]);
fine_ldimensions [d] = fine_grid->_rdimensions [d]*fine_grid->_simd_layout [d];
coarse_ldimensions[d] = coarse_grid->_rdimensions[d]*coarse_grid->_simd_layout[d];
block_ldimensions [d] = fine_ldimensions[d]/coarse_ldimensions[d];
GRID_ASSERT(block_ldimensions[d]*coarse_ldimensions[d] == fine_ldimensions[d]);
}
GRID_ASSERT(block_vol == fine_vol/coarse_vol);
BLAS_V.resize (fine_vol * words * nbasis );
}
void ImportFineGridVectors(std::vector <Field > &vecs, deviceVector<scalar> &blas)
{
GRID_TRACE("ImportFineGridVectors");
int nvec = vecs.size();
typedef typename Field::vector_object vobj;
// std::cout << GridLogMessage <<" BlockProjector importing "<<nvec<< " fine grid vectors" <<std::endl;
assert(vecs[0].Grid()==fine_grid);
subdivides(coarse_grid,fine_grid); // require they map
GRID_ASSERT(vecs[0].Grid()==fine_grid);
int _ndimension = coarse_grid->_ndimension;
assert(block_vol == fine_grid->oSites() / coarse_grid->oSites());
Coordinate block_r (_ndimension);
for(int d=0 ; d<_ndimension;d++){
block_r[d] = fine_grid->_rdimensions[d] / coarse_grid->_rdimensions[d];
}
uint64_t sz = blas.size();
acceleratorMemSet(&blas[0],0,blas.size()*sizeof(scalar));
Coordinate fine_rdimensions = fine_grid->_rdimensions;
Coordinate coarse_rdimensions = coarse_grid->_rdimensions;
Coordinate coarse_l = coarse_ldimensions;
Coordinate block_l = block_ldimensions;
Coordinate fsimd = fine_simd;
int64_t bv= block_vol;
for(int v=0;v<vecs.size();v++){
@@ -162,9 +185,7 @@ public:
// loop over fine sites
const int Nsimd = vobj::Nsimd();
// std::cout << "sz "<<sz<<std::endl;
// std::cout << "prod "<<Nsimd * coarse_grid->oSites() * block_vol * nvec * words<<std::endl;
assert(sz == Nsimd * coarse_grid->oSites() * block_vol * nvec * words);
GRID_ASSERT(sz == coarse_vol * block_vol * nvec * words);
uint64_t lwords= words; // local variable for copy in to GPU
accelerator_for(sf,osites,Nsimd,{
#ifdef GRID_SIMT
@@ -175,30 +196,30 @@ public:
#endif
// One thread per fine site
Coordinate coor_f(_ndimension);
Coordinate coor_l(_ndimension);
Coordinate coor_b(_ndimension);
Coordinate coor_c(_ndimension);
// Fine site to fine coor
// Fine (oSite,lane) to full local coor
Lexicographic::CoorFromIndex(coor_f,sf,fine_rdimensions);
Lexicographic::CoorFromIndex(coor_l,lane,fsimd);
for(int d=0;d<_ndimension;d++) coor_f[d] += fine_rdimensions[d]*coor_l[d];
for(int d=0;d<_ndimension;d++) coor_b[d] = coor_f[d]%block_l[d];
for(int d=0;d<_ndimension;d++) coor_c[d] = coor_f[d]/block_l[d];
for(int d=0;d<_ndimension;d++) coor_b[d] = coor_f[d]%block_r[d];
for(int d=0;d<_ndimension;d++) coor_c[d] = coor_f[d]/block_r[d];
int sc;// coarse site
int sb;// block site
Lexicographic::IndexFromCoor(coor_c,sc,coarse_rdimensions);
Lexicographic::IndexFromCoor(coor_b,sb,block_r);
Lexicographic::IndexFromCoor(coor_c,sc,coarse_l);
Lexicographic::IndexFromCoor(coor_b,sb,block_l);
scalar_object data = extractLane(lane,fineData[sf]);
// BLAS layout address calculation
// words * block_vol * nbasis x coarse_vol
// coarse oSite x block vole x lanes
int64_t site = (lane*osites + sc*bv)*nvec
+ v*bv
// BLAS_F[coarse_vol][nvec][block_vol][words]
int64_t site = (sc*nvec + v)*bv
+ sb;
// assert(site*lwords<sz);
// GRID_ASSERT(site*lwords<sz);
scalar_object * ptr = (scalar_object *)&blasData_p[site*lwords];
@@ -213,25 +234,333 @@ public:
// std::cout << " BlockProjector imported vector"<<v<<std::endl;
}
}
////////////////////////////////////////////////////////////////////////////
// Import direct from multiRHS fine order, avoiding the unpack to a vector
// of single RHS fields.
//
// fine mrhs grid : rhs is dimension 0, undistributed, unvectorised
// Grid order : F[fine_vol][nrhs][words]
// BLAS order : BLAS_F[lane][coarse_vol][nrhs][block_vol][words]
//
// The gather of block_vol from fine_vol, and the transpose of nrhs against
// block_vol, are the irreducible part: this is not the identity.
////////////////////////////////////////////////////////////////////////////
void ImportFineGridMrhsVectors(Field &vec_mrhs, deviceVector<scalar> &blas)
{
typedef typename Field::vector_object vobj;
GridBase *fine_mrhs_grid = vec_mrhs.Grid();
int _ndimension = coarse_grid->_ndimension;
GRID_ASSERT(fine_mrhs_grid->_ndimension == _ndimension+1);
GRID_ASSERT(fine_mrhs_grid->_simd_layout[0] == 1);
GRID_ASSERT(fine_mrhs_grid->_processors[0] == 1);
for(int d=0;d<_ndimension;d++){
GRID_ASSERT(fine_mrhs_grid->_rdimensions[d+1] == fine_grid->_rdimensions[d]);
GRID_ASSERT(fine_mrhs_grid->_simd_layout[d+1] == fine_grid->_simd_layout[d]);
}
int nvec = fine_mrhs_grid->_rdimensions[0]; // nrhs
uint64_t sz = blas.size();
acceleratorMemSet(&blas[0],0,blas.size()*sizeof(scalar));
Coordinate fine_mrhs_rdimensions = fine_mrhs_grid->_rdimensions;
Coordinate fine_rdimensions = fine_grid->_rdimensions;
Coordinate coarse_l = coarse_ldimensions;
Coordinate block_l = block_ldimensions;
Coordinate fsimd = fine_simd;
int64_t bv= block_vol;
autoView( fineData , vec_mrhs, AcceleratorRead);
auto blasData_p = &blas[0];
auto fineData_p = &fineData[0];
int64_t osites = fine_grid->oSites(); // D dimensional
int64_t osites_hi = fine_mrhs_grid->oSites(); // nvec * osites
const int Nsimd = vobj::Nsimd();
GRID_ASSERT(sz == coarse_vol * block_vol * nvec * words);
uint64_t lwords= words;
int64_t lnvec = nvec;
accelerator_for(sfr,osites_hi,Nsimd,{
#ifdef GRID_SIMT
{
int lane=acceleratorSIMTlane(Nsimd); // buffer lane
#else
for(int lane=0;lane<Nsimd;lane++) {
#endif
Coordinate coor_hi(_ndimension+1);
Coordinate coor_f(_ndimension);
Coordinate coor_l(_ndimension);
Coordinate coor_b(_ndimension);
Coordinate coor_c(_ndimension);
// rhs is dimension 0 of the D+1 grid
Lexicographic::CoorFromIndex(coor_hi,sfr,fine_mrhs_rdimensions);
int v = coor_hi[0];
for(int d=0;d<_ndimension;d++) coor_f[d] = coor_hi[d+1];
Lexicographic::CoorFromIndex(coor_l,lane,fsimd);
for(int d=0;d<_ndimension;d++) coor_f[d] += fine_rdimensions[d]*coor_l[d];
for(int d=0;d<_ndimension;d++) coor_b[d] = coor_f[d]%block_l[d];
for(int d=0;d<_ndimension;d++) coor_c[d] = coor_f[d]/block_l[d];
int sc;// coarse site
int sb;// block site
Lexicographic::IndexFromCoor(coor_c,sc,coarse_l);
Lexicographic::IndexFromCoor(coor_b,sb,block_l);
scalar_object data = extractLane(lane,fineData[sfr]);
int64_t site = (sc*lnvec + v)*bv
+ sb;
scalar_object * ptr = (scalar_object *)&blasData_p[site*lwords];
*ptr = data;
#ifdef GRID_SIMT
}
#else
}
#endif
});
}
////////////////////////////////////////////////////////////////////////////
// Export direct to multiRHS coarse order.
//
// Grid order : C[coarse_vol][nrhs][nbasis]
// BLAS order : BLAS_C[lane][coarse_vol][nrhs][nbasis]
//
// At Nsimd()==1 these are the same sequence of addresses.
////////////////////////////////////////////////////////////////////////////
template<class vobj>
void ExportCoarseGridMrhsVectors(Lattice<vobj> &vec_mrhs, deviceVector<scalar> &blas)
{
typedef typename vobj::scalar_object coarse_scalar_object;
GridBase *coarse_mrhs_grid = vec_mrhs.Grid();
int _ndimension = coarse_grid->_ndimension;
GRID_ASSERT(coarse_mrhs_grid->_ndimension == _ndimension+1);
GRID_ASSERT(coarse_mrhs_grid->_simd_layout[0] == 1);
GRID_ASSERT(coarse_mrhs_grid->_processors[0] == 1);
for(int d=0;d<_ndimension;d++){
GRID_ASSERT(coarse_mrhs_grid->_rdimensions[d+1] == coarse_grid->_rdimensions[d]);
GRID_ASSERT(coarse_mrhs_grid->_simd_layout[d+1] == coarse_grid->_simd_layout[d]);
}
int nvec = coarse_mrhs_grid->_rdimensions[0]; // nrhs
Coordinate coarse_mrhs_rdimensions = coarse_mrhs_grid->_rdimensions;
Coordinate coarse_rdimensions = coarse_grid->_rdimensions;
Coordinate coarse_l = coarse_ldimensions;
Coordinate csimd = coarse_simd;
autoView( coarseData , vec_mrhs, AcceleratorWrite);
auto blasData_p = &blas[0];
auto coarseData_p = &coarseData[0];
int64_t osites = coarse_grid->oSites(); // D dimensional
int64_t osites_hi = coarse_mrhs_grid->oSites(); // nvec * osites
const int Nsimd = vobj::Nsimd();
uint64_t cwords=sizeof(typename vobj::scalar_object)/sizeof(scalar);
GRID_ASSERT(cwords==nbasis);
int64_t lnvec = nvec;
accelerator_for(scr,osites_hi,Nsimd,{
#ifdef GRID_SIMT
{
int lane=acceleratorSIMTlane(Nsimd); // buffer lane
#else
for(int lane=0;lane<Nsimd;lane++) {
#endif
Coordinate coor_hi(_ndimension+1);
Coordinate coor_l(_ndimension);
Coordinate coor_c(_ndimension);
Lexicographic::CoorFromIndex(coor_hi,scr,coarse_mrhs_rdimensions);
int v = coor_hi[0];
for(int d=0;d<_ndimension;d++) coor_c[d] = coor_hi[d+1];
Lexicographic::CoorFromIndex(coor_l,lane,csimd);
for(int d=0;d<_ndimension;d++) coor_c[d] += coarse_rdimensions[d]*coor_l[d];
int sc;
Lexicographic::IndexFromCoor(coor_c,sc,coarse_l);
int64_t blas_site = (sc*lnvec + v)*cwords;
coarse_scalar_object * ptr = (coarse_scalar_object *)&blasData_p[blas_site];
coarse_scalar_object data = *ptr;
insertLane(lane,coarseData[scr],data);
#ifdef GRID_SIMT
}
#else
}
#endif
});
}
////////////////////////////////////////////////////////////////////////////
// Reverse directions: BLAS_F -> fine mrhs field, coarse mrhs field -> BLAS_C
////////////////////////////////////////////////////////////////////////////
void ExportFineGridMrhsVectors(Field &vec_mrhs, deviceVector<scalar> &blas)
{
typedef typename Field::vector_object vobj;
GridBase *fine_mrhs_grid = vec_mrhs.Grid();
int _ndimension = coarse_grid->_ndimension;
GRID_ASSERT(fine_mrhs_grid->_ndimension == _ndimension+1);
GRID_ASSERT(fine_mrhs_grid->_simd_layout[0] == 1);
for(int d=0;d<_ndimension;d++){
GRID_ASSERT(fine_mrhs_grid->_rdimensions[d+1] == fine_grid->_rdimensions[d]);
GRID_ASSERT(fine_mrhs_grid->_simd_layout[d+1] == fine_grid->_simd_layout[d]);
}
int nvec = fine_mrhs_grid->_rdimensions[0];
Coordinate fine_mrhs_rdimensions = fine_mrhs_grid->_rdimensions;
Coordinate fine_rdimensions = fine_grid->_rdimensions;
Coordinate coarse_l = coarse_ldimensions;
Coordinate block_l = block_ldimensions;
Coordinate fsimd = fine_simd;
int64_t bv= block_vol;
autoView( fineData , vec_mrhs, AcceleratorWrite);
auto blasData_p = &blas[0];
auto fineData_p = &fineData[0];
int64_t osites = fine_grid->oSites();
int64_t osites_hi = fine_mrhs_grid->oSites();
const int Nsimd = vobj::Nsimd();
uint64_t lwords= words;
int64_t lnvec = nvec;
accelerator_for(sfr,osites_hi,Nsimd,{
#ifdef GRID_SIMT
{
int lane=acceleratorSIMTlane(Nsimd);
#else
for(int lane=0;lane<Nsimd;lane++) {
#endif
Coordinate coor_hi(_ndimension+1);
Coordinate coor_f(_ndimension);
Coordinate coor_l(_ndimension);
Coordinate coor_b(_ndimension);
Coordinate coor_c(_ndimension);
Lexicographic::CoorFromIndex(coor_hi,sfr,fine_mrhs_rdimensions);
int v = coor_hi[0];
for(int d=0;d<_ndimension;d++) coor_f[d] = coor_hi[d+1];
Lexicographic::CoorFromIndex(coor_l,lane,fsimd);
for(int d=0;d<_ndimension;d++) coor_f[d] += fine_rdimensions[d]*coor_l[d];
for(int d=0;d<_ndimension;d++) coor_b[d] = coor_f[d]%block_l[d];
for(int d=0;d<_ndimension;d++) coor_c[d] = coor_f[d]/block_l[d];
int sc,sb;
Lexicographic::IndexFromCoor(coor_c,sc,coarse_l);
Lexicographic::IndexFromCoor(coor_b,sb,block_l);
int64_t site = (sc*lnvec + v)*bv
+ sb;
scalar_object * ptr = (scalar_object *)&blasData_p[site*lwords];
scalar_object data = *ptr;
insertLane(lane,fineData[sfr],data);
#ifdef GRID_SIMT
}
#else
}
#endif
});
}
template<class vobj>
void ImportCoarseGridMrhsVectors(Lattice<vobj> &vec_mrhs, deviceVector<scalar> &blas)
{
typedef typename vobj::scalar_object coarse_scalar_object;
GridBase *coarse_mrhs_grid = vec_mrhs.Grid();
int _ndimension = coarse_grid->_ndimension;
GRID_ASSERT(coarse_mrhs_grid->_ndimension == _ndimension+1);
GRID_ASSERT(coarse_mrhs_grid->_simd_layout[0] == 1);
for(int d=0;d<_ndimension;d++){
GRID_ASSERT(coarse_mrhs_grid->_rdimensions[d+1] == coarse_grid->_rdimensions[d]);
GRID_ASSERT(coarse_mrhs_grid->_simd_layout[d+1] == coarse_grid->_simd_layout[d]);
}
int nvec = coarse_mrhs_grid->_rdimensions[0];
Coordinate coarse_mrhs_rdimensions = coarse_mrhs_grid->_rdimensions;
Coordinate coarse_rdimensions = coarse_grid->_rdimensions;
Coordinate coarse_l = coarse_ldimensions;
Coordinate csimd = coarse_simd;
autoView( coarseData , vec_mrhs, AcceleratorRead);
auto blasData_p = &blas[0];
auto coarseData_p = &coarseData[0];
int64_t osites = coarse_grid->oSites();
int64_t osites_hi = coarse_mrhs_grid->oSites();
const int Nsimd = vobj::Nsimd();
uint64_t cwords=sizeof(typename vobj::scalar_object)/sizeof(scalar);
GRID_ASSERT(cwords==nbasis);
int64_t lnvec = nvec;
accelerator_for(scr,osites_hi,Nsimd,{
#ifdef GRID_SIMT
{
int lane=acceleratorSIMTlane(Nsimd);
#else
for(int lane=0;lane<Nsimd;lane++) {
#endif
Coordinate coor_hi(_ndimension+1);
Coordinate coor_l(_ndimension);
Coordinate coor_c(_ndimension);
Lexicographic::CoorFromIndex(coor_hi,scr,coarse_mrhs_rdimensions);
int v = coor_hi[0];
for(int d=0;d<_ndimension;d++) coor_c[d] = coor_hi[d+1];
Lexicographic::CoorFromIndex(coor_l,lane,csimd);
for(int d=0;d<_ndimension;d++) coor_c[d] += coarse_rdimensions[d]*coor_l[d];
int sc;
Lexicographic::IndexFromCoor(coor_c,sc,coarse_l);
coarse_scalar_object data = extractLane(lane,coarseData[scr]);
int64_t blas_site = (sc*lnvec + v)*cwords;
coarse_scalar_object * ptr = (coarse_scalar_object *)&blasData_p[blas_site];
*ptr = data;
#ifdef GRID_SIMT
}
#else
}
#endif
});
}
void ExportFineGridVectors(std::vector <Field> &vecs, deviceVector<scalar> &blas)
{
GRID_TRACE("ExportFineGridVectors");
typedef typename Field::vector_object vobj;
int nvec = vecs.size();
assert(vecs[0].Grid()==fine_grid);
subdivides(coarse_grid,fine_grid); // require they map
GRID_ASSERT(vecs[0].Grid()==fine_grid);
int _ndimension = coarse_grid->_ndimension;
assert(block_vol == fine_grid->oSites() / coarse_grid->oSites());
Coordinate block_r (_ndimension);
for(int d=0 ; d<_ndimension;d++){
block_r[d] = fine_grid->_rdimensions[d] / coarse_grid->_rdimensions[d];
}
Coordinate fine_rdimensions = fine_grid->_rdimensions;
Coordinate coarse_rdimensions = coarse_grid->_rdimensions;
Coordinate coarse_l = coarse_ldimensions;
Coordinate block_l = block_ldimensions;
Coordinate fsimd = fine_simd;
// std::cout << " export fine Blas norm "<<blasNorm2(blas)<<std::endl;
@@ -259,23 +588,24 @@ public:
#endif
// One thread per fine site
Coordinate coor_f(_ndimension);
Coordinate coor_l(_ndimension);
Coordinate coor_b(_ndimension);
Coordinate coor_c(_ndimension);
Lexicographic::CoorFromIndex(coor_f,sf,fine_rdimensions);
Lexicographic::CoorFromIndex(coor_l,lane,fsimd);
for(int d=0;d<_ndimension;d++) coor_f[d] += fine_rdimensions[d]*coor_l[d];
for(int d=0;d<_ndimension;d++) coor_b[d] = coor_f[d]%block_l[d];
for(int d=0;d<_ndimension;d++) coor_c[d] = coor_f[d]/block_l[d];
for(int d=0;d<_ndimension;d++) coor_b[d] = coor_f[d]%block_r[d];
for(int d=0;d<_ndimension;d++) coor_c[d] = coor_f[d]/block_r[d];
int sc;
int sb;
Lexicographic::IndexFromCoor(coor_c,sc,coarse_rdimensions);
Lexicographic::IndexFromCoor(coor_b,sb,block_r);
Lexicographic::IndexFromCoor(coor_c,sc,coarse_l);
Lexicographic::IndexFromCoor(coor_b,sb,block_l);
// BLAS layout address calculation
// words * block_vol * nbasis x coarse_vol
int64_t site = (lane*osites + sc*bv)*nvec
+ v*bv
// BLAS_F[coarse_vol][nvec][block_vol][words]
int64_t site = (sc*nvec + v)*bv
+ sb;
scalar_object * ptr = (scalar_object *)&blasData_p[site*lwords];
@@ -294,19 +624,22 @@ public:
template<class vobj>
void ImportCoarseGridVectors(std::vector <Lattice<vobj> > &vecs, deviceVector<scalar> &blas)
{
GRID_TRACE("ImportCoarseGridVectors");
int nvec = vecs.size();
typedef typename vobj::scalar_object coarse_scalar_object;
// std::cout << " BlockProjector importing "<<nvec<< " coarse grid vectors" <<std::endl;
assert(vecs[0].Grid()==coarse_grid);
GRID_ASSERT(vecs[0].Grid()==coarse_grid);
int _ndimension = coarse_grid->_ndimension;
uint64_t sz = blas.size();
Coordinate coarse_rdimensions = coarse_grid->_rdimensions;
Coordinate coarse_l = coarse_ldimensions;
Coordinate csimd = coarse_simd;
for(int v=0;v<vecs.size();v++){
// std::cout << " BlockProjector importing coarse vector"<<v<<" "<<norm2(vecs[v])<<std::endl;
@@ -320,7 +653,7 @@ public:
// loop over fine sites
const int Nsimd = vobj::Nsimd();
uint64_t cwords=sizeof(typename vobj::scalar_object)/sizeof(scalar);
assert(cwords==nbasis);
GRID_ASSERT(cwords==nbasis);
accelerator_for(sc,osites,Nsimd,{
#ifdef GRID_SIMT
@@ -330,8 +663,17 @@ public:
for(int lane=0;lane<Nsimd;lane++) {
#endif
// C_br per site
int64_t blas_site = (lane*osites + sc)*nvec*cwords + v*cwords;
Coordinate coor_c(_ndimension);
Coordinate coor_l(_ndimension);
Lexicographic::CoorFromIndex(coor_c,sc,coarse_rdimensions);
Lexicographic::CoorFromIndex(coor_l,lane,csimd);
for(int d=0;d<_ndimension;d++) coor_c[d] += coarse_rdimensions[d]*coor_l[d];
int scl;
Lexicographic::IndexFromCoor(coor_c,scl,coarse_l);
int64_t blas_site = (scl*nvec + v)*cwords;
coarse_scalar_object data = extractLane(lane,coarseData[sc]);
coarse_scalar_object * ptr = (coarse_scalar_object *)&blasData_p[blas_site];
@@ -349,18 +691,21 @@ public:
template<class vobj>
void ExportCoarseGridVectors(std::vector <Lattice<vobj> > &vecs, deviceVector<scalar> &blas)
{
GRID_TRACE("ExportCoarseGridVectors");
int nvec = vecs.size();
typedef typename vobj::scalar_object coarse_scalar_object;
// std::cout << GridLogMessage<<" BlockProjector exporting "<<nvec<< " coarse grid vectors" <<std::endl;
assert(vecs[0].Grid()==coarse_grid);
GRID_ASSERT(vecs[0].Grid()==coarse_grid);
int _ndimension = coarse_grid->_ndimension;
uint64_t sz = blas.size();
Coordinate coarse_rdimensions = coarse_grid->_rdimensions;
Coordinate coarse_l = coarse_ldimensions;
Coordinate csimd = coarse_simd;
// std::cout << " export coarsee Blas norm "<<blasNorm2(blas)<<std::endl;
for(int v=0;v<vecs.size();v++){
@@ -375,7 +720,7 @@ public:
// loop over fine sites
const int Nsimd = vobj::Nsimd();
uint64_t cwords=sizeof(typename vobj::scalar_object)/sizeof(scalar);
assert(cwords==nbasis);
GRID_ASSERT(cwords==nbasis);
accelerator_for(sc,osites,Nsimd,{
// Wrap in a macro "FOR_ALL_LANES(lane,{ ... });
@@ -385,7 +730,16 @@ public:
#else
for(int lane=0;lane<Nsimd;lane++) {
#endif
int64_t blas_site = (lane*osites + sc)*nvec*cwords + v*cwords;
Coordinate coor_c(_ndimension);
Coordinate coor_l(_ndimension);
Lexicographic::CoorFromIndex(coor_c,sc,coarse_rdimensions);
Lexicographic::CoorFromIndex(coor_l,lane,csimd);
for(int d=0;d<_ndimension;d++) coor_c[d] += coarse_rdimensions[d]*coor_l[d];
int scl;
Lexicographic::IndexFromCoor(coor_c,scl,coarse_l);
int64_t blas_site = (scl*nvec + v)*cwords;
coarse_scalar_object * ptr = (coarse_scalar_object *)&blasData_p[blas_site];
coarse_scalar_object data = *ptr;
insertLane(lane,coarseData[sc],data);
@@ -406,10 +760,11 @@ public:
template<class cobj>
void blockProject(std::vector<Field> &fine,std::vector< Lattice<cobj> > & coarse)
{
GRID_TRACE("BlockProject");
int nrhs=fine.size();
int _nbasis = sizeof(typename cobj::scalar_object)/sizeof(scalar);
// std::cout << "blockProject nbasis " <<nbasis<<" " << _nbasis<<std::endl;
assert(nbasis==_nbasis);
GRID_ASSERT(nbasis==_nbasis);
BLAS_F.resize (fine_vol * words * nrhs );
BLAS_C.resize (coarse_vol * nbasis * nrhs );
@@ -425,17 +780,20 @@ public:
deviceVector<scalar *> Cd(coarse_vol);
// std::cout << "BlockProject pointers"<<std::endl;
for(int c=0;c<coarse_vol;c++){
// BLAS_V[coarse_vol][nbasis][block_vol][words]
// BLAS_F[coarse_vol][nrhs][block_vol][words]
// BLAS_C[coarse_vol][nrhs][nbasis]
scalar * Vh = & BLAS_V[c*nbasis*block_vol*words];
scalar * Fh = & BLAS_F[c*nrhs*block_vol*words];
scalar * Ch = & BLAS_C[c*nrhs*nbasis];
acceleratorPut(Vd[c],Vh);
acceleratorPut(Fd[c],Fh);
acceleratorPut(Cd[c],Ch);
// ONE bulk transfer per table. acceleratorPut is a *synchronous* 8-byte
// hipMemcpy, so the elementwise form emitted 3*coarse_vol of them per call:
// a traced run showed 272k hipMemcpy calls costing 4.5 s of API time to move
// 0.7 s worth of bytes. Same fix as BatchedBlas.h's staging rewrite.
if ( coarse_vol ) {
std::vector<scalar *> hVd(coarse_vol), hFd(coarse_vol), hCd(coarse_vol);
for(int c=0;c<coarse_vol;c++){
hVd[c] = & BLAS_V[c*nbasis*block_vol*words];
hFd[c] = & BLAS_F[c*nrhs*block_vol*words];
hCd[c] = & BLAS_C[c*nrhs*nbasis];
}
acceleratorCopyToDevice(&hVd[0],&Vd[0],coarse_vol*sizeof(scalar *));
acceleratorCopyToDevice(&hFd[0],&Fd[0],coarse_vol*sizeof(scalar *));
acceleratorCopyToDevice(&hCd[0],&Cd[0],coarse_vol*sizeof(scalar *));
}
GridBLAS BLAS;
@@ -462,9 +820,10 @@ public:
template<class cobj>
void blockPromote(std::vector<Field> &fine,std::vector<Lattice<cobj> > & coarse)
{
GRID_TRACE("BlockPromote");
int nrhs=fine.size();
int _nbasis = sizeof(typename cobj::scalar_object)/sizeof(scalar);
assert(nbasis==_nbasis);
GRID_ASSERT(nbasis==_nbasis);
BLAS_F.resize (fine_vol * words * nrhs );
BLAS_C.resize (coarse_vol * nbasis * nrhs );
@@ -477,16 +836,20 @@ public:
deviceVector<scalar *> Fd(coarse_vol);
deviceVector<scalar *> Cd(coarse_vol);
for(int c=0;c<coarse_vol;c++){
// BLAS_V[coarse_vol][nbasis][block_vol][words]
// BLAS_F[coarse_vol][nrhs][block_vol][words]
// BLAS_C[coarse_vol][nrhs][nbasis]
scalar * Vh = & BLAS_V[c*nbasis*block_vol*words];
scalar * Fh = & BLAS_F[c*nrhs*block_vol*words];
scalar * Ch = & BLAS_C[c*nrhs*nbasis];
acceleratorPut(Vd[c],Vh);
acceleratorPut(Fd[c],Fh);
acceleratorPut(Cd[c],Ch);
// ONE bulk transfer per table. acceleratorPut is a *synchronous* 8-byte
// hipMemcpy, so the elementwise form emitted 3*coarse_vol of them per call:
// a traced run showed 272k hipMemcpy calls costing 4.5 s of API time to move
// 0.7 s worth of bytes. Same fix as BatchedBlas.h's staging rewrite.
if ( coarse_vol ) {
std::vector<scalar *> hVd(coarse_vol), hFd(coarse_vol), hCd(coarse_vol);
for(int c=0;c<coarse_vol;c++){
hVd[c] = & BLAS_V[c*nbasis*block_vol*words];
hFd[c] = & BLAS_F[c*nrhs*block_vol*words];
hCd[c] = & BLAS_C[c*nrhs*nbasis];
}
acceleratorCopyToDevice(&hVd[0],&Vd[0],coarse_vol*sizeof(scalar *));
acceleratorCopyToDevice(&hFd[0],&Fd[0],coarse_vol*sizeof(scalar *));
acceleratorCopyToDevice(&hCd[0],&Cd[0],coarse_vol*sizeof(scalar *));
}
/////////////////////////////////////////
@@ -508,6 +871,183 @@ public:
ExportFineGridVectors(fine, BLAS_F);
// std::cout << " exported "<<std::endl;
}
////////////////////////////////////////////////////////////////////////////
// multiRHS ordered interfaces. The GEMM is identical; only the import and
// export differ, so those are the whole of the layout question.
////////////////////////////////////////////////////////////////////////////
template<class cobj>
void blockProject(Field &fine_mrhs,Lattice<cobj> &coarse_mrhs)
{
GRID_TRACE("BlockProjectMrhs");
int nrhs = fine_mrhs.Grid()->_rdimensions[0];
int _nbasis = sizeof(typename cobj::scalar_object)/sizeof(scalar);
GRID_ASSERT(nbasis==_nbasis);
GRID_ASSERT(coarse_mrhs.Grid()->_rdimensions[0]==nrhs);
BLAS_F.resize (fine_vol * words * nrhs );
BLAS_C.resize (coarse_vol * nbasis * nrhs );
ImportFineGridMrhsVectors(fine_mrhs,BLAS_F);
ProjectBLAS(nrhs);
ExportCoarseGridMrhsVectors(coarse_mrhs,BLAS_C);
}
template<class cobj>
void blockPromote(Field &fine_mrhs,Lattice<cobj> &coarse_mrhs)
{
GRID_TRACE("BlockPromoteMrhs");
int nrhs = fine_mrhs.Grid()->_rdimensions[0];
int _nbasis = sizeof(typename cobj::scalar_object)/sizeof(scalar);
GRID_ASSERT(nbasis==_nbasis);
GRID_ASSERT(coarse_mrhs.Grid()->_rdimensions[0]==nrhs);
BLAS_F.resize (fine_vol * words * nrhs );
BLAS_C.resize (coarse_vol * nbasis * nrhs );
ImportCoarseGridMrhsVectors(coarse_mrhs,BLAS_C);
PromoteBLAS(nrhs);
ExportFineGridMrhsVectors(fine_mrhs,BLAS_F);
}
////////////////////////////////////////////////////////////////////////////
// Mixed orderings. A single RHS fine operator produces a vector of fine
// fields with no packing; the coarse side is still wanted in mrhs order.
////////////////////////////////////////////////////////////////////////////
template<class cobj>
void blockProject(std::vector<Field> &fine,Lattice<cobj> &coarse_mrhs)
{
GRID_TRACE("BlockProjectMixed");
int nrhs = fine.size();
int _nbasis = sizeof(typename cobj::scalar_object)/sizeof(scalar);
GRID_ASSERT(nbasis==_nbasis);
GRID_ASSERT(coarse_mrhs.Grid()->_rdimensions[0]==nrhs);
BLAS_F.resize (fine_vol * words * nrhs );
BLAS_C.resize (coarse_vol * nbasis * nrhs );
ImportFineGridVectors(fine,BLAS_F);
ProjectBLAS(nrhs);
ExportCoarseGridMrhsVectors(coarse_mrhs,BLAS_C);
}
template<class cobj>
void blockProject(Field &fine_mrhs,std::vector< Lattice<cobj> > &coarse)
{
GRID_TRACE("BlockProjectMixed");
int nrhs = fine_mrhs.Grid()->_rdimensions[0];
int _nbasis = sizeof(typename cobj::scalar_object)/sizeof(scalar);
GRID_ASSERT(nbasis==_nbasis);
GRID_ASSERT(coarse.size()==nrhs);
BLAS_F.resize (fine_vol * words * nrhs );
BLAS_C.resize (coarse_vol * nbasis * nrhs );
ImportFineGridMrhsVectors(fine_mrhs,BLAS_F);
ProjectBLAS(nrhs);
ExportCoarseGridVectors(coarse,BLAS_C);
}
template<class cobj>
void blockPromote(std::vector<Field> &fine,Lattice<cobj> &coarse_mrhs)
{
GRID_TRACE("BlockPromoteMixed");
int nrhs = fine.size();
int _nbasis = sizeof(typename cobj::scalar_object)/sizeof(scalar);
GRID_ASSERT(nbasis==_nbasis);
GRID_ASSERT(coarse_mrhs.Grid()->_rdimensions[0]==nrhs);
BLAS_F.resize (fine_vol * words * nrhs );
BLAS_C.resize (coarse_vol * nbasis * nrhs );
ImportCoarseGridMrhsVectors(coarse_mrhs,BLAS_C);
PromoteBLAS(nrhs);
ExportFineGridVectors(fine,BLAS_F);
}
template<class cobj>
void blockPromote(Field &fine_mrhs,std::vector< Lattice<cobj> > &coarse)
{
GRID_TRACE("BlockPromoteMixed");
int nrhs = fine_mrhs.Grid()->_rdimensions[0];
int _nbasis = sizeof(typename cobj::scalar_object)/sizeof(scalar);
GRID_ASSERT(nbasis==_nbasis);
GRID_ASSERT(coarse.size()==nrhs);
BLAS_F.resize (fine_vol * words * nrhs );
BLAS_C.resize (coarse_vol * nbasis * nrhs );
ImportCoarseGridVectors(coarse,BLAS_C);
PromoteBLAS(nrhs);
ExportFineGridMrhsVectors(fine_mrhs,BLAS_F);
}
////////////////////////////////////////////////////////////////////////////
// Pointer tables and the batched GEMM, shared by both orderings
////////////////////////////////////////////////////////////////////////////
void BLASPointers(int nrhs,
deviceVector<scalar *> &Vd,
deviceVector<scalar *> &Fd,
deviceVector<scalar *> &Cd)
{
// ONE bulk transfer per table. acceleratorPut is a *synchronous* 8-byte
// hipMemcpy, so the elementwise form emitted 3*coarse_vol of them per call:
// a traced run showed 272k hipMemcpy calls costing 4.5 s of API time to move
// 0.7 s worth of bytes. Same fix as BatchedBlas.h's staging rewrite.
if ( coarse_vol ) {
std::vector<scalar *> hVd(coarse_vol), hFd(coarse_vol), hCd(coarse_vol);
for(int c=0;c<coarse_vol;c++){
hVd[c] = & BLAS_V[c*nbasis*block_vol*words];
hFd[c] = & BLAS_F[c*nrhs*block_vol*words];
hCd[c] = & BLAS_C[c*nrhs*nbasis];
}
acceleratorCopyToDevice(&hVd[0],&Vd[0],coarse_vol*sizeof(scalar *));
acceleratorCopyToDevice(&hFd[0],&Fd[0],coarse_vol*sizeof(scalar *));
acceleratorCopyToDevice(&hCd[0],&Cd[0],coarse_vol*sizeof(scalar *));
}
}
// C_br = V^dag F
void ProjectBLAS(int nrhs)
{
GRID_TRACE("ProjectBLAS");
deviceVector<scalar *> Vd(coarse_vol);
deviceVector<scalar *> Fd(coarse_vol);
deviceVector<scalar *> Cd(coarse_vol);
BLASPointers(nrhs,Vd,Fd,Cd);
GridBLAS BLAS;
int64_t vw = block_vol * words;
BLAS.gemmBatched(GridBLAS_OP_C,GridBLAS_OP_N,
nbasis,nrhs,vw,
scalar(1.0),
Vd,
Fd,
scalar(0.0), // wipe out C
Cd);
BLAS.synchronise();
}
// F_xr = Vxb Cbr
void PromoteBLAS(int nrhs)
{
GRID_TRACE("PromoteBLAS");
deviceVector<scalar *> Vd(coarse_vol);
deviceVector<scalar *> Fd(coarse_vol);
deviceVector<scalar *> Cd(coarse_vol);
BLASPointers(nrhs,Vd,Fd,Cd);
GridBLAS BLAS;
int64_t vw = block_vol * words;
BLAS.gemmBatched(GridBLAS_OP_N,GridBLAS_OP_N,
vw,nrhs,nbasis,
scalar(1.0),
Vd,
Cd,
scalar(0.0), // wipe out F
Fd);
BLAS.synchronise();
}
};
NAMESPACE_END(Grid);
@@ -98,7 +98,7 @@ public:
void ImportEigenVector(Field &evec,RealD &_eval, int ev)
{
// std::cout << " ev " <<ev<<" eval "<<_eval<< std::endl;
assert(ev<eval.size());
GRID_ASSERT(ev<eval.size());
eval[ev] = _eval;
int64_t offset = ev*vol*words;
@@ -113,7 +113,7 @@ public:
// Could use to import a batch of eigenvectors
void ImportEigenBasis(std::vector<Field> &evec,std::vector<RealD> &_eval, int _ev0, int _nev)
{
assert(_ev0+_nev<=evec.size());
GRID_ASSERT(_ev0+_nev<=evec.size());
Allocate(_nev,evec[0].Grid());
@@ -126,8 +126,8 @@ public:
void DeflateSources(std::vector<Field> &source,std::vector<Field> & guess)
{
int nrhs = source.size();
assert(source.size()==guess.size());
assert(grid == guess[0].Grid());
GRID_ASSERT(source.size()==guess.size());
GRID_ASSERT(grid == guess[0].Grid());
conformable(guess[0],source[0]);
int64_t vw = vol * words;
@@ -189,7 +189,7 @@ public:
Cd);
BLAS.synchronise();
assert(BLAS_C.size()==nev*nrhs);
GRID_ASSERT(BLAS_C.size()==nev*nrhs);
std::vector<scalar> HOST_C(BLAS_C.size()); // nrhs . nev -- the coefficients
acceleratorCopyFromDevice(&BLAS_C[0],&HOST_C[0],BLAS_C.size()*sizeof(scalar));
+74 -33
View File
@@ -53,7 +53,22 @@ class TwoLevelCG : public LinearFunction<Field>
// Fine operator, Smoother, CoarseSolver
LinearOperatorBase<Field> &_FineLinop;
LinearFunction<Field> &_Smoother;
GridStopWatch ProjectTimer;
GridStopWatch PromoteTimer;
GridStopWatch CoarseTimer;
GridStopWatch SmoothTimer;
GridStopWatch MatrixTimer;
GridStopWatch M3Timer;
GridStopWatch LinalgTimer;
int64_t M3Calls;
int64_t SmoothCalls;
int64_t MatrixCalls;
int64_t ProjectCalls;
int64_t CoarseCalls;
int64_t PromoteCalls;
// more most opertor functions
TwoLevelCG(RealD tol,
Integer maxit,
@@ -103,12 +118,20 @@ class TwoLevelCG : public LinearFunction<Field>
RealD tn;
GridStopWatch HDCGTimer;
ProjectTimer.Reset();
PromoteTimer.Reset();
CoarseTimer.Reset();
SmoothTimer.Reset();
MatrixTimer.Reset();
M3Timer.Reset();
LinalgTimer.Reset();
M3Calls = SmoothCalls = MatrixCalls = ProjectCalls = CoarseCalls = PromoteCalls = 0;
HDCGTimer.Start();
//////////////////////////
// x0 = Vstart -- possibly modify guess
//////////////////////////
Vstart(x,src);
// r0 = b -A x0
_FineLinop.HermOp(x,mmp[0]);
axpy (r, -1.0,mmp[0], src); // Recomputes r=src-Ax0
@@ -145,33 +168,40 @@ class TwoLevelCG : public LinearFunction<Field>
int peri_kp = (k+1) % mmax;
rtz=rtzp;
M3Timer.Start();
d= PcgM3(p[peri_k],mmp[peri_k]);
M3Timer.Stop();
M3Calls++;
a = rtz/d;
// Memorise this
pAp[peri_k] = d;
LinalgTimer.Start();
axpy(x,a,p[peri_k],x);
RealD rn = axpy_norm(r,-a,mmp[peri_k],r);
LinalgTimer.Stop();
// Compute z = M x
PcgM1(r,z);
{
RealD n1,n2;
n1=norm2(r);
n2=norm2(z);
std::cout << GridLogMessage<<"HDCG::fPcg iteration "<<k<<" : vector r,z "<<n1<<" "<<n2<<"\n";
}
LinalgTimer.Start();
rtzp =real(innerProduct(r,z));
LinalgTimer.Stop();
std::cout << GridLogMessage<<"HDCG::fPcg iteration "<<k<<" : inner rtzp "<<rtzp<<"\n";
// PcgM2(z,p[0]);
PcgM2(z,mu); // ADEF-2 this is identity. Axpy possible to eliminate
p[peri_kp]=mu;
// Standard search direction p -> z + b p
// Standard search direction p -> z + b p
b = (rtzp)/rtz;
int northog;
@@ -202,8 +232,25 @@ class TwoLevelCG : public LinearFunction<Field>
if ( rn <= rsq ) {
HDCGTimer.Stop();
std::cout<<GridLogMessage<<"HDCG: fPcg converged in "<<k<<" iterations and "<<HDCGTimer.Elapsed()<<std::endl;;
std::cout<<GridLogMessage<<"HDCG: fPcg converged in "<<k<<" iterations and "<<HDCGTimer.Elapsed()<<std::endl;
std::cout<<GridLogMessage<<"HDCG: fPcg breakdown"<<std::endl;
auto mspc = [](GridStopWatch &sw, int64_t n) -> double {
return (n > 0) ? sw.useconds() * 1e-3 / n : 0.0;
};
std::cout<<GridLogMessage<<"HDCG: fPcg M3 (fine MVM) "<<M3Timer.Elapsed()
<<" "<<M3Calls<<" calls "<<mspc(M3Timer,M3Calls)<<" ms/call"<<std::endl;
std::cout<<GridLogMessage<<"HDCG: fPcg linalg "<<LinalgTimer.Elapsed()<<std::endl;
std::cout<<GridLogMessage<<"HDCG: fPcg smoother "<<SmoothTimer.Elapsed()
<<" "<<SmoothCalls<<" calls "<<mspc(SmoothTimer,SmoothCalls)<<" ms/call"<<std::endl;
std::cout<<GridLogMessage<<"HDCG: fPcg matrix (in M1) "<<MatrixTimer.Elapsed()
<<" "<<MatrixCalls<<" calls "<<mspc(MatrixTimer,MatrixCalls)<<" ms/call"<<std::endl;
std::cout<<GridLogMessage<<"HDCG: fPcg project "<<ProjectTimer.Elapsed()
<<" "<<ProjectCalls<<" calls "<<mspc(ProjectTimer,ProjectCalls)<<" ms/call"<<std::endl;
std::cout<<GridLogMessage<<"HDCG: fPcg coarse "<<CoarseTimer.Elapsed()
<<" "<<CoarseCalls<<" calls "<<mspc(CoarseTimer,CoarseCalls)<<" ms/call"<<std::endl;
std::cout<<GridLogMessage<<"HDCG: fPcg promote "<<PromoteTimer.Elapsed()
<<" "<<PromoteCalls<<" calls "<<mspc(PromoteTimer,PromoteCalls)<<" ms/call"<<std::endl;
_FineLinop.HermOp(x,mmp[0]);
axpy(tmp,-1.0,src,mmp[0]);
@@ -270,7 +317,7 @@ class TwoLevelCG : public LinearFunction<Field>
std::vector<RealD> src_nrm(nrhs);
for(int rhs=0;rhs<nrhs;rhs++) {
src_nrm[rhs]=norm2(src[rhs]);
assert(src_nrm[rhs]!=0.0);
GRID_ASSERT(src_nrm[rhs]!=0.0);
}
std::vector<RealD> tn(nrhs);
@@ -475,35 +522,29 @@ class TwoLevelADEF2 : public TwoLevelCG<Field>
CoarseField PleftProj(this->coarsegrid);
CoarseField PleftMss_proj(this->coarsegrid);
GridStopWatch SmootherTimer;
GridStopWatch MatrixTimer;
SmootherTimer.Start();
this->SmoothTimer.Start();
this->_Smoother(in,Min);
SmootherTimer.Stop();
this->SmoothTimer.Stop();
this->SmoothCalls++;
MatrixTimer.Start();
this->MatrixTimer.Start();
this->_FineLinop.HermOp(Min,out);
MatrixTimer.Stop();
this->MatrixTimer.Stop();
this->MatrixCalls++;
axpy(tmp,-1.0,out,in); // tmp = in - A Min
GridStopWatch ProjTimer;
GridStopWatch CoarseTimer;
GridStopWatch PromTimer;
ProjTimer.Start();
this->_Aggregates.ProjectToSubspace(PleftProj,tmp);
ProjTimer.Stop();
CoarseTimer.Start();
this->ProjectTimer.Start();
this->_Aggregates.ProjectToSubspace(PleftProj,tmp);
this->ProjectTimer.Stop();
this->ProjectCalls++;
this->CoarseTimer.Start();
this->_CoarseSolver(PleftProj,PleftMss_proj); // Ass^{-1} [in - A Min]_s
CoarseTimer.Stop();
PromTimer.Start();
this->_Aggregates.PromoteFromSubspace(PleftMss_proj,tmp);// tmp = Q[in - A Min]
PromTimer.Stop();
std::cout << GridLogPerformance << "PcgM1 breakdown "<<std::endl;
std::cout << GridLogPerformance << "\tSmoother " << SmootherTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\tMatrix " << MatrixTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\tProj " << ProjTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\tCoarse " << CoarseTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\tProm " << PromTimer.Elapsed() <<std::endl;
this->CoarseTimer.Stop();
this->CoarseCalls++;
this->PromoteTimer.Start();
this->_Aggregates.PromoteFromSubspace(PleftMss_proj,tmp);// tmp = Q[in - A Min]
this->PromoteTimer.Stop();
this->PromoteCalls++;
axpy(out,1.0,Min,tmp); // Min+tmp
}
+5 -5
View File
@@ -92,8 +92,8 @@ class TwoLevelCGmrhs
// Vector case
virtual void operator() (std::vector<Field> &src, std::vector<Field> &x)
{
// SolveSingleSystem(src,x);
SolvePrecBlockCG(src,x);
SolveSingleSystem(src,x);
// SolvePrecBlockCG(src,x);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -161,7 +161,7 @@ class TwoLevelCGmrhs
////////////////////////////////////////////
std::vector<RealD> ssq(nrhs);
for(int rhs=0;rhs<nrhs;rhs++){
ssq[rhs]=norm2(src[rhs]); assert(ssq[rhs]!=0.0);
ssq[rhs]=norm2(src[rhs]); GRID_ASSERT(ssq[rhs]!=0.0);
}
///////////////////////////
@@ -382,7 +382,7 @@ class TwoLevelCGmrhs
}
HDCGTimer.Stop();
std::cout<<GridLogMessage<<"HDCG: PrecBlockCGrQ not converged "<<HDCGTimer.Elapsed()<<std::endl;
assert(0);
GRID_ASSERT(0);
}
virtual void SolveSingleSystem (std::vector<Field> &src, std::vector<Field> &x)
@@ -415,7 +415,7 @@ class TwoLevelCGmrhs
std::vector<RealD> src_nrm(nrhs);
for(int rhs=0;rhs<nrhs;rhs++) {
src_nrm[rhs]=norm2(src[rhs]);
assert(src_nrm[rhs]!=0.0);
GRID_ASSERT(src_nrm[rhs]!=0.0);
}
std::vector<RealD> tn(nrhs);
+4 -4
View File
@@ -47,7 +47,7 @@ class BiCGSTAB : public OperatorFunction<Field>
public:
using OperatorFunction<Field>::operator();
bool ErrorOnNoConverge; // throw an assert when the CG fails to converge.
bool ErrorOnNoConverge; // throw an GRID_ASSERT when the CG fails to converge.
// Defaults true.
RealD Tolerance;
Integer MaxIterations;
@@ -77,7 +77,7 @@ class BiCGSTAB : public OperatorFunction<Field>
// Initial residual computation & set up
RealD guess = norm2(psi);
assert(std::isnan(guess) == 0);
GRID_ASSERT(std::isnan(guess) == 0);
Linop.Op(psi, v);
b = norm2(v);
@@ -214,7 +214,7 @@ class BiCGSTAB : public OperatorFunction<Field>
std::cout << GridLogMessage << "\tAxpyNorm " << AxpyNormTimer.Elapsed() << std::endl;
std::cout << GridLogMessage << "\tLinearComb " << LinearCombTimer.Elapsed() << std::endl;
if(ErrorOnNoConverge){ assert(true_residual / Tolerance < 10000.0); }
if(ErrorOnNoConverge){ GRID_ASSERT(true_residual / Tolerance < 10000.0); }
IterationsToComplete = k;
@@ -224,7 +224,7 @@ class BiCGSTAB : public OperatorFunction<Field>
std::cout << GridLogMessage << "BiCGSTAB did NOT converge" << std::endl;
if(ErrorOnNoConverge){ assert(0); }
if(ErrorOnNoConverge){ GRID_ASSERT(0); }
IterationsToComplete = k;
}
};
@@ -98,7 +98,7 @@ class BlockConjugateGradient : public OperatorFunction<Field> {
int Nblock;
BlockCGtype CGtype;
bool ErrorOnNoConverge; // throw an assert when the CG fails to converge.
bool ErrorOnNoConverge; // throw an GRID_ASSERT when the CG fails to converge.
// Defaults true.
RealD Tolerance;
Integer MaxIterations;
@@ -201,7 +201,7 @@ void operator()(LinearOperatorBase<Field> &Linop, const Field &Src, Field &Psi)
} else if (CGtype == CGmultiRHS ) {
CGmultiRHSsolve(Linop,Src,Psi);
} else {
assert(0);
GRID_ASSERT(0);
}
}
virtual void operator()(LinearOperatorBase<Field> &Linop, const std::vector<Field> &Src, std::vector<Field> &Psi)
@@ -209,7 +209,7 @@ virtual void operator()(LinearOperatorBase<Field> &Linop, const std::vector<Fiel
if ( CGtype == BlockCGrQVec ) {
BlockCGrQsolveVec(Linop,Src,Psi);
} else {
assert(0);
GRID_ASSERT(0);
}
}
@@ -259,10 +259,10 @@ void BlockCGrQsolve(LinearOperatorBase<Field> &Linop, const Field &B, Field &X)
for(int b=0;b<Nblock;b++) std::cout << "src["<<b<<"]" << ssq[b] <<std::endl;
sliceNorm(residuals,B,Orthog);
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
sliceNorm(residuals,X,Orthog);
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
/************************************************************************
* Block conjugate gradient rQ (Sebastien Birk Thesis, after Dubrulle 2001)
@@ -402,7 +402,7 @@ void BlockCGrQsolve(LinearOperatorBase<Field> &Linop, const Field &B, Field &X)
std::cout << GridLogMessage << "BlockConjugateGradient(rQ) did NOT converge "<<k<<" / "<<MaxIterations
<<" residual "<< std::sqrt(max_resid)<< std::endl;
if (ErrorOnNoConverge) assert(0);
if (ErrorOnNoConverge) GRID_ASSERT(0);
IterationsToComplete = k;
}
//////////////////////////////////////////////////////////////////////////
@@ -438,10 +438,10 @@ void CGmultiRHSsolve(LinearOperatorBase<Field> &Linop, const Field &Src, Field &
for(int b=0;b<Nblock;b++) sssum+=ssq[b];
sliceNorm(residuals,Src,Orthog);
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
sliceNorm(residuals,Psi,Orthog);
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
// Initial search dir is guess
Linop.HermOp(Psi, AP);
@@ -540,7 +540,7 @@ void CGmultiRHSsolve(LinearOperatorBase<Field> &Linop, const Field &Src, Field &
}
std::cout << GridLogMessage << "MultiRHSConjugateGradient did NOT converge" << std::endl;
if (ErrorOnNoConverge) assert(0);
if (ErrorOnNoConverge) GRID_ASSERT(0);
IterationsToComplete = k;
}
@@ -554,7 +554,7 @@ void CGmultiRHSsolve(LinearOperatorBase<Field> &Linop, const Field &Src, Field &
void BlockCGrQsolveVec(LinearOperatorBase<Field> &Linop, const std::vector<Field> &B, std::vector<Field> &X)
{
Nblock = B.size();
assert(Nblock == X.size());
GRID_ASSERT(Nblock == X.size());
std::cout<<GridLogMessage<<" Block Conjugate Gradient Vec rQ : Nblock "<<Nblock<<std::endl;
@@ -594,10 +594,10 @@ void BlockCGrQsolveVec(LinearOperatorBase<Field> &Linop, const std::vector<Field
for(int b=0;b<Nblock;b++) sssum+=ssq[b];
for(int b=0;b<Nblock;b++){ residuals[b] = norm2(B[b]);}
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
for(int b=0;b<Nblock;b++){ residuals[b] = norm2(X[b]);}
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
/************************************************************************
* Block conjugate gradient rQ (Sebastien Birk Thesis, after Dubrulle 2001)
@@ -731,7 +731,7 @@ void BlockCGrQsolveVec(LinearOperatorBase<Field> &Linop, const std::vector<Field
}
std::cout << GridLogMessage << "BlockConjugateGradient(rQ) did NOT converge" << std::endl;
if (ErrorOnNoConverge) assert(0);
if (ErrorOnNoConverge) GRID_ASSERT(0);
IterationsToComplete = k;
}
@@ -36,7 +36,7 @@ class CommunicationAvoidingGeneralisedMinimalResidual : public OperatorFunction<
public:
using OperatorFunction<Field>::operator();
bool ErrorOnNoConverge; // Throw an assert when CAGMRES fails to converge,
bool ErrorOnNoConverge; // Throw an GRID_ASSERT when CAGMRES fails to converge,
// defaults to true
RealD Tolerance;
@@ -82,7 +82,7 @@ class CommunicationAvoidingGeneralisedMinimalResidual : public OperatorFunction<
conformable(psi, src);
RealD guess = norm2(psi);
assert(std::isnan(guess) == 0);
GRID_ASSERT(std::isnan(guess) == 0);
RealD cp;
RealD ssq = norm2(src);
@@ -137,7 +137,7 @@ class CommunicationAvoidingGeneralisedMinimalResidual : public OperatorFunction<
std::cout << GridLogMessage << "CommunicationAvoidingGeneralisedMinimalResidual did NOT converge" << std::endl;
if (ErrorOnNoConverge)
assert(0);
GRID_ASSERT(0);
}
RealD outerLoopBody(LinearOperatorBase<Field> &LinOp, const Field &src, Field &psi, RealD rsq) {
@@ -185,7 +185,7 @@ class CommunicationAvoidingGeneralisedMinimalResidual : public OperatorFunction<
}
}
assert(0); // Never reached
GRID_ASSERT(0); // Never reached
return cp;
}
+15 -11
View File
@@ -45,7 +45,7 @@ public:
using OperatorFunction<Field>::operator();
bool ErrorOnNoConverge; // throw an assert when the CG fails to converge.
bool ErrorOnNoConverge; // throw an GRID_ASSERT when the CG fails to converge.
// Defaults true.
RealD Tolerance;
Integer MaxIterations;
@@ -94,7 +94,7 @@ public:
ssq = norm2(src);
RealD guess = norm2(psi);
NormTimer.Stop();
assert(std::isnan(guess) == 0);
GRID_ASSERT(std::isnan(guess) == 0);
AssignTimer.Start();
if ( guess == 0.0 ) {
r = src;
@@ -212,17 +212,21 @@ public:
<< "\tTarget " << Tolerance << std::endl;
// std::cout << GridLogMessage << "\tPreamble " << PreambleTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tSolver Elapsed " << SolverTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "Time breakdown "<<std::endl;
std::cout << GridLogPerformance << "\tMatrix " << MatrixTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\tLinalg " << LinalgTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tInner " << InnerTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tAxpyNorm " << AxpyNormTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tLinearComb " << LinearCombTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tPreamble " << PreambleTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tConstruct " << ConstructTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tNorm " << NormTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tAssign " << AssignTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tSolver " << SolverTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "Solver breakdown "<<std::endl;
std::cout << GridLogMessage << "\tMatrix " << MatrixTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\tLinalg " << LinalgTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\t\tInner " << InnerTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\t\tAxpyNorm " << AxpyNormTimer.Elapsed() <<std::endl;
std::cout << GridLogMessage << "\t\tLinearComb " << LinearCombTimer.Elapsed() <<std::endl;
std::cout << GridLogDebug << "\tMobius flop rate " << DwfFlops/ usecs<< " Gflops " <<std::endl;
if (ErrorOnNoConverge) assert(true_residual / Tolerance < 10000.0);
if (ErrorOnNoConverge) GRID_ASSERT(true_residual / Tolerance < 10000.0);
IterationsToComplete = k;
TrueResidual = true_residual;
@@ -251,7 +255,7 @@ public:
std::cout << GridLogPerformance << "\t\tAxpyNorm " << AxpyNormTimer.Elapsed() <<std::endl;
std::cout << GridLogPerformance << "\t\tLinearComb " << LinearCombTimer.Elapsed() <<std::endl;
if (ErrorOnNoConverge) assert(0);
if (ErrorOnNoConverge) GRID_ASSERT(0);
IterationsToComplete = k;
}
@@ -77,7 +77,7 @@ public:
}
void operator() (const std::vector<FieldD> &src_d_in, std::vector<FieldD> &sol_d){
assert(src_d_in.size() == sol_d.size());
GRID_ASSERT(src_d_in.size() == sol_d.size());
int NBatch = src_d_in.size();
std::cout << GridLogMessage << "NBatch = " << NBatch << std::endl;
@@ -98,9 +98,9 @@ public:
std::vector<RealD> alpha(nshift,1.0);
std::vector<Field> ps(nshift,grid);// Search directions
assert(psi.size()==nshift);
assert(mass.size()==nshift);
assert(mresidual.size()==nshift);
GRID_ASSERT(psi.size()==nshift);
GRID_ASSERT(mass.size()==nshift);
GRID_ASSERT(mresidual.size()==nshift);
// remove dynamic sized arrays on stack; 2d is a pain with vector
std::vector<RealD> bs(nshift);
@@ -122,7 +122,7 @@ public:
// Check lightest mass
for(int s=0;s<nshift;s++){
assert( mass[s]>= mass[primary] );
GRID_ASSERT( mass[s]>= mass[primary] );
converged[s]=0;
}
@@ -338,7 +338,7 @@ public:
}
// ugly hack
std::cout<<GridLogMessage<<"CG multi shift did not converge"<<std::endl;
// assert(0);
// GRID_ASSERT(0);
}
};
@@ -118,9 +118,9 @@ public:
FieldF r_f(SinglePrecGrid);
FieldD mmp_d(DoublePrecGrid);
assert(psi_d.size()==nshift);
assert(mass.size()==nshift);
assert(mresidual.size()==nshift);
GRID_ASSERT(psi_d.size()==nshift);
GRID_ASSERT(mass.size()==nshift);
GRID_ASSERT(mresidual.size()==nshift);
// dynamic sized arrays on stack; 2d is a pain with vector
std::vector<RealD> bs(nshift);
@@ -141,7 +141,7 @@ public:
// Check lightest mass
for(int s=0;s<nshift;s++){
assert( mass[s]>= mass[primary] );
GRID_ASSERT( mass[s]>= mass[primary] );
converged[s]=0;
}
@@ -179,7 +179,7 @@ public:
Linop_d.HermOpAndNorm(p_d,mmp_d,d,qq); // mmp = MdagM p d=real(dot(p, mmp)), qq=norm2(mmp)
tmp_d = tmp_d - mmp_d;
std::cout << " Testing operators match "<<norm2(mmp_d)<<" f "<<norm2(mmp_f)<<" diff "<< norm2(tmp_d)<<std::endl;
// assert(norm2(tmp_d)< 1.0e-4);
// GRID_ASSERT(norm2(tmp_d)< 1.0e-4);
axpy(mmp_d,mass[0],p_d,mmp_d);
RealD rn = norm2(p_d);
@@ -365,7 +365,7 @@ public:
}
std::cout<<GridLogMessage<<"CG multi shift did not converge"<<std::endl;
assert(0);
GRID_ASSERT(0);
}
};
@@ -48,12 +48,12 @@ public:
ShiftedLinop(LinearOperatorBase<Field> &_linop_base, RealD _shift): linop_base(_linop_base), 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<Field> &out){ assert(0); }
void OpDiag (const Field &in, Field &out){ GRID_ASSERT(0); }
void OpDir (const Field &in, Field &out,int dir,int disp){ GRID_ASSERT(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ GRID_ASSERT(0); }
void Op (const Field &in, Field &out){ assert(0); }
void AdjOp (const Field &in, Field &out){ assert(0); }
void Op (const Field &in, Field &out){ GRID_ASSERT(0); }
void AdjOp (const Field &in, Field &out){ GRID_ASSERT(0); }
void HermOp(const Field &in, Field &out){
linop_base.HermOp(in, out);
@@ -151,9 +151,9 @@ public:
FieldD r_d(DoublePrecGrid);
FieldD mmp_d(DoublePrecGrid);
assert(psi_d.size()==nshift);
assert(mass.size()==nshift);
assert(mresidual.size()==nshift);
GRID_ASSERT(psi_d.size()==nshift);
GRID_ASSERT(mass.size()==nshift);
GRID_ASSERT(mresidual.size()==nshift);
// dynamic sized arrays on stack; 2d is a pain with vector
std::vector<RealD> bs(nshift);
@@ -174,7 +174,7 @@ public:
// Check lightest mass
for(int s=0;s<nshift;s++){
assert( mass[s]>= mass[primary] );
GRID_ASSERT( mass[s]>= mass[primary] );
converged[s]=0;
}
@@ -211,7 +211,7 @@ public:
Linop_d.HermOpAndNorm(p_d,mmp_d,d,qq); // mmp = MdagM p d=real(dot(p, mmp)), qq=norm2(mmp)
tmp_d = tmp_d - mmp_d;
std::cout << " Testing operators match "<<norm2(mmp_d)<<" f "<<norm2(mmp_f)<<" diff "<< norm2(tmp_d)<<std::endl;
assert(norm2(tmp_d)< 1.0);
GRID_ASSERT(norm2(tmp_d)< 1.0);
axpy(mmp_d,mass[0],p_d,mmp_d);
RealD rn = norm2(p_d);
@@ -408,7 +408,7 @@ public:
}
std::cout<<GridLogMessage<<"CG multi shift did not converge"<<std::endl;
assert(0);
GRID_ASSERT(0);
}
};
@@ -35,7 +35,7 @@ template<class FieldD,class FieldF,
typename std::enable_if< getPrecision<FieldF>::value == 1, int>::type = 0>
class ConjugateGradientReliableUpdate : public LinearFunction<FieldD> {
public:
bool ErrorOnNoConverge; // throw an assert when the CG fails to converge.
bool ErrorOnNoConverge; // throw an GRID_ASSERT when the CG fails to converge.
// Defaults true.
RealD Tolerance;
Integer MaxIterations;
@@ -66,7 +66,7 @@ public:
DoFinalCleanup(true),
Linop_fallback(NULL)
{
assert(Delta > 0. && Delta < 1. && "Expect 0 < Delta < 1");
GRID_ASSERT(Delta > 0. && Delta < 1. && "Expect 0 < Delta < 1");
};
void setFallbackLinop(LinearOperatorBase<FieldF> &_Linop_fallback, const RealD _fallback_transition_tol){
@@ -90,7 +90,7 @@ public:
// Initial residual computation & set up
RealD guess = norm2(psi);
assert(std::isnan(guess) == 0);
GRID_ASSERT(std::isnan(guess) == 0);
Linop_d.HermOpAndNorm(psi, mmp, d, b);
@@ -217,7 +217,7 @@ public:
CG(Linop_d,src,psi);
IterationsToCleanup = CG.IterationsToComplete;
}
else if (ErrorOnNoConverge) assert(true_residual / Tolerance < 10000.0);
else if (ErrorOnNoConverge) GRID_ASSERT(true_residual / Tolerance < 10000.0);
std::cout << GridLogMessage << "ConjugateGradientReliableUpdate complete.\n";
return;
@@ -263,7 +263,7 @@ public:
std::cout << GridLogMessage << "ConjugateGradientReliableUpdate did NOT converge"
<< std::endl;
if (ErrorOnNoConverge) assert(0);
if (ErrorOnNoConverge) GRID_ASSERT(0);
IterationsToComplete = k;
ReliableUpdatesPerformed = l;
}
@@ -106,7 +106,7 @@ public:
}
std::cout<<GridLogMessage<<"ConjugateResidual did NOT converge"<<std::endl;
assert(0);
GRID_ASSERT(0);
}
};
NAMESPACE_END(Grid);
@@ -36,7 +36,7 @@ class FlexibleCommunicationAvoidingGeneralisedMinimalResidual : public OperatorF
public:
using OperatorFunction<Field>::operator();
bool ErrorOnNoConverge; // Throw an assert when FCAGMRES fails to converge,
bool ErrorOnNoConverge; // Throw an GRID_ASSERT when FCAGMRES fails to converge,
// defaults to true
RealD Tolerance;
@@ -87,7 +87,7 @@ class FlexibleCommunicationAvoidingGeneralisedMinimalResidual : public OperatorF
conformable(psi, src);
RealD guess = norm2(psi);
assert(std::isnan(guess) == 0);
GRID_ASSERT(std::isnan(guess) == 0);
RealD cp;
RealD ssq = norm2(src);
@@ -144,7 +144,7 @@ class FlexibleCommunicationAvoidingGeneralisedMinimalResidual : public OperatorF
std::cout << GridLogMessage << "FlexibleCommunicationAvoidingGeneralisedMinimalResidual did NOT converge" << std::endl;
if (ErrorOnNoConverge)
assert(0);
GRID_ASSERT(0);
}
RealD outerLoopBody(LinearOperatorBase<Field> &LinOp, const Field &src, Field &psi, RealD rsq) {
@@ -191,7 +191,7 @@ class FlexibleCommunicationAvoidingGeneralisedMinimalResidual : public OperatorF
}
}
assert(0); // Never reached
GRID_ASSERT(0); // Never reached
return cp;
}
@@ -36,7 +36,7 @@ class FlexibleGeneralisedMinimalResidual : public OperatorFunction<Field> {
public:
using OperatorFunction<Field>::operator();
bool ErrorOnNoConverge; // Throw an assert when FGMRES fails to converge,
bool ErrorOnNoConverge; // Throw an GRID_ASSERT when FGMRES fails to converge,
// defaults to true
RealD Tolerance;
@@ -85,7 +85,7 @@ class FlexibleGeneralisedMinimalResidual : public OperatorFunction<Field> {
conformable(psi, src);
RealD guess = norm2(psi);
assert(std::isnan(guess) == 0);
GRID_ASSERT(std::isnan(guess) == 0);
RealD cp;
RealD ssq = norm2(src);
@@ -142,7 +142,7 @@ class FlexibleGeneralisedMinimalResidual : public OperatorFunction<Field> {
std::cout << GridLogMessage << "FlexibleGeneralisedMinimalResidual did NOT converge" << std::endl;
if (ErrorOnNoConverge)
assert(0);
GRID_ASSERT(0);
}
RealD outerLoopBody(LinearOperatorBase<Field> &LinOp, const Field &src, Field &psi, RealD rsq) {
@@ -189,7 +189,7 @@ class FlexibleGeneralisedMinimalResidual : public OperatorFunction<Field> {
}
}
assert(0); // Never reached
GRID_ASSERT(0); // Never reached
return cp;
}
@@ -0,0 +1,87 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/iterative/GCRCoefficients.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
NAMESPACE_BEGIN(Grid);
//////////////////////////////////////////////////////////////////////////////
// Recorded GCR coefficients: per-step means over calls of the step length
// a_k and the orthogonalisation coefficients b_kj (already scaled and
// signed as applied: p_{k+1} = r + sum_j b_kj p_{k-j}).
//////////////////////////////////////////////////////////////////////////////
struct GCRCoefficients {
int mmax = 0;
// Every recorded call is kept: calls[c] = list of (a_k, [b_kj]) per step.
// A(k)/B(k,j) return the coefficients of the SELECTED call: by default the
// last complete one. Selection "mean" averages coefficients over calls --
// kept for comparison only: the mean of the coefficients of a nonlinear
// recurrence is not the mean of the polynomials, and in practice (Frontier
// M3, 2026-08-26) it was worse than every individual call.
enum Select { Last=0, First=1, Index=2, Mean=3 };
Select select = Last;
int index = 0;
typedef std::vector<std::pair<ComplexD,std::vector<ComplexD> > > Call;
std::vector<Call> calls;
Call current;
void RecordA(int k, ComplexD a){
if ( k==0 && current.size() ) { calls.push_back(current); current.clear(); }
if ( (int)current.size() <= k ) current.resize(k+1);
current[k].first = a;
}
void RecordB(int k, const std::vector<ComplexD> &b){
if ( (int)current.size() <= k ) current.resize(k+1);
current[k].second = b;
}
void Flush(void){ if ( current.size() ) { calls.push_back(current); current.clear(); } }
int Calls(void) const { return calls.size() + (current.size() ? 1 : 0); }
const Call & Chosen(void) const {
GRID_ASSERT( calls.size() || current.size() );
if ( calls.empty() ) return current;
if ( select==First ) return calls.front();
if ( select==Index ) { GRID_ASSERT(index>=0 && index<(int)calls.size()); return calls[index]; }
return calls.back();
}
int Steps(void) const { return select==Mean ? MeanSteps() : Chosen().size(); }
int NB(int k) const { return select==Mean ? MeanNB(k) : Chosen()[k].second.size(); }
ComplexD A(int k) const { return select==Mean ? MeanA(k) : Chosen()[k].first; }
ComplexD B(int k,int j) const { return select==Mean ? MeanB(k,j) : Chosen()[k].second[j]; }
// mean over calls (comparison only)
int MeanSteps(void) const { int m=0; for(auto &c:calls) m = std::max(m,(int)c.size()); return m; }
int MeanNB(int k) const { int m=0; for(auto &c:calls) if(k<(int)c.size()) m = std::max(m,(int)c[k].second.size()); return m; }
ComplexD MeanA(int k) const { ComplexD s(0.0); int n=0; for(auto &c:calls) if(k<(int)c.size()){ s+=c[k].first; n++; } return s/(double)n; }
ComplexD MeanB(int k,int j) const { ComplexD s(0.0); int n=0; for(auto &c:calls) if(k<(int)c.size() && j<(int)c[k].second.size()){ s+=c[k].second[j]; n++; } return s/(double)n; }
void Report(const std::string &name) const {
const char *sel[4]={"last","first","index","mean"};
std::cout << GridLogMessage << "GCRCoefficients " << name << ": " << Calls() << " calls, " << Steps() << " steps, mmax " << mmax
<< ", selection " << sel[select] << std::endl;
for(int k=0;k<Steps();k++){
std::cout << GridLogMessage << " step " << k << " a=(" << real(A(k)) << "," << imag(A(k)) << ")";
for(int j=0;j<NB(k);j++) std::cout << " b[" << j << "]=(" << real(B(k,j)) << "," << imag(B(k,j)) << ")";
// spread of a_k across calls: how different the individual polynomials are
if ( calls.size()>1 ) {
RealD lo=1e300, hi=0; for(auto &c:calls) if(k<(int)c.size()){ RealD x=real(c[k].first), y=imag(c[k].first); RealD m=std::sqrt(x*x+y*y); lo=std::min(lo,m); hi=std::max(hi,m); }
std::cout << " |a| over calls [" << lo << "," << hi << "]";
}
std::cout << std::endl;
}
}
};
NAMESPACE_END(Grid);
@@ -36,7 +36,7 @@ class GeneralisedMinimalResidual : public OperatorFunction<Field> {
public:
using OperatorFunction<Field>::operator();
bool ErrorOnNoConverge; // Throw an assert when GMRES fails to converge,
bool ErrorOnNoConverge; // Throw an GRID_ASSERT when GMRES fails to converge,
// defaults to true
RealD Tolerance;
@@ -80,7 +80,7 @@ class GeneralisedMinimalResidual : public OperatorFunction<Field> {
conformable(psi, src);
RealD guess = norm2(psi);
assert(std::isnan(guess) == 0);
GRID_ASSERT(std::isnan(guess) == 0);
RealD cp;
RealD ssq = norm2(src);
@@ -135,7 +135,7 @@ class GeneralisedMinimalResidual : public OperatorFunction<Field> {
std::cout << GridLogMessage << "GeneralisedMinimalResidual did NOT converge" << std::endl;
if (ErrorOnNoConverge)
assert(0);
GRID_ASSERT(0);
}
RealD outerLoopBody(LinearOperatorBase<Field> &LinOp, const Field &src, Field &psi, RealD rsq) {
@@ -181,7 +181,7 @@ class GeneralisedMinimalResidual : public OperatorFunction<Field> {
}
}
assert(0); // Never reached
GRID_ASSERT(0); // Never reached
return cp;
}
@@ -175,7 +175,7 @@ public:
eresid(_eresid), MaxIter(_MaxIter),
diagonalisation(_diagonalisation),split_test(0),
Nevec_acc(_Nu)
{ assert( (Nk%Nu==0) && (Nm%Nu==0) ); };
{ GRID_ASSERT( (Nk%Nu==0) && (Nm%Nu==0) ); };
////////////////////////////////
// Helpers
@@ -206,7 +206,7 @@ public:
Glog<<"orthogonalize after: "<<j<<" of "<<k<<" "<< ip <<std::endl;
}
}
assert(normalize(w,if_print) != 0);
GRID_ASSERT(normalize(w,if_print) != 0);
}
void reorthogonalize(Field& w, std::vector<Field>& evec, int k)
{
@@ -225,7 +225,7 @@ public:
w[i] = w[i] - ip * evec[j];
}}
for(int i=0; i<_Nu; ++i)
assert(normalize(w[i],if_print) !=0);
GRID_ASSERT(normalize(w[i],if_print) !=0);
}
@@ -244,7 +244,7 @@ public:
const uint64_t sites = grid->lSites();
int Nbatch = R/Nevec_acc;
assert( R%Nevec_acc == 0 );
GRID_ASSERT( R%Nevec_acc == 0 );
// Glog << "nBatch, Nevec_acc, R, Nu = "
// << Nbatch << "," << Nevec_acc << "," << R << "," << Nu << std::endl;
@@ -302,7 +302,7 @@ public:
}
}
for (int i=0; i<Nu; ++i) {
assert(normalize(w[i],do_print)!=0);
GRID_ASSERT(normalize(w[i],do_print)!=0);
}
Glog << "cuBLAS Zgemm done"<< std::endl;
@@ -374,8 +374,8 @@ cudaStat = cudaMallocManaged((void **)&evec_acc, Nevec_acc*sites*12*sizeof(CUDA_
{
std::string fname = std::string(cname+"::calc_irbl()");
GridBase *grid = evec[0].Grid();
assert(grid == src[0].Grid());
assert( Nu = src.size() );
GRID_ASSERT(grid == src[0].Grid());
GRID_ASSERT( Nu = src.size() );
Glog << std::string(74,'*') << std::endl;
Glog << fname + " starting iteration 0 / "<< MaxIter<< std::endl;
@@ -396,7 +396,7 @@ cudaStat = cudaMallocManaged((void **)&evec_acc, Nevec_acc*sites*12*sizeof(CUDA_
}
Glog << std::string(74,'*') << std::endl;
assert(Nm == evec.size() && Nm == eval.size());
GRID_ASSERT(Nm == evec.size() && Nm == eval.size());
std::vector<std::vector<ComplexD>> lmd(Nu,std::vector<ComplexD>(Nm,0.0));
std::vector<std::vector<ComplexD>> lme(Nu,std::vector<ComplexD>(Nm,0.0));
@@ -579,8 +579,8 @@ cudaStat = cudaMallocManaged((void **)&evec_acc, Nevec_acc*sites*12*sizeof(CUDA_
{
std::string fname = std::string(cname+"::calc_rbl()");
GridBase *grid = evec[0].Grid();
assert(grid == src[0].Grid());
assert( Nu = src.size() );
GRID_ASSERT(grid == src[0].Grid());
GRID_ASSERT( Nu = src.size() );
int Np = (Nm-Nk);
if (Np > 0 && MaxIter > 1) Np /= MaxIter;
@@ -607,7 +607,7 @@ cudaStat = cudaMallocManaged((void **)&evec_acc, Nevec_acc*sites*12*sizeof(CUDA_
}
Glog << std::string(74,'*') << std::endl;
assert(Nm == evec.size() && Nm == eval.size());
GRID_ASSERT(Nm == evec.size() && Nm == eval.size());
std::vector<std::vector<ComplexD>> lmd(Nu,std::vector<ComplexD>(Nm,0.0));
std::vector<std::vector<ComplexD>> lme(Nu,std::vector<ComplexD>(Nm,0.0));
@@ -785,7 +785,7 @@ private:
int Nu = w.size();
int Nm = evec.size();
assert( b < Nm/Nu );
GRID_ASSERT( b < Nm/Nu );
// GridCartesian *grid = evec[0]._grid;
// converts block index to full indicies for an interval [L,R)
@@ -796,7 +796,7 @@ private:
Glog << "Using split grid"<< std::endl;
// LatticeGaugeField s_Umu(SGrid);
assert((Nu%mrhs)==0);
GRID_ASSERT((Nu%mrhs)==0);
std::vector<Field> in(mrhs,f_grid);
Field s_in(sf_grid);
@@ -906,7 +906,7 @@ if(split_test){
for (int u=0; u<Nu; ++u) {
// Glog << "norm2(w[" << u << "])= "<< norm2(w[u]) << std::endl;
assert (!isnan(norm2(w[u])));
GRID_ASSERT (!isnan(norm2(w[u])));
for (int k=L+u; k<R; ++k) {
Glog <<" In block "<< b << "," <<" beta[" << u << "," << k-L << "] = " << lme[u][k] << std::endl;
}
@@ -929,8 +929,8 @@ if(split_test){
Eigen::MatrixXcd & Qt, // Nm x Nm
GridBase *grid)
{
assert( Nk%Nu == 0 && Nm%Nu == 0 );
assert( Nk <= Nm );
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
GRID_ASSERT( Nk <= Nm );
Eigen::MatrixXcd BlockTriDiag = Eigen::MatrixXcd::Zero(Nk,Nk);
for ( int u=0; u<Nu; ++u ) {
@@ -970,8 +970,8 @@ if(split_test){
GridBase *grid)
{
Glog << "diagonalize_lapack: Nu= "<<Nu<<" Nk= "<<Nk<<" Nm= "<<std::endl;
assert( Nk%Nu == 0 && Nm%Nu == 0 );
assert( Nk <= Nm );
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
GRID_ASSERT( Nk <= Nm );
Eigen::MatrixXcd BlockTriDiag = Eigen::MatrixXcd::Zero(Nk,Nk);
for ( int u=0; u<Nu; ++u ) {
@@ -1119,7 +1119,7 @@ if (1){
diagonalize_lapack(eval,lmd,lme,Nu,Nk,Nm,Qt,grid);
#endif
} else {
assert(0);
GRID_ASSERT(0);
}
}
@@ -1131,8 +1131,8 @@ if (1){
Eigen::MatrixXcd& M)
{
//Glog << "unpackHermitBlockTriDiagMatToEigen() begin" << '\n';
assert( Nk%Nu == 0 && Nm%Nu == 0 );
assert( Nk <= Nm );
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
GRID_ASSERT( Nk <= Nm );
M = Eigen::MatrixXcd::Zero(Nk,Nk);
// rearrange
@@ -1159,8 +1159,8 @@ if (1){
Eigen::MatrixXcd& M)
{
//Glog << "packHermitBlockTriDiagMatfromEigen() begin" << '\n';
assert( Nk%Nu == 0 && Nm%Nu == 0 );
assert( Nk <= Nm );
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
GRID_ASSERT( Nk <= Nm );
// rearrange
for ( int u=0; u<Nu; ++u ) {
@@ -121,7 +121,7 @@ public:
eresid(_eresid), MaxIter(_MaxIter),
diagonalisation(_diagonalisation),
Nevec_acc(_Nu)
{ assert( (Nk%Nu==0) && (Nm%Nu==0) ); };
{ GRID_ASSERT( (Nk%Nu==0) && (Nm%Nu==0) ); };
////////////////////////////////
// Helpers
@@ -151,7 +151,7 @@ public:
Glog<<"orthogonalize after: "<<j<<" of "<<k<<" "<< ip <<std::endl;
}
}
assert(normalize(w,if_print) != 0);
GRID_ASSERT(normalize(w,if_print) != 0);
}
void reorthogonalize(Field& w, std::vector<Field>& evec, int k)
{
@@ -169,7 +169,7 @@ public:
w[i] = w[i] - ip * evec[j];
}}
for(int i=0; i<_Nu; ++i)
assert(normalize(w[i],if_print) !=0);
GRID_ASSERT(normalize(w[i],if_print) !=0);
}
void orthogonalize_blockhead(Field& w, std::vector<Field>& evec, int k, int Nu)
@@ -205,8 +205,8 @@ public:
{
std::string fname = std::string(cname+"::calc_irbl()");
GridBase *grid = evec[0].Grid();
assert(grid == src[0].Grid());
assert( Nu = src.size() );
GRID_ASSERT(grid == src[0].Grid());
GRID_ASSERT( Nu = src.size() );
Glog << std::string(74,'*') << std::endl;
Glog << fname + " starting iteration 0 / "<< MaxIter<< std::endl;
@@ -227,7 +227,7 @@ public:
}
Glog << std::string(74,'*') << std::endl;
assert(Nm == evec.size() && Nm == eval.size());
GRID_ASSERT(Nm == evec.size() && Nm == eval.size());
std::vector<std::vector<ComplexD>> lmd(Nu,std::vector<ComplexD>(Nm,0.0));
std::vector<std::vector<ComplexD>> lme(Nu,std::vector<ComplexD>(Nm,0.0));
@@ -413,8 +413,8 @@ public:
{
std::string fname = std::string(cname+"::calc_rbl()");
GridBase *grid = evec[0].Grid();
assert(grid == src[0].Grid());
assert( Nu = src.size() );
GRID_ASSERT(grid == src[0].Grid());
GRID_ASSERT( Nu = src.size() );
int Np = (Nm-Nk);
if (Np > 0 && MaxIter > 1) Np /= MaxIter;
@@ -441,7 +441,7 @@ public:
}
Glog << std::string(74,'*') << std::endl;
assert(Nm == evec.size() && Nm == eval.size());
GRID_ASSERT(Nm == evec.size() && Nm == eval.size());
std::vector<std::vector<ComplexD>> lmd(Nu,std::vector<ComplexD>(Nm,0.0));
std::vector<std::vector<ComplexD>> lme(Nu,std::vector<ComplexD>(Nm,0.0));
@@ -622,7 +622,7 @@ private:
int Nu = w.size();
int Nm = evec.size();
assert( b < Nm/Nu );
GRID_ASSERT( b < Nm/Nu );
// converts block index to full indicies for an interval [L,R)
int L = Nu*b;
@@ -630,7 +630,7 @@ private:
Real beta;
assert((Nu%mrhs)==0);
GRID_ASSERT((Nu%mrhs)==0);
std::vector<Field> in(mrhs,f_grid);
std::vector<Field> out(mrhs,f_grid);
@@ -711,7 +711,7 @@ private:
for (int u=0; u<Nu; ++u) {
// Glog << "norm2(w[" << u << "])= "<< norm2(w[u]) << std::endl;
assert (!isnan(norm2(w[u])));
GRID_ASSERT (!isnan(norm2(w[u])));
for (int k=L+u; k<R; ++k) {
// Glog <<" In block "<< b << "," <<" beta[" << u << "," << k-L << "] = " << lme[u][k] << std::endl;
}
@@ -734,8 +734,8 @@ private:
Eigen::MatrixXcd & Qt, // Nm x Nm
GridBase *grid)
{
assert( Nk%Nu == 0 && Nm%Nu == 0 );
assert( Nk <= Nm );
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
GRID_ASSERT( Nk <= Nm );
Eigen::MatrixXcd BlockTriDiag = Eigen::MatrixXcd::Zero(Nk,Nk);
for ( int u=0; u<Nu; ++u ) {
@@ -775,8 +775,8 @@ private:
GridBase *grid)
{
Glog << "diagonalize_lapack: Nu= "<<Nu<<" Nk= "<<Nk<<" Nm= "<<std::endl;
assert( Nk%Nu == 0 && Nm%Nu == 0 );
assert( Nk <= Nm );
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
GRID_ASSERT( Nk <= Nm );
Eigen::MatrixXcd BlockTriDiag = Eigen::MatrixXcd::Zero(Nk,Nk);
for ( int u=0; u<Nu; ++u ) {
@@ -924,7 +924,7 @@ if (1){
diagonalize_lapack(eval,lmd,lme,Nu,Nk,Nm,Qt,grid);
#endif
} else {
assert(0);
GRID_ASSERT(0);
}
}
@@ -936,8 +936,8 @@ if (1){
Eigen::MatrixXcd& M)
{
// Glog << "unpackHermitBlockTriDiagMatToEigen() begin" << '\n';
assert( Nk%Nu == 0 && Nm%Nu == 0 );
assert( Nk <= Nm );
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
GRID_ASSERT( Nk <= Nm );
M = Eigen::MatrixXcd::Zero(Nk,Nk);
// rearrange
@@ -964,8 +964,8 @@ if (1){
Eigen::MatrixXcd& M)
{
// Glog << "packHermitBlockTriDiagMatfromEigen() begin" << '\n';
assert( Nk%Nu == 0 && Nm%Nu == 0 );
assert( Nk <= Nm );
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
GRID_ASSERT( Nk <= Nm );
// rearrange
for ( int u=0; u<Nu; ++u ) {
@@ -211,7 +211,7 @@ until convergence
void calc(std::vector<RealD>& eval, std::vector<Field>& evec, const Field& src, int& Nconv, bool reverse=false)
{
GridBase *grid = src.Grid();
assert(grid == evec[0].Grid());
GRID_ASSERT(grid == evec[0].Grid());
// GridLogIRL.TimingMode(1);
std::cout << GridLogIRL <<"**************************************************************************"<< std::endl;
@@ -231,7 +231,7 @@ until convergence
}
std::cout << GridLogIRL <<"**************************************************************************"<< std::endl;
assert(Nm <= evec.size() && Nm <= eval.size());
GRID_ASSERT(Nm <= evec.size() && Nm <= eval.size());
// quickly get an idea of the largest eigenvalue to more properly normalize the residuum
RealD evalMaxApprox = 0.0;
@@ -245,9 +245,10 @@ until convergence
_HermOp(src_n,tmp);
// std::cout << GridLogMessage<< tmp<<std::endl; exit(0);
// std::cout << GridLogIRL << " _HermOp " << norm2(tmp) << std::endl;
RealD vnum = real(innerProduct(src_n,tmp)); // HermOp.
// RealD vnum = real(innerProduct(src_n,tmp)); // HermOp.
RealD vnum = real(innerProduct(tmp,tmp)); // HermOp^2.
RealD vden = norm2(src_n);
RealD na = vnum/vden;
RealD na = std::sqrt(vnum/vden);
if (fabs(evalMaxApprox/na - 1.0) < 0.0001)
i=_MAX_ITER_IRL_MEVAPP_;
evalMaxApprox = na;
@@ -255,6 +256,7 @@ until convergence
src_n = tmp;
}
}
std::cout << GridLogIRL << " Final evalMaxApprox " << evalMaxApprox << std::endl;
std::vector<RealD> lme(Nm);
std::vector<RealD> lme2(Nm);
@@ -335,7 +337,7 @@ until convergence
}
std::cout<<GridLogIRL <<"QR decomposed "<<std::endl;
assert(k2<Nm); assert(k2<Nm); assert(k1>0);
GRID_ASSERT(k2<Nm); GRID_ASSERT(k2<Nm); GRID_ASSERT(k1>0);
basisRotate(evec,Qt,k1-1,k2+1,0,Nm,Nm); /// big constraint on the basis
std::cout<<GridLogIRL <<"basisRotated by Qt *"<<k1-1<<","<<k2+1<<")"<<std::endl;
@@ -461,7 +463,7 @@ until convergence
{
std::cout<<GridLogDebug << "Lanczos step " <<k<<std::endl;
const RealD tiny = 1.0e-20;
assert( k< Nm );
GRID_ASSERT( k< Nm );
GridStopWatch gsw_op,gsw_o;
@@ -595,7 +597,7 @@ until convergence
} else if ( diagonalisation == IRLdiagonaliseWithEigen ) {
diagonalize_Eigen(lmd,lme,Nk,Nm,Qt,grid);
} else {
assert(0);
GRID_ASSERT(0);
}
}
@@ -685,7 +687,7 @@ void diagonalize_lapack(std::vector<RealD>& lmd,
}
}
#else
assert(0);
GRID_ASSERT(0);
#endif
}
@@ -80,7 +80,7 @@ public:
ProjectedHermOp(LinearOperatorBase<FineField>& linop, std::vector<FineField> & _subspace) :
_Linop(linop), subspace(_subspace)
{
assert(subspace.size() >0);
GRID_ASSERT(subspace.size() >0);
};
void operator()(const CoarseField& in, CoarseField& out) {
@@ -346,12 +346,12 @@ public:
void testFine(RealD resid)
{
assert(evals_fine.size() == nbasis);
assert(subspace.size() == nbasis);
GRID_ASSERT(evals_fine.size() == nbasis);
GRID_ASSERT(subspace.size() == nbasis);
PlainHermOp<FineField> Op(_FineOp);
ImplicitlyRestartedLanczosHermOpTester<FineField> SimpleTester(Op);
for(int k=0;k<nbasis;k++){
assert(SimpleTester.ReconstructEval(k,resid,subspace[k],evals_fine[k],1.0)==1);
GRID_ASSERT(SimpleTester.ReconstructEval(k,resid,subspace[k],evals_fine[k],1.0)==1);
}
}
@@ -359,8 +359,8 @@ public:
//hence the smoother can be tuned after running the coarse Lanczos by using a different smoother here
void testCoarse(RealD resid,ChebyParams cheby_smooth,RealD relax)
{
assert(evals_fine.size() == nbasis);
assert(subspace.size() == nbasis);
GRID_ASSERT(evals_fine.size() == nbasis);
GRID_ASSERT(subspace.size() == nbasis);
//////////////////////////////////////////////////////////////////////////////////////////////////
// create a smoother and see if we can get a cheap convergence test and smooth inside the IRL
//////////////////////////////////////////////////////////////////////////////////////////////////
@@ -380,7 +380,7 @@ public:
void calcFine(ChebyParams cheby_parms,int Nstop,int Nk,int Nm,RealD resid,
RealD MaxIt, RealD betastp, int MinRes)
{
assert(nbasis<=Nm);
GRID_ASSERT(nbasis<=Nm);
Chebyshev<FineField> Cheby(cheby_parms);
FunctionHermOp<FineField> ChebyOp(Cheby,_FineOp);
PlainHermOp<FineField> Op(_FineOp);
@@ -400,8 +400,8 @@ public:
IRL.calc(evals_fine,subspace,src,Nconv,false);
// Shrink down to number saved
assert(Nstop>=nbasis);
assert(Nconv>=nbasis);
GRID_ASSERT(Nstop>=nbasis);
GRID_ASSERT(Nconv>=nbasis);
evals_fine.resize(nbasis);
subspace.resize(nbasis,_FineGrid);
}
@@ -433,7 +433,7 @@ public:
ImplicitlyRestartedLanczos<CoarseField> IRL(ChebyOp,ChebyOp,ChebySmoothTester,Nstop,Nk,Nm,resid,MaxIt,betastp,MinRes);
int Nconv=0;
IRL.calc(evals_coarse,evec_coarse,src,Nconv,false);
assert(Nconv>=Nstop);
GRID_ASSERT(Nconv>=Nstop);
evals_coarse.resize(Nstop);
evec_coarse.resize (Nstop,_CoarseGrid);
for (int i=0;i<Nstop;i++){
+4 -4
View File
@@ -35,7 +35,7 @@ template<class Field> class MinimalResidual : public OperatorFunction<Field> {
public:
using OperatorFunction<Field>::operator();
bool ErrorOnNoConverge; // throw an assert when the MR fails to converge.
bool ErrorOnNoConverge; // throw an GRID_ASSERT when the MR fails to converge.
// Defaults true.
RealD Tolerance;
Integer MaxIterations;
@@ -59,7 +59,7 @@ template<class Field> class MinimalResidual : public OperatorFunction<Field> {
// Initial residual computation & set up
RealD guess = norm2(psi);
assert(std::isnan(guess) == 0);
GRID_ASSERT(std::isnan(guess) == 0);
RealD ssq = norm2(src);
RealD rsq = Tolerance * Tolerance * ssq;
@@ -136,7 +136,7 @@ template<class Field> class MinimalResidual : public OperatorFunction<Field> {
std::cout << GridLogMessage << "MR Time elapsed: Linalg " << LinalgTimer.Elapsed() << std::endl;
if (ErrorOnNoConverge)
assert(true_residual / Tolerance < 10000.0);
GRID_ASSERT(true_residual / Tolerance < 10000.0);
IterationsToComplete = k;
@@ -148,7 +148,7 @@ template<class Field> class MinimalResidual : public OperatorFunction<Field> {
<< std::endl;
if (ErrorOnNoConverge)
assert(0);
GRID_ASSERT(0);
IterationsToComplete = k;
}
@@ -37,7 +37,7 @@ class MixedPrecisionFlexibleGeneralisedMinimalResidual : public OperatorFunction
using OperatorFunction<FieldD>::operator();
bool ErrorOnNoConverge; // Throw an assert when MPFGMRES fails to converge,
bool ErrorOnNoConverge; // Throw an GRID_ASSERT when MPFGMRES fails to converge,
// defaults to true
RealD Tolerance;
@@ -91,7 +91,7 @@ class MixedPrecisionFlexibleGeneralisedMinimalResidual : public OperatorFunction
conformable(psi, src);
RealD guess = norm2(psi);
assert(std::isnan(guess) == 0);
GRID_ASSERT(std::isnan(guess) == 0);
RealD cp;
RealD ssq = norm2(src);
@@ -150,7 +150,7 @@ class MixedPrecisionFlexibleGeneralisedMinimalResidual : public OperatorFunction
std::cout << GridLogMessage << "MPFGMRES did NOT converge" << std::endl;
if (ErrorOnNoConverge)
assert(0);
GRID_ASSERT(0);
}
RealD outerLoopBody(LinearOperatorBase<FieldD> &LinOp, const FieldD &src, FieldD &psi, RealD rsq) {
@@ -197,7 +197,7 @@ class MixedPrecisionFlexibleGeneralisedMinimalResidual : public OperatorFunction
}
}
assert(0); // Never reached
GRID_ASSERT(0); // Never reached
return cp;
}
+65
View File
@@ -43,4 +43,69 @@ template<class Field> class PowerMethod
return evalMaxApprox;
}
};
// Non-Hermitian sibling of PowerMethod. Drives Op() -- the operator the
// caller actually applies -- rather than HermOp(), so it returns |lambda_max|
// of a NON-Hermitian operator and reports the spectral-edge diagnostics the
// Hermitian PowerMethod cannot:
// step 0 : |A v|/|v| on the (random) start src -- a one-sample lower bound
// on sigma_max(A).
// step k : |A v_k| -> |lambda_max| as v_k -> the dominant eigenvector; the
// complex Rayleigh quotient <v,Av> gives its phase (real => on the
// axis, a non-converging oscillation => a conjugate pair of equal
// modulus at the top).
// return : |lambda_max|, with a SUMMARY line flagging non-normality when the
// step-0 sigma_max lower bound sits well above the converged
// |lambda_max| -- the case where the numerical range extends beyond
// the spectrum and a spectrum-based smoother/Chebyshev bound is
// unsafe (the field of values must be used instead).
template<class Field> class PowerMethodNonHermitian
{
public:
template<typename T> static RealD normalise(T& v)
{
RealD nn = sqrt(norm2(v));
v = v * (1.0/nn);
return nn;
}
RealD operator()(LinearOperatorBase<Field> &Op, const Field &src)
{
GridBase *grid = src.Grid();
Field v(grid), Av(grid);
v = src;
RealD ratio = 0.0, ratio0 = 0.0;
ComplexD rq(0.0);
const int _MAX_ITER_EST_ = 200;
for (int i=0;i<_MAX_ITER_EST_;i++) {
normalise(v); // v is now unit
Op.Op(v,Av);
ratio = sqrt(norm2(Av)); // |A v| = |lambda_max| in the limit
rq = innerProduct(v,Av); // complex Rayleigh quotient
if ( i==0 ) {
ratio0 = ratio;
std::cout << GridLogMessage << "PowerMethodNonHermitian: step 0 (random v): |Av|/|v| = "
<< ratio << " [lower bound on sigma_max]" << std::endl;
}
if ( (i%10==0) || (i==_MAX_ITER_EST_-1) )
std::cout << GridLogMessage << "PowerMethodNonHermitian: step " << i << " |Av|/|v| = " << ratio
<< " Rayleigh (" << real(rq) << "," << imag(rq) << ")" << std::endl;
v = Av;
}
std::cout << GridLogMessage << "PowerMethodNonHermitian: |lambda_max| ~ " << ratio
<< " Rayleigh (" << real(rq) << "," << imag(rq) << ")"
<< " phase " << atan2(imag(rq),real(rq)) << " rad"
<< " step-0/converged = " << ratio0/ratio
<< (ratio0/ratio > 1.2 ? " ** non-normal: sigma_max well above |lambda_max| **"
: " (near-normal)")
<< std::endl;
return ratio;
}
};
}
@@ -112,7 +112,7 @@ public:
}
std::cout<<GridLogMessage<<"PrecConjugateResidual did NOT converge"<<std::endl;
assert(0);
GRID_ASSERT(0);
}
};
NAMESPACE_END(Grid);
@@ -118,7 +118,7 @@ public:
}
GCRLogLevel<<"Variable Preconditioned GCR did not converge"<<std::endl;
// assert(0);
// GRID_ASSERT(0);
}
RealD GCRnStep(const Field &src, Field &psi,RealD rsq){
@@ -221,7 +221,7 @@ public:
int northog = ((kp)>(mmax-1))?(mmax-1):(kp); // if more than mmax done, we orthog all mmax history.
for(int back=0;back<northog;back++){
int peri_back=(k-back)%mmax; assert((k-back)>=0);
int peri_back=(k-back)%mmax; GRID_ASSERT((k-back)>=0);
b=-real(innerProduct(q[peri_back],Az))/qq[peri_back];
p[peri_kp]=p[peri_kp]+b*p[peri_back];
@@ -231,9 +231,10 @@ public:
qq[peri_kp]=norm2(q[peri_kp]); // could use axpy_norm
LinalgTimer.Stop();
}
assert(0); // never reached
GRID_ASSERT(0); // never reached
return cp;
}
};
NAMESPACE_END(Grid);
#undef GCRLogLevel
#endif
@@ -28,6 +28,7 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
/* END LEGAL */
#ifndef GRID_PREC_GCR_NON_HERM_H
#define GRID_PREC_GCR_NON_HERM_H
#include <Grid/algorithms/iterative/GCRCoefficients.h>
///////////////////////////////////////////////////////////////////////////////////////////////////////
//VPGCR Abe and Zhang, 2005.
@@ -38,13 +39,14 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
///////////////////////////////////////////////////////////////////////////////////////////////////////
NAMESPACE_BEGIN(Grid);
#define GCRLogLevel std::cout << GridLogMessage <<std::string(level,'\t')<< " Level "<<level<<" "
#define GCRLogLevel std::cout << GridLogMessage <<std::string(level,'\t')<< name<<" "
template<class Field>
class PrecGeneralisedConjugateResidualNonHermitian : public LinearFunction<Field> {
public:
using LinearFunction<Field>::operator();
RealD Tolerance;
RealD SSQ;
Integer MaxIterations;
int verbose;
int mmax;
@@ -54,11 +56,38 @@ public:
GridStopWatch PrecTimer;
GridStopWatch MatTimer;
GridStopWatch LinalgTimer;
std::string name;
int ZeroGuess = 0; // caller contract: guess is always zero => first-cycle r0 = src, skip the apply
// persistent GCR history (see GCRnStep)
GridBase *hist_grid = nullptr;
std::vector<Field> q;
std::vector<Field> p;
std::vector<RealD> qq;
int FirstCycle = 0;
LinearFunction<Field> &Preconditioner;
LinearOperatorBase<Field> &Linop;
void Level(int lv) { level=lv; };
void Name(std::string _name) { name = _name; };
void Level(int n) { Name("Level " + std::to_string(n)); level = n; }
void SetZeroGuess(int z) { ZeroGuess = z; };
// Coefficient logging: one line per step with the step length a_k and the
// orthogonalisation coefficients b_{k,j}. These are the data from which a
// FIXED polynomial smoother can be harvested: if they are stable from call
// to call, the adaptive GCR can be replaced by a stationary p(A) with the
// same applies and no reductions. Off by default; boss rank prints.
int LogCoeffs = 0;
void LogCoefficients(int l) { LogCoeffs = l; };
// Optional recorder of the per-step coefficients (means over calls), for
// replay by GCRReplaySmoother (Smoothers.h). Records only; no effect on
// the iteration.
GCRCoefficients *Recorder = nullptr;
void SetCoefficientRecorder(GCRCoefficients *r) { Recorder = r; if(r) r->mmax = mmax; };
// Free the persistent history (e.g. when this solver is replaced by a
// replayed polynomial): re-made on the next call if ever needed again.
void ReleaseHistory(void) { q.clear(); p.clear(); qq.clear(); hist_grid = nullptr; };
PrecGeneralisedConjugateResidualNonHermitian(RealD tol,Integer maxit,LinearOperatorBase<Field> &_Linop,LinearFunction<Field> &Prec,int _mmax,int _nstep) :
Tolerance(tol),
@@ -67,16 +96,17 @@ public:
Preconditioner(Prec),
mmax(_mmax),
nstep(_nstep)
{
level=1;
{
Level(1);
verbose=1;
};
void operator() (const Field &src, Field &psi){
psi=Zero();
// psi=Zero();
RealD cp, ssq,rsq;
ssq=norm2(src);
SSQ=ssq;
rsq=Tolerance*Tolerance*ssq;
Field r(src.Grid());
@@ -89,11 +119,12 @@ public:
SolverTimer.Start();
steps=0;
FirstCycle=1;
for(int k=0;k<MaxIterations;k++){
cp=GCRnStep(src,psi,rsq);
GCRLogLevel <<"PGCR("<<mmax<<","<<nstep<<") "<< steps <<" steps cp = "<<cp<<" target "<<rsq <<std::endl;
GCRLogLevel <<"PGCR("<<mmax<<","<<nstep<<") "<< steps <<" steps cp = "<<sqrt(cp/ssq)<<" target "<<sqrt(rsq/ssq) <<std::endl;
if(cp<rsq) {
@@ -113,77 +144,85 @@ public:
}
GCRLogLevel<<"Variable Preconditioned GCR did not converge"<<std::endl;
// assert(0);
// GRID_ASSERT(0);
}
RealD GCRnStep(const Field &src, Field &psi,RealD rsq){
RealD cp;
ComplexD a, b;
// ComplexD zAz;
RealD zAAz;
ComplexD rq;
GridBase *grid = src.Grid();
// Only r and one scratch for the restart residual; the new p/q directions
// are produced directly in their persistent history slots.
Field r(grid);
Field z(grid);
Field tmp(grid);
Field ttmp(grid);
Field Az(grid);
////////////////////////////////
// history for flexible orthog
////////////////////////////////
std::vector<Field> q(mmax,grid);
std::vector<Field> p(mmax,grid);
std::vector<RealD> qq(mmax);
// History arrays are PERSISTENT across calls (allocated once per grid,
// re-made only if the grid or mmax changes). The per-call form
// std::vector<Field>(mmax,grid) built a temporary and copy-constructed it
// mmax times on every restart cycle -- measured ~5 ms per fine-smoother
// call. Safe: every entry is written before it is read within a cycle
// (q[kp],p[kp] assigned before the northog loop can reach them), so no
// stale content is ever consumed.
if ( hist_grid != grid || (int)q.size() != mmax ) {
q.clear(); p.clear();
q.reserve(mmax); p.reserve(mmax);
for(int i=0;i<mmax;i++){ q.emplace_back(grid); p.emplace_back(grid); }
qq.assign(mmax, 0.0);
hist_grid = grid;
}
GCRLogLevel<< "PGCR nStep("<<nstep<<")"<<std::endl;
//////////////////////////////////
// initial guess x0 is taken as nonzero.
// r0=src-A x0 = src
// r0 = src - A x0. ZeroGuess: on the first cycle x0==0 by caller
// contract (enforced here), so r0 = src exactly; skip the apply.
// Restart cycles (psi!=0) always do the full computation.
//////////////////////////////////
MatTimer.Start();
Linop.Op(psi,Az);
// zAz = innerProduct(Az,psi);
zAAz= norm2(Az);
MatTimer.Stop();
if (ZeroGuess && FirstCycle) {
psi = Zero();
LinalgTimer.Start();
r = src;
LinalgTimer.Stop();
} else {
MatTimer.Start();
Linop.Op(psi,Az);
MatTimer.Stop();
LinalgTimer.Start();
r=src-Az;
LinalgTimer.Stop();
}
FirstCycle=0;
LinalgTimer.Start();
r=src-Az;
LinalgTimer.Stop();
GCRLogLevel<< "PGCR true residual r = src - A psi "<<norm2(r) <<std::endl;
/////////////////////
// p = Prec(r)
/////////////////////
// p[0] = Prec(r), q[0] = A p[0], written straight into the history slots
PrecTimer.Start();
Preconditioner(r,z);
Preconditioner(r,p[0]);
PrecTimer.Stop();
MatTimer.Start();
Linop.Op(z,Az);
Linop.Op(p[0],q[0]);
MatTimer.Stop();
LinalgTimer.Start();
// zAz = innerProduct(Az,psi);
zAAz= norm2(Az);
qq[0]= norm2(q[0]);
//p[0],q[0],qq[0]
p[0]= z;
q[0]= Az;
qq[0]= zAAz;
cp =norm2(r);
LinalgTimer.Stop();
GCRLogLevel<< "PGCR true residual "<< sqrt(cp/SSQ) <<std::endl;
for(int k=0;k<nstep;k++){
GRID_TRACE("PGCR_step");
steps++;
int kp = k+1;
@@ -193,50 +232,76 @@ public:
LinalgTimer.Start();
rq= innerProduct(q[peri_k],r); // what if rAr not real?
a = rq/qq[peri_k];
if ( Recorder ) Recorder->RecordA(k,a);
axpy(psi,a,p[peri_k],psi);
cp = axpy_norm(r,-a,q[peri_k],r);
LinalgTimer.Stop();
if ( LogCoeffs ) {
GCRLogLevel<<"coeff["<<k<<"] a=("<<real(a)<<","<<imag(a)<<")"
<<" |r|/|r0|="<<sqrt(cp/SSQ)<<std::endl;
}
GCRLogLevel<< "PGCR step["<<steps<<"] resid " << cp << " target " <<rsq<<std::endl;
GCRLogLevel<< "PGCR step["<<steps<<"] resid " << sqrt(cp/SSQ)<<std::endl;
if((k==nstep-1)||(cp<rsq)){
return cp;
}
// New direction written straight into its history slot: p = Prec(r), q = A p.
PrecTimer.Start();
Preconditioner(r,z);// solve Az = r
Preconditioner(r,p[peri_kp]);
PrecTimer.Stop();
MatTimer.Start();
Linop.Op(z,Az);
Linop.Op(p[peri_kp],q[peri_kp]);
MatTimer.Stop();
// zAz = innerProduct(Az,psi);
zAAz= norm2(Az);
LinalgTimer.Start();
q[peri_kp]=Az;
p[peri_kp]=z;
int northog = ((kp)>(mmax-1))?(mmax-1):(kp); // if more than mmax done, we orthog all mmax history.
std::ostringstream bs;
// Classical Gram-Schmidt: every coefficient is taken against the
// UN-updated new q (so all northog inner products are independent and
// batchable), then the window is applied. The coefficient is complex:
// for a non-Hermitian operator <q_j,Aq> is complex and keeping only the
// real part left the q's non-orthogonal.
// Batched: one fused kernel + one reduction for all coefficients,
// one fused pass per update (independent of mmax).
std::vector<const Field*> qwin(northog), pwin(northog);
for(int back=0;back<northog;back++){
int peri_back=(k-back)%mmax; assert((k-back)>=0);
b=-real(innerProduct(q[peri_back],Az))/qq[peri_back];
p[peri_kp]=p[peri_kp]+b*p[peri_back];
q[peri_kp]=q[peri_kp]+b*q[peri_back];
int peri_back=(k-back)%mmax; GRID_ASSERT((k-back)>=0);
GRID_ASSERT(peri_back!=peri_kp);
qwin[back] = &q[peri_back];
pwin[back] = &p[peri_back];
}
std::vector<ComplexD> bcoef;
innerProductMulti(bcoef,qwin,q[peri_kp]);
for(int back=0;back<northog;back++){
int peri_back=(k-back)%mmax;
bcoef[back] = -bcoef[back]/qq[peri_back];
if ( LogCoeffs ) bs<<" b["<<back<<"]="<<bcoef[back];
}
if ( Recorder && northog ) Recorder->RecordB(k,bcoef);
if ( northog ) {
axpyMulti(p[peri_kp],bcoef,pwin);
qq[peri_kp]=axpyMultiNorm(q[peri_kp],bcoef,qwin);
} else {
qq[peri_kp]=norm2(q[peri_kp]);
}
if ( LogCoeffs && northog ) {
GCRLogLevel<<"coeff["<<k<<"]"<<bs.str()<<std::endl;
}
qq[peri_kp]=norm2(q[peri_kp]); // could use axpy_norm
LinalgTimer.Stop();
}
assert(0); // never reached
GRID_ASSERT(0); // never reached
return cp;
}
};
NAMESPACE_END(Grid);
#undef GCRLogLevel
#endif
@@ -79,7 +79,7 @@ class QuasiMinimalResidual : public OperatorFunction<Field> {
LinOp.Op(x,r); r = b - r;
assert(normb> 0.0);
GRID_ASSERT(normb> 0.0);
resid = norm2(r)/normb;
if (resid <= Tolerance) {
@@ -105,8 +105,8 @@ class QuasiMinimalResidual : public OperatorFunction<Field> {
for (int i = 1; i <= MaxIterations; i++) {
// Breakdown tests
assert( rho != 0.0);
assert( xi != 0.0);
GRID_ASSERT( rho != 0.0);
GRID_ASSERT( xi != 0.0);
v = (1. / rho) * v_tld;
y = (1. / rho) * y;
@@ -134,10 +134,10 @@ class QuasiMinimalResidual : public OperatorFunction<Field> {
ep=Zep.real();
std::cout << "Zep "<<Zep <<std::endl;
// Complex Audit
assert(abs(ep)>0);
GRID_ASSERT(abs(ep)>0);
beta = ep / delta;
assert(abs(beta)>0);
GRID_ASSERT(abs(beta)>0);
v_tld = p_tld - beta * v;
y = v_tld;
@@ -158,7 +158,7 @@ class QuasiMinimalResidual : public OperatorFunction<Field> {
std::cout << "theta "<<theta<<std::endl;
std::cout << "gamma "<<gamma<<std::endl;
assert(abs(gamma)> 0.0);
GRID_ASSERT(abs(gamma)> 0.0);
eta = -eta * rho_1 * gamma* gamma / (beta * gamma_1 * gamma_1);
@@ -178,7 +178,7 @@ class QuasiMinimalResidual : public OperatorFunction<Field> {
}
std::cout << "Iteration "<<i<<" resid " << resid<<std::endl;
}
assert(0);
GRID_ASSERT(0);
return; // no convergence
}
#else
+54 -54
View File
@@ -327,9 +327,9 @@ namespace Grid {
/////////////////////////////////////////////////////
// src_o = (source_o - Moe MeeInv source_e)
/////////////////////////////////////////////////////
_Matrix.MooeeInv(src_e,tmp); assert( tmp.Checkerboard() ==Even);
_Matrix.Meooe (tmp,Mtmp); assert( Mtmp.Checkerboard() ==Odd);
tmp=src_o-Mtmp; assert( tmp.Checkerboard() ==Odd);
_Matrix.MooeeInv(src_e,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
_Matrix.Meooe (tmp,Mtmp); GRID_ASSERT( Mtmp.Checkerboard() ==Odd);
tmp=src_o-Mtmp; GRID_ASSERT( tmp.Checkerboard() ==Odd);
_Matrix.Mooee(tmp,src_o); // Extra factor of "m" in source from dumb choice of matrix norm.
}
@@ -347,17 +347,17 @@ namespace Grid {
///////////////////////////////////////////////////
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
///////////////////////////////////////////////////
_Matrix.Meooe(sol_o,tmp); assert( tmp.Checkerboard() ==Even);
src_e = src_e-tmp; assert( src_e.Checkerboard() ==Even);
_Matrix.MooeeInv(src_e,sol_e); assert( sol_e.Checkerboard() ==Even);
_Matrix.Meooe(sol_o,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
src_e = src_e-tmp; GRID_ASSERT( src_e.Checkerboard() ==Even);
_Matrix.MooeeInv(src_e,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_e); assert( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_o); assert( sol_o.Checkerboard() ==Odd );
setCheckerboard(sol,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_o); GRID_ASSERT( sol_o.Checkerboard() ==Odd );
}
virtual void RedBlackSolve (Matrix & _Matrix,const Field &src_o, Field &sol_o)
{
SchurStaggeredOperator<Matrix,Field> _HermOpEO(_Matrix);
this->_HermitianRBSolver(_HermOpEO,src_o,sol_o); assert(sol_o.Checkerboard()==Odd);
this->_HermitianRBSolver(_HermOpEO,src_o,sol_o); GRID_ASSERT(sol_o.Checkerboard()==Odd);
};
virtual void RedBlackSolve (Matrix & _Matrix,const std::vector<Field> &src_o, std::vector<Field> &sol_o)
{
@@ -396,13 +396,13 @@ namespace Grid {
/////////////////////////////////////////////////////
// src_o = Mdag * (source_o - Moe MeeInv source_e)
/////////////////////////////////////////////////////
_Matrix.MooeeInv(src_e,tmp); assert( tmp.Checkerboard() ==Even);
_Matrix.Meooe (tmp,Mtmp); assert( Mtmp.Checkerboard() ==Odd);
tmp=src_o-Mtmp; assert( tmp.Checkerboard() ==Odd);
_Matrix.MooeeInv(src_e,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
_Matrix.Meooe (tmp,Mtmp); GRID_ASSERT( Mtmp.Checkerboard() ==Odd);
tmp=src_o-Mtmp; GRID_ASSERT( tmp.Checkerboard() ==Odd);
// get the right MpcDag
SchurDiagMooeeOperator<Matrix,Field> _HermOpEO(_Matrix);
_HermOpEO.MpcDag(tmp,src_o); assert(src_o.Checkerboard() ==Odd);
_HermOpEO.MpcDag(tmp,src_o); GRID_ASSERT(src_o.Checkerboard() ==Odd);
}
virtual void RedBlackSolution(Matrix & _Matrix,const Field &sol_o, const Field &src_e,Field &sol)
@@ -416,17 +416,17 @@ namespace Grid {
///////////////////////////////////////////////////
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
///////////////////////////////////////////////////
_Matrix.Meooe(sol_o,tmp); assert( tmp.Checkerboard() ==Even);
src_e_i = src_e-tmp; assert( src_e_i.Checkerboard() ==Even);
_Matrix.MooeeInv(src_e_i,sol_e); assert( sol_e.Checkerboard() ==Even);
_Matrix.Meooe(sol_o,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
src_e_i = src_e-tmp; GRID_ASSERT( src_e_i.Checkerboard() ==Even);
_Matrix.MooeeInv(src_e_i,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_e); assert( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_o); assert( sol_o.Checkerboard() ==Odd );
setCheckerboard(sol,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_o); GRID_ASSERT( sol_o.Checkerboard() ==Odd );
}
virtual void RedBlackSolve (Matrix & _Matrix,const Field &src_o, Field &sol_o)
{
SchurDiagMooeeOperator<Matrix,Field> _HermOpEO(_Matrix);
this->_HermitianRBSolver(_HermOpEO,src_o,sol_o); assert(sol_o.Checkerboard()==Odd);
this->_HermitianRBSolver(_HermOpEO,src_o,sol_o); GRID_ASSERT(sol_o.Checkerboard()==Odd);
};
virtual void RedBlackSolve (Matrix & _Matrix,const std::vector<Field> &src_o, std::vector<Field> &sol_o)
{
@@ -461,9 +461,9 @@ namespace Grid {
/////////////////////////////////////////////////////
// src_o = Mdag * (source_o - Moe MeeInv source_e)
/////////////////////////////////////////////////////
_Matrix.MooeeInv(src_e, tmp); assert( tmp.Checkerboard() == Even );
_Matrix.Meooe (tmp, Mtmp); assert( Mtmp.Checkerboard() == Odd );
src_o -= Mtmp; assert( src_o.Checkerboard() == Odd );
_Matrix.MooeeInv(src_e, tmp); GRID_ASSERT( tmp.Checkerboard() == Even );
_Matrix.Meooe (tmp, Mtmp); GRID_ASSERT( Mtmp.Checkerboard() == Odd );
src_o -= Mtmp; GRID_ASSERT( src_o.Checkerboard() == Odd );
}
virtual void RedBlackSolution(Matrix& _Matrix, const Field& sol_o, const Field& src_e, Field& sol)
@@ -478,18 +478,18 @@ namespace Grid {
///////////////////////////////////////////////////
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
///////////////////////////////////////////////////
_Matrix.Meooe(sol_o, tmp); assert( tmp.Checkerboard() == Even );
src_e_i = src_e - tmp; assert( src_e_i.Checkerboard() == Even );
_Matrix.MooeeInv(src_e_i, sol_e); assert( sol_e.Checkerboard() == Even );
_Matrix.Meooe(sol_o, tmp); GRID_ASSERT( tmp.Checkerboard() == Even );
src_e_i = src_e - tmp; GRID_ASSERT( src_e_i.Checkerboard() == Even );
_Matrix.MooeeInv(src_e_i, sol_e); GRID_ASSERT( sol_e.Checkerboard() == Even );
setCheckerboard(sol, sol_e); assert( sol_e.Checkerboard() == Even );
setCheckerboard(sol, sol_o); assert( sol_o.Checkerboard() == Odd );
setCheckerboard(sol, sol_e); GRID_ASSERT( sol_e.Checkerboard() == Even );
setCheckerboard(sol, sol_o); GRID_ASSERT( sol_o.Checkerboard() == Odd );
}
virtual void RedBlackSolve(Matrix& _Matrix, const Field& src_o, Field& sol_o)
{
NonHermitianSchurDiagMooeeOperator<Matrix,Field> _OpEO(_Matrix);
this->_HermitianRBSolver(_OpEO, src_o, sol_o); assert(sol_o.Checkerboard() == Odd);
this->_HermitianRBSolver(_OpEO, src_o, sol_o); GRID_ASSERT(sol_o.Checkerboard() == Odd);
}
virtual void RedBlackSolve(Matrix& _Matrix, const std::vector<Field>& src_o, std::vector<Field>& sol_o)
@@ -539,13 +539,13 @@ namespace Grid {
/////////////////////////////////////////////////////
// src_o = Mpcdag *MooeeInv * (source_o - Moe MeeInv source_e)
/////////////////////////////////////////////////////
_Matrix.MooeeInv(src_e,tmp); assert( tmp.Checkerboard() ==Even);
_Matrix.Meooe (tmp,Mtmp); assert( Mtmp.Checkerboard() ==Odd);
_Matrix.MooeeInv(src_e,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
_Matrix.Meooe (tmp,Mtmp); GRID_ASSERT( Mtmp.Checkerboard() ==Odd);
Mtmp=src_o-Mtmp;
_Matrix.MooeeInv(Mtmp,tmp); assert( tmp.Checkerboard() ==Odd);
_Matrix.MooeeInv(Mtmp,tmp); GRID_ASSERT( tmp.Checkerboard() ==Odd);
// get the right MpcDag
_HermOpEO.MpcDag(tmp,src_o); assert(src_o.Checkerboard() ==Odd);
_HermOpEO.MpcDag(tmp,src_o); GRID_ASSERT(src_o.Checkerboard() ==Odd);
}
virtual void RedBlackSolution(Matrix & _Matrix,const Field &sol_o, const Field &src_e,Field &sol)
@@ -560,12 +560,12 @@ namespace Grid {
///////////////////////////////////////////////////
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
///////////////////////////////////////////////////
_Matrix.Meooe(sol_o,tmp); assert( tmp.Checkerboard() ==Even);
tmp = src_e-tmp; assert( src_e.Checkerboard() ==Even);
_Matrix.MooeeInv(tmp,sol_e); assert( sol_e.Checkerboard() ==Even);
_Matrix.Meooe(sol_o,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
tmp = src_e-tmp; GRID_ASSERT( src_e.Checkerboard() ==Even);
_Matrix.MooeeInv(tmp,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_e); assert( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_o); assert( sol_o.Checkerboard() ==Odd );
setCheckerboard(sol,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_o); GRID_ASSERT( sol_o.Checkerboard() ==Odd );
};
virtual void RedBlackSolve (Matrix & _Matrix,const Field &src_o, Field &sol_o)
@@ -612,12 +612,12 @@ namespace Grid {
/////////////////////////////////////////////////////
// src_o = Mdag * (source_o - Moe MeeInv source_e)
/////////////////////////////////////////////////////
_Matrix.MooeeInv(src_e,tmp); assert( tmp.Checkerboard() ==Even);
_Matrix.Meooe (tmp,Mtmp); assert( Mtmp.Checkerboard() ==Odd);
tmp=src_o-Mtmp; assert( tmp.Checkerboard() ==Odd);
_Matrix.MooeeInv(src_e,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
_Matrix.Meooe (tmp,Mtmp); GRID_ASSERT( Mtmp.Checkerboard() ==Odd);
tmp=src_o-Mtmp; GRID_ASSERT( tmp.Checkerboard() ==Odd);
// get the right MpcDag
_HermOpEO.MpcDag(tmp,src_o); assert(src_o.Checkerboard() ==Odd);
_HermOpEO.MpcDag(tmp,src_o); GRID_ASSERT(src_o.Checkerboard() ==Odd);
}
virtual void RedBlackSolution(Matrix & _Matrix,const Field &sol_o, const Field &src_e,Field &sol)
@@ -638,12 +638,12 @@ namespace Grid {
///////////////////////////////////////////////////
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
///////////////////////////////////////////////////
_Matrix.Meooe(sol_o_i,tmp); assert( tmp.Checkerboard() ==Even);
tmp = src_e-tmp; assert( src_e.Checkerboard() ==Even);
_Matrix.MooeeInv(tmp,sol_e); assert( sol_e.Checkerboard() ==Even);
_Matrix.Meooe(sol_o_i,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
tmp = src_e-tmp; GRID_ASSERT( src_e.Checkerboard() ==Even);
_Matrix.MooeeInv(tmp,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_e); assert( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_o_i); assert( sol_o_i.Checkerboard() ==Odd );
setCheckerboard(sol,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
setCheckerboard(sol,sol_o_i); GRID_ASSERT( sol_o_i.Checkerboard() ==Odd );
};
virtual void RedBlackSolve (Matrix & _Matrix,const Field &src_o, Field &sol_o)
@@ -684,9 +684,9 @@ namespace Grid {
/////////////////////////////////////////////////////
// src_o = Mdag * (source_o - Moe MeeInv source_e)
/////////////////////////////////////////////////////
_Matrix.MooeeInv(src_e, tmp); assert( tmp.Checkerboard() == Even );
_Matrix.Meooe (tmp, Mtmp); assert( Mtmp.Checkerboard() == Odd );
src_o -= Mtmp; assert( src_o.Checkerboard() == Odd );
_Matrix.MooeeInv(src_e, tmp); GRID_ASSERT( tmp.Checkerboard() == Even );
_Matrix.Meooe (tmp, Mtmp); GRID_ASSERT( Mtmp.Checkerboard() == Odd );
src_o -= Mtmp; GRID_ASSERT( src_o.Checkerboard() == Odd );
}
virtual void RedBlackSolution(Matrix& _Matrix, const Field& sol_o, const Field& src_e, Field& sol)
@@ -707,12 +707,12 @@ namespace Grid {
///////////////////////////////////////////////////
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
///////////////////////////////////////////////////
_Matrix.Meooe(sol_o_i, tmp); assert( tmp.Checkerboard() == Even );
tmp = src_e - tmp; assert( src_e.Checkerboard() == Even );
_Matrix.MooeeInv(tmp, sol_e); assert( sol_e.Checkerboard() == Even );
_Matrix.Meooe(sol_o_i, tmp); GRID_ASSERT( tmp.Checkerboard() == Even );
tmp = src_e - tmp; GRID_ASSERT( src_e.Checkerboard() == Even );
_Matrix.MooeeInv(tmp, sol_e); GRID_ASSERT( sol_e.Checkerboard() == Even );
setCheckerboard(sol, sol_e); assert( sol_e.Checkerboard() == Even );
setCheckerboard(sol, sol_o_i); assert( sol_o_i.Checkerboard() == Odd );
setCheckerboard(sol, sol_e); GRID_ASSERT( sol_e.Checkerboard() == Even );
setCheckerboard(sol, sol_o_i); GRID_ASSERT( sol_o_i.Checkerboard() == Odd );
};
virtual void RedBlackSolve(Matrix& _Matrix, const Field& src_o, Field& sol_o)
+98 -9
View File
@@ -30,6 +30,8 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
/* END LEGAL */
#pragma once
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
NAMESPACE_BEGIN(Grid);
inline RealD AggregatePowerLaw(RealD x)
@@ -64,7 +66,21 @@ public:
{
};
void GlobalOrthonormalise(void)
{
// Normalise all vectors
for(int i=0;i<nbasis; i++){
RealD scale = std::pow(norm2(subspace[i]),-0.5);
subspace[i] = subspace[i]*scale;
}
for(int i=0;i<nbasis; i++){
for(int j=0;j<i; j++){
basisOrthogonalize(subspace,subspace[i],j);
}
RealD scale = std::pow(norm2(subspace[i]),-0.5);
subspace[i] = subspace[i]*scale;
}
}
void Orthogonalise(void){
CoarseScalar InnerProd(CoarseGrid);
// std::cout << GridLogMessage <<" Block Gramm-Schmidt pass 1"<<std::endl;
@@ -95,7 +111,7 @@ public:
RealD scale;
ConjugateGradient<FineField> CG(1.0e-2,100,false);
ConjugateGradient<FineField> CG(1.0e-4,2000,false);
FineField noise(FineGrid);
FineField Mn(FineGrid);
@@ -108,14 +124,16 @@ public:
hermop.Op(noise,Mn); std::cout<<GridLogMessage << "noise ["<<b<<"] <n|MdagM|n> "<<norm2(Mn)<<std::endl;
for(int i=0;i<1;i++){
for(int i=0;i<2;i++){
CG(hermop,noise,subspace[b]);
noise = subspace[b];
scale = std::pow(norm2(noise),-0.5);
noise=noise*scale;
hermop.Op(noise,Mn); std::cout<<GridLogMessage << "intermediate["<<i<<"] <i|MdagM|i> "<<norm2(Mn)<<std::endl;
}
hermop.Op(noise,Mn); std::cout<<GridLogMessage << "filtered["<<b<<"] <f|MdagM|f> "<<norm2(Mn)<<std::endl;
@@ -124,6 +142,60 @@ public:
}
}
virtual void CreateSubspaceGCR(GridParallelRNG &RNG,LinearOperatorBase<FineField> &DiracOp,int nn=nbasis)
{
RealD scale;
TrivialPrecon<FineField> simple_fine;
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,10,DiracOp,simple_fine,30,30);
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,10,DiracOp,simple_fine,12,12);
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,30,DiracOp,simple_fine,12,12);
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.0005,30,DiracOp,simple_fine,20,20);
PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.0005,30,DiracOp,simple_fine,10,10);
FineField noise(FineGrid);
FineField src(FineGrid);
FineField guess(FineGrid);
FineField Mn(FineGrid);
for(int b=0;b<nn;b++){
subspace[b] = Zero();
gaussian(RNG,noise);
scale = std::pow(norm2(noise),-0.5);
noise=noise*scale;
DiracOp.Op(noise,Mn); std::cout<<GridLogMessage << "noise ["<<b<<"] <n|Op|n> "<<innerProduct(noise,Mn)<<std::endl;
for(int i=0;i<3;i++){
// void operator() (const Field &src, Field &psi){
#if 1
if (i==0)std::cout << GridLogMessage << " inverting on noise "<<std::endl;
src = noise;
guess=Zero();
GCR(src,guess);
subspace[b] = guess;
#else
if (i==0)std::cout << GridLogMessage << " inverting on zero "<<std::endl;
src=Zero();
guess = noise;
GCR(src,guess);
subspace[b] = guess;
#endif
noise = subspace[b];
scale = std::pow(norm2(noise),-0.5);
noise=noise*scale;
DiracOp.Op(noise,Mn); std::cout<<GridLogMessage << "intermediate["<<i<<"] <f|Op|f> "<<innerProduct(noise,Mn)<<" <f|OpDagOp|f>"<<norm2(Mn)<<std::endl;
}
DiracOp.Op(noise,Mn); std::cout<<GridLogMessage << "filtered["<<b<<"] <f|Op|f> "<<innerProduct(noise,Mn)<<" <f|OpDagOp|f>"<<norm2(Mn)<<std::endl;
subspace[b] = noise;
}
GlobalOrthonormalise();
}
////////////////////////////////////////////////////////////////////////////////////////////////
// World of possibilities here. But have tried quite a lot of experiments (250+ jobs run on Summit)
// and this is the best I found
@@ -160,14 +232,21 @@ public:
int b =0;
{
ComplexD ip;
// Filter
Chebyshev<FineField> Cheb(lo,hi,orderfilter);
Cheb(hermop,noise,Mn);
// normalise
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
subspace[b] = Mn;
hermop.Op(Mn,tmp);
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
hermop.Op(Mn,tmp);
ip= innerProduct(Mn,tmp);
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|Op|n> "<<norm2(tmp)<<" "<<ip<<std::endl;
hermop.AdjOp(Mn,tmp);
ip = innerProduct(Mn,tmp);
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|AdjOp|n> "<<norm2(tmp)<<" "<<ip<<std::endl;
b++;
}
@@ -213,8 +292,18 @@ public:
Mn=*Tnp;
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
subspace[b] = Mn;
hermop.Op(Mn,tmp);
std::cout<<GridLogMessage << n<<" filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
ComplexD ip;
hermop.Op(Mn,tmp);
ip= innerProduct(Mn,tmp);
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|Op|n> "<<norm2(tmp)<<" "<<ip<<std::endl;
hermop.AdjOp(Mn,tmp);
ip = innerProduct(Mn,tmp);
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|AdjOp|n> "<<norm2(tmp)<<" "<<ip<<std::endl;
b++;
}
@@ -226,7 +315,7 @@ public:
}
}
assert(b==nn);
GRID_ASSERT(b==nn);
}
+221
View File
@@ -0,0 +1,221 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/multigrid/BlockCyclic.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
NAMESPACE_BEGIN(Grid);
///////////////////////////////////////////////////////////////////////////////
// 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
// 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
// binds a world rank to a grid coordinate and caches local extents.
//
// Conventions (fixed here, relied on by every later stage):
//
// * Global block b of a dimension with Pg processes is owned by process
// coordinate b % Pg (ScaLAPACK csrc=0), and is that process's local
// block b / Pg.
// * Rank <-> grid coordinate is ROW MAJOR over the process grid:
// rank = p*Pc + q , p = rank/Pc , q = rank%Pc .
// The eventual ring transport must construct its neighbour tables with
// the same convention.
// * Local storage is COLUMN MAJOR with ld = mloc, matching BlockRows:
// local element (i,j) lives at data[i + j*mloc].
// * The trailing partial block (N % nb != 0) belongs to the owner of the
// last full-size block position; only that one block is short.
//
// Element (gi,gj) therefore lives on grid coordinate
// ( (gi/nb) % Pr , (gj/nb) % Pc )
// at local coordinate
// ( ((gi/nb)/Pr)*nb + gi%nb , ((gj/nb)/Pc)*nb + gj%nb ).
//
// Everything here is host-side integer arithmetic; nothing allocates.
///////////////////////////////////////////////////////////////////////////////
class BlockCyclicLayout
{
public:
///////////////////////////////////////////////////////////////////////////
// Closest-to-square factorisation Pr*Pc == P with Pr <= Pc.
// For fixed P the per-rank SUMMA volume N^2 (1/Pr + 1/Pc) is minimised
// at the most square grid. P=288 -> 16 x 18.
///////////////////////////////////////////////////////////////////////////
// Nearest-to-square factorisation with Pr >= Pc. The orientation matters
// for SUMMA: B panels travel along process columns in slots of
// S = ceil(Pc/Pr) panels, padded; with Pr >= Pc, S == 1 and there is no
// padding. (288 as 16x18 had S=2 -- half the B-ring bytes were zeros;
// 18x16 moves ~30% fewer bytes for the same inverse.)
static void ChooseProcessGrid(int P, int &Pr, int &Pc)
{
GRID_ASSERT(P >= 1);
int r = 1;
for(int f=1; (int64_t)f*f <= (int64_t)P; f++)
if ( P % f == 0 ) r = f;
Pc = r;
Pr = P / r;
}
///////////////////////////////////////////////////////////////////////////
// Number of rows (or columns) of a dimension of global extent N, block nb,
// owned by process coordinate p of Pg. ScaLAPACK "numroc", csrc=0.
///////////////////////////////////////////////////////////////////////////
static int64_t NumLocal(int64_t N, int64_t nb, int p, int Pg)
{
GRID_ASSERT(N >= 0);
GRID_ASSERT(nb >= 1);
GRID_ASSERT(p >= 0);
GRID_ASSERT(p < Pg);
int64_t nblocks = N / nb; // full blocks
int64_t extra = N % nb; // trailing partial block
int64_t full = nblocks / Pg; // full blocks everyone owns
int64_t rem = nblocks % Pg; // coords [0,rem) own one more
int64_t n = full*nb;
if ( p < (int)rem ) n += nb; // an extra full block
if ( p == (int)rem ) n += extra; // the partial block, if any
return n;
}
///////////////////////////////////////////////////////////////////////////
// Global index -> (owner coordinate, local index) in one dimension.
///////////////////////////////////////////////////////////////////////////
static void GlobalToLocal(int64_t g, int64_t nb, int Pg,
int &owner, int64_t &loc)
{
GRID_ASSERT(g >= 0);
int64_t b = g / nb; // global block
owner = (int)(b % Pg);
loc = (b / Pg)*nb + (g % nb);
}
///////////////////////////////////////////////////////////////////////////
// (process coordinate, local index) -> global index in one dimension.
// Inverse of GlobalToLocal on the owned set.
///////////////////////////////////////////////////////////////////////////
static int64_t LocalToGlobal(int64_t l, int64_t nb, int p, int Pg)
{
GRID_ASSERT(l >= 0);
int64_t lb = l / nb; // local block
int64_t b = lb*Pg + p; // global block
return b*nb + (l % nb);
}
///////////////////////////////////////////////////////////////////////////
// Instance layer: bind a rank of a Pr x Pc grid.
///////////////////////////////////////////////////////////////////////////
int64_t N; // global matrix dimension (square)
int64_t nb; // block size
int Pr, Pc; // process grid
int me; // world rank within the grid, row major
int prow, pcol;// my grid coordinate
int64_t mloc,nloc; // my local extents; storage column major, ld = mloc
BlockCyclicLayout(int64_t N_, int64_t nb_, int Pr_, int Pc_, int me_)
{
N = N_;
nb = nb_;
Pr = Pr_;
Pc = Pc_;
me = me_;
GRID_ASSERT( N >= 0 );
GRID_ASSERT( nb >= 1 );
GRID_ASSERT( Pr >= 1 );
GRID_ASSERT( Pc >= 1 );
GRID_ASSERT( me >= 0 );
GRID_ASSERT( me < Pr*Pc );
prow = me / Pc; // ROW MAJOR rank convention
pcol = me % Pc;
mloc = NumLocal(N, nb, prow, Pr);
nloc = NumLocal(N, nb, pcol, Pc);
}
// Owning rank of global element (gi,gj), row-major rank convention.
int OwnerRank(int64_t gi, int64_t gj) const
{
int pr,pc; int64_t li,lj;
GlobalToLocal(gi, nb, Pr, pr, li);
GlobalToLocal(gj, nb, Pc, pc, lj);
return pr*Pc + pc;
}
// My local storage offset of global element (gi,gj).
// The caller must know I own it; asserted, not assumed.
int64_t LocalOffset(int64_t gi, int64_t gj) const
{
int pr,pc; int64_t li,lj;
GlobalToLocal(gi, nb, Pr, pr, li);
GlobalToLocal(gj, nb, Pc, pc, lj);
GRID_ASSERT( pr == prow );
GRID_ASSERT( pc == pcol );
return li + lj*mloc; // column major, ld = mloc
}
// Do I own global element (gi,gj)?
int Owns(int64_t gi, int64_t gj) const
{
return OwnerRank(gi,gj) == me;
}
///////////////////////////////////////////////////////////////////////////
// Block-aligned global range [g0,g1) -> my contiguous local range [l0,l1).
//
// For fixed owner p the local index is monotone in the global index, so a
// coordinate's owned elements of ANY global range are contiguous in local
// storage; and for a BLOCK-ALIGNED range the bounds are exactly
// NumLocal(g0) and NumLocal(g1), because NumLocal(g,...) counts the owned
// elements below g. This is what lets a windowed product view the local
// sub-matrix of a global window as &data[l0 + c0*mloc] with the SAME ld --
// no gather, no copy. Verified exhaustively in Test_blockcyclic T7.
//
// g0 must be a block multiple; g1 a block multiple or N itself.
///////////////////////////////////////////////////////////////////////////
static void RangeToLocal(int64_t g0, int64_t g1,
int64_t N, int64_t nb, int p, int Pg,
int64_t &l0, int64_t &l1)
{
GRID_ASSERT( 0 <= g0 );
GRID_ASSERT( g0 <= g1 );
GRID_ASSERT( g1 <= N );
GRID_ASSERT( g0 % nb == 0 );
GRID_ASSERT( (g1 % nb == 0) || (g1 == N) );
l0 = NumLocal(g0, nb, p, Pg);
l1 = NumLocal(g1, nb, p, Pg);
}
// Instance forms, rows and columns of my own coordinate.
void RowRange(int64_t g0, int64_t g1, int64_t &l0, int64_t &l1) const
{ RangeToLocal(g0,g1,N,nb,prow,Pr,l0,l1); }
void ColRange(int64_t g0, int64_t g1, int64_t &l0, int64_t &l1) const
{ RangeToLocal(g0,g1,N,nb,pcol,Pc,l0,l1); }
// Size of global block b (the trailing block may be short).
int64_t BlockSize(int64_t b) const
{
int64_t lo = b*nb;
GRID_ASSERT( lo < N );
return std::min(N, lo+nb) - lo;
}
};
NAMESPACE_END(Grid);
@@ -0,0 +1,283 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/multigrid/BlockCyclicRedistribute.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
#include <Grid/algorithms/multigrid/BlockCyclicSumma.h>
NAMESPACE_BEGIN(Grid);
///////////////////////////////////////////////////////////////////////////////
// My rows of a distributed dense matrix in the 1D rank-major row layout:
// rank r owns contiguous global rows [rowStart[r], rowStart[r+1]) of an
// N x N matrix, stored rows x cols column major, ld = rows; element (i,j)
// at data[i + j*ld]. This is the layout the stencil->dense import
// produces and the apply slab consumes; the 2D inverse slots between them
// through the redistribution below.
///////////////////////////////////////////////////////////////////////////////
class BlockRows
{
public:
deviceVector<ComplexD> data;
int64_t rows;
int64_t cols;
int64_t ld;
BlockRows()
{
rows = 0;
cols = 0;
ld = 0;
}
void Resize(int64_t r, int64_t c)
{
rows = r;
cols = c;
ld = r;
data.resize((uint64_t)r*c);
}
ComplexD *ColumnWindow(int64_t col0)
{
GRID_ASSERT( col0 >= 0 );
GRID_ASSERT( col0 <= cols );
return &data[(uint64_t)col0*ld];
}
};
///////////////////////////////////////////////////////////////////////////////
// Stage 4 of the 2D distributed dense inverse: redistribution between the
// 1D rank-major row layout (BlockRows above) and the 2D block-cyclic
// layout.
//
// This is what lets the EXISTING stencil->dense import, its certificate,
// the fp32 slab conversion and the apply path all remain byte-for-byte
// untouched: the 2D inverse slots between them as
//
// RowsToCyclic -> BlockCyclicSchurInverse::Invert -> CyclicToRows
//
// Volume is one matrix pass each way -- N^2/P elements per rank (~1 GB at
// production), trivial against the inversion itself.
//
// Transport: PURE POINT-TO-POINT, like everything else in this stack.
// Ranks exchange in a round-robin TOURNAMENT (the circle method, on an odd
// modulus M so it covers every pair exactly once for any P, with byes):
// at round r, ranks x and y are partners iff x+y == r (mod M). Each
// meeting handles both directed edges of the pair in ONE SendToRecvFrom,
// padded to the larger of the two edge sizes -- SendToRecvFrom carries a
// single byte count for both directions, and both endpoints compute the
// same max from the shared descriptors, so there is no asymmetric-size
// case and no zero-count shape. Pairs with nothing to exchange skip the
// round, decided identically at both ends.
//
// Element enumeration within an edge is canonical -- ascending global
// column outer, ascending global row inner -- and each endpoint builds its
// OWN local offset tables from the shared descriptors, so no index data is
// ever transmitted. The round trip is BITWISE exact (pure data movement,
// no arithmetic): Test_schur2d_redist proves it.
///////////////////////////////////////////////////////////////////////////////
class BlockCyclicRedistribute
{
public:
/////////////////////////////////////////////////////////////////////////
// The directed edge (1D rank r1, 2D rank r2): global rows of r1's range
// whose row-block coordinate is r2's prow; ALL global columns whose
// column-block coordinate is r2's pcol. Every rank can enumerate any
// edge from (rowStart, layout) alone.
/////////////////////////////////////////////////////////////////////////
static void EdgeRows(const std::vector<int64_t> &rowStart, int r1,
const BlockCyclicLayout &L, int r2,
std::vector<int64_t> &rows)
{
rows.clear();
int p = r2 / L.Pc; // row-major rank convention
for(int64_t i=rowStart[r1]; i<rowStart[r1+1]; i++)
if ( (int)((i/L.nb) % L.Pr) == p ) rows.push_back(i);
}
static void EdgeCols(const BlockCyclicLayout &L, int r2,
std::vector<int64_t> &cols)
{
cols.clear();
int q = r2 % L.Pc;
for(int64_t b=0; b*L.nb<L.N; b++){
if ( (int)(b % L.Pc) != q ) continue;
int64_t g0=b*L.nb, g1=std::min(L.N,(b+1)*L.nb);
for(int64_t j=g0;j<g1;j++) cols.push_back(j);
}
}
/////////////////////////////////////////////////////////////////////////
// Gather/scatter one edge between a matrix (device, column major, ld)
// and a dense edge buffer, through device offset tables.
// buffer(a,b) = elem(rows[a], cols[b]), a fastest.
/////////////////////////////////////////////////////////////////////////
static void MoveEdge(int toBuffer,
ComplexD *mat, int64_t ld,
const std::vector<int64_t> &roff, // per-row offset in mat
const std::vector<int64_t> &coff, // per-col offset in mat
ComplexD *buf)
{
int64_t nr = roff.size();
int64_t nc = coff.size();
if ( !(nr && nc) ) return;
deviceVector<int64_t> dro(nr), dco(nc);
acceleratorCopyToDevice((void *)&roff[0], (void *)&dro[0], nr*sizeof(int64_t));
acceleratorCopyToDevice((void *)&coff[0], (void *)&dco[0], nc*sizeof(int64_t));
int64_t *ro = &dro[0];
int64_t *co = &dco[0];
if ( toBuffer ) {
accelerator_for(idx, (uint64_t)(nr*nc), 1, {
int64_t b = idx / nr;
int64_t a = idx - b*nr;
buf[a + b*nr] = mat[ ro[a] + co[b]*ld ];
});
} else {
accelerator_for(idx, (uint64_t)(nr*nc), 1, {
int64_t b = idx / nr;
int64_t a = idx - b*nr;
mat[ ro[a] + co[b]*ld ] = buf[a + b*nr];
});
}
}
/////////////////////////////////////////////////////////////////////////
// My offset tables for an edge, on whichever side I am.
/////////////////////////////////////////////////////////////////////////
static void Offsets1D(const std::vector<int64_t> &rows,
const std::vector<int64_t> &cols,
int64_t row0,
std::vector<int64_t> &roff, std::vector<int64_t> &coff)
{
roff.resize(rows.size()); coff.resize(cols.size());
for(uint64_t a=0;a<rows.size();a++) roff[a] = rows[a]-row0; // local row
for(uint64_t b=0;b<cols.size();b++) coff[b] = cols[b]; // global col
}
static void Offsets2D(const BlockCyclicLayout &L,
const std::vector<int64_t> &rows,
const std::vector<int64_t> &cols,
std::vector<int64_t> &roff, std::vector<int64_t> &coff)
{
roff.resize(rows.size()); coff.resize(cols.size());
for(uint64_t a=0;a<rows.size();a++){
int p; int64_t l;
BlockCyclicLayout::GlobalToLocal(rows[a], L.nb, L.Pr, p, l);
GRID_ASSERT( p == L.prow );
roff[a] = l;
}
for(uint64_t b=0;b<cols.size();b++){
int q; int64_t l;
BlockCyclicLayout::GlobalToLocal(cols[b], L.nb, L.Pc, q, l);
GRID_ASSERT( q == L.pcol );
coff[b] = l;
}
}
/////////////////////////////////////////////////////////////////////////
// The worker. dir=+1 : 1D rows -> block cyclic ; dir=-1 : back.
/////////////////////////////////////////////////////////////////////////
static void Redistribute(int dir, GridBase *grid,
const std::vector<int64_t> &rowStart,
ComplexD *rows1d, int64_t myrows,
BlockCyclicMatrix &A)
{
BlockCyclicLayout &L = A.layout;
int P = grid->ProcessorCount();
int me = grid->ThisRank();
GRID_ASSERT( (int)rowStart.size() == P+1 );
GRID_ASSERT( rowStart[P] == L.N );
GRID_ASSERT( rowStart[me+1]-rowStart[me] == myrows );
int64_t row0 = rowStart[me];
int64_t ld1 = myrows ? myrows : 1;
std::vector<int64_t> rows, cols, roff, coff;
deviceVector<ComplexD> sbuf(1), rbuf(1);
///////////////////////////////////////////////////////////////////////
// Self edge: purely local, via a bounce buffer (shares all the code).
///////////////////////////////////////////////////////////////////////
EdgeRows(rowStart, me, L, me, rows);
EdgeCols(L, me, cols);
if ( rows.size() && cols.size() ){
uint64_t ne = rows.size()*cols.size();
if ( sbuf.size() < ne ) sbuf.resize(ne);
std::vector<int64_t> roff2, coff2;
Offsets1D(rows, cols, row0, roff, coff);
Offsets2D(L, rows, cols, roff2, coff2);
if ( dir > 0 ) {
MoveEdge(1, rows1d, ld1, roff, coff, &sbuf[0]);
MoveEdge(0, &A.data[0], L.mloc, roff2, coff2, &sbuf[0]);
} else {
MoveEdge(1, &A.data[0], L.mloc, roff2, coff2, &sbuf[0]);
MoveEdge(0, rows1d, ld1, roff, coff, &sbuf[0]);
}
}
///////////////////////////////////////////////////////////////////////
// Tournament over all pairs: odd modulus M, partner = (r - me) mod M.
// Every unordered pair meets exactly once; partner==me or >=P is a bye.
///////////////////////////////////////////////////////////////////////
int M = (P%2) ? P : P+1;
for(int r=0;r<M;r++){
int partner = (int)(((int64_t)r - me + 2L*M) % M);
if ( partner == me || partner >= P ) continue;
// outbound edge: my (dir>0 ? 1D rows : 2D data) -> partner
// inbound edge: partner -> my (dir>0 ? 2D data : 1D rows)
std::vector<int64_t> orow, ocol, irow, icol;
if ( dir > 0 ) { EdgeRows(rowStart, me, L, partner, orow); EdgeCols(L, partner, ocol);
EdgeRows(rowStart, partner, L, me, irow); EdgeCols(L, me, icol); }
else { EdgeRows(rowStart, partner, L, me, orow); EdgeCols(L, me, ocol);
EdgeRows(rowStart, me, L, partner, irow); EdgeCols(L, partner, icol); }
uint64_t nout = orow.size()*ocol.size();
uint64_t nin = irow.size()*icol.size();
if ( !(nout || nin) ) continue; // both ends compute this identically
uint64_t nmax = std::max(nout,nin); // symmetric padded transfer
if ( sbuf.size() < nmax ) sbuf.resize(nmax);
if ( rbuf.size() < nmax ) rbuf.resize(nmax);
if ( nout ){
if ( dir > 0 ) { Offsets1D(orow, ocol, row0, roff, coff);
MoveEdge(1, rows1d, ld1, roff, coff, &sbuf[0]); }
else { Offsets2D(L, orow, ocol, roff, coff);
MoveEdge(1, &A.data[0], L.mloc, roff, coff, &sbuf[0]); }
}
grid->SendToRecvFrom((void *)&sbuf[0], partner,
(void *)&rbuf[0], partner,
nmax*sizeof(ComplexD));
if ( nin ){
if ( dir > 0 ) { Offsets2D(L, irow, icol, roff, coff);
MoveEdge(0, &A.data[0], L.mloc, roff, coff, &rbuf[0]); }
else { Offsets1D(irow, icol, row0, roff, coff);
MoveEdge(0, rows1d, ld1, roff, coff, &rbuf[0]); }
}
}
}
static void RowsToCyclic(GridBase *grid, const std::vector<int64_t> &rowStart,
ComplexD *rows1d, int64_t myrows, BlockCyclicMatrix &A)
{ Redistribute(+1, grid, rowStart, rows1d, myrows, A); }
static void CyclicToRows(GridBase *grid, const std::vector<int64_t> &rowStart,
BlockCyclicMatrix &A, ComplexD *rows1d, int64_t myrows)
{ Redistribute(-1, grid, rowStart, rows1d, myrows, A); }
};
NAMESPACE_END(Grid);
@@ -0,0 +1,446 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/multigrid/BlockCyclicSchurInverse.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
#include <Grid/algorithms/blas/BatchedInverse.h>
#include <Grid/algorithms/multigrid/BlockCyclicSumma.h>
NAMESPACE_BEGIN(Grid);
///////////////////////////////////////////////////////////////////////////////
// Stage 3 of the 2D distributed dense inverse: the recursive Schur
// complement on a block-cyclic matrix, in place.
//
// The recursion splits the GLOBAL INDEX RANGE at the block boundary
// nearest the midpoint -- not the rank range as the retired 1D
// RecursiveSchurInverse did -- so every rank owns part of every sub-block
// at every depth, and the ownership gating (inI/inJ, dummy operands,
// zero-width rank ranges) a 1D scheme needs has no analogue here.
//
// I = [c0,m) J = [m,c1) (block-aligned, m the mid block boundary)
// 1. recurse I : A11 -> A11inv (in place)
// 2. Bt = A11inv . A12 (scratch, I x J)
// 3. Ct = A21 . A11inv (scratch, J x I)
// 4. A22 -= A21 . Bt == S (in place)
// 5. recurse J : S -> Sinv (in place)
// 6. Tt = Sinv . Ct (scratch, J x I)
// 7. Ut = Bt . Sinv (scratch, I x J)
// 8. A11 += Ut . Ct == X11 (in place)
// 9. A12 = -Ut , A21 = -Tt (window copies)
//
// SCRATCH SHARING. Four full-size block-cyclic scratch matrices (Bt, Ct,
// Tt, Ut) serve the ENTIRE tree, used through windows. This is safe at
// every depth because of a window-disjointness invariant:
//
// * every temporary of a node has its row range in one half of the
// node's window and its column range in the other (I x J or J x I);
// * everything any DESCENDANT touches -- its A windows and its own
// temporaries -- has BOTH ranges inside a single half (I x I during
// step 1, J x J during step 5).
//
// Hence a descendant window and a live ancestor temporary always differ in
// at least one dimension by disjoint ranges. Only Bt and Ct are live
// across the step-5 recursion (Tt, Ut are written after it), and both are
// covered by the invariant.
//
// LEAF. A leaf is a single diagonal block, and block (b,b) of a
// block-cyclic layout lives ENTIRELY on rank (b%Pr, b%Pc). The leaf
// inversion is therefore purely local -- pack the strided block dense,
// GridBLASInverse, unpack -- with NO communication and no assembly.
// Successive leaves cycle over ranks, so leaf work is naturally spread.
//
// COMMUNICATION. Every transfer in the whole inversion is a
// SendToRecvFrom inside BlockCyclicSumma's rings: pure point-to-point, no
// collectives on the critical path, deterministic summation order (so
// repeated inversions are bitwise identical). ReportTelemetry() is the
// one optional exception: it performs reductions, and is only ever called
// explicitly by a caller who wants the numbers.
//
// NUMERICS. No pivoting: every A11 and every Schur complement met on the
// way down must be non-singular. The growth telemetry stands in for
// pivoting.
///////////////////////////////////////////////////////////////////////////////
class BlockCyclicSchurInverse
{
public:
BlockCyclicSumma SUMMA;
GridBLASInverse INV;
// Telemetry: accumulated LOCALLY, no comms unless ReportTelemetry().
double telLeafMaxInv;
uint64_t nLeaf;
// BIG LEAVES. Below span s blocks a sub-block lives on <= s of the Pr
// process rows / s of the Pc columns; when s is small relative to the grid
// the SUMMA rings run on a few ranks while the rest block in their next
// SendToRecvFrom. Instead: gather the (s*nb)^2 sub-block to one rank,
// invert locally (inverseLU), scatter back. Fires only while
// span < min(Pr,Pc) -- when the whole grid participates the rings are not
// degenerate and gathering would only concentrate memory (and at the top
// of the tree would gather the whole matrix). Larger s concentrates more
// memory on the root; smaller s loses ring parallelism.
int leafSpan = 9;
uint64_t nBigLeaf = 0; int64_t maxBigW = 0;
double tBigGather = 0, tBigInv = 0, tBigScatter = 0;
uint64_t nNode;
double tLeaf;
double tGemm; // wall in Multiply calls (comms+gemm)
double tCopy;
BlockCyclicSchurInverse()
{
telLeafMaxInv = 0.0;
nLeaf = nNode = 0;
tLeaf = tGemm = tCopy = 0.0;
}
///////////////////////////////////////////////////////////////////////////
// Window copy-scale: Dst[i0:i1, j0:j1] = alpha * Src[same window].
// Both share one layout, so the local bands coincide; pure local kernel.
///////////////////////////////////////////////////////////////////////////
void WindowCopyScale(ComplexD alpha,
BlockCyclicMatrix &Src, BlockCyclicMatrix &Dst,
int64_t i0, int64_t i1, int64_t j0, int64_t j1)
{
BlockCyclicLayout &L = Dst.layout;
GRID_ASSERT( Src.layout.N==L.N && Src.layout.nb==L.nb );
GRID_ASSERT( Src.layout.Pr==L.Pr && Src.layout.Pc==L.Pc );
int64_t li0,li1, lj0,lj1;
L.RowRange(i0,i1, li0,li1);
L.ColRange(j0,j1, lj0,lj1);
int64_t m = li1-li0, n = lj1-lj0;
if ( !(m && n) ) return;
ComplexD *src = Src.LocalWindow(li0,lj0);
ComplexD *dst = Dst.LocalWindow(li0,lj0);
int64_t ldS = Src.layout.mloc;
int64_t ldD = L.mloc;
tCopy -= usecond();
accelerator_for(idx, (uint64_t)(m*n), 1, {
int64_t jj = idx / m;
int64_t ii = idx - jj*m;
dst[ii + jj*ldD] = alpha*src[ii + jj*ldS];
});
tCopy += usecond();
}
///////////////////////////////////////////////////////////////////////////
// Leaf: single diagonal block (b,b), entirely on rank (b%Pr, b%Pc).
// Local pack -> dense inverse -> unpack; every other rank does nothing
// and needs no synchronisation: the next SUMMA's rings pair them up.
///////////////////////////////////////////////////////////////////////////
void Leaf(BlockCyclicMatrix &A, int64_t b)
{
GRID_TRACE("SchurLeaf");
BlockCyclicLayout &L = A.layout;
nLeaf++;
if ( (int)(b % L.Pr) != L.prow ) return;
if ( (int)(b % L.Pc) != L.pcol ) return;
tLeaf -= usecond();
int64_t g0 = b*L.nb;
int64_t g1 = std::min(L.N, g0+L.nb);
int64_t w = g1-g0;
int64_t lr0,lr1, lc0,lc1;
L.RowRange(g0,g1, lr0,lr1);
L.ColRange(g0,g1, lc0,lc1);
GRID_ASSERT( lr1-lr0 == w );
GRID_ASSERT( lc1-lc0 == w );
// Pack the strided block dense (inverseBatched assumes lda == w).
deviceVector<ComplexD> dense((uint64_t)w*w);
{
ComplexD *src = A.LocalWindow(lr0,lc0);
ComplexD *dst = &dense[0];
int64_t ld = L.mloc;
accelerator_for(idx, (uint64_t)(w*w), 1, {
int64_t jj = idx / w;
int64_t ii = idx - jj*w;
dst[ii + jj*w] = src[ii + jj*ld];
});
}
{
deviceVector<ComplexD*> bp(1);
std::vector<ComplexD*> ptr(1);
ptr[0] = &dense[0];
acceleratorCopyToDevice(&ptr[0], &bp[0], sizeof(ComplexD*));
INV.inverseBatched(w, bp);
}
{
ComplexD *src = &dense[0];
ComplexD *dst = A.LocalWindow(lr0,lc0);
int64_t ld = L.mloc;
accelerator_for(idx, (uint64_t)(w*w), 1, {
int64_t jj = idx / w;
int64_t ii = idx - jj*w;
dst[ii + jj*ld] = src[ii + jj*w];
});
}
// Growth telemetry, local only.
{
std::vector<ComplexD> h((uint64_t)w*w);
acceleratorCopyFromDevice(&dense[0], &h[0], h.size()*sizeof(ComplexD));
double mx = 0.0;
for(auto &z : h){
double re=z.real(), im=z.imag();
mx = std::max(mx, re*re+im*im);
}
telLeafMaxInv = std::max(telLeafMaxInv, std::sqrt(mx));
}
tLeaf += usecond();
}
///////////////////////////////////////////////////////////////////////////
// BIG LEAF on block range [b0,b1): gather to root = owner of block (b0,b0),
// invert there, scatter back. Rank q's piece of the sub-block is the
// contiguous local window RowRange(c0,c1) x ColRange(c0,c1); its local row
// ii maps to global row (brq0 + (ii/nb)*Pr)*nb + ii%nb where brq0 is q's
// first block row >= b0 (closed form: no tables). Transport is pairwise
// SendToRecvFrom with root (symmetric byte count: the reverse direction
// carries a same-size dummy -- a leaf-local cost, accepted for simplicity).
///////////////////////////////////////////////////////////////////////////
static int64_t FirstBlock(int64_t b0, int p, int Pg){ int64_t r = ((b0 % Pg) <= p) ? b0 - (b0 % Pg) + p : b0 - (b0 % Pg) + Pg + p; return r; }
void BigLeaf(BlockCyclicMatrix &A, int64_t b0, int64_t b1)
{
GRID_TRACE("SchurBigLeaf");
BlockCyclicLayout &L = A.layout;
GridBase *grid = A.grid;
const int Pr=L.Pr, Pc=L.Pc, nb=(int)L.nb;
GRID_ASSERT( L.me == L.prow*Pc + L.pcol ); // rank convention shared with the SUMMA rings
const int64_t c0 = b0*L.nb, c1 = std::min(L.N, b1*L.nb), W = c1-c0;
const int root = (int)((b0%Pr)*Pc + (b0%Pc));
const int me = L.me;
nBigLeaf++; maxBigW = std::max(maxBigW, W);
// my piece
int64_t lr0,lr1,lc0,lc1; L.RowRange(c0,c1,lr0,lr1); L.ColRange(c0,c1,lc0,lc1);
const int64_t mq = lr1-lr0, nq = lc1-lc0;
deviceVector<ComplexD> dense; // root only: W x W column major
deviceVector<ComplexD> piece, dummy; // piece: my mq x nq contiguous; dummy: reverse-direction filler
if ( me == root ) dense.resize((uint64_t)W*W);
auto pack_piece = [&](ComplexD *dst, int64_t m, int64_t n, int64_t r0, int64_t cc0){
ComplexD *src = A.LocalWindow(r0,cc0); const int64_t ld = L.mloc;
accelerator_for(idx,(uint64_t)(m*n),1,{ int64_t jj=idx/m, ii=idx-jj*m; dst[ii+jj*m] = src[ii+jj*ld]; });
};
auto unpack_piece = [&](ComplexD *src, int64_t m, int64_t n, int64_t r0, int64_t cc0){
ComplexD *dst = A.LocalWindow(r0,cc0); const int64_t ld = L.mloc;
accelerator_for(idx,(uint64_t)(m*n),1,{ int64_t jj=idx/m, ii=idx-jj*m; dst[ii+jj*ld] = src[ii+jj*m]; });
};
// root: piece of rank q <-> dense, via the closed-form block map
auto root_place = [&](ComplexD *pc, int64_t m, int64_t n, int q, int to_dense){
const int pq=q/Pc, cq=q%Pc;
const int64_t brq0=FirstBlock(b0,pq,Pr), bcq0=FirstBlock(b0,cq,Pc);
ComplexD *dn = &dense[0]; const int64_t WW=W, NB=nb, PR=Pr, PC=Pc, C0=c0;
accelerator_for(idx,(uint64_t)(m*n),1,{
int64_t jj=idx/m, ii=idx-jj*m;
int64_t gr = (brq0 + (ii/NB)*PR)*NB + ii%NB - C0;
int64_t gc = (bcq0 + (jj/NB)*PC)*NB + jj%NB - C0;
if ( to_dense ) dn[gr + gc*WW] = pc[ii+jj*m]; else pc[ii+jj*m] = dn[gr + gc*WW];
});
};
auto piece_dims = [&](int q, int64_t &m, int64_t &n){
const int pq=q/Pc, cq=q%Pc;
m = BlockCyclicLayout::NumLocal(c1,L.nb,pq,Pr) - BlockCyclicLayout::NumLocal(c0,L.nb,pq,Pr);
n = BlockCyclicLayout::NumLocal(c1,L.nb,cq,Pc) - BlockCyclicLayout::NumLocal(c0,L.nb,cq,Pc);
};
// ---- gather ----
tBigGather -= usecond();
if ( mq*nq ) { piece.resize((uint64_t)mq*nq); dummy.resize((uint64_t)mq*nq); pack_piece(&piece[0],mq,nq,lr0,lc0); accelerator_barrier(); }
if ( me == root ) {
deviceVector<ComplexD> stage;
for(int q=0;q<Pr*Pc;q++){
int64_t m,n; piece_dims(q,m,n); if ( !(m*n) ) continue;
if ( q == root ) { root_place(&piece[0],m,n,q,1); continue; }
if ( stage.size() < (uint64_t)(m*n) ) stage.resize((uint64_t)m*n);
deviceVector<ComplexD> junk((uint64_t)m*n);
grid->SendToRecvFrom((void *)&junk[0], q, (void *)&stage[0], q, (uint64_t)m*n*sizeof(ComplexD));
root_place(&stage[0],m,n,q,1);
}
accelerator_barrier();
} else if ( mq*nq ) {
grid->SendToRecvFrom((void *)&piece[0], root, (void *)&dummy[0], root, (uint64_t)mq*nq*sizeof(ComplexD));
}
tBigGather += usecond();
// ---- invert on root: blocked getrf_64 + identity getrs_64 ----
tBigInv -= usecond();
if ( me == root ) INV.inverseLU(W, &dense[0]);
tBigInv += usecond();
// ---- scatter ----
tBigScatter -= usecond();
if ( me == root ) {
deviceVector<ComplexD> stage;
for(int q=0;q<Pr*Pc;q++){
int64_t m,n; piece_dims(q,m,n); if ( !(m*n) ) continue;
if ( q == root ) { root_place(&piece[0],m,n,q,0); accelerator_barrier(); continue; }
if ( stage.size() < (uint64_t)(m*n) ) stage.resize((uint64_t)m*n);
deviceVector<ComplexD> junk((uint64_t)m*n);
root_place(&stage[0],m,n,q,0); accelerator_barrier();
grid->SendToRecvFrom((void *)&stage[0], q, (void *)&junk[0], q, (uint64_t)m*n*sizeof(ComplexD));
}
} else if ( mq*nq ) {
grid->SendToRecvFrom((void *)&dummy[0], root, (void *)&piece[0], root, (uint64_t)mq*nq*sizeof(ComplexD));
}
if ( mq*nq ) { unpack_piece(&piece[0],mq,nq,lr0,lc0); accelerator_barrier(); }
tBigScatter += usecond();
}
///////////////////////////////////////////////////////////////////////////
// The recursion, on global BLOCK range [b0,b1). SPMD: every rank calls
// with identical arguments; there is no ownership gating to get wrong.
///////////////////////////////////////////////////////////////////////////
void SchurNode(BlockCyclicMatrix &A,
BlockCyclicMatrix &Bt, BlockCyclicMatrix &Ct,
BlockCyclicMatrix &Tt, BlockCyclicMatrix &Ut,
int64_t b0, int64_t b1)
{
BlockCyclicLayout &L = A.layout;
int64_t span = b1-b0;
GRID_ASSERT( span >= 1 );
if ( span == 1 ) { Leaf(A, b0); return; }
if ( span <= leafSpan && span < std::min(L.Pr,L.Pc) ) { BigLeaf(A, b0, b1); return; }
GRID_TRACE("SchurNode");
nNode++;
int64_t bm = b0 + span/2;
int64_t c0 = b0*L.nb;
int64_t m = bm*L.nb;
int64_t c1 = std::min(L.N, b1*L.nb);
ComplexD one (1.0,0.0), mone(-1.0,0.0), zero(0.0,0.0);
// 1. A11 -> A11inv
SchurNode(A,Bt,Ct,Tt,Ut, b0,bm);
tGemm -= usecond();
// 2. Bt[I,J] = A11inv . A12
SUMMA.Multiply(one, A, A, zero, Bt, c0,m, m,c1, c0,m );
// 3. Ct[J,I] = A21 . A11inv
SUMMA.Multiply(one, A, A, zero, Ct, m,c1, c0,m, c0,m );
// 4. A22 -= A21 . Bt (the Schur complement, in place)
SUMMA.Multiply(mone, A, Bt, one, A, m,c1, m,c1, c0,m );
tGemm += usecond();
// 5. S -> Sinv (Bt, Ct live across this call: see invariant)
SchurNode(A,Bt,Ct,Tt,Ut, bm,b1);
tGemm -= usecond();
// 6. Tt[J,I] = Sinv . Ct
SUMMA.Multiply(one, A, Ct, zero, Tt, m,c1, c0,m, m,c1 );
// 7. Ut[I,J] = Bt . Sinv
SUMMA.Multiply(one, Bt, A, zero, Ut, c0,m, m,c1, m,c1 );
// 8. A11 += Ut . Ct
SUMMA.Multiply(one, Ut, Ct, one, A, c0,m, c0,m, m,c1 );
tGemm += usecond();
// 9. Off-diagonal signs
{ GRID_TRACE("SchurCopy");
WindowCopyScale(mone, Ut, A, c0,m, m,c1);
WindowCopyScale(mone, Tt, A, m,c1, c0,m);
}
}
///////////////////////////////////////////////////////////////////////////
// PUBLIC ENTRY. In-place inverse of the whole matrix. Scratch (4x the
// matrix footprint) is allocated here and released on return.
///////////////////////////////////////////////////////////////////////////
void Invert(BlockCyclicMatrix &A)
{
BlockCyclicLayout &L = A.layout;
GRID_ASSERT( L.N >= 1 );
int64_t nblocks = (L.N + L.nb - 1)/L.nb;
BlockCyclicMatrix Bt(A.grid, L.N, L.nb, L.Pr, L.Pc);
BlockCyclicMatrix Ct(A.grid, L.N, L.nb, L.Pr, L.Pc);
BlockCyclicMatrix Tt(A.grid, L.N, L.nb, L.Pr, L.Pc);
BlockCyclicMatrix Ut(A.grid, L.N, L.nb, L.Pr, L.Pc);
telLeafMaxInv = 0.0;
nLeaf = nNode = 0;
tLeaf = tGemm = tCopy = 0.0;
SchurNode(A, Bt,Ct,Tt,Ut, 0, nblocks);
}
///////////////////////////////////////////////////////////////////////////
// Optional, and the ONLY place any reduction happens: call it if you
// want the numbers, never from Invert.
///////////////////////////////////////////////////////////////////////////
void ReportTelemetry(GridBase *grid)
{
RealD mx = telLeafMaxInv;
grid->GlobalMax(mx);
std::cout << GridLogMessage << "BlockCyclicSchurInverse:"
<< " nodes " << nNode << " leaves " << nLeaf
<< " max|leafinv| " << mx
<< " (boss secs: gemm+comms " << tGemm/1.0e6
<< " leaf " << tLeaf/1.0e6
<< " copy " << tCopy/1.0e6 << ")"
<< std::endl;
if ( nBigLeaf ) {
RealD ti = tBigInv/1.0e6; grid->GlobalMax(ti); // inverse runs on the root of each leaf: report the max over ranks
std::cout << GridLogMessage << "BlockCyclicSchurInverse: BIG LEAVES (span " << leafSpan
<< ", inverseLU): " << nBigLeaf
<< " leaves, max W " << maxBigW
<< " boss secs: gather " << tBigGather/1.0e6 << " scatter " << tBigScatter/1.0e6
<< " inverse (max over ranks) " << ti << std::endl;
}
// SUMMA breakdown: boss-rank seconds, plus the ring/gemm time spread over
// ranks (min/max) -- skew shows as max >> min.
RealD ring = (SUMMA.tRingA+SUMMA.tRingB)/1.0e6;
RealD rmin = -ring, rmax = ring; grid->GlobalMax(rmin); grid->GlobalMax(rmax); rmin = -rmin; // no GlobalMin: max of negation
RealD gmin = -SUMMA.tGemm/1.0e6, gmax = SUMMA.tGemm/1.0e6; grid->GlobalMax(gmin); grid->GlobalMax(gmax); gmin = -gmin;
double gb = SUMMA.bytesRing/1.0e9;
std::cout << GridLogMessage << "BlockCyclicSumma:"
<< " multiplies " << SUMMA.nMultiply << " gemms " << SUMMA.nGemm << " ring msgs " << SUMMA.nRingMsg
<< " | boss secs: alloc " << SUMMA.tAlloc/1.0e6
<< " pack " << SUMMA.tPack/1.0e6
<< " ringA " << SUMMA.tRingA/1.0e6 << " ringB " << SUMMA.tRingB/1.0e6
<< " gemm " << SUMMA.tGemm/1.0e6
<< " | ring min/max over ranks " << rmin << "/" << rmax
<< " gemm min/max " << gmin << "/" << gmax
<< " | ring bytes/rank " << gb << " GB -> " << (ring>0 ? gb/ring : 0.0) << " GB/s/rank (boss)"
<< std::endl;
// Ring time decomposed by message size (boss rank; every rank sends the
// same sequence of sizes). Time is wall time inside SendToRecvFrom, so it
// includes waiting for the partner -- a bucket whose GB/s is far below the
// probe's for the same size is wait, not wire.
std::cout << GridLogMessage << "BlockCyclicSumma ring histogram (boss): size-bucket msgs GB xfer-secs GB/s %time" << std::endl;
std::streamsize oldprec = std::cout.precision();
for(int b=0;b<SUMMA.NHIST;b++){
if ( !SUMMA.histN[b] ) continue;
double sec = SUMMA.histUs[b]/1.0e6, g = SUMMA.histBytes[b]/1.0e9;
double lo = (double)(1ull<<b);
char sz[32]; if (lo>=1048576) snprintf(sz,32,"%6.1f MB",lo/1048576.); else if (lo>=1024) snprintf(sz,32,"%6.1f KB",lo/1024.); else snprintf(sz,32,"%6.0f B ",lo);
std::cout << GridLogMessage << " >=" << sz
<< std::setw(8) << SUMMA.histN[b]
<< std::setw(10) << std::setprecision(3) << g
<< std::setw(9) << std::setprecision(3) << sec
<< std::setw(9) << std::setprecision(3) << (sec>0 ? g/sec : 0.0)
<< std::setw(8) << std::setprecision(3) << (ring>0 ? 100.0*sec/ring : 0.0);
std::cout << std::endl;
}
std::cout.precision(oldprec);
}
};
NAMESPACE_END(Grid);
@@ -0,0 +1,349 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/multigrid/BlockCyclicSumma.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
#include <Grid/algorithms/multigrid/BlockCyclic.h>
NAMESPACE_BEGIN(Grid);
///////////////////////////////////////////////////////////////////////////////
// Stage 2 of the 2D distributed dense inverse: the windowed SUMMA product
//
// C[i0:i1, j0:j1] <- beta C[i0:i1, j0:j1]
// + alpha A[i0:i1, k0:k1] . B[k0:k1, j0:j1]
//
// on block-cyclic matrices sharing one BlockCyclicLayout. All ranges are
// BLOCK ALIGNED (multiples of nb, or N itself at the top end): stage 3's
// recursion splits on block boundaries, so nothing else is ever needed, and
// alignment makes every local window a contiguous band of local storage
// (BlockCyclic.h RangeToLocal, Test_blockcyclic T7).
//
// Transport is PURE POINT-TO-POINT: SendToRecvFrom on explicit world ranks
// computed from the row-major rank convention. No collectives of any kind
// -- no sub-communicators, no broadcast, no allgather -- by design: the
// collective pathologies measured on this machine (MPI_Allgatherv at
// ~0.18 MB/s with skewed counts, mpir_request.h:508 aborts, allreduce at
// 39% of the P2P rate) motivated this implementation. SendToRecvFrom is
// the most exercised device-buffer path in Grid and the only one never
// implicated.
//
// Algorithm: round-based ring allgather SUMMA. The k range is processed in
// rounds of Pc consecutive global blocks. Within a round
//
// * process column c owns at most one A panel (blocks s with s%Pc == c);
// the Pc panels circulate around each process-ROW ring in Pc-1 steps;
// * process row r owns up to ceil(Pc/Pr) B panels (blocks s%Pr == r);
// they circulate around each process-COLUMN ring in Pr-1 steps;
// * every rank then accumulates Cloc += alpha * Apanel_s . Bpanel_s
// for each block s of the round, in ascending s: a fixed summation
// order, so REPEATED RUNS ARE BITWISE IDENTICAL (no reduction, no
// order ambiguity -- the property the P2P doctrine buys).
//
// Ring chunks are PADDED to a fixed size (full nb panels, fixed
// panels-per-origin): SendToRecvFrom carries one byte count for both
// directions, so symmetric transfers eliminate every variable-size edge
// case at a worst-case ~1/Pc extra volume on ragged rounds. Padding is
// never read: GEMMs address only the leading nb_s x width of each slot.
//
// Per-rank received volume: (k-extent) * (mloc_i + nloc_j) elements --
// the N^2 (1/Pr + 1/Pc) SUMMA optimum, ~sqrt(P)/2 below the 1D scheme.
///////////////////////////////////////////////////////////////////////////////
class BlockCyclicMatrix
{
public:
GridBase *grid; // borrowed, never owned
BlockCyclicLayout layout;
deviceVector<ComplexD> data; // column major, ld = layout.mloc
BlockCyclicMatrix(GridBase *g, int64_t N, int64_t nb, int Pr, int Pc)
: grid(g),
layout(N, nb, Pr, Pc, g->ThisRank())
{
GRID_ASSERT( Pr*Pc == g->ProcessorCount() );
uint64_t sz = (uint64_t)layout.mloc*layout.nloc;
data.resize( sz ? sz : 1 );
}
ComplexD *LocalWindow(int64_t li, int64_t lj)
{
return &data[0] + li + lj*layout.mloc;
}
/////////////////////////////////////////////////////////////////////////
// TEST-SCALE import/export of a replicated global matrix (host, O(N^2)
// loops, one collective in Export). For unit tests and the stage-3
// oracle only; production data enters through the direct block-cyclic
// import, never through these.
/////////////////////////////////////////////////////////////////////////
void ImportGlobal(const std::vector<ComplexD> &G)
{
int64_t N = layout.N;
GRID_ASSERT( (int64_t)G.size() == N*N );
std::vector<ComplexD> h((uint64_t)layout.mloc*layout.nloc, ComplexD(0.0,0.0));
for(int64_t j=0;j<N;j++){
for(int64_t i=0;i<N;i++){
if ( layout.Owns(i,j) ) h[layout.LocalOffset(i,j)] = G[i + j*N];
}
}
if ( h.size() )
acceleratorCopyToDevice(&h[0], &data[0], h.size()*sizeof(ComplexD));
}
void ExportGlobal(std::vector<ComplexD> &G)
{
int64_t N = layout.N;
G.assign((uint64_t)N*N, ComplexD(0.0,0.0));
std::vector<ComplexD> h((uint64_t)layout.mloc*layout.nloc);
if ( h.size() )
acceleratorCopyFromDevice(&data[0], &h[0], h.size()*sizeof(ComplexD));
for(int64_t j=0;j<N;j++){
for(int64_t i=0;i<N;i++){
if ( layout.Owns(i,j) ) G[i + j*N] = h[layout.LocalOffset(i,j)];
}
}
if ( N ) grid->GlobalSumVector((ComplexD *)&G[0], (int)(N*N)); // zero-fill: exact
}
};
class BlockCyclicSumma
{
public:
GridBLAS BLAS;
// Per-rank telemetry (boss-rank seconds when printed; no reductions here).
// Every Multiply is: buffer alloc, pack panels, ring A along the process
// row, ring B along the process column, then the local GEMMs. The rings
// are synchronous SendToRecvFrom, so tRing is time the GPU is idle unless
// a future version overlaps them with the GEMMs.
// PERSISTENT ring buffers: allocated once (grow-only) and reused by every
// Multiply, so the device addresses handed to MPI never change. Fresh
// per-call buffers rotated through the caching allocator's blocks, and a
// GPU-direct RDMA registration cache keyed on address then re-registers
// per message: measured 1.26 GB/s/rank on 13 MB ring messages (production,
// GRID_ALLOC_NCACHE_LARGE=64) against 62 s total for the same inverse when
// hipMalloc returned a stable address.
deviceVector<ComplexD> Abuf;
deviceVector<ComplexD> Bbuf;
double tAlloc=0, tPack=0, tRingA=0, tRingB=0, tGemm=0;
uint64_t bytesRing=0, nRingMsg=0, nMultiply=0, nGemm=0;
// Per-message-size histogram (bucket = floor(log2 bytes)): count, bytes,
// microseconds -- decomposes the ring time by packet size so a low average
// GB/s can be attributed (many small latency-bound messages vs slow large
// ones vs partner-wait). Time is wall time inside SendToRecvFrom, so a
// bucket far below the wire rate for its size is wait, not wire.
static const int NHIST=48;
uint64_t histN[NHIST]={0}, histBytes[NHIST]={0}; double histUs[NHIST]={0};
void HistAdd(uint64_t bytes, double us){ int b=0; while((bytes>>b)>1) b++; histN[b]++; histBytes[b]+=bytes; histUs[b]+=us; }
void ResetTelemetry(void){ tAlloc=tPack=tRingA=tRingB=tGemm=0; bytesRing=nRingMsg=nMultiply=nGemm=0; for(int b=0;b<NHIST;b++){histN[b]=histBytes[b]=0; histUs[b]=0;} }
static int Overlap(int64_t a0,int64_t a1,int64_t b0,int64_t b1)
{ return (a0 < b1) && (b0 < a1); }
void Multiply(ComplexD alpha,
BlockCyclicMatrix &A,
BlockCyclicMatrix &B,
ComplexD beta,
BlockCyclicMatrix &C,
int64_t i0, int64_t i1,
int64_t j0, int64_t j1,
int64_t k0, int64_t k1)
{
BlockCyclicLayout &L = C.layout;
GridBase *grid = C.grid;
const int64_t N = L.N;
const int64_t nb = L.nb;
const int Pr = L.Pr, Pc = L.Pc;
const int prow = L.prow, pcol = L.pcol;
///////////////////////////////////////////////////////////////////////
// Conformability: one layout, one communicator, aligned ranges.
///////////////////////////////////////////////////////////////////////
auto same = [&](BlockCyclicLayout &X){
GRID_ASSERT( X.N==N ); GRID_ASSERT( X.nb==nb );
GRID_ASSERT( X.Pr==Pr ); GRID_ASSERT( X.Pc==Pc );
GRID_ASSERT( X.me==L.me );
};
same(A.layout); same(B.layout);
GRID_ASSERT( A.grid==grid ); GRID_ASSERT( B.grid==grid );
auto aligned = [&](int64_t g0,int64_t g1){
GRID_ASSERT( 0<=g0 ); GRID_ASSERT( g0<=g1 ); GRID_ASSERT( g1<=N );
GRID_ASSERT( g0%nb==0 ); GRID_ASSERT( (g1%nb==0)||(g1==N) );
};
aligned(i0,i1); aligned(j0,j1); aligned(k0,k1);
GRID_ASSERT( k1 > k0 ); // pure scaling not supported here
// In-place windows are legal only if the written window is disjoint
// from anything read (stage 3 uses this; make violation loud).
if ( &C==&A ) GRID_ASSERT( !Overlap(j0,j1,k0,k1) );
if ( &C==&B ) GRID_ASSERT( !Overlap(i0,i1,k0,k1) );
///////////////////////////////////////////////////////////////////////
// My local bands of the three windows (contiguous: T7).
///////////////////////////////////////////////////////////////////////
int64_t li0,li1, lj0,lj1;
L.RowRange(i0,i1, li0,li1);
L.ColRange(j0,j1, lj0,lj1);
const int64_t mloc_i = li1-li0; // my rows of the i window
const int64_t nloc_j = lj1-lj0; // my cols of the j window
const int64_t kb0 = k0/nb;
const int64_t kb1 = (k1+nb-1)/nb; // block-aligned or ==N: exact
const int64_t S = (Pc + Pr - 1)/Pr; // max B panels per origin row
///////////////////////////////////////////////////////////////////////
// Round buffers, padded to fixed slot sizes (see header comment).
// A: Pc slots of mloc_i x nb (slot c = panel of the block owned by c)
// B: Pr slots of S x (nb x nloc_j)
///////////////////////////////////////////////////////////////////////
const uint64_t slotA = (uint64_t)mloc_i*nb;
const uint64_t slotB1 = (uint64_t)nb*nloc_j; // one panel
const uint64_t slotB = (uint64_t)S*slotB1;
nMultiply++;
tAlloc -= usecond();
if ( Abuf.size() < std::max<uint64_t>(slotA*Pc,1) ) Abuf.resize( std::max<uint64_t>(slotA*Pc,1) );
if ( Bbuf.size() < std::max<uint64_t>(slotB*Pr,1) ) Bbuf.resize( std::max<uint64_t>(slotB*Pr,1) );
tAlloc += usecond();
deviceVector<ComplexD *> ap(1), bp(1), cp(1);
std::vector<ComplexD *> ptr(1);
int firstblock = 1;
for(int64_t r0=kb0; r0<kb1; r0+=Pc){ // rounds of Pc k-blocks
int64_t r1 = std::min(kb1, r0+Pc);
/////////////////////////////////////////////////////////////////////
// Pack MY panels of this round into my origin slots.
/////////////////////////////////////////////////////////////////////
tPack -= usecond();
{ GRID_TRACE("SummaPack");
for(int64_t s=r0; s<r1; s++){
int64_t nb_s = L.BlockSize(s);
if ( (int)(s%Pc) == pcol && mloc_i ){ // my A panel: block-column s
int64_t lc0, lc1;
L.ColRange(s*nb, std::min(N,(s+1)*nb), lc0, lc1);
GRID_ASSERT( lc1-lc0 == nb_s );
ComplexD *src = A.LocalWindow(li0, lc0);
ComplexD *dst = &Abuf[0] + slotA*pcol;
int64_t ld = A.layout.mloc;
int64_t m = mloc_i;
accelerator_for(idx, (uint64_t)(m*nb_s), 1, {
int64_t jj = idx / m;
int64_t ii = idx - jj*m;
dst[ii + jj*m] = src[ii + jj*ld];
});
}
if ( (int)(s%Pr) == prow && nloc_j ){ // my B panel: block-row s
int64_t lr0, lr1;
L.RowRange(s*nb, std::min(N,(s+1)*nb), lr0, lr1);
GRID_ASSERT( lr1-lr0 == nb_s );
int64_t idxs = (s - r0 - ((prow - r0%Pr + Pr) % Pr)) / Pr; // my panel # in round
GRID_ASSERT( idxs >= 0 ); GRID_ASSERT( idxs < S );
ComplexD *src = B.LocalWindow(lr0, lj0);
ComplexD *dst = &Bbuf[0] + slotB*prow + slotB1*idxs;
int64_t ld = B.layout.mloc;
int64_t nn = nloc_j;
accelerator_for(idx, (uint64_t)(nb_s*nn), 1, {
int64_t jj = idx / nb_s;
int64_t ii = idx - jj*nb_s;
dst[ii + jj*nb_s] = src[ii + jj*ld];
});
}
}
accelerator_barrier();
}
tPack += usecond();
/////////////////////////////////////////////////////////////////////
// Ring allgather along my process ROW: Pc-1 symmetric steps. At
// step t send the slot of origin (pcol-t+1), receive origin (pcol-t).
/////////////////////////////////////////////////////////////////////
if ( Pc > 1 && slotA ){
GRID_TRACE("SummaRingA");
tRingA -= usecond();
int dest = prow*Pc + (pcol+1)%Pc;
int src = prow*Pc + (pcol-1+Pc)%Pc;
for(int t=1;t<Pc;t++){
int cs = (pcol - t + 1 + Pc*Pc) % Pc;
int cr = (pcol - t + Pc*Pc) % Pc;
double tm = usecond();
grid->SendToRecvFrom((void *)(&Abuf[0]+slotA*cs), dest,
(void *)(&Abuf[0]+slotA*cr), src,
slotA*sizeof(ComplexD));
HistAdd(slotA*sizeof(ComplexD), usecond()-tm);
bytesRing += slotA*sizeof(ComplexD); nRingMsg++;
}
tRingA += usecond();
}
/////////////////////////////////////////////////////////////////////
// Ring allgather along my process COLUMN: Pr-1 symmetric steps.
/////////////////////////////////////////////////////////////////////
if ( Pr > 1 && slotB ){
GRID_TRACE("SummaRingB");
tRingB -= usecond();
int dest = ((prow+1)%Pr)*Pc + pcol;
int src = ((prow-1+Pr)%Pr)*Pc + pcol;
for(int t=1;t<Pr;t++){
int rs = (prow - t + 1 + Pr*Pr) % Pr;
int rr = (prow - t + Pr*Pr) % Pr;
double tm = usecond();
grid->SendToRecvFrom((void *)(&Bbuf[0]+slotB*rs), dest,
(void *)(&Bbuf[0]+slotB*rr), src,
slotB*sizeof(ComplexD));
HistAdd(slotB*sizeof(ComplexD), usecond()-tm);
bytesRing += slotB*sizeof(ComplexD); nRingMsg++;
}
tRingB += usecond();
}
/////////////////////////////////////////////////////////////////////
// Local update, ascending s: fixed order, bitwise-reproducible.
/////////////////////////////////////////////////////////////////////
tGemm -= usecond();
{ GRID_TRACE("SummaGEMM");
for(int64_t s=r0; s<r1; s++){
int64_t nb_s = L.BlockSize(s);
if ( !(mloc_i && nloc_j && nb_s) ) { firstblock = 0; continue; }
int cA = (int)(s%Pc);
int rB = (int)(s%Pr);
int64_t idxs = (s - r0 - ((rB - r0%Pr + Pr) % Pr)) / Pr;
ComplexD beta_use = firstblock ? beta : ComplexD(1.0,0.0);
firstblock = 0;
ptr[0] = &Abuf[0] + slotA*cA;
acceleratorCopyToDevice(&ptr[0], &ap[0], sizeof(ComplexD *));
ptr[0] = &Bbuf[0] + slotB*rB + slotB1*idxs;
acceleratorCopyToDevice(&ptr[0], &bp[0], sizeof(ComplexD *));
ptr[0] = C.LocalWindow(li0, lj0);
acceleratorCopyToDevice(&ptr[0], &cp[0], sizeof(ComplexD *));
BLAS.gemmBatched(GridBLAS_OP_N, GridBLAS_OP_N,
(int)mloc_i, (int)nloc_j, (int)nb_s,
alpha, ap, (int)mloc_i,
bp, (int)nb_s,
beta_use, cp, (int)C.layout.mloc);
BLAS.synchronise();
nGemm++;
}
}
tGemm += usecond();
}
}
};
NAMESPACE_END(Grid);
+6 -6
View File
@@ -309,7 +309,7 @@ public:
if ((out.size()!=ndir)&&(out.size()!=ndir+1)) {
std::cout <<"MdirAll out size "<< out.size()<<std::endl;
std::cout <<"MdirAll ndir "<< ndir<<std::endl;
assert(0);
GRID_ASSERT(0);
}
for(int p=0;p<ndir;p++){
MdirCalc(in,out[p],p);
@@ -373,7 +373,7 @@ public:
conformable(in.Grid(), _cbgrid); // verifies half grid
conformable(in.Grid(), out.Grid()); // drops the cb check
assert(in.Checkerboard() == Even);
GRID_ASSERT(in.Checkerboard() == Even);
out.Checkerboard() = Odd;
DhopInternal(StencilEven, Aodd, in, out, dag);
@@ -383,7 +383,7 @@ public:
conformable(in.Grid(), _cbgrid); // verifies half grid
conformable(in.Grid(), out.Grid()); // drops the cb check
assert(in.Checkerboard() == Odd);
GRID_ASSERT(in.Checkerboard() == Odd);
out.Checkerboard() = Even;
DhopInternal(StencilOdd, Aeven, in, out, dag);
@@ -391,7 +391,7 @@ public:
void MooeeInternal(const CoarseVector &in, CoarseVector &out, int dag, int inv) {
out.Checkerboard() = in.Checkerboard();
assert(in.Checkerboard() == Odd || in.Checkerboard() == Even);
GRID_ASSERT(in.Checkerboard() == Odd || in.Checkerboard() == Even);
CoarseMatrix *Aself = nullptr;
if(in.Grid()->_isCheckerBoarded) {
@@ -406,7 +406,7 @@ public:
Aself = (inv) ? &AselfInv : &A[geom.npoint-1];
DselfInternal(Stencil, *Aself, in, out, dag);
}
assert(Aself != nullptr);
GRID_ASSERT(Aself != nullptr);
}
void DselfInternal(CartesianStencil<siteVector,siteVector,DefaultImplParams> &st, CoarseMatrix &a,
@@ -697,7 +697,7 @@ public:
evenmask = where(mod(bcb,2)==(Integer)0,one,zero);
oddmask = one-evenmask;
assert(self_stencil!=-1);
GRID_ASSERT(self_stencil!=-1);
for(int i=0;i<nbasis;i++){
@@ -0,0 +1,854 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/multigrid/DenseCoarseMatrix.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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
#include <Grid/algorithms/blas/BatchedBlas.h>
#include <Grid/algorithms/blas/BatchedInverse.h>
#include <Grid/algorithms/multigrid/BlockCyclicSchurInverse.h>
#include <Grid/algorithms/multigrid/BlockCyclicRedistribute.h>
#include <unordered_map>
NAMESPACE_BEGIN(Grid);
//////////////////////////////////////////////////////////////////////////////////////
// DenseCoarseMatrix: a coarsened operator treated as a DENSE matrix -- explicit,
// row-distributed A^{-1} of a GeneralCoarsenedMatrix.
//
// - Stencil -> dense DIRECT IMPORT. The coarse operator IS the dense matrix
// unrolled: Dense[(s,a),(s+shift_p,b)] += A[p][s]_{a,b}. Rows of my sites are
// assembled from purely LOCAL _A[p] data: no operator applies, no comms.
// ACCUMULATE (+=) because on short axes distinct shifts wrap to the same
// neighbour. An IMPORT CERTIFICATE compares the dense apply against Op.M on a
// NON-CONSTANT vector (a constant one cannot see a shift-sign error).
//
// - Inversion is END-TO-END fp64 through the 2D block-cyclic recursive Schur
// complement (BlockCyclicSchurInverse): fp64 rank-major import ->
// RowsToCyclic -> in-place recursion (pure point-to-point SUMMA rings and
// local leaves; bitwise reproducible) -> CyclicToRows -> ONE terminal
// rounding into the fp32 apply slab. Distributed at every N and P.
//
// - Split-K apply through GridBLAS.gemmBatched with EXPLICIT leading dimensions
// (arXiv:2409.03904 fig 11): the tiny-output/huge-K GEMM Y = slab^T X becomes
// SPLITK chunk-GEMMs by pointer offset into the resident slab (lda = N),
// partials reduced in one accelerator_for. The source vector is assembled by
// a cartesian ring ALLGATHER of device buffers (pure P2P; ~8x fewer bytes
// than a padded allreduce, and no collective size cliffs). Platform-agnostic:
// deviceVector + GridBLAS run the SAME code on HIP/CUDA/SYCL and CPU(Eigen).
//
// VERIFY ||A Ainv x - x||/||x|| certifies the DEVICE slab + split-K path at the
// end of Import, since the single-RHS apply routes through the same core.
//
// Tensor-depth agnostic: site scalar objects treated as contiguous ComplexD
// (iScalar wrappers add no data), so any MG level's coarse operator imports.
//////////////////////////////////////////////////////////////////////////////////////
//
// Depends on a coarse operator only to extract its matrix elements; thereafter
// it is given a coarse vector and applies the inverse. Import() is a template
// member so any of the coarse classes will do, and the type of a
// DenseCoarseMatrix does not record which one built it.
//
template<class CComplex,int nbasis>
class DenseCoarseMatrix : public LinearFunction<Lattice<iVector<CComplex,nbasis> > > {
public:
typedef iVector<CComplex,nbasis > siteVector;
typedef Lattice<siteVector> CoarseVector;
typedef Lattice<iMatrix<CComplex,nbasis > > CoarseMatrix;
typedef CoarseVector Field;
using LinearFunction<Field>::operator();
typedef typename Field::vector_object vobj;
typedef typename vobj::scalar_object sobj;
typedef typename CoarseMatrix::vector_object Mvobj;
typedef typename Mvobj::scalar_object Msobj;
GridBase *grid;
int nd;
int64_t N; // dense rank = gSites * nbasis
int lsites; // my local sites
int64_t nrows; // my rows = lsites * nbasis
std::vector<Coordinate> myLcoor; // local coordinate of my site ss
std::vector<int64_t> myGsite; // global lex site index of my site ss
std::vector<ComplexF> slab; // nrows x N row-major: A during setup, rows of A^{-1} after
static const int MRHS_MAX = 32;
static const int SPLITK = 32; // requested split-K chunk count, snapped DOWN to a divisor of N
// Apply machinery: resident slab + persistent buffers + AOT split-K pointers.
GridBLAS BLAS;
deviceVector<ComplexF> dSlab;
deviceVector<ComplexF> dX; // N x MRHS_MAX
deviceVector<ComplexF> dY; // nrows x MRHS_MAX
deviceVector<ComplexF> dG; // N x MRHS_MAX lex-major staging for the allgather
deviceVector<int> dLex2Rank;// lex index of a process coordinate -> its rank (allgather block order -> row-block order)
deviceVector<int64_t> dRm2G; // rank-major index (rank*nrows + ss*nbasis + b) -> global column (gsite*nbasis + b) of x / the slab
int myLex;
deviceVector<ComplexF> dPartial; // NK x (nrows x MRHS_MAX)
deviceVector<ComplexF*> aptrs; // slab K-chunk pointers (lda = N)
deviceVector<ComplexF*> xptrs; // X K-chunk pointers (ldb = N)
deviceVector<ComplexF*> cptrs; // partial buffers (ldc = nrows)
std::vector<ComplexF> hX;
std::vector<ComplexF> hY;
int NK; // split-K chunk count (divides N)
DenseCoarseMatrix(GridBase *g)
: grid(g)
{
GRID_ASSERT( sizeof(sobj) == nbasis*sizeof(ComplexD) );
GRID_ASSERT( sizeof(Msobj) == nbasis*nbasis*sizeof(ComplexD) );
nd = grid->_ndimension;
N = grid->gSites() * nbasis;
lsites = grid->lSites();
nrows = (int64_t)lsites * nbasis;
std::cout << GridLogMessage << "DenseCoarseMatrix: N = " << N
<< " (" << grid->gSites() << " sites x " << nbasis << ")"
<< " rows/rank = " << nrows
<< " slab = " << (double)nrows*N*sizeof(ComplexF)/1024./1024. << " MB/rank"
<< std::endl;
////////////////////////////////////////////////////////////////////
// Enumerate my sites: local coords and global lexicographic indices
////////////////////////////////////////////////////////////////////
Coordinate ldims = grid->LocalDimensions();
Coordinate gdims = grid->GlobalDimensions();
myLcoor.resize(lsites);
myGsite.resize(lsites);
for(int ss=0; ss<lsites; ss++){
Coordinate lcoor(nd);
Lexicographic::CoorFromIndex(lcoor, ss, ldims);
Coordinate gcoor(nd);
for(int d=0; d<nd; d++) gcoor[d] = grid->_lstart[d] + lcoor[d];
int64_t gsite;
Lexicographic::IndexFromCoor(gcoor, gsite, gdims);
myLcoor[ss] = lcoor;
myGsite[ss] = gsite;
}
slab.resize((uint64_t)nrows * N);
}
////////////////////////////////////////////////////////////////////
// The only place a coarse operator is needed: pull its elements, invert,
// and make the slab resident. Any class exposing Geometry() and
// ExtractMatrix(p,A) will do -- single RHS or either multiRHS.
////////////////////////////////////////////////////////////////////
template<class CoarseOp>
void Import(CoarseOp &Op)
{
double t0 = usecond();
{
ImportDense(Op); // slab <- my rows of A (LOCAL, no comms)
ImportCertificate(Op); // dense apply == Op.M, before inversion
InvertDense(Op); // slab <- my rows of A^{-1}
double t1 = usecond();
std::cout << GridLogMessage << "DenseCoarseMatrix: import+invert took "
<< (t1-t0)/1.0e6 << " s" << std::endl;
}
////////////////////////////////////////////////////////////////////
// Device residency + persistent apply buffers + AOT split-K pointers
////////////////////////////////////////////////////////////////////
{
uint64_t sbytes = (uint64_t)nrows * N * sizeof(ComplexF);
dSlab.resize((uint64_t)nrows*N);
acceleratorCopyToDevice(&slab[0],&dSlab[0],sbytes);
// Split-K chunk count, snapped DOWN to a divisor of N.
NK = 1;
for(int j=1;j<=SPLITK;j++) if ( (N % j) == 0 ) NK = j;
int64_t Kc = N / NK;
dX.resize((uint64_t)N*MRHS_MAX);
dY.resize((uint64_t)nrows*MRHS_MAX);
dPartial.resize((uint64_t)NK*nrows*MRHS_MAX);
hX.resize((uint64_t)N*MRHS_MAX);
hY.resize((uint64_t)nrows*MRHS_MAX);
aptrs.resize(NK); xptrs.resize(NK); cptrs.resize(NK);
std::vector<ComplexF*> h(NK);
for(int j=0;j<NK;j++) h[j] = &dSlab[0] + (uint64_t)j*Kc; // K-offset, lda=N
acceleratorCopyToDevice(&h[0],&aptrs[0],NK*sizeof(ComplexF*));
for(int j=0;j<NK;j++) h[j] = &dX[0] + (uint64_t)j*Kc; // K-offset, ldb=N
acceleratorCopyToDevice(&h[0],&xptrs[0],NK*sizeof(ComplexF*));
for(int j=0;j<NK;j++) h[j] = &dPartial[0] + (uint64_t)j*nrows*MRHS_MAX; // compact, ldc=nrows
acceleratorCopyToDevice(&h[0],&cptrs[0],NK*sizeof(ComplexF*));
{
dG.resize((uint64_t)N*MRHS_MAX);
// allgather delivers blocks in lexicographic-coordinate order; the row
// blocks of x are in RANK order. Same table as BuildRankMajorMap.
int P = grid->ProcessorCount();
std::vector<int> l2r(P);
for(int lp=0; lp<P; lp++){ Coordinate pc(nd); Lexicographic::CoorFromIndex(pc, lp, grid->_processors); l2r[lp] = grid->RankFromProcessorCoor(pc); }
dLex2Rank.resize(P);
acceleratorCopyToDevice(&l2r[0], &dLex2Rank[0], P*sizeof(int));
myLex = CartesianLexIndex(grid);
GRID_ASSERT( l2r[myLex] == grid->ThisRank() );
// x and the slab columns are in GLOBAL-SITE order (hX[myGsite*nbasis+b]);
// the gathered blocks are in RANK-MAJOR order (rank*nrows + ss*nbasis + b).
// The two coincide only on one rank, so scatter through the inverse map.
std::vector<int64_t> g2rm; BuildRankMajorMap(g2rm);
std::vector<int64_t> rm2g(N); for(int64_t g=0; g<N; g++) rm2g[g2rm[g]] = g;
for(int ss=0; ss<lsites; ss++) GRID_ASSERT( rm2g[(int64_t)grid->ThisRank()*nrows + (int64_t)ss*nbasis] == myGsite[ss]*nbasis );
dRm2G.resize(N);
acceleratorCopyToDevice(&rm2g[0], &dRm2G[0], N*sizeof(int64_t));
}
std::cout << GridLogMessage << "DenseCoarseMatrix: slab resident on device ("
<< sbytes/1024./1024. << " MB/rank), split-K NK=" << NK << " (Kc=" << Kc
<< "); DEVICE cartesian ring ALLGATHER (P2P)" << std::endl;
}
////////////////////////////////////////////////////////////////////
// VERIFY: || A (Ainv x) - x || / ||x|| through the DEVICE split-K core.
////////////////////////////////////////////////////////////////////
{
Field x(grid); Field y(grid); Field z(grid);
x = ComplexD(1.0,0.0);
double ta = usecond();
(*this)(x, y);
double tb = usecond();
ApplyOracle(Op, y, z);
z = z - x;
RealD rel = std::sqrt(norm2(z)/norm2(x));
std::cout << GridLogMessage << "DenseCoarseMatrix: VERIFY ||A Ainv x - x||/||x|| = "
<< rel << " (one apply took " << (tb-ta)/1000.0 << " ms)" << std::endl;
GRID_ASSERT(rel < 1.0e-2);
}
std::cout << GridLogMessage << "DenseCoarseMatrix: setup complete, total "
<< (usecond()-t0)/1.0e6 << " s" << std::endl;
}
////////////////////////////////////////////////////////////////////
// Apply the source operator to a D dimensional field, whichever kind it is.
//
// A multiRHS operator lives on the D+1 grid, so drive it with several right
// hand sides at once: slice r carries (r+1)*in, and linearity says the
// results must scale likewise. One apply, and unlike a single rhs check it
// also catches rhs mixing. Cheap check, not a production path.
////////////////////////////////////////////////////////////////////
template<class CoarseOp>
void ApplyOracle(CoarseOp &Op,const Field &in, Field &out)
{
if ( Op.Grid() == grid ) { Op.M(in,out); return; }
GridBase *mgrid = Op.Grid();
GRID_ASSERT(mgrid->_ndimension == nd+1);
int nr = mgrid->_fdimensions[0];
Field min(mgrid), mout(mgrid);
for(int r=0;r<nr;r++){
Field scaled(grid);
scaled = ComplexD(r+1.0,0.0)*in;
InsertSliceFast(scaled,min,r,0);
}
Op.M(min,mout);
ExtractSliceFast(out,mout,0,0);
for(int r=1;r<nr;r++){
Field sr(grid),d(grid);
ExtractSliceFast(sr,mout,r,0);
d = sr - ComplexD(r+1.0,0.0)*out;
RealD rel = std::sqrt(norm2(d)/norm2(sr));
if ( rel >= 1.0e-6 ) {
std::cout << GridLogMessage << "DenseCoarseMatrix: oracle rhs "<<r
<<" inconsistent with rhs 0, rel "<<rel<<std::endl;
}
GRID_ASSERT( rel < 1.0e-6 );
}
}
////////////////////////////////////////////////////////////////////
// 1. Direct stencil -> dense import of MY ROWS of A (no comms):
// Dense[(s,a),(wrap(s+shift_p),b)] += A[p][s]_{a,b}
////////////////////////////////////////////////////////////////////
template<class CoarseOp>
void ImportDense(CoarseOp &Op)
{
double t = -usecond();
Coordinate gdims = grid->GlobalDimensions();
uint64_t nelem = (uint64_t)nrows * N;
thread_for(i, nelem, { slab[i] = ComplexF(0.0,0.0); });
for(int p=0; p<Op.Geometry().npoint; p++){
Coordinate shift = Op.Geometry().shifts[p];
// _A[p] is PADDED after ExchangeCoarseLinks (end of CoarsenOperator):
// extract the unpadded field before peeking with unpadded coordinates
// (exactly as MultiGeneralCoarsenedMatrix::CopyMatrix does).
CoarseMatrix Aun(grid); Op.ExtractMatrix(p,Aun);
autoView(Av, Aun, CpuRead);
thread_for(ss, lsites, {
Coordinate ncoor(nd);
for(int d=0; d<nd; d++){
int64_t g = grid->_lstart[d] + myLcoor[ss][d] + shift[d];
ncoor[d] = (int)((g % gdims[d] + gdims[d]) % gdims[d]);
}
int64_t nsite;
Lexicographic::IndexFromCoor(ncoor, nsite, gdims);
Msobj m;
peekLocalSite(m, Av, myLcoor[ss]);
ComplexD *md = (ComplexD *)&m;
// The operator contracts out(s,b) = sum_a A[p](s)(a,b) in(nbr,a)
// (GeneralCoarsenedMatrix.h Mult kernel): the stored site matrix
// acts TRANSPOSED, so element (a,b) lands at dense row (s,b),
// column (nbr,a).
for(int b=0; b<nbasis; b++){
ComplexF *row = &slab[(uint64_t)(ss*nbasis+b)*N + nsite*nbasis];
for(int a=0; a<nbasis; a++)
row[a] += ComplexF(md[a*nbasis+b]); // += : wrapped shifts may collide
}
});
}
t += usecond();
// Structural diagnostic: identically-zero rows of my slab (a healthy
// coarse operator has none; dead rows mean a rank-deficient import
// or operator and the inverse will be NaN).
int64_t zrows = 0;
for(int64_t r=0; r<nrows; r++)
{
double mx = 0.0;
const ComplexF *row = &slab[(uint64_t)r*N];
for(int64_t j=0; j<N; j++)
{
mx = std::max(mx, (double)abs(row[j]));
}
if ( mx < 1.0e-30 ) zrows++;
}
RealD gz = (RealD)zrows;
grid->GlobalSumVector(&gz, 1);
std::cout << GridLogMessage << "DenseCoarseMatrix: stencil->dense import took "
<< t/1.0e6 << " s (" << Op.Geometry().npoint << " points, local, no comms)"
<< " zero rows " << (int64_t)gz << "/" << N << std::endl;
// Debug: coordinate pattern of live sites (mechanism fingerprint)
if ( (int64_t)gz > 0 )
{
int shown = 0;
for(int ss=0; ss<lsites && shown<24; ss++)
{
double mx = 0.0;
const ComplexF *row = &slab[(uint64_t)(ss*nbasis)*N];
for(int64_t j=0; j<N; j++)
{
mx = std::max(mx, (double)abs(row[j]));
}
if ( mx > 1.0e-30 )
{
std::cout << GridLogMessage << "DenseCoarseMatrix: LIVE site ss=" << ss
<< " lcoor " << myLcoor[ss] << std::endl;
shown++;
}
}
}
}
////////////////////////////////////////////////////////////////////
// 2. IMPORT CERTIFICATE: dense rows vs Op.M on a NON-CONSTANT vector.
// (Constant x has x[s+d]==x[s-d]: blind to a shift-sign error.)
////////////////////////////////////////////////////////////////////
template<class CoarseOp>
void ImportCertificate(CoarseOp &Op)
{
Field x(grid); Field Ax(grid); Field Dx(grid);
for(int ss=0; ss<lsites; ss++){
sobj s;
for(int b=0; b<nbasis; b++){
double ph = 0.37*(double)(myGsite[ss]*nbasis+b);
((ComplexD *)&s)[b] = ComplexD(std::cos(ph),std::sin(0.61*ph));
}
pokeLocalSite(s, x, myLcoor[ss]);
}
// gather full x (zero-fill + exact GlobalSum), dense rows on host
std::vector<ComplexD> xh((uint64_t)N, ComplexD(0.0,0.0));
for(int ss=0; ss<lsites; ss++){
sobj s;
peekLocalSite(s, x, myLcoor[ss]);
for(int b=0; b<nbasis; b++) xh[ myGsite[ss]*nbasis + b ] = ((ComplexD *)&s)[b];
}
grid->GlobalSumVector(&xh[0], (int)N);
std::vector<ComplexD> yh(nrows);
thread_for(r, nrows, {
ComplexD acc(0.0,0.0);
const ComplexF *row = &slab[(uint64_t)r * N];
for(int64_t j=0; j<N; j++) acc += ComplexD(row[j]) * xh[j];
yh[r] = acc;
});
for(int ss=0; ss<lsites; ss++){
sobj s;
for(int b=0; b<nbasis; b++) ((ComplexD *)&s)[b] = yh[ss*nbasis+b];
pokeLocalSite(s, Dx, myLcoor[ss]);
}
ApplyOracle(Op, x, Ax);
Field d(grid); d = Dx - Ax;
RealD rel = std::sqrt(norm2(d)/norm2(Ax));
std::cout << GridLogMessage << "DenseCoarseMatrix: IMPORT CERTIFICATE ||Dense x - A x||/||A x|| = "
<< rel << std::endl;
if ( rel >= 1.0e-3 ) {
std::cout << GridLogMessage << "DenseCoarseMatrix: IMPORT CERTIFICATE FAILED. If O(1), the "
<< "stencil shift-sign convention of the coarse operator has changed: "
<< "the import in ImportDense/ImportDenseFP64 must change with it"
<< std::endl;
}
GRID_ASSERT(rel < 1.0e-3);
}
////////////////////////////////////////////////////////////////////
// 3a. Global column -> rank-major column map, computed LOCALLY.
// Rank-major ordering: rank q's rows/columns are the contiguous
// block [q*nrows, (q+1)*nrows), ordered by q's local site index
// (uniform local volumes make ownership arithmetic exact).
// MPI_Cart_rank is queried ONCE PER RANK (serial, P calls) into a
// lex-processor table; the per-site sweep is then pure arithmetic.
////////////////////////////////////////////////////////////////////
void BuildRankMajorMap(std::vector<int64_t> &g2rm)
{
int P = grid->ProcessorCount();
Coordinate pdims = grid->_processors;
Coordinate gdims = grid->GlobalDimensions();
Coordinate ldims = grid->LocalDimensions();
std::vector<int> lexp2rank(P);
for(int lp=0; lp<P; lp++)
{
Coordinate pcoor(nd);
Lexicographic::CoorFromIndex(pcoor, lp, pdims);
lexp2rank[lp] = grid->RankFromProcessorCoor(pcoor);
}
int64_t gsites = grid->gSites();
g2rm.resize(N);
thread_for(gsite, gsites, {
Coordinate gcoor(nd);
Coordinate pcoor(nd);
Coordinate lcoor(nd);
Lexicographic::CoorFromIndex(gcoor, gsite, gdims);
for(int d=0; d<nd; d++)
{
pcoor[d] = gcoor[d]/ldims[d];
lcoor[d] = gcoor[d]-pcoor[d]*ldims[d];
}
int64_t lexp;
int64_t lsite;
Lexicographic::IndexFromCoor(pcoor, lexp, pdims);
Lexicographic::IndexFromCoor(lcoor, lsite, ldims);
int64_t base = (int64_t)lexp2rank[lexp]*nrows + lsite*nbasis;
for(int b=0; b<nbasis; b++)
{
g2rm[(uint64_t)gsite*nbasis + b] = base + b;
}
});
}
////////////////////////////////////////////////////////////////////
// 3b. Direct stencil -> fp64 rank-major import of MY ROWS of A (the
// end-to-end fp64 path: the stencil source IS ComplexD; nothing is
// rounded through fp32 on the way into the inversion). Same
// loop/sign/accumulate/transposed-contraction discipline as
// ImportDense; output is column-major rows x N with columns in
// rank-major order (g2rm).
// ALWAYS-ON CERTIFICATE: the fp64 import, rounded, must agree with
// the fp32 slab entry at the corresponding global column, over the
// WHOLE of my rows (few ulp: wrapped-shift collisions accumulate in
// different precision order). NaN-proof: non-finite entries are
// counted explicitly since max() silently masks NaN.
////////////////////////////////////////////////////////////////////
template<class CoarseOp>
void ImportDenseFP64(CoarseOp &Op, BlockRows &S, std::vector<int64_t> &g2rm)
{
Coordinate gdims = grid->GlobalDimensions();
std::vector<ComplexD> h((uint64_t)nrows*N, ComplexD(0.0,0.0));
for(int p=0; p<Op.Geometry().npoint; p++)
{
Coordinate shift = Op.Geometry().shifts[p];
CoarseMatrix Aun(grid); Op.ExtractMatrix(p,Aun);
autoView(Av, Aun, CpuRead);
thread_for(ss, lsites, {
Coordinate ncoor(nd);
for(int d=0; d<nd; d++)
{
int64_t g = grid->_lstart[d] + myLcoor[ss][d] + shift[d];
ncoor[d] = (int)((g % gdims[d] + gdims[d]) % gdims[d]);
}
int64_t nsite;
Lexicographic::IndexFromCoor(ncoor, nsite, gdims);
Msobj m;
peekLocalSite(m, Av, myLcoor[ss]);
ComplexD *md = (ComplexD *)&m;
// Transposed contraction as ImportDense: (a,b) lands at
// row (s,b), column (nbr,a); column index in rank-major order.
for(int a=0; a<nbasis; a++)
{
int64_t jj = g2rm[ nsite*nbasis + a ];
for(int b=0; b<nbasis; b++)
{
h[(uint64_t)(ss*nbasis+b) + (uint64_t)jj*nrows] += md[a*nbasis+b];
}
}
});
}
// Certificate vs the fp32 slab (slab holds A at this point)
double mx = 0.0;
int64_t nbad = 0;
for(int64_t i=0; i<nrows; i++)
{
for(int64_t gcol=0; gcol<N; gcol++)
{
ComplexD d64 = h[(uint64_t)(i + g2rm[gcol]*nrows)];
ComplexF f32 = slab[(uint64_t)i*N + gcol];
double dev = abs(ComplexD(f32) - d64);
if ( !std::isfinite(dev) ) nbad++;
else mx = std::max(mx, dev);
}
}
RealD gmx = mx;
RealD gbad = (RealD)nbad;
grid->GlobalMax(gmx);
grid->GlobalSumVector(&gbad, 1);
std::cout << GridLogMessage << "DenseCoarseMatrix: fp64 import certificate "
<< "max|A64 - A32| = " << gmx
<< " non-finite entries " << (int64_t)gbad << std::endl;
GRID_ASSERT( gbad == 0 );
GRID_ASSERT( gmx < 1.0e-5 );
S.Resize(nrows, N);
acceleratorCopyToDevice(&h[0], &S.data[0], (uint64_t)nrows*N*sizeof(ComplexD));
}
////////////////////////////////////////////////////////////////////
// 3c. The inverse: distributed recursive Schur, END-TO-END fp64.
// stencil (ComplexD) -> fp64 rank-major import -> fp64 recursion ->
// ONE terminal rounding into the fp32 apply slab. Everything
// downstream (device residency, split-K apply, VERIFY) is fp32.
////////////////////////////////////////////////////////////////////
template<class CoarseOp>
void InvertDense(CoarseOp &Op)
{
double t1 = usecond();
int P = grid->ProcessorCount();
int me = grid->ThisRank();
// Uniform local volumes => contiguous uniform ownership
std::vector<int64_t> rowStart(P+1);
for(int r=0; r<=P; r++)
{
rowStart[r] = (int64_t)r*nrows;
}
GRID_ASSERT( rowStart[P] == N );
std::vector<int64_t> g2rm;
BuildRankMajorMap(g2rm);
// Self-certifying map: my own global rows land at my rank-major slots
for(int ss=0; ss<lsites; ss++)
{
for(int a=0; a<nbasis; a++)
{
GRID_ASSERT( g2rm[ myGsite[ss]*nbasis + a ] == (int64_t)me*nrows + ss*nbasis + a );
}
}
BlockRows S;
ImportDenseFP64(Op, S, g2rm);
////////////////////////////////////////////////////////////////
// The 2D block-cyclic recursion (BlockCyclicSchurInverse).
// Everything in it -- redistribution, SUMMA rings, leaves -- is
// point-to-point SendToRecvFrom; no collectives at all. Block size
// nb = rows-per-rank makes the redistribution edges maximally
// regular.
////////////////////////////////////////////////////////////////
double t2, t3;
{
int Pr,Pc;
BlockCyclicLayout::ChooseProcessGrid(P, Pr, Pc);
int64_t nb = nrows;
GRID_ASSERT( nb >= 1 );
std::cout << GridLogMessage << "DenseCoarseMatrix: 2D SCHUR invert, process grid "
<< Pr << " x " << Pc << " nb " << nb
<< " (pure P2P: redistribute + SUMMA rings + local leaves)" << std::endl;
BlockCyclicMatrix A2(grid, N, nb, Pr, Pc);
BlockCyclicSchurInverse RSI2;
t2 = usecond();
BlockCyclicRedistribute::RowsToCyclic(grid, rowStart, &S.data[0], nrows, A2);
RSI2.Invert(A2);
BlockCyclicRedistribute::CyclicToRows(grid, rowStart, A2, &S.data[0], nrows);
t3 = usecond();
RSI2.ReportTelemetry(grid);
}
// The single terminal rounding: fp64 inverse -> fp32 apply slab
// (row-major, global columns)
{
std::vector<ComplexD> h((uint64_t)nrows*N);
acceleratorCopyFromDevice(&S.data[0], &h[0], (uint64_t)nrows*N*sizeof(ComplexD));
thread_for(gcol, N, {
int64_t jj = g2rm[gcol];
for(int64_t i=0; i<nrows; i++)
{
slab[(uint64_t)i*N + gcol] = ComplexF(h[(uint64_t)(i + jj*nrows)]);
}
});
}
double t4 = usecond();
std::cout << GridLogMessage << "DenseCoarseMatrix: SCHUR fp64 distributed invert took "
<< (t4-t1)/1.0e6 << " s (recursion " << (t3-t2)/1.0e6 << " s)" << std::endl;
}
////////////////////////////////////////////////////////////////////
// CORE apply on packed data: hX[N x nr] (zero-filled, my sites only)
// -> ring allgather -> split-K GEMM against the resident slab ->
// reduce partials -> hY[nrows x nr] (column major). Platform-agnostic:
// deviceVector + GridBLAS (Eigen fallback on CPU builds).
// tprof (optional): per-phase microseconds {allgather, H2D, gemm+reduce,
// D2H}, printed by the caller on GridLogPerformance.
////////////////////////////////////////////////////////////////////
void SlabApplyPacked(int nr, double *tprof)
{
GRID_TRACE("DenseSlabApply");
GRID_ASSERT(nr <= MRHS_MAX);
uint64_t nY = (uint64_t)nrows * nr;
int64_t Kc = N / NK;
double t1 = usecond();
double t2, t3;
{
// ALLGATHER: x is not a reduction -- every rank owns the rows of x at
// global columns myGsite[ss]*nbasis+b (scattered by site coordinate, NOT
// a contiguous block) and needs all of it. Only MY rows go host->device
// (nrows x nr, ~15 KB at nr=1), packed rank-major [r][ss*nbasis+b],
// gathered along the process grid, then scattered through rm2g into the
// column-major dX (ld = N, global-site columns) the split-K GEMM reads.
const uint64_t chunk = (uint64_t)nrows*nr; // my block: [r][ss*nbasis+b]
{ GRID_TRACE("DenseH2D");
std::vector<ComplexF> hG(chunk);
for(int r=0;r<nr;r++)
for(int ss=0; ss<lsites; ss++)
memcpy(&hG[(uint64_t)r*nrows + (uint64_t)ss*nbasis], &hX[(uint64_t)r*N + (uint64_t)myGsite[ss]*nbasis], nbasis*sizeof(ComplexF));
acceleratorCopyToDevice(&hG[0], &dG[(uint64_t)myLex*chunk], chunk*sizeof(ComplexF)); // my slot is my LEX index
}
t2 = usecond();
{ GRID_TRACE("DenseAllgather");
CartesianRingAllGather(grid, (ComplexF *)&dG[0], chunk);
// scatter lex block L=[r][i] -> dX[r*N + rm2g[rank(L)*nrows + i]]
ComplexF *g = &dG[0]; ComplexF *x = &dX[0]; int *l2r = &dLex2Rank[0]; int64_t *rm2g = &dRm2G[0];
const int64_t nrw = nrows; const int64_t NN = N; const int nrr = nr;
accelerator_for(idx, (uint64_t)N*nr, 1, {
int64_t r = idx / NN; int64_t gi = idx - r*NN;
int64_t L = gi / nrw; int64_t i = gi - L*nrw;
x[r*NN + rm2g[(int64_t)l2r[L]*nrw + i]] = g[L*(nrw*nrr) + r*nrw + i];
});
}
t3 = usecond();
}
// Y = op(slab,T) . X : row-major slab (nrows x N) == col-major A^T
// (N x nrows, lda=N) => transpose gives the nrows x N operator.
// Split-K: NK chunk-GEMMs by pointer offset (AOT lists), then reduce.
ComplexF one (1.0,0.0);
ComplexF zero(0.0,0.0);
{ GRID_TRACE("DenseSplitKGEMM");
BLAS.gemmBatched(GridBLAS_OP_T, GridBLAS_OP_N,
(int)nrows, nr, (int)Kc,
one, aptrs, (int)N,
xptrs, (int)N,
zero, cptrs, (int)nrows);
BLAS.synchronise();
ComplexF *pp = &dPartial[0];
ComplexF *py = &dY[0];
uint64_t stride = (uint64_t)nrows*MRHS_MAX;
int nk = NK;
accelerator_for(i, nY, 1, {
ComplexF acc(0.0,0.0);
for(int j=0;j<nk;j++) acc += pp[(uint64_t)j*stride + i];
py[i] = acc;
});
}
double t4 = usecond();
{ GRID_TRACE("DenseD2H");
acceleratorCopyFromDevice(&dY[0],&hY[0],nY*sizeof(ComplexF));
}
double t5 = usecond();
if (tprof) {
tprof[0] = t3-t2; // allgather
tprof[1] = t2-t1; // H2D
tprof[2] = t4-t3; // gemm+reduce
tprof[3] = t5-t4; // D2H
}
}
////////////////////////////////////////////////////////////////////
// Single-RHS apply (also the VERIFY path => certifies device slab).
////////////////////////////////////////////////////////////////////
virtual void operator()(const Field &src, Field &psi)
{
GRID_TRACE("DenseApply1");
uint64_t nX = (uint64_t)N;
{ GRID_TRACE("DensePack");
thread_for(i, nX, { hX[i]=ComplexF(0.0,0.0); });
for(int ss=0; ss<lsites; ss++){
sobj s;
peekLocalSite(s, src, myLcoor[ss]);
for(int b=0; b<nbasis; b++)
hX[ myGsite[ss]*nbasis + b ] = ComplexF(((ComplexD *)&s)[b]);
}
}
SlabApplyPacked(1, nullptr);
{ GRID_TRACE("DenseUnpack");
for(int ss=0; ss<lsites; ss++){
sobj s;
for(int b=0; b<nbasis; b++)
((ComplexD *)&s)[b] = ComplexD(hY[ss*nbasis + b]);
pokeLocalSite(s, psi, myLcoor[ss]);
}
}
}
////////////////////////////////////////////////////////////////////
// Defect of an applied inverse. Was a DENSE_CC_CHECK block inside
// operator(), but that is virtual and cannot take an operator, so the
// caller now asks for it explicitly.
////////////////////////////////////////////////////////////////////
template<class CoarseOp>
void CheckApply(CoarseOp &Op,const Field &src,const Field &psi)
{
Field tmp(grid);
Op.M(psi, tmp);
tmp = tmp - src;
std::cout << GridLogMessage << "DenseCoarseMatrix: apply defect ||A x - b||/||b|| = "
<< std::sqrt(norm2(tmp)/norm2(src)) << std::endl;
}
////////////////////////////////////////////////////////////////////
// Batched (vector-of-fields) apply.
////////////////////////////////////////////////////////////////////
void ApplyBatch(std::vector<Field> &src, std::vector<Field> &psi)
{
int nr = src.size();
GRID_ASSERT(nr <= MRHS_MAX);
double t0 = usecond();
uint64_t nX = (uint64_t)N*nr;
{ GRID_TRACE("DensePack");
thread_for(i, nX, { hX[i]=ComplexF(0.0,0.0); });
for(int rr=0; rr<nr; rr++){
for(int ss=0; ss<lsites; ss++){
sobj s;
peekLocalSite(s, src[rr], myLcoor[ss]);
for(int b=0; b<nbasis; b++)
hX[ (uint64_t)rr*N + myGsite[ss]*nbasis + b ] = ComplexF(((ComplexD *)&s)[b]);
}
}
}
SlabApplyPacked(nr, nullptr);
{ GRID_TRACE("DenseUnpack");
for(int rr=0; rr<nr; rr++){
for(int ss=0; ss<lsites; ss++){
sobj s;
for(int b=0; b<nbasis; b++)
((ComplexD *)&s)[b] = ComplexD(hY[(uint64_t)rr*nrows + (ss*nbasis+b)]);
pokeLocalSite(s, psi[rr], myLcoor[ss]);
}
}
}
double t1 = usecond();
std::cout << GridLogMessage << "DenseCoarseMatrix: batched apply " << nr << " rhs took "
<< (t1-t0)/1000.0 << " ms (" << (t1-t0)/1000.0/nr << " ms/rhs)" << std::endl;
}
template<class CoarseOp>
void CheckApplyBatch(CoarseOp &Op,std::vector<Field> &src,std::vector<Field> &psi,int nr)
{
Field tmp(grid);
for(int rr=0; rr<nr; rr++){
Op.M(psi[rr], tmp);
tmp = tmp - src[rr];
std::cout << GridLogMessage << "DenseCoarseMatrix: batch defect["<<rr<<"] = "
<< std::sqrt(norm2(tmp)/norm2(src[rr])) << std::endl;
}
}
////////////////////////////////////////////////////////////////////
// 6D mrhs apply: operates DIRECTLY on the packed 6D field (rhs = dim 0).
////////////////////////////////////////////////////////////////////
void ApplyBatch6D(const Field &in6, Field &out6, int nr)
{
GRID_ASSERT(nr <= MRHS_MAX);
GRID_ASSERT(in6.Grid()->_ndimension == nd+1); // {rhs, s, x,y,z,t}
double t0 = usecond();
Field &in = const_cast<Field &>(in6);
uint64_t nX = (uint64_t)N * nr;
thread_for(i, nX, { hX[i]=ComplexF(0.0,0.0); });
{ GRID_TRACE("DensePack");
autoView(iv, in, CpuRead);
Coordinate c6(nd+1);
for(int ss=0; ss<lsites; ss++){
for(int d=0; d<nd; d++) c6[d+1] = myLcoor[ss][d];
for(int rr=0; rr<nr; rr++){
c6[0] = rr;
sobj s;
peekLocalSite(s, iv, c6);
for(int b=0; b<nbasis; b++)
hX[(uint64_t)rr*N + myGsite[ss]*nbasis + b] = ComplexF(((ComplexD *)&s)[b]);
}
}
}
double t1 = usecond();
double tprof[4];
SlabApplyPacked(nr, tprof);
double t5 = usecond();
{ GRID_TRACE("DenseUnpack");
autoView(ov, out6, CpuWrite);
Coordinate c6(nd+1);
for(int ss=0; ss<lsites; ss++){
for(int d=0; d<nd; d++) c6[d+1] = myLcoor[ss][d];
for(int rr=0; rr<nr; rr++){
c6[0] = rr;
sobj s;
for(int b=0; b<nbasis; b++)
((ComplexD *)&s)[b] = ComplexD(hY[(uint64_t)rr*nrows + (ss*nbasis+b)]); // Y col-major
pokeLocalSite(s, ov, c6);
}
}
}
double t6 = usecond();
std::cout << GridLogMessage << "DenseCoarseMatrix: apply6D " << nr << " rhs took "
<< (t6-t0)/1000.0 << " ms" << std::endl;
std::cout << GridLogPerformance << "DenseCoarseMatrix: apply6D profile:"
<< " pack " << (t1-t0)/1000.0
<< " allgather " << tprof[0]/1000.0
<< " H2D " << tprof[1]/1000.0
<< " gemm+reduce "<< tprof[2]/1000.0
<< " D2H " << tprof[3]/1000.0
<< " unpack " << (t6-t5)/1000.0
<< " ms" << std::endl;
}
};
NAMESPACE_END(Grid);
@@ -31,6 +31,7 @@ Author: Peter Boyle <pboyle@bnl.gov>
#include <Grid/lattice/PaddedCell.h>
#include <Grid/stencil/GeneralLocalStencil.h>
#include <Grid/algorithms/deflation/MultiRHSBlockProject.h>
NAMESPACE_BEGIN(Grid);
@@ -66,9 +67,24 @@ public:
std::vector<CoarseMatrix> _Adag;
std::vector<CoarseVector> MultTemporaries;
int64_t MultCalls;
double MultFlopsAccum;
double MultUsecAccum;
///////////////////////
// Interface
///////////////////////
//////////////////////////////////////////////////////////////////////////
// Bilingual accessors: everything a consumer needs to read the operator
// without knowing which of the three coarse classes it holds. The D
// dimensional grid the elements live on, the geometry they are indexed by,
// and one unpadded point at a time (a whole npoint vector is too much
// memory at production nbasis).
//////////////////////////////////////////////////////////////////////////
GridCartesian * CoarseGridD(void) { return _CoarseGrid; };
NonLocalStencilGeometry & Geometry(void) { return geom; };
void ExtractMatrix(int p,CoarseMatrix &A) { A = Cell.Extract(_A[p]); };
GridBase * Grid(void) { return _CoarseGrid; }; // this is all the linalg routines need to know
GridBase * FineGrid(void) { return _FineGrid; }; // this is all the linalg routines need to know
GridCartesian * CoarseGrid(void) { return _CoarseGrid; }; // this is all the linalg routines need to know
@@ -99,24 +115,25 @@ public:
}
}
}
assert(nfound==geom.npoint);
GRID_ASSERT(nfound==geom.npoint);
ExchangeCoarseLinks();
}
*/
GeneralCoarsenedMatrix(NonLocalStencilGeometry &_geom,GridBase *FineGrid, GridCartesian * CoarseGrid)
GeneralCoarsenedMatrix(NonLocalStencilGeometry &_geom,GridBase *FineGrid, GridCartesian * CoarseGrid,int _herm=1)
: geom(_geom),
_FineGrid(FineGrid),
_CoarseGrid(CoarseGrid),
hermitian(1),
hermitian(_herm),
Cell(_geom.Depth(),_CoarseGrid),
Stencil(Cell.grids.back(),geom.shifts)
Stencil(Cell.grids.back(),geom.shifts),
MultCalls(0), MultFlopsAccum(0.0), MultUsecAccum(0.0)
{
{
int npoint = _geom.npoint;
}
_A.resize(geom.npoint,CoarseGrid);
// _Adag.resize(geom.npoint,CoarseGrid);
if ( !hermitian ) _Adag.resize(geom.npoint,CoarseGrid);
}
void M (const CoarseVector &in, CoarseVector &out)
{
@@ -124,10 +141,10 @@ public:
}
void Mdag (const CoarseVector &in, CoarseVector &out)
{
assert(hermitian);
Mult(_A,in,out);
// if ( hermitian ) M(in,out);
// else Mult(_Adag,in,out);
if(hermitian)
Mult(_A,in,out);
else
Mult(_Adag,in,out);
}
void Mult (std::vector<CoarseMatrix> &A,const CoarseVector &in, CoarseVector &out)
{
@@ -227,29 +244,28 @@ public:
text+=usecond();
ttot+=usecond();
std::cout << GridLogPerformance<<"Coarse 1rhs Mult Aviews "<<tviews<<" us"<<std::endl;
std::cout << GridLogPerformance<<"Coarse Mult exch "<<texch<<" us"<<std::endl;
std::cout << GridLogPerformance<<"Coarse Mult mult "<<tmult<<" us"<<std::endl;
std::cout << GridLogPerformance<<" of which mult2 "<<tmult2<<" us"<<std::endl;
std::cout << GridLogPerformance<<"Coarse Mult ext "<<text<<" us"<<std::endl;
std::cout << GridLogPerformance<<"Coarse Mult temps "<<ttemps<<" us"<<std::endl;
std::cout << GridLogPerformance<<"Coarse Mult copy "<<tcopy<<" us"<<std::endl;
std::cout << GridLogPerformance<<"Coarse Mult tot "<<ttot<<" us"<<std::endl;
// std::cout << GridLogPerformance<<std::endl;
std::cout << GridLogPerformance<<"Coarse Kernel flops "<< flops<<std::endl;
std::cout << GridLogPerformance<<"Coarse Kernel flop/s "<< flops/tmult<<" mflop/s"<<std::endl;
std::cout << GridLogPerformance<<"Coarse Kernel bytes/s "<< bytes/tmult<<" MB/s"<<std::endl;
std::cout << GridLogPerformance<<"Coarse overall flops/s "<< flops/ttot<<" mflop/s"<<std::endl;
std::cout << GridLogPerformance<<"Coarse total bytes "<< bytes/1e6<<" MB"<<std::endl;
MultCalls++;
MultFlopsAccum += flops;
MultUsecAccum += ttot;
std::cout << GridLogPerformance
<< "Coarse Mult call " << MultCalls
<< " tot " << ttot << " us"
<< " kernel " << tmult << " us"
<< " kernel " << flops/tmult*1e-3 << " GFlop/s"
<< " overall " << MultFlopsAccum/MultUsecAccum*1e-3 << " GFlop/s (cumul)"
<< " bw " << bytes/tmult*1e-3 << " GB/s"
<< std::endl;
};
void PopulateAdag(void)
{
#if 0
// Serial global peek/poke reference implementation
for(int64_t bidx=0;bidx<CoarseGrid()->gSites() ;bidx++){
Coordinate bcoor;
CoarseGrid()->GlobalIndexToGlobalCoor(bidx,bcoor);
for(int p=0;p<geom.npoint;p++){
Coordinate scoor = bcoor;
for(int mu=0;mu<bcoor.size();mu++){
@@ -262,6 +278,36 @@ public:
pokeSite(adj(link),_Adag[pp],bcoor);
}
}
#else
// Parallel: _Adag[pp](x) = adj( _A[p](x + s_pp) ), pp = Reverse(p), s_pp = -s_p.
// The neighbour fetch reuses the same padded-cell + stencil machinery as Mult,
// reading one matrix element per coalesced access so no whole site matrix
// (230KB at nbasis=60) ever lands on a GPU thread stack (HIP limit 128KB).
// Halo sites compute garbage neighbours; Cell.Extract discards them.
// Must run on the unpadded _A, i.e. before ExchangeCoarseLinks.
const int Nsimd = CComplex::Nsimd();
for(int p=0;p<geom.npoint;p++){
int pp = geom.Reverse(p);
CoarseMatrix Apad = Cell.ExchangePeriodic(_A[p]);
CoarseMatrix Dpad(Apad.Grid());
int64_t osites = Apad.Grid()->oSites();
{
autoView( Apad_v , Apad, AcceleratorRead);
autoView( Dpad_v , Dpad, AcceleratorWriteDiscard);
autoView( Stencil_v, Stencil, AcceleratorRead);
accelerator_for(sj, osites*nbasis, Nsimd, {
int32_t ss = sj/nbasis;
int32_t j = sj%nbasis;
auto SE = Stencil_v.GetEntry(pp,ss);
for(int i=0;i<nbasis;i++){
auto z = coalescedReadGeneralPermute(Apad_v[SE->_offset](i,j),SE->_permute,Nd);
coalescedWrite(Dpad_v[ss](j,i),conjugate(z));
}
});
}
_Adag[pp] = Cell.Extract(Dpad);
}
#endif
}
/////////////////////////////////////////////////////////////
//
@@ -417,10 +463,19 @@ public:
int osites=CoarseGrid()->oSites();
autoView( A_v , _A[k], AcceleratorWrite);
autoView( FT_v , FT[k], AcceleratorRead);
accelerator_for(sss, osites, 1, {
accelerator_for(sss, osites, nbasis, {
#ifdef GRID_SIMT
int j = acceleratorSIMTlane(nbasis);
A_v[sss](i,j) = FT_v[sss](j);
#else
// CPU build: acceleratorSIMTlane()==0 -- an un-looped SIMT tensor
// index writes ONLY j=0 and silently drops the other nbasis-1
// columns (caught by Test_schur_dense_coarse import certificate,
// 2026-08-14). Loop explicitly.
for(int j=0;j<nbasis;j++){
A_v[sss](i,j) = FT_v[sss](j);
}
#endif
});
}
tinv+=usecond();
@@ -428,8 +483,8 @@ public:
// Only needed if nonhermitian
if ( ! hermitian ) {
// std::cout << GridLogMessage<<"PopulateAdag "<<std::endl;
// PopulateAdag();
std::cout << GridLogMessage<<"PopulateAdag "<<std::endl;
PopulateAdag();
}
// Need to write something to populate Adag from A
@@ -441,8 +496,20 @@ public:
std::cout << GridLogMessage<<"CoarsenOperator inv "<<tinv<<" us"<<std::endl;
}
#else
//////////////////////////////////////////////////////////////////////
// Galerkin projection of matrix
//////////////////////////////////////////////////////////////////////
void CoarsenOperator(LinearOperatorBase<Lattice<Fobj> > &linop,
Aggregation<Fobj,CComplex,nbasis> & Subspace)
{
CoarsenOperator(linop,Subspace,Subspace);
}
//////////////////////////////////////////////////////////////////////
// Petrov - Galerkin projection of matrix
//////////////////////////////////////////////////////////////////////
void CoarsenOperator(LinearOperatorBase<Lattice<Fobj> > &linop,
Aggregation<Fobj,CComplex,nbasis> & U,
Aggregation<Fobj,CComplex,nbasis> & V)
{
std::cout << GridLogMessage<< "GeneralCoarsenMatrix "<< std::endl;
GridBase *grid = FineGrid();
@@ -458,11 +525,9 @@ public:
// Orthogonalise the subblocks over the basis
/////////////////////////////////////////////////////////////
CoarseScalar InnerProd(CoarseGrid());
blockOrthogonalise(InnerProd,Subspace.subspace);
blockOrthogonalise(InnerProd,V.subspace);
blockOrthogonalise(InnerProd,U.subspace);
// for(int s=0;s<Subspace.subspace.size();s++){
// std::cout << " subspace norm "<<norm2(Subspace.subspace[s])<<std::endl;
// }
const int npoint = geom.npoint;
Coordinate clatt = CoarseGrid()->GlobalDimensions();
@@ -507,13 +572,9 @@ public:
// Now compute the matrix elements of linop between the orthonormal
// set of vectors.
///////////////////////////////////////////////////////////////////////
FineField phaV(grid); // Phased block basis vector
FineField MphaV(grid);// Matrix applied
std::vector<FineComplexField> phaF(npoint,grid);
std::vector<CoarseComplexField> pha(npoint,CoarseGrid());
CoarseVector coarseInner(CoarseGrid());
typedef typename CComplex::scalar_type SComplex;
FineComplexField one(grid); one=SComplex(1.0);
FineComplexField zz(grid); zz = Zero();
@@ -532,37 +593,52 @@ public:
pha[p] =exp(pha[p]*ci);
blockZAXPY(phaF[p],pha[p],one,zz);
}
tphase+=usecond();
std::vector<CoarseVector> ComputeProj(npoint,CoarseGrid());
std::vector<CoarseVector> FT(npoint,CoarseGrid());
// Import basis into BLAS layout once; blockProject then reads it once per
// basis vector rather than once per (i,p) as in scalar blockProject.
// Process all npoint in a single batch.
MultiRHSBlockProject<FineField> Projector;
Projector.Allocate(nbasis, grid, CoarseGrid());
Projector.ImportBasis(U.subspace);
std::vector<FineField> phaV_batch(npoint, grid);
std::vector<FineField> MphaV_batch(npoint, grid);
std::vector<CoarseVector> proj_batch(npoint, CoarseGrid());
std::vector<CoarseVector> ComputeProj(npoint, CoarseGrid());
std::vector<CoarseVector> FT(npoint, CoarseGrid());
// Pre-allocate BLAS_F and BLAS_C to avoid repeated hipMalloc/hipFree of
// ~5.6 GB per blockProject call, which hangs on ROCm for large allocations.
Projector.BLAS_F.resize(Projector.fine_vol * Projector.words * npoint);
Projector.BLAS_C.resize(Projector.coarse_vol * nbasis * npoint);
for(int i=0;i<nbasis;i++){// Loop over basis vectors
accelerator_barrier(); // ensure prior iteration's async writes are retired
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<"/"<<nbasis<< std::endl;
for(int p=0;p<npoint;p++){ // Loop over momenta in npoint
tphaseBZ-=usecond();
phaV = phaF[p]*Subspace.subspace[i];
tphaseBZ+=usecond();
/////////////////////////////////////////////////////////////////////
// Multiple phased subspace vector by matrix and project to subspace
// Remove local bulk phase to leave relative phases
/////////////////////////////////////////////////////////////////////
tmat-=usecond();
linop.Op(phaV,MphaV);
tmat+=usecond();
// std::cout << i << " " <<p << " MphaV "<<norm2(MphaV)<<" "<<norm2(phaV)<<std::endl;
tphaseBZ-=usecond();
for(int p=0;p<npoint;p++)
phaV_batch[p] = phaF[p] * V.subspace[i];
tphaseBZ+=usecond();
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<" phaseBZ done"<< std::endl;
tproj-=usecond();
blockProject(coarseInner,MphaV,Subspace.subspace);
coarseInner = conjugate(pha[p]) * coarseInner;
tmat-=usecond();
for(int p=0;p<npoint;p++)
linop.Op(phaV_batch[p], MphaV_batch[p]);
tmat+=usecond();
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<" mat done"<< std::endl;
ComputeProj[p] = coarseInner;
tproj+=usecond();
// std::cout << i << " " <<p << " ComputeProj "<<norm2(ComputeProj[p])<<std::endl;
}
// One batched GEMM reads BLAS_V once for all npoint vectors.
tproj-=usecond();
Projector.blockProject(MphaV_batch, proj_batch);
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<" blockProject done"<< std::endl;
for(int p=0;p<npoint;p++)
ComputeProj[p] = conjugate(pha[p]) * proj_batch[p];
tproj+=usecond();
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<" proj done"<< std::endl;
tinv-=usecond();
for(int k=0;k<npoint;k++){
@@ -570,14 +646,23 @@ public:
for(int l=0;l<npoint;l++){
FT[k]= FT[k]+ invMkl(l,k)*ComputeProj[l];
}
int osites=CoarseGrid()->oSites();
autoView( A_v , _A[k], AcceleratorWrite);
autoView( FT_v , FT[k], AcceleratorRead);
accelerator_for(sss, osites, 1, {
accelerator_for(sss, osites, nbasis, {
#ifdef GRID_SIMT
int j = acceleratorSIMTlane(nbasis);
A_v[sss](i,j) = FT_v[sss](j);
#else
// CPU build: acceleratorSIMTlane()==0 -- an un-looped SIMT tensor
// index writes ONLY j=0 and silently drops the other nbasis-1
// columns (caught by Test_schur_dense_coarse import certificate,
// 2026-08-14). Loop explicitly.
for(int j=0;j<nbasis;j++){
A_v[sss](i,j) = FT_v[sss](j);
}
#endif
});
}
tinv+=usecond();
@@ -585,13 +670,13 @@ public:
// Only needed if nonhermitian
if ( ! hermitian ) {
// std::cout << GridLogMessage<<"PopulateAdag "<<std::endl;
// PopulateAdag();
std::cout << GridLogMessage<<"PopulateAdag "<<std::endl;
PopulateAdag();
}
for(int p=0;p<geom.npoint;p++){
std::cout << " _A["<<p<<"] "<<norm2(_A[p])<<std::endl;
}
// for(int p=0;p<geom.npoint;p++){
// std::cout << " _A["<<p<<"] "<<norm2(_A[p])<<std::endl;
// }
// Need to write something to populate Adag from A
ExchangeCoarseLinks();
@@ -606,10 +691,10 @@ public:
void ExchangeCoarseLinks(void){
for(int p=0;p<geom.npoint;p++){
_A[p] = Cell.ExchangePeriodic(_A[p]);
// _Adag[p]= Cell.ExchangePeriodic(_Adag[p]);
if ( !hermitian ) _Adag[p]= Cell.ExchangePeriodic(_Adag[p]);
}
}
virtual void Mdiag (const Field &in, Field &out){ assert(0);};
virtual void Mdiag (const Field &in, Field &out){ GRID_ASSERT(0);};
virtual void Mdir (const Field &in, Field &out,int dir, int disp){assert(0);};
virtual void MdirAll (const Field &in, std::vector<Field> &out){assert(0);};
};
@@ -77,15 +77,24 @@ public:
GridBase * Grid(void) { return _CoarseGridMulti; }; // this is all the linalg routines need to know
GridCartesian * CoarseGrid(void) { return _CoarseGridMulti; }; // this is all the linalg routines need to know
// Can be used to do I/O on the operator matrices externally
void SetMatrix (int p,CoarseMatrix & A)
//////////////////////////////////////////////////////////////////////////
// Bilingual accessors, matching GeneralCoarsenedMatrix. Grid() here is the
// D+1 multiRHS grid and this class never holds the D dimensional one, so
// ExtractMatrix writes into whatever grid the caller's lattice is on.
//////////////////////////////////////////////////////////////////////////
NonLocalStencilGeometry & Geometry(void) { return geom_srhs; };
void ExtractMatrix(int p,CoarseMatrix &A) { BLAStoGrid(A,BLAS_A[p]); };
// 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<CoarseMatrix> & A)
{
assert(A.size()==geom_srhs.npoint);
GRID_ASSERT(A.size()==geom_srhs.npoint);
GridtoBLAS(A[p],BLAS_A[p]);
}
void GetMatrix (int p,CoarseMatrix & A)
void GetMatrix (int p,std::vector<CoarseMatrix> & A)
{
assert(A.size()==geom_srhs.npoint);
GRID_ASSERT(A.size()==geom_srhs.npoint);
BLAStoGrid(A[p],BLAS_A[p]);
}
void CopyMatrix (GeneralCoarseOp &_Op)
@@ -178,14 +187,14 @@ public:
for(int32_t point = 0 ; point < geom.npoint; point++){
int i=s*orhs*geom.npoint+point;
int32_t nbr = Stencil._entries[i]._offset*CComplex::Nsimd(); // oSite -> lSite
assert(nbr<BLAS_B.size());
GRID_ASSERT(nbr<BLAS_B.size());
ComplexD * ptr = (ComplexD *)&BLAS_B[nbr];
acceleratorPut(BLAS_BP[point][j],ptr); // neighbour indexing in ghost zone volume
}
j++;
}
}
assert(j==unpadded_sites);
GRID_ASSERT(j==unpadded_sites);
}
template<class vobj> void GridtoBLAS(const Lattice<vobj> &from,deviceVector<typename vobj::scalar_object> &to)
{
@@ -194,7 +203,7 @@ public:
typedef typename vobj::vector_type vector_type;
GridBase *Fg = from.Grid();
assert(!Fg->_isCheckerBoarded);
GRID_ASSERT(!Fg->_isCheckerBoarded);
int nd = Fg->_ndimension;
to.resize(Fg->lSites());
@@ -241,10 +250,10 @@ public:
typedef typename vobj::vector_type vector_type;
GridBase *Tg = grid.Grid();
assert(!Tg->_isCheckerBoarded);
GRID_ASSERT(!Tg->_isCheckerBoarded);
int nd = Tg->_ndimension;
assert(in.size()==Tg->lSites());
GRID_ASSERT(in.size()==Tg->lSites());
Coordinate LocalLatt = Tg->LocalDimensions();
size_t nsite = 1;
@@ -669,7 +678,7 @@ Grid : Message : 328.193436 s : CoarsenOperator mat 122213270 us
const int Nsimd = CComplex::Nsimd();
int64_t nrhs =pin.Grid()->GlobalDimensions()[0];
assert(nrhs>=1);
GRID_ASSERT(nrhs>=1);
RealD flops,bytes;
int64_t osites=in.Grid()->oSites(); // unpadded
@@ -721,7 +730,7 @@ Grid : Message : 328.193436 s : CoarsenOperator mat 122213270 us
// std::cout << GridLogMessage<<"Coarse overall flops/s "<< flops/t_tot<<" mflop/s"<<std::endl;
// std::cout << GridLogMessage<<"Coarse total bytes "<< bytes/1e6<<" MB"<<std::endl;
};
virtual void Mdiag (const Field &in, Field &out){ assert(0);};
virtual void Mdiag (const Field &in, Field &out){ GRID_ASSERT(0);};
virtual void Mdir (const Field &in, Field &out,int dir, int disp){assert(0);};
virtual void MdirAll (const Field &in, std::vector<Field> &out){assert(0);};
};
@@ -0,0 +1,870 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/GeneralCoarsenedMatrixMultiRHS.h
Copyright (C) 2015
Author: Peter Boyle <pboyle@bnl.gov>
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 Fobj,class CComplex,int nbasis>
class MultiGeneralCoarsenedOperatorV2 : public SparseMatrixBase<Lattice<iVector<CComplex,nbasis > > > {
public:
typedef typename CComplex::scalar_object SComplex;
typedef GeneralCoarsenedMatrix<Fobj,CComplex,nbasis> GeneralCoarseOp;
typedef MultiGeneralCoarsenedOperatorV2<Fobj,CComplex,nbasis> MultiGeneralCoarseOp;
typedef iVector<CComplex,nbasis > siteVector;
typedef iMatrix<CComplex,nbasis > siteMatrix;
typedef iVector<SComplex,nbasis > calcVector;
typedef iMatrix<SComplex,nbasis > calcMatrix;
typedef Lattice<iScalar<CComplex> > CoarseComplexField;
typedef Lattice<siteVector> CoarseVector;
typedef Lattice<iMatrix<CComplex,nbasis > > CoarseMatrix;
typedef iMatrix<CComplex,nbasis > Cobj;
typedef iVector<CComplex,nbasis > Cvec;
typedef Lattice< CComplex > CoarseScalar; // used for inner products on fine field
typedef Lattice<Fobj > 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<FineInner> FineComplexField;
typedef Lattice<FineInner> 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<calcVector> BLAS_B;
deviceVector<calcVector> BLAS_C;
std::vector<deviceVector<calcMatrix> > BLAS_A;
std::vector<deviceVector<ComplexD *> > BLAS_AP;
std::vector<deviceVector<ComplexD *> > BLAS_BP;
deviceVector<ComplexD *> BLAS_CP;
///////////////////////
// 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
<< "MultiGeneralCoarsenedOperatorV2: 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);
}
}
//////////////////////////////////////////////////////////////////////////
// Bilingual accessors, matching GeneralCoarsenedMatrix. Note Grid() is the
// D+1 multiRHS grid here, so a consumer wanting the space the elements live
// on must ask for CoarseGridD().
//////////////////////////////////////////////////////////////////////////
NonLocalStencilGeometry & Geometry(void) { return geom_srhs; };
void ExtractMatrix(int p,CoarseMatrix &A) { BLAStoGrid(A,BLAS_A[p]); };
// 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<CoarseMatrix> & A)
{
GRID_ASSERT(A.size()==geom_srhs.npoint);
GridtoBLAS(A[p],BLAS_A[p]);
}
void GetMatrix (int p,std::vector<CoarseMatrix> & A)
{
GRID_ASSERT(A.size()==geom_srhs.npoint);
BLAStoGrid(A[p],BLAS_A[p]);
}
void CopyMatrix (GeneralCoarseOp &_Op)
{
for(int p=0;p<geom.npoint;p++){
auto Aup = _Op.Cell.Extract(_Op._A[p]);
//Unpadded
GridtoBLAS(Aup,BLAS_A[p]);
}
}
/*
void CheckMatrix (GeneralCoarseOp &_Op)
{
std::cout <<"************* Checking the little direc operator mRHS"<<std::endl;
for(int p=0;p<geom.npoint;p++){
//Unpadded
auto Aup = _Op.Cell.Extract(_Op._A[p]);
auto Ack = Aup;
BLAStoGrid(Ack,BLAS_A[p]);
std::cout << p<<" Ack "<<norm2(Ack)<<std::endl;
std::cout << p<<" Aup "<<norm2(Aup)<<std::endl;
}
std::cout <<"************* "<<std::endl;
}
*/
///////////////////////////////////////////////////////////////////////////
// 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.
///////////////////////////////////////////////////////////////////////////
MultiGeneralCoarsenedOperatorV2(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_A.resize(geom.npoint);
BLAS_AP.resize(geom.npoint);
for(int p=0;p<geom.npoint;p++){
BLAS_A[p].resize (unpadded_sites); // no ghost zone, npoint elements
BLAS_AP[p].resize(unpadded_sites);
}
// Site identity mapping for A
for(int p=0;p<geom.npoint;p++){
for(int ss=0;ss<unpadded_sites;ss++){
ComplexD *ptr = (ComplexD *)&BLAS_A[p][ss];
acceleratorPut(BLAS_AP[p][ss],ptr);
}
}
}
virtual ~MultiGeneralCoarsenedOperatorV2()
{
ReleaseGrid();
}
///////////////////////////////////////////////////////////////////////////
// Free everything SetGrid allocated. The D+1 grid is borrowed from the
// caller and is never deleted here. Safe to call repeatedly and before
// the destructor.
///////////////////////////////////////////////////////////////////////////
void ReleaseGrid(void)
{
if ( CellMulti != nullptr ) { delete CellMulti; CellMulti = nullptr; }
_CoarseGridMulti = nullptr; // borrowed, not owned
_Nrhs = -1;
BLAS_B.resize(0);
BLAS_C.resize(0);
for(int p=0;p<BLAS_BP.size();p++){
BLAS_BP[p].resize(0);
}
BLAS_BP.resize(0);
BLAS_CP.resize(0);
}
///////////////////////////////////////////////////////////////////////////
// Everything that depends on Nrhs. Idempotent; lazy called on demand.
//
// The stencil lives on the D dimensional padded grid. PaddedCell only pads
// a dimension when it is distributed, and the rhs direction never is, so
// the D+1 padded grid is exactly Nrhs copies of the D dimensional padded
// grid with rhs innermost. The neighbour offset therefore carries an Nrhs
// factor, in the same way the Nsimd factor is carried.
///////////////////////////////////////////////////////////////////////////
void SetGrid(GridCartesian *CoarseGridMulti)
{
GRID_ASSERT(CoarseGridMulti != nullptr);
if ( CoarseGridMulti == _CoarseGridMulti ) return; // idempotent on identity
ReleaseGrid();
/////////////////////////////////////////////////
// The D+1 grid is supplied and owned by the caller. Two operators over
// the same coarse space must share one grid object or their fields will
// not conform, so this is never manufactured internally.
/////////////////////////////////////////////////
int nd = _CoarseGrid->_ndimension;
GRID_ASSERT(CoarseGridMulti->_ndimension == nd+1);
GRID_ASSERT(CoarseGridMulti->_processors[0] == 1); // rhs is not distributed
for(int d=0;d<nd;d++){
GRID_ASSERT(CoarseGridMulti->_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);
/////////////////////////////////////////////////
// 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<geom.npoint;p++){
BLAS_BP[p].resize(unpadded_sites);
}
BLAS_CP.resize(unpadded_sites);
// Site identity mapping for C
for(int ss=0;ss<unpadded_sites;ss++){
ComplexD *ptr = (ComplexD *)&BLAS_C[ss*nrhs];
acceleratorPut(BLAS_CP[ss],ptr);
}
// Neighbour table is more complicated
int32_t j=0; // Interior point counter (unpadded)
for(int32_t s=0;s<padded_sites;s++){ // D volume, padded
int ghost_zone=0;
for(int32_t point = 0 ; point < geom.npoint; point++){
int i=s*geom.npoint+point;
if( Stencil._entries[i]._wrap ) { // stencil is indexed by the oSite of the D dim grid
ghost_zone=1; // If general stencil wrapped in any direction, wrap=1
}
}
if( ghost_zone==0) {
for(int32_t point = 0 ; point < geom.npoint; point++){
int i=s*geom.npoint+point;
int32_t nbr = Stencil._entries[i]._offset*CComplex::Nsimd(); // oSite -> lSite, D dim
nbr = nbr*nrhs; // D -> D+1, rhs innermost
GRID_ASSERT(nbr<BLAS_B.size());
ComplexD * ptr = (ComplexD *)&BLAS_B[nbr];
acceleratorPut(BLAS_BP[point][j],ptr); // neighbour indexing in ghost zone volume
}
j++;
}
}
GRID_ASSERT(j==unpadded_sites);
}
template<class vobj> void GridtoBLAS(const Lattice<vobj> &from,deviceVector<typename vobj::scalar_object> &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<nd;i++) nsite *= LocalLatt[i];
////////////////////////////////////////////////////////////////////////////////////////////////
// do the index calc on the GPU
////////////////////////////////////////////////////////////////////////////////////////////////
Coordinate f_ostride = Fg->_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<nd;i++){
from_coor[i] = base[i];
}
int from_oidx = 0; for(int d=0;d<nd;d++) from_oidx+=f_ostride[d]*(from_coor[d]%f_rdimensions[d]);
int from_lane = 0; for(int d=0;d<nd;d++) from_lane+=f_istride[d]*(from_coor[d]/f_rdimensions[d]);
const vector_type* from = (const vector_type *)&from_v[from_oidx];
scalar_type* to = (scalar_type *)&to_v[idx];
scalar_type stmp;
for(int w=0;w<words;w++){
stmp = getlane(from[w], from_lane);
to[w] = stmp;
}
});
}
template<class vobj> void BLAStoGrid(Lattice<vobj> &grid,deviceVector<typename vobj::scalar_object> &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<nd;i++) nsite *= LocalLatt[i];
////////////////////////////////////////////////////////////////////////////////////////////////
// do the index calc on the GPU
////////////////////////////////////////////////////////////////////////////////////////////////
Coordinate t_ostride = Tg->_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<nd;i++){
to_coor[i] = base[i];
}
int to_oidx = 0; for(int d=0;d<nd;d++) to_oidx+=t_ostride[d]*(to_coor[d]%t_rdimensions[d]);
int to_lane = 0; for(int d=0;d<nd;d++) to_lane+=t_istride[d]*(to_coor[d]/t_rdimensions[d]);
vector_type* to = (vector_type *)&to_v[to_oidx];
scalar_type* from = (scalar_type *)&from_v[idx];
scalar_type stmp;
for(int w=0;w<words;w++){
stmp=from[w];
putlane(to[w], stmp, to_lane);
}
});
}
///////////////////////////////////////////////////////////////////////////
// Shared by both CoarsenOperator variants
//
// conj(pha[block]) proj[k (which mom)][j (basis vec cpt)][block]
// = \sum_{l in ball} e^{i q_k . delta_l} < phi_{block,j} | MdagM | phi_{(block+delta_l),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}
///////////////////////////////////////////////////////////////////////////
void CoarsenFourierMatrix(GridBase *CoarseGrid,Eigen::MatrixXcd &invMkl)
{
const int npoint = geom_srhs.npoint;
Coordinate clatt = CoarseGrid->GlobalDimensions();
int Nd = CoarseGrid->Nd();
Eigen::MatrixXcd Mkl = Eigen::MatrixXcd::Zero(npoint,npoint);
ComplexD ci(0.0,1.0);
for(int k=0;k<npoint;k++){ // Loop over momenta
for(int l=0;l<npoint;l++){ // Loop over nbr relative
ComplexD phase(0.0,0.0);
for(int mu=0;mu<Nd;mu++){
RealD TwoPiL = M_PI * 2.0/ clatt[mu];
phase=phase+TwoPiL*geom_srhs.shifts[k][mu]*geom_srhs.shifts[l][mu];
}
phase=exp(phase*ci);
Mkl(k,l) = phase;
}
}
invMkl = Mkl.inverse();
}
///////////////////////////////////////////////////////////////////////////
// blockOrthogonalise and blockZAXPY are block operations on the fine
// vectors, using a coarse shaped field only as an index set. They need a
// grid carrying the fine SIMD layout, which the coarse space no longer
// does. Constructed local to the caller so it cannot be mistaken for the
// coarse grid.
///////////////////////////////////////////////////////////////////////////
void CoarsenBlockGridLayout(GridBase *grid,GridBase *CoarseGrid,
Coordinate &latt,Coordinate &simd,Coordinate &mpi)
{
int nd = CoarseGrid->_ndimension;
latt.resize(nd); simd.resize(nd); mpi.resize(nd);
for(int d=0;d<nd;d++){
latt[d] = CoarseGrid->_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<CoarseGrid->_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<CoarseComplexField> &pha,
std::vector<FineComplexField> &phaF)
{
const int npoint = geom_srhs.npoint;
Coordinate clatt = CoarseGrid->GlobalDimensions();
int Nd = CoarseGrid->Nd();
ComplexD ci(0.0,1.0);
typedef typename CComplex::scalar_type SComplex;
FineComplexField one(grid); one=SComplex(1.0);
FineComplexField zz(grid); zz = Zero();
BlockComplexField pha_blk (BlockGrid);
BlockComplexField blk_coor(BlockGrid);
for(int p=0;p<npoint;p++){ // Loop over momenta in npoint
CoarseComplexField coor(CoarseGrid);
pha[p] =Zero();
pha_blk=Zero();
for(int mu=0;mu<Nd;mu++){
RealD TwoPiL = M_PI * 2.0/ clatt[mu];
LatticeCoordinate(coor,mu);
pha[p] = pha[p] + (TwoPiL * geom_srhs.shifts[p][mu]) * coor;
LatticeCoordinate(blk_coor,mu);
pha_blk = pha_blk + (TwoPiL * geom_srhs.shifts[p][mu]) * blk_coor;
}
pha[p] =exp(pha[p] *ci);
pha_blk=exp(pha_blk*ci);
blockZAXPY(phaF[p],pha_blk,one,zz);
}
}
///////////////////////////////////////////////////////////////////////////
// Remove the bulk phase from the batch of coarse projections and
// accumulate the Fourier inverse into A. Both variants reach here with
// TmpProj in the same batch coarse order, so this is shared verbatim.
///////////////////////////////////////////////////////////////////////////
void CoarsenAccumulate(int p,int i0,int nbv,int batch,
Eigen::MatrixXcd &invMkl,
std::vector<CoarseComplexField> &pha,
CoarseComplexField &phaB,
CoarseVector &TmpProj,
std::vector<CoarseMatrix> &_A,
GridBase *CoarseGrid)
{
typedef typename CComplex::scalar_type SComplex;
const int npoint = geom_srhs.npoint;
for(int b=0;b<batch;b++) InsertSliceFast(pha[p],phaB,b,0);
TmpProj = conjugate(phaB)*TmpProj;
int osites=CoarseGrid->oSites();
for(int k=0;k<npoint;k++){
SComplex sc(invMkl(p,k).real(),invMkl(p,k).imag());
CComplex coef(sc);
autoView( A_v , _A[k], AcceleratorWrite);
autoView( TP_v , TmpProj, AcceleratorRead);
accelerator_for(sss, osites, 1, {
for(int b=0;b<nbv;b++){
for(int j=0;j<nbasis;j++){
A_v[sss](i0+b,j) = A_v[sss](i0+b,j) + coef*TP_v[b+batch*sss](j);
}
}
});
}
}
void CoarsenReport(RealD tphase,RealD tphaseBZ,RealD tslice,
RealD tmat,RealD tproj,RealD tinv)
{
std::cout << GridLogMessage<<"CoarsenOperator phase "<<tphase<<" us"<<std::endl;
std::cout << GridLogMessage<<"CoarsenOperator phaseBZ "<<tphaseBZ<<" us"<<std::endl;
std::cout << GridLogMessage<<"CoarsenOperator slice "<<tslice <<" us"<<std::endl;
std::cout << GridLogMessage<<"CoarsenOperator mat "<<tmat <<" us"<<std::endl;
std::cout << GridLogMessage<<"CoarsenOperator proj "<<tproj<<" us"<<std::endl;
std::cout << GridLogMessage<<"CoarsenOperator inv "<<tinv<<" us"<<std::endl;
}
///////////////////////////////////////////////////////////////////////////
// Coarsen a NATIVELY multiRHS fine operator.
//
// linop acts on the D+1 dimensional fine grid FineGridMulti, with the batch
// of phased basis vectors carried in the rhs direction. A single RHS
// operator can be promoted with MrhsPromotedOperator, but that pays an
// ExtractSlice/InsertSlice pair per rhs; prefer the single RHS variant
// below in that case.
///////////////////////////////////////////////////////////////////////////
void CoarsenOperator(LinearOperatorBase<Lattice<Fobj> > &linop,
GridCartesian *FineGridMulti,
std::vector<FineField> &Subspace,
GridBase *CoarseGrid)
{
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<grid->_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);
MultiRHSBlockProject<Lattice<Fobj> > Projector;
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<FineComplexField> phaF(npoint,grid);
std::vector<CoarseComplexField> pha (npoint,CoarseGrid);
tphase=-usecond();
CoarsenPhases(grid,CoarseGrid,&BlockGrid,pha,phaF);
tphase+=usecond();
std::vector<CoarseMatrix> _A;
_A.resize(npoint,CoarseGrid);
for(int k=0;k<npoint;k++) _A[k] = Zero();
Coordinate cmlatt,cmsimd,cmmpi;
CoarsenBatchGridLayout(CoarseGrid,batch,cmlatt,cmsimd,cmmpi);
GridCartesian CoarseBatchGrid(cmlatt,cmsimd,cmmpi);
CoarseVector TmpProj(&CoarseBatchGrid);
CoarseComplexField phaB(&CoarseBatchGrid);
FineField hi_in (FineGridMulti);
FineField hi_out(FineGridMulti);
FineField zzF(grid); zzF = Zero();
for(int i0=0;i0<nbasis;i0+=batch){ // Loop over batches of basis vectors
int nbv = MIN(batch,nbasis-i0);
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i0<<"/"<<nbasis<< std::endl;
for(int p=0;p<npoint;p++){ // Loop over momenta
// One phase, applied to the whole batch. Tail slices are zeroed so
// the operator never sees undefined data.
for(int b=0;b<nbv;b++){
tphaseBZ-=usecond();
phaV = phaF[p]*Subspace[i0+b];
tphaseBZ+=usecond();
tslice-=usecond();
InsertSliceFast(phaV,hi_in,b,0);
tslice+=usecond();
}
tslice-=usecond();
for(int b=nbv;b<batch;b++){
InsertSliceFast(zzF,hi_in,b,0);
}
tslice+=usecond();
tmat-=usecond();
linop.Op(hi_in,hi_out);
tmat+=usecond();
tproj-=usecond();
Projector.blockProject(hi_out,TmpProj);
tproj+=usecond();
tinv-=usecond();
CoarsenAccumulate(p,i0,nbv,batch,invMkl,pha,phaB,TmpProj,_A,CoarseGrid);
tinv+=usecond();
}
}
for(int p=0;p<npoint;p++){
GridtoBLAS(_A[p],BLAS_A[p]);
}
CoarsenReport(tphase,tphaseBZ,tslice,tmat,tproj,tinv);
}
///////////////////////////////////////////////////////////////////////////
// Coarsen a SINGLE RHS fine operator.
//
// No multiRHS packing: the operator is applied once per phased basis
// vector and the batch is assembled on the coarse side by the mixed
// blockProject, which takes a vector of fine fields and writes the batch
// coarse field the accumulate expects. Only nbv applications per momentum,
// so a batch that does not divide nbasis wastes nothing, and the live fine
// storage is batch fields rather than two D+1 fields of extent batch.
///////////////////////////////////////////////////////////////////////////
void CoarsenOperator(LinearOperatorBase<Lattice<Fobj> > &linop,
std::vector<FineField> &Subspace,
GridBase *CoarseGrid,
int batch)
{
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);
MultiRHSBlockProject<Lattice<Fobj> > Projector;
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<FineComplexField> phaF(npoint,grid);
std::vector<CoarseComplexField> pha (npoint,CoarseGrid);
tphase=-usecond();
CoarsenPhases(grid,CoarseGrid,&BlockGrid,pha,phaF);
tphase+=usecond();
std::vector<CoarseMatrix> _A;
_A.resize(npoint,CoarseGrid);
for(int k=0;k<npoint;k++) _A[k] = Zero();
Coordinate cmlatt,cmsimd,cmmpi;
CoarsenBatchGridLayout(CoarseGrid,batch,cmlatt,cmsimd,cmmpi);
GridCartesian CoarseBatchGrid(cmlatt,cmsimd,cmmpi);
CoarseVector TmpProj(&CoarseBatchGrid);
CoarseComplexField phaB(&CoarseBatchGrid);
std::vector<FineField> MphaV(batch,grid);
for(int i0=0;i0<nbasis;i0+=batch){ // Loop over batches of basis vectors
int nbv = MIN(batch,nbasis-i0);
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i0<<"/"<<nbasis<< std::endl;
for(int p=0;p<npoint;p++){ // Loop over momenta
for(int b=0;b<nbv;b++){
tphaseBZ-=usecond();
phaV = phaF[p]*Subspace[i0+b];
tphaseBZ+=usecond();
tmat-=usecond();
linop.Op(phaV,MphaV[b]);
tmat+=usecond();
}
// The accumulate reads only the first nbv slices, but the projector
// sees the whole vector, so the tail must not be undefined.
for(int b=nbv;b<batch;b++) MphaV[b] = Zero();
tproj-=usecond();
Projector.blockProject(MphaV,TmpProj);
tproj+=usecond();
tinv-=usecond();
CoarsenAccumulate(p,i0,nbv,batch,invMkl,pha,phaB,TmpProj,_A,CoarseGrid);
tinv+=usecond();
}
}
for(int p=0;p<npoint;p++){
GridtoBLAS(_A[p],BLAS_A[p]);
}
CoarsenReport(tphase,tphaseBZ,tslice,tmat,tproj,tinv);
}
void Mdag(const CoarseVector &in, CoarseVector &out)
{
this->M(in,out);
}
void M (const CoarseVector &in, CoarseVector &out)
{
// std::cout << GridLogMessage << "New Mrhs coarse"<<std::endl;
conformable(CoarseGrid(),in.Grid());
conformable(in.Grid(),out.Grid());
out.Checkerboard() = in.Checkerboard();
RealD t_tot;
RealD t_exch;
RealD t_GtoB;
RealD t_BtoG;
RealD t_mult;
CheckGridSet();
if ( in.Grid() != _CoarseGridMulti ) {
std::cout << GridLogError
<< "MultiGeneralCoarsenedOperatorV2::M called with a field on a"
<< std::endl;
std::cout << GridLogError
<< " different grid object from the one given to SetGrid(). Two"
<< std::endl;
std::cout << GridLogError
<< " grids of identical shape do not conform; share one object."
<< std::endl;
GRID_ASSERT(in.Grid() == _CoarseGridMulti);
}
GRID_TRACE("CoarseV2Mult");
t_tot=-usecond();
CoarseVector tin=in;
t_exch=-usecond();
// lambda scope so the roctx range covers exactly the exchange; the
// PaddedCellFwd/BwdMPI markers inside it then nest properly.
CoarseVector pin = [&](){ GRID_TRACE("CoarseV2Exchange");
return CellMulti->ExchangePeriodic(tin); }(); //padded input
t_exch+=usecond();
CoarseVector pout(pin.Grid());
int npoint = geom.npoint;
typedef calcMatrix* Aview;
typedef LatticeView<Cvec> 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("CoarseV2GridToBLAS");
GridtoBLAS(pin,BLAS_B);
}
t_GtoB+=usecond();
GridBLAS BLAS;
t_mult=-usecond();
{ GRID_TRACE("CoarseV2StencilGEMM");
for(int p=0;p<geom.npoint;p++){
RealD c = 1.0;
if (p==0) c = 0.0;
ComplexD beta(c);
BLAS.gemmBatched(nbasis,nrhs,nbasis,
ComplexD(1.0),
BLAS_AP[p],
BLAS_BP[p],
ComplexD(c),
BLAS_CP);
}
BLAS.synchronise();
}
t_mult+=usecond();
t_BtoG=-usecond();
{ GRID_TRACE("CoarseV2BLASToGrid");
BLAStoGrid(out,BLAS_C);
}
t_BtoG+=usecond();
t_tot+=usecond();
/*
std::cout << GridLogMessage << "New Mrhs coarse DONE "<<std::endl;
std::cout << GridLogMessage<<"Coarse Mult exch "<<t_exch<<" us"<<std::endl;
std::cout << GridLogMessage<<"Coarse Mult mult "<<t_mult<<" us"<<std::endl;
std::cout << GridLogMessage<<"Coarse Mult GtoB "<<t_GtoB<<" us"<<std::endl;
std::cout << GridLogMessage<<"Coarse Mult BtoG "<<t_BtoG<<" us"<<std::endl;
std::cout << GridLogMessage<<"Coarse Mult tot "<<t_tot<<" us"<<std::endl;
*/
// std::cout << GridLogMessage<<std::endl;
// std::cout << GridLogMessage<<"Coarse Kernel flops "<< flops<<std::endl;
// std::cout << GridLogMessage<<"Coarse Kernel flop/s "<< flops/t_mult<<" mflop/s"<<std::endl;
// std::cout << GridLogMessage<<"Coarse Kernel bytes/s "<< bytes/t_mult/1000<<" GB/s"<<std::endl;
// std::cout << GridLogMessage<<"Coarse overall flops/s "<< flops/t_tot<<" mflop/s"<<std::endl;
// std::cout << GridLogMessage<<"Coarse total bytes "<< bytes/1e6<<" MB"<<std::endl;
};
virtual void Mdiag (const Field &in, Field &out){ GRID_ASSERT(0);};
virtual void Mdir (const Field &in, Field &out,int dir, int disp){assert(0);};
virtual void MdirAll (const Field &in, std::vector<Field> &out){assert(0);};
};
NAMESPACE_END(Grid);
+3 -3
View File
@@ -67,8 +67,8 @@ public:
}
int point(int dir, int disp) {
assert(disp == -1 || disp == 0 || disp == 1);
assert(base+0 <= dir && dir < base+4);
GRID_ASSERT(disp == -1 || disp == 0 || disp == 1);
GRID_ASSERT(base+0 <= dir && dir < base+4);
// directions faster index = new indexing
// 4d (base = 0):
@@ -131,7 +131,7 @@ public:
return p;
}
}
assert(0);
GRID_ASSERT(0);
return -1;
}
void BuildShifts(void)
+256
View File
@@ -0,0 +1,256 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/multigrid/MrhsMultiGrid.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
#include <Grid/algorithms/deflation/MultiRHSBlockProject.h>
NAMESPACE_BEGIN(Grid);
//////////////////////////////////////////////////////////////////////
// mrhs LinearFunction interface: vector-of-fields in, vector out.
// The outer level carries mrhs as std::vector<Field>; below it mrhs
// is PACKED into a single D+1 field (rhs = dim 0) and the coarse
// classes are plain LinearFunctions on that.
//////////////////////////////////////////////////////////////////////
template<class Field>
class MrhsLinearFunction {
public:
virtual void operator()(std::vector<Field> &in, std::vector<Field> &out) = 0;
};
//////////////////////////////////////////////////////////////////////
// Single-polynomial mrhs PGCR: one step length and one set of
// orthogonalisation coefficients SHARED across the right-hand sides
// (vnorm2/vinnerProduct sum over rhs). For DWF the dense spectrum
// makes sharing near-free and the summed reductions amortise; see the
// mrhs-HDCG paper (arXiv:2409.03904). Orthogonalisation is classical
// Gram-Schmidt, batched per rhs (rankInnerProductMulti/axpyMulti) with
// ONE GlobalSumVector for all coefficients.
//
// OnStep is a programmatic per-step hook for algorithmic studies
// (e.g. driving the GCR coefficient recorder); it is not a consumer
// parameter.
//////////////////////////////////////////////////////////////////////
template<class Field>
class MrhsPGCRNonHermitian {
public:
RealD Tolerance; Integer MaxIterations; int mmax,nstep,steps,level;
int ZeroGuess = 0; int FirstCycle = 0;
std::string name = "Level 1";
LinearOperatorBase<Field> &Linop;
MrhsLinearFunction<Field> &Preconditioner;
std::function<void(int)> OnStep; // called with the outer step count after every step
void Level(int lv){ name = "Level " + std::to_string(lv); level=lv; }
void Name(std::string n){ name = n; }
void SetZeroGuess(int z){ ZeroGuess=z; }
MrhsPGCRNonHermitian(RealD tol,Integer maxit,LinearOperatorBase<Field> &_Linop,MrhsLinearFunction<Field> &Prec,int _mmax,int _nstep)
: Tolerance(tol),MaxIterations(maxit),Linop(_Linop),Preconditioner(Prec),mmax(_mmax),nstep(_nstep){ level=1; }
static RealD vnorm2(std::vector<Field> &x){ RealD s=0; for(auto &f:x) s+=norm2(f); return s; }
static ComplexD vinnerProduct(std::vector<Field> &x,std::vector<Field> &y){ ComplexD s(0); for(int r=0;r<(int)x.size();r++) s+=innerProduct(x[r],y[r]); return s; }
static void vaxpy(std::vector<Field> &z,ComplexD a,std::vector<Field> &x,std::vector<Field> &y){ for(int r=0;r<(int)z.size();r++) axpy(z[r],a,x[r],y[r]); }
void vOp(std::vector<Field> &in,std::vector<Field> &out){ GRID_TRACE("MrhsPGCR::vOp"); for(int r=0;r<(int)in.size();r++) Linop.Op(in[r],out[r]); }
void operator()(std::vector<Field> &src,std::vector<Field> &psi){
RealD cp,ssq,rsq; int nrhs=src.size(); GridBase *grid=src[0].Grid();
ssq=vnorm2(src); rsq=Tolerance*Tolerance*ssq;
std::vector<Field> r(nrhs,grid);
GridStopWatch T; T.Start(); steps=0; FirstCycle=1;
for(int k=0;k<MaxIterations;k++){
cp=GCRnStep(src,psi,rsq);
std::cout<<GridLogMessage<<std::string(level,'\t')<<" "<<name<<" MrhsPGCR("<<mmax<<","<<nstep<<") "<<steps<<" steps cp = "<<cp<<" target "<<rsq<<std::endl;
if(cp<rsq){
T.Stop(); vOp(psi,r); for(int rr=0;rr<nrhs;rr++) axpy(r[rr],-1.0,src[rr],r[rr]);
RealD tr=vnorm2(r);
std::cout<<GridLogMessage<<std::string(level,'\t')<<" "<<name<<" MrhsPGCR: Converged on iteration "<<steps
<<" computed residual "<<std::sqrt(cp/ssq)<<" true residual "<<std::sqrt(tr/ssq)<<" target "<<Tolerance<<std::endl;
std::cout<<GridLogMessage<<std::string(level,'\t')<<" "<<name<<" MrhsPGCR Time elapsed: Total "<<T.Elapsed()<<std::endl;
return;
}
}
std::cout<<GridLogMessage<<"MrhsPGCR: did not converge"<<std::endl;
}
RealD GCRnStep(std::vector<Field> &src,std::vector<Field> &psi,RealD rsq){
RealD cp; ComplexD a,rq; int nrhs=src.size(); GridBase *grid=src[0].Grid();
std::vector<Field> r(nrhs,grid),Az(nrhs,grid); // Az: restart residual scratch only
std::vector< std::vector<Field> > q(mmax,std::vector<Field>(nrhs,grid));
std::vector< std::vector<Field> > p(mmax,std::vector<Field>(nrhs,grid));
std::vector<RealD> qq(mmax);
if (ZeroGuess && FirstCycle) { for(int rr=0;rr<nrhs;rr++){ psi[rr]=Zero(); r[rr]=src[rr]; } }
else { vOp(psi,Az); for(int rr=0;rr<nrhs;rr++) r[rr]=src[rr]-Az[rr]; }
FirstCycle=0;
// p[0]=Prec(r), q[0]=A p[0], produced directly in the history slots (no copies)
Preconditioner(r,p[0]); vOp(p[0],q[0]); qq[0]=vnorm2(q[0]); cp=vnorm2(r);
for(int k=0;k<nstep;k++){
steps++; int kp=k+1, peri_k=k%mmax, peri_kp=kp%mmax;
if ( OnStep ) OnStep(steps);
rq=vinnerProduct(q[peri_k],r); a=rq/qq[peri_k];
vaxpy(psi,a,p[peri_k],psi); vaxpy(r,-a,q[peri_k],r); cp=vnorm2(r);
std::cout<<GridLogMessage<<std::string(level,'\t')<<" "<<name<<" MrhsPGCR step["<<steps<<"] resid "<<cp<<" target "<<rsq<<std::endl;
if((k==nstep-1)||(cp<rsq)) return cp;
// New direction straight into its history slot: p=Prec(r), q=A p.
Preconditioner(r,p[peri_kp]);
vOp(p[peri_kp],q[peri_kp]);
int northog=((kp)>(mmax-1))?(mmax-1):(kp);
{
GRID_TRACE("MrhsPGCR orthog");
// Classical Gram-Schmidt: all coefficients against the UN-updated new q
// (independent, batchable), then apply. Complex coefficient: the
// operator is non-Hermitian, real(<q_j,Aq>) alone left q's non-orthogonal.
// Batched per rhs (one fused kernel + one reduction each), the shared
// coefficient summed over rhs on the host, ONE GlobalSumVector.
std::vector<ComplexD> bcoef(northog,ComplexD(0.0)), part;
for(int rr=0;rr<nrhs;rr++){
std::vector<const Field*> qwin(northog);
for(int back=0;back<northog;back++){ int peri_back=(k-back)%mmax; GRID_ASSERT((k-back)>=0); qwin[back]=&q[peri_back][rr]; }
rankInnerProductMulti(part,qwin,q[peri_kp][rr]);
for(int back=0;back<northog;back++) bcoef[back]+=part[back];
}
if(northog) grid->GlobalSumVector(&bcoef[0],northog);
for(int back=0;back<northog;back++){ int peri_back=(k-back)%mmax; bcoef[back]=-bcoef[back]/qq[peri_back]; }
for(int rr=0;rr<nrhs;rr++){
std::vector<const Field*> qwin(northog), pwin(northog);
for(int back=0;back<northog;back++){ int peri_back=(k-back)%mmax; qwin[back]=&q[peri_back][rr]; pwin[back]=&p[peri_back][rr]; }
axpyMulti(p[peri_kp][rr],bcoef,pwin);
axpyMulti(q[peri_kp][rr],bcoef,qwin);
}
}
qq[peri_kp]=vnorm2(q[peri_kp]);
}
GRID_ASSERT(0); return cp;
}
};
//////////////////////////////////////////////////////////////////////
// Dense L3 solve on the packed D+1 coarse-coarse field. Adapts any
// class exposing ApplyBatch6D (DenseCoarseMatrix) to a LinearFunction.
//////////////////////////////////////////////////////////////////////
template<class DenseType, class CoarseCoarseField>
class MrhsDenseCCSolve : public LinearFunction<CoarseCoarseField> {
public:
DenseType &_Dense;
int _nrhs;
MrhsDenseCCSolve(DenseType &D, int nrhs) : _Dense(D), _nrhs(nrhs) {}
using LinearFunction<CoarseCoarseField>::operator();
virtual void operator()(const CoarseCoarseField &in, CoarseCoarseField &out){
_Dense.ApplyBatch6D(in, out, _nrhs);
}
};
//////////////////////////////////////////////////////////////////////
// L2->L3 half V-cycle on the D+1 coarse field: coarse-coarse correction
// through the mixed blockProject, then post-smooth.
//////////////////////////////////////////////////////////////////////
template<class CoarseField, class CoarseCoarseField>
class MrhsCoarseThreeLevelPrec : public LinearFunction<CoarseField> {
public:
LinearOperatorBase<CoarseField> &_CoarseOp;
LinearFunction<CoarseField> &_CoarseSmoother;
MultiRHSBlockProject<CoarseField> &_Projector;
LinearFunction<CoarseCoarseField> &_CoarseCoarseSolve;
GridBase *_Coarse5d, *_CoarseCoarse5d, *_CoarseCoarseMrhs;
int _nrhs;
MrhsCoarseThreeLevelPrec(LinearOperatorBase<CoarseField> &CoarseOp,
LinearFunction<CoarseField> &CoarseSmoother,
MultiRHSBlockProject<CoarseField> &Projector,
LinearFunction<CoarseCoarseField> &CoarseCoarseSolve,
GridBase *Coarse5d, GridBase *CoarseCoarse5d, GridBase *CoarseCoarseMrhs, int nrhs)
: _CoarseOp(CoarseOp), _CoarseSmoother(CoarseSmoother), _Projector(Projector),
_CoarseCoarseSolve(CoarseCoarseSolve),
_Coarse5d(Coarse5d), _CoarseCoarse5d(CoarseCoarse5d), _CoarseCoarseMrhs(CoarseCoarseMrhs), _nrhs(nrhs) {}
using LinearFunction<CoarseField>::operator();
virtual void operator()(const CoarseField &in, CoarseField &out) {
CoarseField vec1(in.Grid());
CoarseField vec2(in.Grid());
out = in;
_CoarseOp.Op(out,vec1); sub(vec1,in,vec1);
// restrict, through the mixed blockProject: D+1 coarse in, D+1 cc out
CoarseCoarseField CCsrc(_CoarseCoarseMrhs);
CoarseCoarseField CCsol(_CoarseCoarseMrhs);
_Projector.blockProject(vec1,CCsrc);
_CoarseCoarseSolve(CCsrc,CCsol);
_Projector.blockPromote(vec1,CCsol);
add(out,out,vec1);
_CoarseOp.Op(out,vec1); sub(vec1,in,vec1);
_CoarseSmoother(vec1,vec2);
add(out,out,vec2);
}
};
//////////////////////////////////////////////////////////////////////
// L1->L2 mrhs V-cycle. The whole V-cycle is preconditioner: its fine
// residuals and the smoother may run with sloppy halos; the caller
// (the outer Krylov) gets the exact operator back on exit. SetSloppy
// is wired by the composer to PVdagMLinearOperator::SloppyComms (a
// no-op by default), replacing the file-scope global the example used.
//////////////////////////////////////////////////////////////////////
template<class FineField, class MrhsCoarseVector, class FineSmoother>
class MrhsTwoLevelMG : public MrhsLinearFunction<FineField> {
public:
typedef MrhsCoarseVector CoarseVector;
LinearOperatorBase<FineField> &_FineOperator;
FineSmoother &_PostSmoother;
MultiRHSBlockProject<FineField> &_Projector;
LinearFunction<CoarseVector> &_CoarseSolve;
GridBase *_CoarseGrid, *_CoarseGridMrhs;
std::function<void(int)> SetSloppy = [](int){};
int SloppyComms = 0; // value passed to SetSloppy on entry
MrhsTwoLevelMG(LinearOperatorBase<FineField> &FineOp, FineSmoother &Post,
MultiRHSBlockProject<FineField> &Projector, LinearFunction<CoarseVector> &CoarseSolve,
GridBase *CoarseGrid, GridBase *CoarseGridMrhs)
: _FineOperator(FineOp),_PostSmoother(Post),_Projector(Projector),_CoarseSolve(CoarseSolve),
_CoarseGrid(CoarseGrid),_CoarseGridMrhs(CoarseGridMrhs){}
virtual void operator()(std::vector<FineField> &in, std::vector<FineField> &out){
GRID_TRACE("MGVcycle");
SetSloppy(SloppyComms);
int nrhs=in.size(); GridBase *fgrid=in[0].Grid();
std::vector<FineField> vec1(nrhs,fgrid),vec2(nrhs,fgrid);
for(int r=0;r<nrhs;r++) out[r]=in[r];
{ GRID_TRACE("MGFineResidual");
for(int r=0;r<nrhs;r++){ _FineOperator.Op(out[r],vec1[r]); sub(vec1[r],in[r],vec1[r]); }
}
// fine vector -> D+1 coarse, via the mixed blockProject
CoarseVector CsrcMrhs(_CoarseGridMrhs), CsolMrhs(_CoarseGridMrhs);
{ GRID_TRACE("MGProject");
_Projector.blockProject(vec1,CsrcMrhs);
}
CsolMrhs=Zero();
{ GRID_TRACE("MGCoarseSolve");
_CoarseSolve(CsrcMrhs,CsolMrhs);
}
{ GRID_TRACE("MGPromote");
_Projector.blockPromote(vec1,CsolMrhs);
for(int r=0;r<nrhs;r++) add(out[r],out[r],vec1[r]);
}
{ GRID_TRACE("MGFineResidual2");
for(int r=0;r<nrhs;r++){ _FineOperator.Op(out[r],vec1[r]); sub(vec1[r],in[r],vec1[r]); }
}
{ GRID_TRACE("MGPostSmooth");
for(int r=0;r<nrhs;r++){
_PostSmoother(vec1[r],vec2[r]); add(out[r],out[r],vec2[r]);
}
}
SetSloppy(0);
}
};
NAMESPACE_END(Grid);
@@ -0,0 +1,161 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./lib/algorithms/multigrid/MrhsPromotedOperator.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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);
///////////////////////////////////////////////////////////////////////////////
// Present a D dimensional operator as a D+1 dimensional operator with Nrhs in
// dimension 0. Field type is unchanged; only the Grid differs, so this is a
// LinearOperatorBase and callers need no template parameter: a native mrhs
// operator derives from the same base and substitutes without a call site
// change.
//
// Slices in and out around the wrapped operator. No arithmetic beyond the
// wrapped call, but one ExtractSliceFast/InsertSliceFast pair per right hand
// side: data motion, not work.
//
// AdjOp is carried so that A^dag may be coarsened as a separate coarse
// operator when needed, rather than doubling coarse storage.
///////////////////////////////////////////////////////////////////////////////
template<class Field>
class MrhsPromotedOperator : public LinearOperatorBase<Field>
{
private:
LinearOperatorBase<Field> &_LinOp;
GridBase *_LowGrid;
int _Nrhs;
public:
MrhsPromotedOperator(LinearOperatorBase<Field> &LinOp,GridBase *LowGrid,int Nrhs)
: _LinOp(LinOp), _LowGrid(LowGrid), _Nrhs(Nrhs)
{
GRID_ASSERT(_Nrhs>=1);
}
GridBase *LowGrid(void) { return _LowGrid; }
int Nrhs(void) { return _Nrhs; }
// Reset on each call; retrieve and accumulate in the caller
RealD tslice;
RealD top;
void OpDiag (const Field &in, Field &out)
{
SliceLoop(in,out,[&](Field &i,Field &o){ _LinOp.OpDiag(i,o); });
}
void Op (const Field &in, Field &out)
{
SliceLoop(in,out,[&](Field &i,Field &o){ _LinOp.Op(i,o); });
}
void AdjOp (const Field &in, Field &out)
{
SliceLoop(in,out,[&](Field &i,Field &o){ _LinOp.AdjOp(i,o); });
}
void HermOp (const Field &in, Field &out)
{
SliceLoop(in,out,[&](Field &i,Field &o){ _LinOp.HermOp(i,o); });
}
void OpDir (const Field &in, Field &out,int dir,int disp)
{
SliceLoop(in,out,[&](Field &i,Field &o){ _LinOp.OpDir(i,o,dir,disp); });
}
//////////////////////////////////////////////////////////////////
// Norms of the D+1 field are the sums over slices
//////////////////////////////////////////////////////////////////
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2)
{
Conformable(in,out);
Field lo_in (_LowGrid);
Field lo_out(_LowGrid);
n1=0.0;
n2=0.0;
for(int r=0;r<_Nrhs;r++){
RealD r1,r2;
ExtractSliceFast(lo_in,in,r,0);
_LinOp.HermOpAndNorm(lo_in,lo_out,r1,r2);
InsertSliceFast(lo_out,out,r,0);
n1=n1+r1;
n2=n2+r2;
}
}
void OpDirAll(const Field &in, std::vector<Field> &out)
{
int npoint = out.size();
Field lo_in(_LowGrid);
std::vector<Field> lo_out(npoint,_LowGrid);
for(int r=0;r<_Nrhs;r++){
ExtractSliceFast(lo_in,in,r,0);
_LinOp.OpDirAll(lo_in,lo_out);
for(int p=0;p<npoint;p++){
InsertSliceFast(lo_out[p],out[p],r,0);
}
}
}
private:
void Conformable(const Field &in,const Field &out)
{
conformable(in.Grid(),out.Grid());
GRID_ASSERT(in.Grid()->_ndimension == _LowGrid->_ndimension+1);
GRID_ASSERT(in.Grid()->_fdimensions[0] == _Nrhs);
}
template<class Kernel>
void SliceLoop(const Field &in,Field &out,Kernel K)
{
Conformable(in,out);
Field lo_in (_LowGrid);
Field lo_out(_LowGrid);
tslice=0.0;
top=0.0;
for(int r=0;r<_Nrhs;r++){
tslice-=usecond();
ExtractSliceFast(lo_in,in,r,0);
tslice+=usecond();
top-=usecond();
K(lo_in,lo_out);
top+=usecond();
tslice-=usecond();
InsertSliceFast(lo_out,out,r,0);
tslice+=usecond();
}
}
};
NAMESPACE_END(Grid);
+8
View File
@@ -32,3 +32,11 @@ Author: Peter Boyle <pboyle@bnl.gov>
#include <Grid/algorithms/multigrid/CoarsenedMatrix.h>
#include <Grid/algorithms/multigrid/GeneralCoarsenedMatrix.h>
#include <Grid/algorithms/multigrid/GeneralCoarsenedMatrixMultiRHS.h>
#include <Grid/algorithms/multigrid/GeneralCoarsenedMatrixMultiRHSV2.h>
#include <Grid/algorithms/multigrid/MrhsPromotedOperator.h>
#include <Grid/algorithms/multigrid/Smoothers.h>
#include <Grid/algorithms/multigrid/PVdagMMultiGridParams.h>
// PVdagMOperators.h / MrhsMultiGrid.h / PVdagMMultiGrid.h /
// DenseCoarseMatrix.h are NOT in this umbrella: consumers of the PVdagM
// chain include PVdagMMultiGrid.h explicitly (it pulls the dense stack
// and BLAS).
+536
View File
@@ -0,0 +1,536 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/multigrid/PVdagMMultiGrid.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
#include <Grid/algorithms/multigrid/DenseCoarseMatrix.h>
#include <Grid/algorithms/multigrid/PVdagMOperators.h>
#include <Grid/algorithms/multigrid/MrhsMultiGrid.h>
NAMESPACE_BEGIN(Grid);
//////////////////////////////////////////////////////////////////////////////////////
// The three-level mrhs PVdagM multigrid, as objects:
//
// MGCoarseGrids the derived coarse grids (owned here, since
// conformability is pointer identity and someone
// must hold them; declare it BEFORE anything that
// borrows from it)
// PVdagMMultiGridCoarsening ALL the coarsening information for one gauge
// configuration: the raw near-null basis, the
// transfer operators it induces, the Galerkin
// coarse operator at each level, and the dense
// bottom inverse. Everything here is a function
// of the gauge field -- the state HMC must
// rebuild or maintain as U evolves -- and
// everything is solver-independent.
// PVdagMMultiGridSolver the solve chain composed on a borrowed
// coarsening: smoothers, coarse Krylov, V-cycle,
// outer mrhs PGCR.
//
// Scope discipline: grids outlive the coarsening outlives the solver.
//////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Subspace I/O: bare-vector scidac records. Loaded vectors are RAW --
// deliberately NOT re-orthogonalised: CoarsenOperator block
// orthonormalises in place, and projecting a block-orthonormal vector
// onto its own block-orthonormalised aggregation gives e_k with the
// near-null content silently gone. GramGuard below catches that.
//////////////////////////////////////////////////////////////////////
template <class Field>
void saveSubspace(std::vector<Field> &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 <class Field>
void loadSubspace(std::vector<Field> &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
}
//////////////////////////////////////////////////////////////////////
// ||<v|v> - I||_F over a set of coarse vectors. Small means the raw
// near-null content survived the projection.
//////////////////////////////////////////////////////////////////////
template<class CoarseField>
RealD GramDefect(std::vector<CoarseField> &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<class CoarseField>
void GramGuard(const std::string &name,std::vector<CoarseField> &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: ||<"<<name<<"|"<<name<<"> - I||_F = " << defect
<< " (e_k leak would be " << leak << ", trip at " << trip << ")" << std::endl;
GRID_ASSERT( defect < trip );
}
//////////////////////////////////////////////////////////////////////
// The derived grids of the three-level chain, owned in one place.
// The coarse space is UNVECTORISED (sComplex scalar, simd {1,..,1});
// the 5D/6D grids are built directly so the SIMD layout is ours.
// rhs/batch is dim 0 of the 6D grids, undistributed -- no divisibility
// constraint on nrhs.
//////////////////////////////////////////////////////////////////////
class MGCoarseGrids {
public:
GridCartesian *FGrid; // borrowed
int Ls;
int batch;
Coordinate clatt; // 4d coarse lattice
Coordinate cclatt; // 4d coarse-coarse lattice
Coordinate c5simd, c5mpi; // 5D coarse simd/mpi
Coordinate cmsimd, cmmpi; // 6D coarse simd/mpi
// owned:
GridCartesian *Coarse5d;
GridCartesian *CoarseBatch; // 6D at the coarsening batch
GridCartesian *CoarseCoarse5d;
GridCartesian *CoarseCoarseBatch;
MGCoarseGrids(GridCartesian *_FGrid, const MGSetupParams &P)
: FGrid(_FGrid)
{
Coordinate fdims = FGrid->FullDimensions(); // {Ls, x,y,z,t}
Coordinate fmpi = FGrid->_processors;
GRID_ASSERT( fdims.size() == 5 );
Ls = fdims[0];
batch = P.CoarsenBatch;
clatt.resize(4); cclatt.resize(4);
for(int d=0;d<4;d++){
GRID_ASSERT( fdims[d+1] % P.Block[d] == 0 );
clatt[d] = fdims[d+1] / P.Block[d];
}
for(int d=0;d<4;d++){
GRID_ASSERT( clatt[d] % P.Block2[d] == 0 );
cclatt[d] = clatt[d] / P.Block2[d];
}
std::cout << GridLogMessage << "MGCoarseGrids: Block " << P.Block << " coarse lattice " << clatt << std::endl;
std::cout << GridLogMessage << "MGCoarseGrids: Block2 " << P.Block2 << " coarse-coarse lattice " << cclatt << std::endl;
Coordinate c5latt({1,clatt[0],clatt[1],clatt[2],clatt[3]});
c5simd = Coordinate({1,1,1,1,1});
c5mpi = Coordinate({1,fmpi[1],fmpi[2],fmpi[3],fmpi[4]});
Coarse5d = new GridCartesian(c5latt,c5simd,c5mpi);
cmsimd = Coordinate({1,1,1,1,1,1});
cmmpi = Coordinate({1,1,fmpi[1],fmpi[2],fmpi[3],fmpi[4]});
Coordinate cblatt({batch,1,clatt[0],clatt[1],clatt[2],clatt[3]});
CoarseBatch = new GridCartesian(cblatt,cmsimd,cmmpi);
Coordinate cc5latt({1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]});
CoarseCoarse5d = new GridCartesian(cc5latt,c5simd,c5mpi);
Coordinate ccblatt({batch,1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]});
CoarseCoarseBatch = new GridCartesian(ccblatt,cmsimd,cmmpi);
}
~MGCoarseGrids()
{
delete CoarseCoarseBatch;
delete CoarseCoarse5d;
delete CoarseBatch;
delete Coarse5d;
}
};
//////////////////////////////////////////////////////////////////////
// ALL the coarsening information for one gauge configuration.
//
// Owns: the RAW near-null bases (fine and coarse -- retained so the
// coarse operators can be REBUILT on a changed gauge field with a
// fixed basis, the cheap HMC maintenance step; DiscardBasis() frees
// them for valence use), the two coarsened operators, the two block
// projectors, the dense bottom inverse (raw owning pointer: null in
// the constructor, allocated by BuildDenseBottom, deleted here), and
// the transient per-Nrhs solve grids (created by SetNrhs, which owns
// the ReleaseGrid -> delete -> new -> SetGrid ordering in ONE place).
//
// Borrows: the grid bundle. Declare MGCoarseGrids first.
//////////////////////////////////////////////////////////////////////
template<class Fobj,class CComplex,int nbasis>
class PVdagMMultiGridCoarsening {
public:
typedef Lattice<Fobj> FineField;
typedef MultiGeneralCoarsenedOperatorV2<Fobj,CComplex,nbasis> CoarseOperator;
typedef typename CoarseOperator::CoarseVector CoarseVector;
typedef typename CoarseVector::vector_object CoarseSiteObj;
typedef iScalar<CComplex> CComplex2; // coarsening deepens the nest by one iScalar
typedef MultiGeneralCoarsenedOperatorV2<CoarseSiteObj,CComplex2,nbasis> CoarseCoarseOperator;
typedef typename CoarseCoarseOperator::CoarseVector CoarseCoarseVector;
typedef DenseCoarseMatrix<CComplex2,nbasis> DenseBottom;
MGCoarseGrids &Grids; // borrowed
MGSetupParams Params;
NextToNearestStencilGeometry5D geom;
NextToNearestStencilGeometry5D geom2;
CoarseOperator CoarseOpPV;
CoarseCoarseOperator CoarseOpL2;
MultiRHSBlockProject<FineField> MrhsProjector;
MultiRHSBlockProject<CoarseVector> MrhsProjectorL2;
DenseBottom *DenseCC;
std::vector<FineField> rawNull; // RAW fine near-null basis
std::vector<CoarseVector> rawPsi; // RAW coarse near-null basis
GridCartesian *CMrhs; // transient solve grids, owned
GridCartesian *CCMrhs;
int nrhs;
PVdagMMultiGridCoarsening(MGCoarseGrids &_Grids, const MGSetupParams &P)
: Grids(_Grids),
Params(P),
geom (_Grids.Coarse5d),
geom2(_Grids.CoarseCoarse5d),
CoarseOpPV(geom ,_Grids.Coarse5d),
CoarseOpL2(geom2,_Grids.CoarseCoarse5d),
DenseCC(nullptr),
CMrhs(nullptr),
CCMrhs(nullptr),
nrhs(-1)
{};
~PVdagMMultiGridCoarsening()
{
if ( DenseCC ) delete DenseCC;
CoarseOpPV.ReleaseGrid(); // borrowers let go before their grids die
CoarseOpL2.ReleaseGrid();
if ( CMrhs ) delete CMrhs;
if ( CCMrhs ) delete CCMrhs;
}
////////////////////////////////////////////////////////////////////
// The RAW fine basis: load from Params.SubspaceFile if it exists,
// else GCR inverse iteration from noise (and save if a file name
// was given). The Aggregation is scaffolding for CreateSubspaceGCR
// only -- that runs entirely on the fine grid, so the coarse grid
// it holds is never dereferenced.
////////////////////////////////////////////////////////////////////
void GetSubspace(GridParallelRNG &RNG, LinearOperatorBase<FineField> &FineOp)
{
uint64_t file_exists=0;
if ( Params.SubspaceFile.length() ) {
if ( Grids.FGrid->IsBoss() ){ std::ifstream f(Params.SubspaceFile); file_exists=f.good()?1:0; }
Grids.FGrid->GlobalSum(file_exists);
}
rawNull.clear();
rawNull.reserve(nbasis);
for(int k=0;k<nbasis;k++) rawNull.push_back(FineField(Grids.FGrid));
if ( file_exists ){
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: loading subspace "
<< Params.SubspaceFile << " (kept RAW)" << std::endl;
loadSubspace(rawNull, Params.SubspaceFile);
} else {
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: GCR subspace generation" << std::endl;
Aggregation<Fobj,CComplex,nbasis> Agg(Grids.Coarse5d,Grids.FGrid,0);
Agg.CreateSubspaceGCR(RNG,FineOp,nbasis);
for(int k=0;k<nbasis;k++)
rawNull[k]=Agg.subspace[k];
if ( Params.SubspaceFile.length() )
saveSubspace(rawNull, Params.SubspaceFile);
}
}
////////////////////////////////////////////////////////////////////
// Both coarsenings from the retained RAW basis. Calling this again
// on a CHANGED fine operator is the fixed-basis rebuild: for the
// PVdagM coarsening the coarse operator with a fixed basis is exact
// in the changed links. CoarsenOperator block-orthonormalises its
// input in place, so working copies are taken and the RAW vectors
// survive for the next rebuild.
//
// The fine operator must be the PVdagM wrapper (it needs
// SloppyComms: the coarsening builds the PRECONDITIONER, so its
// halos follow the FineSloppyComms policy, restored EXACT on exit).
////////////////////////////////////////////////////////////////////
template<class PVdagMOp>
void Coarsen(PVdagMOp &FineOp)
{
GRID_ASSERT( rawNull.size() == nbasis );
// L1: working copy of the raw fine basis, orthonormalised in place
std::vector<FineField> sub(rawNull);
CoarseOpPV.SetGrid(Grids.CoarseBatch);
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: L1 CoarsenOperator, batch "
<< Grids.batch << std::endl;
FineOp.SloppyComms(Params.FineSloppyComms);
CoarseOpPV.CoarsenOperator(FineOp,sub,Grids.Coarse5d,Grids.batch);
FineOp.SloppyComms(0);
// Transfer operators from the orthonormalised basis; then the
// Galerkin images of the RAW basis define the L2 null space.
MrhsProjector.Allocate(nbasis,Grids.FGrid,Grids.Coarse5d);
MrhsProjector.ImportBasis(sub);
sub.clear(); sub.shrink_to_fit();
std::vector<CoarseVector> psi(nbasis,Grids.Coarse5d);
MrhsProjector.blockProject(rawNull,psi);
GramGuard("psi_coarse",psi,Grids.Coarse5d);
rawPsi.clear();
rawPsi.reserve(nbasis);
for(int k=0;k<nbasis;k++) rawPsi.push_back(psi[k]);
// L2: the V2 L1 operator is natively multiRHS at the batch grid
CoarseOpL2.SetGrid(Grids.CoarseCoarseBatch);
NonHermitianLinearOperator<CoarseOperator,CoarseVector> LinOpCoarse(CoarseOpPV);
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: L2 CoarsenOperator, batch "
<< Grids.batch << std::endl;
CoarseOpL2.CoarsenOperator(LinOpCoarse,Grids.CoarseBatch,psi,Grids.CoarseCoarse5d);
MrhsProjectorL2.Allocate(nbasis,Grids.Coarse5d,Grids.CoarseCoarse5d);
MrhsProjectorL2.ImportBasis(psi); // now block orthonormal
{
std::vector<CoarseCoarseVector> psi_cc(nbasis,Grids.CoarseCoarse5d);
MrhsProjectorL2.blockProject(rawPsi,psi_cc); // RAW vectors in
GramGuard("psi_cc",psi_cc,Grids.CoarseCoarse5d);
}
nrhs = -1; // both operators left at the batch grids
}
////////////////////////////////////////////////////////////////////
// Dense bottom: import the L2 matrix elements, invert (2D
// block-cyclic, fp64), then the check Import cannot run itself --
// the mrhs operator applies only on a D+1 grid, so drive it at
// Nrhs 1 through a slice: ||A Ainv x - x||/||x||.
////////////////////////////////////////////////////////////////////
void BuildDenseBottom(void)
{
if ( DenseCC ) delete DenseCC;
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: L3 dense bottom import" << std::endl;
DenseCC = new DenseBottom(Grids.CoarseCoarse5d);
DenseCC->Import(CoarseOpL2);
Coordinate cc1latt({1,1,Grids.cclatt[0],Grids.cclatt[1],Grids.cclatt[2],Grids.cclatt[3]});
GridCartesian *CoarseCoarseOne = new GridCartesian(cc1latt,Grids.cmsimd,Grids.cmmpi);
CoarseOpL2.SetGrid(CoarseCoarseOne);
CoarseCoarseVector x(Grids.CoarseCoarse5d),y(Grids.CoarseCoarse5d),z(Grids.CoarseCoarse5d);
GridParallelRNG dRNG(Grids.CoarseCoarse5d); dRNG.SeedFixedIntegers(std::vector<int>({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 << "PVdagMMultiGridCoarsening: L3 dense ||A Ainv x - x||/||x|| = "
<< rel << std::endl;
GRID_ASSERT( rel < 1.0e-2 );
CoarseOpL2.ReleaseGrid(); // let go before the grid dies
delete CoarseCoarseOne;
nrhs = -1;
}
////////////////////////////////////////////////////////////////////
// Retarget both operators to a solve Nrhs. The matrix elements are
// Nrhs independent and survive; only the D+1 grids and BLAS buffers
// change. The ordering discipline (borrowers release BEFORE their
// grids are destroyed) lives here and nowhere else.
////////////////////////////////////////////////////////////////////
void SetNrhs(int nr)
{
if ( nr == nrhs ) return;
CoarseOpPV.ReleaseGrid();
CoarseOpL2.ReleaseGrid();
if ( CMrhs ) { delete CMrhs; CMrhs =nullptr; }
if ( CCMrhs ) { delete CCMrhs; CCMrhs=nullptr; }
Coordinate cml ({nr,1,Grids.clatt[0], Grids.clatt[1], Grids.clatt[2], Grids.clatt[3]});
Coordinate ccml({nr,1,Grids.cclatt[0],Grids.cclatt[1],Grids.cclatt[2],Grids.cclatt[3]});
CMrhs = new GridCartesian(cml, Grids.cmsimd,Grids.cmmpi);
CCMrhs = new GridCartesian(ccml,Grids.cmsimd,Grids.cmmpi);
CoarseOpPV.SetGrid(CMrhs);
CoarseOpL2.SetGrid(CCMrhs);
nrhs = nr;
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: operators at Nrhs " << nr << std::endl;
}
////////////////////////////////////////////////////////////////////
// Free the retained RAW bases (valence use: coarsening is final for
// this configuration and the fine basis is ~GB-scale host memory).
////////////////////////////////////////////////////////////////////
void DiscardBasis(void)
{
rawNull.clear(); rawNull.shrink_to_fit();
rawPsi.clear(); rawPsi.shrink_to_fit();
}
};
//////////////////////////////////////////////////////////////////////
// The solve chain on a borrowed coarsening: adaptive PGCR smoothers
// at both levels (the one correct route for this non-Hermitian
// chain), dense bottom, mrhs V-cycle, outer shared-coefficient mrhs
// PGCR. Construction retargets the coarsening to this Nrhs.
//
// Halo policy: the whole V-cycle is preconditioner and runs with
// sloppy halos when FineSloppyComms is set; the outer Krylov is EXACT
// (its applications define what "converged" means), asserted by the
// FINAL true-residual report in Solve.
//////////////////////////////////////////////////////////////////////
template<class Matrix,class Coarsening>
class PVdagMMultiGridSolver {
public:
typedef typename Coarsening::FineField FineField;
typedef typename Coarsening::CoarseOperator CoarseOperator;
typedef typename Coarsening::CoarseVector CoarseVector;
typedef typename Coarsening::CoarseCoarseOperator CoarseCoarseOperator;
typedef typename Coarsening::CoarseCoarseVector CoarseCoarseVector;
typedef typename Coarsening::DenseBottom DenseBottom;
typedef PrecGeneralisedConjugateResidualNonHermitian<FineField> FineSmoother_t;
typedef PrecGeneralisedConjugateResidualNonHermitian<CoarseVector> CoarseKrylov_t;
Coarsening &C;
PVdagMMultiGridParams Params;
int nrhs;
int _regrid; // FIRST member-like init: SetNrhs before members capture grids
PVdagMLinearOperator<Matrix,FineField> PVdagM;
ShiftedPVdagMLinearOperator<Matrix,FineField> ShiftedPVdagM;
TrivialPrecon<FineField> simple_fine;
TrivialPrecon<CoarseVector> simpleC;
NonHermitianLinearOperator<CoarseOperator,CoarseVector> LinOpC;
ShiftedLinearOperator<CoarseVector> ShiftedC;
MrhsDenseCCSolve<DenseBottom,CoarseCoarseVector> ccSolve;
CoarseKrylov_t CoarseSmootherGCR;
MrhsCoarseThreeLevelPrec<CoarseVector,CoarseCoarseVector> L2to3Precon;
CoarseKrylov_t L2PGCR;
FineSmoother_t SmootherGCR;
MrhsTwoLevelMG<FineField,CoarseVector,FineSmoother_t> ThreeLevelPrecon;
MrhsPGCRNonHermitian<FineField> L1PGCR;
PVdagMMultiGridSolver(Matrix &Ddwf, Matrix &Dpv,
Coarsening &_C, const PVdagMMultiGridParams &P, int nr)
: C(_C), Params(P), nrhs(nr),
_regrid((C.SetNrhs(nr),0)), // members below capture C.CMrhs/C.CCMrhs
PVdagM(Ddwf,Dpv),
ShiftedPVdagM(P.FineSmoother.Shift,Ddwf,Dpv),
LinOpC(C.CoarseOpPV),
ShiftedC(P.CoarseSmoother.Shift,LinOpC),
ccSolve(*C.DenseCC,nr),
// Coarse smoother: one preconditioned restart of nstep GCR steps.
CoarseSmootherGCR(0.01,1,ShiftedC,simpleC,P.CoarseSmoother.Mmax,P.CoarseSmoother.Nstep),
L2to3Precon(LinOpC,CoarseSmootherGCR,C.MrhsProjectorL2,ccSolve,
C.Grids.Coarse5d,C.Grids.CoarseCoarse5d,C.CCMrhs,nr),
// Coarse Krylov: Order/16 restarts of 16 steps.
L2PGCR(P.CoarseSolver.Tol,P.CoarseSolver.Order/16,LinOpC,L2to3Precon,P.CoarseSolver.Mmax,16),
// Fine smoother: one restart of nstep GCR steps, tolerance 0 = fixed work.
SmootherGCR(0.0,1,ShiftedPVdagM,simple_fine,P.FineSmoother.Mmax,P.FineSmoother.Nstep),
ThreeLevelPrecon(PVdagM,SmootherGCR,C.MrhsProjector,L2PGCR,C.Grids.Coarse5d,C.CMrhs),
L1PGCR(P.Outer.Tol,P.Outer.MaxIterations,PVdagM,ThreeLevelPrecon,P.Outer.Mmax,P.Outer.Nstep)
{
GRID_ASSERT( C.DenseCC != nullptr );
CoarseSmootherGCR.Level(2);
CoarseSmootherGCR.Name("Csmoother");
CoarseSmootherGCR.SetZeroGuess(1);
L2PGCR.Level(2);
L2PGCR.Name("Couter");
L2PGCR.SetZeroGuess(1);
SmootherGCR.Level(1);
SmootherGCR.Name("Fsmoother");
SmootherGCR.SetZeroGuess(1);
L1PGCR.Level(1);
L1PGCR.Name("Fouter");
L1PGCR.SetZeroGuess(1);
// The V-cycle is preconditioner: sloppy halos inside, exact restored on exit.
ThreeLevelPrecon.SetSloppy = [this](int s){ PVdagM.SloppyComms(s); };
ThreeLevelPrecon.SloppyComms = Params.Setup.FineSloppyComms;
PVdagM.SloppyComms(0);
std::cout << GridLogMessage << "PVdagMMultiGridSolver: Nrhs " << nr
<< ", fine halo policy: preconditioner+coarsening "
<< (Params.Setup.FineSloppyComms ? "SLOPPY (fp32 wire)" : "exact")
<< ", outer Krylov EXACT" << std::endl;
}
void Solve(std::vector<FineField> &src, std::vector<FineField> &sol)
{
GRID_ASSERT( (int)src.size() == nrhs );
// Start the solve from a clean device: evict setup-era Lattice copies
// and release the allocation caches' held blocks, so the LRU cap
// applies to the solve's own working set (OOM policy, Frontier).
MemoryManager::EvictAll();
MemoryManager::DropCache();
GridStopWatch w; w.Start();
L1PGCR(src,sol);
w.Stop();
std::cout << GridLogMessage << "PVdagMMultiGridSolver: Nrhs "<<nrhs<<" total " << w.Elapsed()
<< " (per RHS: " << w.useconds()/1.0e6/nrhs << " s)" << std::endl;
// The outer operator is exact by policy; assert the state rather than
// trust it -- a preconditioner that failed to restore would surface here.
PVdagM.SloppyComms(0);
{
FineField Ax(src[0].Grid());
RealD worst=0.0;
for(int r=0;r<nrhs;r++){
PVdagM.Op(sol[r],Ax);
Ax=Ax-src[r];
RealD rn=std::sqrt(norm2(Ax)/norm2(src[r]));
std::cout << GridLogMessage << "FINAL Nrhs "<<nrhs<<": rhs["<<r<<"] true residual = " << rn << std::endl;
worst=std::max(worst,rn);
}
std::cout << GridLogMessage << "FINAL Nrhs "<<nrhs<<": worst-case residual = " << worst
<< " (exact-halo verification)" << std::endl;
}
}
};
NAMESPACE_END(Grid);
@@ -0,0 +1,155 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/multigrid/PVdagMMultiGridParams.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
NAMESPACE_BEGIN(Grid);
//////////////////////////////////////////////////////////////////////////////////////
// Serialisable parameters for the mrhs PVdagM multigrid solver chain.
//
// Named for the multigrid FORM it configures, as WilsonMGParams is for the
// Wilson MG tests; an MrhsHDCGParams will compose the same generic MG*
// sub-structs for the Hermitian chain. Read from XML (or JSON -- the
// serialisation macros give both) via ReadPVdagMMultiGridParams below and
// printed at startup by the macro's operator<<, so every log identifies its
// own run. The constructor defaults ARE a tuned operating point, not
// arbitrary: an unconfigured run reproduces it, so changing them changes what
// an unconfigured run does. Smoother mmax == nstep (full GCR history).
//
// There are NO environment-variable controls anywhere in this subsystem:
// parameters live here, library-internal constants are hard defaults in their
// classes, verbosity is the --log channels.
// Research instruments (GCR coefficient recording/replay, Chebyshev and
// stationary smoother variants, power iteration) are deliberately NOT part
// of this interface: they remain programmatic, for algorithmic studies,
// not consumer knobs.
//////////////////////////////////////////////////////////////////////////////////////
// The smoother is the adaptive shifted PGCR -- the one correct route for
// this non-Hermitian chain (stationary replay and Chebyshev were explored
// and did not win here; Chebyshev remains the HERMITIAN chain's smoother).
struct MGSmootherParams : Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(MGSmootherParams,
RealD, Shift,
int, Nstep,
int, Mmax);
MGSmootherParams(RealD shift=0.1, int nstep=6, int mmax=6)
: Shift(shift), Nstep(nstep), Mmax(mmax) {};
};
struct MGCoarseSolverParams : Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(MGCoarseSolverParams,
RealD, Tol,
int, Order,
int, Mmax);
MGCoarseSolverParams() : Tol(0.05), Order(200), Mmax(16) {};
};
struct MGOuterParams : Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(MGOuterParams,
RealD, Tol,
int, MaxIterations,
int, Mmax,
int, Nstep);
MGOuterParams() : Tol(1.0e-8), MaxIterations(1000), Mmax(4), Nstep(8) {};
};
struct MGDenseParams : Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(MGDenseParams,
int, LeafSpan); // big-leaf span in blocks; see BlockCyclicSchurInverse
MGDenseParams() : LeafSpan(9) {};
};
struct MGSetupParams : Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(MGSetupParams,
std::vector<int>, Block, // fine -> coarse blocking
std::vector<int>, Block2, // coarse -> coarse-coarse blocking
int, CoarsenBatch,
std::string, SubspaceFile, // scidac; empty = create from noise, no I/O
int, FineSloppyComms);// fp32 wire INSIDE the preconditioner only
MGSetupParams()
: Block({2,2,3,3}), Block2({4,4,2,4}), CoarsenBatch(9),
SubspaceFile(""), FineSloppyComms(1) {};
};
struct PVdagMMultiGridParams : Serializable {
GRID_SERIALIZABLE_CLASS_MEMBERS(PVdagMMultiGridParams,
MGSetupParams, Setup,
MGSmootherParams, FineSmoother,
MGSmootherParams, CoarseSmoother,
MGCoarseSolverParams, CoarseSolver,
MGOuterParams, Outer,
MGDenseParams, Dense);
PVdagMMultiGridParams()
: FineSmoother (0.1, 6, 6),
CoarseSmoother(0.1, 2, 2) {};
};
inline void CheckValidity(const PVdagMMultiGridParams &P)
{
GRID_ASSERT( P.Setup.Block.size() == 4 );
GRID_ASSERT( P.Setup.Block2.size() == 4 );
GRID_ASSERT( P.Setup.CoarsenBatch >= 1 );
GRID_ASSERT( P.FineSmoother.Nstep > 0 );
GRID_ASSERT( P.CoarseSmoother.Nstep > 0 );
GRID_ASSERT( P.CoarseSolver.Tol > 0.0 );
GRID_ASSERT( P.Outer.Tol > 0.0 );
GRID_ASSERT( P.Dense.LeafSpan >= 1 );
}
//////////////////////////////////////////////////////////////////////////////////////
// Read from an XML file; on a MISSING file the boss writes <file>.templ from
// the defaults and returns false so the caller can Grid_finalize() and exit
// -- the produced template is the documentation of every parameter. An
// empty filename means "defaults": no I/O, returns true. Always prints the
// effective parameters, so the log describes the run.
//
// Drivers fetch the filename themselves, e.g.
// std::string pfile("");
// if( GridCmdOptionExists(argv,argv+argc,"--multigrid-params") )
// pfile = GridCmdOptionPayload(argv,argv+argc,"--multigrid-params");
// if( !ReadPVdagMMultiGridParams(Params, pfile) ) { Grid_finalize(); return 0; }
//////////////////////////////////////////////////////////////////////////////////////
inline bool ReadPVdagMMultiGridParams(PVdagMMultiGridParams &P, const std::string &xmlfile)
{
if ( xmlfile.length() ) {
bool good;
{ std::ifstream f(xmlfile); good = f.good(); }
if ( !good ) {
std::cout << GridLogMessage << "PVdagMMultiGridParams: " << xmlfile << " does not exist" << std::endl;
if ( GlobalSharedMemory::WorldRank == 0 ) {
XmlWriter WR(xmlfile+".templ");
write(WR, "PVdagMMultiGridParams", P);
std::cout << GridLogMessage << "PVdagMMultiGridParams: template written to "
<< xmlfile << ".templ" << std::endl;
}
return false;
}
XmlReader RD(xmlfile);
read(RD, "PVdagMMultiGridParams", P);
}
CheckValidity(P);
std::cout << GridLogMessage << "PVdagMMultiGridParams ("
<< (xmlfile.length() ? xmlfile : std::string("defaults")) << "):" << std::endl;
std::cout << P << std::endl;
return true;
}
NAMESPACE_END(Grid);
@@ -0,0 +1,88 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/multigrid/PVdagMOperators.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
NAMESPACE_BEGIN(Grid);
//////////////////////////////////////////////////////////////////////
// A = PV^dag M (non-Hermitian). The operator the PVdagM multigrid
// coarsens and solves.
//
// SloppyComms delegates to BOTH underlying fermion operators, so one
// call controls every fine halo in the chain -- including any shifted
// wrapper built on the same two matrices. Halo-precision POLICY:
// reduced-precision (fp32 wire) halos belong in the PRECONDITIONER --
// the smoother, the V-cycle's own residuals, the coarsening -- and
// NEVER in the outer Krylov, whose applications define what
// "converged" means. The operators default to EXACT; the V-cycle
// turns sloppiness on for its own scope only (MrhsTwoLevelMG).
//////////////////////////////////////////////////////////////////////
template<class Matrix,class Field>
class PVdagMLinearOperator : public LinearOperatorBase<Field> {
Matrix &_Mat; Matrix &_PV;
public:
PVdagMLinearOperator(Matrix &Mat,Matrix &PV): _Mat(Mat),_PV(PV) {};
void SloppyComms(int sloppy) { _Mat.SloppyComms(sloppy); _PV.SloppyComms(sloppy); }
void OpDiag (const Field &in, Field &out) { GRID_ASSERT(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { GRID_ASSERT(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ GRID_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); }
};
//////////////////////////////////////////////////////////////////////
// A + shift, built directly on the two matrices (fine level: avoids
// an extra fine-field pass through a generic shifted wrapper).
//////////////////////////////////////////////////////////////////////
template<class Matrix,class Field>
class ShiftedPVdagMLinearOperator : public LinearOperatorBase<Field> {
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) { GRID_ASSERT(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { GRID_ASSERT(0); }
void OpDirAll (const Field &in, std::vector<Field> &out){ GRID_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(in,tmp); _Mat.Mdag(tmp,out); out = out + shift*in; }
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){ GRID_ASSERT(0); }
void HermOp(const Field &in, Field &out){ Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); }
};
//////////////////////////////////////////////////////////////////////
// Op + shift for any operator (coarse levels).
//////////////////////////////////////////////////////////////////////
template<class Field>
class ShiftedLinearOperator : public LinearOperatorBase<Field> {
LinearOperatorBase<Field> &_Op; RealD shift;
public:
ShiftedLinearOperator(RealD _shift, LinearOperatorBase<Field> &Op) : _Op(Op), shift(_shift) {}
void OpDiag (const Field &in, Field &out) { GRID_ASSERT(0); }
void OpDir (const Field &in, Field &out,int dir,int disp) { GRID_ASSERT(0); }
void OpDirAll (const Field &in, std::vector<Field> &out) { GRID_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){ GRID_ASSERT(0); }
void HermOp (const Field &in, Field &out) { Field tmp(in.Grid()); Op(in,tmp); AdjOp(tmp,out); }
};
NAMESPACE_END(Grid);
+292
View File
@@ -0,0 +1,292 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/algorithms/multigrid/Smoothers.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
#include <Grid/algorithms/iterative/GCRCoefficients.h>
NAMESPACE_BEGIN(Grid);
//////////////////////////////////////////////////////////////////////////////
// Multigrid smoothers as LinearFunction<Field> objects.
//
// Gathered here from ~15 copies in tests/solver and tests/debug (HDCR /
// HDCG era), plus the fixed-polynomial smoothers of 2026:
//
// ChebyshevSmoother Chebyshev approx to 1/x on [lo,hi], applied
// through HermOp (the original HDCG smoother on
// a Hermitian shifted operator).
// ChebyshevNonHermitianSmoother same polynomial applied through Op(), for
// a non-Hermitian (near-normal, real-spectrum)
// smoother operator such as shifted PVdagM.
// ChebyshevInverter one Chebyshev-corrected step with residual print.
// MirsSmoother shifted-MdagM CG, HDCG arXiv:1402.2585.
// GCRReplaySmoother replays a GCR's recorded step lengths a_k and
// orthogonalisation coefficients b_kj with NO
// inner products: one matvec per step, zero
// reductions. The "PreconditionerMirsPoly"
// idea of 1402.2585 p.13 applied to GCR.
//
// Recording: PrecGeneralisedConjugateResidualNonHermitian::SetCoefficientRecorder
// accumulates per-step means over calls into a GCRCoefficients; construct a
// GCRReplaySmoother from it.
//////////////////////////////////////////////////////////////////////////////
inline RealD InverseApproximation(RealD x){ return 1.0/x; }
//////////////////////////////////////////////////////////////////////////////
// HermOp-based Chebyshev smoother. The second template parameter and the
// 5-argument constructor exist only so the historical call sites
// ChebyshevSmoother<LatticeFermion,DomainWallFermionD> S(lo,hi,ord,HermOp,Ddwf);
// compile unchanged; the Matrix argument was never used.
//////////////////////////////////////////////////////////////////////////////
template<class Field,class Matrix=void> class ChebyshevSmoother : public LinearFunction<Field>
{
public:
using LinearFunction<Field>::operator();
typedef LinearOperatorBase<Field> FineOperator;
FineOperator & _SmootherOperator;
Chebyshev<Field> Cheby;
ChebyshevSmoother(RealD _lo,RealD _hi,int _ord, FineOperator &SmootherOperator) :
_SmootherOperator(SmootherOperator),
Cheby(_lo,_hi,_ord,InverseApproximation)
{
std::cout << GridLogMessage<<" Chebyshev smoother order "<<_ord<<" ["<<_lo<<","<<_hi<<"]"<<std::endl;
};
template<class M>
ChebyshevSmoother(RealD _lo,RealD _hi,int _ord, FineOperator &SmootherOperator, M &) :
ChebyshevSmoother(_lo,_hi,_ord,SmootherOperator) {};
void operator() (const Field &in, Field &out)
{
Cheby(_SmootherOperator,in,out);
}
};
//////////////////////////////////////////////////////////////////////////////
// Op()-based Chebyshev smoother: x = S(A) r with S the Chebyshev fit to 1/x
// on [lo,hi]. Same three-term recurrence as Chebyshev<Field>::operator()
// but through Op, for the non-Hermitian smoother operators of the PVdagM
// multigrid (real coefficients / near-normal, as the recorded GCR
// coefficients show). `order` matvecs, no reductions.
//////////////////////////////////////////////////////////////////////////////
template<class Field> class ChebyshevNonHermitianSmoother : public LinearFunction<Field>
{
public:
using LinearFunction<Field>::operator();
LinearOperatorBase<Field> &Linop;
RealD lo, hi; int order;
std::vector<RealD> Coeffs;
int Verbose = 0;
std::string name = "cheb";
ChebyshevNonHermitianSmoother(RealD _lo,RealD _hi,int _order,LinearOperatorBase<Field> &Op)
: Linop(Op), lo(_lo), hi(_hi), order(_order)
{
GRID_ASSERT(order>=2);
Coeffs.resize(order);
for(int j=0;j<order;j++){
RealD s=0;
for(int k=0;k<order;k++){
RealD y=std::cos(M_PI*(k+0.5)/order);
RealD x=0.5*(y*(hi-lo)+(hi+lo));
s=s+InverseApproximation(x)*std::cos( j*M_PI*(k+0.5)/order );
}
Coeffs[j] = s * 2.0/order;
}
std::cout << GridLogMessage<<" ChebyshevNonHermitian smoother order "<<order<<" ["<<lo<<","<<hi<<"]"<<std::endl;
}
void operator() (const Field &in, Field &out)
{
GRID_TRACE("ChebyshevSmoother");
GridBase *grid=in.Grid();
Field T0(grid); T0 = in;
Field T1(grid), T2(grid), y(grid);
Field *Tnm=&T0, *Tn=&T1, *Tnp=&T2;
RealD xscale = 2.0/(hi-lo);
RealD mscale = -(hi+lo)/(hi-lo);
Linop.Op(T0,y);
axpby(T1,xscale,mscale,y,in);
axpby(out,0.5*Coeffs[0],Coeffs[1],T0,T1);
for(int n=2;n<order;n++){
Linop.Op(*Tn,y);
axpby(y,xscale,mscale,y,(*Tn));
axpby(*Tnp,2.0,-1.0,y,(*Tnm));
if ( Coeffs[n] != 0.0 ) axpy(out,Coeffs[n],*Tnp,out);
Field *swizzle=Tnm; Tnm=Tn; Tn=Tnp; Tnp=swizzle;
}
if ( Verbose ) { Linop.Op(out,y); y = y - in; std::cout << GridLogMessage << " " << name << " cheb |r|/|r0| = " << std::sqrt(norm2(y)/norm2(in)) << std::endl; }
}
};
template<class Field> class ChebyshevInverter : public LinearFunction<Field>
{
public:
using LinearFunction<Field>::operator();
typedef LinearOperatorBase<Field> FineOperator;
FineOperator & _Operator;
Chebyshev<Field> Cheby;
ChebyshevInverter(RealD _lo,RealD _hi,int _ord, FineOperator &Operator) :
_Operator(Operator),
Cheby(_lo,_hi,_ord,InverseApproximation)
{
std::cout << GridLogMessage<<" Chebyshev Inverter order "<<_ord<<" ["<<_lo<<","<<_hi<<"]"<<std::endl;
};
void operator() (const Field &in, Field &out)
{
Field r(in.Grid());
Field AinvR(in.Grid());
_Operator.HermOp(out,r);
r = in - r; // b - A x
Cheby(_Operator,r,AinvR); // A^{-1} ( b - A x ) ~ A^{-1} b - x
out = out + AinvR;
_Operator.HermOp(out,r);
r = in - r; // b - A x
RealD rr = norm2(r);
RealD ss = norm2(in);
std::cout << GridLogMessage << "ChebshevInverse resid " <<::sqrt(rr/ss)<<std::endl;
}
};
//////////////////////////////////////////////////////////////////////////////
// MIRS: CG on the infra-red shifted MdagM (HDCG, arXiv:1402.2585).
//////////////////////////////////////////////////////////////////////////////
template<class Field,class Matrix> class MirsSmoother : public LinearFunction<Field>
{
public:
using LinearFunction<Field>::operator();
typedef LinearOperatorBase<Field> FineOperator;
Matrix & SmootherMatrix;
FineOperator & SmootherOperator;
RealD tol;
RealD shift;
int maxit;
MirsSmoother(RealD _shift,RealD _tol,int _maxit,FineOperator &_SmootherOperator,Matrix &_SmootherMatrix) :
shift(_shift),tol(_tol),maxit(_maxit),
SmootherOperator(_SmootherOperator),
SmootherMatrix(_SmootherMatrix)
{};
void operator() (const Field &in, Field &out)
{
ZeroGuesser<Field> Guess;
ConjugateGradient<Field> CG(tol,maxit,false);
Field src(in.Grid());
ShiftedMdagMLinearOperator<SparseMatrixBase<Field>,Field> MdagMOp(SmootherMatrix,shift);
SmootherOperator.AdjOp(in,src);
Guess(src,out);
CG(MdagMOp,src,out);
}
};
//////////////////////////////////////////////////////////////////////////////
// Replay of a recorded GCR with a trivial preconditioner:
// p_0 = r_0 ; x_{k+1} = x_k + a_k p_k ; r_{k+1} = r_k - a_k A p_k ;
// p_{k+1} = r_{k+1} + sum_{j<northog(k)} b_kj p_{k-j}
// One matvec per step, no reductions, history of mmax p vectors held
// persistently (allocated on first use, per grid).
//////////////////////////////////////////////////////////////////////////////
template<class Field> class GCRReplaySmoother : public LinearFunction<Field>
{
public:
using LinearFunction<Field>::operator();
LinearOperatorBase<Field> &Linop;
int mmax, nstep;
std::vector<ComplexD> a;
std::vector<std::vector<ComplexD> > b;
GridBase *hist_grid = nullptr;
std::vector<Field> p;
int Verbose = 0; // 1: print |r_m|/|r_0| per call (one extra reduction)
std::string name = "replay";
GCRReplaySmoother(LinearOperatorBase<Field> &Op, const GCRCoefficients &c) : Linop(Op)
{
mmax = c.mmax; GRID_ASSERT(mmax>=1);
nstep = c.Steps(); GRID_ASSERT(nstep>=1);
a.resize(nstep); b.resize(nstep);
// No std::abs / std::isfinite on ComplexD: it is thrust::complex on HIP
// builds and neither overload exists. Work on the real and imaginary
// parts explicitly.
auto finite = [](ComplexD z){ RealD x=real(z), y=imag(z); return (x==x) && (y==y) && (x-x==0.0) && (y-y==0.0); };
auto cabs = [](ComplexD z){ RealD x=real(z), y=imag(z); return std::sqrt(x*x+y*y); };
RealD amax=0.0, bmax=0.0;
for(int k=0;k<nstep;k++){
a[k] = c.A(k);
GRID_ASSERT( finite(a[k]) );
amax = std::max(amax, cabs(a[k]));
b[k].resize(c.NB(k));
GRID_ASSERT( c.NB(k) <= mmax-1 );
for(int j=0;j<c.NB(k);j++){
b[k][j] = c.B(k,j);
GRID_ASSERT( finite(b[k][j]) );
bmax = std::max(bmax, cabs(b[k][j]));
}
}
std::cout << GridLogMessage << " GCRReplaySmoother: max|a| " << amax << " max|b| " << bmax << std::endl;
std::cout << GridLogMessage << " GCRReplaySmoother: " << nstep << " steps, mmax " << mmax
<< ", from " << c.Calls() << " recorded calls" << std::endl;
}
void operator() (const Field &src, Field &psi)
{
GRID_TRACE("GCRReplaySmoother");
GridBase *grid = src.Grid();
if ( hist_grid != grid ) {
p.clear(); p.reserve(mmax);
for(int i=0;i<mmax;i++) p.emplace_back(grid);
hist_grid = grid;
}
Field r(grid), q(grid);
r = src;
psi = Zero();
p[0] = r;
RealD r0 = Verbose ? norm2(src) : 0.0;
for(int k=0;k<nstep;k++){
int kp=k+1, peri_k=k%mmax, peri_kp=kp%mmax;
Linop.Op(p[peri_k],q); // q_k = A p_k
axpy(psi, a[k], p[peri_k], psi);
if ( k==nstep-1 ) {
if ( Verbose ) { axpy(r,-a[k],q,r); std::cout << GridLogMessage << " " << name << " replay |r|/|r0| = " << std::sqrt(norm2(r)/r0) << std::endl; }
break;
}
axpy(r, -a[k], q, r);
p[peri_kp] = r;
for(int j=0;j<(int)b[k].size();j++){
int peri_back=(k-j)%mmax;
axpy(p[peri_kp], b[k][j], p[peri_back], p[peri_kp]);
}
}
}
};
//////////////////////////////////////////////////////////////////////////////
// A LinearFunction that forwards to a replaceable target: lets a V-cycle be
// built once around a smoother slot whose implementation is swapped at run
// time (record with the adaptive GCR, then replay the polynomial).
//////////////////////////////////////////////////////////////////////////////
template<class Field> class SwitchableSmoother : public LinearFunction<Field>
{
public:
using LinearFunction<Field>::operator();
LinearFunction<Field> *current;
std::string label;
SwitchableSmoother(LinearFunction<Field> &initial, const std::string &l="initial") : current(&initial), label(l) {}
void Set(LinearFunction<Field> &f, const std::string &l)
{
current = &f; label = l;
std::cout << GridLogMessage << " SwitchableSmoother -> " << l << std::endl;
}
void operator() (const Field &in, Field &out) { (*current)(in,out); }
};
NAMESPACE_END(Grid);
+7 -5
View File
@@ -57,7 +57,7 @@ public:
if ( (_Tp*)ptr == (_Tp *) NULL ) {
printf("Grid CPU Allocator got NULL for %lu bytes\n",(unsigned long) bytes );
}
assert( ( (_Tp*)ptr != (_Tp *)NULL ) );
GRID_ASSERT( ( (_Tp*)ptr != (_Tp *)NULL ) );
return ptr;
}
@@ -69,7 +69,7 @@ public:
}
// FIXME: hack for the copy constructor: it must be avoided to avoid single thread loop
void construct(pointer __p, const _Tp& __val) { assert(0);};
void construct(pointer __p, const _Tp& __val) { };
void construct(pointer __p) { };
void destroy(pointer __p) { };
};
@@ -106,7 +106,7 @@ public:
if ( (_Tp*)ptr == (_Tp *) NULL ) {
printf("Grid Shared Allocator got NULL for %lu bytes\n",(unsigned long) bytes );
}
assert( ( (_Tp*)ptr != (_Tp *)NULL ) );
GRID_ASSERT( ( (_Tp*)ptr != (_Tp *)NULL ) );
return ptr;
}
@@ -154,7 +154,7 @@ public:
if ( (_Tp*)ptr == (_Tp *) NULL ) {
printf("Grid Device Allocator got NULL for %lu bytes\n",(unsigned long) bytes );
}
assert( ( (_Tp*)ptr != (_Tp *)NULL ) );
GRID_ASSERT( ( (_Tp*)ptr != (_Tp *)NULL ) );
return ptr;
}
@@ -175,10 +175,11 @@ template<typename _Tp> inline bool operator!=(const devAllocator<_Tp>&, const d
// Template typedefs
////////////////////////////////////////////////////////////////////////////////
template<class T> using hostVector = std::vector<T,alignedAllocator<T> >; // Needs autoview
template<class T> using Vector = std::vector<T,uvmAllocator<T> >; //
template<class T> using Vector = std::vector<T,uvmAllocator<T> >; // Really want to deprecate
template<class T> using uvmVector = std::vector<T,uvmAllocator<T> >; // auto migrating page
template<class T> using deviceVector = std::vector<T,devAllocator<T> >; // device vector
/*
template<class T> class vecView
{
protected:
@@ -214,6 +215,7 @@ template<class T> vecView<T> VectorView(Vector<T> &vec,ViewMode _mode)
#define autoVecView(v_v,v,mode) \
auto v_v = VectorView(v,mode); \
ViewCloser<decltype(v_v)> _autoView##v_v(v_v);
*/
NAMESPACE_END(Grid);
+86 -14
View File
@@ -63,12 +63,10 @@ void MemoryManager::PrintBytes(void)
std::cout << " MemoryManager : "<<(total_device>>20)<<" accelerator Mbytes "<<std::endl;
std::cout << " MemoryManager : "<<(total_host>>20) <<" cpu Mbytes "<<std::endl;
uint64_t cacheBytes;
cacheBytes = CacheBytes[Cpu];
std::cout << " MemoryManager : "<<(cacheBytes>>20) <<" cpu cache Mbytes "<<std::endl;
cacheBytes = CacheBytes[Acc];
std::cout << " MemoryManager : "<<(cacheBytes>>20) <<" acc cache Mbytes "<<std::endl;
cacheBytes = CacheBytes[Shared];
std::cout << " MemoryManager : "<<(cacheBytes>>20) <<" shared cache Mbytes "<<std::endl;
cacheBytes = HostCacheBytes();
std::cout << " MemoryManager : "<<(cacheBytes>>20) <<" cpu alloc cache Mbytes "<<std::endl;
cacheBytes = DeviceCacheBytes();
std::cout << " MemoryManager : "<<(cacheBytes>>20) <<" acc alloc cache Mbytes "<<std::endl;
#ifdef GRID_CUDA
cuda_mem();
@@ -86,6 +84,31 @@ MemoryManager::AllocationCacheEntry MemoryManager::Entries[MemoryManager::Nalloc
int MemoryManager::Victim[MemoryManager::NallocType];
int MemoryManager::Ncache[MemoryManager::NallocType] = { 2, 0, 8, 8, 0, 16, 8, 0, 16 };
uint64_t MemoryManager::CacheBytes[MemoryManager::NallocType];
uint64_t MemoryManager::DeviceAllocCalls;
uint64_t MemoryManager::DeviceFreeCalls;
uint64_t MemoryManager::DeviceAllocBytes;
uint64_t MemoryManager::DeviceFreeBytes;
uint64_t MemoryManager::DeviceCacheHits;
void MemoryManager::PrintAllocCounts(void)
{
std::cout << GridLogMemory << "MemoryManager: device allocator calls: acceleratorAllocDevice "
<< DeviceAllocCalls <<" ("<< DeviceAllocBytes <<" bytes), acceleratorFreeDevice "
<< DeviceFreeCalls <<" ("<< DeviceFreeBytes <<" bytes), served from ring cache "
<< DeviceCacheHits << std::endl;
std::cout << GridLogMemory << "MemoryManager: view traffic: HostToDevice "
<< HostToDeviceXfer <<" transfers ("<< HostToDeviceBytes <<" bytes), DeviceToHost "
<< DeviceToHostXfer <<" transfers ("<< DeviceToHostBytes <<" bytes), evictions "
<< DeviceEvictions << std::endl;
}
void MemoryManager::Snapshot(const std::string &where)
{
if ( !GridLogMemory.isActive() ) return;
std::cout << GridLogMemory << "---------------- memory snapshot: "<< where <<" ----------------"<<std::endl;
PrintAllocCounts();
PrintBytes();
acceleratorMem();
}
//////////////////////////////////////////////////////////////////////
// Actual allocation and deallocation utils
//////////////////////////////////////////////////////////////////////
@@ -95,6 +118,11 @@ void *MemoryManager::AcceleratorAllocate(size_t bytes)
void *ptr = (void *) Lookup(bytes,Acc);
if ( ptr == (void *) NULL ) {
ptr = (void *) acceleratorAllocDevice(bytes);
DeviceAllocCalls++; DeviceAllocBytes+=bytes;
std::cout << GridLogMemory << "MemoryManager: acceleratorAllocDevice size "<< bytes
<<" AccPtr "<< std::hex << (uint64_t)ptr << std::dec << std::endl;
} else {
DeviceCacheHits++;
}
#ifdef GRID_MM_VERBOSE
std::cout <<"AcceleratorAllocate "<<std::endl;
@@ -105,9 +133,13 @@ void *MemoryManager::AcceleratorAllocate(size_t bytes)
void MemoryManager::AcceleratorFree (void *ptr,size_t bytes)
{
total_device-=bytes;
void *__freeme = Insert(ptr,bytes,Acc);
size_t freed_bytes=0;
void *__freeme = Insert(ptr,bytes,Acc,&freed_bytes);
if ( __freeme ) {
acceleratorFreeDevice(__freeme);
DeviceFreeCalls++; DeviceFreeBytes+=freed_bytes;
std::cout << GridLogMemory << "MemoryManager: acceleratorFreeDevice size "<< freed_bytes
<<" AccPtr "<< std::hex << (uint64_t)__freeme << std::dec << std::endl;
}
#ifdef GRID_MM_VERBOSE
std::cout <<"AcceleratorFree "<<std::endl;
@@ -274,8 +306,7 @@ void MemoryManager::InitMessage(void) {
#endif
}
void *MemoryManager::Insert(void *ptr,size_t bytes,int type)
void *MemoryManager::Insert(void *ptr,size_t bytes,int type,size_t *freed)
{
#ifdef ALLOCATION_CACHE
int cache;
@@ -283,19 +314,59 @@ void *MemoryManager::Insert(void *ptr,size_t bytes,int type)
else if (bytes >= GRID_ALLOC_HUGE_LIMIT) cache = type + 1;
else cache = type;
return Insert(ptr,bytes,Entries[cache],Ncache[cache],Victim[cache],CacheBytes[cache]);
return Insert(ptr,bytes,Entries[cache],Ncache[cache],Victim[cache],CacheBytes[cache],freed);
#else
return ptr;
#endif
}
void MemoryManager::DropCache(void)
{
// Release every block held in the allocation caches (the "recent
// allocations" pools: Small/Large/Huge x Cpu/Acc/Shared). Blocks in these
// pools are freed from the caller's point of view but still occupy memory;
// after a large setup phase (SUMMA scratch, coarsening temporaries) they can
// hold gigabytes of device memory that hipMalloc then cannot get. Each pool
// is freed with the call that pairs with its allocator.
for(int sz=0;sz<3;sz++) {
int t;
t = Acc+sz;
for(int e=0;e<Ncache[t];e++) {
if( Entries[t][e].valid ) {
acceleratorFreeDevice(Entries[t][e].address);
Entries[t][e].valid = 0; Entries[t][e].bytes = 0; Entries[t][e].address = NULL;
}
}
CacheBytes[t]=0; Victim[t]=0;
t = Shared+sz;
for(int e=0;e<Ncache[t];e++) {
if( Entries[t][e].valid ) {
acceleratorFreeShared(Entries[t][e].address);
Entries[t][e].valid = 0; Entries[t][e].bytes = 0; Entries[t][e].address = NULL;
}
}
CacheBytes[t]=0; Victim[t]=0;
t = Cpu+sz;
for(int e=0;e<Ncache[t];e++) {
if( Entries[t][e].valid ) {
#ifdef GRID_UVM
acceleratorFreeShared(Entries[t][e].address);
#else
acceleratorFreeCpu(Entries[t][e].address);
#endif
Entries[t][e].valid = 0; Entries[t][e].bytes = 0; Entries[t][e].address = NULL;
}
}
CacheBytes[t]=0; Victim[t]=0;
}
}
void *MemoryManager::Insert(void *ptr,size_t bytes,AllocationCacheEntry *entries,int ncache,int &victim, uint64_t &cacheBytes)
void *MemoryManager::Insert(void *ptr,size_t bytes,AllocationCacheEntry *entries,int ncache,int &victim, uint64_t &cacheBytes,size_t *freed)
{
#ifdef GRID_OMP
assert(omp_in_parallel()==0);
GRID_ASSERT(omp_in_parallel()==0);
#endif
if (ncache == 0) return ptr;
if (ncache == 0) { if(freed) *freed = bytes; return ptr; } // uncached: the incoming block is the one freed
void * ret = NULL;
int v = -1;
@@ -314,6 +385,7 @@ void *MemoryManager::Insert(void *ptr,size_t bytes,AllocationCacheEntry *entries
if ( entries[v].valid ) {
ret = entries[v].address;
if ( freed ) *freed = entries[v].bytes; // the DISPLACED block is what actually gets freed
cacheBytes -= entries[v].bytes;
entries[v].valid = 0;
entries[v].address = NULL;
@@ -345,7 +417,7 @@ void *MemoryManager::Lookup(size_t bytes,int type)
void *MemoryManager::Lookup(size_t bytes,AllocationCacheEntry *entries,int ncache,uint64_t & cacheBytes)
{
#ifdef GRID_OMP
assert(omp_in_parallel()==0);
GRID_ASSERT(omp_in_parallel()==0);
#endif
for(int e=0;e<ncache;e++){
if ( entries[e].valid && ( entries[e].bytes == bytes ) ) {
+18 -4
View File
@@ -108,12 +108,12 @@ private:
/////////////////////////////////////////////////
// Free pool
/////////////////////////////////////////////////
static void *Insert(void *ptr,size_t bytes,int type) ;
static void *Insert(void *ptr,size_t bytes,int type,size_t *freed=nullptr) ;
static void *Lookup(size_t bytes,int type) ;
static void *Insert(void *ptr,size_t bytes,AllocationCacheEntry *entries,int ncache,int &victim,uint64_t &cbytes) ;
static void *Insert(void *ptr,size_t bytes,AllocationCacheEntry *entries,int ncache,int &victim,uint64_t &cbytes,size_t *freed=nullptr) ;
static void *Lookup(size_t bytes,AllocationCacheEntry *entries,int ncache,uint64_t &cbytes) ;
public:
public:
static void PrintBytes(void);
static void Audit(std::string s);
static void Init(void);
@@ -137,7 +137,19 @@ private:
static uint64_t DeviceToHostXfer;
static uint64_t DeviceEvictions;
static uint64_t DeviceDestroy;
// Calls that actually reach the runtime, as distinct from the Evict/Clone traffic above:
// a free only reaches acceleratorFreeDevice when displaced from the allocation ring cache,
// and an allocate only reaches acceleratorAllocDevice on a ring miss.
static uint64_t DeviceAllocCalls;
static uint64_t DeviceFreeCalls;
static uint64_t DeviceAllocBytes;
static uint64_t DeviceFreeBytes;
static uint64_t DeviceCacheHits;
static void PrintAllocCounts(void);
// Labelled snapshot: allocator counts + footprint + device free/total. Silent unless
// --log Memory is on, so it can be left in hot code.
static void Snapshot(const std::string &where);
static uint64_t DeviceCacheBytes();
static uint64_t HostCacheBytes();
@@ -215,6 +227,8 @@ private:
static void NotifyDeletion(void * CpuPtr);
static void Print(void);
static void PrintAll(void);
static void EvictAll(void);
static void DropCache(void);
static void PrintState( void* CpuPtr);
static int isOpen (void* CpuPtr);
static void ViewClose(void* CpuPtr,ViewMode mode);
+131 -80
View File
@@ -9,6 +9,7 @@ static char print_buffer [ MAXLINE ];
#define mprintf(...) snprintf (print_buffer,MAXLINE, __VA_ARGS__ ); std::cout << GridLogMemory << print_buffer << std::endl;
#define dprintf(...) snprintf (print_buffer,MAXLINE, __VA_ARGS__ ); std::cout << GridLogDebug << print_buffer << std::endl;
//#define dprintf(...)
//#define mprintf(...)
////////////////////////////////////////////////////////////
// For caching copies of data on device
@@ -49,12 +50,12 @@ int MemoryManager::EntryPresent(uint64_t CpuPtr)
{
if(AccViewTable.empty()) return 0;
auto count = AccViewTable.count(CpuPtr); assert((count==0)||(count==1));
auto count = AccViewTable.count(CpuPtr); GRID_ASSERT((count==0)||(count==1));
return count;
}
void MemoryManager::EntryCreate(uint64_t CpuPtr,size_t bytes,ViewMode mode,ViewAdvise hint)
{
assert(!EntryPresent(CpuPtr));
GRID_ASSERT(!EntryPresent(CpuPtr));
AcceleratorViewEntry AccCache;
AccCache.CpuPtr = CpuPtr;
AccCache.AccPtr = (uint64_t)NULL;
@@ -68,9 +69,9 @@ void MemoryManager::EntryCreate(uint64_t CpuPtr,size_t bytes,ViewMode mode,View
}
MemoryManager::AccViewTableIterator MemoryManager::EntryLookup(uint64_t CpuPtr)
{
assert(EntryPresent(CpuPtr));
GRID_ASSERT(EntryPresent(CpuPtr));
auto AccCacheIterator = AccViewTable.find(CpuPtr);
assert(AccCacheIterator!=AccViewTable.end());
GRID_ASSERT(AccCacheIterator!=AccViewTable.end());
return AccCacheIterator;
}
void MemoryManager::EntryErase(uint64_t CpuPtr)
@@ -78,9 +79,28 @@ void MemoryManager::EntryErase(uint64_t CpuPtr)
auto AccCache = EntryLookup(CpuPtr);
AccViewTable.erase(CpuPtr);
}
/////////////////////////////////////////////////////////////////////////////////
// LRU membership invariant:
//
// LRU_valid == 1 <=> AccPtr != NULL && accLock == 0 && cpuLock == 0
//
// i.e. the LRU queue contains exactly the device-resident, completely unlocked
// entries -- the evictable set. Membership is maintained EAGERLY at the lock
// 0<->1 edges, O(1) via the stored LRU_entry iterator:
//
// AcceleratorViewOpen lock 0->1 : LRUremove (gated on LRU_valid)
// AcceleratorViewClose accLock->0: LRUinsert (AccPtr necessarily exists)
// CpuViewOpen lock 0->1 : LRUremove (gated on LRU_valid)
// CpuViewClose cpuLock->0: LRUinsert (iff AccPtr exists)
// Evict/AccDiscard : LRUremove (frees the device copy)
//
// Consequences: victims taken from LRU.back() are evictable by construction;
// Evict() on a locked entry is an invariant violation (asserted), and the
// eviction loops (EvictVictims/EvictAll) cannot spin.
/////////////////////////////////////////////////////////////////////////////////
void MemoryManager::LRUinsert(AcceleratorViewEntry &AccCache)
{
assert(AccCache.LRU_valid==0);
GRID_ASSERT(AccCache.LRU_valid==0);
if (AccCache.transient) {
LRU.push_back(AccCache.CpuPtr);
AccCache.LRU_entry = --LRU.end();
@@ -93,7 +113,7 @@ void MemoryManager::LRUinsert(AcceleratorViewEntry &AccCache)
}
void MemoryManager::LRUremove(AcceleratorViewEntry &AccCache)
{
assert(AccCache.LRU_valid==1);
GRID_ASSERT(AccCache.LRU_valid==1);
LRU.erase(AccCache.LRU_entry);
AccCache.LRU_valid = 0;
DeviceLRUBytes-=AccCache.bytes;
@@ -107,19 +127,19 @@ void MemoryManager::AccDiscard(AcceleratorViewEntry &AccCache)
// Remove from Accelerator, remove entry, without flush
// Cannot be locked. If allocated Must be in LRU pool.
///////////////////////////////////////////////////////////
assert(AccCache.state!=Empty);
GRID_ASSERT(AccCache.state!=Empty);
dprintf("MemoryManager: Discard(%lx) %lx\n",(uint64_t)AccCache.CpuPtr,(uint64_t)AccCache.AccPtr);
assert(AccCache.accLock==0);
assert(AccCache.cpuLock==0);
assert(AccCache.CpuPtr!=(uint64_t)NULL);
dprintf("MemoryManager: Discard(%lx) %lx",(uint64_t)AccCache.CpuPtr,(uint64_t)AccCache.AccPtr);
GRID_ASSERT(AccCache.accLock==0);
GRID_ASSERT(AccCache.cpuLock==0);
GRID_ASSERT(AccCache.CpuPtr!=(uint64_t)NULL);
if(AccCache.AccPtr) {
AcceleratorFree((void *)AccCache.AccPtr,AccCache.bytes);
DeviceDestroy++;
DeviceBytes -=AccCache.bytes;
LRUremove(AccCache);
AccCache.AccPtr=(uint64_t) NULL;
dprintf("MemoryManager: Free(%lx) LRU %ld Total %ld\n",(uint64_t)AccCache.AccPtr,DeviceLRUBytes,DeviceBytes);
dprintf("MemoryManager: Free(%lx) LRU %ld Total %ld",(uint64_t)AccCache.AccPtr,DeviceLRUBytes,DeviceBytes);
}
uint64_t CpuPtr = AccCache.CpuPtr;
EntryErase(CpuPtr);
@@ -129,21 +149,21 @@ void MemoryManager::Evict(AcceleratorViewEntry &AccCache)
{
///////////////////////////////////////////////////////////////////////////
// Make CPU consistent, remove from Accelerator, remove from LRU, LEAVE CPU only entry
// Cannot be acclocked. If allocated must be in LRU pool.
// Cannot be locked. If allocated must be in LRU pool.
//
// Nov 2022... Felix issue: Allocating two CpuPtrs, can have an entry in LRU-q with CPUlock.
// and require to evict the AccPtr copy. Eviction was a mistake in CpuViewOpen
// but there is a weakness where CpuLock entries are attempted for erase
// Take these OUT LRU queue when CPU locked?
// Cannot take out the table as cpuLock data is important.
// (Historical: a Nov 2022 incident (two CpuPtrs; eviction called from
// CpuViewOpen -- since excised) could present a cpuLocked entry here, and
// silent-return guards were added. The LRU membership invariant (see
// LRUinsert) now excludes ALL locked entries from the queue eagerly at the
// lock edges, so a locked victim is an invariant violation: asserted.)
///////////////////////////////////////////////////////////////////////////
assert(AccCache.state!=Empty);
GRID_ASSERT(AccCache.state!=Empty);
mprintf("MemoryManager: Evict CpuPtr %lx AccPtr %lx cpuLock %ld accLock %ld\n",
mprintf("MemoryManager: Evict CpuPtr %lx AccPtr %lx cpuLock %ld accLock %ld",
(uint64_t)AccCache.CpuPtr,(uint64_t)AccCache.AccPtr,
(uint64_t)AccCache.cpuLock,(uint64_t)AccCache.accLock);
if (AccCache.accLock!=0) return;
if (AccCache.cpuLock!=0) return;
GRID_ASSERT(AccCache.accLock==0);
GRID_ASSERT(AccCache.cpuLock==0);
if(AccCache.state==AccDirty) {
Flush(AccCache);
}
@@ -153,7 +173,7 @@ void MemoryManager::Evict(AcceleratorViewEntry &AccCache)
AccCache.AccPtr=(uint64_t)NULL;
AccCache.state=CpuDirty; // CPU primary now
DeviceBytes -=AccCache.bytes;
dprintf("MemoryManager: Free(AccPtr %lx) footprint now %ld \n",(uint64_t)AccCache.AccPtr,DeviceBytes);
dprintf("MemoryManager: Free(AccPtr %lx) footprint now %ld ",(uint64_t)AccCache.AccPtr,DeviceBytes);
}
// uint64_t CpuPtr = AccCache.CpuPtr;
DeviceEvictions++;
@@ -161,28 +181,28 @@ void MemoryManager::Evict(AcceleratorViewEntry &AccCache)
}
void MemoryManager::Flush(AcceleratorViewEntry &AccCache)
{
assert(AccCache.state==AccDirty);
assert(AccCache.cpuLock==0);
assert(AccCache.accLock==0);
assert(AccCache.AccPtr!=(uint64_t)NULL);
assert(AccCache.CpuPtr!=(uint64_t)NULL);
GRID_ASSERT(AccCache.state==AccDirty);
GRID_ASSERT(AccCache.cpuLock==0);
GRID_ASSERT(AccCache.accLock==0);
GRID_ASSERT(AccCache.AccPtr!=(uint64_t)NULL);
GRID_ASSERT(AccCache.CpuPtr!=(uint64_t)NULL);
acceleratorCopyFromDevice((void *)AccCache.AccPtr,(void *)AccCache.CpuPtr,AccCache.bytes);
mprintf("MemoryManager: acceleratorCopyFromDevice Flush size %ld AccPtr %lx -> CpuPtr %lx\n",(uint64_t)AccCache.bytes,(uint64_t)AccCache.AccPtr,(uint64_t)AccCache.CpuPtr); fflush(stdout);
mprintf("MemoryManager: acceleratorCopyFromDevice Flush size %ld AccPtr %lx -> CpuPtr %lx",(uint64_t)AccCache.bytes,(uint64_t)AccCache.AccPtr,(uint64_t)AccCache.CpuPtr); fflush(stdout);
DeviceToHostBytes+=AccCache.bytes;
DeviceToHostXfer++;
AccCache.state=Consistent;
}
void MemoryManager::Clone(AcceleratorViewEntry &AccCache)
{
assert(AccCache.state==CpuDirty);
assert(AccCache.cpuLock==0);
assert(AccCache.accLock==0);
assert(AccCache.CpuPtr!=(uint64_t)NULL);
GRID_ASSERT(AccCache.state==CpuDirty);
GRID_ASSERT(AccCache.cpuLock==0);
GRID_ASSERT(AccCache.accLock==0);
GRID_ASSERT(AccCache.CpuPtr!=(uint64_t)NULL);
if(AccCache.AccPtr==(uint64_t)NULL){
AccCache.AccPtr=(uint64_t)AcceleratorAllocate(AccCache.bytes);
DeviceBytes+=AccCache.bytes;
}
mprintf("MemoryManager: acceleratorCopyToDevice Clone size %ld AccPtr %lx <- CpuPtr %lx\n",
mprintf("MemoryManager: acceleratorCopyToDevice Clone size %ld AccPtr %lx <- CpuPtr %lx",
(uint64_t)AccCache.bytes,
(uint64_t)AccCache.AccPtr,(uint64_t)AccCache.CpuPtr); fflush(stdout);
acceleratorCopyToDevice((void *)AccCache.CpuPtr,(void *)AccCache.AccPtr,AccCache.bytes);
@@ -193,10 +213,10 @@ void MemoryManager::Clone(AcceleratorViewEntry &AccCache)
void MemoryManager::CpuDiscard(AcceleratorViewEntry &AccCache)
{
assert(AccCache.state!=Empty);
assert(AccCache.cpuLock==0);
assert(AccCache.accLock==0);
assert(AccCache.CpuPtr!=(uint64_t)NULL);
GRID_ASSERT(AccCache.state!=Empty);
GRID_ASSERT(AccCache.cpuLock==0);
GRID_ASSERT(AccCache.accLock==0);
GRID_ASSERT(AccCache.CpuPtr!=(uint64_t)NULL);
if(AccCache.AccPtr==(uint64_t)NULL){
AccCache.AccPtr=(uint64_t)AcceleratorAllocate(AccCache.bytes);
DeviceBytes+=AccCache.bytes;
@@ -210,33 +230,49 @@ void MemoryManager::CpuDiscard(AcceleratorViewEntry &AccCache)
void MemoryManager::ViewClose(void* Ptr,ViewMode mode)
{
if( (mode==AcceleratorRead)||(mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard) ){
dprintf("AcceleratorViewClose %lx\n",(uint64_t)Ptr);
dprintf("AcceleratorViewClose %lx",(uint64_t)Ptr);
AcceleratorViewClose((uint64_t)Ptr);
} else if( (mode==CpuRead)||(mode==CpuWrite)){
CpuViewClose((uint64_t)Ptr);
} else {
assert(0);
GRID_ASSERT(0);
}
}
void *MemoryManager::ViewOpen(void* _CpuPtr,size_t bytes,ViewMode mode,ViewAdvise hint)
{
uint64_t CpuPtr = (uint64_t)_CpuPtr;
if( (mode==AcceleratorRead)||(mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard) ){
dprintf("AcceleratorViewOpen %lx\n",(uint64_t)CpuPtr);
dprintf("AcceleratorViewOpen %lx",(uint64_t)CpuPtr);
return (void *) AcceleratorViewOpen(CpuPtr,bytes,mode,hint);
} else if( (mode==CpuRead)||(mode==CpuWrite)){
return (void *)CpuViewOpen(CpuPtr,bytes,mode,hint);
} else {
assert(0);
GRID_ASSERT(0);
return NULL;
}
}
void MemoryManager::EvictVictims(uint64_t bytes)
{
assert(bytes<DeviceMaxBytes);
if(bytes>=DeviceMaxBytes) {
printf("EvictVictims bytes %ld DeviceMaxBytes %ld\n",bytes,DeviceMaxBytes);
}
GRID_ASSERT(bytes<DeviceMaxBytes);
while(bytes+DeviceLRUBytes > DeviceMaxBytes){
if ( DeviceLRUBytes > 0){
assert(LRU.size()>0);
GRID_ASSERT(LRU.size()>0);
uint64_t victim = LRU.back(); // From the LRU
auto AccCacheIterator = EntryLookup(victim);
auto & AccCache = AccCacheIterator->second;
Evict(AccCache);
} else {
return;
}
}
}
void MemoryManager::EvictAll(void)
{
while(LRU.size()>0){
if ( DeviceLRUBytes > 0){
uint64_t victim = LRU.back(); // From the LRU
auto AccCacheIterator = EntryLookup(victim);
auto & AccCache = AccCacheIterator->second;
@@ -260,19 +296,19 @@ uint64_t MemoryManager::AcceleratorViewOpen(uint64_t CpuPtr,size_t bytes,ViewMod
if (!AccCache.AccPtr) {
EvictVictims(bytes);
}
assert((mode==AcceleratorRead)||(mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard));
GRID_ASSERT((mode==AcceleratorRead)||(mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard));
assert(AccCache.cpuLock==0); // Programming error
GRID_ASSERT(AccCache.cpuLock==0); // Programming error
if(AccCache.state!=Empty) {
dprintf("ViewOpen found entry %lx %lx : sizes %ld %ld accLock %ld\n",
dprintf("ViewOpen found entry %lx %lx : sizes %ld %ld accLock %ld",
(uint64_t)AccCache.CpuPtr,
(uint64_t)CpuPtr,
(uint64_t)AccCache.bytes,
(uint64_t)bytes,
(uint64_t)AccCache.accLock);
assert(AccCache.CpuPtr == CpuPtr);
assert(AccCache.bytes ==bytes);
GRID_ASSERT(AccCache.CpuPtr == CpuPtr);
GRID_ASSERT(AccCache.bytes ==bytes);
}
/*
* State transitions and actions
@@ -289,7 +325,7 @@ uint64_t MemoryManager::AcceleratorViewOpen(uint64_t CpuPtr,size_t bytes,ViewMod
* AccWrite AccDirty AccDirty - -
*/
if(AccCache.state==Empty) {
assert(AccCache.LRU_valid==0);
GRID_ASSERT(AccCache.LRU_valid==0);
AccCache.CpuPtr = CpuPtr;
AccCache.AccPtr = (uint64_t)NULL;
AccCache.bytes = bytes;
@@ -305,7 +341,7 @@ uint64_t MemoryManager::AcceleratorViewOpen(uint64_t CpuPtr,size_t bytes,ViewMod
AccCache.state = Consistent; // Empty + AccRead => Consistent
}
AccCache.accLock= 1;
dprintf("Copied Empty entry into device accLock= %d\n",AccCache.accLock);
dprintf("Copied Empty entry into device accLock= %d",AccCache.accLock);
} else if(AccCache.state==CpuDirty ){
if(mode==AcceleratorWriteDiscard) {
CpuDiscard(AccCache);
@@ -318,30 +354,30 @@ uint64_t MemoryManager::AcceleratorViewOpen(uint64_t CpuPtr,size_t bytes,ViewMod
AccCache.state = Consistent; // CpuDirty + AccRead => Consistent
}
AccCache.accLock++;
dprintf("CpuDirty entry into device ++accLock= %d\n",AccCache.accLock);
dprintf("CpuDirty entry into device ++accLock= %d",AccCache.accLock);
} else if(AccCache.state==Consistent) {
if((mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard))
AccCache.state = AccDirty; // Consistent + AcceleratorWrite=> AccDirty
else
AccCache.state = Consistent; // Consistent + AccRead => Consistent
AccCache.accLock++;
dprintf("Consistent entry into device ++accLock= %d\n",AccCache.accLock);
dprintf("Consistent entry into device ++accLock= %d",AccCache.accLock);
} else if(AccCache.state==AccDirty) {
if((mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard))
AccCache.state = AccDirty; // AccDirty + AcceleratorWrite=> AccDirty
else
AccCache.state = AccDirty; // AccDirty + AccRead => AccDirty
AccCache.accLock++;
dprintf("AccDirty entry ++accLock= %d\n",AccCache.accLock);
dprintf("AccDirty entry ++accLock= %d",AccCache.accLock);
} else {
assert(0);
GRID_ASSERT(0);
}
assert(AccCache.accLock>0);
GRID_ASSERT(AccCache.accLock>0);
// If view is opened on device must remove from LRU
if(AccCache.LRU_valid==1){
// must possibly remove from LRU as now locked on GPU
dprintf("AccCache entry removed from LRU \n");
dprintf("AccCache entry removed from LRU ");
LRUremove(AccCache);
}
@@ -358,16 +394,16 @@ void MemoryManager::AcceleratorViewClose(uint64_t CpuPtr)
auto AccCacheIterator = EntryLookup(CpuPtr);
auto & AccCache = AccCacheIterator->second;
assert(AccCache.cpuLock==0);
assert(AccCache.accLock>0);
GRID_ASSERT(AccCache.cpuLock==0);
GRID_ASSERT(AccCache.accLock>0);
AccCache.accLock--;
// Move to LRU queue if not locked and close on device
if(AccCache.accLock==0) {
dprintf("AccleratorViewClose %lx AccLock decremented to %ld move to LRU queue\n",(uint64_t)CpuPtr,(uint64_t)AccCache.accLock);
dprintf("AccleratorViewClose %lx AccLock decremented to %ld move to LRU queue",(uint64_t)CpuPtr,(uint64_t)AccCache.accLock);
LRUinsert(AccCache);
} else {
dprintf("AccleratorViewClose %lx AccLock decremented to %ld\n",(uint64_t)CpuPtr,(uint64_t)AccCache.accLock);
dprintf("AccleratorViewClose %lx AccLock decremented to %ld",(uint64_t)CpuPtr,(uint64_t)AccCache.accLock);
}
}
void MemoryManager::CpuViewClose(uint64_t CpuPtr)
@@ -375,10 +411,17 @@ void MemoryManager::CpuViewClose(uint64_t CpuPtr)
auto AccCacheIterator = EntryLookup(CpuPtr);
auto & AccCache = AccCacheIterator->second;
assert(AccCache.cpuLock>0);
assert(AccCache.accLock==0);
GRID_ASSERT(AccCache.cpuLock>0);
GRID_ASSERT(AccCache.accLock==0);
AccCache.cpuLock--;
// Return to LRU queue when fully unlocked -- mirrors AcceleratorViewClose.
// Asymmetry vs the Acc side: a device copy need not exist for a host view;
// only device-resident entries belong in the (evictable) LRU queue.
if( (AccCache.cpuLock==0) && (AccCache.AccPtr!=(uint64_t)NULL) ) {
dprintf("CpuViewClose %lx cpuLock decremented to zero, move to LRU queue",(uint64_t)CpuPtr);
LRUinsert(AccCache);
}
}
/*
* Action State StateNext Flush Clone
@@ -409,12 +452,12 @@ uint64_t MemoryManager::CpuViewOpen(uint64_t CpuPtr,size_t bytes,ViewMode mode,V
// EvictVictims(bytes);
// }
assert((mode==CpuRead)||(mode==CpuWrite));
assert(AccCache.accLock==0); // Programming error
GRID_ASSERT((mode==CpuRead)||(mode==CpuWrite));
GRID_ASSERT(AccCache.accLock==0); // Programming error
if(AccCache.state!=Empty) {
assert(AccCache.CpuPtr == CpuPtr);
assert(AccCache.bytes==bytes);
GRID_ASSERT(AccCache.CpuPtr == CpuPtr);
GRID_ASSERT(AccCache.bytes==bytes);
}
if(AccCache.state==Empty) {
@@ -429,20 +472,28 @@ uint64_t MemoryManager::CpuViewOpen(uint64_t CpuPtr,size_t bytes,ViewMode mode,V
AccCache.state = CpuDirty; // CpuDirty +CpuRead/CpuWrite => CpuDirty
AccCache.cpuLock++;
} else if(AccCache.state==Consistent) {
assert(AccCache.AccPtr != (uint64_t)NULL);
GRID_ASSERT(AccCache.AccPtr != (uint64_t)NULL);
if(mode==CpuWrite)
AccCache.state = CpuDirty; // Consistent +CpuWrite => CpuDirty
else
AccCache.state = Consistent; // Consistent +CpuRead => Consistent
AccCache.cpuLock++;
} else if(AccCache.state==AccDirty) {
assert(AccCache.AccPtr != (uint64_t)NULL);
GRID_ASSERT(AccCache.AccPtr != (uint64_t)NULL);
Flush(AccCache);
if(mode==CpuWrite) AccCache.state = CpuDirty; // AccDirty +CpuWrite => CpuDirty, Flush
else AccCache.state = Consistent; // AccDirty +CpuRead => Consistent, Flush
AccCache.cpuLock++;
} else {
assert(0); // should be unreachable
GRID_ASSERT(0); // should be unreachable
}
GRID_ASSERT(AccCache.cpuLock>0);
// If view is opened on host must remove from LRU -- mirrors AcceleratorViewOpen.
// LRU_valid==1 here implies this is the 0->1 lock edge of a device-resident entry.
if(AccCache.LRU_valid==1){
dprintf("CpuViewOpen: entry removed from LRU ");
LRUremove(AccCache);
}
AccCache.transient= transient? EvictNext : 0;
@@ -524,12 +575,12 @@ void MemoryManager::Audit(std::string s)
std::cout << " Memory Manager::Audit() from "<<s<<std::endl;
for(auto it=LRU.begin();it!=LRU.end();it++){
uint64_t cpuPtr = *it;
assert(EntryPresent(cpuPtr));
GRID_ASSERT(EntryPresent(cpuPtr));
auto AccCacheIterator = EntryLookup(cpuPtr);
auto & AccCache = AccCacheIterator->second;
LruBytes2+=AccCache.bytes;
assert(AccCache.LRU_valid==1);
assert(AccCache.LRU_entry==it);
GRID_ASSERT(AccCache.LRU_valid==1);
GRID_ASSERT(AccCache.LRU_entry==it);
}
std::cout << " Memory Manager::Audit() LRU queue matches table entries "<<std::endl;
@@ -548,7 +599,7 @@ void MemoryManager::Audit(std::string s)
if( AccCache.LRU_valid ) LruCnt++;
if ( AccCache.cpuLock || AccCache.accLock ) {
assert(AccCache.LRU_valid==0);
GRID_ASSERT(AccCache.LRU_valid==0);
std::cout << GridLogError << s<< "\n\t 0x"<<std::hex<<AccCache.CpuPtr<<std::dec
<< "\t0x"<<std::hex<<AccCache.AccPtr<<std::dec<<"\t" <<str
@@ -557,16 +608,16 @@ void MemoryManager::Audit(std::string s)
<< "\t LRUvalid " << AccCache.LRU_valid<<std::endl;
}
assert( AccCache.cpuLock== 0 ) ;
assert( AccCache.accLock== 0 ) ;
GRID_ASSERT( AccCache.cpuLock== 0 ) ;
GRID_ASSERT( AccCache.accLock== 0 ) ;
}
std::cout << " Memory Manager::Audit() no locked table entries "<<std::endl;
assert(LruBytes1==LruBytes2);
assert(LruBytes1==DeviceLRUBytes);
GRID_ASSERT(LruBytes1==LruBytes2);
GRID_ASSERT(LruBytes1==DeviceLRUBytes);
std::cout << " Memory Manager::Audit() evictable bytes matches sum over table "<<std::endl;
assert(AccBytes==DeviceBytes);
GRID_ASSERT(AccBytes==DeviceBytes);
std::cout << " Memory Manager::Audit() device bytes matches sum over table "<<std::endl;
assert(LruCnt == LRU.size());
GRID_ASSERT(LruCnt == LRU.size());
std::cout << " Memory Manager::Audit() LRU entry count matches "<<std::endl;
}
+1
View File
@@ -24,6 +24,7 @@ void MemoryManager::PrintState(void* CpuPtr)
std::cout << GridLogMessage << "Host<->Device memory movement not currently managed by Grid." << std::endl;
};
void MemoryManager::Print(void){};
void MemoryManager::EvictAll(void){}; // nothing to evict in unified memory
void MemoryManager::PrintAll(void){};
void MemoryManager::NotifyDeletion(void *ptr){};
+3 -3
View File
@@ -10,16 +10,16 @@ void check_huge_pages(void *Buf,uint64_t BYTES)
{
#ifdef __linux__
int fd = open("/proc/self/pagemap", O_RDONLY);
assert(fd >= 0);
GRID_ASSERT(fd >= 0);
const int page_size = 4096;
uint64_t virt_pfn = (uint64_t)Buf / page_size;
off_t offset = sizeof(uint64_t) * virt_pfn;
uint64_t npages = (BYTES + page_size-1) / page_size;
std::vector<uint64_t> pagedata(npages);
uint64_t ret = lseek(fd, offset, SEEK_SET);
assert(ret == offset);
GRID_ASSERT(ret == offset);
ret = ::read(fd, &pagedata[0], sizeof(uint64_t)*npages);
assert(ret == sizeof(uint64_t) * npages);
GRID_ASSERT(ret == sizeof(uint64_t) * npages);
int nhugepages = npages / 512;
int n4ktotal, nnothuge;
n4ktotal = 0;
+4 -4
View File
@@ -165,7 +165,7 @@ public:
//
if ( _simd_layout[dimension] > 2 ) {
for(int d=0;d<_ndimension;d++){
if ( d != dimension ) assert ( (_simd_layout[d]==1) );
if ( d != dimension ) GRID_ASSERT ( (_simd_layout[d]==1) );
}
permute_type = RotateBit; // How to specify distance; this is not just direction.
return permute_type;
@@ -187,7 +187,7 @@ public:
inline int64_t gSites(void) const { return (int64_t)_isites*(int64_t)_osites*(int64_t)_Nprocessors; };
inline int Nd (void) const { return _ndimension;};
inline const Coordinate LocalStarts(void) { return _lstart; };
inline const Coordinate &LocalStarts(void) { return _lstart; };
inline const Coordinate &FullDimensions(void) { return _fdimensions;};
inline const Coordinate &GlobalDimensions(void) { return _gdimensions;};
inline const Coordinate &LocalDimensions(void) { return _ldimensions;};
@@ -216,11 +216,11 @@ public:
// Global addressing
////////////////////////////////////////////////////////////////
void GlobalIndexToGlobalCoor(int64_t gidx,Coordinate &gcoor){
assert(gidx< gSites());
GRID_ASSERT(gidx< gSites());
Lexicographic::CoorFromIndex(gcoor,gidx,_gdimensions);
}
void LocalIndexToLocalCoor(int lidx,Coordinate &lcoor){
assert(lidx<lSites());
GRID_ASSERT(lidx<lSites());
Lexicographic::CoorFromIndex(lcoor,lidx,_ldimensions);
}
void GlobalCoorToGlobalIndex(const Coordinate & gcoor,int64_t & gidx){
+2 -2
View File
@@ -128,10 +128,10 @@ public:
// Use a reduced simd grid
_ldimensions[d] = _gdimensions[d] / _processors[d]; //local dimensions
//std::cout << _ldimensions[d] << " " << _gdimensions[d] << " " << _processors[d] << std::endl;
assert(_ldimensions[d] * _processors[d] == _gdimensions[d]);
GRID_ASSERT(_ldimensions[d] * _processors[d] == _gdimensions[d]);
_rdimensions[d] = _ldimensions[d] / _simd_layout[d]; //overdecomposition
assert(_rdimensions[d] * _simd_layout[d] == _ldimensions[d]);
GRID_ASSERT(_rdimensions[d] * _simd_layout[d] == _ldimensions[d]);
_lstart[d] = _processor_coor[d] * _ldimensions[d];
_lend[d] = _processor_coor[d] * _ldimensions[d] + _ldimensions[d] - 1;
+9 -9
View File
@@ -67,7 +67,7 @@ public:
}
virtual int CheckerBoard(const Coordinate &site){
int linear=0;
assert(site.size()==_ndimension);
GRID_ASSERT(site.size()==_ndimension);
for(int d=0;d<_ndimension;d++){
if(_checker_dim_mask[d])
linear=linear+site[d];
@@ -160,11 +160,11 @@ public:
_isCheckerBoarded = true;
_checker_dim = checker_dim;
assert(checker_dim_mask[checker_dim] == 1);
GRID_ASSERT(checker_dim_mask[checker_dim] == 1);
_ndimension = dimensions.size();
assert(checker_dim_mask.size() == _ndimension);
assert(processor_grid.size() == _ndimension);
assert(simd_layout.size() == _ndimension);
GRID_ASSERT(checker_dim_mask.size() == _ndimension);
GRID_ASSERT(processor_grid.size() == _ndimension);
GRID_ASSERT(simd_layout.size() == _ndimension);
_fdimensions.resize(_ndimension);
_gdimensions.resize(_ndimension);
@@ -190,20 +190,20 @@ public:
if (d == _checker_dim)
{
assert((_gdimensions[d] & 0x1) == 0);
GRID_ASSERT((_gdimensions[d] & 0x1) == 0);
_gdimensions[d] = _gdimensions[d] / 2; // Remove a checkerboard
_gsites /= 2;
}
_ldimensions[d] = _gdimensions[d] / _processors[d];
assert(_ldimensions[d] * _processors[d] == _gdimensions[d]);
GRID_ASSERT(_ldimensions[d] * _processors[d] == _gdimensions[d]);
_lstart[d] = _processor_coor[d] * _ldimensions[d];
_lend[d] = _processor_coor[d] * _ldimensions[d] + _ldimensions[d] - 1;
// Use a reduced simd grid
_simd_layout[d] = simd_layout[d];
_rdimensions[d] = _ldimensions[d] / _simd_layout[d]; // this is not checking if this is integer
assert(_rdimensions[d] * _simd_layout[d] == _ldimensions[d]);
assert(_rdimensions[d] > 0);
GRID_ASSERT(_rdimensions[d] * _simd_layout[d] == _ldimensions[d]);
GRID_ASSERT(_rdimensions[d] > 0);
// all elements of a simd vector must have same checkerboard.
// If Ls vectorised, this must still be the case; e.g. dwf rb5d
+1
View File
@@ -34,6 +34,7 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
NAMESPACE_BEGIN(Grid);
bool Stencil_force_mpi = true;
bool Stencil_force_barrier = false;
///////////////////////////////////////////////////////////////
// Info that is setup once and indept of cartesian layout
+52 -15
View File
@@ -33,9 +33,12 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
///////////////////////////////////
#include <Grid/communicator/SharedMemory.h>
#define NVLINK_GET
NAMESPACE_BEGIN(Grid);
extern bool Stencil_force_mpi ;
extern bool Stencil_force_barrier ;
class CartesianCommunicator : public SharedMemory {
@@ -106,7 +109,7 @@ public:
// very VERY rarely (Log, serial RNG) we need world without a grid
////////////////////////////////////////////////////////////////////////////////
static int RankWorld(void) ;
static void BroadcastWorld(int root,void* data, int bytes);
static void BroadcastWorld(int root,void* data, uint64_t bytes);
static void BarrierWorld(void);
////////////////////////////////////////////////////////////
@@ -147,7 +150,8 @@ public:
sizeof(obj),d*100+p);
}
CommsComplete(list);
if (!list.empty()) // avoid triggering GRID_ASSERT in comms == none
CommsComplete(list);
for(int p=1;p<_processors[d];p++){
accum = accum + column[p];
}
@@ -172,32 +176,38 @@ public:
int dest,
void *recv,
int from,
int bytes,int dir);
uint64_t bytes,int dir);
void SendToRecvFrom(void *xmit,
int xmit_to_rank,
void *recv,
int recv_from_rank,
int bytes);
uint64_t bytes);
int IsOffNode(int rank);
double StencilSendToRecvFrom(void *xmit,
int xmit_to_rank,int do_xmit,
void *recv,
int recv_from_rank,int do_recv,
int bytes,int dir);
uint64_t bytes,int dir);
double StencilSendToRecvFromPrepare(std::vector<CommsRequest_t> &list,
void *xmit,
int xmit_to_rank,int do_xmit,
void *recv,
int recv_from_rank,int do_recv,
int xbytes,int rbytes,int dir);
uint64_t xbytes,uint64_t rbytes,int dir);
// Could do a PollHtoD and have a CommsMerge dependence
void StencilSendToRecvFromPollDtoH (std::vector<CommsRequest_t> &list);
void StencilSendToRecvFromPollIRecv(std::vector<CommsRequest_t> &list);
double StencilSendToRecvFromBegin(std::vector<CommsRequest_t> &list,
void *xmit,
void *xmit,void *xmit_comp,
int xmit_to_rank,int do_xmit,
void *recv,
void *recv,void *recv_comp,
int recv_from_rank,int do_recv,
int xbytes,int rbytes,int dir);
uint64_t xbytes,uint64_t rbytes,int dir);
void StencilSendToRecvFromComplete(std::vector<CommsRequest_t> &waitall,int i);
@@ -211,24 +221,51 @@ public:
////////////////////////////////////////////////////////////
// Broadcast a buffer and composite larger
////////////////////////////////////////////////////////////
void Broadcast(int root,void* data, int bytes);
void Broadcast(int root,void* data, uint64_t bytes);
////////////////////////////////////////////////////////////
// All2All down one dimension
////////////////////////////////////////////////////////////
template<class T> void AllToAll(int dim,std::vector<T> &in, std::vector<T> &out){
assert(dim>=0);
assert(dim<_ndimension);
assert(in.size()==out.size());
GRID_ASSERT(dim>=0);
GRID_ASSERT(dim<_ndimension);
GRID_ASSERT(in.size()==out.size());
int numnode = _processors[dim];
uint64_t bytes=sizeof(T);
uint64_t words=in.size()/numnode;
assert(numnode * words == in.size());
assert(words < (1ULL<<31));
GRID_ASSERT(numnode * words == in.size());
GRID_ASSERT(words < (1ULL<<31));
AllToAll(dim,(void *)&in[0],(void *)&out[0],words,bytes);
}
void AllToAll(int dim ,void *in,void *out,uint64_t words,uint64_t bytes);
void AllToAll(void *in,void *out,uint64_t words ,uint64_t bytes);
////////////////////////////////////////////////////////////////////////////
// Variable count all to all. Counts and displacements are in units of
// "bytes" sized words and are indexed by rank within this communicator.
// For exchanges that are a permutation but do not divide evenly between
// ranks; AllToAll above is the uniform count special case.
////////////////////////////////////////////////////////////////////////////
void AllToAllV(void *in ,const std::vector<int> &sendcounts,const std::vector<int> &senddispls,
void *out,const std::vector<int> &recvcounts,const std::vector<int> &recvdispls,
uint64_t bytes);
////////////////////////////////////////////////////////////////////////////
// Gather to all. Every rank contributes sendcount words from "in"; every
// rank receives the concatenation of all contributions in rank order.
// Counts and displacements are in units of "bytes" sized words and are
// indexed by rank within this communicator; recvcounts[ThisRank()] must
// equal sendcount. AllGather below is the uniform count special case.
//
// This is the primitive form of the zero-fill + GlobalSumVector idiom used
// wherever each element of the result has exactly one contributing rank.
// That idiom moves the payload twice and reduces over zeros; this moves it
// once and performs no arithmetic.
////////////////////////////////////////////////////////////////////////////
void AllGatherV(void *in ,int sendcount,
void *out,const std::vector<int> &recvcounts,const std::vector<int> &recvdispls,
uint64_t bytes);
void AllGather (void *in ,void *out,uint64_t words,uint64_t bytes);
template<class obj> void Broadcast(int root,obj &data)
{
+375 -156
View File
@@ -28,9 +28,17 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
#include <Grid/GridCore.h>
#include <Grid/communicator/SharedMemory.h>
void GridAbort(void) { MPI_Abort(MPI_COMM_WORLD,SIGABRT); }
extern void * Grid_backtrace_buffer[_NBACKTRACE];
NAMESPACE_BEGIN(Grid);
Grid_MPI_Comm CartesianCommunicator::communicator_world;
#ifdef GRID_CHECKSUM_COMMS
uint64_t checksum_index = 1;
#endif
////////////////////////////////////////////
// First initialise of comms system
@@ -55,11 +63,11 @@ void CartesianCommunicator::Init(int *argc, char ***argv)
#endif
//If only 1 comms thread we require any threading mode other than SINGLE, but for multiple comms threads we need MULTIPLE
if( (nCommThreads == 1) && (provided == MPI_THREAD_SINGLE) ) {
assert(0);
GRID_ASSERT(0);
}
if( (nCommThreads > 1) && (provided != MPI_THREAD_MULTIPLE) ) {
assert(0);
GRID_ASSERT(0);
}
}
@@ -80,20 +88,20 @@ void CartesianCommunicator::Init(int *argc, char ***argv)
void CartesianCommunicator::ShiftedRanks(int dim,int shift,int &source,int &dest)
{
int ierr=MPI_Cart_shift(communicator,dim,shift,&source,&dest);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
int CartesianCommunicator::RankFromProcessorCoor(Coordinate &coor)
{
int rank;
int ierr=MPI_Cart_rank (communicator, &coor[0], &rank);
assert(ierr==0);
GRID_ASSERT(ierr==0);
return rank;
}
void CartesianCommunicator::ProcessorCoorFromRank(int rank, Coordinate &coor)
{
coor.resize(_ndimension);
int ierr=MPI_Cart_coords (communicator, rank, _ndimension,&coor[0]);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -120,8 +128,8 @@ CartesianCommunicator::CartesianCommunicator(const Coordinate &processors)
//////////////////////////////////
CartesianCommunicator::CartesianCommunicator(const Coordinate &processors,const CartesianCommunicator &parent,int &srank)
{
_ndimension = processors.size(); assert(_ndimension>=1);
int parent_ndimension = parent._ndimension; assert(_ndimension >= parent._ndimension);
_ndimension = processors.size(); GRID_ASSERT(_ndimension>=1);
int parent_ndimension = parent._ndimension; GRID_ASSERT(_ndimension >= parent._ndimension);
Coordinate parent_processor_coor(_ndimension,0);
Coordinate parent_processors (_ndimension,1);
Coordinate shm_processors (_ndimension,1);
@@ -145,7 +153,7 @@ CartesianCommunicator::CartesianCommunicator(const Coordinate &processors,const
childsize *= processors[d];
}
int Nchild = Nparent/childsize;
assert (childsize * Nchild == Nparent);
GRID_ASSERT (childsize * Nchild == Nparent);
Coordinate ccoor(_ndimension); // coor within subcommunicator
Coordinate scoor(_ndimension); // coor of split within parent
@@ -171,12 +179,12 @@ CartesianCommunicator::CartesianCommunicator(const Coordinate &processors,const
// Split the communicator
////////////////////////////////////////////////////////////////
int ierr= MPI_Comm_split(parent.communicator,srank,crank,&comm_split);
assert(ierr==0);
GRID_ASSERT(ierr==0);
} else {
srank = 0;
int ierr = MPI_Comm_dup (parent.communicator,&comm_split);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -201,7 +209,7 @@ CartesianCommunicator::CartesianCommunicator(const Coordinate &processors,const
}
}
for(int d=0;d<processors.size();d++){
assert(_processor_coor[d] == ccoor[d] );
GRID_ASSERT(_processor_coor[d] == ccoor[d] );
}
}
@@ -243,7 +251,7 @@ void CartesianCommunicator::InitFromMPICommunicator(const Coordinate &processors
for(int i=0;i<_ndimension*2;i++){
MPI_Comm_dup(communicator,&communicator_halo[i]);
}
assert(Size==_Nprocessors);
GRID_ASSERT(Size==_Nprocessors);
}
CartesianCommunicator::~CartesianCommunicator()
@@ -259,62 +267,74 @@ CartesianCommunicator::~CartesianCommunicator()
}
#ifdef USE_GRID_REDUCTION
void CartesianCommunicator::GlobalSum(float &f){
FlightRecorder::StepLog("GlobalSumP2P");
CartesianCommunicator::GlobalSumP2P(f);
}
void CartesianCommunicator::GlobalSum(double &d)
{
FlightRecorder::StepLog("GlobalSumP2P");
CartesianCommunicator::GlobalSumP2P(d);
}
#else
void CartesianCommunicator::GlobalSum(float &f){
FlightRecorder::StepLog("AllReduce float");
int ierr=MPI_Allreduce(MPI_IN_PLACE,&f,1,MPI_FLOAT,MPI_SUM,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::GlobalSum(double &d)
{
FlightRecorder::StepLog("AllReduce double");
int ierr = MPI_Allreduce(MPI_IN_PLACE,&d,1,MPI_DOUBLE,MPI_SUM,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
#endif
void CartesianCommunicator::GlobalSum(uint32_t &u){
FlightRecorder::StepLog("AllReduce uint32_t");
int ierr=MPI_Allreduce(MPI_IN_PLACE,&u,1,MPI_UINT32_T,MPI_SUM,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::GlobalSum(uint64_t &u){
FlightRecorder::StepLog("AllReduce uint64_t");
int ierr=MPI_Allreduce(MPI_IN_PLACE,&u,1,MPI_UINT64_T,MPI_SUM,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::GlobalSumVector(uint64_t* u,int N){
FlightRecorder::StepLog("AllReduceVector");
int ierr=MPI_Allreduce(MPI_IN_PLACE,u,N,MPI_UINT64_T,MPI_SUM,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::GlobalXOR(uint32_t &u){
int ierr=MPI_Allreduce(MPI_IN_PLACE,&u,1,MPI_UINT32_T,MPI_BXOR,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::GlobalXOR(uint64_t &u){
FlightRecorder::StepLog("GlobalXOR");
int ierr=MPI_Allreduce(MPI_IN_PLACE,&u,1,MPI_UINT64_T,MPI_BXOR,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::GlobalMax(float &f)
{
FlightRecorder::StepLog("GlobalMax");
int ierr=MPI_Allreduce(MPI_IN_PLACE,&f,1,MPI_FLOAT,MPI_MAX,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::GlobalMax(double &d)
{
FlightRecorder::StepLog("GlobalMax");
int ierr = MPI_Allreduce(MPI_IN_PLACE,&d,1,MPI_DOUBLE,MPI_MAX,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::GlobalSumVector(float *f,int N)
{
FlightRecorder::StepLog("GlobalSumVector(float *)");
int ierr=MPI_Allreduce(MPI_IN_PLACE,f,N,MPI_FLOAT,MPI_SUM,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::GlobalSumVector(double *d,int N)
{
FlightRecorder::StepLog("GlobalSumVector(double *)");
int ierr = MPI_Allreduce(MPI_IN_PLACE,d,N,MPI_DOUBLE,MPI_SUM,communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::SendToRecvFromBegin(std::vector<MpiCommsRequest_t> &list,
@@ -322,24 +342,23 @@ void CartesianCommunicator::SendToRecvFromBegin(std::vector<MpiCommsRequest_t> &
int dest,
void *recv,
int from,
int bytes,int dir)
uint64_t bytes,int dir)
{
MPI_Request xrq;
MPI_Request rrq;
assert(dest != _processor);
assert(from != _processor);
GRID_ASSERT(dest != _processor);
GRID_ASSERT(from != _processor);
int tag;
tag= dir+from*32;
int ierr=MPI_Irecv(recv, bytes, MPI_CHAR,from,tag,communicator,&rrq);
assert(ierr==0);
int ierr=MPI_Irecv(recv,(int)( bytes/sizeof(int32_t)), MPI_INT32_T,from,tag,communicator,&rrq);
GRID_ASSERT(ierr==0);
list.push_back(rrq);
tag= dir+_processor*32;
ierr =MPI_Isend(xmit, bytes, MPI_CHAR,dest,tag,communicator,&xrq);
assert(ierr==0);
ierr =MPI_Isend(xmit,(int)(bytes/sizeof(int32_t)), MPI_INT32_T,dest,tag,communicator,&xrq);
GRID_ASSERT(ierr==0);
list.push_back(xrq);
}
void CartesianCommunicator::CommsComplete(std::vector<MpiCommsRequest_t> &list)
@@ -350,7 +369,7 @@ void CartesianCommunicator::CommsComplete(std::vector<MpiCommsRequest_t> &list)
std::vector<MPI_Status> status(nreq);
int ierr = MPI_Waitall(nreq,&list[0],&status[0]);
assert(ierr==0);
GRID_ASSERT(ierr==0);
list.resize(0);
}
@@ -359,61 +378,63 @@ void CartesianCommunicator::SendToRecvFrom(void *xmit,
int dest,
void *recv,
int from,
int bytes)
uint64_t bytes)
{
std::vector<MpiCommsRequest_t> reqs(0);
unsigned long xcrc = crc32(0L, Z_NULL, 0);
unsigned long rcrc = crc32(0L, Z_NULL, 0);
int myrank = _processor;
int ierr;
// Enforce no UVM in comms, device or host OK
assert(acceleratorIsCommunicable(xmit));
assert(acceleratorIsCommunicable(recv));
GRID_ASSERT(acceleratorIsCommunicable(xmit));
GRID_ASSERT(acceleratorIsCommunicable(recv));
// Give the CPU to MPI immediately; can use threads to overlap optionally
// printf("proc %d SendToRecvFrom %d bytes Sendrecv \n",_processor,bytes);
ierr=MPI_Sendrecv(xmit,bytes,MPI_CHAR,dest,myrank,
recv,bytes,MPI_CHAR,from, from,
ierr=MPI_Sendrecv(xmit,(int)(bytes/sizeof(int32_t)),MPI_INT32_T,dest,myrank,
recv,(int)(bytes/sizeof(int32_t)),MPI_INT32_T,from, from,
communicator,MPI_STATUS_IGNORE);
assert(ierr==0);
GRID_ASSERT(ierr==0);
// xcrc = crc32(xcrc,(unsigned char *)xmit,bytes);
// rcrc = crc32(rcrc,(unsigned char *)recv,bytes);
// printf("proc %d SendToRecvFrom %d bytes xcrc %lx rcrc %lx\n",_processor,bytes,xcrc,rcrc); fflush
}
// Basic Halo comms primitive
double CartesianCommunicator::StencilSendToRecvFrom( void *xmit,
int dest, int dox,
void *recv,
int from, int dor,
int bytes,int dir)
uint64_t bytes,int dir)
{
std::vector<CommsRequest_t> list;
double offbytes = StencilSendToRecvFromPrepare(list,xmit,dest,dox,recv,from,dor,bytes,bytes,dir);
offbytes += StencilSendToRecvFromBegin(list,xmit,dest,dox,recv,from,dor,bytes,bytes,dir);
offbytes += StencilSendToRecvFromBegin(list,xmit,xmit,dest,dox,recv,recv,from,dor,bytes,bytes,dir);
StencilSendToRecvFromComplete(list,dir);
return offbytes;
}
int CartesianCommunicator::IsOffNode(int rank)
{
int grank = ShmRanks[rank];
if ( grank == MPI_UNDEFINED ) return true;
else return false;
}
#ifdef ACCELERATOR_AWARE_MPI
void CartesianCommunicator::StencilSendToRecvFromPollIRecv(std::vector<CommsRequest_t> &list) {};
void CartesianCommunicator::StencilSendToRecvFromPollDtoH(std::vector<CommsRequest_t> &list) {};
double CartesianCommunicator::StencilSendToRecvFromPrepare(std::vector<CommsRequest_t> &list,
void *xmit,
int dest,int dox,
void *recv,
int from,int dor,
int xbytes,int rbytes,int dir)
uint64_t xbytes,uint64_t rbytes,int dir)
{
return 0.0; // Do nothing -- no preparation required
}
double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsRequest_t> &list,
void *xmit,
void *xmit,void *xmit_comp,
int dest,int dox,
void *recv,
void *recv,void *recv_comp,
int from,int dor,
int xbytes,int rbytes,int dir)
uint64_t xbytes,uint64_t rbytes,int dir)
{
int ncomm =communicator_halo.size();
int commdir=dir%ncomm;
@@ -426,33 +447,44 @@ double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsReques
int gfrom = ShmRanks[from];
int gme = ShmRanks[_processor];
assert(dest != _processor);
assert(from != _processor);
assert(gme == ShmRank);
GRID_ASSERT(dest != _processor);
GRID_ASSERT(from != _processor);
GRID_ASSERT(gme == ShmRank);
double off_node_bytes=0.0;
int tag;
if ( dor ) {
if ( (gfrom ==MPI_UNDEFINED) || Stencil_force_mpi ) {
tag= dir+from*32;
ierr=MPI_Irecv(recv, rbytes, MPI_CHAR,from,tag,communicator_halo[commdir],&rrq);
assert(ierr==0);
// std::cout << " StencilSendToRecvFrom "<<dir<<" MPI_Irecv "<<std::hex<<recv<<std::dec<<std::endl;
ierr=MPI_Irecv(recv_comp,(int)(rbytes/sizeof(int32_t)), MPI_INT32_T,from,tag,communicator_halo[commdir],&rrq);
GRID_ASSERT(ierr==0);
list.push_back(rrq);
off_node_bytes+=rbytes;
}
#ifdef NVLINK_GET
else {
void *shm = (void *) this->ShmBufferTranslate(from,xmit);
GRID_ASSERT(shm!=NULL);
// std::cout << " StencilSendToRecvFrom "<<dir<<" CopyDeviceToDevice recv "<<std::hex<<recv<<" remote "<<shm <<std::dec<<std::endl;
acceleratorCopyDeviceToDeviceAsynch(shm,recv,rbytes);
}
#endif
}
// This is a NVLINK PUT
if (dox) {
if ( (gdest == MPI_UNDEFINED) || Stencil_force_mpi ) {
tag= dir+_processor*32;
ierr =MPI_Isend(xmit, xbytes, MPI_CHAR,dest,tag,communicator_halo[commdir],&xrq);
assert(ierr==0);
ierr =MPI_Isend(xmit_comp,(int)(xbytes/sizeof(int32_t)), MPI_INT32_T,dest,tag,communicator_halo[commdir],&xrq);
GRID_ASSERT(ierr==0);
list.push_back(xrq);
off_node_bytes+=xbytes;
} else {
#ifndef NVLINK_GET
void *shm = (void *) this->ShmBufferTranslate(dest,recv);
assert(shm!=NULL);
GRID_ASSERT(shm!=NULL);
acceleratorCopyDeviceToDeviceAsynch(xmit,shm,xbytes);
#endif
}
}
return off_node_bytes;
@@ -461,13 +493,13 @@ double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsReques
void CartesianCommunicator::StencilSendToRecvFromComplete(std::vector<CommsRequest_t> &list,int dir)
{
int nreq=list.size();
/*finishes Get/Put*/
acceleratorCopySynchronise();
if (nreq==0) return;
std::vector<MPI_Status> status(nreq);
int ierr = MPI_Waitall(nreq,&list[0],&status[0]);
assert(ierr==0);
GRID_ASSERT(ierr==0);
list.resize(0);
this->StencilBarrier();
}
@@ -510,7 +542,7 @@ double CartesianCommunicator::StencilSendToRecvFromPrepare(std::vector<CommsRequ
int dest,int dox,
void *recv,
int from,int dor,
int xbytes,int rbytes,int dir)
uint64_t xbytes,uint64_t rbytes,int dir)
{
/*
* Bring sequence from Stencil.h down to lower level.
@@ -527,9 +559,9 @@ double CartesianCommunicator::StencilSendToRecvFromPrepare(std::vector<CommsRequ
int gfrom = ShmRanks[from];
int gme = ShmRanks[_processor];
assert(dest != _processor);
assert(from != _processor);
assert(gme == ShmRank);
GRID_ASSERT(dest != _processor);
GRID_ASSERT(from != _processor);
GRID_ASSERT(gme == ShmRank);
double off_node_bytes=0.0;
int tag;
@@ -542,18 +574,24 @@ double CartesianCommunicator::StencilSendToRecvFromPrepare(std::vector<CommsRequ
* - post device - host send buffer transfer asynch
*/
#ifdef GRID_CHECKSUM_COMMS
rbytes += 8;
xbytes += 8;
#endif
if ( dor ) {
if ( (gfrom ==MPI_UNDEFINED) || Stencil_force_mpi ) {
tag= dir+from*32;
host_recv = this->HostBufferMalloc(rbytes);
ierr=MPI_Irecv(host_recv, rbytes, MPI_CHAR,from,tag,communicator_halo[commdir],&rrq);
assert(ierr==0);
ierr=MPI_Irecv(host_recv,(int)(rbytes/sizeof(int32_t)), MPI_INT32_T,from,tag,communicator_halo[commdir],&rrq);
GRID_ASSERT(ierr==0);
CommsRequest_t srq;
srq.PacketType = InterNodeRecv;
srq.bytes = rbytes;
srq.req = rrq;
srq.host_buf = host_recv;
srq.device_buf = recv;
srq.tag = tag;
list.push_back(srq);
off_node_bytes+=rbytes;
}
@@ -561,60 +599,125 @@ double CartesianCommunicator::StencilSendToRecvFromPrepare(std::vector<CommsRequ
if (dox) {
if ( (gdest == MPI_UNDEFINED) || Stencil_force_mpi ) {
#undef DEVICE_TO_HOST_CONCURRENT // pipeline
#ifdef DEVICE_TO_HOST_CONCURRENT
tag= dir+_processor*32;
host_xmit = this->HostBufferMalloc(xbytes);
acceleratorCopyFromDeviceAsynch(xmit, host_xmit,xbytes); // Make this Asynch
CommsRequest_t srq;
#ifdef GRID_CHECKSUM_COMMS
uint64_t xbytes_data = xbytes - 8;
srq.ev = acceleratorCopyFromDeviceAsynch(xmit, host_xmit,xbytes_data); // Make this Asynch
GRID_ASSERT(xbytes % 8 == 0);
// flip one bit so that a zero buffer is not consistent
uint64_t xsum = checksum_gpu((uint64_t*)xmit, xbytes_data / 8) ^ (checksum_index + 1 + 1000 * tag);
*(uint64_t*)(((char*)host_xmit) + xbytes_data) = xsum;
#else
srq.ev = acceleratorCopyFromDeviceAsynch(xmit, host_xmit,xbytes); // Make this Asynch
#endif
// ierr =MPI_Isend(host_xmit, xbytes, MPI_CHAR,dest,tag,communicator_halo[commdir],&xrq);
// assert(ierr==0);
// GRID_ASSERT(ierr==0);
// off_node_bytes+=xbytes;
CommsRequest_t srq;
srq.PacketType = InterNodeXmit;
srq.bytes = xbytes;
// srq.req = xrq;
srq.host_buf = host_xmit;
srq.device_buf = xmit;
srq.tag = tag;
srq.dest = dest;
srq.commdir = commdir;
list.push_back(srq);
#else
tag= dir+_processor*32;
host_xmit = this->HostBufferMalloc(xbytes);
const int chunks=1;
for(int n=0;n<chunks;n++){
void * host_xmitc = (void *)( (uint64_t) host_xmit + n*xbytes/chunks);
void * xmitc = (void *)( (uint64_t) xmit + n*xbytes/chunks);
acceleratorCopyFromDeviceAsynch(xmitc, host_xmitc,xbytes/chunks); // Make this Asynch
}
acceleratorCopySynchronise(); // Complete all pending copy transfers
ierr =MPI_Isend(host_xmit, xbytes, MPI_CHAR,dest,tag,communicator_halo[commdir],&xrq);
assert(ierr==0);
off_node_bytes+=xbytes;
CommsRequest_t srq;
srq.PacketType = InterNodeXmit;
srq.bytes = xbytes;
srq.req = xrq;
srq.host_buf = host_xmit;
srq.device_buf = xmit;
list.push_back(srq);
#endif
}
}
return off_node_bytes;
}
/*
* In the interest of better pipelining, poll for completion on each DtoH and
* start MPI_ISend in the meantime
*/
void CartesianCommunicator::StencilSendToRecvFromPollIRecv(std::vector<CommsRequest_t> &list)
{
int pending = 0;
do {
pending = 0;
for(int idx = 0; idx<list.size();idx++){
if ( list[idx].PacketType==InterNodeRecv ) {
int flag = 0;
MPI_Status status;
int ierr = MPI_Test(&list[idx].req,&flag,&status);
assert(ierr==0);
if ( flag ) {
// std::cout << " PollIrecv "<<idx<<" flag "<<flag<<std::endl;
#ifdef GRID_CHECKSUM_COMMS
acceleratorCopyToDeviceAsynch(list[idx].host_buf,list[idx].device_buf,list[idx].bytes - 8);
#else
acceleratorCopyToDeviceAsynch(list[idx].host_buf,list[idx].device_buf,list[idx].bytes);
#endif
list[idx].PacketType=InterNodeReceiveHtoD;
} else {
pending ++;
}
}
}
// std::cout << " PollIrecv "<<pending<<" pending requests"<<std::endl;
} while ( pending );
}
void CartesianCommunicator::StencilSendToRecvFromPollDtoH(std::vector<CommsRequest_t> &list)
{
int pending = 0;
do {
pending = 0;
for(int idx = 0; idx<list.size();idx++){
if ( list[idx].PacketType==InterNodeXmit ) {
if ( acceleratorEventIsComplete(list[idx].ev) ) {
void *host_xmit = list[idx].host_buf;
uint64_t xbytes = list[idx].bytes;
int dest = list[idx].dest;
int tag = list[idx].tag;
int commdir = list[idx].commdir;
///////////////////
// Send packet
///////////////////
// std::cout << " DtoH is complete for index "<<idx<<" calling MPI_Isend "<<std::endl;
MPI_Request xrq;
int ierr =MPI_Isend(host_xmit, (int)(xbytes/sizeof(int32_t)), MPI_INT32_T,dest,tag,communicator_halo[commdir],&xrq);
GRID_ASSERT(ierr==0);
list[idx].req = xrq; // Update the MPI request in the list
list[idx].PacketType=InterNodeXmitISend;
} else {
// not done, so return to polling loop
pending++;
}
}
}
} while (pending);
}
double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsRequest_t> &list,
void *xmit,
void *xmit,void *xmit_comp,
int dest,int dox,
void *recv,
void *recv,void *recv_comp,
int from,int dor,
int xbytes,int rbytes,int dir)
uint64_t xbytes,uint64_t rbytes,int dir)
{
int ncomm =communicator_halo.size();
int commdir=dir%ncomm;
@@ -627,9 +730,9 @@ double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsReques
int gfrom = ShmRanks[from];
int gme = ShmRanks[_processor];
assert(dest != _processor);
assert(from != _processor);
assert(gme == ShmRank);
GRID_ASSERT(dest != _processor);
GRID_ASSERT(from != _processor);
GRID_ASSERT(gme == ShmRank);
double off_node_bytes=0.0;
int tag;
@@ -644,69 +747,122 @@ double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsReques
* - complete all copies
* - post MPI send asynch
*/
#ifdef NVLINK_GET
if ( dor ) {
// static int printed;
// if((printed<8) && this->IsBoss() ) {
// printf("dir %d doX %d doR %d Face size %ld %ld\n",dir,dox,dor,xbytes,rbytes);
// printed++;
// }
if ( ! ( (gfrom ==MPI_UNDEFINED) || Stencil_force_mpi ) ) {
// Intranode
void *shm = (void *) this->ShmBufferTranslate(from,xmit);
GRID_ASSERT(shm!=NULL);
CommsRequest_t srq;
srq.ev = acceleratorCopyDeviceToDeviceAsynch(shm,recv,rbytes);
srq.PacketType = IntraNodeRecv;
srq.bytes = xbytes;
// srq.req = xrq;
srq.host_buf = NULL;
srq.device_buf = xmit;
srq.tag = -1;
srq.dest = dest;
srq.commdir = dir;
list.push_back(srq);
}
}
#else
if (dox) {
if ( (gdest == MPI_UNDEFINED) || Stencil_force_mpi ) {
#ifdef DEVICE_TO_HOST_CONCURRENT
tag= dir+_processor*32;
// Find the send in the prepared list
int list_idx=-1;
for(int idx = 0; idx<list.size();idx++){
if ( (list[idx].device_buf==xmit)
&&(list[idx].PacketType==InterNodeXmit)
&&(list[idx].bytes==xbytes) ) {
list_idx = idx;
host_xmit = list[idx].host_buf;
}
}
assert(list_idx != -1); // found it
ierr =MPI_Isend(host_xmit, xbytes, MPI_CHAR,dest,tag,communicator_halo[commdir],&xrq);
assert(ierr==0);
list[list_idx].req = xrq; // Update the MPI request in the list
off_node_bytes+=xbytes;
#endif
} else {
if ( !( (gdest == MPI_UNDEFINED) || Stencil_force_mpi ) ) {
// Intranode
void *shm = (void *) this->ShmBufferTranslate(dest,recv);
assert(shm!=NULL);
acceleratorCopyDeviceToDeviceAsynch(xmit,shm,xbytes);
GRID_ASSERT(shm!=NULL);
CommsRequest_t srq;
srq.ev = acceleratorCopyDeviceToDeviceAsynch(xmit,shm,xbytes);
srq.PacketType = IntraNodeXmit;
srq.bytes = xbytes;
// srq.req = xrq;
srq.host_buf = NULL;
srq.device_buf = xmit;
srq.tag = -1;
srq.dest = dest;
srq.commdir = dir;
list.push_back(srq);
}
}
#endif
return off_node_bytes;
}
void CartesianCommunicator::StencilSendToRecvFromComplete(std::vector<CommsRequest_t> &list,int dir)
{
int nreq=list.size();
acceleratorCopySynchronise(); // Complete all pending copy transfers D2D
if (nreq==0) return;
std::vector<MPI_Status> status(nreq);
std::vector<MPI_Request> MpiRequests(nreq);
std::vector<MPI_Status> status;
std::vector<MPI_Request> MpiRequests;
for(int r=0;r<list.size();r++){
// Must check each Send buf is clear to reuse
if ( list[r].PacketType == InterNodeXmitISend ) MpiRequests.push_back(list[r].req);
// if ( list[r].PacketType == InterNodeRecv ) MpiRequests.push_back(list[r].req); // Already "Test" passed
}
for(int r=0;r<nreq;r++){
MpiRequests[r] = list[r].req;
int nreq=MpiRequests.size();
if (nreq>0) {
status.resize(MpiRequests.size());
int ierr = MPI_Waitall(MpiRequests.size(),&MpiRequests[0],&status[0]); // Sends are guaranteed in order. No harm in not completing.
GRID_ASSERT(ierr==0);
}
int ierr = MPI_Waitall(nreq,&MpiRequests[0],&status[0]);
assert(ierr==0);
// for(int r=0;r<nreq;r++){
// if ( list[r].PacketType==InterNodeRecv ) {
// acceleratorCopyToDeviceAsynch(list[r].host_buf,list[r].device_buf,list[r].bytes);
// }
// }
#ifdef GRID_CHECKSUM_COMMS
for(int r=0;r<list.size();r++){
if ( list[r].PacketType == InterNodeReceiveHtoD ) {
uint64_t rbytes_data = list[r].bytes - 8;
uint64_t expected_cs = *(uint64_t*)(((char*)list[r].host_buf) + rbytes_data);
uint64_t computed_cs = checksum_gpu((uint64_t*)list[r].device_buf, rbytes_data / 8) ^ (checksum_index + 1 + 1000 * list[r].tag); //
if (expected_cs != computed_cs) {
// TODO: error message, backtrace, quit
for(int r=0;r<nreq;r++){
if ( list[r].PacketType==InterNodeRecv ) {
acceleratorCopyToDeviceAsynch(list[r].host_buf,list[r].device_buf,list[r].bytes);
fprintf(stderr, "GRID_CHECKSUM_COMMS error:\n");
fprintf(stderr, " processor = %d\n", (int)_processor);
for(int d=0;d<_processors.size();d++)
fprintf(stderr, " processor_coord[%d] = %d\n", d, _processor_coor[d]);
fprintf(stderr, " hostname: %s\n", GridHostname());
fprintf(stderr, " expected_cs: %ld\n", expected_cs);
fprintf(stderr, " computed_cs: %ld\n", computed_cs);
fprintf(stderr, " dest: %d\n", list[r].dest);
fprintf(stderr, " tag: %d\n", list[r].tag);
fprintf(stderr, " commdir: %d\n", list[r].commdir);
fprintf(stderr, " bytes: %ld\n", (uint64_t)list[r].bytes);
fflush(stderr);
// backtrace
int symbols = backtrace(Grid_backtrace_buffer,_NBACKTRACE);
backtrace_symbols_fd(Grid_backtrace_buffer,symbols, 2);
exit(1);
}
}
}
checksum_index += 1;
#endif
acceleratorCopySynchronise(); // Complete all pending copy transfers
list.resize(0); // Delete the list
this->HostBufferFreeAll(); // Clean up the buffer allocs
this->StencilBarrier();
#ifndef NVLINK_GET
this->StencilBarrier(); // if PUT must check our nbrs have filled our receive buffers.
#endif
}
#endif
////////////////////////////////////////////
@@ -715,6 +871,9 @@ void CartesianCommunicator::StencilSendToRecvFromComplete(std::vector<CommsReque
void CartesianCommunicator::StencilBarrier(void)
{
FlightRecorder::StepLog("NodeBarrier");
// Only orders the device-to-device PUT/GET into a peer's window; not needed under --shm-mpi 1.
if ( Stencil_force_mpi && !Stencil_force_barrier ) return;
MPI_Barrier (ShmComm);
}
//void CartesianCommunicator::SendToRecvFromComplete(std::vector<CommsRequest_t> &list)
@@ -722,17 +881,19 @@ void CartesianCommunicator::StencilBarrier(void)
//}
void CartesianCommunicator::Barrier(void)
{
FlightRecorder::StepLog("GridBarrier");
int ierr = MPI_Barrier(communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::Broadcast(int root,void* data, int bytes)
void CartesianCommunicator::Broadcast(int root,void* data,uint64_t bytes)
{
FlightRecorder::StepLog("Broadcast");
int ierr=MPI_Bcast(data,
bytes,
(int)bytes,
MPI_BYTE,
root,
communicator);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
int CartesianCommunicator::RankWorld(void){
int r;
@@ -740,23 +901,25 @@ int CartesianCommunicator::RankWorld(void){
return r;
}
void CartesianCommunicator::BarrierWorld(void){
FlightRecorder::StepLog("BarrierWorld");
int ierr = MPI_Barrier(communicator_world);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::BroadcastWorld(int root,void* data, int bytes)
void CartesianCommunicator::BroadcastWorld(int root,void* data, uint64_t bytes)
{
FlightRecorder::StepLog("BroadcastWorld");
int ierr= MPI_Bcast(data,
bytes,
(int)bytes,
MPI_BYTE,
root,
communicator_world);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void CartesianCommunicator::AllToAll(int dim,void *in,void *out,uint64_t words,uint64_t bytes)
{
Coordinate row(_ndimension,1);
assert(dim>=0 && dim<_ndimension);
GRID_ASSERT(dim>=0 && dim<_ndimension);
// Split the communicator
row[dim] = _processors[dim];
@@ -767,6 +930,7 @@ void CartesianCommunicator::AllToAll(int dim,void *in,void *out,uint64_t words,
}
void CartesianCommunicator::AllToAll(void *in,void *out,uint64_t words,uint64_t bytes)
{
FlightRecorder::StepLog("AllToAll");
// MPI is a pain and uses "int" arguments
// 64*64*64*128*16 == 500Million elements of data.
// When 24*4 bytes multiples get 50x 10^9 >>> 2x10^9 Y2K bug.
@@ -776,12 +940,67 @@ void CartesianCommunicator::AllToAll(void *in,void *out,uint64_t words,uint64_t
int ibytes;
iwords = words;
ibytes = bytes;
assert(words == iwords); // safe to cast to int ?
assert(bytes == ibytes); // safe to cast to int ?
GRID_ASSERT(words == iwords); // safe to cast to int ?
GRID_ASSERT(bytes == ibytes); // safe to cast to int ?
MPI_Type_contiguous(ibytes,MPI_BYTE,&object);
MPI_Type_commit(&object);
MPI_Alltoall(in,iwords,object,out,iwords,object,communicator);
MPI_Type_free(&object);
}
void CartesianCommunicator::AllToAllV(void *in ,const std::vector<int> &sendcounts,const std::vector<int> &senddispls,
void *out,const std::vector<int> &recvcounts,const std::vector<int> &recvdispls,
uint64_t bytes)
{
FlightRecorder::StepLog("AllToAllV");
GRID_ASSERT(sendcounts.size()==(size_t)_Nprocessors);
GRID_ASSERT(senddispls.size()==(size_t)_Nprocessors);
GRID_ASSERT(recvcounts.size()==(size_t)_Nprocessors);
GRID_ASSERT(recvdispls.size()==(size_t)_Nprocessors);
// MPI counts are "int"; the caller sizes the word to keep them in range
int ibytes = bytes;
GRID_ASSERT(bytes == (uint64_t)ibytes);
MPI_Datatype object;
MPI_Type_contiguous(ibytes,MPI_BYTE,&object);
MPI_Type_commit(&object);
int ierr = MPI_Alltoallv(in ,(int *)&sendcounts[0],(int *)&senddispls[0],object,
out,(int *)&recvcounts[0],(int *)&recvdispls[0],object,communicator);
GRID_ASSERT(ierr==0);
MPI_Type_free(&object);
}
void CartesianCommunicator::AllGatherV(void *in ,int sendcount,
void *out,const std::vector<int> &recvcounts,const std::vector<int> &recvdispls,
uint64_t bytes)
{
FlightRecorder::StepLog("AllGatherV");
GRID_ASSERT(recvcounts.size()==(size_t)_Nprocessors);
GRID_ASSERT(recvdispls.size()==(size_t)_Nprocessors);
GRID_ASSERT(recvcounts[_processor]==sendcount);
// MPI counts are "int"; the caller sizes the word to keep them in range
int ibytes = bytes;
GRID_ASSERT(bytes == (uint64_t)ibytes);
MPI_Datatype object;
MPI_Type_contiguous(ibytes,MPI_BYTE,&object);
MPI_Type_commit(&object);
int ierr = MPI_Allgatherv(in ,sendcount,object,
out,(int *)&recvcounts[0],(int *)&recvdispls[0],object,communicator);
GRID_ASSERT(ierr==0);
MPI_Type_free(&object);
}
void CartesianCommunicator::AllGather(void *in,void *out,uint64_t words,uint64_t bytes)
{
FlightRecorder::StepLog("AllGather");
int iwords = words;
int ibytes = bytes;
GRID_ASSERT(words == (uint64_t)iwords);
GRID_ASSERT(bytes == (uint64_t)ibytes);
MPI_Datatype object;
MPI_Type_contiguous(ibytes,MPI_BYTE,&object);
MPI_Type_commit(&object);
int ierr = MPI_Allgather(in,iwords,object,out,iwords,object,communicator);
GRID_ASSERT(ierr==0);
MPI_Type_free(&object);
}
NAMESPACE_END(Grid);
+50 -15
View File
@@ -27,6 +27,8 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
/* END LEGAL */
#include <Grid/GridCore.h>
void GridAbort(void) { abort(); }
NAMESPACE_BEGIN(Grid);
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34,6 +36,7 @@ NAMESPACE_BEGIN(Grid);
///////////////////////////////////////////////////////////////////////////////////////////////////
Grid_MPI_Comm CartesianCommunicator::communicator_world;
void CartesianCommunicator::Init(int *argc, char *** arv)
{
GlobalSharedMemory::Init(communicator_world);
@@ -54,14 +57,14 @@ CartesianCommunicator::CartesianCommunicator(const Coordinate &processors)
{
_shm_processors = Coordinate(processors.size(),1);
_processors = processors;
_ndimension = processors.size(); assert(_ndimension>=1);
_ndimension = processors.size(); GRID_ASSERT(_ndimension>=1);
_processor_coor.resize(_ndimension);
// Require 1^N processor grid for fake
_Nprocessors=1;
_processor = 0;
for(int d=0;d<_ndimension;d++) {
assert(_processors[d]==1);
GRID_ASSERT(_processors[d]==1);
_processor_coor[d] = 0;
}
SetCommunicator(communicator_world);
@@ -87,19 +90,19 @@ void CartesianCommunicator::SendToRecvFrom(void *xmit,
int dest,
void *recv,
int from,
int bytes)
uint64_t bytes)
{
assert(0);
GRID_ASSERT(0);
}
void CartesianCommunicator::CommsComplete(std::vector<CommsRequest_t> &list){ assert(0);}
void CartesianCommunicator::CommsComplete(std::vector<CommsRequest_t> &list){ GRID_ASSERT(list.size()==0);}
void CartesianCommunicator::SendToRecvFromBegin(std::vector<CommsRequest_t> &list,
void *xmit,
int dest,
void *recv,
int from,
int bytes,int dir)
uint64_t bytes,int dir)
{
assert(0);
GRID_ASSERT(0);
}
void CartesianCommunicator::AllToAll(int dim,void *in,void *out,uint64_t words,uint64_t bytes)
@@ -110,11 +113,39 @@ void CartesianCommunicator::AllToAll(void *in,void *out,uint64_t words,uint64_t
{
bcopy(in,out,bytes*words);
}
void CartesianCommunicator::AllToAllV(void *in ,const std::vector<int> &sendcounts,const std::vector<int> &senddispls,
void *out,const std::vector<int> &recvcounts,const std::vector<int> &recvdispls,
uint64_t bytes)
{
// Single rank: the exchange degenerates to a copy of our own segment
GRID_ASSERT(sendcounts.size()==1);
GRID_ASSERT(recvcounts.size()==1);
GRID_ASSERT(sendcounts[0]==recvcounts[0]);
bcopy((char *)in +(uint64_t)senddispls[0]*bytes,
(char *)out+(uint64_t)recvdispls[0]*bytes,bytes*(uint64_t)sendcounts[0]);
}
void CartesianCommunicator::AllGatherV(void *in ,int sendcount,
void *out,const std::vector<int> &recvcounts,const std::vector<int> &recvdispls,
uint64_t bytes)
{
// Single rank: the gather degenerates to a copy of our own contribution
GRID_ASSERT(recvcounts.size()==1);
GRID_ASSERT(recvdispls.size()==1);
GRID_ASSERT(recvcounts[0]==sendcount);
bcopy((char *)in,
(char *)out+(uint64_t)recvdispls[0]*bytes,bytes*(uint64_t)sendcount);
}
void CartesianCommunicator::AllGather(void *in,void *out,uint64_t words,uint64_t bytes)
{
bcopy((char *)in,(char *)out,bytes*words);
}
int CartesianCommunicator::RankWorld(void){return 0;}
void CartesianCommunicator::Barrier(void){}
void CartesianCommunicator::Broadcast(int root,void* data, int bytes) {}
void CartesianCommunicator::BroadcastWorld(int root,void* data, int bytes) { }
void CartesianCommunicator::Broadcast(int root,void* data, uint64_t bytes) {}
void CartesianCommunicator::BroadcastWorld(int root,void* data, uint64_t bytes) { }
void CartesianCommunicator::BarrierWorld(void) { }
int CartesianCommunicator::RankFromProcessorCoor(Coordinate &coor) { return 0;}
void CartesianCommunicator::ProcessorCoorFromRank(int rank, Coordinate &coor){ coor = _processor_coor; }
@@ -124,29 +155,33 @@ void CartesianCommunicator::ShiftedRanks(int dim,int shift,int &source,int &dest
dest=0;
}
int CartesianCommunicator::IsOffNode(int rank) { return false; }
double CartesianCommunicator::StencilSendToRecvFrom( void *xmit,
int xmit_to_rank,int dox,
void *recv,
int recv_from_rank,int dor,
int bytes, int dir)
uint64_t bytes, int dir)
{
return 2.0*bytes;
}
void CartesianCommunicator::StencilSendToRecvFromPollIRecv(std::vector<CommsRequest_t> &list) {};
void CartesianCommunicator::StencilSendToRecvFromPollDtoH(std::vector<CommsRequest_t> &list) {};
double CartesianCommunicator::StencilSendToRecvFromPrepare(std::vector<CommsRequest_t> &list,
void *xmit,
int xmit_to_rank,int dox,
void *recv,
int recv_from_rank,int dor,
int xbytes,int rbytes, int dir)
uint64_t xbytes,uint64_t rbytes, int dir)
{
return xbytes+rbytes;
return 0.0;
}
double CartesianCommunicator::StencilSendToRecvFromBegin(std::vector<CommsRequest_t> &list,
void *xmit,
void *xmit, void *xmit_comp,
int xmit_to_rank,int dox,
void *recv,
void *recv, void *recv_comp,
int recv_from_rank,int dor,
int xbytes,int rbytes, int dir)
uint64_t xbytes,uint64_t rbytes, int dir)
{
return xbytes+rbytes;
}
+254
View File
@@ -0,0 +1,254 @@
/*************************************************************************************
Grid physics library, www.github.com/paboyle/Grid
Source file: ./Grid/communicator/RingAllReduce.h
Copyright (C) 2026
Author: Peter Boyle <pboyle@bnl.gov>
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 */
#pragma once
NAMESPACE_BEGIN(Grid);
/////////////////////////////////////////////////////////////////////////////
// Vector all-reduce on point-to-point only (SendToRecvFrom), no MPI
// collectives: reduce-scatter ring followed by all-gather ring. A
// communicator-level primitive: needs only CartesianCommunicator.
//
// RingAllReduce(comm, buf, n) flat ring over all P ranks
// CartesianRingAllReduce(comm, buf, n, orthogDim=-1)
// ring along each processor dimension
// in turn (P_d ranks per ring).
// orthogDim in 0..Nd-1: that dimension is
// SKIPPED, so the result is the sum over
// all ranks sharing my coordinate in it
// (e.g. orthogDim=3: sum over each
// time-slice of processors separately).
// -1: all dimensions (full allreduce).
//
// Why: Cray MPICH device-buffer MPI_Allreduce aborts above ~8 MB (MPI_FLOAT,
// measured 4.4 MB pass / 13.3 MB fail) and delivers 5.8 GB/s where P2P rings
// deliver ~17 GB/s. A ring has no size cliff -- every step is one symmetric
// SendToRecvFrom of one chunk.
//
// Cost model, N bytes per rank:
// flat : 2(P-1) steps, 2N(P-1)/P bytes per rank -- bandwidth-optimal,
// latency 2(P-1) x step (P=288: 574 steps)
// cartesian : sum_d 2(P_d-1) steps, ~2N per dimension -- few steps, each on
// a neighbour link (3.6.4.4: 26 steps, ~8N bytes)
// Rule of thumb: cartesian for tens of MB, flat for hundreds of MB.
//
// Buffer memory space: whatever SendToRecvFrom accepts on this build (device
// under ACCELERATOR_AWARE_MPI); the reduction runs as accelerator_for on a
// deviceVector working copy, so the caller's buffer is only ever memcpy'd.
// Deterministic: summation order is fixed by rank and chunk index, so the
// result is bitwise reproducible run to run (MPI_Allreduce need not be),
// though it differs from MPI's order at rounding level.
//
// n elements of T; T must support + on the accelerator (RealF/RealD,
// ComplexF/ComplexD). sizeof(T)*chunk must be a multiple of 4 bytes
// (SendToRecvFrom counts int32 words); true for all supported T.
/////////////////////////////////////////////////////////////////////////////
// Ring all-reduce among P ranks with given next/prev neighbours; `me` is this
// rank's position in the ring. work has P*c elements, scratch has c.
template<class T>
void RingAllReduceCore(CartesianCommunicator *comm,
T *work, T *scratch, uint64_t c, int P, int me, int next, int prev)
{
if ( P==1 ) return;
uint64_t bytes = c*sizeof(T);
// reduce-scatter: after P-1 steps rank me owns fully reduced chunk (me+1)%P
for(int s=0;s<P-1;s++){
int sendc = (me - s + 2*P) % P;
int recvc = (me - s - 1 + 2*P) % P;
comm->SendToRecvFrom((void *)&work[sendc*c], next, (void *)scratch, prev, bytes);
T *dst = &work[recvc*c];
accelerator_for(i, c, 1, { dst[i] = dst[i] + scratch[i]; });
}
// all-gather: circulate the reduced chunks
for(int s=0;s<P-1;s++){
int sendc = (me - s + 1 + 2*P) % P;
int recvc = (me - s + 2*P) % P;
comm->SendToRecvFrom((void *)&work[sendc*c], next, (void *)&work[recvc*c], prev, bytes);
}
}
template<class T>
void RingAllReduce(CartesianCommunicator *comm, T *buf, uint64_t n)
{
int P = comm->ProcessorCount();
int me = comm->ThisRank();
if ( P==1 || n==0 ) return;
uint64_t c = (n + P - 1)/P;
deviceVector<T> work(c*P);
deviceVector<T> scratch(c);
T *w = &work[0];
accelerator_for(i, c*P, 1, { w[i] = T(0.0); });
acceleratorCopyDeviceToDevice((void *)buf, (void *)w, n*sizeof(T));
RingAllReduceCore(comm, w, &scratch[0], c, P, me, (me+1)%P, (me+P-1)%P);
acceleratorCopyDeviceToDevice((void *)w, (void *)buf, n*sizeof(T));
}
template<class T>
void CartesianRingAllReduce(CartesianCommunicator *comm, T *buf, uint64_t n, int orthogDim=-1)
{
if ( comm->ProcessorCount()==1 || n==0 ) return;
int Nd = comm->_ndimension;
GRID_ASSERT( orthogDim >= -1 && orthogDim < Nd );
for(int d=0;d<Nd;d++){
if ( d==orthogDim ) continue; // leave this dimension unsummed
int P = comm->_processors[d];
if ( P==1 ) continue;
int me = comm->_processor_coor[d];
int next, prev;
comm->ShiftedRanks(d, 1, prev, next); // (dim, shift, source, dest)
uint64_t c = (n + P - 1)/P;
deviceVector<T> work(c*P);
deviceVector<T> scratch(c);
T *w = &work[0];
accelerator_for(i, c*P, 1, { w[i] = T(0.0); });
acceleratorCopyDeviceToDevice((void *)buf, (void *)w, n*sizeof(T));
RingAllReduceCore(comm, w, &scratch[0], c, P, me, next, prev);
acceleratorCopyDeviceToDevice((void *)w, (void *)buf, n*sizeof(T));
}
}
/////////////////////////////////////////////////////////////////////////////
// Cartesian ring ALLGATHER, point-to-point only.
//
// CartesianRingAllGather(comm, buf, chunk)
// buf holds P*chunk elements of T. Block index = the Grid LEXICOGRAPHIC
// index of the owning process coordinate (dimension 0 fastest,
// Lexicographic::CoorFromIndex convention), NOT the MPI rank: on entry my
// chunk is at buf[CartesianLexIndex(comm)*chunk]; on exit block L is the
// chunk of the process at coordinate CoorFromIndex(L). Map to ranks with
// comm->RankFromProcessorCoor. (Ranks and coordinates are NOT related
// lexicographically on Frontier -- the OptimalCommunicator relabels ranks
// for shared-memory locality; assuming rank order gave a wrong inverse,
// VERIFY 0.9965, 2026-08-26.)
//
// Dimension by dimension from dimension 0 (fastest) upward: each stage is a
// ring over the P_d ranks of that line, after which the held block is the
// concatenation over that coordinate in the lexicographic nesting. Bytes
// sent per rank ~N = P*chunk in total (dominated by the last stage): 8x less
// than a zero-padded CartesianRingAllReduce. Steps: sum_d (P_d-1). Exact.
//
// Written for the dense coarse-coarse apply (every rank owns rows of A^{-1}
// and needs the whole x): 1.86 ms with the allreduce ring at 288 ranks was
// wire speed but moved 35 MB per rank to deliver 4.4.
/////////////////////////////////////////////////////////////////////////////
inline int CartesianLexIndex(CartesianCommunicator *comm)
{
int idx=0, stride=1;
for(int d=0; d<(int)comm->_ndimension; d++){ idx += comm->_processor_coor[d]*stride; stride *= comm->_processors[d]; }
return idx;
}
//
// CartesianRingAllGather(comm, buf, chunk, dim) dim in 0..Nd-1: gather along
// ONE processor dimension only. buf holds P_dim*chunk elements; on entry my
// chunk is at buf[coor[dim]*chunk], on exit block c is the chunk of the rank
// at coordinate c along dim with all other coordinates equal to mine. Every
// rank of the line holds the same result (e.g. dim=3 after a
// CartesianRingAllReduce(orthogDim=3): the boss of each spatial line can then
// write the P_t-times-longer vector, not the P-times-longer one).
/////////////////////////////////////////////////////////////////////////////
template<class T>
void CartesianRingAllGather(CartesianCommunicator *comm, T *buf, uint64_t chunk, int dim=-1)
{
int P = comm->ProcessorCount();
int Nd = comm->_ndimension;
GRID_ASSERT( dim >= -1 && dim < Nd );
if ( dim >= 0 ) P = comm->_processors[dim]; // ranks in my line along dim
if ( P==1 || chunk==0 ) return;
int mylex = (dim<0) ? CartesianLexIndex(comm) : comm->_processor_coor[dim];
deviceVector<T> work((uint64_t)P*chunk);
// ping-pong between buf and work; the held block lives at offset `off` in `cur`
T *cur = buf; uint64_t off = (uint64_t)mylex*chunk;
T *oth = &work[0];
uint64_t blk = chunk; // elements in the held block
for(int d=0; d<Nd; d++){ // dimension 0 first: it is the fastest lex index
if ( dim>=0 && d!=dim ) continue; // single-dimension gather
int Pd = comm->_processors[d];
if ( Pd==1 ) continue;
int med = comm->_processor_coor[d];
int next, prev;
comm->ShiftedRanks(d, 1, prev, next); // (dim, shift, source, dest)
GRID_ASSERT( (blk*sizeof(T))%4 == 0 );
acceleratorCopyDeviceToDevice((void *)(cur+off), (void *)(oth+(uint64_t)med*blk), blk*sizeof(T));
for(int t=1;t<Pd;t++){
int sendslot = (med - t + 1 + Pd) % Pd;
int recvslot = (med - t + Pd) % Pd;
comm->SendToRecvFrom((void *)(oth+(uint64_t)sendslot*blk), next,
(void *)(oth+(uint64_t)recvslot*blk), prev, blk*sizeof(T));
}
T *tmp = cur; cur = oth; oth = tmp; off = 0;
blk *= Pd;
}
GRID_ASSERT( blk == (uint64_t)P*chunk );
if ( cur != buf ) acceleratorCopyDeviceToDevice((void *)cur, (void *)buf, blk*sizeof(T));
}
/////////////////////////////////////////////////////////////////////////////
// Cartesian ALL-TO-ALL along one processor dimension, point-to-point only.
//
// CartesianRingAllToAll(comm, sbuf, rbuf, chunk, dim)
// sbuf, rbuf each hold P_dim*chunk elements of T. Block index is the
// process COORDINATE along dim (not the MPI rank -- ShiftedRanks resolves
// the OptimalCommunicator relabelling): sbuf[c*chunk] is my data destined
// for the rank at coordinate c along dim; on exit rbuf[c*chunk] is the
// block that rank sent me. All other coordinates equal mine.
//
// Direct-pairwise, NOT ring-forwarding: P_dim-1 symmetric exchanges, step k
// with the +/-k neighbours (ShiftedRanks == MPI_Cart_shift, arbitrary shift),
// each moving one chunk with no store-and-forward. Per-rank traffic
// (P_dim-1)*chunk -- a factor P below the AllGather's replicated P*chunk, which
// is the point for the pencil-FFT transpose (partition the orthogonal coords
// rather than replicate the whole transform line on every rank).
//
// On a switched fabric a distance-k send is one logical hop, so direct-pairwise
// moves the minimum; ring-forwarding would only win on a true physical ring.
// Every step is one SendToRecvFrom of one chunk: no collective, no size cliff.
// Send/recv-rank binding follows Cshift_mpi.h: ShiftedRanks(dim,k,xmit,recv)
// sends to coord-k, receives from coord+k. Steps: P_dim-1. Exact.
/////////////////////////////////////////////////////////////////////////////
template<class T>
void CartesianRingAllToAll(CartesianCommunicator *comm,
T *sbuf, T *rbuf, uint64_t chunk, int dim)
{
int Nd = comm->_ndimension;
GRID_ASSERT( dim >= 0 && dim < Nd );
int P = comm->_processors[dim];
int me = comm->_processor_coor[dim];
if ( chunk==0 ) return;
GRID_ASSERT( (chunk*sizeof(T))%4 == 0 ); // SendToRecvFrom counts int32 words
uint64_t bytes = chunk*sizeof(T);
// my own block never goes on the wire
acceleratorCopyDeviceToDevice((void *)&sbuf[(uint64_t)me*chunk],
(void *)&rbuf[(uint64_t)me*chunk], bytes);
if ( P==1 ) return;
for(int k=1;k<P;k++){
int xmit_to_rank, recv_from_rank;
comm->ShiftedRanks(dim, k, xmit_to_rank, recv_from_rank); // xmit=coord-k, recv=coord+k
uint64_t sidx = (uint64_t)((me - k + P) % P); // block destined for the coord-k rank
uint64_t ridx = (uint64_t)((me + k) % P); // block arriving from the coord+k rank
comm->SendToRecvFrom((void *)&sbuf[sidx*chunk], xmit_to_rank,
(void *)&rbuf[ridx*chunk], recv_from_rank, bytes);
}
}
NAMESPACE_END(Grid);
+7 -7
View File
@@ -58,8 +58,8 @@ int GlobalSharedMemory::WorldNode;
void GlobalSharedMemory::SharedMemoryFree(void)
{
assert(_ShmAlloc);
assert(_ShmAllocBytes>0);
GRID_ASSERT(_ShmAlloc);
GRID_ASSERT(_ShmAllocBytes>0);
for(int r=0;r<WorldShmSize;r++){
munmap(WorldShmCommBufs[r],_ShmAllocBytes);
}
@@ -80,7 +80,7 @@ void *SharedMemory::HostBufferMalloc(size_t bytes){
std::cout<< " Current alloc is " << (bytes/(1024*1024)) <<"MB"<<std::endl;
std::cout<< " Current bytes is " << (host_heap_bytes/(1024*1024)) <<"MB"<<std::endl;
std::cout<< " Current heap is " << (host_heap_size/(1024*1024)) <<"MB"<<std::endl;
assert(host_heap_bytes<host_heap_size);
GRID_ASSERT(host_heap_bytes<host_heap_size);
}
return ptr;
}
@@ -100,7 +100,7 @@ void *SharedMemory::ShmBufferMalloc(size_t bytes){
std::cout<< " Current alloc is " << (bytes/(1024*1024)) <<"MB"<<std::endl;
std::cout<< " Current bytes is " << (heap_bytes/(1024*1024)) <<"MB"<<std::endl;
std::cout<< " Current heap is " << (heap_size/(1024*1024)) <<"MB"<<std::endl;
assert(heap_bytes<heap_size);
GRID_ASSERT(heap_bytes<heap_size);
}
//std::cerr << "ShmBufferMalloc "<<std::hex<< ptr<<" - "<<((uint64_t)ptr+bytes)<<std::dec<<std::endl;
return ptr;
@@ -127,13 +127,13 @@ void GlobalSharedMemory::GetShmDims(const Coordinate &WorldDims,Coordinate &ShmD
if ( str ) {
std::vector<int> IntShmDims;
GridCmdOptionIntVector(std::string(str),IntShmDims);
assert(IntShmDims.size() == WorldDims.size());
GRID_ASSERT(IntShmDims.size() == WorldDims.size());
long ShmSize = 1;
for (int dim=0;dim<WorldDims.size();dim++) {
ShmSize *= (ShmDims[dim] = IntShmDims[dim]);
assert(divides(ShmDims[dim],WorldDims[dim]));
GRID_ASSERT(divides(ShmDims[dim],WorldDims[dim]));
}
assert(ShmSize == WorldShmSize);
GRID_ASSERT(ShmSize == WorldShmSize);
return;
}
+20 -2
View File
@@ -50,12 +50,30 @@ typedef MPI_Request MpiCommsRequest_t;
#ifdef ACCELERATOR_AWARE_MPI
typedef MPI_Request CommsRequest_t;
#else
enum PacketType_t { InterNodeXmit, InterNodeRecv, IntraNodeXmit, IntraNodeRecv };
/*
* Enable state transitions as each packet flows.
*/
enum PacketType_t {
FaceGather,
InterNodeXmit,
InterNodeRecv,
IntraNodeXmit,
IntraNodeRecv,
InterNodeXmitISend,
InterNodeReceiveHtoD
};
/*
*Package arguments needed for various actions along packet flow
*/
typedef struct {
PacketType_t PacketType;
void *host_buf;
void *device_buf;
int dest;
int tag;
int commdir;
unsigned long bytes;
acceleratorEvent_t ev;
MpiCommsRequest_t req;
} CommsRequest_t;
#endif
@@ -119,7 +137,7 @@ public:
///////////////////////////////////////////////////
static void SharedMemoryAllocate(uint64_t bytes, int flags);
static void SharedMemoryFree(void);
static void SharedMemoryCopy(void *dest,void *src,size_t bytes);
// static void SharedMemoryCopy(void *dest,void *src,size_t bytes);
static void SharedMemoryZero(void *dest,size_t bytes);
};
+68 -101
View File
@@ -43,10 +43,6 @@ Author: Christoph Lehner <christoph@lhnr.de>
#define GRID_SYCL_LEVEL_ZERO_IPC
#define SHM_SOCKETS
#else
#ifdef HAVE_NUMAIF_H
#warning " Using NUMAIF "
#include <numaif.h>
#endif
#endif
#include <syscall.h>
#endif
@@ -71,7 +67,7 @@ public:
{
int errnum;
sock = socket(AF_UNIX, SOCK_DGRAM, 0); assert(sock>0);
sock = socket(AF_UNIX, SOCK_DGRAM, 0); GRID_ASSERT(sock>0);
struct sockaddr_un sa_un = { 0 };
sa_un.sun_family = AF_UNIX;
@@ -162,7 +158,7 @@ public:
/*Construct from an MPI communicator*/
void GlobalSharedMemory::Init(Grid_MPI_Comm comm)
{
assert(_ShmSetup==0);
GRID_ASSERT(_ShmSetup==0);
WorldComm = comm;
MPI_Comm_rank(WorldComm,&WorldRank);
MPI_Comm_size(WorldComm,&WorldSize);
@@ -188,7 +184,7 @@ void GlobalSharedMemory::Init(Grid_MPI_Comm comm)
// WorldNodes
WorldNodes = WorldSize/WorldShmSize;
assert( (WorldNodes * WorldShmSize) == WorldSize );
GRID_ASSERT( (WorldNodes * WorldShmSize) == WorldSize );
// FIXME: Check all WorldShmSize are the same ?
@@ -213,7 +209,7 @@ void GlobalSharedMemory::Init(Grid_MPI_Comm comm)
MyGroup.resize(WorldShmSize);
for(int rank=0;rank<WorldSize;rank++){
if(WorldShmRanks[rank]!=MPI_UNDEFINED){
assert(g<WorldShmSize);
GRID_ASSERT(g<WorldShmSize);
MyGroup[g++] = rank;
}
}
@@ -229,7 +225,7 @@ void GlobalSharedMemory::Init(Grid_MPI_Comm comm)
// global sum leaders over comm world
///////////////////////////////////////////////////////////////////
int ierr=MPI_Allreduce(MPI_IN_PLACE,&leaders_1hot[0],WorldSize,MPI_INT,MPI_SUM,WorldComm);
assert(ierr==0);
GRID_ASSERT(ierr==0);
///////////////////////////////////////////////////////////////////
// find the group leaders world rank
@@ -250,7 +246,7 @@ void GlobalSharedMemory::Init(Grid_MPI_Comm comm)
WorldNode=g;
}
}
assert(WorldNode!=-1);
GRID_ASSERT(WorldNode!=-1);
_ShmSetup=1;
}
// Gray encode support
@@ -292,7 +288,7 @@ void GlobalSharedMemory::OptimalCommunicatorHypercube(const Coordinate &processo
// Assert power of two shm_size.
////////////////////////////////////////////////////////////////
int log2size = Log2Size(WorldShmSize,MAXLOG2RANKSPERNODE);
assert(log2size != -1);
GRID_ASSERT(log2size != -1);
////////////////////////////////////////////////////////////////
// Identify the hypercube coordinate of this node using hostname
@@ -313,7 +309,7 @@ void GlobalSharedMemory::OptimalCommunicatorHypercube(const Coordinate &processo
// Parse ICE-XA hostname to get hypercube location
gethostname(name,namelen);
int nscan = sscanf(name,"r%di%dn%d",&R,&I,&N) ;
assert(nscan==3);
GRID_ASSERT(nscan==3);
int nlo = N%9;
int nhi = N/9;
@@ -337,8 +333,8 @@ void GlobalSharedMemory::OptimalCommunicatorHypercube(const Coordinate &processo
//////////////////////////////////////////////////////////////////
MPI_Bcast(&rootcoor, sizeof(rootcoor), MPI_BYTE, 0, WorldComm);
hypercoor=hypercoor-rootcoor;
assert(hypercoor<WorldSize);
assert(hypercoor>=0);
GRID_ASSERT(hypercoor<WorldSize);
GRID_ASSERT(hypercoor>=0);
//////////////////////////////////////
// Printing
@@ -386,7 +382,7 @@ void GlobalSharedMemory::OptimalCommunicatorHypercube(const Coordinate &processo
for(int i=0;i<ndimension;i++){
Nprocessors*=processors[i];
}
assert(WorldSize==Nprocessors);
GRID_ASSERT(WorldSize==Nprocessors);
////////////////////////////////////////////////////////////////
// Establish mapping between lexico physics coord and WorldRank
@@ -405,7 +401,7 @@ void GlobalSharedMemory::OptimalCommunicatorHypercube(const Coordinate &processo
// Build the new communicator
/////////////////////////////////////////////////////////////////
int ierr= MPI_Comm_split(WorldComm,0,rank,&optimal_comm);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
void GlobalSharedMemory::OptimalCommunicatorSharedMemory(const Coordinate &processors,Grid_MPI_Comm & optimal_comm,Coordinate &SHM)
{
@@ -435,7 +431,8 @@ void GlobalSharedMemory::OptimalCommunicatorSharedMemory(const Coordinate &proce
for(int i=0;i<ndimension;i++){
Nprocessors*=processors[i];
}
assert(WorldSize==Nprocessors);
// std::cerr << " WorldSize "<<WorldSize << " Nprocessors "<<Nprocessors<<" "<<processors<<std::endl;
GRID_ASSERT(WorldSize==Nprocessors);
////////////////////////////////////////////////////////////////
// Establish mapping between lexico physics coord and WorldRank
@@ -451,7 +448,7 @@ void GlobalSharedMemory::OptimalCommunicatorSharedMemory(const Coordinate &proce
// Build the new communicator
/////////////////////////////////////////////////////////////////
int ierr= MPI_Comm_split(WorldComm,0,rank,&optimal_comm);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
////////////////////////////////////////////////////////////////////////////////////////////
// SHMGET
@@ -460,8 +457,8 @@ void GlobalSharedMemory::OptimalCommunicatorSharedMemory(const Coordinate &proce
void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
{
std::cout << Mheader "SharedMemoryAllocate "<< bytes<< " shmget implementation "<<std::endl;
assert(_ShmSetup==1);
assert(_ShmAlloc==0);
GRID_ASSERT(_ShmSetup==1);
GRID_ASSERT(_ShmAlloc==0);
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// allocate the shared windows for our group
@@ -522,8 +519,8 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
{
void * ShmCommBuf ;
assert(_ShmSetup==1);
assert(_ShmAlloc==0);
GRID_ASSERT(_ShmSetup==1);
GRID_ASSERT(_ShmAlloc==0);
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// allocate the pointer array for shared windows for our group
@@ -542,50 +539,22 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
// Each MPI rank should allocate our own buffer
///////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef ACCELERATOR_AWARE_MPI
printf("Host buffer allocate for GPU non-aware MPI\n");
#if 0
HostCommBuf= acceleratorAllocHost(bytes);
#else
// printf("Host buffer allocate for GPU non-aware MPI\n");
HostCommBuf= malloc(bytes); /// CHANGE THIS TO malloc_host
#ifdef HAVE_NUMAIF_H
#warning "Moving host buffers to specific NUMA domain"
int numa;
char *numa_name=(char *)getenv("MPI_BUF_NUMA");
if(numa_name) {
unsigned long page_size = sysconf(_SC_PAGESIZE);
numa = atoi(numa_name);
unsigned long page_count = bytes/page_size;
std::vector<void *> pages(page_count);
std::vector<int> nodes(page_count,numa);
std::vector<int> status(page_count,-1);
for(unsigned long p=0;p<page_count;p++){
pages[p] =(void *) ((uint64_t) HostCommBuf + p*page_size);
}
int ret = move_pages(0,
page_count,
&pages[0],
&nodes[0],
&status[0],
MPOL_MF_MOVE);
printf("Host buffer move to numa domain %d : move_pages returned %d\n",numa,ret);
if (ret) perror(" move_pages failed for reason:");
}
#endif
acceleratorPin(HostCommBuf,bytes);
#endif
#endif
ShmCommBuf = acceleratorAllocDevice(bytes);
if (ShmCommBuf == (void *)NULL ) {
std::cerr << " SharedMemoryMPI.cc acceleratorAllocDevice failed NULL pointer for " << bytes<<" bytes " << std::endl;
std::cerr << "SharedMemoryMPI.cc acceleratorAllocDevice failed NULL pointer for " << bytes<<" bytes " << std::endl;
exit(EXIT_FAILURE);
}
if ( WorldRank == 0 ){
std::cout << WorldRank << Mheader " SharedMemoryMPI.cc acceleratorAllocDevice "<< bytes
std::cout << Mheader " acceleratorAllocDevice "<< bytes
<< "bytes at "<< std::hex<< ShmCommBuf << " - "<<(bytes-1+(uint64_t)ShmCommBuf) <<std::dec<<" for comms buffers " <<std::endl;
}
SharedMemoryZero(ShmCommBuf,bytes);
std::cout<< "Setting up IPC"<<std::endl;
if ( WorldRank == 0 ){
std::cout<< Mheader "Setting up IPC"<<std::endl;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Loop over ranks/gpu's on our node
///////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -616,8 +585,6 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
if ( err != ZE_RESULT_SUCCESS ) {
std::cerr << "SharedMemoryMPI.cc zeMemGetIpcHandle failed for rank "<<r<<" "<<std::hex<<err<<std::dec<<std::endl;
exit(EXIT_FAILURE);
} else {
std::cout << "SharedMemoryMPI.cc zeMemGetIpcHandle succeeded for rank "<<r<<" "<<std::hex<<err<<std::dec<<std::endl;
}
memcpy((void *)&handle.fd,(void *)&ihandle,sizeof(int));
handle.pid = getpid();
@@ -662,7 +629,7 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
MPI_BYTE,
r,
WorldShmComm);
assert(ierr==0);
GRID_ASSERT(ierr==0);
}
///////////////////////////////////////////////////////////////
@@ -676,12 +643,12 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
#ifdef SHM_SOCKETS
myfd=UnixSockets::RecvFileDescriptor();
#else
std::cout<<"mapping seeking remote pid/fd "
<<handle.pid<<"/"
<<handle.fd<<std::endl;
// std::cout<<"mapping seeking remote pid/fd "
// <<handle.pid<<"/"
// <<handle.fd<<std::endl;
int pidfd = syscall(SYS_pidfd_open,handle.pid,0);
std::cout<<"Using IpcHandle pidfd "<<pidfd<<"\n";
// std::cout<<"Using IpcHandle pidfd "<<pidfd<<"\n";
// int myfd = syscall(SYS_pidfd_getfd,pidfd,handle.fd,0);
myfd = syscall(438,pidfd,handle.fd,0);
int err_t = errno;
@@ -691,7 +658,7 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
assert(0);
}
#endif
std::cout<<"Using IpcHandle mapped remote pid "<<handle.pid <<" FD "<<handle.fd <<" to myfd "<<myfd<<"\n";
// std::cout<<"Using IpcHandle mapped remote pid "<<handle.pid <<" FD "<<handle.fd <<" to myfd "<<myfd<<"\n";
memcpy((void *)&ihandle,(void *)&handle.ze,sizeof(ihandle));
memcpy((void *)&ihandle,(void *)&myfd,sizeof(int));
@@ -700,11 +667,8 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
std::cerr << "SharedMemoryMPI.cc "<<zeContext<<" "<<zeDevice<<std::endl;
std::cerr << "SharedMemoryMPI.cc zeMemOpenIpcHandle failed for rank "<<r<<" "<<std::hex<<err<<std::dec<<std::endl;
exit(EXIT_FAILURE);
} else {
std::cout << "SharedMemoryMPI.cc zeMemOpenIpcHandle succeeded for rank "<<r<<std::endl;
std::cout << "SharedMemoryMPI.cc zeMemOpenIpcHandle pointer is "<<std::hex<<thisBuf<<std::dec<<std::endl;
}
assert(thisBuf!=nullptr);
GRID_ASSERT(thisBuf!=nullptr);
}
#endif
#ifdef GRID_CUDA
@@ -745,8 +709,8 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
{
std::cout << Mheader "SharedMemoryAllocate "<< bytes<< " MMAP implementation "<< GRID_SHM_PATH <<std::endl;
assert(_ShmSetup==1);
assert(_ShmAlloc==0);
GRID_ASSERT(_ShmSetup==1);
GRID_ASSERT(_ShmAlloc==0);
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// allocate the shared windows for our group
//////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -776,13 +740,14 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
void *ptr = (void *) mmap(NULL, bytes, PROT_READ | PROT_WRITE, mmap_flag,fd, 0);
if ( ptr == (void *)MAP_FAILED ) {
printf("mmap %s failed\n",shm_name);
perror("failed mmap"); assert(0);
perror("failed mmap"); GRID_ASSERT(0);
}
assert(((uint64_t)ptr&0x3F)==0);
GRID_ASSERT(((uint64_t)ptr&0x3F)==0);
close(fd);
WorldShmCommBufs[r] =ptr;
// std::cout << Mheader "Set WorldShmCommBufs["<<r<<"]="<<ptr<< "("<< bytes<< "bytes)"<<std::endl;
}
std::cout<< Mheader " Intra-node IPC setup is complete "<<std::endl;
_ShmAlloc=1;
_ShmAllocBytes = bytes;
};
@@ -792,8 +757,8 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
{
std::cout << Mheader "SharedMemoryAllocate "<< bytes<< " MMAP anonymous implementation "<<std::endl;
assert(_ShmSetup==1);
assert(_ShmAlloc==0);
GRID_ASSERT(_ShmSetup==1);
GRID_ASSERT(_ShmAlloc==0);
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// allocate the shared windows for our group
//////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -804,7 +769,7 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
// Hugetlbf and others map filesystems as mappable huge pages
////////////////////////////////////////////////////////////////////////////////////////////
char shm_name [NAME_MAX];
assert(WorldShmSize == 1);
GRID_ASSERT(WorldShmSize == 1);
for(int r=0;r<WorldShmSize;r++){
int fd=-1;
@@ -818,9 +783,9 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
void *ptr = (void *) mmap(NULL, bytes, PROT_READ | PROT_WRITE, mmap_flag,fd, 0);
if ( ptr == (void *)MAP_FAILED ) {
printf("mmap %s failed\n",shm_name);
perror("failed mmap"); assert(0);
perror("failed mmap"); GRID_ASSERT(0);
}
assert(((uint64_t)ptr&0x3F)==0);
GRID_ASSERT(((uint64_t)ptr&0x3F)==0);
close(fd);
WorldShmCommBufs[r] =ptr;
// std::cout << "Set WorldShmCommBufs["<<r<<"]="<<ptr<< "("<< bytes<< "bytes)"<<std::endl;
@@ -839,8 +804,8 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
{
std::cout << Mheader "SharedMemoryAllocate "<< bytes<< " SHMOPEN implementation "<<std::endl;
assert(_ShmSetup==1);
assert(_ShmAlloc==0);
GRID_ASSERT(_ShmSetup==1);
GRID_ASSERT(_ShmAlloc==0);
MPI_Barrier(WorldShmComm);
WorldShmCommBufs.resize(WorldShmSize);
@@ -871,7 +836,7 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
perror("failed mmap");
assert(0);
}
assert(((uint64_t)ptr&0x3F)==0);
GRID_ASSERT(((uint64_t)ptr&0x3F)==0);
WorldShmCommBufs[r] =ptr;
close(fd);
@@ -892,8 +857,8 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
if ( fd<0 ) { perror("failed shm_open"); assert(0); }
void * ptr = mmap(NULL,size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if ( ptr == MAP_FAILED ) { perror("failed mmap"); assert(0); }
assert(((uint64_t)ptr&0x3F)==0);
if ( ptr == MAP_FAILED ) { perror("failed mmap"); GRID_ASSERT(0); }
GRID_ASSERT(((uint64_t)ptr&0x3F)==0);
WorldShmCommBufs[r] =ptr;
close(fd);
@@ -916,14 +881,14 @@ void GlobalSharedMemory::SharedMemoryZero(void *dest,size_t bytes)
bzero(dest,bytes);
#endif
}
void GlobalSharedMemory::SharedMemoryCopy(void *dest,void *src,size_t bytes)
{
#if defined(GRID_CUDA) || defined(GRID_HIP) || defined(GRID_SYCL)
acceleratorCopyToDevice(src,dest,bytes);
#else
bcopy(src,dest,bytes);
#endif
}
//void GlobalSharedMemory::SharedMemoryCopy(void *dest,void *src,size_t bytes)
//{
//#if defined(GRID_CUDA) || defined(GRID_HIP) || defined(GRID_SYCL)
// acceleratorCopyToDevice(src,dest,bytes);
//#else
// bcopy(src,dest,bytes);
//#endif
//}
////////////////////////////////////////////////////////
// Global shared functionality finished
// Now move to per communicator functionality
@@ -950,7 +915,7 @@ void SharedMemory::SetCommunicator(Grid_MPI_Comm comm)
//////////////////////////////////////////////////////////////////////
// Map ShmRank to WorldShmRank and use the right buffer
//////////////////////////////////////////////////////////////////////
assert (GlobalSharedMemory::ShmAlloc()==1);
GRID_ASSERT (GlobalSharedMemory::ShmAlloc()==1);
heap_size = GlobalSharedMemory::ShmAllocBytes();
for(int r=0;r<ShmSize;r++){
@@ -959,6 +924,7 @@ void SharedMemory::SetCommunicator(Grid_MPI_Comm comm)
MPI_Allreduce(MPI_IN_PLACE,&wsr,1,MPI_UINT32_T,MPI_SUM,ShmComm);
ShmCommBufs[r] = GlobalSharedMemory::WorldShmCommBufs[wsr];
// std::cerr << " SetCommunicator rank "<<r<<" comm "<<ShmCommBufs[r] <<std::endl;
}
ShmBufferFreeAll();
@@ -989,7 +955,7 @@ void SharedMemory::SetCommunicator(Grid_MPI_Comm comm)
}
#endif
//SharedMemoryTest();
// SharedMemoryTest();
}
//////////////////////////////////////////////////////////////////
// On node barrier
@@ -1011,19 +977,18 @@ void SharedMemory::SharedMemoryTest(void)
check[0]=GlobalSharedMemory::WorldNode;
check[1]=r;
check[2]=magic;
GlobalSharedMemory::SharedMemoryCopy( ShmCommBufs[r], check, 3*sizeof(uint64_t));
acceleratorCopyToDevice(check,ShmCommBufs[r],3*sizeof(uint64_t));
}
}
ShmBarrier();
for(uint64_t r=0;r<ShmSize;r++){
ShmBarrier();
GlobalSharedMemory::SharedMemoryCopy(check,ShmCommBufs[r], 3*sizeof(uint64_t));
ShmBarrier();
assert(check[0]==GlobalSharedMemory::WorldNode);
assert(check[1]==r);
assert(check[2]==magic);
ShmBarrier();
acceleratorCopyFromDevice(ShmCommBufs[r],check,3*sizeof(uint64_t));
GRID_ASSERT(check[0]==GlobalSharedMemory::WorldNode);
GRID_ASSERT(check[1]==r);
GRID_ASSERT(check[2]==magic);
}
ShmBarrier();
std::cout << GridLogDebug << " SharedMemoryTest has passed "<<std::endl;
}
void *SharedMemory::ShmBuffer(int rank)
@@ -1038,12 +1003,14 @@ void *SharedMemory::ShmBuffer(int rank)
void *SharedMemory::ShmBufferTranslate(int rank,void * local_p)
{
int gpeer = ShmRanks[rank];
assert(gpeer!=ShmRank); // never send to self
GRID_ASSERT(gpeer!=ShmRank); // never send to self
// std::cout << "ShmBufferTranslate for rank " << rank<<" peer "<<gpeer<<std::endl;
if (gpeer == MPI_UNDEFINED){
return NULL;
} else {
uint64_t offset = (uint64_t)local_p - (uint64_t)ShmCommBufs[ShmRank];
uint64_t remote = (uint64_t)ShmCommBufs[gpeer]+offset;
// std::cout << "ShmBufferTranslate : local,offset,remote "<<std::hex<<local_p<<" "<<offset<<" "<<remote<<std::dec<<std::endl;
return (void *) remote;
}
}
+10 -10
View File
@@ -34,7 +34,7 @@ NAMESPACE_BEGIN(Grid);
/*Construct from an MPI communicator*/
void GlobalSharedMemory::Init(Grid_MPI_Comm comm)
{
assert(_ShmSetup==0);
GRID_ASSERT(_ShmSetup==0);
WorldComm = 0;
WorldRank = 0;
WorldSize = 1;
@@ -62,8 +62,8 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
{
std::cout << header "SharedMemoryAllocate "<< bytes<< " GPU implementation "<<std::endl;
void * ShmCommBuf ;
assert(_ShmSetup==1);
assert(_ShmAlloc==0);
GRID_ASSERT(_ShmSetup==1);
GRID_ASSERT(_ShmAlloc==0);
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Each MPI rank should allocate our own buffer
@@ -92,8 +92,8 @@ void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
void GlobalSharedMemory::SharedMemoryAllocate(uint64_t bytes, int flags)
{
void * ShmCommBuf ;
assert(_ShmSetup==1);
assert(_ShmAlloc==0);
GRID_ASSERT(_ShmSetup==1);
GRID_ASSERT(_ShmAlloc==0);
int mmap_flag =0;
#ifdef MAP_ANONYMOUS
mmap_flag = mmap_flag| MAP_SHARED | MAP_ANONYMOUS;
@@ -122,17 +122,17 @@ void GlobalSharedMemory::SharedMemoryZero(void *dest,size_t bytes)
{
acceleratorMemSet(dest,0,bytes);
}
void GlobalSharedMemory::SharedMemoryCopy(void *dest,void *src,size_t bytes)
{
acceleratorCopyToDevice(src,dest,bytes);
}
//void GlobalSharedMemory::SharedMemoryCopy(void *dest,void *src,size_t bytes)
//{
// acceleratorCopyToDevice(src,dest,bytes);
//}
////////////////////////////////////////////////////////
// Global shared functionality finished
// Now move to per communicator functionality
////////////////////////////////////////////////////////
void SharedMemory::SetCommunicator(Grid_MPI_Comm comm)
{
assert(GlobalSharedMemory::ShmAlloc()==1);
GRID_ASSERT(GlobalSharedMemory::ShmAlloc()==1);
ShmRanks.resize(1);
ShmCommBufs.resize(1);
ShmRanks[0] = 0;
+5 -5
View File
@@ -202,7 +202,7 @@ template<class vobj> void Scatter_plane_simple (Lattice<vobj> &rhs,deviceVector<
{
auto buffer_p = & buffer[0];
auto table = MapCshiftTable();
autoView( rhs_v, rhs, AcceleratorWrite);
autoView( rhs_v, rhs, AcceleratorWriteDiscard);
accelerator_for(i,ent,vobj::Nsimd(),{
coalescedWrite(rhs_v[table[i].first],coalescedRead(buffer_p[table[i].second]));
});
@@ -228,7 +228,7 @@ template<class vobj> void Scatter_plane_merge(Lattice<vobj> &rhs,ExtractPointerA
if(cbmask ==0x3 ) {
int _slice_stride = rhs.Grid()->_slice_stride[dimension];
int _slice_block = rhs.Grid()->_slice_block[dimension];
autoView( rhs_v , rhs, AcceleratorWrite);
autoView( rhs_v , rhs, AcceleratorWriteDiscard);
accelerator_for(nn,e1*e2,1,{
int n = nn%e1;
int b = nn/e1;
@@ -240,9 +240,9 @@ template<class vobj> void Scatter_plane_merge(Lattice<vobj> &rhs,ExtractPointerA
// Case of SIMD split AND checker dim cannot currently be hit, except in
// Test_cshift_red_black code.
std::cout << "Scatter_plane merge assert(0); think this is buggy FIXME "<< std::endl;// think this is buggy FIXME
std::cout << "Scatter_plane merge GRID_ASSERT(0); think this is buggy FIXME "<< std::endl;// think this is buggy FIXME
std::cout<<" Unthreaded warning -- buffer is not densely packed ??"<<std::endl;
assert(0); // This will fail if hit on GPU
GRID_ASSERT(0); // This will fail if hit on GPU
autoView( rhs_v, rhs, CpuWrite);
for(int n=0;n<e1;n++){
for(int b=0;b<e2;b++){
@@ -302,7 +302,7 @@ template<class vobj> void Copy_plane(Lattice<vobj>& lhs,const Lattice<vobj> &rhs
{
auto table = MapCshiftTable();
autoView(rhs_v , rhs, AcceleratorRead);
autoView(lhs_v , lhs, AcceleratorWrite);
autoView(lhs_v , lhs, AcceleratorWriteDiscard);
accelerator_for(i,ent,vobj::Nsimd(),{
coalescedWrite(lhs_v[table[i].first],coalescedRead(rhs_v[table[i].second]));
});
+180 -241
View File
@@ -29,8 +29,12 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
#ifndef _GRID_CSHIFT_MPI_H_
#define _GRID_CSHIFT_MPI_H_
NAMESPACE_BEGIN(Grid);
#ifdef GRID_CHECKSUM_COMMS
extern uint64_t checksum_index;
#endif
const int Cshift_verbose=0;
template<class vobj> Lattice<vobj> Cshift(const Lattice<vobj> &rhs,int dimension,int shift)
{
@@ -45,6 +49,20 @@ template<class vobj> Lattice<vobj> Cshift(const Lattice<vobj> &rhs,int dimension
// Map to always positive shift modulo global full dimension.
shift = (shift+fd)%fd;
if( shift ==0 ) {
ret = rhs;
return ret;
}
//
// Potential easy fast cases:
// Shift is a multiple of the local lattice extent.
// Then need only to shift whole subvolumes
int L = rhs.Grid()->_ldimensions[dimension];
if ( (shift%L )==0 && !rhs.Grid()->CheckerBoarded(dimension) ) {
Cshift_simple(ret,rhs,dimension,shift);
return ret;
}
ret.Checkerboard() = rhs.Grid()->CheckerBoardDestination(rhs.Checkerboard(),shift,dimension);
// the permute type
@@ -68,7 +86,56 @@ template<class vobj> Lattice<vobj> Cshift(const Lattice<vobj> &rhs,int dimension
if(Cshift_verbose) std::cout << GridLogPerformance << "Cshift took "<< (t1-t0)/1e3 << " ms"<<std::endl;
return ret;
}
#if 1
template<class vobj> void Cshift_simple(Lattice<vobj>& ret,const Lattice<vobj> &rhs,int dimension,int shift)
{
GridBase *grid=rhs.Grid();
int comm_proc, xmit_to_rank, recv_from_rank;
int fd = rhs.Grid()->_fdimensions[dimension];
int rd = rhs.Grid()->_rdimensions[dimension];
int ld = rhs.Grid()->_ldimensions[dimension];
int pd = rhs.Grid()->_processors[dimension];
int simd_layout = rhs.Grid()->_simd_layout[dimension];
int comm_dim = rhs.Grid()->_processors[dimension] >1 ;
comm_proc = ((shift)/ld)%pd;
grid->ShiftedRanks(dimension,comm_proc,xmit_to_rank,recv_from_rank);
if(comm_dim) {
int64_t bytes = sizeof(vobj) * grid->oSites();
autoView(rhs_v , rhs, AcceleratorRead);
autoView(ret_v , ret, AcceleratorWrite);
void *send_buf = (void *)&rhs_v[0];
void *recv_buf = (void *)&ret_v[0];
#ifdef ACCELERATOR_AWARE_MPI
grid->SendToRecvFrom(send_buf,
xmit_to_rank,
recv_buf,
recv_from_rank,
bytes);
#else
static hostVector<vobj> hrhs; hrhs.resize(grid->oSites());
static hostVector<vobj> hret; hret.resize(grid->oSites());
void *hsend_buf = (void *)&hrhs[0];
void *hrecv_buf = (void *)&hret[0];
acceleratorCopyFromDevice(send_buf,hsend_buf,bytes);
grid->SendToRecvFrom(hsend_buf,
xmit_to_rank,
hrecv_buf,
recv_from_rank,
bytes);
acceleratorCopyToDevice(hrecv_buf,recv_buf,bytes);
#endif
}
}
template<class vobj> void Cshift_comms(Lattice<vobj>& ret,const Lattice<vobj> &rhs,int dimension,int shift)
{
int sshift[2];
@@ -117,15 +184,20 @@ template<class vobj> void Cshift_comms(Lattice<vobj> &ret,const Lattice<vobj> &r
int pd = rhs.Grid()->_processors[dimension];
int simd_layout = rhs.Grid()->_simd_layout[dimension];
int comm_dim = rhs.Grid()->_processors[dimension] >1 ;
assert(simd_layout==1);
assert(comm_dim==1);
assert(shift>=0);
assert(shift<fd);
GRID_ASSERT(simd_layout==1);
GRID_ASSERT(comm_dim==1);
GRID_ASSERT(shift>=0);
GRID_ASSERT(shift<fd);
int buffer_size = rhs.Grid()->_slice_nblock[dimension]*rhs.Grid()->_slice_block[dimension];
static deviceVector<vobj> send_buf; send_buf.resize(buffer_size);
static deviceVector<vobj> recv_buf; recv_buf.resize(buffer_size);
#ifndef ACCELERATOR_AWARE_MPI
int pad = (8 + sizeof(vobj) - 1) / sizeof(vobj);
static hostVector<vobj> hsend_buf; hsend_buf.resize(buffer_size+pad);
static hostVector<vobj> hrecv_buf; hrecv_buf.resize(buffer_size+pad);
#endif
int cb= (cbmask==0x2)? Odd : Even;
int sshift= rhs.Grid()->CheckerBoardShiftForCB(rhs.Checkerboard(),dimension,shift,cb);
RealD tcopy=0.0;
@@ -139,9 +211,11 @@ template<class vobj> void Cshift_comms(Lattice<vobj> &ret,const Lattice<vobj> &r
int comm_proc = ((x+sshift)/rd)%pd;
if (comm_proc==0) {
FlightRecorder::StepLog("Cshift_Copy_plane");
tcopy-=usecond();
Copy_plane(ret,rhs,dimension,x,sx,cbmask);
tcopy+=usecond();
FlightRecorder::StepLog("Cshift_Copy_plane_complete");
} else {
int words = buffer_size;
@@ -149,26 +223,71 @@ template<class vobj> void Cshift_comms(Lattice<vobj> &ret,const Lattice<vobj> &r
int bytes = words * sizeof(vobj);
FlightRecorder::StepLog("Cshift_Gather_plane");
tgather-=usecond();
Gather_plane_simple (rhs,send_buf,dimension,sx,cbmask);
tgather+=usecond();
FlightRecorder::StepLog("Cshift_Gather_plane_complete");
// int rank = grid->_processor;
int recv_from_rank;
int xmit_to_rank;
grid->ShiftedRanks(dimension,comm_proc,xmit_to_rank,recv_from_rank);
tcomms-=usecond();
grid->Barrier();
FlightRecorder::StepLog("Cshift_SendRecv");
#ifdef ACCELERATOR_AWARE_MPI
grid->SendToRecvFrom((void *)&send_buf[0],
xmit_to_rank,
(void *)&recv_buf[0],
recv_from_rank,
bytes);
#else
// bouncy bouncy
acceleratorCopyFromDevice(&send_buf[0],&hsend_buf[0],bytes);
#ifdef GRID_CHECKSUM_COMMS
GRID_ASSERT(bytes % 8 == 0);
checksum_index++;
uint64_t xsum = checksum_gpu((uint64_t*)&send_buf[0], bytes / 8) ^ (1 + checksum_index);
*(uint64_t*)(((char*)&hsend_buf[0]) + bytes) = xsum;
bytes += 8;
#endif
grid->SendToRecvFrom((void *)&hsend_buf[0],
xmit_to_rank,
(void *)&hrecv_buf[0],
recv_from_rank,
bytes);
#ifdef GRID_CHECKSUM_COMMS
bytes -= 8;
acceleratorCopyToDevice(&hrecv_buf[0],&recv_buf[0],bytes);
uint64_t expected_cs = *(uint64_t*)(((char*)&hrecv_buf[0]) + bytes);
uint64_t computed_cs = checksum_gpu((uint64_t*)&recv_buf[0], bytes / 8) ^ (1 + checksum_index);
std::cout << GridLogComms<< " Cshift: "
<<" dim"<<dimension
<<" shift "<<shift
<< " rank "<< grid->ThisRank()
<<" Coor "<<grid->ThisProcessorCoor()
<<" send "<<xsum<<" to "<<xmit_to_rank
<<" recv "<<computed_cs<<" from "<<recv_from_rank
<<std::endl;
GRID_ASSERT(expected_cs == computed_cs);
#else
acceleratorCopyToDevice(&hrecv_buf[0],&recv_buf[0],bytes);
#endif
#endif
FlightRecorder::StepLog("Cshift_SendRecv_complete");
xbytes+=bytes;
grid->Barrier();
tcomms+=usecond();
FlightRecorder::StepLog("Cshift_barrier_complete");
tscatter-=usecond();
Scatter_plane_simple (ret,recv_buf,dimension,x,cbmask);
@@ -203,10 +322,10 @@ template<class vobj> void Cshift_comms_simd(Lattice<vobj> &ret,const Lattice<vo
// << " ld "<<ld<<" pd " << pd<<" simd_layout "<<simd_layout
// << " comm_dim " << comm_dim << " cbmask " << cbmask <<std::endl;
assert(comm_dim==1);
assert(simd_layout==2);
assert(shift>=0);
assert(shift<fd);
GRID_ASSERT(comm_dim==1);
GRID_ASSERT(simd_layout==2);
GRID_ASSERT(shift>=0);
GRID_ASSERT(shift<fd);
RealD tcopy=0.0;
RealD tgather=0.0;
@@ -226,12 +345,24 @@ template<class vobj> void Cshift_comms_simd(Lattice<vobj> &ret,const Lattice<vo
static std::vector<deviceVector<scalar_object> > recv_buf_extract; recv_buf_extract.resize(Nsimd);
scalar_object * recv_buf_extract_mpi;
scalar_object * send_buf_extract_mpi;
for(int s=0;s<Nsimd;s++){
send_buf_extract[s].resize(buffer_size);
recv_buf_extract[s].resize(buffer_size);
}
#ifndef ACCELERATOR_AWARE_MPI
#ifdef GRID_CHECKSUM_COMMS
buffer_size += (8 + sizeof(vobj) - 1) / sizeof(vobj);
#endif
static hostVector<vobj> hsend_buf; hsend_buf.resize(buffer_size);
static hostVector<vobj> hrecv_buf; hrecv_buf.resize(buffer_size);
#ifdef GRID_CHECKSUM_COMMS
buffer_size -= (8 + sizeof(vobj) - 1) / sizeof(vobj);
#endif
#endif
int bytes = buffer_size*sizeof(scalar_object);
ExtractPointerArray<scalar_object> pointers(Nsimd); //
@@ -273,7 +404,7 @@ template<class vobj> void Cshift_comms_simd(Lattice<vobj> &ret,const Lattice<vo
if (nbr_ic) nbr_lane|=inner_bit;
assert (sx == nbr_ox);
GRID_ASSERT (sx == nbr_ox);
if(nbr_proc){
grid->ShiftedRanks(dimension,nbr_proc,xmit_to_rank,recv_from_rank);
@@ -283,11 +414,47 @@ template<class vobj> void Cshift_comms_simd(Lattice<vobj> &ret,const Lattice<vo
send_buf_extract_mpi = &send_buf_extract[nbr_lane][0];
recv_buf_extract_mpi = &recv_buf_extract[i][0];
#ifdef ACCELERATOR_AWARE_MPI
grid->SendToRecvFrom((void *)send_buf_extract_mpi,
xmit_to_rank,
(void *)recv_buf_extract_mpi,
recv_from_rank,
bytes);
#else
// bouncy bouncy
acceleratorCopyFromDevice((void *)send_buf_extract_mpi,(void *)&hsend_buf[0],bytes);
#ifdef GRID_CHECKSUM_COMMS
assert(bytes % 8 == 0);
checksum_index++;
uint64_t xsum = checksum_gpu((uint64_t*)send_buf_extract_mpi, bytes / 8) ^ (1 + checksum_index);
*(uint64_t*)(((char*)&hsend_buf[0]) + bytes) = xsum;
bytes += 8;
#endif
grid->SendToRecvFrom((void *)&hsend_buf[0],
xmit_to_rank,
(void *)&hrecv_buf[0],
recv_from_rank,
bytes);
#ifdef GRID_CHECKSUM_COMMS
bytes -= 8;
acceleratorCopyToDevice((void *)&hrecv_buf[0],(void *)recv_buf_extract_mpi,bytes);
uint64_t expected_cs = *(uint64_t*)(((char*)&hrecv_buf[0]) + bytes);
uint64_t computed_cs = checksum_gpu((uint64_t*)recv_buf_extract_mpi, bytes / 8) ^ (1 + checksum_index);
std::cout << GridLogComms<< " Cshift_comms_simd: "
<<" dim"<<dimension
<<" shift "<<shift
<< " rank "<< grid->ThisRank()
<<" Coor "<<grid->ThisProcessorCoor()
<<" send "<<xsum<<" to "<<xmit_to_rank
<<" recv "<<computed_cs<<" from "<<recv_from_rank
<<std::endl;
assert(expected_cs == computed_cs);
#else
acceleratorCopyToDevice((void *)&hrecv_buf[0],(void *)recv_buf_extract_mpi,bytes);
#endif
#endif
xbytes+=bytes;
grid->Barrier();
@@ -311,234 +478,6 @@ template<class vobj> void Cshift_comms_simd(Lattice<vobj> &ret,const Lattice<vo
std::cout << GridLogPerformance << " Cshift BW "<<(2.0*xbytes)/tcomms<<" MB/s "<<2*xbytes<< " Bytes "<<std::endl;
}
}
#else
template<class vobj> void Cshift_comms(Lattice<vobj> &ret,const Lattice<vobj> &rhs,int dimension,int shift,int cbmask)
{
typedef typename vobj::vector_type vector_type;
typedef typename vobj::scalar_type scalar_type;
GridBase *grid=rhs.Grid();
Lattice<vobj> temp(rhs.Grid());
int fd = rhs.Grid()->_fdimensions[dimension];
int rd = rhs.Grid()->_rdimensions[dimension];
int pd = rhs.Grid()->_processors[dimension];
int simd_layout = rhs.Grid()->_simd_layout[dimension];
int comm_dim = rhs.Grid()->_processors[dimension] >1 ;
assert(simd_layout==1);
assert(comm_dim==1);
assert(shift>=0);
assert(shift<fd);
RealD tcopy=0.0;
RealD tgather=0.0;
RealD tscatter=0.0;
RealD tcomms=0.0;
uint64_t xbytes=0;
int buffer_size = rhs.Grid()->_slice_nblock[dimension]*rhs.Grid()->_slice_block[dimension];
static cshiftVector<vobj> send_buf_v; send_buf_v.resize(buffer_size);
static cshiftVector<vobj> recv_buf_v; recv_buf_v.resize(buffer_size);
vobj *send_buf;
vobj *recv_buf;
{
grid->ShmBufferFreeAll();
size_t bytes = buffer_size*sizeof(vobj);
send_buf=(vobj *)grid->ShmBufferMalloc(bytes);
recv_buf=(vobj *)grid->ShmBufferMalloc(bytes);
}
int cb= (cbmask==0x2)? Odd : Even;
int sshift= rhs.Grid()->CheckerBoardShiftForCB(rhs.Checkerboard(),dimension,shift,cb);
for(int x=0;x<rd;x++){
int sx = (x+sshift)%rd;
int comm_proc = ((x+sshift)/rd)%pd;
if (comm_proc==0) {
tcopy-=usecond();
Copy_plane(ret,rhs,dimension,x,sx,cbmask);
tcopy+=usecond();
} else {
int words = buffer_size;
if (cbmask != 0x3) words=words>>1;
int bytes = words * sizeof(vobj);
tgather-=usecond();
Gather_plane_simple (rhs,send_buf_v,dimension,sx,cbmask);
tgather+=usecond();
// int rank = grid->_processor;
int recv_from_rank;
int xmit_to_rank;
grid->ShiftedRanks(dimension,comm_proc,xmit_to_rank,recv_from_rank);
tcomms-=usecond();
// grid->Barrier();
acceleratorCopyDeviceToDevice((void *)&send_buf_v[0],(void *)&send_buf[0],bytes);
grid->SendToRecvFrom((void *)&send_buf[0],
xmit_to_rank,
(void *)&recv_buf[0],
recv_from_rank,
bytes);
xbytes+=bytes;
acceleratorCopyDeviceToDevice((void *)&recv_buf[0],(void *)&recv_buf_v[0],bytes);
// grid->Barrier();
tcomms+=usecond();
tscatter-=usecond();
Scatter_plane_simple (ret,recv_buf_v,dimension,x,cbmask);
tscatter+=usecond();
}
}
if(Cshift_verbose){
std::cout << GridLogPerformance << " Cshift copy "<<tcopy/1e3<<" ms"<<std::endl;
std::cout << GridLogPerformance << " Cshift gather "<<tgather/1e3<<" ms"<<std::endl;
std::cout << GridLogPerformance << " Cshift scatter "<<tscatter/1e3<<" ms"<<std::endl;
std::cout << GridLogPerformance << " Cshift comm "<<tcomms/1e3<<" ms"<<std::endl;
std::cout << GridLogPerformance << " Cshift BW "<<(2.0*xbytes)/tcomms<<" MB/s "<<2*xbytes<< " Bytes "<<std::endl;
}
}
template<class vobj> void Cshift_comms_simd(Lattice<vobj> &ret,const Lattice<vobj> &rhs,int dimension,int shift,int cbmask)
{
GridBase *grid=rhs.Grid();
const int Nsimd = grid->Nsimd();
typedef typename vobj::vector_type vector_type;
typedef typename vobj::scalar_object scalar_object;
typedef typename vobj::scalar_type scalar_type;
int fd = grid->_fdimensions[dimension];
int rd = grid->_rdimensions[dimension];
int ld = grid->_ldimensions[dimension];
int pd = grid->_processors[dimension];
int simd_layout = grid->_simd_layout[dimension];
int comm_dim = grid->_processors[dimension] >1 ;
//std::cout << "Cshift_comms_simd dim "<< dimension << " fd "<<fd<<" rd "<<rd
// << " ld "<<ld<<" pd " << pd<<" simd_layout "<<simd_layout
// << " comm_dim " << comm_dim << " cbmask " << cbmask <<std::endl;
assert(comm_dim==1);
assert(simd_layout==2);
assert(shift>=0);
assert(shift<fd);
RealD tcopy=0.0;
RealD tgather=0.0;
RealD tscatter=0.0;
RealD tcomms=0.0;
uint64_t xbytes=0;
int permute_type=grid->PermuteType(dimension);
///////////////////////////////////////////////
// Simd direction uses an extract/merge pair
///////////////////////////////////////////////
int buffer_size = grid->_slice_nblock[dimension]*grid->_slice_block[dimension];
// int words = sizeof(vobj)/sizeof(vector_type);
static std::vector<cshiftVector<scalar_object> > send_buf_extract; send_buf_extract.resize(Nsimd);
static std::vector<cshiftVector<scalar_object> > recv_buf_extract; recv_buf_extract.resize(Nsimd);
scalar_object * recv_buf_extract_mpi;
scalar_object * send_buf_extract_mpi;
{
size_t bytes = sizeof(scalar_object)*buffer_size;
grid->ShmBufferFreeAll();
send_buf_extract_mpi = (scalar_object *)grid->ShmBufferMalloc(bytes);
recv_buf_extract_mpi = (scalar_object *)grid->ShmBufferMalloc(bytes);
}
for(int s=0;s<Nsimd;s++){
send_buf_extract[s].resize(buffer_size);
recv_buf_extract[s].resize(buffer_size);
}
int bytes = buffer_size*sizeof(scalar_object);
ExtractPointerArray<scalar_object> pointers(Nsimd); //
ExtractPointerArray<scalar_object> rpointers(Nsimd); // received pointers
///////////////////////////////////////////
// Work out what to send where
///////////////////////////////////////////
int cb = (cbmask==0x2)? Odd : Even;
int sshift= grid->CheckerBoardShiftForCB(rhs.Checkerboard(),dimension,shift,cb);
// loop over outer coord planes orthog to dim
for(int x=0;x<rd;x++){
// FIXME call local permute copy if none are offnode.
for(int i=0;i<Nsimd;i++){
pointers[i] = &send_buf_extract[i][0];
}
tgather-=usecond();
int sx = (x+sshift)%rd;
Gather_plane_extract(rhs,pointers,dimension,sx,cbmask);
tgather+=usecond();
for(int i=0;i<Nsimd;i++){
int inner_bit = (Nsimd>>(permute_type+1));
int ic= (i&inner_bit)? 1:0;
int my_coor = rd*ic + x;
int nbr_coor = my_coor+sshift;
int nbr_proc = ((nbr_coor)/ld) % pd;// relative shift in processors
int nbr_ic = (nbr_coor%ld)/rd; // inner coord of peer
int nbr_ox = (nbr_coor%rd); // outer coord of peer
int nbr_lane = (i&(~inner_bit));
int recv_from_rank;
int xmit_to_rank;
if (nbr_ic) nbr_lane|=inner_bit;
assert (sx == nbr_ox);
if(nbr_proc){
grid->ShiftedRanks(dimension,nbr_proc,xmit_to_rank,recv_from_rank);
tcomms-=usecond();
// grid->Barrier();
acceleratorCopyDeviceToDevice((void *)&send_buf_extract[nbr_lane][0],(void *)send_buf_extract_mpi,bytes);
grid->SendToRecvFrom((void *)send_buf_extract_mpi,
xmit_to_rank,
(void *)recv_buf_extract_mpi,
recv_from_rank,
bytes);
acceleratorCopyDeviceToDevice((void *)recv_buf_extract_mpi,(void *)&recv_buf_extract[i][0],bytes);
xbytes+=bytes;
// grid->Barrier();
tcomms+=usecond();
rpointers[i] = &recv_buf_extract[i][0];
} else {
rpointers[i] = &send_buf_extract[nbr_lane][0];
}
}
tscatter-=usecond();
Scatter_plane_merge(ret,rpointers,dimension,x,cbmask);
tscatter+=usecond();
}
if(Cshift_verbose){
std::cout << GridLogPerformance << " Cshift (s) copy "<<tcopy/1e3<<" ms"<<std::endl;
std::cout << GridLogPerformance << " Cshift (s) gather "<<tgather/1e3<<" ms"<<std::endl;
std::cout << GridLogPerformance << " Cshift (s) scatter "<<tscatter/1e3<<" ms"<<std::endl;
std::cout << GridLogPerformance << " Cshift (s) comm "<<tcomms/1e3<<" ms"<<std::endl;
std::cout << GridLogPerformance << " Cshift BW "<<(2.0*xbytes)/tcomms<<" MB/s"<<std::endl;
}
}
#endif
NAMESPACE_END(Grid);
+2 -2
View File
@@ -21895,7 +21895,7 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC
/// @brief user-defined string literal for JSON values
/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json/
JSON_HEDLEY_NON_NULL(1)
inline nlohmann::json operator "" _json(const char* s, std::size_t n)
inline nlohmann::json operator ""_json(const char* s, std::size_t n)
{
return nlohmann::json::parse(s, s + n);
}
@@ -21903,7 +21903,7 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t n)
/// @brief user-defined string literal for JSON pointer
/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json_pointer/
JSON_HEDLEY_NON_NULL(1)
inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n)
inline nlohmann::json::json_pointer operator ""_json_pointer(const char* s, std::size_t n)
{
return nlohmann::json::json_pointer(std::string(s, n));
}
+2 -2
View File
@@ -74,7 +74,7 @@ accelerator_inline vobj predicatedWhere(const iobj &predicate,
extract(iftrue, truevals);
extract(iffalse, falsevals);
extract<vInteger, Integer>(TensorRemove(predicate), mask);
extract(TensorRemove(predicate), mask);
for (int s = 0; s < Nsimd; s++) {
if (mask[s]) falsevals[s] = truevals[s];
@@ -245,7 +245,7 @@ template <class T1,typename std::enable_if<is_lattice<T1>::value, T1>::type * =
inline void CBFromExpression(int &cb, const T1 &lat) // Lattice leaf
{
if ((cb == Odd) || (cb == Even)) {
assert(cb == lat.Checkerboard());
GRID_ASSERT(cb == lat.Checkerboard());
}
cb = lat.Checkerboard();
}
+16 -16
View File
@@ -120,12 +120,12 @@ public:
GRID_TRACE("ExpressionTemplateEval");
GridBase *egrid(nullptr);
GridFromExpression(egrid,expr);
assert(egrid!=nullptr);
GRID_ASSERT(egrid!=nullptr);
conformable(this->_grid,egrid);
int cb=-1;
CBFromExpression(cb,expr);
assert( (cb==Odd) || (cb==Even));
GRID_ASSERT( (cb==Odd) || (cb==Even));
this->checkerboard=cb;
auto exprCopy = expr;
@@ -144,12 +144,12 @@ public:
GRID_TRACE("ExpressionTemplateEval");
GridBase *egrid(nullptr);
GridFromExpression(egrid,expr);
assert(egrid!=nullptr);
GRID_ASSERT(egrid!=nullptr);
conformable(this->_grid,egrid);
int cb=-1;
CBFromExpression(cb,expr);
assert( (cb==Odd) || (cb==Even));
GRID_ASSERT( (cb==Odd) || (cb==Even));
this->checkerboard=cb;
auto exprCopy = expr;
@@ -168,12 +168,12 @@ public:
GRID_TRACE("ExpressionTemplateEval");
GridBase *egrid(nullptr);
GridFromExpression(egrid,expr);
assert(egrid!=nullptr);
GRID_ASSERT(egrid!=nullptr);
conformable(this->_grid,egrid);
int cb=-1;
CBFromExpression(cb,expr);
assert( (cb==Odd) || (cb==Even));
GRID_ASSERT( (cb==Odd) || (cb==Even));
this->checkerboard=cb;
auto exprCopy = expr;
ExpressionViewOpen(exprCopy);
@@ -191,11 +191,11 @@ public:
Lattice(const LatticeUnaryExpression<Op,T1> & expr) {
this->_grid = nullptr;
GridFromExpression(this->_grid,expr);
assert(this->_grid!=nullptr);
GRID_ASSERT(this->_grid!=nullptr);
int cb=-1;
CBFromExpression(cb,expr);
assert( (cb==Odd) || (cb==Even));
GRID_ASSERT( (cb==Odd) || (cb==Even));
this->checkerboard=cb;
resize(this->_grid->oSites());
@@ -206,11 +206,11 @@ public:
Lattice(const LatticeBinaryExpression<Op,T1,T2> & expr) {
this->_grid = nullptr;
GridFromExpression(this->_grid,expr);
assert(this->_grid!=nullptr);
GRID_ASSERT(this->_grid!=nullptr);
int cb=-1;
CBFromExpression(cb,expr);
assert( (cb==Odd) || (cb==Even));
GRID_ASSERT( (cb==Odd) || (cb==Even));
this->checkerboard=cb;
resize(this->_grid->oSites());
@@ -221,11 +221,11 @@ public:
Lattice(const LatticeTrinaryExpression<Op,T1,T2,T3> & expr) {
this->_grid = nullptr;
GridFromExpression(this->_grid,expr);
assert(this->_grid!=nullptr);
GRID_ASSERT(this->_grid!=nullptr);
int cb=-1;
CBFromExpression(cb,expr);
assert( (cb==Odd) || (cb==Even));
GRID_ASSERT( (cb==Odd) || (cb==Even));
this->checkerboard=cb;
resize(this->_grid->oSites());
@@ -236,7 +236,7 @@ public:
template<class sobj> inline Lattice<vobj> & operator = (const sobj & r){
vobj vtmp;
vtmp = r;
#if 0
#if 1
deviceVector<vobj> vvtmp(1);
acceleratorPut(vvtmp[0],vtmp);
vobj *vvtmp_p = & vvtmp[0];
@@ -264,7 +264,7 @@ public:
Lattice(GridBase *grid,ViewMode mode=AcceleratorWriteDiscard) {
this->_grid = grid;
resize(this->_grid->oSites());
assert((((uint64_t)&this->_odata[0])&0xF) ==0);
GRID_ASSERT((((uint64_t)&this->_odata[0])&0xF) ==0);
this->checkerboard=0;
SetViewMode(mode);
}
@@ -289,7 +289,7 @@ public:
///////////////////////////////////////////
// move constructor
///////////////////////////////////////////
Lattice(Lattice && r){
Lattice(Lattice && r) noexcept {
this->_grid = r.Grid();
this->_odata = r._odata;
this->_odata_size = r._odata_size;
@@ -330,7 +330,7 @@ public:
///////////////////////////////////////////
// Move assignment possible if same type
///////////////////////////////////////////
inline Lattice<vobj> & operator = (Lattice<vobj> && r){
inline Lattice<vobj> & operator = (Lattice<vobj> && r) noexcept {
resize(0); // deletes if appropriate
this->_grid = r.Grid();
+5 -5
View File
@@ -166,9 +166,9 @@ void basisReorderInPlace(std::vector<Field> &_v,std::vector<RealD>& sort_vals, s
{
int vlen = idx.size();
assert(vlen>=1);
assert(vlen<=sort_vals.size());
assert(vlen<=_v.size());
GRID_ASSERT(vlen>=1);
GRID_ASSERT(vlen<=sort_vals.size());
GRID_ASSERT(vlen<=_v.size());
for (size_t i=0;i<vlen;i++) {
@@ -186,7 +186,7 @@ void basisReorderInPlace(std::vector<Field> &_v,std::vector<RealD>& sort_vals, s
if (idx[j]==i)
break;
assert(idx[i] > i); assert(j!=idx.size()); assert(idx[j]==i);
GRID_ASSERT(idx[i] > i); GRID_ASSERT(j!=idx.size()); GRID_ASSERT(idx[j]==i);
swap(_v[i],_v[idx[i]]); // should use vector move constructor, no data copy
std::swap(sort_vals[i],sort_vals[idx[i]]);
@@ -224,7 +224,7 @@ void basisSortInPlace(std::vector<Field> & _v,std::vector<RealD>& sort_vals, boo
template<class Field>
void basisDeflate(const std::vector<Field> &_v,const std::vector<RealD>& eval,const Field& src_orig,Field& result) {
result = Zero();
assert(_v.size()==eval.size());
GRID_ASSERT(_v.size()==eval.size());
int N = (int)_v.size();
for (int i=0;i<N;i++) {
Field& tmp = _v[i];
+88 -25
View File
@@ -40,16 +40,16 @@ NAMESPACE_BEGIN(Grid);
//Query supporting logical &&, ||,
//////////////////////////////////////////////////////////////////////////
typedef iScalar<vInteger> vPredicate ;
template<class vobj> using vPredicate = iScalar<IntegerPredicate<vobj> > ;
//////////////////////////////////////////////////////////////////////////
// compare lattice to lattice
//////////////////////////////////////////////////////////////////////////
template<class vfunctor,class lobj,class robj>
inline Lattice<vPredicate> LLComparison(vfunctor op,const Lattice<lobj> &lhs,const Lattice<robj> &rhs)
inline Lattice<vPredicate<lobj> > LLComparison(vfunctor op,const Lattice<lobj> &lhs,const Lattice<robj> &rhs)
{
Lattice<vPredicate> ret(rhs.Grid());
Lattice<vPredicate<lobj> > ret(rhs.Grid());
autoView( lhs_v, lhs, CpuRead);
autoView( rhs_v, rhs, CpuRead);
autoView( ret_v, ret, CpuWrite);
@@ -62,9 +62,9 @@ inline Lattice<vPredicate> LLComparison(vfunctor op,const Lattice<lobj> &lhs,con
// compare lattice to scalar
//////////////////////////////////////////////////////////////////////////
template<class vfunctor,class lobj,class robj>
inline Lattice<vPredicate> LSComparison(vfunctor op,const Lattice<lobj> &lhs,const robj &rhs)
inline Lattice<vPredicate<lobj> > LSComparison(vfunctor op,const Lattice<lobj> &lhs,const robj &rhs)
{
Lattice<vPredicate> ret(lhs.Grid());
Lattice<vPredicate<lobj> > ret(lhs.Grid());
autoView( lhs_v, lhs, CpuRead);
autoView( ret_v, ret, CpuWrite);
thread_for( ss, lhs_v.size(), {
@@ -76,9 +76,9 @@ inline Lattice<vPredicate> LSComparison(vfunctor op,const Lattice<lobj> &lhs,con
// compare scalar to lattice
//////////////////////////////////////////////////////////////////////////
template<class vfunctor,class lobj,class robj>
inline Lattice<vPredicate> SLComparison(vfunctor op,const lobj &lhs,const Lattice<robj> &rhs)
inline Lattice<vPredicate<robj> > SLComparison(vfunctor op,const lobj &lhs,const Lattice<robj> &rhs)
{
Lattice<vPredicate> ret(rhs.Grid());
Lattice<vPredicate<robj> > ret(rhs.Grid());
autoView( rhs_v, rhs, CpuRead);
autoView( ret_v, ret, CpuWrite);
thread_for( ss, rhs_v.size(), {
@@ -92,88 +92,151 @@ inline Lattice<vPredicate> SLComparison(vfunctor op,const lobj &lhs,const Lattic
//////////////////////////////////////////////////////////////////////////
// Less than
template<class lobj,class robj>
inline Lattice<vPredicate> operator < (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<lobj> > operator < (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
return LLComparison(vlt<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator < (const Lattice<lobj> & lhs, const robj & rhs) {
inline Lattice<vPredicate<lobj> > operator < (const Lattice<lobj> & lhs, const robj & rhs) {
return LSComparison(vlt<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator < (const lobj & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<robj> > operator < (const lobj & lhs, const Lattice<robj> & rhs) {
return SLComparison(vlt<lobj,robj>(),lhs,rhs);
}
// Less than equal
template<class lobj,class robj>
inline Lattice<vPredicate> operator <= (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<lobj> > operator <= (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
return LLComparison(vle<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator <= (const Lattice<lobj> & lhs, const robj & rhs) {
inline Lattice<vPredicate<lobj> > operator <= (const Lattice<lobj> & lhs, const robj & rhs) {
return LSComparison(vle<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator <= (const lobj & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<robj> > operator <= (const lobj & lhs, const Lattice<robj> & rhs) {
return SLComparison(vle<lobj,robj>(),lhs,rhs);
}
// Greater than
template<class lobj,class robj>
inline Lattice<vPredicate> operator > (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<lobj> > operator > (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
return LLComparison(vgt<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator > (const Lattice<lobj> & lhs, const robj & rhs) {
inline Lattice<vPredicate<lobj> > operator > (const Lattice<lobj> & lhs, const robj & rhs) {
return LSComparison(vgt<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator > (const lobj & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<robj> > operator > (const lobj & lhs, const Lattice<robj> & rhs) {
return SLComparison(vgt<lobj,robj>(),lhs,rhs);
}
// Greater than equal
template<class lobj,class robj>
inline Lattice<vPredicate> operator >= (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<lobj> > operator >= (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
return LLComparison(vge<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator >= (const Lattice<lobj> & lhs, const robj & rhs) {
inline Lattice<vPredicate<lobj> > operator >= (const Lattice<lobj> & lhs, const robj & rhs) {
return LSComparison(vge<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator >= (const lobj & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<robj> > operator >= (const lobj & lhs, const Lattice<robj> & rhs) {
return SLComparison(vge<lobj,robj>(),lhs,rhs);
}
// equal
template<class lobj,class robj>
inline Lattice<vPredicate> operator == (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<lobj> > operator == (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
return LLComparison(veq<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator == (const Lattice<lobj> & lhs, const robj & rhs) {
inline Lattice<vPredicate<lobj> > operator == (const Lattice<lobj> & lhs, const robj & rhs) {
return LSComparison(veq<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator == (const lobj & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<robj> > operator == (const lobj & lhs, const Lattice<robj> & rhs) {
return SLComparison(veq<lobj,robj>(),lhs,rhs);
}
// not equal
template<class lobj,class robj>
inline Lattice<vPredicate> operator != (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<lobj> > operator != (const Lattice<lobj> & lhs, const Lattice<robj> & rhs) {
return LLComparison(vne<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator != (const Lattice<lobj> & lhs, const robj & rhs) {
inline Lattice<vPredicate<lobj> > operator != (const Lattice<lobj> & lhs, const robj & rhs) {
return LSComparison(vne<lobj,robj>(),lhs,rhs);
}
template<class lobj,class robj>
inline Lattice<vPredicate> operator != (const lobj & lhs, const Lattice<robj> & rhs) {
inline Lattice<vPredicate<robj> > operator != (const lobj & lhs, const Lattice<robj> & rhs) {
return SLComparison(vne<lobj,robj>(),lhs,rhs);
}
//////////////////////////////////////////////////////////////////////////
// Real-part relational comparison for COMPLEX lattices.
//
// Complex has no ordering, so operator<,>,<=,>= are (deliberately) undefined for
// complex operands -- Comparison() in Lattice_comparison_utils.h is IfNotComplex --
// and where() cannot be driven by a complex lattice directly. It is however often
// useful to threshold on the REAL PART of a complex (scalar/singlet) field, e.g. a
// momentum-magnitude mask phat^2 > pc^2. These free functions compare the real part
// to a real threshold and return the matching IntegerPredicate, so the result feeds
// where() exactly like the built-in relationals.
//
// Written with the per-lane getlane/putlane accessors (as in FFT.h, PaddedCell.h),
// not extract/merge buffers. One wrinkle: the predicate type IntegerPredicate<CComplex>
// is vInteger, whose Nsimd (>= the widest real type's) exceeds the complex operand's
// Nsimd by s = Npred/Nsimd. where() only reads the ii=0 representative of each group,
// at physical lane lane*s (the "s-fold skip" -- cf. extract()'s getlane(i*s)), so
// filling the rest of the group is not functionally required; we replicate the value
// across all s lanes anyway because some Grid code asserts the s replicas are equal
// (and it mirrors what merge() does). When vInteger is reworked to carry the operand
// Nsimd (scope later) s becomes 1 and the inner loop drops out.
//////////////////////////////////////////////////////////////////////////
template<class scalar> class sRealLt { public:
accelerator_inline Integer operator()(const scalar &a, RealD b) const { return a.real() < b ? 1 : 0; } };
template<class scalar> class sRealLe { public:
accelerator_inline Integer operator()(const scalar &a, RealD b) const { return a.real() <= b ? 1 : 0; } };
template<class scalar> class sRealGt { public:
accelerator_inline Integer operator()(const scalar &a, RealD b) const { return a.real() > b ? 1 : 0; } };
template<class scalar> class sRealGe { public:
accelerator_inline Integer operator()(const scalar &a, RealD b) const { return a.real() >= b ? 1 : 0; } };
template<class sfunctor,class CComplex>
inline Lattice<vPredicate<CComplex> > RealPartComparison(sfunctor op,const Lattice<CComplex> &lhs, RealD thr)
{
Lattice<vPredicate<CComplex> > ret(lhs.Grid());
autoView( lv, lhs, AcceleratorRead);
autoView( rv, ret, AcceleratorWrite);
typedef typename CComplex::vector_type vsimd;
const int Nsimd = vsimd::Nsimd();
const int s = IntegerPredicate<CComplex>::Nsimd() / Nsimd; // lane-replication factor (see note)
accelerator_for(ss, lhs.Grid()->oSites(), Nsimd, {
vsimd v = TensorRemove(lv[ss]); // strip iScalar nest to the bare complex SIMD word
#ifdef GRID_SIMT
{ int lane = acceleratorSIMTlane(Nsimd); // GPU: this thread == this operand lane
#else
for(int lane=0;lane<Nsimd;lane++){ // CPU: walk the packed operand lanes
#endif
Integer p = op(v.getlane(lane), thr);
for(int ii=0;ii<s;ii++) rv[ss]._internal.putlane(p, lane*s+ii); // replicate across the group
#ifdef GRID_SIMT
}
#else
}
#endif
});
return ret;
}
template<class CComplex> inline Lattice<vPredicate<CComplex> > RealPartLessThan (const Lattice<CComplex> &a, RealD b){ return RealPartComparison(sRealLt<typename CComplex::vector_type::scalar_type>(),a,b); }
template<class CComplex> inline Lattice<vPredicate<CComplex> > RealPartLessEqual (const Lattice<CComplex> &a, RealD b){ return RealPartComparison(sRealLe<typename CComplex::vector_type::scalar_type>(),a,b); }
template<class CComplex> inline Lattice<vPredicate<CComplex> > RealPartGreaterThan (const Lattice<CComplex> &a, RealD b){ return RealPartComparison(sRealGt<typename CComplex::vector_type::scalar_type>(),a,b); }
template<class CComplex> inline Lattice<vPredicate<CComplex> > RealPartGreaterEqual (const Lattice<CComplex> &a, RealD b){ return RealPartComparison(sRealGe<typename CComplex::vector_type::scalar_type>(),a,b); }
NAMESPACE_END(Grid);
#endif
+27 -21
View File
@@ -39,47 +39,53 @@ NAMESPACE_BEGIN(Grid);
//
// Should guard this with isGridTensor<> enable if?
/////////////////////////////////////////
//
// Predicate type matching the operand's vectorisation
//
template<class obj> using IntegerPredicate =
typename GridTypeMapper<typename GridTypeMapper<obj>::vector_type>::Integerified;
//
// Generic list of functors
//
template<class lobj,class robj> class veq {
public:
accelerator vInteger operator()(const lobj &lhs, const robj &rhs)
accelerator IntegerPredicate<lobj> operator()(const lobj &lhs, const robj &rhs)
{
return (lhs) == (rhs);
}
};
template<class lobj,class robj> class vne {
public:
accelerator vInteger operator()(const lobj &lhs, const robj &rhs)
accelerator IntegerPredicate<lobj> operator()(const lobj &lhs, const robj &rhs)
{
return (lhs) != (rhs);
}
};
template<class lobj,class robj> class vlt {
public:
accelerator vInteger operator()(const lobj &lhs, const robj &rhs)
accelerator IntegerPredicate<lobj> operator()(const lobj &lhs, const robj &rhs)
{
return (lhs) < (rhs);
}
};
template<class lobj,class robj> class vle {
public:
accelerator vInteger operator()(const lobj &lhs, const robj &rhs)
accelerator IntegerPredicate<lobj> operator()(const lobj &lhs, const robj &rhs)
{
return (lhs) <= (rhs);
}
};
template<class lobj,class robj> class vgt {
public:
accelerator vInteger operator()(const lobj &lhs, const robj &rhs)
accelerator IntegerPredicate<lobj> operator()(const lobj &lhs, const robj &rhs)
{
return (lhs) > (rhs);
}
};
template<class lobj,class robj> class vge {
public:
accelerator vInteger operator()(const lobj &lhs, const robj &rhs)
accelerator IntegerPredicate<lobj> operator()(const lobj &lhs, const robj &rhs)
{
return (lhs) >= (rhs);
}
@@ -133,78 +139,78 @@ NAMESPACE_BEGIN(Grid);
// Integer and real get extra relational functions.
//////////////////////////////////////////////////////////////////////////////////////////////////////
template<class sfunctor, class vsimd,IfNotComplex<vsimd> = 0>
accelerator_inline vInteger Comparison(sfunctor sop,const vsimd & lhs, const vsimd & rhs)
accelerator_inline IntegerPredicate<vsimd> Comparison(sfunctor sop,const vsimd & lhs, const vsimd & rhs)
{
typedef typename vsimd::scalar_type scalar;
ExtractBuffer<scalar> vlhs(vsimd::Nsimd()); // Use functors to reduce this to single implementation
ExtractBuffer<scalar> vrhs(vsimd::Nsimd());
ExtractBuffer<Integer> vpred(vsimd::Nsimd());
vInteger ret;
IntegerPredicate<vsimd> ret;
extract<vsimd,scalar>(lhs,vlhs);
extract<vsimd,scalar>(rhs,vrhs);
for(int s=0;s<vsimd::Nsimd();s++){
vpred[s] = sop(vlhs[s],vrhs[s]);
}
merge<vInteger,Integer>(ret,vpred);
merge<IntegerPredicate<vsimd>,Integer>(ret,vpred);
return ret;
}
template<class sfunctor, class vsimd,IfNotComplex<vsimd> = 0>
accelerator_inline vInteger Comparison(sfunctor sop,const vsimd & lhs, const typename vsimd::scalar_type & rhs)
accelerator_inline IntegerPredicate<vsimd> Comparison(sfunctor sop,const vsimd & lhs, const typename vsimd::scalar_type & rhs)
{
typedef typename vsimd::scalar_type scalar;
ExtractBuffer<scalar> vlhs(vsimd::Nsimd()); // Use functors to reduce this to single implementation
ExtractBuffer<Integer> vpred(vsimd::Nsimd());
vInteger ret;
IntegerPredicate<vsimd> ret;
extract<vsimd,scalar>(lhs,vlhs);
for(int s=0;s<vsimd::Nsimd();s++){
vpred[s] = sop(vlhs[s],rhs);
}
merge<vInteger,Integer>(ret,vpred);
merge<IntegerPredicate<vsimd>,Integer>(ret,vpred);
return ret;
}
template<class sfunctor, class vsimd,IfNotComplex<vsimd> = 0>
accelerator_inline vInteger Comparison(sfunctor sop,const typename vsimd::scalar_type & lhs, const vsimd & rhs)
accelerator_inline IntegerPredicate<vsimd> Comparison(sfunctor sop,const typename vsimd::scalar_type & lhs, const vsimd & rhs)
{
typedef typename vsimd::scalar_type scalar;
ExtractBuffer<scalar> vrhs(vsimd::Nsimd()); // Use functors to reduce this to single implementation
ExtractBuffer<Integer> vpred(vsimd::Nsimd());
vInteger ret;
IntegerPredicate<vsimd> ret;
extract<vsimd,scalar>(rhs,vrhs);
for(int s=0;s<vsimd::Nsimd();s++){
vpred[s] = sop(lhs,vrhs[s]);
}
merge<vInteger,Integer>(ret,vpred);
merge<IntegerPredicate<vsimd>,Integer>(ret,vpred);
return ret;
}
#define DECLARE_RELATIONAL_EQ(op,functor) \
template<class vsimd,IfSimd<vsimd> = 0>\
accelerator_inline vInteger operator op (const vsimd & lhs, const vsimd & rhs)\
accelerator_inline IntegerPredicate<vsimd> operator op (const vsimd & lhs, const vsimd & rhs)\
{\
typedef typename vsimd::scalar_type scalar;\
return Comparison(functor<scalar,scalar>(),lhs,rhs);\
}\
template<class vsimd,IfSimd<vsimd> = 0>\
accelerator_inline vInteger operator op (const vsimd & lhs, const typename vsimd::scalar_type & rhs) \
accelerator_inline IntegerPredicate<vsimd> operator op (const vsimd & lhs, const typename vsimd::scalar_type & rhs) \
{\
typedef typename vsimd::scalar_type scalar;\
return Comparison(functor<scalar,scalar>(),lhs,rhs);\
}\
template<class vsimd,IfSimd<vsimd> = 0>\
accelerator_inline vInteger operator op (const typename vsimd::scalar_type & lhs, const vsimd & rhs) \
accelerator_inline IntegerPredicate<vsimd> operator op (const typename vsimd::scalar_type & lhs, const vsimd & rhs) \
{\
typedef typename vsimd::scalar_type scalar;\
return Comparison(functor<scalar,scalar>(),lhs,rhs);\
}\
template<class vsimd>\
accelerator_inline vInteger operator op(const iScalar<vsimd> &lhs,const typename vsimd::scalar_type &rhs) \
accelerator_inline IntegerPredicate<vsimd> operator op(const iScalar<vsimd> &lhs,const typename vsimd::scalar_type &rhs) \
{ \
return lhs._internal op rhs; \
} \
template<class vsimd>\
accelerator_inline vInteger operator op(const typename vsimd::scalar_type &lhs,const iScalar<vsimd> &rhs) \
accelerator_inline IntegerPredicate<vsimd> operator op(const typename vsimd::scalar_type &lhs,const iScalar<vsimd> &rhs) \
{ \
return lhs op rhs._internal; \
} \
@@ -212,7 +218,7 @@ NAMESPACE_BEGIN(Grid);
#define DECLARE_RELATIONAL(op,functor) \
DECLARE_RELATIONAL_EQ(op,functor) \
template<class vsimd>\
accelerator_inline vInteger operator op(const iScalar<vsimd> &lhs,const iScalar<vsimd> &rhs)\
accelerator_inline IntegerPredicate<vsimd> operator op(const iScalar<vsimd> &lhs,const iScalar<vsimd> &rhs)\
{ \
return lhs._internal op rhs._internal; \
}
+2 -2
View File
@@ -32,8 +32,8 @@ NAMESPACE_BEGIN(Grid);
template<class obj1,class obj2> void conformable(const Lattice<obj1> &lhs,const Lattice<obj2> &rhs)
{
assert(lhs.Grid() == rhs.Grid());
assert(lhs.Checkerboard() == rhs.Checkerboard());
GRID_ASSERT(lhs.Grid() == rhs.Grid());
GRID_ASSERT(lhs.Checkerboard() == rhs.Checkerboard());
}
NAMESPACE_END(Grid);
+3 -3
View File
@@ -42,7 +42,7 @@ static void sliceMaddMatrix (Lattice<vobj> &R,Eigen::MatrixXcd &aa,const Lattice
// Lattice<vobj> Xslice(SliceGrid);
// Lattice<vobj> Rslice(SliceGrid);
assert( FullGrid->_simd_layout[Orthog]==1);
GRID_ASSERT( FullGrid->_simd_layout[Orthog]==1);
//FIXME package in a convenient iterator
//Should loop over a plane orthogonal to direction "Orthog"
@@ -86,7 +86,7 @@ static void sliceMulMatrix (Lattice<vobj> &R,Eigen::MatrixXcd &aa,const Lattice<
int Nblock = X.Grid()->GlobalDimensions()[Orthog];
GridBase *FullGrid = X.Grid();
assert( FullGrid->_simd_layout[Orthog]==1);
GRID_ASSERT( FullGrid->_simd_layout[Orthog]==1);
//FIXME package in a convenient iterator
//Should loop over a plane orthogonal to direction "Orthog"
@@ -140,7 +140,7 @@ static void sliceInnerProductMatrix( Eigen::MatrixXcd &mat, const Lattice<vobj>
mat = Eigen::MatrixXcd::Zero(Nblock,Nblock);
assert( FullGrid->_simd_layout[Orthog]==1);
GRID_ASSERT( FullGrid->_simd_layout[Orthog]==1);
// int nh = FullGrid->_ndimension;
// int nl = SliceGrid->_ndimension;
// int nl = nh-1;
+9 -9
View File
@@ -98,8 +98,8 @@ void pokeSite(const sobj &s,Lattice<vobj> &l,const Coordinate &site){
int Nsimd = grid->Nsimd();
assert( l.Checkerboard()== l.Grid()->CheckerBoard(site));
assert( sizeof(sobj)*Nsimd == sizeof(vobj));
GRID_ASSERT( l.Checkerboard()== l.Grid()->CheckerBoard(site));
GRID_ASSERT( sizeof(sobj)*Nsimd == sizeof(vobj));
int rank,odx,idx;
// Optional to broadcast from node 0.
@@ -135,7 +135,7 @@ void peekSite(sobj &s,const Lattice<vobj> &l,const Coordinate &site){
int Nsimd = grid->Nsimd();
assert( l.Checkerboard() == l.Grid()->CheckerBoard(site));
GRID_ASSERT( l.Checkerboard() == l.Grid()->CheckerBoard(site));
int rank,odx,idx;
grid->GlobalCoorToRankIndex(rank,odx,idx,site);
@@ -159,14 +159,14 @@ template<class vobj,class sobj>
inline void peekLocalSite(sobj &s,const LatticeView<vobj> &l,Coordinate &site)
{
GridBase *grid = l.getGrid();
assert(l.mode==CpuRead);
GRID_ASSERT(l.mode==CpuRead);
typedef typename vobj::scalar_type scalar_type;
typedef typename vobj::vector_type vector_type;
int Nsimd = grid->Nsimd();
// assert( l.Checkerboard()== grid->CheckerBoard(site));
assert( sizeof(sobj)*Nsimd == sizeof(vobj));
// GRID_ASSERT( l.Checkerboard()== grid->CheckerBoard(site));
GRID_ASSERT( sizeof(sobj)*Nsimd == sizeof(vobj));
static const int words=sizeof(vobj)/sizeof(vector_type);
int odx,idx;
@@ -195,15 +195,15 @@ template<class vobj,class sobj>
inline void pokeLocalSite(const sobj &s,LatticeView<vobj> &l,Coordinate &site)
{
GridBase *grid=l.getGrid();
assert(l.mode==CpuWrite);
GRID_ASSERT(l.mode==CpuWrite);
typedef typename vobj::scalar_type scalar_type;
typedef typename vobj::vector_type vector_type;
int Nsimd = grid->Nsimd();
// assert( l.Checkerboard()== grid->CheckerBoard(site));
assert( sizeof(sobj)*Nsimd == sizeof(vobj));
// GRID_ASSERT( l.Checkerboard()== grid->CheckerBoard(site));
GRID_ASSERT( sizeof(sobj)*Nsimd == sizeof(vobj));
static const int words=sizeof(vobj)/sizeof(vector_type);
int odx,idx;
+212 -61
View File
@@ -292,26 +292,26 @@ inline ComplexD innerProduct(const Lattice<vobj> &left,const Lattice<vobj> &righ
bool ok;
#ifdef GRID_SYCL
uint64_t csum=0;
uint64_t csum2=0;
if ( FlightRecorder::LoggingMode != FlightRecorder::LoggingModeNone)
{
// Hack
// Fast integer xor checksum. Can also be used in comms now.
autoView(l_v,left,AcceleratorRead);
Integer words = left.Grid()->oSites()*sizeof(vobj)/sizeof(uint64_t);
uint64_t *base= (uint64_t *)&l_v[0];
csum=svm_xor(base,words);
ok = FlightRecorder::CsumLog(csum);
if ( !ok ) {
csum2=svm_xor(base,words);
std::cerr<< " Bad CSUM " << std::hex<< csum << " recomputed as "<<csum2<<std::dec<<std::endl;
} else {
// csum2=svm_xor(base,words);
// std::cerr<< " ok CSUM " << std::hex<< csum << " recomputed as "<<csum2<<std::dec<<std::endl;
}
assert(ok);
}
// uint64_t csum=0;
// uint64_t csum2=0;
// if ( FlightRecorder::LoggingMode != FlightRecorder::LoggingModeNone)
// {
// Hack
// Fast integer xor checksum. Can also be used in comms now.
// autoView(l_v,left,AcceleratorRead);
// Integer words = left.Grid()->oSites()*sizeof(vobj)/sizeof(uint64_t);
// uint64_t *base= (uint64_t *)&l_v[0];
// csum=svm_xor(base,words);
// ok = FlightRecorder::CsumLog(csum);
// if ( !ok ) {
// csum2=svm_xor(base,words);
// std::cerr<< " Bad CSUM " << std::hex<< csum << " recomputed as "<<csum2<<std::dec<<std::endl;
// } else {
// csum2=svm_xor(base,words);
// std::cerr<< " ok CSUM " << std::hex<< csum << " recomputed as "<<csum2<<std::dec<<std::endl;
// }
// GRID_ASSERT(ok);
// }
#endif
FlightRecorder::StepLog("rank inner product");
ComplexD nrm = rankInnerProduct(left,right);
@@ -322,11 +322,11 @@ inline ComplexD innerProduct(const Lattice<vobj> &left,const Lattice<vobj> &righ
ComplexD nrm2 = rankInnerProduct(left,right);
RealD local2 = real(nrm2);
std::cerr<< " Bad NORM " << local << " recomputed as "<<local2<<std::endl;
assert(ok);
GRID_ASSERT(ok);
}
FlightRecorder::StepLog("Start global sum");
// grid->GlobalSumP2P(nrm);
grid->GlobalSum(nrm);
grid->GlobalSumP2P(nrm);
// grid->GlobalSum(nrm);
FlightRecorder::StepLog("Finished global sum");
// std::cout << " norm "<< nrm << " p2p norm "<<nrmck<<std::endl;
FlightRecorder::ReductionLog(local,real(nrm));
@@ -376,40 +376,9 @@ axpby_norm_fast(Lattice<vobj> &z,sobj a,sobj b,const Lattice<vobj> &x,const Latt
coalescedWrite(z_v[ss],tmp);
});
bool ok;
#ifdef GRID_SYCL
uint64_t csum=0;
uint64_t csum2=0;
if ( FlightRecorder::LoggingMode != FlightRecorder::LoggingModeNone)
{
// z_v
{
Integer words = sites*sizeof(vobj)/sizeof(uint64_t);
uint64_t *base= (uint64_t *)&z_v[0];
csum=svm_xor(base,words);
ok = FlightRecorder::CsumLog(csum);
if ( !ok ) {
csum2=svm_xor(base,words);
std::cerr<< " Bad z_v CSUM " << std::hex<< csum << " recomputed as "<<csum2<<std::dec<<std::endl;
}
assert(ok);
}
// inner_v
{
Integer words = sites*sizeof(inner_t)/sizeof(uint64_t);
uint64_t *base= (uint64_t *)&inner_tmp_v[0];
csum=svm_xor(base,words);
ok = FlightRecorder::CsumLog(csum);
if ( !ok ) {
csum2=svm_xor(base,words);
std::cerr<< " Bad inner_tmp_v CSUM " << std::hex<< csum << " recomputed as "<<csum2<<std::dec<<std::endl;
}
assert(ok);
}
}
#endif
nrm = real(TensorRemove(sumD(inner_tmp_v,sites)));
ok = FlightRecorder::NormLog(real(nrm));
assert(ok);
GRID_ASSERT(ok);
RealD local = real(nrm);
grid->GlobalSum(nrm);
FlightRecorder::ReductionLog(local,real(nrm));
@@ -495,13 +464,13 @@ template<class vobj> inline void sliceSum(const Lattice<vobj> &Data,
typedef typename vobj::scalar_object sobj;
typedef typename vobj::scalar_object::scalar_type scalar_type;
GridBase *grid = Data.Grid();
assert(grid!=NULL);
GRID_ASSERT(grid!=NULL);
const int Nd = grid->_ndimension;
const int Nsimd = grid->Nsimd();
assert(orthogdim >= 0);
assert(orthogdim < Nd);
GRID_ASSERT(orthogdim >= 0);
GRID_ASSERT(orthogdim < Nd);
int fd=grid->_fdimensions[orthogdim];
int ld=grid->_ldimensions[orthogdim];
@@ -588,14 +557,14 @@ static void sliceInnerProductVector( std::vector<ComplexD> & result, const Latti
typedef typename vobj::vector_type vector_type;
typedef typename vobj::scalar_type scalar_type;
GridBase *grid = lhs.Grid();
assert(grid!=NULL);
GRID_ASSERT(grid!=NULL);
conformable(grid,rhs.Grid());
const int Nd = grid->_ndimension;
const int Nsimd = grid->Nsimd();
assert(orthogdim >= 0);
assert(orthogdim < Nd);
GRID_ASSERT(orthogdim >= 0);
GRID_ASSERT(orthogdim < Nd);
int fd=grid->_fdimensions[orthogdim];
int ld=grid->_ldimensions[orthogdim];
@@ -824,6 +793,188 @@ static void sliceInnerProductMatrix( Eigen::MatrixXcd &mat, const Lattice<vobj>
delete SliceGrid;
}
/////////////////////////////////////////////////////////////////////////////
// Batched linear algebra for Krylov orthogonalisation (GCR history windows).
//
// innerProductMulti(out,left,right): out[j] = <left[j],right> for all j in
// ONE kernel (right read once), ONE device reduction / sync (the per-site
// partials are an iVector over the batch) and ONE GlobalSumVector.
// axpyMulti(z,b,x): z = z + sum_j b[j] x[j] in ONE pass.
// axpyMultiNorm(z,b,x): same, returning global |z|^2 from the
// same pass (one reduction, one GlobalSum).
//
// The batch width is a template parameter chosen at runtime from {2,4,8,16}
// so a short window (mmax=2 smoother) does not pay for 16 partial lanes;
// windows longer than 16 are processed in chunks of 16, one reduction each.
// Same code path for every Lattice<vobj>: fine fermion fields and coarse
// multi-RHS fields (nrhs folded into the grid) alike.
/////////////////////////////////////////////////////////////////////////////
// The batch of views and coefficients is passed to the kernel BY VALUE as
// lambda-captured kernel arguments (a ViewPack), so there is no per-call
// deviceVector allocation and no synchronous host->device memcpy of pointer
// tables: the only sync points are the reductions themselves.
// LatticeView has no default constructor, so the pack holds raw aligned
// storage and views are copied in bytewise (as basisRotateJ does through
// acceleratorPut); the struct is trivially copyable as a kernel argument.
template<class View,int B>
struct ViewPack {
alignas(View) unsigned char raw[B*sizeof(View)];
ComplexD b[B];
accelerator_inline const View & v(int j) const { return reinterpret_cast<const View *>(raw)[j]; }
void set(int j,const View &view){ memcpy(raw+j*sizeof(View),&view,sizeof(View)); }
};
template<int B,class vobj>
void rankInnerProductMultiChunk(ComplexD *out,int m,
const std::vector<const Lattice<vobj>*> &left,
const Lattice<vobj> &right)
{
typedef decltype(innerProductD(vobj(),vobj())) inner_t;
typedef iVector<inner_t,B> batch_t;
typedef decltype(right.View(AcceleratorRead)) View;
GRID_ASSERT(m>=1 && m<=B);
GridBase *grid = right.Grid();
const uint64_t sites = grid->oSites();
std::vector<View> h_v; h_v.reserve(m);
ViewPack<View,B> pack;
for(int j=0;j<m;j++){
conformable(*left[j],right);
h_v.push_back(left[j]->View(AcceleratorRead));
pack.set(j,h_v[j]);
}
for(int j=m;j<B;j++) pack.set(j,h_v[0]); // valid but unused lanes
deviceVector<batch_t> partial(sites);
batch_t *partial_v = &partial[0];
{
autoView(right_v,right,AcceleratorRead);
accelerator_for(ss,sites,1,{
auto r = right_v[ss];
batch_t acc;
for(int j=0;j<B;j++){
if ( j<m ) acc._internal[j] = innerProductD(pack.v(j)[ss],r);
else zeroit(acc._internal[j]);
}
partial_v[ss] = acc;
});
}
for(int j=0;j<m;j++) h_v[j].ViewClose();
auto res = sum(partial_v,sites); // one reduction for the whole batch
for(int j=0;j<m;j++) out[j] = TensorRemove(res._internal[j]);
}
template<class vobj>
void rankInnerProductMulti(std::vector<ComplexD> &out,
const std::vector<const Lattice<vobj>*> &left,
const Lattice<vobj> &right)
{
int m = left.size();
out.resize(m);
for(int j0=0;j0<m;j0+=16){
int mm = std::min(16,m-j0);
std::vector<const Lattice<vobj>*> sub(left.begin()+j0,left.begin()+j0+mm);
if ( mm<=2 ) rankInnerProductMultiChunk<2> (&out[j0],mm,sub,right);
else if ( mm<=4 ) rankInnerProductMultiChunk<4> (&out[j0],mm,sub,right);
else if ( mm<=8 ) rankInnerProductMultiChunk<8> (&out[j0],mm,sub,right);
else rankInnerProductMultiChunk<16>(&out[j0],mm,sub,right);
}
}
template<class vobj>
void innerProductMulti(std::vector<ComplexD> &out,
const std::vector<const Lattice<vobj>*> &left,
const Lattice<vobj> &right)
{
rankInnerProductMulti(out,left,right);
if ( out.size() ) right.Grid()->GlobalSumVector(&out[0],(int)out.size());
}
// z = z + sum_{j<m} b[j] x[j] for one chunk of at most B vectors; if do_norm,
// also writes per-site |z|^2 into inner_tmp_v.
template<int B,class vobj>
void axpyMultiChunk(Lattice<vobj> &z,const ComplexD *b,
const std::vector<const Lattice<vobj>*> &x,int m,
int do_norm,
decltype(innerProduct(vobj(),vobj())) *inner_tmp_v)
{
typedef decltype(z.View(AcceleratorRead)) View;
GRID_ASSERT(m>=1 && m<=B);
GridBase *grid = z.Grid();
const uint64_t nsimd = grid->Nsimd();
const uint64_t sites = grid->oSites();
std::vector<View> h_v; h_v.reserve(m);
ViewPack<View,B> pack;
for(int j=0;j<m;j++){
conformable(*x[j],z);
GRID_ASSERT(x[j]!=&z); // window must not alias the accumulator
h_v.push_back(x[j]->View(AcceleratorRead));
pack.set(j,h_v[j]);
pack.b[j] = b[j];
}
for(int j=m;j<B;j++){ pack.set(j,h_v[0]); pack.b[j] = ComplexD(0.0); }
autoView(z_v,z,AcceleratorWrite);
accelerator_for(ss,sites,nsimd,{
auto acc = coalescedRead(z_v[ss]);
for(int j=0;j<B;j++) if ( j<m ) acc = acc + pack.b[j]*coalescedRead(pack.v(j)[ss]);
coalescedWrite(z_v[ss],acc);
if ( do_norm ) coalescedWrite(inner_tmp_v[ss],innerProduct(acc,acc));
});
for(int j=0;j<m;j++) h_v[j].ViewClose();
}
// z = z + sum_j b[j] x[j]; if do_norm, returns global |z|^2 from the same
// (last) pass. Chunks of 16 for windows longer than 16.
template<class vobj>
RealD axpyMultiNormImpl(Lattice<vobj> &z,const std::vector<ComplexD> &b,
const std::vector<const Lattice<vobj>*> &x,int do_norm)
{
typedef decltype(innerProduct(vobj(),vobj())) inner_t;
int m = x.size();
GRID_ASSERT((int)b.size()>=m);
GridBase *grid = z.Grid();
const uint64_t sites = grid->oSites();
deviceVector<inner_t> inner_tmp(do_norm ? sites : 1);
inner_t *inner_tmp_v = &inner_tmp[0];
if ( m==0 ) {
if ( do_norm ) return norm2(z);
return 0.0;
}
for(int j0=0;j0<m;j0+=16){
int mm = std::min(16,m-j0);
int last = (j0+mm>=m);
std::vector<const Lattice<vobj>*> sub(x.begin()+j0,x.begin()+j0+mm);
int dn = do_norm && last;
if ( mm<=2 ) axpyMultiChunk<2> (z,&b[j0],sub,mm,dn,inner_tmp_v);
else if ( mm<=4 ) axpyMultiChunk<4> (z,&b[j0],sub,mm,dn,inner_tmp_v);
else if ( mm<=8 ) axpyMultiChunk<8> (z,&b[j0],sub,mm,dn,inner_tmp_v);
else axpyMultiChunk<16>(z,&b[j0],sub,mm,dn,inner_tmp_v);
}
RealD nrm = 0.0;
if ( do_norm ) {
nrm = real(TensorRemove(sumD(inner_tmp_v,sites)));
grid->GlobalSum(nrm);
}
return nrm;
}
template<class vobj>
void axpyMulti(Lattice<vobj> &z,const std::vector<ComplexD> &b,const std::vector<const Lattice<vobj>*> &x)
{
axpyMultiNormImpl(z,b,x,0);
}
template<class vobj>
RealD axpyMultiNorm(Lattice<vobj> &z,const std::vector<ComplexD> &b,const std::vector<const Lattice<vobj>*> &x)
{
return axpyMultiNormImpl(z,b,x,1);
}
NAMESPACE_END(Grid);
+179 -23
View File
@@ -197,55 +197,205 @@ __global__ void reduceKernel(const vobj *lat, sobj *buffer, Iterator n) {
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Possibly promote to double and sum
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#undef GRID_REDUCTION_TIMING
template <class vobj>
inline typename vobj::scalar_objectD sumD_gpu_small(const vobj *lat, Integer osites)
inline typename vobj::scalar_objectD sumD_gpu_small(const vobj *lat, Integer osites)
{
typedef typename vobj::scalar_objectD sobj;
typedef decltype(lat) Iterator;
Integer nsimd= vobj::Nsimd();
Integer size = osites*nsimd;
Integer numThreads, numBlocks;
int ok = getNumBlocksAndThreads(size, sizeof(sobj), numThreads, numBlocks);
assert(ok);
GRID_ASSERT(ok);
Integer smemSize = numThreads * sizeof(sobj);
// Move out of UVM
// Turns out I had messed up the synchronise after move to compute stream
// as running this on the default stream fools the synchronise
deviceVector<sobj> buffer(numBlocks);
sobj *buffer_v = &buffer[0];
sobj result;
#ifdef GRID_REDUCTION_TIMING
RealD t_kernel = -usecond();
#endif
reduceKernel<<< numBlocks, numThreads, smemSize, computeStream >>>(lat, buffer_v, size);
accelerator_barrier();
#ifdef GRID_REDUCTION_TIMING
t_kernel += usecond();
RealD t_d2h = -usecond();
#endif
acceleratorCopyFromDevice(buffer_v,&result,sizeof(result));
#ifdef GRID_REDUCTION_TIMING
t_d2h += usecond();
std::cout << GridLogDebug << " sumD_gpu_small"
<< " sizeof(sobj)=" << sizeof(sobj)
<< " blocks=" << numBlocks << " threads=" << numThreads
<< " kernel+barrier=" << t_kernel << " us"
<< " D2H=" << t_d2h << " us" << std::endl;
#endif
return result;
}
// Fused pack+reduce: reads R words of each vobj at word offset 'base',
// accumulates directly into iVector<iScalar<scalarD>,R> without staging
// through an intermediate bundle buffer. One HBM pass instead of three.
template <int R, class vobj, class sobj, class Iterator>
__device__ void packReduceBlocks(
const iScalar<typename vobj::vector_type> *idat,
sobj *g_odata, Iterator osites, int base, int words)
{
constexpr Iterator nsimd = vobj::Nsimd();
Iterator blockSize = blockDim.x;
extern __shared__ __align__(COALESCE_GRANULARITY) unsigned char shmem_pointer[];
sobj *sdata = (sobj *)shmem_pointer;
Iterator tid = threadIdx.x;
Iterator i = blockIdx.x * (blockSize * 2) + threadIdx.x;
Iterator gridSize = blockSize * 2 * gridDim.x;
sobj mySum = Zero();
while (i < osites * nsimd) {
Iterator lane = i % nsimd;
Iterator ss = i / nsimd;
sobj tmpD; zeroit(tmpD);
for (int k = 0; k < R; k++) {
auto w = extractLane(lane, idat[ss * words + base + k]);
iScalar<typename vobj::scalar_typeD> wd; wd = w;
tmpD._internal[k] = wd;
}
mySum += tmpD;
if (i + blockSize < osites * nsimd) {
lane = (i + blockSize) % nsimd;
ss = (i + blockSize) / nsimd;
sobj tmpD2; zeroit(tmpD2);
for (int k = 0; k < R; k++) {
auto w = extractLane(lane, idat[ss * words + base + k]);
iScalar<typename vobj::scalar_typeD> wd; wd = w;
tmpD2._internal[k] = wd;
}
mySum += tmpD2;
}
i += gridSize;
}
reduceBlock(sdata, mySum, tid);
if (tid == 0) g_odata[blockIdx.x] = sdata[0];
}
template <int R, class vobj, class sobj, class Iterator>
__global__ void packReduceKernel(
const iScalar<typename vobj::vector_type> *idat,
sobj *buffer, Iterator osites, int base, int words)
{
Iterator blockSize = blockDim.x;
packReduceBlocks<R, vobj, sobj>(idat, buffer, osites, base, words);
if (gridDim.x > 1) {
const Iterator tid = threadIdx.x;
__shared__ bool amLast;
extern __shared__ __align__(COALESCE_GRANULARITY) unsigned char shmem_pointer[];
sobj *smem = (sobj *)shmem_pointer;
acceleratorFence();
if (tid == 0) {
unsigned int ticket = atomicInc(&retirementCount, gridDim.x);
amLast = (ticket == gridDim.x - 1);
}
acceleratorSynchroniseAll();
if (amLast) {
Iterator i = tid;
sobj mySum = Zero();
while (i < (Iterator)gridDim.x) {
mySum += buffer[i];
i += blockSize;
}
reduceBlock(smem, mySum, tid);
if (tid == 0) {
buffer[0] = smem[0];
retirementCount = 0;
}
}
}
}
template<int R, class vobj>
inline void sumD_gpu_reduce_words(const vobj *lat, Integer osites,
typename vobj::scalar_typeD *ret_p, int base)
{
typedef typename vobj::vector_type vector;
typedef typename vobj::scalar_typeD scalarD;
using BundleScalarD = iVector<iScalar<scalarD>, R>;
constexpr int Nsimd = vobj::Nsimd();
const int words = sizeof(vobj) / sizeof(vector);
const iScalar<vector> *idat = (const iScalar<vector> *)lat;
Integer size = (Integer)osites * Nsimd;
Integer numThreads, numBlocks;
int ok = getNumBlocksAndThreads(size, sizeof(BundleScalarD), numThreads, numBlocks);
GRID_ASSERT(ok);
Integer smemSize = numThreads * sizeof(BundleScalarD);
deviceVector<BundleScalarD> buffer(numBlocks);
BundleScalarD *buffer_v = &buffer[0];
BundleScalarD result;
#ifdef GRID_REDUCTION_TIMING
RealD t_kernel = -usecond();
#endif
packReduceKernel<R, vobj, BundleScalarD, Integer>
<<<numBlocks, numThreads, smemSize, computeStream>>>
(idat, buffer_v, osites, base, words);
accelerator_barrier();
#ifdef GRID_REDUCTION_TIMING
t_kernel += usecond();
RealD t_d2h = -usecond();
#endif
acceleratorCopyFromDevice(buffer_v, &result, sizeof(result));
#ifdef GRID_REDUCTION_TIMING
t_d2h += usecond();
std::cout << GridLogDebug << " sumD_gpu_reduce_words R=" << R
<< " base=" << base
<< " kernel=" << t_kernel << " D2H=" << t_d2h << " us" << std::endl;
#endif
for (int k = 0; k < R; k++)
ret_p[base + k] = TensorRemove(result._internal[k]);
}
template <class vobj>
inline typename vobj::scalar_objectD sumD_gpu_large(const vobj *lat, Integer osites)
{
typedef typename vobj::vector_type vector;
typedef typename vobj::scalar_typeD scalarD;
typedef typename vobj::scalar_objectD sobj;
sobj ret;
typedef typename vobj::vector_type vector;
typedef typename vobj::scalar_typeD scalarD;
typedef typename vobj::scalar_objectD sobjD;
const int words = sizeof(vobj) / sizeof(vector);
sobjD ret; zeroit(ret);
scalarD *ret_p = (scalarD *)&ret;
const int words = sizeof(vobj)/sizeof(vector);
deviceVector<vector> buffer(osites);
vector *dat = (vector *)lat;
vector *buf = &buffer[0];
iScalar<vector> *tbuf =(iScalar<vector> *) &buffer[0];
for(int w=0;w<words;w++) {
#ifdef GRID_REDUCTION_TIMING
RealD t_large = -usecond();
#endif
int w = 0;
while (w + 12 <= words) { sumD_gpu_reduce_words<12>(lat, osites, ret_p, w); w += 12; }
while (w + 4 <= words) { sumD_gpu_reduce_words< 4>(lat, osites, ret_p, w); w += 4; }
while (w < words) { sumD_gpu_reduce_words< 1>(lat, osites, ret_p, w); w += 1; }
#ifdef GRID_REDUCTION_TIMING
t_large += usecond();
std::cout << GridLogDebug << "sumD_gpu_large"
<< " sizeof(sobjD)=" << sizeof(sobjD)
<< " words=" << words << " total=" << t_large << " us" << std::endl;
#endif
accelerator_for(ss,osites,1,{
buf[ss] = dat[ss*words+w];
});
ret_p[w] = sumD_gpu_small(tbuf,osites);
}
return ret;
}
@@ -288,5 +438,11 @@ inline typename vobj::scalar_object sum_gpu_large(const vobj *lat, Integer osite
result = sumD_gpu_large(lat,osites);
return result;
}
template<class Word> Word checksum_gpu(Word *vec,uint64_t L)
{
Word w;
bzero(&w,sizeof(w));
return w;
}
NAMESPACE_END(Grid);

Some files were not shown because too many files have changed in this diff Show More