11#include " flagcx_tuner.h"
2+ #include " adaptor.h"
23#include " check.h"
34#include " param.h"
45#include " timer.h"
56#include " tuner/tuner_util.h"
7+ #include " utils.h"
68#include < cfloat>
79#include < iostream>
810#include < map>
911#include < sstream>
1012#include < string>
1113#include < vector>
1214// A category of collective operation. the minimal unit for tuning.
13- struct TunerCollCategory {
14- flagcxCommOp_t collType = flagcxNumCommOps;
15- size_t nBytes = 0 ;
16- };
1715
1816bool operator <(const struct TunerCollCategory &lhs,
1917 const struct TunerCollCategory &rhs) {
@@ -87,9 +85,28 @@ struct TunerProfileKey {
8785 }
8886};
8987
88+ // (collType,nBytes,configIdx)
89+ // Used for counting the number of configs corresponding to each Collective Op
90+ struct TunerCommTagCounterKey {
91+ size_t nBytes;
92+ uint32_t collType; // flagcxCommOp_t
93+ uint32_t commTagIdx; // index of commTag in configList
94+ };
95+
96+ static bool operator <(const struct TunerCommTagCounterKey &lhs,
97+ const struct TunerCommTagCounterKey &rhs) {
98+ if (lhs.nBytes != rhs.nBytes )
99+ return lhs.nBytes < rhs.nBytes ;
100+ if (lhs.collType != rhs.collType )
101+ return lhs.collType < rhs.collType ;
102+ return lhs.commTagIdx < rhs.commTagIdx ;
103+ }
104+
90105// number loops of collectives call before using profiled data.
91106// Each loop will go thoroughly through all search space of all candidates.
92107#define TUNER_SEARCH_NLOOPS 5
108+ #define PROFILE_ROUND \
109+ 2 // Use data from the 3rd round, as it's likely more stable.
93110
94111// customized context structure for internal use
95112struct flagcxTunerContext {
@@ -109,6 +126,8 @@ struct flagcxTunerContext {
109126 std::map<TunerCollCategory, int >
110127 collBestCommMap; // record the best communicator for each collective
111128 // category. value is comm index in configList.
129+ std::map<struct TunerCommTagCounterKey , int >
130+ configCounterMap; // record per (collType,nBytes,configIdx) counter.
112131
113132 // timer
114133 flagcxTimer<TunerProfileKey> timer;
@@ -147,10 +166,11 @@ flagcxResult_t flagcxTunerInit(size_t nRanks, size_t nNodes,
147166 ctx->logger = logFunction;
148167 *context = ctx;
149168
150- // Initialize commTagIdxMap
169+ // Initialize commTagIdxMap and activeCommList
151170 for (size_t i = 0 ; i < ctx->configList .size (); ++i) {
152171 const auto &cfg = ctx->configList [i];
153172 ctx->commTagIdxMap [cfg.commTag ] = i;
173+ ctx->activeCommList .push_back (i);
154174 }
155175
156176 // Whether comm tag specified by environment variable
@@ -215,7 +235,6 @@ flagcxResult_t flagcxTunerSetCandidate(void *context, uint32_t index,
215235 const auto &curCfg = ctx->configList [index];
216236 FLAGCXCHECK (setEnvConfig (curCfg, FLAGCX_ENV_TYPE_CREATION ));
217237 *commTag = curCfg.commTag ;
218- ctx->activeCommList .push_back (index);
219238 return flagcxSuccess;
220239}
221240
@@ -226,14 +245,14 @@ static int getCommIdxFromSeqId(const struct flagcxTunerContext *ctx,
226245 if (ctx->activeCommList .size () == 0 ) {
227246 return -1 ;
228247 }
229- return ctx->activeCommList [seqId % ctx->activeCommList . size () ];
248+ return ctx->activeCommList [seqId / ctx->searchNLoops ];
230249}
231250
232251// Given a communicator index in configList, get the corresponding startup phase
233252// seqId for specific round. Logic must be consistent with getCommIdxFromSeqId.
234253static int getSeqIdForCommIdx (const struct flagcxTunerContext *ctx, int commIdx,
235254 uint32_t round) {
236- int seqId = round * ctx-> activeCommList . size () ;
255+ int seqId = 0 ;
237256 bool found = false ;
238257 for (const auto &idx : ctx->activeCommList ) {
239258 if (idx != commIdx) {
@@ -243,7 +262,7 @@ static int getSeqIdForCommIdx(const struct flagcxTunerContext *ctx, int commIdx,
243262 break ;
244263 }
245264 }
246- return (found ? seqId : -1 );
265+ return (found ? ( seqId * ctx-> searchNLoops ) + round : -1 );
247266}
248267
249268// add a small factor to avoid switching between two close communicators caused
@@ -261,13 +280,11 @@ static flagcxResult_t findBestComm(struct flagcxTunerContext *ctx,
261280 int bestCommIdx = -1 ; // index of best communicator in configList
262281 float minTime = FLT_MAX ;
263282 // calculate the best communicator based on profiling data
283+ const uint32_t profileDataRound = PROFILE_ROUND ;
264284 for (const auto &idx : ctx->activeCommList ) {
265- // For now, use round = 2 as the time of that collective category.
266- const uint32_t kProfileDataRound =
267- 2 ; // Use data from the 3rd round, as it's likely more stable.
268285 int seqId = getSeqIdForCommIdx (
269286 ctx, idx,
270- std::min (kProfileDataRound ,
287+ std::min (profileDataRound ,
271288 static_cast <uint32_t >(ctx->searchNLoops - 1 )));
272289 TunerProfileKey profileKey (cat.nBytes , static_cast <uint32_t >(cat.collType ),
273290 static_cast <uint32_t >(seqId), idx);
@@ -327,17 +344,60 @@ static flagcxResult_t findBestComm(struct flagcxTunerContext *ctx,
327344 return flagcxSuccess;
328345}
329346
347+ flagcxResult_t flagcxCreateOrReplaceHomoComm (
348+ flagcxComm_t *comm, struct flagcxTunerContext *ctx, uint32_t seqId,
349+ const struct TunerCollCategory &collCat, bool createBest) {
350+
351+ // If a communicator has already been created for the corresponding collCat in
352+ // comm->homoCommMap, delete it before creating a new one to ensure that each
353+ // collCat has only one communicator.
354+ auto it = (*comm)->homoCommMap .find (collCat);
355+ if (it != (*comm)->homoCommMap .end ()) {
356+ // Destroy Comm of collCat
357+ FLAGCXCHECK (cclAdaptors[flagcxCCLAdaptorDevice]->commDestroy (it->second ));
358+ // Remove entry from map
359+ (*comm)->homoCommMap .erase (it);
360+ }
361+
362+ uint32_t nConfigs = 0 ;
363+ uint32_t idx = getCommIdxFromSeqId (ctx, seqId);
364+ struct flagcxCommTag tag = {" " };
365+ FLAGCXCHECK (flagcxTunerSetCandidate ((*comm)->tunerContext , idx, &tag));
366+ FLAGCXCHECK (
367+ (*comm)->tuner ->getCandidateNumber ((*comm)->tunerContext , &nConfigs));
368+ if (createBest) {
369+ INFO (FLAGCX_INIT | FLAGCX_TUNING ,
370+ " create the communicator of the best Config (CommId = %d)" ,
371+ ctx->collBestCommMap [collCat]);
372+ } else {
373+ INFO (FLAGCX_INIT | FLAGCX_TUNING ,
374+ " start to prepare communicator tag=%s(%u/%u)" , tag.tag , idx, nConfigs);
375+ }
376+
377+ flagcxInnerComm_t innerComm = NULL ;
378+ FLAGCXCHECK (flagcxHomoCommInit (
379+ (*comm)->commId , (*comm)->uniqueIdData ,
380+ (struct bootstrapState *)((*comm)->tuner ->bootstrap ), *comm, &innerComm));
381+ // Store new communicator of collCat into homoCommMap
382+ (*comm)->homoCommMap [collCat] = innerComm;
383+ // For backward compatible, also assign homo_comm field.
384+ (*comm)->homo_comm = innerComm;
385+ return flagcxSuccess;
386+ }
387+
330388// Communicator selection logic:
331- // Always favor the communicator specified by environment variable if possible.
332- // Otherwise,
333- // for the first searchNLoops * activeCommCount collectives {collType, nBytes}
334- // we will cycle through all the communicators use round-robin policy.
335- // after that, we will select the best communicator based on profiling data
336- // if no profiling data available, we will return flagcxInternalError for now.
389+ // 1) Honor environment override when ctx->envTagIdx is set.
390+ // 2) Otherwise, for the initial searchNLoops * activeCommCount invocations of
391+ // each {collType, nBytes}, cycle through ctx->activeCommList via seqId
392+ // (tuning phase).
393+ // 3) After the tuning window, rely on the best communicator recorded in
394+ // ctx->collBestCommMap (populated via profiling). If no best entry exists,
395+ // return flagcxInternalError.
337396flagcxResult_t flagcxTunerGetCollInfo (void *context, flagcxCommOp_t collType,
338397 size_t nBytes, int numPipeOps,
339398 float **collCostTable, int regBuff,
340- struct flagcxCommTag *commTag) {
399+ struct flagcxCommTag *commTag,
400+ flagcxComm_t *comm) {
341401 struct flagcxTunerContext *ctx =
342402 static_cast <struct flagcxTunerContext *>(context);
343403 // Use env comm tag when possible.
@@ -350,8 +410,7 @@ flagcxResult_t flagcxTunerGetCollInfo(void *context, flagcxCommOp_t collType,
350410 return flagcxSuccess;
351411 }
352412
353- // for the first searchNLoops * activeCommCount collectives, use round-robin
354- // policy
413+ // get a seqId for {collType, nBytes}
355414 struct TunerCollCategory collCat = {collType, nBytes};
356415 auto it = ctx->collSeqMap .find (collCat);
357416 uint32_t seqId = 0 ;
@@ -363,38 +422,78 @@ flagcxResult_t flagcxTunerGetCollInfo(void *context, flagcxCommOp_t collType,
363422 }
364423
365424 if (seqId < ctx->searchNLoops * ctx->activeCommList .size ()) {
366- int idx = getCommIdxFromSeqId (ctx, seqId);
367- if (idx == -1 ) {
425+
426+ // Every {collType, nBytes, commTagIdx} will be profiled searchNLoops times.
427+ int cfgIdx = getCommIdxFromSeqId (ctx, seqId);
428+ if (cfgIdx == -1 ) {
368429 WARN (" No active communicator found for startup phase seqId=%u." , seqId);
369430 return flagcxInternalError;
370431 }
371- const auto &cfg = ctx->configList [idx];
372- FLAGCXCHECK (setEnvConfig (cfg, FLAGCX_ENV_TYPE_COLL ));
432+ TunerCommTagCounterKey key{nBytes, static_cast <uint32_t >(collType),
433+ static_cast <uint32_t >(cfgIdx)};
434+ auto cit = ctx->configCounterMap .find (key);
435+ if (cit == ctx->configCounterMap .end ()) {
436+ // create a new communicator and destroy old communicator
437+ FLAGCXCHECK (
438+ flagcxCreateOrReplaceHomoComm (comm, ctx, seqId, collCat, false ));
439+ (*comm)->tunerInnerComm = (*comm)->homoCommMap [collCat];
440+ ctx->configCounterMap [key] = 1 ;
441+ } else {
442+ // use old communicator
443+ (*comm)->tunerInnerComm = (*comm)->homoCommMap [collCat];
444+ ctx->configCounterMap [key]++;
445+ }
446+ const auto &cfg = ctx->configList [cfgIdx];
373447 *commTag = cfg.commTag ;
374- INFO (FLAGCX_TUNING , " Use Communicator tag %s in startup phase seqId=%u." ,
375- commTag->tag , seqId);
448+ FLAGCXCHECK (setEnvConfig (cfg, FLAGCX_ENV_TYPE_COLL ));
376449 return flagcxSuccess;
377450 }
378451
379452 // Select a communicator from active communicators based on profiling data
380453 // after searchNLoops * activeCommCount collectives. If we do not have a best
381454 // communicator recorded for this collective category, find it.
382- if (ctx->collBestCommMap .find (collCat) == ctx->collBestCommMap .end ()) {
455+ if ((*comm)->homoBestCommMap [collCat] == nullptr ) {
456+ // Find the best config
383457 FLAGCXCHECK (findBestComm (ctx, collCat));
458+ // Check whether the optimal config has been found; if not, return an error.
459+ auto it2 = ctx->collBestCommMap .find (collCat);
460+ if (it2 == ctx->collBestCommMap .end ()) {
461+ WARN (" No best communicator found for collective type %d with size %zu." ,
462+ collType, nBytes);
463+ return flagcxInternalError;
464+ }
465+ // If the optimal config has been found, create a communicator of best
466+ // config
467+ const uint32_t profileDataRound = PROFILE_ROUND ;
468+ uint32_t bestSeqId = getSeqIdForCommIdx (
469+ ctx, it2->second ,
470+ std::min (profileDataRound,
471+ static_cast <uint32_t >(ctx->searchNLoops - 1 )));
472+ FLAGCXCHECK (
473+ flagcxCreateOrReplaceHomoComm (comm, ctx, bestSeqId, collCat, true ));
474+ auto &cfg = ctx->configList [it2->second ];
475+ FLAGCXCHECK (setEnvConfig (cfg, FLAGCX_ENV_TYPE_COLL ));
476+ *commTag = cfg.commTag ;
477+ (*comm)->tunerInnerComm = (*comm)->homoCommMap [collCat];
478+ // Store the best communicator of collCat into homoBestCommMap
479+ (*comm)->homoBestCommMap [collCat] = (*comm)->homoCommMap [collCat];
480+ } else {
481+ // The best communicator has been created
482+ // get it in collBestCommMap directly
483+ auto it2 = ctx->collBestCommMap .find (collCat);
484+ if (it2 == ctx->collBestCommMap .end ()) {
485+ WARN (" No best communicator found for collective type %d with size %zu." ,
486+ collType, nBytes);
487+ return flagcxInternalError;
488+ }
489+ auto &cfg = ctx->configList [it2->second ];
490+ FLAGCXCHECK (setEnvConfig (cfg, FLAGCX_ENV_TYPE_COLL ));
491+ *commTag = cfg.commTag ;
492+ (*comm)->tunerInnerComm = (*comm)->homoBestCommMap [collCat];
493+ INFO (FLAGCX_TUNING ,
494+ " Use Communicator tag %s based on profile data, seqId=%d." ,
495+ commTag->tag , seqId);
384496 }
385-
386- // Use the best communicator calculated earlier.
387- auto it2 = ctx->collBestCommMap .find (collCat);
388- if (it2 == ctx->collBestCommMap .end ()) {
389- WARN (" No best communicator found for collective type %d with size %zu." ,
390- collType, nBytes);
391- return flagcxInternalError;
392- }
393- auto &cfg = ctx->configList [it2->second ];
394- FLAGCXCHECK (setEnvConfig (cfg, FLAGCX_ENV_TYPE_COLL ));
395- *commTag = cfg.commTag ;
396- INFO (FLAGCX_TUNING , " Use Communicator tag %s based on profile data." ,
397- commTag->tag );
398497 return flagcxSuccess;
399498}
400499
@@ -493,4 +592,5 @@ flagcxTuner_t internalTuner = {"internal tuner",
493592 flagcxTunerGetCollInfo,
494593 flagcxTunerStartProfiling,
495594 flagcxTunerStopProfiling,
496- flagcxTunerDestroy};
595+ flagcxTunerDestroy,
596+ flagcxCreateOrReplaceHomoComm};
0 commit comments