← 학습 경로

공통 · 2026-09-14

연산 최적화에서 모델 전체 성능으로

한 연산의 개선이 모델 전체 시간에 미치는 영향을 살펴보고, 병목을 다시 측정하며 최적화를 반복하는 과정과 성능 목표·메모리 용량에 따른 자원 확장을 설명합니다.

지난 글에서는 FlashAttention이 큰 점수·확률 행렬의 저장과 재읽기를 줄이는 과정을 살펴봤습니다. 그런데 모델은 어텐션만 계산하지 않습니다. 어텐션이 빨라진 뒤에도 MLP와 정규화 등 다른 연산은 계속 수행해야 합니다. 한 연산을 빠르게 만든 효과는 모델 전체의 실행 시간에서 확인해야 합니다.

이번 글에서는 한 부분의 개선이 전체 시간을 얼마나 줄이는지 작은 예시로 알아봅니다. 이어서 최적화하면서 주요 병목이 달라지고, 개선과 측정을 반복하게 되는 이유를 살펴봅니다. 마지막으로 현재 GPU의 자원을 효율적으로 사용하는 것에서 나아가, 실행 목표와 메모리 용량에 따라 더 많은 자원을 활용하는 선택으로 연결하겠습니다.

한 연산의 개선과 전체 실행 시간

정해진 입력으로 모델의 출력을 한 번 계산하는 데 100ms가 걸린다고 해봅시다. 여기서 전체 실행은 한 번의 순전파를 뜻합니다. 토큰을 반복해서 생성하거나 요청이 대기하는 시간까지 포함한 서비스의 응답 시간과는 측정 범위가 다릅니다.

이 100ms 중 어텐션에 60ms, MLP에 30ms, 나머지에 10ms가 걸린다고 가정하겠습니다. 이 예시의 어텐션은 FlashAttention을 적용하는 core attention 부분이며, QKV·출력 projection과 정규화 등은 나머지에 포함합니다. 수치는 원리를 설명하기 위한 가정으로, 특정 모델의 시간 비중이나 FlashAttention의 실제 측정 결과가 아닙니다.

Attention·MLP·나머지의 시간이 60·30·10ms에서 20·30·10ms, 다시 20·15·10ms로 변합니다. 전체 시간은 같은 시간 축에서 100ms, 60ms, 45ms로 줄어듭니다.

그림 1은 여러 층에서 수행한 시간을 연산 종류별로 모아 보여줍니다. 실제 모델에서 모든 어텐션을 끝낸 뒤 모든 MLP를 실행한다는 뜻은 아닙니다. 또한 이 예시에서는 연산들이 서로 겹치지 않아, 각 부분의 시간을 더하면 전체 시간이 된다고 가정합니다.

먼저 어텐션을 개선해 60ms를 20ms로 줄였습니다. 어텐션만 보면 60 ÷ 20 = 3이므로 세 배 빠른 실행입니다. 하지만 MLP의 30ms와 나머지 10ms는 그대로입니다. 전체 시간은 20 + 30 + 10 = 60ms가 되어, 처음보다 40ms 줄어듭니다. 어텐션은 세 배 빨라졌지만, 모델 전체는 약 1.67배 빨라진 것입니다.

이 예시에서 어텐션 시간을 거의 0으로 줄이더라도, MLP와 나머지 연산에 필요한 40ms는 남습니다. 따라서 어텐션만 아무리 빠르게 만들어도 전체 실행 시간을 40ms 아래로 줄일 수는 없습니다. 개선하지 않은 부분에 걸리는 시간이 전체 속도 향상의 한계를 정한다는 것이 암달의 법칙의 핵심입니다. 새로운 최적화 기법을 볼 때도 그 기법이 빠르게 만드는 부분이 내 실행에서 얼마나 큰 비중을 차지하는지 함께 봐야 합니다.

최적화하면서 달라지는 병목

그림 1의 첫 단계에서는 어텐션이 60ms로 가장 많은 시간을 차지했습니다. 이를 20ms로 줄인 다음에는 MLP의 30ms가 가장 커집니다. MLP 자체가 느려진 것은 아닙니다. 어텐션의 시간이 줄어들면서, 전체 실행에서 MLP가 차지하는 비중이 30%에서 50%로 커진 것입니다.

이제 MLP의 실행을 살펴보고, 입력 재사용이나 데이터 이동에서 개선할 부분을 찾을 수 있습니다. 예시처럼 MLP를 30ms에서 15ms로 줄이면 전체 시간은 20 + 15 + 10 = 45ms가 됩니다. 다시 가장 큰 부분은 어텐션의 20ms입니다. 처음 최적화한 부분도, 다른 부분을 개선한 뒤에는 다시 중요한 대상이 될 수 있습니다.

앞선 글에서는 한 연산이 메모리에서 데이터를 가져오는 속도나 연산 장치의 처리 속도에 제한되는 이유를 봤습니다. 이제는 그 관찰을 모델 전체로 넓힙니다. 전체 시간을 많이 차지하는 부분을 찾고, 그 안에서 어떤 자원이 실행을 제한하는지 살펴보는 것입니다. MLP의 시간이 크다고 확인했다면, 실제 개선 방법은 행렬의 모양과 데이터 재사용, 연산 장치의 활용 정도를 보고 정해야 합니다.

이 과정은 측정 → 주요 병목 개선 → 다시 측정으로 반복됩니다. 목표는 전체 실행 시간을 줄이는 것이며, 병목이 달라지는 것은 그 과정에서 나타날 수 있는 결과입니다. 한 번 개선한 뒤에도 같은 부분이 여전히 가장 큰 비중을 차지할 수 있습니다. 또한 시간 비중이 크더라도 이미 효율적으로 실행되고 있다면 더 줄일 여지가 작을 수 있으므로, 비중과 개선 가능성을 함께 판단해야 합니다. 이런 반복적인 접근은 CUDA 최적화 가이드에서도 강조합니다.

개선 전후를 비교할 때는 같은 모델과 입력 크기, 자료형, 장치에서 같은 범위의 시간을 재야 합니다. 특정 커널의 시간과 모델 전체 시간을 구별하고, 첫 실행의 준비 비용을 포함할지도 맞춥니다. GPU 작업은 비동기로 실행될 수 있으므로, CPU가 작업을 등록하는 데 걸린 시간을 GPU가 계산을 마친 시간으로 해석해서는 안 됩니다. GPU 실행 시간 측정에서는 측정하려는 작업의 시작과 완료를 기준으로 삼아야 합니다.

또한 그림 1에서는 각 부분의 시간을 더했지만, 실제 실행에서는 현재 계산을 하면서 다음 계산에 쓸 데이터를 가져올 수도 있습니다. 이때 계산 시간과 데이터 이동 시간을 그대로 더하면, 동시에 진행된 시간을 두 번 세게 됩니다. 따라서 각 부분의 시간뿐 아니라, 모델 실행을 시작해서 필요한 작업을 모두 마칠 때까지 걸린 시간도 확인해야 합니다.

입력이 바뀌면 주요 병목도 달라질 수 있습니다. 예를 들어 행렬 곱에서 토큰 하나를 처리할 때와 달리, 여러 토큰을 함께 처리하면 가져온 가중치를 여러 토큰의 계산에 재사용할 수 있습니다. 그러면 토큰마다 가중치를 읽는 데 드는 비용을 줄일 수 있어, 계산과 데이터 이동이 차지하는 시간 비중도 달라집니다. 한 입력에서 효과가 컸던 최적화가 다른 입력에서도 효과적인지는, 그 입력으로 다시 실행해 확인해야 합니다.

목표에 맞춰 자원 확장하기

지금까지는 주어진 GPU에서 불필요한 데이터 이동과 대기를 줄이고, 연산 장치를 더 잘 활용하는 방법을 살펴봤습니다. 이런 개선은 같은 자원으로 더 좋은 성능을 얻는 데 중요합니다. 하지만 실행에 필요한 용량이나 달성하려는 성능 목표에 따라, 더 많은 자원을 사용하는 선택도 필요합니다.

왼쪽은 실행 시간이 100ms에서 60ms로 줄었지만 목표인 30ms에 도달하지 못한 경우입니다. 오른쪽은 필요한 메모리 16GB가 한 GPU의 용량 12GB를 넘는 경우입니다. 두 상황에서 자원 확장을 검토하는 흐름으로 이어집니다.

실행 시간과 처리량의 목표

그림 2의 왼쪽에서는 실행 시간을 100ms에서 60ms로 줄였지만 목표인 30ms에는 도달하지 못했습니다. 같은 GPU에서 더 개선할 수도 있고, 여러 GPU에 계산을 나누어 완료 시간을 줄이는 방법을 검토할 수도 있습니다. 여기서 30ms는 설명용 목표이며, 어떤 성능 목표가 적절한지는 실제 작업의 요구에 따라 정해야 합니다.

한 작업이 끝나는 시간과 별개로, 같은 시간에 처리해야 할 작업의 수가 늘어날 수도 있습니다. 각 요청의 처리 속도는 충분하더라도, 들어오는 요청이 많아지면 더 많은 요청을 함께 처리할 자원이 필요합니다. 모델이 GPU 한 장에 들어간다면, 여러 GPU에 같은 모델을 올리고 서로 다른 요청을 맡기는 방법을 생각할 수 있습니다. 한 요청의 시간을 줄이는 목적과 서비스 전체의 처리량을 늘리는 목적을 구분하는 이유입니다.

실행에 필요한 메모리 용량

그림 2의 오른쪽은 속도 이전에 용량이 문제가 되는 경우입니다. 한 GPU의 메모리가 12GB인데 선택한 실행에 필요한 메모리가 16GB라면, 그 상태를 한 GPU에 모두 담을 수 없습니다. 여기서 필요량은 가중치뿐 아니라, 해당 실행 시점에 함께 보관해야 하는 중간값과 상태 등을 포함합니다.

FlashAttention처럼 불필요한 중간 저장을 줄여 필요량을 낮출 수도 있습니다. 그렇지만 그러한 개선으로도 선택한 모델과 입력을 현재 메모리에 담을 수 없다면, 실행 조건을 바꾸거나 사용할 저장 자원을 늘려야 합니다. 여러 GPU에 모델과 필요한 상태를 나누어 저장하는 것은 이때 검토할 수 있는 방법입니다. 데이터를 빠르게 읽는 문제와, 필요한 데이터를 보관할 공간이 있는 문제는 서로 다릅니다.

현재 자원의 최적화와 자원 확장은 함께 검토할 수 있습니다. 한 GPU에서 가능한 모든 개선을 끝낸 뒤에야 여러 GPU를 사용할 수 있는 것은 아닙니다. 메모리 용량이 부족한지, 한 요청이 너무 오래 걸리는지, 같은 시간에 더 많은 요청을 처리해야 하는지에 따라 필요한 선택이 달라집니다.

GPU를 추가하면 연산 장치와 메모리가 늘어납니다. 그 자원을 사용하려면 어떤 데이터와 계산을 각 GPU에 맡길지 정하고, 서로 필요한 데이터를 전달해야 합니다. 다음 글에서는 여러 GPU를 쓴다는 것이 무엇을 뜻하는지, 그리고 늘어난 자원이 주는 이점과 함께 어떤 통신과 대기를 고려해야 하는지 살펴보겠습니다.