

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# 1단계: SageMaker의 분산 모델 병렬 라이브러리를 사용하여 자체 훈련 스크립트 수정
<a name="model-parallel-customize-training-script"></a>

이 섹션에서는 Amazon SageMaker AI 모델 병렬화 라이브러리의 핵심 기능을 사용하도록 훈련 스크립트를 사용자 지정하는 방법을 알아봅니다. 라이브러리 고유의 API 함수 및 파라미터를 사용하려면 *SageMaker Python SDK 설명서*의 [SageMaker 모델 병렬 라이브러리 API](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smd_model_parallel.html)와 함께 이 설명서를 사용하는 것이 좋습니다.

이 섹션에 제공된 훈련 스크립트 예제는 단순화되었으며 라이브러리를 사용하기 위해 수행해야 하는 필수 변경 사항을 강조하도록 설계되었습니다. SageMaker 모델 병렬화 라이브러리와 함께 TensorFlow 또는 PyTorch 훈련 스크립트를 사용하는 방법을 보여주는 실행 가능한 엔드-투-엔드 노트북 예제는 [Amazon SageMaker AI 모델 병렬화 라이브러리 v2 예시](distributed-model-parallel-v2-examples.md)을/를 참조하세요.

**Topics**
+ [SageMaker 모델 병렬화 라이브러리를 사용하여 훈련 스크립트 모델을 분할합니다](#model-parallel-model-splitting-using-smp-lib)
+ [TensorFlow 훈련 스크립트 수정](model-parallel-customize-training-script-tf.md)
+ [PyTorch 훈련 스크립트 수정](model-parallel-customize-training-script-pt.md)

## SageMaker 모델 병렬화 라이브러리를 사용하여 훈련 스크립트 모델을 분할합니다
<a name="model-parallel-model-splitting-using-smp-lib"></a>

훈련 스크립트를 수정하여 모델 분할을 설정하는 방법에는 자동 분할 또는 수동 분할이라는 두 가지 방법이 있습니다.

### 자동 모델 분할
<a name="model-parallel-automated-model-splitting"></a>

SageMaker의 모델 병렬화 라이브러리를 사용하면 *자동 모델 파티션*이라고도 하는 *자동 모델 분할*을 활용할 수 있습니다. 라이브러리는 메모리 균형을 유지하고 디바이스 간 통신을 최소화하며 성능을 최적화하는 파티셔닝 알고리즘을 사용합니다. 속도 또는 메모리를 최적화하도록 자동 파티셔닝 알고리즘을 구성할 수 있습니다.

또는 수동 모델 분할을 사용할 수 있습니다. 모델 아키텍처에 익숙하고 모델을 효율적으로 분할하는 방법을 잘 아는 경우가 아니라면 자동 모델 분할을 사용하는 것이 좋습니다.

#### 작동 방식
<a name="model-parallel-automated-model-splitting-how-it-works"></a>

자동 파티셔닝은 `smp.step`로 데코레이트된 함수를 처음 호출하는 첫 번째 훈련 단계에서 발생합니다. 이 호출 중에 라이브러리는 GPU 메모리 제한을 피하기 위해 먼저 CPU RAM에 대한 모델 버전을 구성한 다음 모델 그래프를 분석하고 파티셔닝을 결정합니다. 이 결정에 따라 각 모델 파티션을 GPU에 로드한 다음 첫 번째 단계만 실행합니다. 이러한 분석 및 파티셔닝 단계 때문에 첫 번째 훈련 단계는 더 오래 걸릴 수 있습니다.

어느 프레임워크에서든 라이브러리는 AWS 인프라에 최적화된 자체 백엔드를 통해 디바이스 간의 통신을 관리합니다.

자동 파티션 디자인은 프레임워크의 특성에 맞게 조정되며 라이브러리는 각 프레임워크에서 보다 자연스러운 상세한 수준에서 파티셔닝을 수행합니다. 예를 들어 TensorFlow에서는 각 특정 작업을 서로 다른 디바이스에 할당할 수 있지만 PyTorch에서는 할당이 모듈 수준에서 수행되며 각 모듈은 여러 작업으로 구성됩니다. 다음 섹션에서는 각 프레임워크의 설계 세부 사항을 검토합니다.

##### PyTorch를 사용한 자동 모델 분할
<a name="model-parallel-auto-model-split-pt"></a>

첫 번째 훈련 단계에서 모델 병렬화 라이브러리는 모델 그래프를 구성하고 텐서 및 파라미터 형태를 결정하기 위한 추적 단계를 내부적으로 실행합니다. 이 추적 단계가 끝나면 라이브러리는 트리를 생성합니다. 트리는 모델에 중첩된 `nn.Module` 객체뿐만 아니라 추적에서 수집한 추가 데이터(예: 저장된 `nn.Parameters`의 량, 각 `nn.Module`의 실행 시간)로 구성됩니다.

그런 다음 라이브러리는 근본부터 이 트리를 탐색하여 각 `nn.Module`을 디바이스에 할당하는 파티션 알고리즘을 실행합니다. 이 알고리즘은 컴퓨팅 부하(모듈 실행 시간으로 측정)와 메모리 사용(총 저장된 `nn.Parameter` 크기 및 활성화로 측정)의 균형을 맞춥니다. 여러 개의 `nn.Modules`이 동일한 `nn.Parameter`을 공유하는 경우 동일한 파라미터를 여러 버전으로 유지하지 않도록 이러한 모듈이 동일한 디바이스에 배치됩니다. 파티셔닝 결정이 내려지면 할당된 모듈과 가중치가 해당 디바이스에 로드됩니다.

PyTorch 훈련 스크립트에 `smp.step` 데코레이터를 등록하는 방법에 대한 지침은 [PyTorch를 사용한 자동 분할](model-parallel-customize-training-script-pt.md#model-parallel-customize-training-script-pt-16)을/를 참조하세요.

##### TensorFlow를 사용한 자동 모델 분할
<a name="model-parallel-auto-model-split-tf"></a>

모델 병렬화 라이브러리는 훈련 가능한 변수의 크기와 그래프 구조를 분석하고 내부적으로 그래프 분할 알고리즘을 사용합니다. 이 알고리즘은 디바이스 간에 필요한 통신량을 최소화하기 위해 각 작업에 디바이스를 할당하는데, 여기에는 두 가지 제약이 따릅니다.
+ 각 디바이스에 저장된 변수의 수 균형 조정
+ 각 디바이스에서 실행되는 작업 수의 균형 조정

(Python SDK의 모델 병렬화 파라미터에서) `optimize`를 위한 `speed`를 지정하면 라이브러리는 각 디바이스의 연산과 `tf.Variable` 객체 수의 균형을 맞추려고 합니다. 그렇지 않으면 `tf.Variables`의 전체 크기의 균형을 맞추려고 합니다.

파티셔닝 결정이 내려지면 라이브러리는 각 디바이스에서 실행해야 하는 서브그래프를 직렬화된 형태로 만들어 각 디바이스로 가져옵니다. 파티셔닝 중에 라이브러리는 동일한 `tf.Variable`을 사용하는 작업과 동일한 Keras 계층에 속하는 작업을 동일한 디바이스에 배치합니다. 또한 TensorFlow에서 부과하는 코로케이션 제약도 고려합니다. 즉, 예를 들어 `tf.Variable`을 공유하는 두 개의 Keras 계층이 있는 경우 이러한 계층에 속하는 모든 작업이 단일 디바이스에 배치됩니다.

PyTorch 훈련 스크립트에 `smp.step` 데코레이터를 등록하는 방법에 대한 지침은 [TensorFlow를 사용한 자동 분할](model-parallel-customize-training-script-tf.md#model-parallel-customize-training-script-tf-23)을/를 참조하세요.

##### 프레임워크 간 자동 모델 분할 비교
<a name="model-parallel-auto-model-split-comparison"></a>

TensorFlow에서 기본 계산 단위는 `tf.Operation`이고, TensorFlow는 모델을 `tf.Operation`의 DAG(유방향 비순환 그래프)로 나타내므로 모델 병렬화 라이브러리는 이 DAG를 파티션하여 각 노드가 하나의 기기로 이동하도록 합니다. 결정적으로, `tf.Operation` 객체에는 사용자 지정 가능한 속성이 충분히 풍부하며, 모든 모델이 해당 객체의 그래프로 구성된다는 점에서 보편적입니다.

반면에 PyTorch는 충분히 풍부하고 보편적인 동등한 작업 개념을 가지고 있지 않습니다. 이러한 특성을 가진 PyTorch의 계산 단위 중 가장 가까운 단위는 훨씬 더 높은 세분성 수준에 있는 `nn.Module`입니다. 이것이 바로 PyTorch에서 라이브러리가 이 수준에서 파티셔닝을 수행하는 이유입니다.

### 수동 모델 분할
<a name="model-parallel-manual-model-splitting"></a>

디바이스 간에 모델을 분할하는 방법을 수동으로 지정하려면 `smp.partition` 컨텍스트 관리자를 사용하세요. 수동 파티셔닝을 위한 컨텍스트 매니저를 설정하는 자세한 방법은 다음 페이지를 참조하세요.
+ [TensorFlow를 사용한 수동 분할](model-parallel-customize-training-script-tf.md#model-parallel-customize-training-script-tf-manual)
+ [PyTorch를 사용한 수동 분할](model-parallel-customize-training-script-pt.md#model-parallel-customize-training-script-pt-16-hvd)

수정 후 이 옵션을 사용하려면 2단계에서 SageMaker Python SDK의 프레임워크 추정기 클래스에서 `auto_partition`을 `False`로 지정하고 `default_partition`을 정의해야 합니다. `smp.partition` 컨텍스트 관리자를 통해 파티션에 명시적으로 배치되지 않은 모든 작업은 `default_partition`에서 실행됩니다. 이 경우 자동 분할 로직이 우회되고 각 작업은 사양에 따라 배치됩니다. 결과 그래프 구조를 기반으로 모델 병렬화 라이브러리는 파이프라인 실행 일정을 자동으로 생성합니다.

# TensorFlow 훈련 스크립트 수정
<a name="model-parallel-customize-training-script-tf"></a>

이 섹션에서는 TensorFlow 훈련 스크립트를 수정하여 자동 파티셔닝과 수동 파티셔닝을 위한 SageMaker 모델 병렬화 라이브러리를 구성하는 방법을 알아봅니다. 이 예제 모음에는 하이브리드 모델 및 데이터 병렬화를 위해 Horovod와 통합된 예제도 포함되어 있습니다.

**참고**  
라이브러리에서 지원하는 TensorFlow 버전을 찾으려면 [지원되는 프레임워크 및 AWS 리전](distributed-model-parallel-support.md)을/를 참조하세요.

라이브러리를 사용하기 위해 훈련 스크립트를 수정해야 하는 필수 사항은 [TensorFlow를 사용한 자동 분할](#model-parallel-customize-training-script-tf-23)에 나열되어 있습니다.

Horovod에서 하이브리드 모델 및 데이터 병렬화를 사용하도록 훈련 스크립트를 수정하는 방법을 알아보려면 [하이브리드 모델 및 데이터 병렬화를 위한 TensorFlow 및 Horovod를 사용한 자동 분할](#model-parallel-customize-training-script-tf-2.3)을/를 참조하세요.

수동 파티셔닝을 사용하려는 경우에도 [TensorFlow를 사용한 수동 분할](#model-parallel-customize-training-script-tf-manual)을/를 검토하세요.

다음 주제에서는 TensorFlow 모델의 자동 파티셔닝과 수동 파티셔닝을 위한 SageMaker의 모델 병렬화 라이브러리를 구성하는 데 사용할 수 있는 훈련 스크립트의 예를 보여줍니다.

**참고**  
자동 파티셔닝은 기본적으로 활성화되어 있습니다. 달리 지정하지 않는 한, 예제 스크립트는 자동 파티셔닝을 사용합니다.

**Topics**
+ [TensorFlow를 사용한 자동 분할](#model-parallel-customize-training-script-tf-23)
+ [하이브리드 모델 및 데이터 병렬화를 위한 TensorFlow 및 Horovod를 사용한 자동 분할](#model-parallel-customize-training-script-tf-2.3)
+ [TensorFlow를 사용한 수동 분할](#model-parallel-customize-training-script-tf-manual)
+ [지원되지 않는 프레임워크 기능](#model-parallel-tf-unsupported-features)

## TensorFlow를 사용한 자동 분할
<a name="model-parallel-customize-training-script-tf-23"></a>

SageMaker의 모델 병렬화 라이브러리로 TensorFlow 모델을 실행하려면 다음과 같은 훈련 스크립트 변경이 필요합니다.

1. [https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#smp.init](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#smp.init)을 사용하여 라이브러리를 가져오고 초기화합니다.

1. Keras 모델 클래스 [https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_tensorflow.html](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_tensorflow.html)에서 상속하여 Keras 모델을 정의합니다. `smp.DistributedModel` 객체의 호출 메서드에서 모델 출력을 반환합니다. 호출 메서드에서 반환되는 모든 텐서는 모델 병렬 장치 간에 브로드캐스트되므로 통신 오버헤드가 발생하므로 호출 메서드 외부에서 필요하지 않은 텐서(예: 중간 활성화)는 반환되지 않아야 합니다.

1. `tf.Dataset.batch()` 메서드에서 `drop_remainder=True`로 설정합니다. 이는 배치 크기를 항상 마이크로배치 수로 나눌 수 있도록 하기 위한 것입니다.

1. `smp.dp_rank()`를 사용하여 데이터 파이프라인에서 무작위 작업을 시드(Seed) 하세요. 예를 들어 서로 다른 모델 파티션을 포함하는 GPU 간의 데이터 샘플의 일관성을 보장하기 위한 `shuffle(ds, seed=smp.dp_rank())`를 시드합니다.

1. 순방향 및 역방향 로직을 Step Function에 넣고 이를 `smp.step`로 데코레이트하세요.

1. `reduce_mean`과 같은 [https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#StepOutput](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#StepOutput) 메서드를 사용하여 마이크로배치의 출력값에 대해 후처리를 수행합니다. [https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#smp.init](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#smp.init) 함수는 `smp.DistributedModel`의 출력값에 따라 달라지는 반환 값이 있어야 합니다.

1. 평가 단계가 있는 경우에도 마찬가지로 `smp.step`로 데코레이팅된 함수 안에 순방향 로직을 배치하고 [`StepOutput` API](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#StepOutput)를 사용하여 출력값을 후처리하세요.

SageMaker의 모델 병렬화 라이브러리 API에 대한 자세한 내용은 [API 설명서](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smd_model_parallel.html)를 참조하세요.

다음 Python 스크립트는 변경 후의 훈련 스크립트의 예입니다.

```
import tensorflow as tf

# smdistributed: Import TF2.x API
import smdistributed.modelparallel.tensorflow as smp

# smdistributed: Initialize
smp.init()

# Download and load MNIST dataset.
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data(
    "MNIST-data-%d" % smp.rank()
)
x_train, x_test = x_train / 255.0, x_test / 255.0

# Add a channels dimension
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]

# smdistributed: If needed, seed the shuffle with smp.dp_rank(), and drop_remainder
# in batching to make sure batch size is always divisible by number of microbatches
train_ds = (
    tf.data.Dataset.from_tensor_slices((x_train, y_train))
    .shuffle(10000, seed=smp.dp_rank())
    .batch(256, drop_remainder=True)
)

# smdistributed: Define smp.DistributedModel the same way as Keras sub-classing API 
class MyModel(smp.DistributedModel):
    def __init__(self):
        super(MyModel, self).__init__()
        # define layers

    def call(self, x, training=None):
        # define forward pass and return the model output

model = MyModel()

loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
optimizer = tf.keras.optimizers.Adam()
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name="train_accuracy")

# smdistributed: Define smp.step. Return any tensors needed outside
@smp.step
def get_grads(images, labels):
    predictions = model(images, training=True)
    loss = loss_object(labels, predictions)

    grads = optimizer.get_gradients(loss, model.trainable_variables)
    return grads, loss, predictions


@tf.function
def train_step(images, labels):
    gradients, loss, predictions = get_grads(images, labels)

    # smdistributed: Accumulate the gradients across microbatches
    gradients = [g.accumulate() for g in gradients]
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

    # smdistributed: Merge predictions and average losses across microbatches
    train_accuracy(labels, predictions.merge())
    return loss.reduce_mean()


for epoch in range(5):
    # Reset the metrics at the start of the next epoch
    train_accuracy.reset_states()
    for images, labels in train_ds:
        loss = train_step(images, labels)
    accuracy = train_accuracy.result()
```

훈련 스크립트 준비를 마쳤으면 [2단계: SageMaker Python SDK를 사용하여 훈련 작업 시작](model-parallel-sm-sdk.md)으로 진행하세요. 하이브리드 모델 및 데이터 병렬화 훈련 작업을 실행하려면 다음 섹션으로 계속합니다.

## 하이브리드 모델 및 데이터 병렬화를 위한 TensorFlow 및 Horovod를 사용한 자동 분할
<a name="model-parallel-customize-training-script-tf-2.3"></a>

하이브리드 모델 및 데이터 병렬화를 위해 SageMaker 모델 병렬화 라이브러리를 Horovod와 함께 사용할 수 있습니다. 라이브러리가 하이브리드 병렬화를 위해 모델을 분할하는 방법에 대한 자세한 내용은 [파이프라인 병렬화(PyTorch 및 TensorFlow에서 사용 가능)](model-parallel-intro.md#model-parallel-intro-pp)을/를 참조하세요.

이 단계에서는 SageMaker 모델 병렬화 라이브러리에 맞게 훈련 스크립트를 수정하는 방법을 중점적으로 다룹니다.

[2단계: SageMaker Python SDK를 사용하여 훈련 작업 시작](model-parallel-sm-sdk.md)에서 설정할 하이브리드 병렬화 구성을 선택하도록 훈련 스크립트를 올바르게 설정하려면 데이터 병렬 순위와 모델 병렬 순위를 각각 자동으로 감지하는 라이브러리의 도우미 함수 `smp.dp_rank()` 및 `smp.mp_rank()`를 사용하세요.

라이브러리가 지원하는 모든 MPI 프리미티브를 찾으려면 SageMaker Python SDK 설명서에서 [MPI 기본](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#mpi-basics)을 참조하세요.

스크립트에 필요한 변경 사항은 다음과 같습니다.
+ `hvd.allreduce` 추가
+ Horovod에서 요구하는 대로 첫 번째 배치 이후에 변수를 브로드캐스팅합니다
+ `smp.dp_rank()`를 사용하여 데이터 파이프라인의 셔플링 및/또는 샤딩 작업을 시드(seed) 합니다.

**참고**  
Horovod를 사용할 때는 훈련 스크립트에서 `hvd.init`을 직접 호출해서는 안 됩니다. 대신 [2단계: SageMaker Python SDK를 사용하여 훈련 작업 시작](model-parallel-sm-sdk.md)의 SageMaker Python SDK `modelparallel` 파라미터에서 `"horovod"`을 `True`로 설정해야 합니다. 이렇게 하면 라이브러리가 모델 파티션의 디바이스 할당을 기반으로 Horovod를 내부적으로 초기화할 수 있습니다. 훈련 스크립트에서 `hvd.init()`을 직접 호출하면 문제가 발생할 수 있습니다.

**참고**  
훈련 스크립트에서 직접 `hvd.DistributedOptimizer` API를 사용하면 API가 `AllReduce` 작업을 암시적으로 `smp.step` 내부에 배치하기 때문에 훈련 성능과 속도가 저하될 수 있습니다. 다음 예와 같이 `smp.step`에서 반환된 그래디언트에 따라 `accumulate()` 또는 `reduce_mean()`을 호출한 후 `hvd.allreduce`를 직접 호출하여 Horovod와 함께 모델 병렬화 라이브러리를 사용하는 것이 좋습니다.

SageMaker의 모델 병렬화 라이브러리 API에 대한 자세한 내용은 [API 설명서](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smd_model_parallel.html)를 참조하세요.

```
import tensorflow as tf
import horovod.tensorflow as hvd

# smdistributed: Import TF2.x API 
import smdistributed.modelparallel.tensorflow as smp

# smdistributed: Initialize
smp.init()

# Download and load MNIST dataset.
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data(
    "MNIST-data-%d" % smp.rank()
)
x_train, x_test = x_train / 255.0, x_test / 255.0

# Add a channels dimension
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]

# smdistributed: Seed the shuffle with smp.dp_rank(), and drop_remainder
# in batching to make sure batch size is always divisible by number of microbatches
train_ds = (
    tf.data.Dataset.from_tensor_slices((x_train, y_train))
    .shuffle(10000, seed=smp.dp_rank())
    .batch(256, drop_remainder=True)
)

# smdistributed: Define smp.DistributedModel the same way as Keras sub-classing API 
class MyModel(smp.DistributedModel):
    def __init__(self):
        super(MyModel, self).__init__()
        # define layers

    def call(self, x, training=None):
        # define forward pass and return model outputs


model = MyModel()

loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
optimizer = tf.keras.optimizers.Adam()
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name="train_accuracy")

# smdistributed: Define smp.step. Return any tensors needed outside
@smp.step
def get_grads(images, labels):
    predictions = model(images, training=True)
    loss = loss_object(labels, predictions)

    grads = optimizer.get_gradients(loss, model.trainable_variables)
    return grads, loss, predictions


@tf.function
def train_step(images, labels, first_batch):
    gradients, loss, predictions = get_grads(images, labels)

    # smdistributed: Accumulate the gradients across microbatches
    # Horovod: AllReduce the accumulated gradients
    gradients = [hvd.allreduce(g.accumulate()) for g in gradients]
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

    # Horovod: Broadcast the variables after first batch 
    if first_batch:
        hvd.broadcast_variables(model.variables, root_rank=0)
        hvd.broadcast_variables(optimizer.variables(), root_rank=0)

    # smdistributed: Merge predictions across microbatches
    train_accuracy(labels, predictions.merge())
    return loss.reduce_mean()


for epoch in range(5):
    # Reset the metrics at the start of the next epoch
    train_accuracy.reset_states()

    for batch, (images, labels) in enumerate(train_ds):
        loss = train_step(images, labels, tf.constant(batch == 0))
```

## TensorFlow를 사용한 수동 분할
<a name="model-parallel-customize-training-script-tf-manual"></a>

`smp.partition` 컨텍스트 관리자를 사용하여 작업을 특정 파티션에 배치합니다. `smp.partition` 컨텍스트에 배치되지 않은 모든 작업은 `default_partition`에 배치됩니다. SageMaker의 모델 병렬화 라이브러리 API에 대한 자세한 내용은 [API 설명서](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smd_model_parallel.html)를 참조하세요.

```
import tensorflow as tf

# smdistributed: Import TF2.x API.
import smdistributed.modelparallel.tensorflow as smp

# smdistributed: Initialize
smp.init()

# Download and load MNIST dataset.
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data(
    "MNIST-data-%d" % smp.rank()
)
x_train, x_test = x_train / 255.0, x_test / 255.0

# Add a channels dimension
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]

# smdistributed: If needed, seed the shuffle with smp.dp_rank(), and drop_remainder
# in batching to make sure batch size is always divisible by number of microbatches.
train_ds = (
    tf.data.Dataset.from_tensor_slices((x_train, y_train))
    .shuffle(10000, seed=smp.dp_rank())
    .batch(256, drop_remainder=True)
)

# smdistributed: Define smp.DistributedModel the same way as Keras sub-classing API.
class MyModel(smp.DistributedModel):
    def __init__(self):
         # define layers

    def call(self, x):
        with smp.partition(0):
            x = self.layer0(x)
        with smp.partition(1):
            return self.layer1(x)


model = MyModel()

loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
optimizer = tf.keras.optimizers.Adam()
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name="train_accuracy")

# smdistributed: Define smp.step. Return any tensors needed outside
@smp.step
def get_grads(images, labels):
    predictions = model(images, training=True)
    loss = loss_object(labels, predictions)

    grads = optimizer.get_gradients(loss, model.trainable_variables)
    return grads, loss, predictions


@tf.function
def train_step(images, labels):
    gradients, loss, predictions = get_grads(images, labels)

    # smdistributed: Accumulate the gradients across microbatches
    gradients = [g.accumulate() for g in gradients]
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

    # smdistributed: Merge predictions and average losses across microbatches
    train_accuracy(labels, predictions.merge())
    return loss.reduce_mean()


for epoch in range(5):
    # Reset the metrics at the start of the next epoch
    train_accuracy.reset_states()
    for images, labels in train_ds:
        loss = train_step(images, labels)
    accuracy = train_accuracy.result()
```

## 지원되지 않는 프레임워크 기능
<a name="model-parallel-tf-unsupported-features"></a>

다음 TensorFlow 기능은 라이브러리에서 지원되지 않습니다.
+ 현재 `tf.GradientTape()`은 지원되지 않습니다. 대신 `Optimizer.get_gradients()` 또는 `Optimizer.compute_gradients()`를 사용하여 그래디언트를 계산할 수 있습니다.
+ 현재 `tf.train.Checkpoint.restore()` API는 지원되지 않습니다. 체크포인팅의 경우 대신 `smp.CheckpointManager`를 사용합니다. 이는 동일한 API와 기능을 제공합니다. `smp.CheckpointManager`을 이용한 체크포인트 복원은 첫 번째 단계 이후에 이루어져야 한다는 점에 유의하세요.

# PyTorch 훈련 스크립트 수정
<a name="model-parallel-customize-training-script-pt"></a>

이 섹션에서는 PyTorch 훈련 스크립트를 수정하여 자동 파티셔닝과 수동 파티셔닝을 위한 SageMaker 모델 병렬화 라이브러리를 구성하는 방법을 알아봅니다.

**참고**  
라이브러리에서 지원하는 PyTorch 버전을 찾으려면 [지원되는 프레임워크 및 AWS 리전](distributed-model-parallel-support.md)을/를 참조하세요.

**작은 정보**  
SageMaker 모델 병렬화 라이브러리와 함께 PyTorch 훈련 스크립트를 사용하는 방법을 보여주는 엔드-투-엔드 노트북 예제는 [Amazon SageMaker AI 모델 병렬화 라이브러리 v1 예시](distributed-model-parallel-examples.md)을/를 참조하세요.

자동 파티셔닝은 기본적으로 활성화되어 있음을 유의하세요. 달리 지정하지 않는 한, 다음 스크립트는 자동 파티셔닝을 사용합니다.

**Topics**
+ [PyTorch를 사용한 자동 분할](#model-parallel-customize-training-script-pt-16)
+ [PyTorch를 사용한 수동 분할](#model-parallel-customize-training-script-pt-16-hvd)
+ [고려 사항](#model-parallel-pt-considerations)
+ [지원되지 않는 프레임워크 기능](#model-parallel-pt-unsupported-features)

## PyTorch를 사용한 자동 분할
<a name="model-parallel-customize-training-script-pt-16"></a>

SageMaker의 모델 병렬화 라이브러리로 PyTorch 훈련 스크립트를 실행하려면 다음과 같은 훈련 스크립트 변경이 필요합니다.

1. [https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#smp.init](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#smp.init)을 사용하여 라이브러리를 가져오고 초기화합니다.

1. [https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_pytorch.html#smp.DistributedModel](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_pytorch.html#smp.DistributedModel)로 모델을 래핑합니다. 기본 `nn.Module` 객체의 `forward` 메서드에서 반환되는 모든 텐서는 모델 병렬 디바이스 간에 브로드캐스트되므로 통신 오버헤드가 발생하므로 호출 메서드 외부에서 필요하지 않은 텐서(예: 중간 활성화)는 반환되지 않아야 합니다.
**참고**  
FP16 훈련의 경우 [smdistributed.modelparallel.torch.model\$1create()](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/latest/smd_model_parallel_pytorch.html) 컨텍스트 관리자를 사용하여 모델을 래핑해야 합니다. 자세한 내용은 [모델 병렬 처리를 사용한 FP16 훈련](model-parallel-extended-features-pytorch-fp16.md) 섹션을 참조하세요.

1. [https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_pytorch.html#smp.DistributedOptimizer](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_pytorch.html#smp.DistributedOptimizer)를 이용해 옵티마이저를 래핑합니다.
**참고**  
FP16 훈련의 경우 정적 또는 동적 손실 스케일링을 설정해야 합니다. 자세한 내용은 [모델 병렬 처리를 사용한 FP16 훈련](model-parallel-extended-features-pytorch-fp16.md) 섹션을 참조하세요.

1. 사용자 모델 대신 반환된 `DistributedModel` 객체를 사용하세요.

1. 순방향 및 역방향 로직을 Step Function에 넣고 이를 [https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#smp.init](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#smp.init)로 데코레이트하세요.

1. `torch.cuda.set_device(smp.local_rank())`를 통해 각 프로세스를 자체 디바이스로 제한합니다.

1. `smp.step` 호출 전에 `.to()` API를 사용하여 입력 텐서를 GPU로 이동합니다 (아래 예제 참조).

1. `torch.Tensor.backward`와 `torch.autograd.backward`를 `DistributedModel.backward`로 바꾸세요.

1. `reduce_mean`과 같은 [https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#StepOutput](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#StepOutput) 메서드를 사용하여 마이크로배치의 출력값에 대해 후처리를 수행합니다.

1. 평가 단계가 있는 경우에도 마찬가지로 `smp.step`로 데코레이팅된 함수 안에 순방향 로직을 배치하고 [`StepOutput` API](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_common_api.html#StepOutput)를 사용하여 출력값을 후처리하세요.

1. `DataLoader`에서 `drop_last=True`로 설정합니다. 또는 배치 크기를 마이크로배치 수로 나눌 수 없는 경우 훈련 루프에서 배치를 수동으로 건너뛰어도 됩니다.

SageMaker의 모델 병렬화 라이브러리 API에 대한 자세한 내용은 [API 설명서](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smd_model_parallel.html)를 참조하세요.

```
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchnet.dataset import SplitDataset
from torchvision import datasets

import smdistributed.modelparallel.torch as smp

class GroupedNet(nn.Module):
    def __init__(self):
        super(GroupedNet, self).__init__()
        # define layers

    def forward(self, x):
        # define forward pass and return model outputs


# smdistributed: Define smp.step. Return any tensors needed outside.
@smp.step
def train_step(model, data, target):
    output = model(data)
    loss = F.nll_loss(output, target, reduction="mean")
    model.backward(loss)
    return output, loss


def train(model, device, train_loader, optimizer):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        # smdistributed: Move input tensors to the GPU ID used by the current process,
        # based on the set_device call.
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        # Return value, loss_mb is a StepOutput object
        _, loss_mb = train_step(model, data, target)

        # smdistributed: Average the loss across microbatches.
        loss = loss_mb.reduce_mean()

        optimizer.step()

# smdistributed: initialize the backend
smp.init()

# smdistributed: Set the device to the GPU ID used by the current process.
# Input tensors should be transferred to this device.
torch.cuda.set_device(smp.local_rank())
device = torch.device("cuda")

# smdistributed: Download only on a single process per instance.
# When this is not present, the file is corrupted by multiple processes trying
# to download and extract at the same time
dataset = datasets.MNIST("../data", train=True, download=False)

# smdistributed: Shard the dataset based on data-parallel ranks
if smp.dp_size() > 1:
    partitions_dict = {f"{i}": 1 / smp.dp_size() for i in range(smp.dp_size())}
    dataset = SplitDataset(dataset, partitions=partitions_dict)
    dataset.select(f"{smp.dp_rank()}")

# smdistributed: Set drop_last=True to ensure that batch size is always divisible
# by the number of microbatches
train_loader = torch.utils.data.DataLoader(dataset, batch_size=64, drop_last=True)

model = GroupedNet()
optimizer = optim.Adadelta(model.parameters(), lr=4.0)

# smdistributed: Use the DistributedModel container to provide the model
# to be partitioned across different ranks. For the rest of the script,
# the returned DistributedModel object should be used in place of
# the model provided for DistributedModel class instantiation.
model = smp.DistributedModel(model)
optimizer = smp.DistributedOptimizer(optimizer)

train(model, device, train_loader, optimizer)
```

## PyTorch를 사용한 수동 분할
<a name="model-parallel-customize-training-script-pt-16-hvd"></a>

[https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_pytorch.html#smp.DistributedOptimizer](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smp_versions/v1.2.0/smd_model_parallel_pytorch.html#smp.DistributedOptimizer) 컨텍스트 관리자를 사용하여 특정 디바이스에 모듈을 배치합니다. `smp.partition` 컨텍스트에 배치되지 않은 모든 모듈은 `default_partition`에 배치됩니다. `auto_partition`가 `False`로 설정된 경우 `default_partition`을 제공해야 합니다. 특정 `smp.partition` 컨텍스트 내에서 생성된 모듈은 해당 파티션에 배치됩니다.

SageMaker의 모델 병렬화 라이브러리 API에 대한 자세한 내용은 [API 설명서](https://sagemaker.readthedocs.io/en/v2.199.0/api/training/smd_model_parallel.html)를 참조하세요.

```
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchnet.dataset import SplitDataset
from torchvision import datasets

import smdistributed.modelparallel.torch as smp

class GroupedNet(nn.Module):
    def __init__(self):
        super(GroupedNet, self).__init__()
        with smp.partition(0):
            # define child modules on device 0
        with smp.partition(1):
            # define child modules on device 1

    def forward(self, x):
        # define forward pass and return model outputs


# smdistributed: Define smp.step. Return any tensors needed outside.
@smp.step
def train_step(model, data, target):
    output = model(data)
    loss = F.nll_loss(output, target, reduction="mean")
    model.backward(loss)
    return output, loss


def train(model, device, train_loader, optimizer):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        # smdistributed: Move input tensors to the GPU ID used by the current process,
        # based on the set_device call.
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        # Return value, loss_mb is a StepOutput object
        _, loss_mb = train_step(model, data, target)

        # smdistributed: Average the loss across microbatches.
        loss = loss_mb.reduce_mean()

        optimizer.step()

# smdistributed: initialize the backend
smp.init()

# smdistributed: Set the device to the GPU ID used by the current process.
# Input tensors should be transferred to this device.
torch.cuda.set_device(smp.local_rank())
device = torch.device("cuda")

# smdistributed: Download only on a single process per instance.
# When this is not present, the file is corrupted by multiple processes trying
# to download and extract at the same time
dataset = datasets.MNIST("../data", train=True, download=False)

# smdistributed: Shard the dataset based on data-parallel ranks
if smp.dp_size() > 1:
    partitions_dict = {f"{i}": 1 / smp.dp_size() for i in range(smp.dp_size())}
    dataset = SplitDataset(dataset, partitions=partitions_dict)
    dataset.select(f"{smp.dp_rank()}")

# smdistributed: Set drop_last=True to ensure that batch size is always divisible
# by the number of microbatches
train_loader = torch.utils.data.DataLoader(dataset, batch_size=64, drop_last=True)

model = GroupedNet()
optimizer = optim.Adadelta(model.parameters(), lr=4.0)

# smdistributed: Use the DistributedModel container to provide the model
# to be partitioned across different ranks. For the rest of the script,
# the returned DistributedModel object should be used in place of
# the model provided for DistributedModel class instantiation.
model = smp.DistributedModel(model)
optimizer = smp.DistributedOptimizer(optimizer)

train(model, device, train_loader, optimizer)
```

## 고려 사항
<a name="model-parallel-pt-considerations"></a>

SageMaker의 모델 병렬화 라이브러리를 사용하여 PyTorch 훈련 스크립트를 구성할 때는 다음에 주의해야 합니다.
+ 글로벌 그래디언트 규범(예: LAMB 옵티마이저의 일부 변형 또는 글로벌 그래디언트 클리핑 등 전체 모델의 그라데이션 규범)에 의존하는 최적화 기법을 사용하는 경우 정확성을 위해 모델 파티션의 모든 표준을 수집해야 합니다. 라이브러리의 통신 기본 데이터 유형을 사용하여 이 작업을 수행할 수 있습니다.
+ 모델에 있는 `nn.Modules`의 전달 메서드에 대한 모든 `torch.Tensor` 인수는 모듈 출력 계산에 사용해야 합니다. 즉, 라이브러리는 모듈 출력이 종속되지 않는 모듈에 대한 `torch.Tensor` 인수가 있는 경우를 지원하지 않습니다.
+ `smp.DistributedModel.backward()` 호출에 대한 인수는 모든 모델 출력에 종속되어야 합니다. 즉, `smp.DistributedModel.forward` 호출에 공급되는 텐서의 계산에 사용되지 않는 `smp.DistributedModel.backward` 호출의 출력은 있을 수 없습니다.
+ 코드에 `torch.cuda.synchronize()` 호출이 있는 경우 동기화 호출 직전에 `torch.cuda.set_device(smp.local_rank())`를 호출해야 할 수도 있습니다. 그렇지 않으면 디바이스 0에 불필요한 CUDA 컨텍스트가 생성되어 메모리를 불필요하게 소비하게 될 수 있습니다.
+ 라이브러리는 서로 다른 디바이스에 `nn.Modules`을 배치하므로 모델 내 모듈이 `smp.step` 내부에서 수정된 글로벌 상태에 종속되지 않아야 합니다. 훈련 기간 동안 고정된 상태로 유지되거나 모든 프로세스에서 볼 수 있는 방식으로 `smp.step` 외부에서 수정된 모든 상태는 허용됩니다.
+ 라이브러리를 사용할 때는 모델을 GPU로 옮길 필요가 없습니다 (예: `model.to(device)` 사용). 모델이 파티셔닝되기 전(첫 번째 `smp.step` 호출 전)에 모델을 GPU로 이동하려고 하면 이동 호출이 무시됩니다. 라이브러리는 순위에 할당된 모델 부분을 자동으로 해당 GPU로 이동합니다. 라이브러리를 이용하는 훈련이 시작되면 모델을 CPU로 옮겨 사용하지 마세요. 프로세스에서 보유한 파티션에 할당되지 않은 모듈의 경우 올바른 파라미터를 가질 수 없기 때문입니다. 모델을 재훈련시키거나 모델 병렬화 라이브러리를 사용하여 훈련시킨 후 라이브러리 없이 추론에 사용하려는 경우, 체크포인트 API를 사용하여 전체 모델을 저장하고 일반 PyTorch 모듈에 다시 로드하는 것이 좋습니다.
+ 한 피드를 다른 피드로 출력하는 것과 같은 모듈 목록이 있는 경우 해당 목록을 `nn.Sequential`로 바꾸면 성능이 크게 향상될 수 있습니다.
+ 가중치 업데이트(`optimizer.step()`)는 전체 역방향 패스가 완료되고 그래디언트가 준비되는 시점이기 때문에 `smp.step`의 외부에서 실행해야 합니다. 모델 및 데이터 병렬화가 포함된 하이브리드 모델을 사용하는 경우 이 시점에서 그래디언트 AllReduce 작업도 완료됩니다.
+ 라이브러리를 데이터 병렬화와 함께 사용하는 경우 AllReduce가 단계에 참여하지 않는 순위를 기다리지 않도록 모든 데이터 병렬 랭크의 배치 수가 동일한지 확인하세요.
+ ml.p4d 인스턴스 유형(예: ml.p4d.24xlarge)을 사용하여 훈련 작업을 시작하는 경우 데이터 로더 변수 `num_workers=0`로 설정해야 합니다. 예를 들어, `DataLoader`를 다음과 같이 정의할 수 있습니다.

  ```
  dataloader = torch.utils.data.DataLoader(
              data,
              batch_size=batch_size,
              num_workers=0,
              pin_memory=True,
              drop_last=True,
              shuffle=shuffle,
          )
  ```
+ `smp.step`에 대한 입력은 `DataLoader`에서 생성된 모델 입력이어야 합니다. 이는 `smp.step`가 배치 차원을 따라 입력 텐서를 내부적으로 분할하고 파이프라인을 생성하기 때문입니다. 즉, `DataLoader`를 `smp.step` 함수에 전달하여 내부에서 모델 입력을 생성하는 것은 작동하지 않습니다.

  예를 들어, `DataLoader`를 다음과 같이 정의하면

  ```
  train_loader = torch.utils.data.DataLoader(dataset, batch_size=64, drop_last=True)
  ```

  `train_loader`에서 생성된 모델 입력에 액세스하여 `smp.step`로 데코레이팅된 함수에 전달해야 합니다. `smp.step`함수에 `train_loader`를 직접 전달하지 마세요.

  ```
  def train(model, device, train_loader, optimizer):
      model.train()
      for batch_idx, (data, target) in enumerate(train_loader):
          ...
          _, loss_mb = train_step(model, data, target)
          ...
  
  @smp.step
  def train_step(model, data, target):
      ...
      return output, loss
  ```
+ `smp.step`에의 입력 텐서는 `.to()` API를 사용하여 현재 디바이스로 이동해야 하며, 이는 `torch.cuda.set_device(local_rank())` 호출 후에 이루어져야 합니다.

  예를 들어 다음과 같이 `train` 함수를 정의할 수 있습니다. 이 함수는 입력 텐서를 사용하여 `train_step`을 호출하기 전에 `.to()` API를 사용하여 현재 디바이스에 `data`와 `target`을 추가합니다.

  ```
  def train(model, device, train_loader, optimizer):
      model.train()
      for batch_idx, (data, target) in enumerate(train_loader):
          # smdistributed: Move input tensors to the GPU ID used by the current process,
          # based on the set_device call.
          data, target = data.to(device), target.to(device)
          optimizer.zero_grad()
          # Return value, loss_mb is a StepOutput object
          _, loss_mb = train_step(model, data, target)
  
          # smdistributed: Average the loss across microbatches.
          loss = loss_mb.reduce_mean()
  
          optimizer.step()
  ```

  위 `train` 함수에서 이 `smp.set`로 데코레이팅된 함수의 입력 텐서는 현재 디바이스로 이동되었습니다. 모델을 현재 디바이스로 옮길 필요는 *없습니다*. 라이브러리는 순위에 할당된 모델 부분을 자동으로 해당 GPU로 이동합니다.

  ```
  @smp.step
  def train_step(model, data, target):
      output = model(data)
      loss = F.nll_loss(output, target, reduction="mean")
      model.backward(loss)
      return output, loss
  ```

## 지원되지 않는 프레임워크 기능
<a name="model-parallel-pt-unsupported-features"></a>

SageMaker의 모델 병렬화 라이브러리에서는 다음과 같은 PyTorch 기능을 지원하지 않습니다.
+ 네이티브 [PyTorch DDP](https://pytorch.org/tutorials/intermediate/ddp_tutorial.html)에서 데이터 병렬화를 사용하는 경우 라이브러리는 [https://pytorch.org/docs/stable/generated/torch.nn.parallel.DistributedDataParallel.html](https://pytorch.org/docs/stable/generated/torch.nn.parallel.DistributedDataParallel.html) 래퍼 모듈을 지원하지 않습니다. 라이브러리는 파라미터 브로드캐스트 및 그래디언트 AllReduce를 포함하여 PyTorch DDP와의 통합을 내부적으로 관리합니다. 라이브러리를 사용할 때 모듈 버퍼는 훈련 시작 시 한 번만 브로드캐스트됩니다. 모델에 각 단계에서 데이터 병렬 그룹 간에 동기화해야 하는 모듈 버퍼가 있는 경우 `smp.get_dp_process_group()`를 통해 얻을 수 있는 프로세스 그룹을 이용하는 `torch.distributed` API를 통해 그렇게 할 수 있습니다.
+ 혼합 정밀도 훈련의 경우 `apex.amp` 모듈은 지원되지 않습니다. 라이브러리를 자동 혼합 정밀도와 함께 사용하는 권장 방법은 `torch.cuda.amp`를 이용하는 것입니다. 단, torch에서의 구현 대신 `smp.amp.GradScaler`를 사용하는 경우는 예외입니다.
+ `torch.jit.ScriptModules`와 `ScriptFunctions`는 `smp.DistributedModel`에서 지원되지 않습니다.
+ `apex` : `apex`의 `FusedLayerNorm`, `FusedAdam`, `FusedLAMB`, `FusedNovoGrad`는 지원되지 않습니다. 대신 `smp.optimizers` 및 `smp.nn` API를 통해 라이브러리로 이들을 구현할 수 있습니다.