Skip to content

Commit 5314e45

Browse files
committed
fix: stabilize distributed training resume flow
Track DataLoader progress by global batches so distributed ranks slice data consistently and can resume/cycle from saved consumption counts. Also scope CCL unique ID files per run, generate NCCL IDs only on the main rank, clean up run-local rendezvous files, and add DataLoader coverage.
1 parent ee4695c commit 5314e45

15 files changed

Lines changed: 277 additions & 77 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,9 @@ The following examples demonstrate **LLaMA 3 supervised fine-tuning (SFT)** usin
121121
./infini_run \
122122
--nnodes=2 \
123123
--nproc_per_node=1 \
124-
--node_rank=[rank_id] \
125-
-- ./llama3 \
124+
--node_rank=[rank_id] \
125+
--rdzv_endpoint=[master_addr]:29500 \
126+
./llama3 \
126127
--device cuda \
127128
--input_bin [training_data_path] \
128129
--llmc_filepath [model_path] \

example/gpt2/main.cc

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -374,15 +374,17 @@ void Train(const nn::parallel::Rank &rank) {
374374
start_step = resume_result.global_step;
375375
size_t consumed_batches = resume_result.consumed_batches;
376376

377-
// TODO(jym): Replace with Sampler abstraction when available.
378-
// Skip dataloader to resume from the correct batch position.
379-
if (consumed_batches > 0) {
380-
size_t start = train_iter.BatchIndex();
381-
// Each rank processes every ddp_world_size-th batch starting from its own rank.
382-
// num_skips calculates how many ++ iterations to reach the saved batch position.
383-
size_t num_skips = (consumed_batches - start) / ddp_world_size;
384-
for (size_t i = 0; i < num_skips; ++i) { ++train_iter; }
385-
}
377+
// consumed_batches is the number of global dataloader batches consumed across cyclic epochs.
378+
train_iter.SeekGlobalBatch(consumed_batches % train_loader.NumGlobalBatches());
379+
auto next_train_batch = [&]() {
380+
auto batch = *train_iter;
381+
++train_iter;
382+
if (train_iter == train_loader.end()) {
383+
train_iter = train_loader.begin();
384+
}
385+
++consumed_batches;
386+
return batch;
387+
};
386388

387389
auto save_checkpoint = [&](const std::filesystem::path &save_dir, int64_t global_step) {
388390
SaveCheckpoint({
@@ -457,11 +459,7 @@ void Train(const nn::parallel::Rank &rank) {
457459
infini_train::AutocastGuard autocast_guard(device.type(), dtype);
458460

459461
// (bs, seq_len), (bs, seq_len)
460-
auto [x, y] = *train_iter;
461-
// if we are trying to overfit a single batch, we reset the loader here by commenting out the line below
462-
// TODO(dcj): support dataloader.reset() later
463-
++train_iter;
464-
consumed_batches = train_iter.BatchIndex();
462+
auto [x, y] = next_train_batch();
465463
x = std::make_shared<Tensor>(x->To(device));
466464
y = std::make_shared<Tensor>(y->To(device));
467465

@@ -491,11 +489,7 @@ void Train(const nn::parallel::Rank &rank) {
491489
scheduler->Step();
492490
}
493491
} else {
494-
auto [x, y] = *train_iter;
495-
// if we are trying to overfit a single batch, we reset the loader here by commenting out the line below
496-
// TODO(dcj): support dataloader.reset() later
497-
++train_iter;
498-
consumed_batches = train_iter.BatchIndex();
492+
auto [x, y] = next_train_batch();
499493
x = std::make_shared<Tensor>(x->To(device));
500494
y = std::make_shared<Tensor>(y->To(device));
501495

example/llama3/main.cc

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -354,15 +354,17 @@ void Train(const nn::parallel::Rank &rank) {
354354
start_step = resume_result.global_step;
355355
size_t consumed_batches = resume_result.consumed_batches;
356356

357-
// TODO(jym): Replace with Sampler abstraction when available.
358-
// Skip dataloader to resume from the correct batch position.
359-
if (consumed_batches > 0) {
360-
size_t start = train_iter.BatchIndex();
361-
// Each rank processes every ddp_world_size-th batch starting from its own rank.
362-
// num_skips calculates how many ++ iterations to reach the saved batch position.
363-
size_t num_skips = (consumed_batches - start) / ddp_world_size;
364-
for (size_t i = 0; i < num_skips; ++i) { ++train_iter; }
365-
}
357+
// consumed_batches is the number of global dataloader batches consumed across cyclic epochs.
358+
train_iter.SeekGlobalBatch(consumed_batches % train_loader.NumGlobalBatches());
359+
auto next_train_batch = [&]() {
360+
auto batch = *train_iter;
361+
++train_iter;
362+
if (train_iter == train_loader.end()) {
363+
train_iter = train_loader.begin();
364+
}
365+
++consumed_batches;
366+
return batch;
367+
};
366368

367369
auto save_checkpoint = [&](const std::filesystem::path &save_dir, int64_t global_step) {
368370
SaveCheckpoint({
@@ -435,11 +437,7 @@ void Train(const nn::parallel::Rank &rank) {
435437
infini_train::AutocastGuard autocast_guard(device.type(), dtype);
436438

437439
// (bs, seq_len), (bs, seq_len)
438-
auto [x, y] = *train_iter;
439-
// if we are trying to overfit a single batch, we reset the loader here by commenting out the line below
440-
// TODO(dcj): support dataloader.reset() later
441-
++train_iter;
442-
consumed_batches = train_iter.BatchIndex();
440+
auto [x, y] = next_train_batch();
443441
x = std::make_shared<Tensor>(x->To(device));
444442
y = std::make_shared<Tensor>(y->To(device));
445443

@@ -468,11 +466,7 @@ void Train(const nn::parallel::Rank &rank) {
468466
scheduler->Step();
469467
}
470468
} else {
471-
auto [x, y] = *train_iter;
472-
// if we are trying to overfit a single batch, we reset the loader here by commenting out the line below
473-
// TODO(dcj): support dataloader.reset() later
474-
++train_iter;
475-
consumed_batches = train_iter.BatchIndex();
469+
auto [x, y] = next_train_batch();
476470
x = std::make_shared<Tensor>(x->To(device));
477471
y = std::make_shared<Tensor>(y->To(device));
478472

infini_train/include/core/ccl/ccl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class CclImpl {
2828

2929
virtual void GetAsyncError(const CclComm *comm, CclStatus *async_error) const;
3030

31-
virtual void GetUniqueId(CclUniqueId **unique_id) const;
31+
virtual void CreateUniqueId(CclUniqueId **unique_id, bool generate_id) const;
3232

3333
virtual void CommInitAll(CclComm **comms, int ndev, const int *devlist) const;
3434

infini_train/include/dataloader.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Tensor;
1212
namespace infini_train {
1313
class DataLoaderIterator {
1414
public:
15-
DataLoaderIterator(const Dataset &dataset, size_t batch_size, size_t batch_idx, size_t max_batch_idx,
15+
DataLoaderIterator(const Dataset &dataset, size_t batch_size, size_t global_batch_idx, size_t num_global_batches,
1616
size_t ddp_rank = 0, size_t ddp_world_size = 1);
1717

1818
std::pair<std::shared_ptr<Tensor>, std::shared_ptr<Tensor>> operator*() const;
@@ -24,13 +24,14 @@ class DataLoaderIterator {
2424
friend bool operator!=(const DataLoaderIterator &lhs, const DataLoaderIterator &rhs);
2525
friend bool operator==(const DataLoaderIterator &lhs, const DataLoaderIterator &rhs);
2626

27-
size_t BatchIndex() const;
27+
size_t GlobalBatchIndex() const;
28+
DataLoaderIterator &SeekGlobalBatch(size_t global_batch_idx);
2829

2930
private:
3031
const Dataset *dataset_ = nullptr; // not owned
3132
size_t batch_size_ = 0;
32-
size_t batch_idx_ = 0;
33-
size_t max_batch_idx_ = 0;
33+
size_t global_batch_idx_ = 0;
34+
size_t num_global_batches_ = 0;
3435
size_t ddp_rank_ = 0;
3536
size_t ddp_world_size_ = 1;
3637
};
@@ -42,10 +43,12 @@ class DataLoader {
4243
virtual DataLoaderIterator begin() const;
4344
virtual DataLoaderIterator end() const;
4445

46+
size_t NumGlobalBatches() const;
47+
4548
protected:
4649
std::shared_ptr<Dataset> dataset_;
4750
size_t batch_size_ = 0;
48-
size_t max_batch_idx_ = 0;
51+
size_t num_global_batches_ = 0;
4952
};
5053

5154
class DistributedDataLoader : public DataLoader {

infini_train/src/core/ccl/ccl.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ void CclImpl::GetAsyncError(const CclComm *comm, CclStatus *async_error) const {
1616
LOG(FATAL) << "CclImpl::GetAsyncError is not implemented.";
1717
}
1818

19-
void CclImpl::GetUniqueId(CclUniqueId **unique_id) const { LOG(FATAL) << "CclImpl::GetUniqueId is not implemented."; }
19+
void CclImpl::CreateUniqueId(CclUniqueId **, bool) const {
20+
LOG(FATAL) << "CclImpl::CreateUniqueId is not implemented.";
21+
}
2022

2123
void CclImpl::CommInitAll(CclComm **comms, int ndev, const int *devlist) const {
2224
LOG(FATAL) << "CclImpl::CommInitAll is not implemented.";

infini_train/src/core/ccl/ccl_utils.cc

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <chrono>
44
#include <cstdio>
5+
#include <cstdlib>
56
#include <filesystem>
67
#include <fstream>
78
#include <iterator>
@@ -11,26 +12,36 @@
1112

1213
namespace infini_train::core {
1314
namespace {
14-
std::string UniqueIdFileName(const std::string &name, bool tmp = false) {
15-
return "cclUniqueId_" + name + (tmp ? ".tmp" : ".bin");
15+
std::string UniqueIdPath(const std::string &pg_name) {
16+
const char *run_id = std::getenv("INFINI_RUN_ID");
17+
const std::string prefix = run_id == nullptr ? "" : std::string(run_id) + "_";
18+
return "cclUniqueId_" + prefix + pg_name + ".bin";
19+
}
20+
21+
std::string UniqueIdTmpPath(const std::string &pg_name) {
22+
const char *run_id = std::getenv("INFINI_RUN_ID");
23+
const std::string prefix = run_id == nullptr ? "" : std::string(run_id) + "_";
24+
return "cclUniqueId_" + prefix + pg_name + ".tmp";
1625
}
1726
} // namespace
1827

1928
void WriteUniqueIdFile(const CclUniqueId &unique_id, const std::string &pg_name) {
20-
const std::string tmp_path = UniqueIdFileName(pg_name, true);
29+
const std::string tmp_path = UniqueIdTmpPath(pg_name);
2130

2231
std::ofstream ofs(tmp_path, std::ios::binary);
2332
CHECK(ofs.good()) << "Failed to open unique_id tmp file for write: " << tmp_path;
2433
const size_t size = unique_id.Size();
2534
ofs.write(reinterpret_cast<const char *>(unique_id.Data()), static_cast<std::streamsize>(size));
2635
ofs.close();
2736

28-
std::rename(tmp_path.c_str(), UniqueIdFileName(pg_name).c_str());
37+
const std::string file_path = UniqueIdPath(pg_name);
38+
CHECK_EQ(std::rename(tmp_path.c_str(), file_path.c_str()), 0)
39+
<< "Failed to rename unique_id file from " << tmp_path << " to " << file_path;
2940
}
3041

3142
void ReadUniqueIdFile(CclUniqueId *unique_id, const std::string &pg_name) {
3243
CHECK_NOTNULL(unique_id);
33-
const std::string file_path = UniqueIdFileName(pg_name);
44+
const std::string file_path = UniqueIdPath(pg_name);
3445

3546
while (!std::filesystem::exists(file_path)) { std::this_thread::sleep_for(std::chrono::microseconds(1000)); }
3647

@@ -46,10 +57,15 @@ void ReadUniqueIdFile(CclUniqueId *unique_id, const std::string &pg_name) {
4657
}
4758

4859
void CleanupUniqueIdFile(const std::string &pg_name) {
49-
const std::string file_path = UniqueIdFileName(pg_name);
60+
const std::string file_path = UniqueIdPath(pg_name);
5061
if (std::filesystem::exists(file_path)) {
5162
std::filesystem::remove(file_path);
5263
}
64+
65+
const std::string tmp_path = UniqueIdTmpPath(pg_name);
66+
if (std::filesystem::exists(tmp_path)) {
67+
std::filesystem::remove(tmp_path);
68+
}
5369
}
5470

5571
} // namespace infini_train::core

infini_train/src/core/ccl/cuda/nccl_impl.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,16 @@ void NcclImpl::GetAsyncError(const CclComm *comm, CclStatus *async_error) const
6868
}
6969
}
7070

71-
void NcclImpl::GetUniqueId(CclUniqueId **unique_id) const {
71+
void NcclImpl::CreateUniqueId(CclUniqueId **unique_id, bool generate_id) const {
7272
CHECK_NOTNULL(unique_id);
7373
if (*unique_id == nullptr) {
7474
*unique_id = new NcclUniqueId();
7575
}
7676
auto *nccl_unique_id = dynamic_cast<NcclUniqueId *>(*unique_id);
7777
CHECK_NOTNULL(nccl_unique_id);
78-
NCCL_CHECK(ncclGetUniqueId(nccl_unique_id->nccl_unique_id()));
78+
if (generate_id) {
79+
NCCL_CHECK(ncclGetUniqueId(nccl_unique_id->nccl_unique_id()));
80+
}
7981
}
8082

8183
void NcclImpl::CommInitAll(CclComm **comms, int ndev, const int *devlist) const {

infini_train/src/core/ccl/cuda/nccl_impl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class NcclImpl final : public CclImpl {
1717

1818
void GetAsyncError(const CclComm *comm, CclStatus *async_error) const override;
1919

20-
void GetUniqueId(CclUniqueId **unique_id) const override;
20+
void CreateUniqueId(CclUniqueId **unique_id, bool generate_id) const override;
2121

2222
void CommInitAll(CclComm **comms, int ndev, const int *devlist) const override;
2323

0 commit comments

Comments
 (0)