diff --git a/quest/src/api/environment.cpp b/quest/src/api/environment.cpp index 541491899..7a667b48b 100644 --- a/quest/src/api/environment.cpp +++ b/quest/src/api/environment.cpp @@ -340,6 +340,9 @@ void printQuregAutoDeployments(bool isDensMatr) { prevGpuAccel = 0; prevMulti = 0; + // assume 1 qubit is deployable, so that an undeployable first row is still reported + bool prevCanDeploy = true; + // test to theoretically max #qubits, surpassing max that can fit in RAM and GPUs, because // auto-deploy will still try to deploy there to (then subsequent validation will fail) int maxQubits = mem_getMaxNumQuregQubitsBeforeGlobalMemSizeofOverflow(isDensMatr, globalEnvPtr->numNodes); @@ -350,22 +353,34 @@ void printQuregAutoDeployments(bool isDensMatr) { useDistrib = modeflag::USE_AUTO; useGpuAccel = modeflag::USE_AUTO; useMulti = modeflag::USE_AUTO;; - autodep_chooseQuregDeployment(numQubits, isDensMatr, useDistrib, useGpuAccel, useMulti, *globalEnvPtr); - - // skip if deployments are unchanged - if (useDistrib == prevDistrib && + // mustUtiliseAllNodes=false: this merely queries sizes, so must never abort upon + // those which createQureg() would reject; we report them as undeployable below + autodep_chooseQuregDeployment(numQubits, isDensMatr, useDistrib, useGpuAccel, useMulti, *globalEnvPtr, false, __func__); + + // createQureg() rejects auto-deployments which would replicate the Qureg between nodes + bool canDeploy = (globalEnvPtr->numNodes == 1) || useDistrib; + + // skip if deployments are unchanged, or remain unavailable (the other deployments + // still vary while unavailable, but are not reported, so must not open a new row) + if (!canDeploy && !prevCanDeploy) + continue; + if (canDeploy == prevCanDeploy && + useDistrib == prevDistrib && useGpuAccel == prevGpuAccel && useMulti == prevMulti) continue; // else prepare string summarising the new deployments (trailing space is fine) - string value = ""; - if (useMulti) - value += "[omp] "; // ordered by #qubits to attempt consistent printed columns - if (useGpuAccel) - value += "[gpu] "; - if (useDistrib) - value += "[mpi] "; + string value = "(no automatic deployment available)"; + if (canDeploy) { + value = ""; + if (useMulti) + value += "[omp] "; // ordered by #qubits to attempt consistent printed columns + if (useGpuAccel) + value += "[gpu] "; + if (useDistrib) + value += "[mpi] "; + } // log the #qubits of the deployment change rows.push_back({printer_toStr(numQubits) + " qubits", value}); @@ -374,6 +389,7 @@ void printQuregAutoDeployments(bool isDensMatr) { prevDistrib = useDistrib; prevGpuAccel = useGpuAccel; prevMulti = useMulti; + prevCanDeploy = canDeploy; } // tailor table title to type of Qureg diff --git a/quest/src/api/qureg.cpp b/quest/src/api/qureg.cpp index fa7c73b05..8cc62e7e4 100644 --- a/quest/src/api/qureg.cpp +++ b/quest/src/api/qureg.cpp @@ -150,7 +150,7 @@ Qureg validateAndCreateCustomQureg(int numQubits, int isDensMatr, int useDistrib validate_newQuregParams(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, env, caller); // automatically overwrite distrib, GPU, and multithread fields which were left as modeflag::USE_AUTO - autodep_chooseQuregDeployment(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, env); + autodep_chooseQuregDeployment(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, env, true, caller); Qureg qureg = qureg_populateNonHeapFields(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread); diff --git a/quest/src/core/autodeployer.cpp b/quest/src/core/autodeployer.cpp index 27c412687..71a339bb3 100644 --- a/quest/src/core/autodeployer.cpp +++ b/quest/src/core/autodeployer.cpp @@ -12,6 +12,7 @@ #include "quest/include/environment.h" #include "quest/src/core/memory.hpp" +#include "quest/src/core/validation.hpp" #include "quest/src/core/autodeployer.hpp" #include "quest/src/comm/comm_config.hpp" #include "quest/src/cpu/cpu_config.hpp" @@ -87,6 +88,44 @@ void chooseWhetherToDistributeQureg(int numQubits, int isDensMatr, int &useDistr } +void assertAutoDeploymentIsDistributed(int numQubits, int isDensMatr, int numEnvNodes, int useDistrib, const char* caller) { + + bool dividesEvenly = (numQubits >= mem_getMinNumQubitsForDistribution(numEnvNodes)); + + static const string indivisibleMsg = + "Automatic deployment cannot distribute this ${NUM_QUBITS} qubit state between the " + "environment's ${NUM_NODES} nodes, because it cannot be divided evenly between them; that " + "requires at least ${MIN_NODE_QUBITS} qubits. Every node would instead redundantly simulate " + "the entire state. Specify the deployment explicitly in order to deliberately forgo " + "distribution, or launch the environment with fewer nodes."; + + static const string undersizedMsg = + "Automatic deployment cannot distribute this ${NUM_QUBITS} qubit state between the " + "environment's ${NUM_NODES} nodes, because each node would then store only 2^${LOCAL_QUBITS} " + "amplitudes, fewer than the 2^${MIN_QUBITS} below which distribution is not automatically " + "chosen. Every node would instead redundantly simulate the entire state. Specify the " + "deployment explicitly in order to deliberately forgo distribution, or launch the " + "environment with fewer nodes."; + + const string& msg = (dividesEvenly)? undersizedMsg : indivisibleMsg; + + tokenSubs vars = (dividesEvenly)? + tokenSubs{ + {"${NUM_QUBITS}", numQubits}, + {"${NUM_NODES}", numEnvNodes}, + {"${LOCAL_QUBITS}", mem_getEffectiveNumStateVecQubitsPerNode(numQubits, isDensMatr, numEnvNodes)}, + {"${MIN_QUBITS}", MIN_NUM_LOCAL_QUBITS_FOR_AUTO_QUREG_DISTRIBUTION}} : + tokenSubs{ + {"${NUM_QUBITS}", numQubits}, + {"${NUM_NODES}", numEnvNodes}, + {"${MIN_NODE_QUBITS}", mem_getMinNumQubitsForDistribution(numEnvNodes)}}; + + // nodes can disagree since deployment consulted their own RAM and VRAM; a lone + // failing node would hang the others inside the error handler's synchronisation + assertAllNodesAgreeThat(useDistrib == 1, msg, vars, caller); +} + + void chooseWhetherToGpuAccelQureg(int numQubits, int isDensMatr, int &useGpuAccel, int numQuregNodes) { // if the flag is already set, don't change it @@ -121,12 +160,15 @@ void chooseWhetherToMultithreadQureg(int numQubits, int isDensMatr, int &useMult } -void autodep_chooseQuregDeployment(int numQubits, int isDensMatr, int &useDistrib, int &useGpuAccel, int &useMultithread, QuESTEnv env) { +void autodep_chooseQuregDeployment(int numQubits, int isDensMatr, int &useDistrib, int &useGpuAccel, int &useMultithread, QuESTEnv env, bool mustUtiliseAllNodes, const char* caller) { // preconditions: // - the given configuration is compatible with env (assured by prior validation) // - this means no deployment is forced (=1) which is incompatible with env + // record whether the user deferred distribution to us, since explicit replication is deliberate + bool wasAutoDistrib = (useDistrib == modeflag::USE_AUTO); + // disable any automatic deployments not permitted by env (it's gauranteed we never overwrite =1 to =0) if (!env.isDistributed) useDistrib = 0; @@ -142,6 +184,13 @@ void autodep_chooseQuregDeployment(int numQubits, int isDensMatr, int &useDistri // overwrite useDistrib chooseWhetherToDistributeQureg(numQubits, isDensMatr, useDistrib, useGpuAccel, env.numNodes); + + // an automatic Qureg deployment must make use of every node, else each would redundantly + // simulate the entire state; only an explicit deployment may forgo distribution. note every + // node reaches this call, so that the assertion within can seek consensus over useDistrib + if (mustUtiliseAllNodes && wasAutoDistrib && env.numNodes > 1) + assertAutoDeploymentIsDistributed(numQubits, isDensMatr, env.numNodes, useDistrib, caller); + int numQuregNodes = (useDistrib)? env.numNodes : 1; // overwrite useGpuAccel @@ -170,5 +219,9 @@ void autodep_chooseFullStateDiagMatrDeployment(int numQubits, int &useDistrib, i // the FullStateDiagMatr is a statevector Qureg. int isDensMatr = 0; - autodep_chooseQuregDeployment(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, env); + // unlike a Qureg, a replicated matrix wastes no nodes; it is merely a local copy of the + // diagonal which every node consults while distributedly modifying its own Qureg partition + bool mustUtiliseAllNodes = false; + + autodep_chooseQuregDeployment(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, env, mustUtiliseAllNodes, __func__); } diff --git a/quest/src/core/autodeployer.hpp b/quest/src/core/autodeployer.hpp index 0a5a8d73c..258a964be 100644 --- a/quest/src/core/autodeployer.hpp +++ b/quest/src/core/autodeployer.hpp @@ -24,7 +24,10 @@ void autodep_chooseQuESTEnvDeployment(int &useDistrib, int &useGpuAccel, int &useMultithread); -void autodep_chooseQuregDeployment(int numQubits, int isDensMatr, int &useDistrib, int &useGpuAccel, int &useMultithread, QuESTEnv env); +// mustUtiliseAllNodes=true forbids automatically replicating the Qureg between the environment's +// nodes, which would leave each redundantly simulating the same state. it is false for +// reportQuESTEnv()'s enumeration, which merely queries every size rather than deploying them +void autodep_chooseQuregDeployment(int numQubits, int isDensMatr, int &useDistrib, int &useGpuAccel, int &useMultithread, QuESTEnv env, bool mustUtiliseAllNodes, const char* caller); void autodep_chooseFullStateDiagMatrDeployment(int numQubits, int &useDistrib, int &useGpuAccel, int &useMultithread, QuESTEnv env); diff --git a/quest/src/core/validation.hpp b/quest/src/core/validation.hpp index b0c08bc58..a253d9dca 100644 --- a/quest/src/core/validation.hpp +++ b/quest/src/core/validation.hpp @@ -17,6 +17,7 @@ #include #include +#include using std::vector; using std::string; @@ -31,6 +32,19 @@ const int validate_STRUCT_PROPERTY_UNKNOWN_FLAG = -1; +/* + * REPORTING + */ + +// map like "${X}" -> 5, with max-size signed int values to prevent overflows. +// in C++11, these can be initialised with {{"${X}", 5}, ...} +using tokenSubs = std::map; + +// exposed beyond validation.cpp so the autodeployer can raise its own errors +void assertAllNodesAgreeThat(bool valid, string msg, tokenSubs vars, const char* func); + + + /* * VALIDATION ERROR HANDLER */