Skip to content

Commit 494deb1

Browse files
authored
[CRL] From creating all communicators to creating a single communicator (flagos-ai#304)
1 parent 386be5a commit 494deb1

6 files changed

Lines changed: 223 additions & 102 deletions

File tree

flagcx/core/flagcx_tuner.cc

Lines changed: 143 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
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

1816
bool 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
95112
struct 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.
234253
static 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.
337396
flagcxResult_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};

flagcx/core/flagcx_tuner.h

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
#ifndef FLAGCX_TUNER_H_
22
#define FLAGCX_TUNER_H_
33

4-
#include "tuner.h"
4+
#include "../adaptor/include/tuner.h"
5+
6+
// A category of collective operation. the minimal unit for tuning.
7+
struct TunerCollCategory {
8+
flagcxCommOp_t collType;
9+
size_t nBytes;
10+
};
11+
12+
bool operator<(const struct TunerCollCategory &lhs,
13+
const struct TunerCollCategory &rhs);
514

615
struct flagcxTuner {
716
// Name of the tuner
@@ -62,7 +71,8 @@ struct flagcxTuner {
6271
flagcxResult_t (*getCollInfo)(void *context, flagcxCommOp_t collType,
6372
size_t nBytes, int numPipeOps,
6473
float **collCostTable, int regBuff,
65-
struct flagcxCommTag *commTag);
74+
struct flagcxCommTag *commTag,
75+
flagcxComm_t *comm);
6676

6777
// Start profiling for a specific collective with given parameters.
6878
// Inputs:
@@ -91,6 +101,11 @@ struct flagcxTuner {
91101

92102
// Terminates the tuner and cleans up any resources that the tuner allocated.
93103
flagcxResult_t (*destroy)(void *context);
104+
105+
// Create/destroy communicator
106+
flagcxResult_t (*createOrReplaceHomoComm)(
107+
flagcxComm_t *comm, struct flagcxTunerContext *ctx, uint32_t seqId,
108+
const struct TunerCollCategory &collCat, bool createBest);
94109
};
95110

96111
typedef struct flagcxTuner flagcxTuner_t;
@@ -102,19 +117,17 @@ bool operator==(const struct flagcxCommTag &lhs,
102117

103118
extern flagcxTuner_t internalTuner;
104119

120+
// On-demand communicator lifecycle helpers implemented in flagcx/flagcx.cc
121+
flagcxResult_t flagcxCreateHomoCommForTag(flagcxComm_t comm, uint32_t idx);
122+
flagcxResult_t flagcxDestroyHomoCommByTag(flagcxComm_t comm, uint32_t idx);
123+
105124
#define FLAGCXCALLWITHTUNER(call, comm, commOp, count, datatype, stream) \
106125
do { \
107126
comm->tunerInnerComm = nullptr; \
108127
size_t nBytes = count * getFlagcxDataTypeSize(datatype); \
109128
struct flagcxCommTag tag = {""}; \
110129
FLAGCXCHECK(comm->tuner->getCollInfo(comm->tunerContext, commOp, nBytes, \
111-
0, NULL, 0, &tag)); \
112-
const auto it = comm->homoCommMap.find(tag); \
113-
if (it == comm->homoCommMap.end()) { \
114-
WARN("communicator %s was not initialized.", tag.tag); \
115-
return flagcxInternalError; \
116-
} \
117-
comm->tunerInnerComm = it->second; \
130+
0, NULL, 0, &tag, &comm)); \
118131
flagcxProfileKey pkey; \
119132
FLAGCXCHECK(comm->tuner->startProfiling(comm->tunerContext, commOp, \
120133
nBytes, stream, &tag, &pkey)); \

flagcx/core/global_comm.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ struct flagcxComm {
4242
flagcxInnerComm_t homo_comm;
4343
flagcxHeteroComm_t hetero_comm;
4444
flagcxInnerComm_t homoInterComm;
45-
flagcxInnerComm_t tunerInnerComm; // innerComm selected by tuner
4645
// experimental for multi-nic support
4746
int homoInterRootRank;
4847
int homoInterMyRank;
@@ -51,8 +50,13 @@ struct flagcxComm {
5150
std::vector<flagcxVendorType> clusterVendorMap;
5251
struct flagcxTuner *tuner;
5352
void *tunerContext;
54-
std::map<struct flagcxCommTag, flagcxInnerComm_t>
53+
std::map<struct TunerCollCategory, flagcxInnerComm_t>
5554
homoCommMap; // key: commTag returned by tuner
55+
std::map<struct TunerCollCategory, flagcxInnerComm_t>
56+
homoBestCommMap; // key: commTag returned by tuner
57+
flagcxInnerComm_t tunerInnerComm; // innerComm selected by tuner
58+
flagcxUniqueId_t commId;
59+
flagcxUniqueId *uniqueIdData;
5660
};
5761

5862
#endif // end include guard

0 commit comments

Comments
 (0)