Kakao i Machine Learning::튜토리얼::분산 학습하기(CLI)

페이지 이동경로

CLI로 분산학습하기

Kakao i Machine Learning은 대규모 딥러닝 모델을 학습할 수 있는 빠른 분산학습을 지원합니다. 학습을 GPU 인스턴스 간에 병렬로 진행하여 효율적으로 학습할 수 있습니다. Kakao i Machine Learning에서 모델을 분산학습하기 위해 분산학습용 코드 작성 및 학습을 실행하는 방법을 안내합니다.

  • KiML CLI는 Shell 환경에서 Kakao i Machine Learning을 사용하는 인터페이스입니다.
  • kiml 명령어를 사용하며, --help로 인자값을 확인할 수 있습니다.

분산학습 실행 방법

분산학습의 유형은 학습 실행 시 지정하는 인스턴스 타입에 따라서 구분됩니다.
Single Instance Multi GPU 분산학습은 여러 장의 GPU를 포함한 인스턴스 1개를 지정하고, Multi Instance Multi GPU 분산학습은 여러 장의 GPU를 포함한 인스턴스를 여러 개 지정합니다.

안내
Kakao i Machine Learning에서 제공하는 인스턴스 종류에 대한 자세한 설명은 인스턴스 타입 문서를 참고하시기 바랍니다.

Single Instance Multi GPU

하나의 인스턴스에서 여러 장의 GPU를 사용하여 분산학습을 수행할 수 있습니다.

코드예제Single Instance Multi GPU Syntax

kiml run submit \
--name {run-name}
--dataset {dataset-name} \
--experiment {experiment-name} \
--image {image-name} \
--instance-type {instance-type} \
--num-replica 1 \
"python mnist.py \
--data_dir /app/input/dataset/{dataset_name} \
--output_dir /app/output"

Single Instance Multi GPU Parameters
파라미터 설명
{run-name} 실행 이름(선택)
{dataset-name} 실행에 참조할 데이터세트 이름
{experiment-name} 실행이 속할 실험의 이름
{image-name} 실행을 구성할 이미지 이름
{instance-type} 실행을 구성할 인스턴스 타입 이름

Multi Instance Multi GPU

여러 개의 인스턴스로 분산학습을 수행할 수 있습니다. 실험 생성 시 --num-replica에 인스턴스 개수를 지정합니다.

  • 모든 인스턴스는 output directory를 공유합니다.
  • 기본적으로 master instance(0번 instance)의 로그만 출력합니다. 전체 인스턴스의 로그를 조회하기 위해서는 SDK에서 Run.print_logs()에서 master_only 매개변수를 통해 설정할 수 있습니다.

코드예제Multi Instance Multi GPU Syntax

kiml run submit \
--name {run-name}
--dataset {dataset-name} \
--experiment {experiment-name} \
--image {image-name} \
--instance-type {instance-type} \
--num-replica 2 \
"python mnist.py \
--data_dir /app/input/dataset/{dataset_name} \
--output_dir /app/outputs"

Multi Instance Multi GPU Parameters
파라미터 설명
{run-name} 실행 이름(선택)
{dataset-name} 실행에 참조할 데이터세트 이름
{experiment-name} 실행이 속할 실험의 이름
{image-name} 실행을 구성할 이미지 이름
{instance-type} 실행을 구성할 인스턴스 타입 이름

프레임워크별 지원 사항

실행한 프레임워크 PyTorch 또는 TensorFlow에 따라 제공하는 환경변수가 달라집니다.

PyTorch

각 인스턴스에서 torchrun을 실행해서 GPU 개수만큼 프로세스를 생성하여 스크립트를 실행합니다.

torchrun

torchrun은 분산학습을 하는 데 필요한 여러 환경 변수를 제공합니다. 이를 활용하여 분산학습을 수행할 수 있습니다.

안내
예약 환경 변수에 대한 자세한 내용은 실험/실행 > 예약 환경 변수 문서를 참고하시기 바랍니다.
torchrun이 제공하는 환경변수
환경변수 설명
LOCAL_RANK The local rank.
RANK The global rank.
GROUP_RANK The rank of the worker group. A number between 0 and max_nnodes. When running a single worker group per node, this is the rank of the node.
ROLE_RANK The rank of the worker across all the workers that have the same role. The role of the worker is specified in the WorkerSpec.
LOCAL_WORLD_SIZE The local world size (e.g. number of workers running locally); equals to -nproc_per_node specified on torchrun
WORLD_SIZE The world size (total number of workers in the job)
ROLE_WORLD_SIZE The total number of workers that was launched with the same role specified in WorkerSpec
MASTER_ADDR The FQDN of the host that is running worker with rank 0; used to initialize the Torch Distributed backend.
MASTER_PORT The port on theMASTER_ADDR that can be used to host the C10d TCP store.
TORCHELASTIC_RESTART_COUNT The number of worker group restarts so far.
TORCHELASTIC_MAX_RESTARTS The configured maximum number of restarts.
TORCHELASTIC_RUN_ID Equal to the rendezvousrun_id(e.g. unique job id).
PYTHON_EXEC System executable override. If provided, the python user script will use the value of PYTHON_EXEC as executable. The sys.executable is used by default.
안내
torchrun에 대한 자세한 설명은 PyTorch > torchrun 공식 문서를 참고하시기 바랍니다.

프로세스 그룹 초기화

PyTorch에서는 분산 프로세스끼리 프로세스 그룹을 만들어 통신합니다.

각 분산 프로세스에서 torch.distributed.init_process_group를 실행해 프로세스 그룹을 초기화합니다. 이때 torchrun에서 제공하는 환경변수(MASTER_ADDR, MASTER_PORT, RANK=, WORLD_SIZE)를 이용합니다.

프로세스 그룹이 초기화되면 torch.nn.parallel.DistributedDataParallel을 이용해서 data parallel 학습을 수행하거나 torch.distributed 내에 있는 point-to-point communication이나 collective communication을 수행할 수 있습니다.

이외에도 다른 환경변수들을 스크립트에서 활용할 수 있습니다. LOCAL_RANK를 이용하면 프로세스에서 사용할 Gpu device를 설정하거나 RANK를 이용하여 chief 프로세스 여부를 판단할 수 있습니다.

코드예제프로세스 그룹 통신 Sample Code

import os
import torch
import torch.nn as nn
import torch.distributed as dist

dist.init_process_group(backend='nccl')

local_rank = os.environ['LOCAL_RANK']
global_rank = os.environ['RANK']
chief = global_rank == 0

if local_rank == 0:
	# 각 노드별로 준비작업은 local rank가 0인 프로세스가 맡는다.
	# 예: 데이터 준비, 검증 등
	pass

dist.barrier()

device = torch.device('cuda', local_rank)
...
model = model.to(device)
model = nn.parallel.DistributedDataParallel(model, device_ids=[device], output_device=device)
...
for x, y in train_loader:
	x = x.to(device)
	y = y.to(device)

	pred = model(x)
	loss = criterion(pred, y)
	...

	if chief:
		summary_writer.add_scalar('loss', loss)
...

TensorFlow

TensorFlow를 이용한 실행의 경우 TF_CONFIG 환경변수를 통해 분산학습을 지원합니다. 각 인스턴스에 적절하게 TF_CONFIG을 설정해 인스턴스 간에 올바르게 통신할 수 있도록 합니다.

TF_CONFIG

TF_CONFIG은 JSON 형태이고 아래의 속성을 지닙니다.

  • cluster: 모든 인스턴스에서 공유하는 분산 클러스터 구성 정보
  • task: 현재 인스턴스가 담당하고 있는 역할 (인스턴스 별로 다름)
안내
자세한 내용은 [TensorFlow 가이드] TF_CONFIG 환경변수 설정하기 문서를 참고하시기 바랍니다.

TensorFlow에서는 tf.distribute.Strategy라는 API를 통해 여러 GPU 또는 여러 노드를 이용한 분산학습을 수행할 수 있습니다. Kakao i Machine Learning 플랫폼은 tf.distribute.MirroredStrategy(single instance)와 tf.distribute.experimental.MultiWorkerMirroredStrategy(single/multi instance)를 지원합니다.

코드예제분산 학습 Sample Code

// 2개의 instance에 걸친 분산학습에서 2번째 instance
{
    "cluster": {
        "worker": ["host1:port", "host2:port"],
    },
   "task": {"type": "worker", "index": 1}
}

코드예제TF_CONFIG Sample Code

import tensorflow as tf

multiworker_strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy()

with multiworker_strategy.scope():
  model = tf.keras.Sequential([tf.keras.layers.Dense(1, input_shape=(1,))])
  model.compile(loss='mse', optimizer='sgd')
...

샘플 코드 및 실행 예제

간단한 분산 학습 실행 테스트를 위한 샘플 코드입니다.

PyTorch

  1. 아래의 명령을 입력하여 실험을 생성합니다.

    코드예제실험 생성 Syntax

    kiml experiment create {experiment-name}
    

    실험 생성 Parameters

    파라미터 설명
    {experiment-name} 생성할 실험 이름 설정

  2. 아래 명령어로 requirements.txt를 생성합니다. Ubuntu OS에서 Package Manager를 이용해 직접 설치할 경우, apt-get install <package name > 명령어로 설치할 수 있습니다.

    코드예제requirements.txt 생성 Syntax

    click==8.1.3
    

  3. 아래의 명령을 입력하여 커스텀 이미지를 생성합니다.

    튜토리얼 Tip!
    kiml image create는 pip requirements에 명세된 파이썬 패키지를 지정된 기본 이미지에 설치하여 새로운 커스텀 이미지를 생성합니다. -rf 옵션의 아규먼트로 앞에서 생성한 requirements.txt 경로를 입력하고, -b 옵션의 아규먼트로는 기본 이미지를 지정합니다.

    코드예제커스텀 이미지 생성 Syntax

    kiml image create --name {image-name} -rf requirements.txt -b cosmos-pytorch1.10.0-cuda11.3-cudnn8-py3.7-ubuntu18.04
    

    커스텀 이미지 생성 Parameters

    파라미터 설명
    {image-name} 생성할 이미지 이름 설정

  4. 아래 pytorch_CIFAR100.zip 데이터를 다운로드 후, 압축을 해제합니다.

    pytorch_CIFAR100.zip 파일
  5. 압축을 해제한 데이터를 default 스토리지에 업로드한 뒤 데이터세트로 지정합니다.

    코드예제데이터 업로드 Syntax

      kiml data upload {local-path} default/pytorch_dataset
    

    데이터 업로드 Parameters

    파라미터 설명
    {local-path} 업로드할 파일이 저장된 위치

    코드예제데이터세트 지정 Syntax

      kiml dataset create {dataset-name} --path default/pytorch_dataset
    

    데이터세트 지정 Parameters

    파라미터 설명
    {dataset-name} 생성할 데이터세트의 이름

  6. 아래의 train.py 파일을 다운받습니다.

    train.py 파일

    코드예제train.py 파일 다운

    import os
    
    import click
    import torch
    import torch.nn as nn
    import torch.optim as optim
    import torch.nn.functional as F
    import torchvision
    import torchvision.transforms as transforms
    import torch.distributed as dist
    from torch.utils.tensorboard.writer import SummaryWriter
    
    
    def evaluate(model, test_loader, device):
       with torch.no_grad():
           model.eval()
    
           correct = total = 0
           for x, y in test_loader:
               x = x.to(device)
               y = y.to(device)
    
               pred = model(x)
    
               max_val, max_args = torch.max(pred, 1)
    
               correct += torch.sum(max_args == y).item()
               total += len(y)
    
       accuracy = correct / total
    
       return accuracy
    
    
    @click.command()
    @click.option("--data_dir", required=True)
    @click.option("--output_dir", required=True)
    def main(data_dir, output_dir):
       local_rank = int(os.environ["LOCAL_RANK"])
    
       device = (
           torch.device("cuda", local_rank)
           if torch.cuda.is_available()
           else torch.device("cpu", local_rank)
       )
       backend = "nccl" if torch.cuda.is_available() else "gloo"
       dist.init_process_group(backend)
    
       log_dir = os.path.join(output_dir, "log")
       model_path = os.path.join(output_dir, "model.pt")
    
       summary_writer = SummaryWriter(log_dir=log_dir)
    
       model = torchvision.models.mobilenet_v3_small(num_classes=100)
    
       criterion = nn.CrossEntropyLoss()
       optimizer = optim.Adam(model.parameters(), lr=0.01)
       transform = transforms.Compose(
           [
               transforms.ToTensor(),
               transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
           ]
       )
    
       trainset = torchvision.datasets.CIFAR100(
           root=data_dir, train=True, download=False, transform=transform
       )
       train_sampler = torch.utils.data.distributed.DistributedSampler(trainset)
       trainloader = torch.utils.data.DataLoader(
           trainset, batch_size=100, shuffle=False, num_workers=2, sampler=train_sampler
       )
    
       testset = torchvision.datasets.CIFAR100(
           root=data_dir, train=False, download=False, transform=transform
       )
       testloader = torch.utils.data.DataLoader(
           testset, batch_size=100, shuffle=False, num_workers=2
       )
    
       model = model.to(device)
       model = torch.nn.parallel.DistributedDataParallel(model)
    
       print("Start Training")
       for epoch in range(10):
           model.train()
    
           train_sampler.set_epoch(epoch)
           running_loss = 0.0
           for i, data in enumerate(trainloader, 0):
               inputs, labels = data[0].to(device), data[1].to(device)
    
               outputs = model(inputs)
               loss = criterion(outputs, labels)
    
               optimizer.zero_grad()
               loss.backward()
               optimizer.step()
    
               running_loss += loss.item()
    
           running_loss /= len(trainloader)
           running_loss = torch.tensor(running_loss).to(device)
           avg_loss = running_loss.clone()
           dist.all_reduce(avg_loss, op=dist.ReduceOp.SUM)
           avg_loss /= dist.get_world_size()
    
           if local_rank == 0:
               summary_writer.add_scalar("loss", avg_loss.item(), global_step=epoch + 1)
               print("Epoch {} Loss: {:.4f}".format(epoch + 1, avg_loss.item()))
    
           accuracy = evaluate(model, testloader, device)
    
           if local_rank == 0:
               summary_writer.add_scalar("accuracy", accuracy, global_step=epoch + 1)
               print("Epoch {} Accuracy: {:.4f}".format(epoch + 1, accuracy))
    
       print("Finished Training")
    
       if local_rank == 0:
           torch.save(model.state_dict(), model_path)
           print("Saved model to {}".format(model_path))
    
       dist.destroy_process_group()
    
    
    if __name__ == "__main__":
       main()
    

  7. 다운받은 train.py 파일이 저장된 위치로 이동 후, 아래 run submit 명령어로 학습을 실행합니다. OS 환경에 따라 입력하는 스크립트 명령어에 차이가 있습니다.

    코드예제학습 실행 Syntax (Linux 또는 Mac 환경 CLI)

    kiml run submit --name {run-name} --dataset {dataset-name} --experiment {experiment-name} --image {image-name} --instance-type {instance-type} --num-replica {num-of-instances} "torchrun --nproc_per_node=\$GPU_COUNT --node_rank=\$RANK --nnodes=\$WORLD_SIZE --master_addr=\$MASTER_ADDR --master_port=\$MASTER_PORT train.py --data_dir /app/input/dataset/{dataset-name} --output_dir /app/outputs"
    

    코드예제학습 실행 Syntax (Windows 환경)

    kiml run submit --name {run-name} --dataset {dataset-name} --experiment {experiment-name} --image {image-name} --instance-type {instance-type} --num-replica {num-of-instances} "torchrun --nproc_per_node=`$GPU_COUNT --node_rank=`$RANK --nnodes=`$WORLD_SIZE --master_addr=`$MASTER_ADDR --master_port=`$MASTER_PORT train.py --data_dir /app/input/dataset/{dataset-name} --output_dir /app/outputs"
    

    학습 실행 Parameters

    파라미터 설명
    {run-name} 실행 이름(선택)
    {dataset-name} 실행에 참조할 데이터세트 이름
    {experiment-name} 실행이 속할 실험의 이름
    {image-name} 실행을 구성할 이미지 이름
    {instance-type} 실행을 구성할 인스턴스 타입 이름
    {num-of-instances} 인스턴스 개수
    - Single instance: 1
    - Multi instance: 2 이상의 숫자
    $GPU_COUNT 인스턴스(노드)당 GPU 개수
    $RANK 예약 환경 변수
    - 분산 학습이 진행 중인 각 인스턴스(노드)의 순서 번호
    $WORLD_SIZE 예약 환경 변수
    - 학습에 할당 된 인스턴스(노드)의 개수
    - num-of-instances와 동일한 값을 가짐
    $MASTER_ADDR 예약 환경 변수
    - 분산 학습을 진행하는 마스터 노드의 주소
    $MASTER_PORT 예약 환경 변수
    - 분산 학습을 진행하는 마스터 노드의 포트

TensorFlow

  1. 아래의 명령을 입력하여 실험을 생성합니다.

    코드예제실험 생성 Syntax

    kiml experiment create {experiment-name}
    

    실행 생성 Parameters

    파라미터 설명
    {experiment-name} 생성할 실험 이름 설정

  2. 아래의 내용으로 requirements.txt를 생성합니다.

    코드예제requirements.txt 생성 Syntax

    click
    tensorflow-datasets
    

    우분투에서 package manager로 패키지를 직접 설치할 경우, apt-get install <package name > 명령어로 설치할 수 있습니다. apt-cache search 명령어로 설치할 수 있는 패키지를 검색할 수 있습니다. 아래 예시 코드를 참고하시기 바랍니다.

    코드예제패키지 설치 Sample Code

    #! DEBIAN_FRONTEND=noninteractive apt-get install -y libopenmpi-dev
    

  3. 아래의 명령을 입력하여 커스텀 이미지를 생성합니다.

    튜토리얼 Tip!
    kiml image create는 pip requirements에 명세된 파이썬 패키지를 지정된 기본 이미지에 설치합니다. -rf 옵션의 아규먼트로 앞에서 생성한 requirements.txt 경로를 입력하고, -b 옵션 아규먼트로는 기본 이미지를 지정합니다.

    코드예제커스텀 이미지 Syntax

    Kiml image create --name {image-name} -rf requirements.txt -b cosmos-tensorflow2.8.0-cuda11.2-cudnn8-py3.8-ubuntu20.04
    

    커스텀 이미지 Parameters

    파라미터 설명
    {image-name} 생성할 이미지 이름 설정

  4. 아래의 데이터를 다운받고, 압축을 해제합니다.

    tf_CIFAR100 파일
  5. 압축을 해제한 데이터를 default 스토리지에 업로드한 뒤 데이터세트로 지정합니다.

    코드예제데이터 업로드 Syntax

      kiml data upload {local-path} default/tf_dataset
    

    데이터 업로드 Parameters

    파라미터 설명
    {local-path} 업로드할 파일이 저장된 위치
    - Windows 환경: tf_CIFAR100/tf_dataset 입력
    - Mac 환경: /tf_dataset 입력

    코드예제데이터세트 지정 Syntax

      kiml dataset create {dataset-name} --path default/tf_dataset
    

    데이터세트 지정 Parameters

    파라미터 설명
    {dataset-name} 생성할 데이터세트의 이름

  6. 아래의 train.py 파일을 다운받습니다.

    train.py 파일

    코드예제train.py 파일 다운로드

    import os
    
    import click
    import tensorflow as tf
    import tensorflow_datasets as tfds
    
    
    TARGET_SIZE = (224, 224)
    NUM_CLASSES = 100
    
    
    def transform(feats):
       img = feats["image"]
       lbl = feats["label"]
       img = tf.image.convert_image_dtype(img, tf.float32)
       img = tf.image.resize(img, TARGET_SIZE)
    
       return img, lbl
    
    
    @click.command()
    @click.option("--data_dir", required=True)
    @click.option("--sample_batch", default=4)
    @click.option("--batch_size", default=32)
    @click.option("--num_workers", default=1)
    @click.option("--learning_rate", default=0.001)
    @click.option("--output_dir", required=True)
    def main(data_dir, sample_batch, batch_size, num_workers, learning_rate, output_dir):
       log_dir = os.path.join(output_dir, "log")
       model_path = os.path.join(output_dir, "model.pt")
    
       strategy = tf.distribute.MirroredStrategy()
    
       dss, info = tfds.load("cifar100", with_info=True, data_dir=data_dir, download=False)
    
       ds = dss["train"]
       ds = ds.map(transform, num_parallel_calls=num_workers)
       ds = ds.batch(batch_size).take(sample_batch).prefetch(2)
    
       options = tf.data.Options()
       options.experimental_distribute.auto_shard_policy = (
           tf.data.experimental.AutoShardPolicy.OFF
       )
       ds = ds.with_options(options)
    
       with strategy.scope():
           model = tf.keras.applications.efficientnet.EfficientNetB0(
               classes=NUM_CLASSES, weights=None
           )
    
           criterion = tf.keras.losses.SparseCategoricalCrossentropy()
           optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
           metric = tf.keras.metrics.SparseCategoricalAccuracy()
    
           model.compile(loss=criterion, optimizer=optimizer, metrics=[metric])
    
       print("Start training")
       tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir)
       training_history = model.fit(
           ds,
           epochs=10,
           callbacks=[tensorboard_callback],
       )
       print("Finished Training")
    
    
    if __name__ == "__main__":
       main()
    

  7. 다운받은 파일이 저장된 위치에서 아래의 명령어로 학습을 실행합니다.

    코드예제학습 실행 Syntax

    kiml run submit --name {run-name} --dataset {dataset-name} --experiment {experiment-name} --image {image-name} --instance-type {instance-type}  --num-replica {num_of-instances} --env-var PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python "python -u train.py --data_dir /app/input/dataset/{dataset_name} --output_dir /app/outputs"
    

    학습 실행 Parameters

    파라미터 입력값 설명
    --name {run-name} 실행 이름(선택)
    --dataset {dataset-name} 실행에 참조할 데이터세트 이름
    --experiment {experiment-name} 실행이 속할 실험의 이름
    --image {image-name} 실행을 구성할 이미지 이름
    --instance-type {instance-type} 실행을 구성할 인스턴스 타입 이름
    --num-replica {num-of-instances} 인스턴스 개수
    - Single instance: 1
    - Multi instance: 2 이상의 숫자
    --env-var PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python 환경변수

이 문서가 만족스러운 이유를 알려주세요.
이 문서에 아쉬운 점을 알려주세요.
평가해주셔서 감사합니다.

더 자세한 의견은 documentation@kakaoenterprise.com 으로 제보해주세요.