Bỏ qua điều hướng
CÔNG TY CỔ PHẦN GIẢI PHÁP CÔNG NGHỆ HQG
Giai đoạn 5 · Nhiều GPUNâng cao

Huấn luyện nhiều GPU và nhiều node: torchrun, DDP, FSDP, DeepSpeed ZeRO, NCCL

Chọn giữa DDP, FSDP và DeepSpeed ZeRO 1/2/3, chạy torchrun trên một hoặc nhiều node, kiểm tra NCCL bằng nccl-tests, biến môi trường gỡ lỗi, checkpoint và tổ chức lưu trữ dữ liệu song song.

Khoảng 6 phút đọcCập nhật: 09/20267 bước
Mục tiêu

Chạy được một job huấn luyện phân tán ổn định trên nhiều GPU (và nhiều node nếu có), biết đọc log NCCL khi treo và khôi phục được từ checkpoint.

Dành cho ai
  • Kỹ sư ML huấn luyện mô hình lớn
  • Kỹ sư hạ tầng vận hành cụm GPU
Yêu cầu
  • Máy nhiều GPU, lý tưởng có NVLink/NVSwitch cho FSDP/ZeRO-3
  • Nhiều node: mạng RDMA (InfiniBand/RoCE) và lưu trữ dùng chung
  • Driver, CUDA, (HGX: Fabric Manager) theo bài cài driver & CUDA
  • Cùng phiên bản môi trường Python/container trên mọi node
Mục lục bài

DDP, FSDP hay DeepSpeed ZeRO?

Chiến lượcChia gì giữa các GPUDùng khi
DDPKhông chia — mỗi GPU giữ bản sao đầy đủ mô hình, đồng bộ gradientMô hình + optimizer vừa một GPU, muốn tăng tốc bằng thêm dữ liệu song song
ZeRO-1Chia trạng thái optimizerGần vừa một GPU, cần tiết kiệm thêm bộ nhớ, ít tốn giao tiếp
ZeRO-2Chia optimizer + gradientMô hình vừa nhưng optimizer/gradient không vừa
ZeRO-3 / FSDP (full shard)Chia cả trọng số, gradient, optimizerMô hình không vừa một GPU; cần kết nối GPU–GPU nhanh

Lưu ý: Chia càng nhiều thì tiết kiệm VRAM càng nhiều nhưng giao tiếp giữa GPU càng nặng. Trên máy card PCIe không NVLink, ZeRO-3/FSDP full shard có thể chậm rõ rệt — hãy đo trên hệ thống thật.

Các bước thực hiện

Tiến độ của bạn
0/7

Tiến độ chỉ lưu trên trình duyệt này.

  1. Bước 1: Kiểm tra topo và NCCL bằng nccl-tests

    Bash
    nvidia-smi topo -m
    sudo apt install -y libnccl2 libnccl-dev   # từ repo CUDA
    git clone https://github.com/NVIDIA/nccl-tests.git
    cd nccl-tests
    make CUDA_HOME=/usr/local/cuda
    # all-reduce trên 8 GPU trong một node, kích thước tin nhắn từ 8 byte đến 8 GB
    ./build/all_reduce_perf -b 8 -e 8G -f 2 -g 8

    Chạy xong không lỗi, cột busbw ổn định giữa các lần chạy. Ghi lại kết quả làm mốc — khi job chậm bất thường, chạy lại để so sánh.

  2. Bước 2: Viết script DDP tối giản

    ddp_min.py
    import os
    import torch
    import torch.distributed as dist
    from torch.nn.parallel import DistributedDataParallel as DDP
    from torch.utils.data import DataLoader, DistributedSampler, TensorDataset
    
    def main():
        dist.init_process_group(backend="nccl")
        local_rank = int(os.environ["LOCAL_RANK"])
        torch.cuda.set_device(local_rank)
    
        data = TensorDataset(torch.randn(10_000, 1024), torch.randn(10_000, 1))
        sampler = DistributedSampler(data, shuffle=True)
        loader = DataLoader(data, batch_size=64, sampler=sampler, num_workers=2, pin_memory=True)
    
        model = torch.nn.Sequential(torch.nn.Linear(1024, 4096), torch.nn.GELU(), torch.nn.Linear(4096, 1)).cuda()
        model = DDP(model, device_ids=[local_rank])
        opt = torch.optim.AdamW(model.parameters(), lr=1e-4)
    
        for epoch in range(3):
            sampler.set_epoch(epoch)  # xáo trộn khác nhau mỗi epoch
            for x, y in loader:
                x, y = x.cuda(non_blocking=True), y.cuda(non_blocking=True)
                loss = torch.nn.functional.mse_loss(model(x), y)
                opt.zero_grad(set_to_none=True)
                loss.backward()
                opt.step()
            if dist.get_rank() == 0:
                print(f"epoch {epoch} loss {loss.item():.4f}")
    
        dist.destroy_process_group()
    
    if __name__ == "__main__":
        main()
  3. Bước 3: Chạy bằng torchrun trên một node

    Bash
    torchrun --standalone --nproc_per_node=8 ddp_min.py
    # Chỉ dùng GPU 0-3
    CUDA_VISIBLE_DEVICES=0,1,2,3 torchrun --standalone --nproc_per_node=4 ddp_min.py

    torchrun tự đặt RANK, LOCAL_RANK, WORLD_SIZE cho từng tiến trình. Script Hugging Face Trainer/TRL cũng chạy được bằng torchrun hoặc accelerate launch.

  4. Bước 4: Bật FSDP hoặc DeepSpeed ZeRO cho script Hugging Face

    Với script TRL/Trainer (như train_sft.py ở bài fine-tune), cách đơn giản là dùng DeepSpeed qua file cấu hình. Giá trị "auto" để Trainer tự điền theo SFTConfig.

    ds_zero3.json
    {
      "bf16": { "enabled": "auto" },
      "zero_optimization": {
        "stage": 3,
        "overlap_comm": true,
        "contiguous_gradients": true,
        "stage3_gather_16bit_weights_on_model_save": true
      },
      "gradient_accumulation_steps": "auto",
      "gradient_clipping": "auto",
      "train_micro_batch_size_per_gpu": "auto",
      "train_batch_size": "auto"
    }
    Bash
    uv pip install deepspeed
    # Trong SFTConfig thêm: deepspeed="ds_zero3.json" (bỏ quantization 4-bit khi dùng ZeRO-3 full fine-tune)
    torchrun --standalone --nproc_per_node=8 train_sft.py
    
    # Hoặc FSDP qua Accelerate: trả lời câu hỏi cấu hình, chọn FSDP
    accelerate config
    accelerate launch train_sft.py

    Cảnh báo: Tổ hợp QLoRA 4-bit với FSDP/ZeRO-3 có yêu cầu riêng về phiên bản và cấu hình. Làm theo hướng dẫn chính thức của PEFT/Accelerate cho đúng tổ hợp, đừng ghép tuỳ ý.

  5. Bước 5: Chạy nhiều node

    Chạy cùng một lệnh trên mọi node, trỏ về một endpoint rendezvous (IP của node đầu). Mở cổng giữa các node trên mạng nội bộ.

    Bash
    # Trên MỖI node (ví dụ 2 node, 8 GPU mỗi node)
    export NCCL_SOCKET_IFNAME=ens1f0      # tên card mạng dùng cho bootstrap, xem bằng: ip -br a
    torchrun \
      --nnodes=2 \
      --nproc_per_node=8 \
      --rdzv_id=job-001 \
      --rdzv_backend=c10d \
      --rdzv_endpoint=10.0.0.11:29500 \
      ddp_min.py
    • Mọi node dùng cùng image/môi trường, cùng mã nguồn, cùng đường dẫn dữ liệu.
    • Đồng bộ thời gian (chrony) để log khớp nhau.
    • Với Slurm, dùng srun bọc lệnh torchrun và lấy địa chỉ node đầu từ biến môi trường của Slurm.
  6. Bước 6: Biến môi trường NCCL để gỡ lỗi

    BiếnTác dụng
    NCCL_DEBUG=INFOIn thông tin khởi tạo: dùng mạng nào, NVLink/P2P hay không
    NCCL_DEBUG_SUBSYS=INIT,NETGiới hạn log vào phần khởi tạo và mạng
    NCCL_SOCKET_IFNAMEChọn card mạng cho kết nối socket/bootstrap
    NCCL_IB_HCAChọn thiết bị InfiniBand/RoCE (ví dụ mlx5)
    NCCL_IB_DISABLE=1Tắt IB để khoanh vùng lỗi — CHỈ để chẩn đoán
    NCCL_P2P_DISABLE=1Tắt P2P giữa GPU để khoanh vùng lỗi — CHỈ để chẩn đoán
    TORCH_DISTRIBUTED_DEBUG=DETAILPyTorch kiểm tra và báo lệch collective giữa các rank
    Bash
    NCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,NET torchrun --standalone --nproc_per_node=8 ddp_min.py 2>&1 | tee nccl.log
    grep -E "NET/|via P2P|NVLS|Using network" nccl.log | head -n 40
  7. Bước 7: Checkpoint và lưu trữ dữ liệu

    • Lưu checkpoint định kỳ (save_steps) và giới hạn số bản (save_total_limit); tiếp tục bằng trainer.train(resume_from_checkpoint=True).
    • FSDP/ZeRO-3 lưu checkpoint dạng chia mảnh — ghi vào lưu trữ dùng chung mọi rank đều thấy, hoặc dùng torch.distributed.checkpoint.
    • Chỉ rank 0 ghi log và file tổng hợp để tránh ghi đè.
    • Tiền xử lý/tokenize dữ liệu một lần thành shard (Arrow/Parquet), không tokenize lại mỗi lần chạy.
    • Dữ liệu trên lưu trữ song song hoặc sao chép sẵn về NVMe cục bộ của từng node; tăng num_workers DataLoader tới khi GPU không còn chờ dữ liệu.
    • Sao chép checkpoint quan trọng ra nơi lưu trữ khác (xem bài giám sát & vận hành).

Kiểm tra thành công

  • all_reduce_perf chạy hết không lỗi trên một node và (nếu có) giữa các node.
  • Log NCCL_DEBUG=INFO cho thấy dùng NVLink/P2P trong node và IB/RoCE giữa các node như thiết kế.
  • nvidia-smi cho thấy mọi GPU cùng được dùng khi job chạy.
  • Dừng job giữa chừng rồi chạy lại từ checkpoint, loss tiếp tục liền mạch.

Lỗi thường gặp & cách sửa

Job treo ngay lúc khởi tạo, không in lỗi

Nguyên nhân thường gặp: Các node không kết nối được cổng rendezvous, NCCL chọn nhầm card mạng, tường lửa chặn.

Kiểm tra nc -zv 10.0.0.11 29500 từ node khác, đặt NCCL_SOCKET_IFNAME đúng card, bật NCCL_DEBUG=INFO để xem node nào không tham gia.

Watchdog caught collective operation timeout

Nguyên nhân thường gặp: Một rank chết/OOM, các rank chạy khác nhánh code (lệch collective), hoặc mạng lỗi.

Tìm log của rank lỗi đầu tiên (thường OOM hoặc exception), bật TORCH_DISTRIBUTED_DEBUG=DETAIL, đảm bảo mọi rank chạy cùng số bước và cùng điều kiện if/else quanh collective.

Nhiều GPU trong một node nhưng tốc độ không tăng, NCCL không dùng P2P

Nguyên nhân thường gặp: Topo PCIe đi qua CPU, ACS bật trên switch PCIe chặn P2P, hoặc IOMMU cấu hình không phù hợp máy bare-metal.

Xem nvidia-smi topo -m, đọc mục Troubleshooting về ACS/IOMMU trong tài liệu NCCL, cập nhật BIOS theo khuyến nghị của hãng máy chủ.

NET/IB: No device found

Nguyên nhân thường gặp: Chưa cài driver/OFED cho card InfiniBand/RoCE, hoặc container không được gắn thiết bị RDMA.

Bash
ibv_devices
ibstat
# Container: gắn thiết bị RDMA, ví dụ --device=/dev/infiniband --cap-add=IPC_LOCK --ulimit memlock=-1

Bước tiếp theo

Nguồn chính chủ

Lệnh, tên gói và tham số thay đổi theo phiên bản. Trước khi chạy trên máy thật, hãy kiểm tra phiên bản mới nhất tại trang chính chủ:

Giới hạn của bài

  • Bài không đưa số băng thông NCCL "chuẩn" để so sánh — mốc đúng là kết quả đo trên chính cụm của bạn lúc khoẻ.
  • Bài không đi sâu tensor/pipeline parallel khi huấn luyện (Megatron-LM, NeMo) và cấu hình Slurm.
Cần người dựng hệ thống cùng?

Kỹ sư HQG khảo sát, lên cấu hình, lắp đặt và bàn giao hạ tầng AI chạy được thật.