원문 : https://community.arm.com/developer/tools-software/graphics/b/blog/posts/the-mali-gpu-an-abstract-machine-part-4---the-bifrost-shader-core

주의 : 번역이 개판이므로 이상하면 원문을 참고하세요.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.


최근에 Mali Bifrost 아키텍쳐 패밀리인 Mali-G71 에 대해 발표했습니다. 그것의 전체적인 렌더링 모델은 이전의 Mali GPU 들과 유사한데요, - Bifrost 패밀리는 여전히 매우 파이프라인화되어 있는 타일 기반 렌더러입니다 - 원래의 "Abstract Machine" 블로그 시리즈를 따라가기 위해서 요구되는 프로그래머블( programmable ) 셰이더 코어에서 충분한 변화가 있었습니다.

이 블로그에서 필자는 전형적인 Bifrost 셰이더 코어의 블록 레벨 아키텍쳐를 소개하고, 애플리케이션 개발자가 그 하드웨어를 사용했을 때 기대할 수 있는 성능 향상에 대해서 설명할 것입니다. 그런 성능향상은 칸텐트를 최적화하고 DS-5 Streamline 과 같은 툴들을 통해서 노출되는 하드웨어 성능 카운터들을 이해할 때 올 수 있습니다. 이 블로그에서는 여러분이 이 시리즈의 처음 두 파트를 읽었다고 가정합니다. 만약 읽지 않았다면 읽고 오세요.

GPU Architecture

Bifrost GPU 의 탑레벨 아키텍쳐는 이전의 Midgard GPU 에서와 동일합니다.

The Shader Cores

Midgard 와 마찬가지로 Bifrost 도 통합 셰이더 코어 아키텍쳐입니다. 이는 단일 클래스의 셰이더 코어가 설계에 존재하는 모든 종류의 셰이더 프로그램과 컴퓨트 커널들을 실행하는 기능을 가졌다는 것을 의미합니다.

셰이더 코어의 개수는 특정 실리콘 칩마다 다릅니다; 우리 파트너들은 성능 요구와 실리콘 면적 제한에 기반해 셰이더 코어의 개수를 선택합니다. Mali-G71 GPU 는 저사양 디바이스를 위해 하나의 코어부터 고성능 설계를 위해 32 개의 코어까지 다양합니다.

Work Dispatch

GPU 를 위한 그래픽스 작업은 큐의 쌍으로 제출됩니다. 하나는 버텍스/타일링/컴퓨트 워크로드를 위한 것이며, 다른 하나는 프래그먼트 워크로드를 위한 것입니다. 모든 작업은 각 큐에 단일 서브미션( submission )으로서 제출된( submit ) 하나의 렌더타깃을 위한 것입니다.

각 큐의 워크로드는 더 작은 조각으로 쪼개져 GPU 안의 모든 셰이더 코어들에 분배됩니다. 타일링의 경우에는 고정 함수 타일링 유닛에 분배됩니다. 두 큐로부터의 워크로드들은 동시에 셰이더 코어에 의해 처리됩니다; 예를 들어, 서로 다른 렌더 타깃을 위한 버텍스 처리와 프래그먼트 처리가 병렬적으로 실행될 수 있습니다( 파이프라인 방법론에 대한 세부사항을 원한다면 첫 번째 블로그를 확인하세요 ).

Level 2 Cache and Memory Bandwidth

시스템 내의 처리 유닛들은 성능을 증진시키고 반복되는 데이터 페치( fetch )에 의해 발생하는 메모리 대역폭을 줄이기 위해 L2 캐시를 공유합니다. L2 캐시의 크기는 우리 실리콘 파트너들에 의해 그들의 요구에 맞게 구성됩니다. 하지만 일반적으로는 GPU 의 셰이더 코어당 64 KB 입니다.

메인 메모리에 대한 GPU 외부의 버스 포트의 개수는, 그리고 그에 의한 이용가능한 메모리 대역폭은 구현된 셰이더 코어의 개수에 의존합니다. 일반적으로, 우리는 클락 당 코어 당 32 비트 픽셀을 쓸 수 있도록 하는데, 클락 사이클 당 ( 읽기와 쓰기를 위해 ) 256 비트 메모리 대역폭을 가질 수 있도록 8 코어 설계를 하는 것이 합리적입니다. AXI 포트의 개수는 Midgard 보다 증가했으며, 다운스트림( downstream ) 메모리 시스템이 그것을 지원할 수 있다면, 클락 당 더 높은 최대 대역폭에 접근할 수 있도록 하기 위해 12 개가 넘는 코어들과 함께 더 크게 구성되었습니다.

이용가능한 메모리 대역폭은 GPU( 주파수, AXI 포트 폭 ) 와 다운스트림 메모리 시스템( 주파수, AXI 데이터 폭, AXI 지연 )에 모두 의존한다는 점에 주의하십시오. 많은 설계들에서 AXI 클락은 GPU 클락보다 더 낮을 겁니다. 그러므로 이론적인 GPU 대역폭을 실제로 이용할 수는 없습니다.

The Bifrost Shader Core

Mali 셰이더 코어는 프로그래머블 코어를 둘러싼 여러 개의 고정 함수 하드웨어 블록으로써 구조화되어 있습니다. 프로그래머블 코어는 Bifrost GPU 패밀리에서 가장 크게 변화된 영역입니다. 시리즈의 이전 블로그에서 언급한 Midgard "Tripipe" 설계에서 많은 변경이 있었습니다:

Bifrost 프로그래머블 실행( Execution ) 코어는 하나 이상의 실행 엔진들 -- Mali-G71 의 경우에는 세 개 -- 과 여러 개의 공유 데이터 처리 유닛들로 구성되는데, 이것들은 메시징 구조( messaging fabric )으로 연결되어 있습니다.

The Execution Engines

실행 엔진은 실제로 프로그래머블 셰이더 명령들을 실행할 책임이 있습니다. 각 명령은 단일 복합( composite ) 산술( arithmetic ) 처리 파이프라인과 실행 엔진이 처리 중인 스레드를 위해 요구되는 스레드 상태를 모두 포함합니다.

The Execution Engines: Arithemtic Processing

Bifrost 산술 유닛은 기능 유닛 활용도( functional unit utilization )를 향상시키기 위해 쿼드 벡터화 체계( quad-vectorization scheme )를 구현합니다. 스레드들은 4 개의 번들( bundle )로 그룹화되어 있으며, 이를 쿼드라 부르는데, 각 쿼드들은 128 비트 데이터 처리 유닛의 너비를 채웁니다. 단일 스레드의 관점으로에서 볼 때, 이 아키텍쳐는 스칼라 32 비트 연산의 스트림처럼 보입니다. 이는 셰이더 컴파일러를 위한 상대적으로 직관적인 forward task 의 높은 활용도를 성취할 수 있도록 만들어 줍니다. 아래의 예제는 vec3 산술 연산이 순수한 SIMD 유닛에 매핑되는 방식을 보여 줍니다( 파이프라인은 클락 당 한 스레드를 실행합니다 ):

쿼드 기반 유닛과 비교하면 다음과 같습니다( 파이프라인이 클랑 당 네 개의 스레드를 위해 스레드 당 한 레인( lane )을 실행합니다 ):

프로그램에서의 벡터 길이와는 무관하게 유용한 작업으로 하드웨어 유닛을들을 꽉 차게 유지하도록 만드는 기능의 이점들이 이들 다이어그램에서 명확하게 강조되어 있습니다. 데이터가 32 비트 타입보다 더 작아짐으로 인해서 제공되는 성능 향상과 전력 효율성은 모바일 디바이스에서 여전히 매우 중요합니다. 그러므로 Bifrost 는 int8, int16, fp16 데이터 타입들을 위한 네이티브 서포트( native support, 역주 : 애플리케이션 실행 환경에 이미 존재하는 기능 )를 유지합니다. 이 타입들은 데이터 유닛의 128 비트 데이터 너비를 채우기 위해서 패킹( packed )될 수 있습니다. 그러므로 단일 128 비트 수학 유닛은 8x fp16/int16 연산들을 클락 사이클 당 한 번 처리할 수 있으며 16x int8 연산들을 파이클 당 한 번 처리할 수 있습니다.

The Execution Engines: Thread State

복잡한 프로그램의 성능과 성능 확장성( scalability )을 향상시키기 위해서, Bifrost 는 셰이더 프로그램들이 사용하기 위한 다소 큰 범용 목적( general-purpose ) 레지스터( register ) 파일을 구현합니다. Mali-G71 은 64x32 비트 레지스터들을 제공하는데, GPU 가 스레드를 최대로 점유하는 것을 허용하며, [ ARM Mali Compute Architecture Fundamentals ] 에서 설명한 레지스터 파일 용례( usage ) 와 스레드 카운트 사이에 존재하던 이전의 트레이드 오프( trade off )를 제거했습니다.

OpenGL ES 유니폼( uniform ) 상수와 Vulkan 의 푸시( push ) 상수를 저장하기 위해서 사용되는 빠른 상수 저장소( constant storage )의 크기가 증가되었습니다. 이는 프로그램이 상수 저장소를 대량으로 사용할 때 발생하는 캐시 접근 부하를 줄여 줍니다.

Data Processing Unit: Load/Store Unit

로드/스토어 유닛은 ( 텍스쳐를 제외한 ) 모든 범용 목적 메모리 접근을 다룹니다. 버텍스 애트리뷰트( attribute ) 페치, 베어링( varying, 역주 : 버텍스 버퍼 출력이자 프래그먼트 버퍼 입력인 변수 ) 페치, 버퍼 접근, 스레드 스택 접근 등을 포함합니다. 이는 코어 당 16 KB L1 데이터 캐시를 포함하는데, L1 캐시는 공유 L2 캐시에 의해 뒷받침됩니다.

로드/스토어 캐시는 클락 사이클 당 단일 64 바이트 캐시 라인에 접근할 수 있으며, 스레드 쿼드 사이에서의 접근은 요구되는 고유( unique ) 캐시 접근 요청들을 줄이기 위해 최적화됩니다. 예를 들어, 네 개의 스레드가 같은 캐시라인의 데이터에 접근하고 있다고 할 때, 그 데이터는 단일 사이클에 반환될 수 있습니다.

이 로드/스토어 병합 기능은 일반적인 OpenCL 컴퓨트 커널들에서 찾아 볼 수 있는 데이터 접근 패턴들을 매우 가속화해 줄 수 있습니다. 이는 일반적으로 제한된 메모리 접근이며, 알고리즘 설계에서 그것의 활용을 최대화하는 것이 핵심적인 최적화 목표입니다. 또한 Mali 산술 유닛들이 스칼라임에도 불구하고, 데이터 접근 패턴은 잘 작성된 벡터 로드를 통해 여전히 이점을 얻을 수 있으며, 그래서 여전히 가능할 때마다 벡터화된 셰이더와 커널 코드를 작성하는 것이 좋습니다.

Data Processing Unit: Varying Unit

베어링 유닛은 전용 고정 함수 베어링 보간기( dedicated fixed-function varying interpolator )입니다. 그것은 프로그래머블 산술 유닛과 같은 최적화 전략을 구현합니다; 그것은 스레드 쿼드 사이의 보간값을 벡터화해서 기능 유닛 활용도를 보장하며, 더 빠른 fp16 최적화를 지원합니다.

이 유닛은 클락 당 쿼드 당 128 비트를 보간할 수 있습니다; 즉, mediump ( fp16 ) vec4 를 보간하는 데는 네 스레드 쿼드 당 2 사이클이 걸립니다. 베어링 변수 벡터 길이를 최소화하기 위한 최적화와 fp32 보다는 fp16 을 공격적으로 사용하면 결과적으로 애플리케이션 성능을 향상시킬 수 있습니다.

Data Processing Unit: ZS/Blend

ZS 와 블렌드 유닛은 모든 타일 메모리 접근을 다룰 책임이 있으며, 둘 다 뎁스/스텐실 테스트나 컬러 블렌딩과 같은 내장 OpenGL ES 연산들을 위한 것입니다. 또한 그 타일 버퍼에 프로그램이 접근하는데 필요한 기능들은 다음과 같습니다:

LS 파이프가 타일 버퍼 접근, 베어링 보간, 로드/스토어 캐시 접근을 제어하는 모놀리식( monolithic ) 파이프라인이었던 Midgard 의 설계와는 다르게, Bifrost 는 세 개의 더 작고 더 효율적인 병렬 데이터 유닛을 구현했습니다. 이는 그 타일 버퍼 접근이 베어링 보간을 위해 비동기적으로 실행될 수 있다는 것을 의미합니다. 예를 들면, 그래픽스 알고리즘이 프로그램에서 타일 버퍼 접근을 사용하고 있다면 처리중인 리소스들에 대한 경쟁이 줄어드는 것을 확인할 수 있을 것입니다. Midgard 에서는 LS 파이프라인이 매우 무거운 경향이 있었습니다.

Data Processing Unit: Texture Unit

텍스쳐 유닛은 모든 텍스쳐 접근을 구현합니다. 그것은 코어 당 16 KB L1 데이터 캐시를 포함하며, 공유 L2 캐시에 의해 뒷받침됩니다. Mali-G71 에서는 이 블록의 아키텍쳐 성능이 이전 Midgard GPU 에서와 동일합니다; 클락 당 하나의 바이리니어( bilinear ) 필터링된 텍셀( GL_LINEAR_MIPMAP_NEAREST )을 반환합니다. 예를 들어 바이리니어 텍스쳐 룩업( lookup )을 네 개의 스레드 쿼드의 각 스레드에서 보간하는 데는 4 사이클이 걸립니다.

어떤 텍스쳐 접근 모드들은 데이터를 생성하기 위해서 다중 사이클을 요구합니다:

  • 트라이리니어( trilinear ) 필터링( GL_LINEAR_MIPMAP_LINEAR )은 텍셀당 두 개의 바이리니어 샘플들을 요구하며, 텍셀 당 2 사이클이 필요합니다.
  • 볼륨( volumetric ) 3D 텍스쳐는 2D 텍스쳐보다 두 배의 사이클을 요구합니다; 즉, 트라이리니어 필터링된 3D 텍스쳐는 4 사이클을 요구합니다. 바이리니어 필터링된 3D 텍스쳐는 2 사이클을 요구합니다.
  • 와이드( wide ) 유형의 텍스쳐 포맷( 컬러 채널 당 16 비트 이상 )은 픽셀 당 여러 개의 사이클을 요구할 수 있습니다.

Bifrost 에서 새롭게 최적화된 기능인데, 와이드 포맷 규칙의 예외는 뎁스 텍스쳐 샘플링입니다. 일반적으로 셰도우 매핑 기술이나 디퍼드 라이팅 알고리즘에 의해서 사용되는 DEPTH_COMPONENT16 이나 DEPTH_COMPONENT24 텍스쳐들로부터 샘플링하는 것은 최적화되었으며, 이제 단일 사이클 룩업입니다. 이는 Midgard 패밀리 GPU 에서보다 상대적으로 두 배의 성능을 달성합니다.

The Bifrost Geometry Flow

셰이더 코어 변경에 더해, Bifrost 는 새로운 인덱스 주도 버텍스 셰이딩( Index-Driven Vertex Shading, IDVS ) 지오메트리 처리 파이프라인을 소개합니다. 이전의 Mali GPU 들은 타일링 전에 버텍스 셰이딩을 모두 처리했고, 보통 이는 삼각형을 컬링( cull, 프러스텀 바깥인 경우나 면 방향( facing ) 테스트 실패했을 때 자름 )하기 위해서만 사용되는 베어링과 비교했을 때 계산과 대역폭을 낭비하는 결과를 낳았습니다.

IDVS 파이프라인은 버텍스 데이터를 절반으로 나눕니다; 하나는 위치를 처리하는데 사용되고, 다른 하나는 남은 베어링을 처리하는 데 사용됩니다:

이 플로우는 두 개의 중요한 최적화를 제공합니다:

  • 인덱스 버퍼가 먼저 읽히고, 버텍스 셰이딩은 인덱스 버퍼에 의해서 참조되고 있는 버텍스가 하나 이상 존재하는 버텍스들의 배치( batch )만이 제출( submit )됩니다. 이는 인덱스 버퍼에 있는 공간적 공백( gap )을 버텍스 셰이딩이 스킵할 수 있도록 해 줍니다.
  • 베어링 셰이딩은 clip-and-cull 단계에서 살아 남은 프리미티브들에 대해서만 제출됩니다; 이는 컬링된 삼각형에 대해서만 기여하고 있는 버텍스에 대한 중복 계산과 대역폭을 상당히 감소시켜 줍니다.

Bifrost 지오메트리 플로우를 최대한 활용하려면 패킹된 버텍스 버퍼들을 부분적으로 디인터리빙( deinterleave )하는 것이 좋습니다; 포지션( position )에 기여하는 애트리뷰트들을 하나의 패킹된 버퍼에 배치하고, 포지션 베어링에 기여하지 않는 애트리뷰트들은 다른 패킹된 버퍼에 배치합니다. 이는 화면에 기여하지 않거나 컬링된 버텍스들에 대해서는 비(non)-포지션 베어링들이 캐시에 올라가지 않는다는 것을 의미합니다. 필자의 동료인 stacysmith 는 이러한 유형의 지오메트리 처리 파이프라인을 이용하기 위해 버퍼 패킹을 최적화하는 것에 대한 블로그를 작성했습니다.

Performance Counters

이전의 Midgard GPU 와 마찬가지로, Bifrost 하드웨어도 수많은 성능 카운터들을 제공해 애플리케이션 개발자들이 애플리케이션을 프로우파일링하고 최적화할 수 있도록 하고 있습니다. Bifrost 아키텍쳐에 대해 애플리케이션 개발자들이 이용할 수 있는 성능 카운터에 대한 더 많은 내용을 확인하세요.

댓글과 의견을 항상 환영합니다.

Cheers,

Pete.

+ Recent posts