nccl-reader / nccl

Optimized primitives for collective multi-GPU communication
Other
3 stars 0 forks source link

[tsuji] 読み進めメモ #3

Open tyohei opened 5 years ago

tyohei commented 5 years ago

疑問

AllReduce の動作

AllReduce の main

ncclEnqueueCheck enqueue.cc#L409


saveKernel(info) enqueue.cc#L356

For 文の中身

まだ良くわからない

computeColl(info, &coll, &proxyArgs)

  1. info から coll へ情報(root, count, send/recv buffers, devComm, opCount)をコピー
  2. info から proxyArgs へ情報(nsteps, sliceSteps, chunkSteps, llMode, opCount)を計算及びコピー
    • その際に llMode or treeMode を判定している → これらの違いは?
    • llMode の意味は?
    • sliceSteps の意味は?
    • chunkSteps の意味は?

ncclBarrierEnqueue(info->comm)

基本的には User stream (nccl の通信を呼び出すストリーミ?、デフォルトストリーミかな)、とパラメータで指定されたストリーム(グループの場合もある)の違いによって、 cudaStreamWaitEvent を呼び出すようになっている

このあとに if(isLast) があるので、intra のプロセスを待ち、最後のプロセスが処理をするようになっている

    if (comm->launchMode == ncclComm::GROUP) {
      // I'm the last. Launch all operations.
      NCCLCHECK(ncclLaunchCooperativeKernelMultiDevice(comm->intraParams, comm->intraCudaDevs, comm->intraRanks, *comm-
    }
    NCCLCHECK(ncclCpuBarrierLast(comm));

こんな処理


ncclBarrierEnqueueWait(info->comm)

// Start the network proxies as soon as the kernel has been launched. We can't // perform any CUDA call between the two or having a cudaFree between the CUDA // launch and the transportStartProxy call could cause a deadlock. // Also, starting the proxies after the CUDA launch seems to be better for // performance (latency).


ncclEnqueueEvents(comm)

必要ならストリームを待つのと、 userStreamSet を初期化する

けっこう単純な関数

ncclResult_t ncclEnqueueEvents(ncclComm_t comm) {
  struct cudaLaunchParams *params = comm->myParams;
  // Enqueue event after NCCL kernel
  CUDACHECK(cudaEventRecord(comm->doneEvent, params->stream));
  // Use internal NCCL stream for CGMD/GROUP launch if required or if the user stream is NULL
  if (comm->launchMode == ncclComm::GROUP && (comm->groupCudaStream || comm->userStream == NULL)) {
    // Create dependency between NCCL internal stream and user stream
    CUDACHECK(cudaStreamWaitEvent(comm->userStream, comm->doneEvent, 0));
  }
  comm->userStreamSet = false;
  return ncclSuccess;
}
tyohei commented 5 years ago

ncclColl

struct ncclColl {
  union {
    struct {
      struct CollectiveArgs args;
      uint16_t funcIndex;
      uint16_t nextIndex;
      uint8_t  active;
    };
    int data[0x10];
  };
};
tyohei commented 5 years ago

ncclInfo

struct ncclInfo {
  ncclColl_t coll;
  const char* opName;
  // NCCL Coll Args
  const void* sendbuff;
  void* recvbuff;
  size_t count;
  ncclDataType_t datatype;
  ncclRedOp_t op;
  int root;
  ncclComm_t comm;
  cudaStream_t stream;
  // Algorithm details
  int chunkSteps;
  int sliceSteps;
  // Computed later
  ncclPattern_t pattern;
  size_t nBytes;
  int nstepsPerLoop;
  int nchunksPerLoop;
};
tyohei commented 5 years ago

CollectiveArgs

struct CollectiveArgs {
  struct ncclDevComm* comm;
  uint64_t opCount;

  // local and remote input, output, and buffer
  const void * ThisInput;
  void * ThisOutput;

  // general parameters
  size_t N;
  uint32_t root;
  uint8_t bid;
  uint8_t nChannels;
  uint16_t nThreads;

  int lastChunkSize;
};
tyohei commented 5 years ago

今までのを整理

次は myParams に意識して saveKernel を読んでいく

tyohei commented 5 years ago

saveKernel

computeColl

For 文

tyohei commented 5 years ago

9/1

理由: CUDAカーネルが実際にローンチされているのは ncclBarrierEnqueueWait の関数内。 で、このカーネルの実引数は info->comm->myParams が渡されてる で、この info->comm->myParamssaveKernel 内で色々定義されている

tyohei commented 5 years ago
init.cc:576:  params->blockDim.x = 0; params->blockDim.y = params->blockDim.z = 1;

変更されるのは blockDim.x のみ、 blockDim.yblockDim.z は常に 1 に固定

init.cc:577:  params->gridDim.x = 0; params->gridDim.y = params->gridDim.z = 1;

変更されるのは blockDim.x のみ、 blockDim.yblockDim.z は常に 1 に固定

tyohei commented 5 years ago

ncclChannel

struct ncclChannel {
  union {
    struct {
      struct ncclRing ring;
      struct ncclTree tree;

      int id;
      int nthreads;
      int buffSize;

      // Communication structures
      struct ncclPeer* peers;
      struct ncclPeer* devPeers;

      // Operation list for aggregation
      struct ncclColl* collectives;
      struct ncclColl* devCollectives;
      int collStart;
      int collCount;
      int collFifoHead; // Only used by GPU
      int collFifoTail; // Only used by CPU
    };
    int data[0x80];
  };
};
static_assert(sizeof(struct ncclChannel) == 0x80*sizeof(int), "ncclChannel must have a pow2 size");
tyohei commented 5 years ago

info->comm->myParams->gridDim.x == coll.args.nChannels が成立 が普通に利用する場合は gridDim.x == nChannels が成立する

coll.args.nChannels <= info->comm->nChannels が成立 coll.args.nChannelssaveKernel() 内の computeColl() で定義される info->comm->nChannelsncclInitRank() 内の ncclGetRings() で定義される

info->comm->myParams->blockDim.x >= coll.args.nThreads が成立 が普通に利用する場合は blockDim.x == nThreads - 1 が成立する

For 文の中身を呼んでいるが、

struct ncclChannel* channel = info->comm->channels+(info->comm->myParams->gridDim.x % info->comm->nChannels);

ここで、同じ channel になりなえない気がする。んー

tyohei commented 5 years ago

ncclProxyArgs

struct ncclProxyArgs {
  proxyProgressFunc_t progress;
  struct ncclChannel* channel;
  struct ncclConnector* connector;
  int sliceSteps;
  int chunkSteps;
  int nsteps;
  uint64_t opCount;
  int llMode;
  int state;   // add component before this line -- it is left out during initialization

  // Internal state
  uint64_t head;
  uint64_t tail;
  uint64_t end;
  void* requests[NCCL_STEPS];
  int idle;

  // Element linking
  pthread_mutex_t mutex;
  struct ncclProxyArgs* next;
  struct ncclProxyArgs* nextPeer;
};
tyohei commented 5 years ago

煮詰まってきたのでここまでのを整理

AllReduce (mode=PARALLEL) の全体の流れ

saveKernel の流れ

次読むもの