Perfetto를 활용해 RBLN 프로파일러로 추출한 데이터를 분석하는 방법¶
이 페이지에서는 Perfetto UI 상에 나타나는 정보를 기반으로 프로파일링 결과를 분석하는 기초적인 방법을 설명합니다.
프로파일 정보에 대한 이해¶
Task 세부 항목¶
-
이름 구조 (Name)
RBLN 프로파일러는
{sequence_index}_{layer_name}_{command_index}
와 같이 작업명을 구성합니다. 이러한 명명 규칙을 통해 인공 신경망 계층의 특정 지점을 인식하고 해당 지점에서의 성능을 평가할 수 있습니다.sequence_index
는 모듈이 실행되는 순서를 나타내며, 한개 이상의 런타임 안에서 해당 모듈의 순서를 가리킵니다.layer_name
은 특정한 텐서 수준 연산을 나타냅니다. 여러 개의 연산을 하나의 연산으로 합쳐서 처리할 경우, “fused()”이라는 이름으로 하나의 그룹을 이루며 각 연산의 이름을 “+”로 연결합니다.command_index
는 임의의 연산이 컴파일러에 의해 여러 개의 명령어로 나뉠 경우, 이 과정에서 생성된 명령어들을 구분하기 위해 사용합니다.
-
Task 목록 (Category)
Host
Neural Engine Clusters
Neural DMA
Task DMA
External HDMA
Device HDMA
Device Sync
-
의존성 연결 (
Preceding Flows
&Following Flows
)Perfetto UI 상 아래쪽에 위치한
Preceding Flows
와Following Flows
는 명령어 간 의존성을 나타냅니다. 모델 컴파일 과정에서 이러한 의존성은 Command Processor에 의해 장치의 구성과 할당 상태를 고려하여 적절히 처리되도록 결정됩니다.Neural DMA
와 관련된 의존성은 Neural Engine 내부의 Task Manager에서 관리하기 때문에, 이에 해당하는 의존성은 Perfetto 상에 표시되지 않습니다.-
External HDMA
와Task DMA
사이의 의존성External HDMA
는 호스트 DRAM에서 ATOM™ 내부 DRAM으로 가중치와 입력 데이터를 전송하는데, 이 과정에서 ATOM™ 내부 DRAM으로부터 칩 내부 Shared Memory로 데이터를 전송하는Task DMA
와 의존성을 형성합니다. 이와 유사하게,Task DMA
를 통해 ATOM™ 내부 DRAM으로 전송되는 최종 출력 데이터는External HDMA
를 통해 호스트 DRAM으로 전송해야 합니다. 따라서External HDMA
를 반드시Task DMA
이후에 수행해야 한다는 의존성을 지키는 것이 매우 중요합니다.이러한 시각화된 흐름은 명령어 간의 명시적인 의존성을 나타냅니다. 하지만, 이것이 꼭 이러한 연결이 없는 명령어에는 의존성이 없다는 것을 의미하지는 않습니다. 예를 들어, 각 Neural Engine 내부의 Task Manager는 단일 하드웨어 수준에서의 의존성을 관리합니다. 또한, 첫 번째
Neural Engine Clusters
명령어를 이와 관련된External HDMA
가 끝난 후에 동작하도록 하는 장벽 (barrier) 또한 존재합니다. -
Neural Engine Clusters
와Task DMA
사이의 의존성Neural Engine Clusters
와Task DMA
사이의 연결은 주로 가중치 또는 신경망의 계층 간 입/출력을 전송하는 것과 관련이 있는 의존성을 보여주며, 이 연결에 따라 작업을 수행하도록 고안되었습니다. 위 예시에 표시된 의존성은0_fused(bn1+conv1+relu)_1
연산을 처리하기 위해 이 때 필요한 가중치를Neural Engine Clusters
에 불러와야 함을 나타내고, 이러한 의존성은 컴파일러에 의해 결정됩니다. 또한, 만약 컴파일러에서Neural Engine Clusters
와Task DMA
사이를 연결해야 한다고 판단한다면 이것을 의존성이 필요한 상황이라고 간주합니다. 예를 들어, 인공 신경망 계층들의 연산 이후에 생성된 데이터가 그 크기 때문에 임시적으로 ATOM™ 내 DRAM에 저장되는 상황이 이에 해당합니다. -
Device HDMA
와Device Sync
사이의 의존성여러 대의 ATOM™을 사용하는 대규모 모델의 경우 샤딩(sharding)을 통해 신경망 계층들을 조각내어 여러 대의 ATOM™에서 실행할 수 있도록 합니다.
첫 번째 상황은 루트(root) ATOM™ (
ATOM 0
)이Device HDMA
를 통해 데이터를 받는 과정에서 리프(leaf) ATOM™들 (ATOM 1, 2, 3
)과 동기화합니다. 그림 (1)에서는 위 과정에서 데이터가Device HDMA
를 통해 루트 ATOM™ (ATOM 0
)로 모이는 과정을 보여줍니다. 이후 각 리프 ATOM™ (ATOM 1, 2, 3
)의 동기화가 해제됩니다.두 번째 상황은 이와 반대로 루트 ATOM™ (
ATOM 0
)이Device HDMA
를 통해 데이터를 보내는 상황에서 리프 ATOM™들(ATOM 1, 2, 3
)과 동기화합니다. 그림 (2)에서는 데이터가Device HDMA
를 통해 각각의 리프 ATOM™들로 각각 전송되는 과정을 보여줍니다. 그 후, 각 리프 ATOM™은 데이터를 제대로 받았음을 확인하기 위해 동기화 신호를 보냅니다.이러한 텐서 병렬화 모델은 컴파일 과정에서
Device HDMA
에 대한 의존성을 확인하기 위해 동기화 명령어를 생성합니다.
-
상세 명명 규칙¶
인공 신경망을 실행하는 것은 일련의 텐서 연산으로 표현할 수 있고, RBLN 컴파일러를 통해 각 텐서 연산은 몇 개의 명령어로 나타낼 수 있습니다. 따라서, 시각화된 프로파일링 결과 상에서 일련의 명령어들은 인공 신경망의 모듈 한 개와 연결하여 생각할 수 있습니다. 이 절에서는 타임라인에서 각 명령어들의 명명 규칙에 대해 간략하게 설명합니다.
상황 1. 모델의 계층 이름과의 연결 (파이토치)¶
모델의 구조는 해당 모델의 구조를 출력하거나 그 구현부를 확인하는 것으로 알 수 있습니다. 이 정보를 Perfetto 상에서 시각화된 명령어들과 비교하면 특정 연산이 어떻게 일련의 명령어들로 변환되었는지를 확인할 수 있습니다. 위 예시를 보면, LlamaForCausalLM
의 model.layers[0].self_attn.q_proj
는 Perfetto 상에서 0_causal_lm.model.0.self_attn.q_proj
과 같이 표현됩니다.
모델의 구조 및 계층들의 순서는 실제 명령어가 처리되는 순서와 차이가 있을 수 있습니다. 몇몇 명령어들은 컴파일 과정에서 생략될 수 있고, 이러한 경우 단일 계층의 command index
가 끊어진 것처럼 보입니다. 또한, 여러 연산들을 ATOM™에서 효율적으로 실행하기 위해 컴파일 과정에서 임의의 명령어들이 추가될 수도 있습니다. 위 예시에서는 내부의 rotary positional embedding (RoPE) 계층을 처리하기 위해 0_causal_lm.model_0
가 새로 추가되었음을 확인할 수 있습니다.
상황 2. 명령어 생성¶
RBLN 컴파일러는 성능과 효율성 개선을 위해 연속된 연산들을 하나의 함수로 합치는 연산결합(operation fusion)을 지원합니다. 합쳐진 함수의 layer_name
은 fused(name0+name1...)
과 같은 형태를 가집니다. 위 예시에서는 합성곱(convolution) 계층과 배치 정규화(batch normalization) 계층, 활성화 함수 계층이 fused(layer1.2+layer1.2.bn2+layer1.2.conv2)
라는 이름의 함수로 합쳐진 것을 보여줍니다.
단일 모듈을 처리하기 위한 명령어들은 layer_name
을 공유하고, 이들 각각은 command_index
를 통해 구분할 수 있습니다. fused(layer1.2+layer1.2.bn2+layer1.2.conv2)
를 실행하기 위해 Task DMA
를 통해 메모리에 접근하거나 Neural Engine Clusters
에서 연산을 처리하는 등의 명령어들은 0_fused(layer1.2+layer1.2.bn2+layer1.2.conv2)_{command_id}
과 같은 형태를 갖습니다.
상황 3. 계층 내 재활용 (파이토치)¶
torch.add
, torch.nn.functional.relu
와 같은 inline 연산들은 모델이 해당 연산들의 이름을 속성(attribute)값으로 갖고 있지 않습니다. 그렇기 때문에 해당 연산들은 컴파일 과정에서 이름이 없어지게 됩니다. 따라서, Perfetto 상에서 어떤 명령어들이 각 연산에 대응하는지 구분하기가 어려울 수 있습니다. 이러한 상황을 방지하기 위해 inline 연산 대신 torch.nn.module
을 상속받는 클래스를 명시적으로 사용할 것을 권장합니다.
또한, 단일 클래스 속성(attribute)을 여러 번 반복해서 사용하는 경우에도 Perfetto 상의 어떤 명령어가 실제로 어떤 연산에 대응하는지 알아보기 어려울 수 있습니다. 파이토치에서 제공하는 ResNet50에서, Bottleneck
모듈은 nn.ReLU
클래스를 사용하기 위해서 self.relu
를 명시적으로 한 번만 정의하였습니다. 이 경우, 위의 프로파일링 결과에서 볼 수 있듯 relu
라는 이름은 첫 번째를 제외한 두 번째와 세 번째 Neural Engines Clusters
명령어에서 빠져 있습니다. 이는 하나의 self.relu
속성을 forward
함수에서 반복해서 사용했기 때문입니다.
위 파이토치 코드에서 Bottleneck
모듈의 forward
함수를 위와 같이 서로 다른 속성(self.relu1
, self.relu2
, self.relu3
)을 사용하도록 변경하면 첫 번째 Neural Engine Clusters
명령어 이름 (1) 외에 그 이후 명령어 (2), (3)에도 relu
가 생략되지 않고 명시적으로 포함됩니다.