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

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

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


( 역주 : 2016 년 4 분기에 나온 Mali-G51 부터는 Bifrost 셰이더 코어입니다. Mali-T 로 시작하는 것이 Midgard 이고, Mali-$(Number)-MP 가 Utgard 입니다. 이 시리즈의 Part 4 에서는 Bifrost 에 대해 다룹니다 )

시리즈의 처음 두 블로그들에서, 필자는 Mali GPU 들에 의해 사용되는 프레임 수준 파이프라이닝과 타일 기반 렌더링 아키텍쳐에 대해서 소개했습니다. 이는 애플리케이션의 성능을 최적화할 때 그래픽스 스택의 동작을 설명하기 위해서 사용될 수 있는 멘탈 모델을 개발자들에게 제공하려는 목적으로 작성되었습니다.

이 블로그에서, 필자는 이 추상 머신의 생성을 완료하고, 최종 요소를 형성할 것입니다: 틀에박힌( stereotypical ) Mali "Midgard" GPU programmable core. 이 블로그는 여러분이 처음 두 파트들을 읽고 왔다고 가정합니다. 그러므로 읽지 않았다면 먼저 읽고 오시기 바랍니다.

GPU Architecture

Mali GPU 의 "Midgard" 패밀리들( Mali-T600, Mali-T799, Mali-T800 )은 통합( unified ) 셰이더 코어 아키텍쳐를 사용합니다. 이는 설계상으로 단일 유형의 셰이더 코어만이 존재한다는 것을 의미합니다. 이 단일 코어는 모든 유형의 프로그래머블( programmable ) 셰이더 코어를 실행하는데, 버텍스 셰이더, 프래그먼트 셰이더, 컴퓨트 커널들을 포함합니다.

셰이더 코어의 정확한 개수는 특정 실리콘( silicon ) 칩( chip )마다 다릅니다; 실리콘 파트너들은 성능 요구사항과 실리콘 면적( area ) 제한에 기반해서 얼마나 많은 셰이더 코어를 구현할지를 선택할 수 있습니다. Mali-T760 GPU 는 저사양 장치들을 위해 단일 코어부터 고성능 설계를 위해 16 코어까지 달라질 수 있습니다. 하지만 거의 대부분의 구현에서는 4 코어나 8 코어 사이를 유지합니다.

GPU 를 위한 그래픽스 작업은 큐 쌍에 들어 가는데, 버텍스/타일링 워크로드( workload )를 위한 것이 하나이고 프레그먼트 워크로드를 위한 것이 다른 하나입니다. 그것들은 하나의 렌더 타깃을 위해서 동작하며, 각 큐에 단일 서브미션( submission )으로서 서밋( submit )됩니다. 두 큐로부터의 워크로드는 GPU 에 의해 동시에 처리될 수 있습니다. 그래서 서로 다른 렌더 타깃들을 위한 버텍스 처리와 프래그먼트 처리는 병렬적으로 실행될 수 있습니다( 파이프라인 방법론에 대한 세부사항을 원한다면 첫 번째 블로그를 확인하세요 ). 단일 렌더 타깃에 대한 워크로드는 작은 조각으로 나뉘어져서 GPU 에 있는 모든 셰이더 코어들 사이에서 분배됩니다. 타일링 워크로드의 경우에는 고정 함수( fixed-function ) 타일링 유닛에 분배됩니다( 타일링에 대한 개요를 원한다면 시리즈의 두 번째 블로그를 확인하세요 ).

시스템 내의 셰이더 코어들은 L2 캐시를 공유해서 성능을 증진시키며 반복적인 데이터 페치( fetch )에 의해 발생하는 메모리 대역폭을 감소시킵니다. 코어 개수와 마찬가지로, L2 캐시의 크기는 실리콘 파트너들에 의해서 구성됩니다. 하지만 일반적으로는 GPU 의 셰이더 코어 당 32 에서 64 KB 의 범위에 있습니다. 이 캐시가 외부 메모리에 대해 가지고 있는 메모리 포트( port ) 의 개수와 버스 폭( bus width )은 구성이 가능하며, 이 역시 실리콘 파트너가 성능, 전력, 면적 요구사항을 튜닝합니다. 보통 우리는 한 클락( clock ) 당 한 코어 당 32 비트 픽셀을 쓸 수 있도록 요구하며, 8 코어 설계로 클락 사이클( cycle ) 당 ( 읽기 및 쓰기 모두를 위해 ) 총 256 비트의 메모리 대역폭을 기대하는 것이 합리적일 겁니다.

The Midgard Shader Core

Mali 셰이더 코어는 여러 개의 고정 함수 하드웨어 블록으로서 구성되는데, 이는 프로그래머들 "트라이파이프( tripipe, 삼중 파이프 )" 실행( execution ) 코어를 감쌉니다. 고정 함수 유닛들은 셰이더 연산을 위한 설정을 수행하거나 - 삼각형 래스터화( rasterizing )이나 뎁스 테스트 - 셰이더 후처리( post-shader activity )를 제어합니다 - 블렌딩( blending )이나 전체 타일의 가치있는 데이터를 렌더링의 끝에서 메모리에 쓰기. 삼중 파이프 자체는 프로그래머블 파트이며, 셰이더 프로그램의 실행에 대한 책임이 있습니다.

The Tripipe

파이프라인 설계에는 세 가지 분류의 실행 파이프라인이 존재합니다: 하나는 산술( arithmetic ) 연산을 다루며, 하나는 메모리 로드/스토어( load/store ) 및 베어링 변수( varying, 역주 : 버텍스 셰이더 출력이자 프래그먼트 셰이더 입력으로 사용되는 변수 ) 접근을, 하나는 텍스쳐 접근을 다룹니다. 셰이더 코어 당 하나의 로드/스토어 와 하나의 텍스쳐 파이프가 존재합니다. 하지만 산술 파이프라인의 개수는 다양하며, GPU 마다 다릅니다; 오늘날의 대부분의 실리콘 제폼은 두 개의 산술 파이프라인을 가지고 있을 겁니다. 하지만 Mali-T880 은 세 개를 가지고 있습니다.

Massively Multi-threaded Machine

단일 코어에서 한 번에 하나의 스레드만을 실행하도록 하는 전통적인 CPU 아키텍쳐와는 다르게, 트라이파이프는 엄청난 다중 스레드 기반 처리 엔진입니다. 수 백개의 하드웨어 스레드가 트라이파이프 내부에서 동시에 실행될 수 있으며, 셰이딩되는 각 버텍스나 프래그먼트 당 하나의 스레드가 생성될 수 있습니다. 이 엄청난 개수의 스레드들은 메모리 지연( latency )를 감추기 위해 존재합니다; 어떤 스레드가 메모리 작업을 위해 대기하고 있다해도 중요하지 않습니다. 왜냐하면 적어도 한 스레드에서는 실행이 가능할 것이고, 효율적인 실행을 유지할 수 있습니다.

Arithmetic Pipeline: Vector Core

산술 파이프라인( A-pipe ) 는 SIMD( single instruction multiple data ) 벡터 프로세싱 유닛입니다. 이는 128 비트 쿼드-워드( quad-word ) 레지스터( register )들 상에서 수행되는 산술 유닛을 가지고 있습니다. 이 레지스터들은 2 x FP64, 4 x FP32, 8 x FP16, 2 x int64, 4 x int32, 8 x int16, 16 x int8 중의 하나로서 유연하게 접근될 수 있습니다. 그러므로 단일 산술 벡터 작업을 8 개의 "mediump" 값으로 단일 연산에서 수행할 수 있습니다. 그리고 OpenCL 커널들은 클락 사이클 당 SIMD 당 16 픽셀을 처리하기 위해서 8 비트 휘도( luminance ) 데이터 상에서 동작합니다.

아키텍쳐 파이프라인의 내부 아키텍쳐를 밝힐 수는 없지만, 각 GPU 를 위한 공용 성능 데이터를 사용해서 이용 가능한 수학 유닛의 개수에 대한 아이디어를 제공할 수는 있습니다. 예를 들어 16 코어를 가진 Mali-T760 은 600 MHz 로 326 FP32 GFLOPS 를 달성합니다. 이는 이 셰이더 코어의 경우에 클락 사이클 당 34 FP32 FLOPS 입니다; 그것이 두 개의 파이프라인을 가지므로 클락 사이클 당 파이프라인당 17 FP32 FLOPS 가 됩니다. 연산( operation )의 관점에서 이용가능한 성능은 FP16/int16/int8 데이터 타입에서는 증가하고 FP64/int64 데이터 타입에서는 감소할 것입니다.

Texture Pipeline

텍스쳐 파이프라인( T-pipe )은 텍스쳐를 사용하는 모든 메모리 접근에 대한 책임이 있습니다. 텍스쳐 파이프라인은 클락 당 바이리니어 필터링된( binlinear filterd ) 하나의 텍셀( texel )를 반환할 수 있습니다; 트라이리니어 필터링( trilinear filtering )은 메모리로에 있는 두 개의 서로 다른 밉맵( mipmap )로부터 샘플( sample )을 로드할 것을 요구하므로, 완료되려면 두 번째 클락 사이클이 요구됩니다.

Load/Store Pipeline

로드/스토어 파이프라인( LS-pipe )은 텍스쳐링과 관련이 없는 모든 메모리 접근에 대한 책임이 있습니다.

그래픽스 워크로드의 경우, 이는 버텍스 애트리뷰트 인풋( vertex attribute input ) 당 읽기, 버텍스 셰이딩 동안 계산된 버텍스 아웃풋( output ) 당 쓰기, 그리고 프래그먼트 셰이딩 동안 버텍스 셰이더에 의해 작성되고 베어링 변수로 보간될 수 있는 버텍스 아웃풋 값 당 읽기를 의미합니다.

일반적으로 모든 명령( instruction )들은 단일 메모리 접근 연산이며, 그것들은 벡터 연산임에도 불구하고 산술 연산처럼 단일 사이클에서 전체 "highp" vec4  베어링을 로드할 수 있습니다.

Early ZS Testing and Late ZS Testing

OpenGL ES 명세의 "fragment operations" 은 -- 그것은 뎁스 테스트와 스텐실 테스트를 포함합니다 -- 파이프라인의 끝에서 프래그먼트 세이딩이 완료된 후에 발생합니다. 이는 명세를 매우 단순하게 만들지만, 무엇인가를 셰이딩하는데 많은 시간을 소비해야 하고 그것이 ZS 테스트에 의해서 무효화( killed )된 것으로 밝혀진다면 프레임의 끝으로 던져져 버리기만 함을 내포합니다. 그것을 그냥 버려 버리는( discard ) 컬러링 프래그먼트들에서는 성능향상과 에너지 절약에 도움이 되므로, ZS 테스트를 미리 할 수가 있습니다( 예를 들어 프래그먼트 셰이딩 전에 ). 늦은 ZS 테스트는 ( 예를 들어 프래그먼트 셰이딩 이후에 ) 피할 수 없습니다( 즉, "discard" 를 호출할 수도 있는 프래그먼트에 대한 의존성같은 것들은 트라이파이프에 존재할 때까지는 비결정된 뎁스 상태를 가집니다 ).

전통적인 이른 Z 구조와 더불어, 우리는 프래그먼트를 중지시킬 수 있는 일부 오우버드로( overdraw ) 제거( removal ) 기능을 가지고 있습니다. 그것은 이미 래스터화되었지만 출력 화면에는 유용한 방식으로 기여하지 않는다면, 실제 렌더링을 막을 수 있습니다. 내 동료인 seanellis 는 이 기술에 대해 살펴 보는 훌륭한 블로그를 작성했습니다 - Killing Pixels - A New Optimization for Shading on Arm Mali GPUs - 그러므로 여기에서 깊게 살펴 보지는 않겠습니다.

Memory System

이 섹션은 나중에 추가되었습니다. 그래서 만약 이 블로그를 이전에 이미 읽었다면 이 섹션을 기억하지 못할 것입니다. 여러분이 이상하게 아니니까 걱정하지 마세요. 우리는 OpenCL 커널과 OpenGL ES 컴퓨트 셰이더를 작성하는 개발자들에게 GPU 캐시 구조에 대해서 더 많은 정보를 달라는 요청을 받아왔습니다. 캐시 지역성( locality )을 최적화하기 위해서는 데이터 구조체와 버퍼들을 깔고 가는게 실제로 이득이 되기 때문입니다. 사실들은 다음과 같습니다:

  • 셰이더 코어 당 두 개의 16 KB L1 데이터 캐시; 하나는 텍스쳐 접근을 위해 하나는 범용 메모리 접근을 위해.
  • 모든 셰이더 코어들에 의해 공유되는 단일 논리 L2 캐시. 이 캐시의 크기는 다양하며, 실리콘 통합자( integrator )에 의해서 구성됩니다. 하지만 일반적으로는 인스턴스화된( instantiated ) 셰이더 코어 당 32 KB 와 64 KB 의 사이입니다.
  • 두 개의 캐시 레벨은 모두 64 byte 캐시 라인을 사용합니다.

GPU Limits

  • 단순한 모델에 기반해, GPU 성능의 근본적인 속성들의 일부를 알아내는 것이 가능합니다:
  • GPU 는 클락 당 셰이더 코어 당 하나의 버텍스를 제출합니다.
  • GPU 는 클락 당 셰이더 코어 당 하나의 프래그먼트를 제출합니다.
  • GPU 는 클락 당 셰이더 코어당 하나의 픽셀을 리타이어( retire, 역주 : 명령이 분기 예측 실패같은 것들 없이 실제로 완료되는 것을 의미 )합니다.
  • 클락 당 파이프 당 하나의 명령을 제출할 수 있습니다. 그러므로 일반적인 셰이더 코어에 대해서는 만약 가능하다면 병렬적으로 4 개의 명령들을 제출할 수 있습니다:
    • A-pipe 당 17 FP32 연산.
    • LS-pipe 당 한 벡터 로드, 한 벡터 스토어, or one vector varying.
    • T-pipe 당 한 바이리니어 필터 텍셀.
  • 클라 당 코어 당 GPU 는 일반적으로 32 비트 DDR 접근( 읽기와 쓰기 )을 가지게 됩니다[ 구성 가능 ].
만약 Mali-T769 MP8 을 600 MHz 로 스케일링하면, 이론적인 최대 성능은 다음과 같을 수 있습니다:
  • Fill rate:
    • 클락 당 8 픽셀 = 4.8 GPix/s.
    • 완전한 1080p 에 대해 초 당 2314 프레임.
  • Texture rate:
    • 클락 당 8 개의 바이리니어 텍셀 = 4.8 GPix/s.
    • 1080p @ 60 FPS 를 위해, 픽셀 당 38 바이리니어 텍스쳐 룩업( lookup ).
  • Arithmetic reate:
    • 코어 당 파이프 당 17 FLOPS = 163 FP32 GFLOPS.
    • 1080p @ 60 FPS 를 위해, 픽셀 당 1311 FPLOPS.
  • Bandwidth:
    • 클락 당 256 비트 메모리 접근 = 19.2 GB/s 일기 및 쓰기 대역폭.
    • 1080p @ 60 FPS 를 위해, 픽셀당 154 바이트.

OpenCL and Compute

주의깊은 독자들은 필자가 버텍스와 프래그먼트에 대해서 많이 이야기하고 있음을 눈치챘을 겁니다 - the staple of graphics work - 하지만 OpenCL 과 RenderScript 컴퓨트 스레드가 코어로 들어 오고 있는 과정에 대해서는 거의 언급하지 않았습니다. 이 두 가지 유형의 작업은 버텍스 스레드와 거의 유사하게 동작합니다 - 버텍스 셰이더를 실행하는 것을 1 차원 컴퓨트 문제로서의 버텍스 배열로 볼 수 있습니다. 그러므로 버텍스 스레드 생성자( creator )는 컴퓨트 스레드를 생성( spawn )하기도 합니다. 더 정확하게 이야기하자면, 컴퓨트 스레드 생성자가 버텍스도 생성하고 있는 것입니다.

Performance Counters

문서는 Midgard 패밀리 성능 카운터에 대해 설명하고 있습니다. 이 블로그에서 설명한 블록 아케텍쳐에 매핑되어 있으며, Midgard 패밀리에 대한 필자의 블로그에서 찾아 보실 수 있습니다.

Next Time ...

이 블로그는 시리즈의 첫 번째 챕터를 마무리하며, 추상 머진을 개발했습니다. 추상 머신은 기본 동작을 정의하는데, 애플리케이션 개발자가 Midgard 패밀리인 Mali GPU 를 이해할 수 있도록 합니다. 이 시리즈의 후반에서, 필자는 이 새로운 지식이 작동하도록 하기 시작할 것입니다. 그리고 일반적인 애플리케이션 개발 함정과 유용한 최적화 기술들에 대해서 조사할 것입니다. 이런 것들은 Arm DS-5 Streamline 프로우파일링 툴의 Mali integration 을 사용해 식별되고 디버깅될 수 있습니다.

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

Pete

+ Recent posts