Accelerate documentation
Jupyter Notebooks에서 분산 학습 시작
Jupyter Notebooks에서 분산 학습 시작
이 튜토리얼에서는 분산 시스템의 Jupyter Notebook에서 🤗 Accelerate를 사용하여 컴퓨터 비전 모델을 미세 조정하는 방법을 배웁니다. 또한 환경이 올바르게 구성되었는지, 데이터가 올바르게 준비되었는지, 그리고 마지막으로 학습을 시작하는 방법을 확인하기 위해 필요한 몇 가지 요구 사항을 설정하는 방법도 배웁니다.
이 튜토리얼은 Jupyter Notebook으로도 여기에서 사용할 수 있습니다.
환경 구성
학습을 시작하기 전에 시스템에 Accelerate 설정 파일이 존재해야 합니다. 일반적으로 터미널에서 다음을 실행하고 프롬프트에 응답하면 됩니다:
accelerate config
다만, 일반적인 기본값이 적합하고 TPU에서 실행하지 않는 경우, Accelerate에는 utils.write_basic_config()를 통해 디바이스 구성을 설정 파일에 빠르게 기록하는 유틸리티가 있습니다.
다음 코드는 CUDA 런타임 또는 XPU 런타임이 이 작업을 수행하도록 호출되었기 때문에, 구성을 작성한 후 Jupyter를 다시 시작합니다.
멀티 디바이스 시스템에서 CUDA와 XPU는 한 번 이상 초기화할 수 없습니다. 노트북에서 디버깅을 위해 CUDA/XPU를 호출하는 것은 문제가 없으나, 최종적으로 학습을 수행하려면 완전한 정리 및 재시작이 필요합니다.
import os
from accelerate.utils import write_basic_config
write_basic_config() # Write a config file
os._exit(00) # Restart the notebook데이터셋 및 모델 준비
다음으로 데이터셋을 준비해야 합니다. 앞서 언급했듯이, DataLoaders와 모델을 준비할 때 아무것도 어떤 GPU에도 올라가지 않도록 세심한 주의를 기울여야 합니다.
이 경우, 해당 특정 코드를 함수로 분리하여 나중에 소개할 노트북 런처 인터페이스에서 호출하는 것이 권장됩니다.
여기의 안내에 따라 데이터셋이 다운로드되었는지 확인하세요.
import os, re, torch, PIL
import numpy as np
from torch.optim.lr_scheduler import OneCycleLR
from torch.utils.data import DataLoader, Dataset
from torchvision.transforms import Compose, RandomResizedCrop, Resize, ToTensor
from accelerate import Accelerator
from accelerate.utils import set_seed
from timm import create_model먼저 파일 이름에 기반하여 클래스 이름을 추출하는 함수를 만들어야 합니다:
import os
data_dir = "../../images"
fnames = os.listdir(data_dir)
fname = fnames[0]
print(fname)beagle_32.jpg
여기서 라벨은 beagle입니다. 정규식을 사용하면 파일명에서 라벨을 추출할 수 있습니다:
import re
def extract_label(fname):
stem = fname.split(os.path.sep)[-1]
return re.search(r"^(.*)_\d+\.jpg$", stem).groups()[0]extract_label(fname)
…
"beagle"다음으로 이미지와 라벨을 가져오도록 Dataset 클래스를 만들어야 합니다:
class PetsDataset(Dataset):
def __init__(self, file_names, image_transform=None, label_to_id=None):
self.file_names = file_names
self.image_transform = image_transform
self.label_to_id = label_to_id
def __len__(self):
return len(self.file_names)
def __getitem__(self, idx):
fname = self.file_names[idx]
raw_image = PIL.Image.open(fname)
image = raw_image.convert("RGB")
if self.image_transform is not None:
image = self.image_transform(image)
label = extract_label(fname)
if self.label_to_id is not None:
label = self.label_to_id[label]
return {"image": image, "label": label}이제 데이터셋을 구성합니다. 학습 함수 외부에서 모든 파일 이름과 라벨을 찾아 선언하고, 실행되는 함수 내에서 참조로 사용할 수 있습니다:
fnames = [os.path.join("../../images", fname) for fname in fnames if fname.endswith(".jpg")]이제 모든 라벨을 수집합니다:
all_labels = [extract_label(fname) for fname in fnames]
id_to_label = list(set(all_labels))
id_to_label.sort()
label_to_id = {lbl: i for i, lbl in enumerate(id_to_label)}다음으로, 만든 dataloaders를 반환하는 get_dataloaders 함수를 만들어야 합니다. 앞서 언급했듯이, DataLoaders를 만들 때 데이터가 자동으로 GPU 또는 TPU 장치로 전송되는 경우, 이 메서드를 사용하여 만들어야 합니다.
def get_dataloaders(batch_size: int = 64):
"Builds a set of dataloaders with a batch_size"
random_perm = np.random.permutation(len(fnames))
cut = int(0.8 * len(fnames))
train_split = random_perm[:cut]
eval_split = random_perm[cut:]
# For training a simple RandomResizedCrop will be used
train_tfm = Compose([RandomResizedCrop((224, 224), scale=(0.5, 1.0)), ToTensor()])
train_dataset = PetsDataset([fnames[i] for i in train_split], image_transform=train_tfm, label_to_id=label_to_id)
# For evaluation a deterministic Resize will be used
eval_tfm = Compose([Resize((224, 224)), ToTensor()])
eval_dataset = PetsDataset([fnames[i] for i in eval_split], image_transform=eval_tfm, label_to_id=label_to_id)
# Instantiate dataloaders
train_dataloader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size, num_workers=4)
eval_dataloader = DataLoader(eval_dataset, shuffle=False, batch_size=batch_size * 2, num_workers=4)
return train_dataloader, eval_dataloader마지막으로, 나중에 사용할 스케줄러를 가져와야 합니다:
from torch.optim.lr_scheduler import CosineAnnealingLR학습 함수 작성
이제 학습 루프를 구성할 수 있습니다. notebook_launcher()는 분산 시스템 전체에서 실행될 호출 대상 함수를 전달하는 방식으로 작동합니다.
동물 분류 문제를 위한 기본적인 학습 루프는 다음과 같습니다:
각 섹션에 대한 설명을 제공하기 위해 코드를 분리했습니다. 복사하여 붙여넣을 수 있는 전체 버전은 마지막에 제공됩니다.
def training_loop(mixed_precision="fp16", seed: int = 42, batch_size: int = 64):
set_seed(seed)
accelerator = Accelerator(mixed_precision=mixed_precision)먼저 학습 루프에서 가능한 한 초기에 시드를 설정하고 Accelerator 객체를 생성해야 합니다.
TPU에서 학습하는 경우, 학습 루프는 모델을 매개변수로 받아야 하며, 모델은 학습 루프 함수 외부에서 인스턴스화되어야 합니다. 그 이유는 TPU 모범 사례에서 확인해 보세요.
다음으로 데이터 로더를 구축하고 모델을 생성해야 합니다:
train_dataloader, eval_dataloader = get_dataloaders(batch_size)
model = create_model("resnet50d", pretrained=True, num_classes=len(label_to_id))시드가 새로운 가중치 초기화도 제어하도록 모델을 여기서 구성합니다
이 예제에서는 전이 학습을 수행하므로, 모델의 인코더는 처음에 동결되어 모델의 헤드만 초기에 학습되도록 설정됩니다:
for param in model.parameters():
param.requires_grad = False
for param in model.get_classifier().parameters():
param.requires_grad = True이미지 배치 정규화는 학습 속도를 약간 빠르게 만듭니다:
mean = torch.tensor(model.default_cfg["mean"])[None, :, None, None]
std = torch.tensor(model.default_cfg["std"])[None, :, None, None]이 상수들을 활성 디바이스에서 사용할 수 있도록 하려면, Accelerator의 device로 설정해야 합니다:
mean = mean.to(accelerator.device)
std = std.to(accelerator.device)다음으로 학습에 사용되는 나머지 PyTorch 클래스를 인스턴스화합니다:
optimizer = torch.optim.Adam(params=model.parameters(), lr=3e-2 / 25)
lr_scheduler = OneCycleLR(optimizer=optimizer, max_lr=3e-2, epochs=5, steps_per_epoch=len(train_dataloader))모든 것을 prepare()에 전달하기 전에.
특정 순서를 기억할 필요는 없으며, prepare 메서드에 전달한 순서와 동일한 순서로 객체를 언팩하기만 하면 됩니다.
model, optimizer, train_dataloader, eval_dataloader, lr_scheduler = accelerator.prepare(
model, optimizer, train_dataloader, eval_dataloader, lr_scheduler
)이제 모델을 학습합니다:
for epoch in range(5):
model.train()
for batch in train_dataloader:
inputs = (batch["image"] - mean) / std
outputs = model(inputs)
loss = torch.nn.functional.cross_entropy(outputs, batch["label"])
accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()평가 루프는 학습 루프와 약간 다른 형태를 띱니다. 각 배치에 전달된 요소의 수와 전체 정확도는 두 개의 상수에 추가됩니다:
model.eval()
accurate = 0
num_elems = 0다음으로 표준 PyTorch 루프의 나머지 부분이 있습니다:
for batch in eval_dataloader:
inputs = (batch["image"] - mean) / std
with torch.no_grad():
outputs = model(inputs)
predictions = outputs.argmax(dim=-1)마지막으로, 가장 큰 차이점입니다.
분산 평가 수행 시 예측값과 라벨을 gather()를 통해 전달해야 현재 디바이스에 모든 데이터가 사용 가능해지고 올바르게 계산된 메트릭을 얻을 수 있습니다:
accurate_preds = accelerator.gather(predictions) == accelerator.gather(batch["label"])
num_elems += accurate_preds.shape[0]
accurate += accurate_preds.long().sum()이제 이 문제에 대한 실제 메트릭을 계산하면 되며, print()를 사용하여 메인 프로세스에서 출력할 수 있습니다:
eval_metric = accurate.item() / num_elems
accelerator.print(f"epoch {epoch}: {100 * eval_metric:.2f}")이 학습 루프의 전체 버전은 아래에서 확인할 수 있습니다:
def training_loop(mixed_precision="fp16", seed: int = 42, batch_size: int = 64):
set_seed(seed)
# Initialize accelerator
accelerator = Accelerator(mixed_precision=mixed_precision)
# Build dataloaders
train_dataloader, eval_dataloader = get_dataloaders(batch_size)
# Instantiate the model (you build the model here so that the seed also controls new weight initializations)
model = create_model("resnet50d", pretrained=True, num_classes=len(label_to_id))
# Freeze the base model
for param in model.parameters():
param.requires_grad = False
for param in model.get_classifier().parameters():
param.requires_grad = True
# You can normalize the batches of images to be a bit faster
mean = torch.tensor(model.default_cfg["mean"])[None, :, None, None]
std = torch.tensor(model.default_cfg["std"])[None, :, None, None]
# To make these constants available on the active device, set it to the accelerator device
mean = mean.to(accelerator.device)
std = std.to(accelerator.device)
# Instantiate the optimizer
optimizer = torch.optim.Adam(params=model.parameters(), lr=3e-2 / 25)
# Instantiate the learning rate scheduler
lr_scheduler = OneCycleLR(optimizer=optimizer, max_lr=3e-2, epochs=5, steps_per_epoch=len(train_dataloader))
# Prepare everything
# There is no specific order to remember, you just need to unpack the objects in the same order you gave them to the
# prepare method.
model, optimizer, train_dataloader, eval_dataloader, lr_scheduler = accelerator.prepare(
model, optimizer, train_dataloader, eval_dataloader, lr_scheduler
)
# Now you train the model
for epoch in range(5):
model.train()
for batch in train_dataloader:
inputs = (batch["image"] - mean) / std
outputs = model(inputs)
loss = torch.nn.functional.cross_entropy(outputs, batch["label"])
accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
model.eval()
accurate = 0
num_elems = 0
for batch in eval_dataloader:
inputs = (batch["image"] - mean) / std
with torch.no_grad():
outputs = model(inputs)
predictions = outputs.argmax(dim=-1)
accurate_preds = accelerator.gather(predictions) == accelerator.gather(batch["label"])
num_elems += accurate_preds.shape[0]
accurate += accurate_preds.long().sum()
eval_metric = accurate.item() / num_elems
# Use accelerator.print to print only on the main process.
accelerator.print(f"epoch {epoch}: {100 * eval_metric:.2f}")notebook_launcher 사용
남은 것은 notebook_launcher()를 사용하는 것입니다.
함수, 인자(튜플 형태), 그리고 학습에 사용할 프로세스 수를 전달합니다. (자세한 내용은 문서를 참고하세요)
from accelerate import notebook_launcherargs = ("fp16", 42, 64)
notebook_launcher(training_loop, args, num_processes=2)여러 노드에서 실행하는 경우, 각 노드에서 Jupyter 세션을 설정하고 동시에 실행 셀을 실행해야 합니다.
IP 주소가 “172.31.43.8”인 메인 컴퓨터를 포함하여, 각 노드(컴퓨터)에 8개의 GPU가 있는 2개 노드 환경의 경우 다음과 같습니다:
notebook_launcher(training_loop, args, master_addr="172.31.43.8", node_rank=0, num_nodes=2, num_processes=8)그리고 다른 머신의 두 번째 Jupyter 세션에서:
node_rank가 변경된 것을 주목해 보세요
notebook_launcher(training_loop, args, master_addr="172.31.43.8", node_rank=1, num_nodes=2, num_processes=8)TPU에서 실행하는 경우 다음과 같습니다:
model = create_model("resnet50d", pretrained=True, num_classes=len(label_to_id))
args = (model, "fp16", 42, 64)
notebook_launcher(training_loop, args, num_processes=8)탄력성(elasticity)을 통해 장애 허용을 활성화하여 학습 프로세스를 시작하려면 PyTorch에서 제공하는 elastic_launch 기능을 사용할 수 있습니다. 이를 위해 rdzv_backend 및 max_restarts와 같은 추가 매개변수를 설정해야 합니다. 탄력성 기능을 갖춘 notebook_launcher를 사용하는 예시는 다음과 같습니다:
notebook_launcher(
training_loop,
args,
num_processes=2,
max_restarts=3
)실행 중에는 진행 상황과 함께 몇 개의 디바이스에서 실행되었는지 표시됩니다. 이 튜토리얼은 두 개의 GPU로 실행되었습니다:
Launching training on 2 GPUs.
epoch 0: 88.12
epoch 1: 91.73
epoch 2: 92.58
epoch 3: 93.90
epoch 4: 94.71그리고 끝입니다!
notebook_launcher()는 Accelerate 설정 파일을 무시한다는 점에 유의하세요. 설정을 기반으로 실행하려면 다음을 사용하세요:
accelerate launch
디버깅
notebook_launcher를 실행할 때 흔히 발생하는 문제는 CUDA/XPU has already been initialized 오류를 받는 것입니다. 이는 일반적으로 노트북 내의 import 또는 이전 코드에서 PyTorch의 torch.cuda 또는 torch.xpu 서브라이브러리를 호출하기 때문에 발생합니다. 문제를 좁혀 찾기 위해 환경 변수에 ACCELERATE_DEBUG_MODE=yes를 설정하고 notebook_launcher를 실행할 수 있습니다. 그러면 프로세스를 생성할 때 일반 프로세스가 생성되고 CUDA/XPU를 문제 없이 사용할 수 있는지 추가적으로 확인됩니다. (이후에도 CUDA/XPU 코드를 실행할 수 있습니다).
결론
이 노트북에서는 Jupyter Notebook 안에서 분산 학습을 수행하는 방법을 보여 주었습니다. 기억해 두어야 할 주요 사항:
- CUDA/XPU를 사용하거나 (또는 CUDA/XPU를 import하는) 코드는 notebook_launcher()에 전달되는 함수에 저장하는지 확인하세요.
num_processes를 학습에 사용되는 장치의 수(GPU, XPU, CPU, TPU 등)로 설정하세요.- TPU를 사용하는 경우, 모델은 학습 루프 함수 외부에서 선언해야 합니다