LLM 시대, LangChain(랭체인)으로 배우는 AI 소프트웨어 개발
이번에는 Transformer의 구조와 동작원리에 대해 설명합니다. Transformer는 LLM의 핵심 구성요소이며, 높은 범용성을 지닌 모델입니다. 2017년 발표된 오리지널 Transformer는 인코더-디코더 아키텍처를 채택했으나, GPT-3이후 현재 주류인 LLM에서는 주로 디코더만 사용하는 Transformer가 일반적입니다. 따라서 이번에는 디코더 전용 Transformer에 중점을 두어 설명합니다.
Transformer는 뉴럴 네트워크를 기반으로 하며, 여러 층(layer)으로 구성된 모델입니다. 많은 층을 가진 깊은 네트워크를 사용하는 학습기법을 특히 딥러닝이라 부르며, Transformer 역시 다층 구조를 가지므로 딥러닝 모델의 한 종류로 분류됩니다.
우선 Transformer의 구성에 대해 설명한 후, 각 층별 역할에 대해 살펴보고, 이러한 역할을 구현하는 기술에 대해 다룹니다.
아래 그림은 Transformer의 구성을 보여줍니다. Transformer의 입력은 n개의 토큰으로 구성되며, 출력은 후속 토큰의 확률 분포를 나타냅니다. 보다 정확히 말하면, 입력은 n요소의 벡터 x = [x1, x2, ..., xn] 각(xi는 토큰 ID)로 주어지며, n은 입력 토큰수(가변길이)를 의미합니다. 예를 들어, GPT-3의 경우 최대 토큰 수가 2048이므로 n은 2048이하의 자연수가 됩니다.
출력은 n x |v| 크기의 행렬로 나타나는데, 여기서 |V|는 어휘수(전체 토큰ID 갯수)입니다. 예를 들어, GPT-3의 경우 어휘수가 50257이므로, 출력 행렬의 크기는 최대 2048 x 50257이 됩니다. 이 행렬의 각 행은 입력 토큰 시퀀스에 대해 후속 토큰의 확률 분포를 나타내며, 최종 행의 확률 분포를 사용해 다음 토큰을 샘플링합니다.
아래 그림의 예에서 입력은 벡터 [2, 4, 1, ..., 3]으로 주어지고, 출력의 마지막 행, 즉 [0.192, 0.055, 0.167, ..., 0.102]는 다음 토큰의 확률분포를 나타냅니다. 입력 n개의 토큰에 대해 n+1번째 토큰을 예측하게 되므로 n번째 행이 최종행이 됩니다.
구성이 복잡해 보일 수 있지만, 이를 몇개 부분으로 나누면 이해하기 쉬워집니다. 이 강좌에서는 이를 임베딩층, 디코더스택, 출력선형층의 세 부분으로 구분하여 설명합니다. 이후 각 부분의 역할과 그 구현 메커니즘에 대해 다룹니다.
Transformer는 임베딩층, 디코더 스택, 출력선형층의 세가지 파트로 구성됩니다. 이 중 디코더 스택은 동일한 디코더를 N개 겹쳐 쌓는(스택) 구조입니다.
임베딩층은 각 토큰ID를 벡터로 변환합니다. 이는 토큰ID가 단순히 어휘집(보캡) 내 위치를 나타내는 기호적(symbolic)표현에 불과하며, 숫자 자체에는 의미가 없기 때문입니다. 예를 들어, 토큰ID가 1인 단어와 2인 단어가 의미적으로 유사하다는 보장은 없고 두 ID의 차이가 단어의미의 유사성을 나타내지도 않습니다.
반면에, 벡터 표현을 사용하면 단어의 의미적 뉘앙스나 문법적 특징을 반영해 토큰을 표현할 수 있습니다. 예를 들어, 문법적으로나 의미적으로 유사한 단어들은 벡터 공간에서 서로 가까운 위치에 배치될 수 있습니다. 아래 그림에서는 '자동차', '차', '트럭'과 같은 단어들이 의미적으로 가깝기 때문에 벡터 공간에서도 인접한 위치에 있고, '사과'와는 거리가 먼 위치에 있는 모습을 보여줍니다. 여기서는 3차원 벡터로 표현했지만, 실제로는 수백차원 이상의 벡터가 사용됩니다. 예를 들어, GPT-3에서는 12,288차원의 벡터표현을 사용합니다.
또한, 이 벡터 공간에서는 유사 단어의 벡터가 가깝게 위치할뿐 아니라, 벡터들간의 상대적 위치가 단어 간 의미적 관계를 나타냅니다. 아래 그림에 나오는 유명한 예시인 'King - Man + Woman = Queen'을 살펴봅시다. King 벡터에 서 Man 벡터를 뺴면, King에서 '남성'이라는 속성을 제거한 벡터가 얻어집니다. (점선 화살표) 이 벡터는 '성별과 무관한 왕'이라는 개념을 나타냅니다. 여기에 '여성'을 뜻하는 Woman벡터를 더하면, Queen(여왕)에 해당하는 벡터를 얻을 수 있습니다. 아래 그림에서는 Woman 벡터에 이 점선 벡터를 더해 Queen벡터가 얻어지는 과정을 시각적으로 보여줍니다.
처럼 벡터 표현을 사용하면 단어간 의미적 관계를 모델링을 할 수 있습니다. 예를 들어, 'Seoul(서울) - Korea(한국) + France(프랑스) = Paris(파리)'나 'Cats - Cat + Dog = Dogs'와 같은 사례도 잘 알려진 예시입니다. 또한, 이 임베딩층에서는 각 토큰이 문장에서 어느 위치에 나타나는지에 대한 위치정보 역시 토큰 임베딩 벡터 시퀀스에 부가됩니다. 이를 '위치 임베딩(Positional Embedding)'이라고 하며, Transformer가 문장의 순서정보를 이해하는데 중요한 역할을 합니다.
디코더 스택은 n개의 토큰 임ㅂ베딩 벡터( 크기 n x Dmodel의 행렬)를 입력으로 받아, 각 토큰 임베딩 벡터에 다양한 정보를 반영하여 다시 n개의 토큰 임베딩 벡터를 출력합니다. 여기서 반영되는 정보에는 토큰 간의 상호 연관성이나 문맥등이 포함됩니다.
예를 들어, 다음과 같은 토큰 시퀀스가 입력된다고 가정해 봅시다.
"그", "는", "새로운", "검은", "차", "를", "구매","했다","."
이 문장에서 '새로운'과 '검은'이라는 형용사는 '차'(자동차)라는 명사를 수식하므로, 해당 토큰들 간에는 강한 연관성이 존재합니다. 따라서, '차' 토큰의 임베딩 벡터에는 '새로운'과 '검은' 형용사의 정보가 반영될 것으로 기대할 수 있습니다.
아래 그림은 이러한 과정을 3차원 벡터 공간으로 단순화해 시각화한 예시입니다. 실제 임베딩 벡터는 수백차원 이상의 고차원 공간에서 표현되지만, 여기서는 예시를 위해 3차원으로 축속해서 보여줍니다. '차' 임베딩 벡터에 '새로운', '검은'과 같은 형용사 정보를 추가로 반영함으로써 해당 벡터가 나타내는 의미가 더욱 구체화됩니다. 여기서는 형용사와 명사의 관계에만 초점을 맞추었지만, 실제로는 문장 전체의 맥락과 토큰간의 다양한 상호관계가 함께 고려되어 임베딩 벡터에 반영됩니다.
출력선형층은 n x Dmodel크기의 행렬(즉, n개의 토큰 임베딩)을 입력으로 받아, n x |V|크기의 행렬을 출력합니다. 이 행렬은 각 토큰에 대해 후속 토큰의 확률 분포를 나타냅니다.
입력으로 들어오는 토큰 임베딩 벡터는 N개의 디코더 스택을 거쳐 갱신된 상태이며, 각 토큰에는 다양한 정보가 반영되어 있습니다. 출력선형층에서는 각 토큰 임베딩 벡터를 어휘수(|V|)차원의 벡터로 변환하여 후속 토큰에 대한 확률을 계산합니다. 즉, 토큰 임베딩 벡터를 후속 토큰의 확률 분포로 변환하는 역할을 수행합니다.
임베딩층에서는 토큰ID를 요소로 갖는 n차원 벡터를 입력으로 받아, n x Dmodel크기의 행렬을 출력합니다. 이 출력행렬의 각 행은 입력된 토큰ID에 대응하는 임베딩 벡터입니다. 임베딩층의 처리를 자세히 살펴보면, 아래 그림은 임베딩층을 확대하여 보여줍니다. 임베딩 과정은 텍스트 임베딩과 위치 임베딩의 두 단계로 구성됩니다.
텍스트 임베딩: 토큰ID를 벡터 표현으로 변환합니다.
위치 임베딩: 각 토큰의 위치 정보를 벡터에 추가하여, 문장 내 순서와 문맥 정보를 반영합니다.
이와 같이 임베딩층은 텍스트 임베딩과 위치 임베딩을 결합하여 Transformer가 처리할 수 있는 고차원 벡터 스퀀스를 생성합니다.
텍스트 임베딩에서는 토큰ID를 요소로 갖는 n차원 벡터 x = [x1, x2, ..., xn](각 xi는 토큰ID)를 입력으로 받아, 각 요소에 해당하는 임베딩 벡터를 출력합니다. 이 벡터 표현은 LLM의 파라미터로 학습되며, 각 토큰ID마다 하나의 임베딩 벡터가 대응됩니다. 출력은 n x Dmodel크기의 행렬이며, 각 행은 입력된 토큰ID에 대응하는 임베딩 벡터입니다.
아래 그림은 텍스트 임베딩의 구성을 보여줍니다. 텍스트 임베딩 내부는 |V| x Dmodel 크기의 행렬 WE로 구성되며, 각 행은 어휘 집합 냉의 토큰 ID에 해당하는 임베딩 벡터입니다. LLM 구현에서는 이 행렬을 테이블로 간주할 수 있습니다. 즉, 토큰ID를 인덱스로 하여 이 테이블에서 해당 임베딩 벡터를 조회(lookup)할 수 있습니다.
예를 들어, 아래 그림에서는 입력으로 토큰ID 벡터 [2, 4, 1, ... , 3]가 주어집니다. 이 때, 입력 벡터의 첫번째 요소(0부터 세었을 때 1번쨰)는 토큰ID가 4인데, 이 임베딩 벡터는 WE의 4번째 행에 해당합니다. 만약, WE의 4번째 행의 임베딩 벡터가 [0.5, -0.6, 0.7, ..., -0.8]라면, 출력행렬의 첫번쨰 행은 [0.5, -0.6, 0.7, ..., -0.8]가 됩니다.
테이블 조회(lookup)연산은 수학적으로 토큰ID의 윈-핫 벡터 e(x)와 행렬 WE의 곱으로 표현됩니다. 윈-핫 벡터는 어휘크기 |V|의 요소를 갖는 벡터로 해당 토큰ID에 대응하는 위치의 값만 1이고 나머지는 모두 0인 벡터입니다. 예를 들어, 토큰ID가 2인 경우, 윈-핫 벡터 e(2)는 [0, 0, 1, 0, ..., 0]가 됩니다. 이 벡터에서는 0부터 셀 때 2번째 요소만 1이고 나머지는 0입니다.
수식으로 표현하면 텍스트 임베딩 출력은 다음과 같이 나타낼 수 있습니다.
여기서 WE 행렬은 LLM의 파라미터로서, |V| x Dmodel크기의 값을 가집니다. 테이블 조회 연산이 벡터와 행렬의 곱으로 수학적으로 표현될 수 있다는 사실은 해당 파라미터를 머신러닝을 통해 학습(미분가능연산으로 파라미터를 최적화)하는데 중요한 성질입니다. 이 부분에 대한 자세한 내용은 2장에서 다룹니다.
텍스트 임베딩을 통해 각 토큰의 벡터 표현을 얻을 수 있었습니다. 그러나, 이것만으로는 후속 처리에 충분하지 않습니다. 왜냐하면 텍스트 임베딩으로 생성된 벡터에는 각 토큰이 입력 텍스트에서 어디에 등장했는지에 대한 정보가 포함되어 있지 않기 때문입니다.
Transformer의 후속 처리에서는 토큰 임베딩 벡터에 주의(attention)메커니즘을 연속적으로 적용하여, 각 토큰이 다른 토큰과 어떤 관계를 맺고 있는지 계산합니다. 하지만, 위치정보가 없으면 동일한 토큰이 다른 위치에 나타나더라도 동일한 임베딩 벡터로 표현되어 토큰 간의 관계를 올바르게 파악할 수 없습니다. 예를 들어, "A dog bites a man."과 "A man bites a dog."라는 두 토큰 시퀀스를 생각해 보면, 서로 다른 위치에 등장하는 동일한 토큰(예: dog, man)이 후속처리 후에도 같은 임베딩 벡터를 갖게 됩니다. (엄밀히 말하면, 이후의 마스크 처리 효과로 완전히 동일하지 않지만) 이러한 문제를 해결하기 위해 위치 임베딩이 도입됩니다. 즉, 위치 임베딩은 동일한 토큰이라도 등장 위치에 따라 서로 다른 임베딩 벡터를 갖도록 조정합니다.
위 그림은 위치 임베딩의 구성을 보여줍니다. 여기서는 n x Dmodel크기의 임베딩 행렬에 대해, 각 토큰의 위치를 나타내는 행렬을 덧셈하여 위치 정보를 임베딩 벡터에 부여합니다. 이때 덧셈에 사용되는 행렬을 위치 임베딩이라고 합니다. 위치 임베딩은 학습가능한 파라미터가 아니고 고정된 행렬로 토큰의 위치에 따라 값이 달라집니다. 위 그림에서는 이 행렬을 히트맵으로 시각화하여 표현하고 있습니다.
위치 임베딩의 처리과정은 단순히 각 토큰에 해당하는 위치 임베딩을 덧셈하는 것입니다. 그렇다면 어떤 위치 임베딩을 선택해야 할까요? 임베딩 벡터에 더하는 것을 전제로 할 때 위치 임베딩은 다음과 같은 조건을 만족해야 합니다.
임베딩 벡터와 동일한 차원수를 가져야 합니다.
서로 다른 위치는 서로 다른 값을 가져야 합니다.
토큰의 상대적 위치관계를 표현할 수 있어야 합니다.
값이 너무 커지지 않아야 합니다.
먼저 토큰 임베딩 벡터에 더하는 위치 임베딩은 그 벡터와 같은 차원을 가져야 합니다. 또한 서로 다른 위치라면 다른 값을 가져야 하며, 가까운 위치에 있는 토큰은 유사한 위치 임베딩을 멀리 떨어진 토큰을 크게 다른 위치 임베딩을 가져야 합니다. 마지막으로 위치 임베딩의 값이 너무 크면 토큰 임베딩 벡터와의 합이 과도하게 커져 모델학습이 불안정해질 수 있으므로 일정 범위 내로 제한되어야 합니다.
이러한 조건을 만족시키기 위해 삼각함수를 이용한 위치 코딩이 널리 사용됩니다. 먼저, 위 그림의 히트맵을 통해 위치 임베딩의 시각적 특징을 살펴보면, 세로축은 토큰의 위치를, 가로축은 위치 임베딩의 차원을 나타냅니다. 세로 방향으로 보면 토큰의 위치가 변함에 따라 위치 임베딩 값도 변화하며, 가까운 토큰들은 비슷한 임베딩 값을 멀리 떨어진 토큰들은 큰 차이를 보이는 임베딩값을 갖는 것을 확인할 수 있습니다. 한 열을 보면 주기적인 변화가 나타나는데, 이는 삼각함수를 사용하여 생성되기 떄문입니다. 삼각함수를 사용함으로써 위치 임베딩의 각 요소 값은 -1에서 1사이에 수렴합니다. 따라서, 위 그림의 위치 임베딩은 앞서 언급한 조건들을 만족함을 알 수 있습니다.
그렇다면 구체적으로 위치 임베딩은 어떻게 생성될까요? 위치 임베딩의 계산식은 다음과 같이 표현됩니다.
여기서 pos는 토큰의 위치(시퀀스내 인덱스), i는 차원 인덱스, Dmodel은 임베딩 벡터의 차원수입니다. 이 함수는 짝수 인덱스와 홀수 인덱스에 대해 각각 sin과 cos값을 생성하여 위치 임베딩을 구성하며, 각 인덱스마다 다른 주파수를 가지므로 서로 다른 위치에 대해 고유한 값을 부여할 수 있습니다. 또한, 이 위치 임베딩은 Transformer의 학습 가능한 파라미터로 설정되는 것이 아니라, 고정된 값으로 모델에 내장되기 때문에 고정 위치 임베딩이라고도 합니다.
위치 임베딩 층의 출력은 수식으로 다음과 같이 표현할 수 있습니다.
여기서 E는 텍스트 임베딩의 출력 행렬(각 행이 토큰의 임베딩 벡터를 나타냄)이고, PE는 위치 임베딩 행렬입니다. 이 PE의 각 행은 토큰의 위치에 대응하며, 임베딩 벡터에 위치 정보를 추가하는 역할을 합니다. 이 덧셈 연산을 통해 Transformer는 토큰의 위치 정보를 임베딩 벡터에 통합할 수 있게 됩니다.
이러한 위치 임베딩 층의 도입으로 Transformer는 시퀀스 내 토큰들의 순서를 효과적으로 처리할 수 있게 되어, 동일한 단어라도 서로 다른 문맥이나 위치에 있을 경우, 서로 다른 임베딩 벡터를 갖게 됩니다. 이를 통해 보다 정밀한 언어 처리가 가능해집니다. 또한 위치 임베딩은 선형적으로 더해지기 때문에, 임베딩 벡터와 위치 임베딩간의 관계가 유지되며, Transformer 모델이 학습 및 추론시 이들 정보를 상호보완적으로 활용할 수 있습니다. 이와 같이, Transformer는 임베딩 벡터와 위치 임베딩의 결합을 통해 단어의 의미와 문맥을 동시에 포착할 수 있도록 설계되었습니다.
아래 그림은 디코더 스택의 구성을 보여줍니다. 디코더 스택은 동일한 구성의 여러 디코더층으로 이루어져 있으며, 디코드층의 수는 LLM마다 다릅니다. 예를 들어, GPT-3에는 96개의 디코더층이 존재합니다. 아래 그림에서는 오른쪽 끝에 디코더층의 세부구성을 나타내고 있습니다. 각 디코더는 주로 네 가지 층으로 구성됩니다. 구체적으로는 마스크드 멀티헤드 어텐션(Masked Multi-head Attention)기법, 피드포워드 네트워크(Feedforward Network), 그리고 2번의 레이어 정규화(Layer Normalization)으로 이루어져 있습니다.
마스크드 멀티헤드 어텐션: 이 층에서는 토큰 간의 연관성을 계산합니다.
피드포워드 네트워크: 출력 임베딩 벡터에 비선형 계산을 적용합니다.
이 두 계산 과정에서 산출된 정보는 잔차 연결(Residual Connection)을 통해 입력 임베딩 벡터에 더해집니다. 각 추가 단계마다 레이어 정규화가 적용되어 출력 임베딩 벡터가 갱신됩니다. 참고로 GPT-3의 경우, 레이어 정규화는 각 서브 레이어 이후가 아니라 이전에 배치됩니다.
디코더 스택에 입력된 토큰 임베딩 벡터는 디코더 스팩을 거치며 점진적으로 다양한 정보가 부여되고, 최종적으로 출력 임베딩 벡터가 생성됩니다. 디코더 스택처럼 동일한 서브컴포넌트를 반복적으로 쌓는 구성은 딥러닝 분야에서 일반적인 구조입니다. 입력에 가까운 층은 구체적인 정보를 처리하고 출력에 가까운 층은 더 추상적인 정보를 다루게 됩니다. 디코더 스택 역시 입력 임베딩 벡터에 대해 구체적인 정보 부여를 반복하며, 최종적으로 추상적인 정보를 담은 출력 임베딩 벡터를 생성하도록 설계되어 있습니다. 이우에는 마스크드 멀티헤드 어텐션 기법과 피드포워드 네트워크층에 대해 설명합니다.
마스크드 멀티헤드 어텐션 기법은 그 이름 그대로, 여러 개의 마스크드 어텐션 기법을 병렬로 적용하는 것을 의미합니다. 아래 그림은 마스크드 멀티헤드 어텐션 기법의 구성을 보여줍니다. 이 기법은 n x Dmodel크기의 입력 임베딩 행렬을 받아, n x Dmodel 크기의 출력 임베딩 행렬을 생성합니다. 입력 임베딩 행렬은 헤드 수 h만큼 분할되어 각 마스크드 어텐션 기법에 입력됩니다. 각 마스크드 어텐션 기법의 출력은 n x Dhead 크기의 행렬이며, 여기서 Dhead = Dmodel/h입니다. 이 출력 행렬들은 다시 연결(concat)되어 n x Dmodel 크기의 행렬로 복원된 후, 선형변환을 거쳐 최종 출력 임베딩 행렬을 생성합니다. 이 선형변환은 Dmodel x Dmodel 크기의 가중치 행렬 Wo를 사용하여 수행됩니다.
마스크드 멀티헤드 어탠션 기법의 출력을 수식으로 표현하면 다음과 같습니다.
여기서,
X는 입력 임베딩 행렬,
각 headi는 개별 마스크드 어텐션 기법의 출력,
Wo는 이들을 결합한 후 적용되는 가중치 행렬,
Concat은 행렬의 연결(Concatenation)을 의미합니다.
주의 메커니즘은 Transformer 내에서 특히 중요한 기능 중 하나입니다. 실제로 Transformer를 제안한 논문의 제목인 "Attention Is All You Need"는 주의 메커니즘의 중요성을 암시합니다. 주의 메커니즘은 토큰 간의 연관성을 계산하고, 이 연관성을 기반으로 임베딩 벡터를 갱신합니다.
예를 들어, 입력 토큰 시퀀스가 "그", "는", "새로운", "검은", "차", "를", "사다","다",".","그것","은"인 경우를 생각해 봅시다. 이 상황에서 다음 토큰을 예측하려면, "그것"이라는 토큰이 "차"를 가리키고 있다는 것을 이해해야 합니다. 또한, "차"가 어떤 종류의 차량인지를 파악하기 위해 "새로운"와 "검은"이 "차"를 수식하는 등 토큰 간의 연관성을 파악하는 것이 다음 토큰 예측에 매우 중요합니다. 다시 말해, 각 토큰마다 "지시대명사가 무엇을 가리키는지" 또는 "형용사가 무엇을 의미하는지"를 탐색할 수 있는 메커니즘이 필요하며, 이를 구현하는 것이 바로 주의 메커니즘입니다.
주의 메커니즘은 이러한 탐색을 위해 '쿼리(Query)'를 사용합니다. 멀티헤드 어텐션에서 여러 헤드를 사용하는 이유는 여러 개의 쿼리를 동시에 사용하여 다양한 정보를 동시에 검색할 수 있기 때문입니다. 이 쿼리를 기반으로 키(Key)를 검색하고, 그 결과를 바탕으로 값(Value)을 가져옵니다. 즉, 주의 메커니즘은 쿼리, 키, 값이 세가지 요소를 사용하여 토큰 간의 연관성을 계산합니다. 이 때, 쿼리, 키, 값은 각각 n x Dhead 크기의 행렬 Q, K, K로 계산되며, 이 행렬의 i번째 행은 i번째 토큰에 해당한느 쿼리, 키, 값 벡터를 나타냅니다.
중요한 점은 , 특정 쿼리가 '지시대명사'나 '형용사'와 같은 역할을 명시적으로 지정할 필요가 없다는 것입니다. 각 헤드에 존재하는 쿼리의 역할은 입력 데이터로부터 자동으로 학습되며, 쿼리가 포착하는 연관성은 단순히 문법적 관계에 국한되지 않고, 문맥에 따른 다양한 관점의 연관성을 자동으로 반영할 수 있습니다. 주의 메커니즘은 각 토큰의 쿼리와 키의 유사도를 계산하고, 그 유사도에 따라 값을 가중합산하여 최종 출력을 계산합니다. 이 주의 메커니즘의 직관적인 설명은 아래 그림으로 시각화되어 있습니다.
Transformer에서는 N x Dhead개의 주의 기법 헤드(N: 레이어수, Dhead: 헤드수)가 존재합니다. 여기서는 그중 하나의 헤드만을 가정하여 설명하였습니다. 이 헤드는 지시대명서가 무엇을 가리키는지를 계산한다고 가정합니다. 또한, 입력 토큰 시퀀스가 "그", "는", "새로운", "검은", "차", "를", "사다","다",".","그것","은"인 경우를 상정합니다. 임베딩층 이후 각 토큰은 Dmodel 차원의 벡터로 표현됩니다. 주의 메커니즘(어텐션)층에서는 이 Dmodel차원의 벡터로부터 앞서 언급한 쿼리, 키, 값 3가지 벡터를 생성합니다. 단, 설명을 위해 그림에서는 이를 2차원으로 간소화하여 직관적으로 표현하고 있습니다.
쿼리(Query): 현재 토큰을 대표하는 벡터
키(Key): 비교대상 토큰을 대표하는 벡터
값(Value): 최종 출력에 활용할 토큰 정보를 담는 벡터
주의 메커니즘이 수행하는 핵심 작업은 쿼리를 키와 비교하여 유사도가 높은 벡터를 찾는 것입니다. 예를 들어, "그것"이 어떤 대상을 가리키는지 찾으려면, "그것"의 쿼리 벡터와 다른 토큰들의 키 벡터를 비교하여 유사도가 가장 높은 벡터를 찾습니다. 그림에서는 "그것"과 가장 유사한 키 벡터가 "차" ㅂ벡터임을 확인할 수 있으며, 다른 키 벡터들은 "그것"과 유사도가 낮습니다. 이렇게 유사도가 높은 벡터를 기반으로 값(Value) 벡터들을 가중합산하여 최종출력벡터를 계산합니다.
이번에는 다른 주의 기법 헤드를 가정해 봅시다. 이번 헤드는 형용사가 명사를 수식하는 관계를 계산한다고 생각해 볼 수 있습니다. 아래 그림을 참조하면, 이 헤드에서 '차'에 대한 쿼리 벡터를 살펴볼 때, 이 쿼리 벡터와 유사한 키 벡터가 '새로운', '검은'과 같은 형용사 벡터임을 알 수 있습니다. 이처럼 유사도가 높은 키 벡터를 찾아 해당값(value) 벡터들을 가중합산하여 최종 출력 벡터를 계산합니다.
앞서 주의 메커니즘(어텐션)이 토큰 간의 연관성을 어떻게 계산하는지 직관적으로 살펴보았습니다. 이제 마스크드 어텐션 기법이 실제로 어떻게 계산되는지, 그림과 수식을 ㅌ통해 구체적으로 설명하겠습니다. 아래 그림은 주의 메커니즘의 구성을 나타냅니다.
주의 메커니즘을 계산하려면 쿼리(Q), 키(K), 값(V) 행렬이 필요합니다. 이는 다음과 같이 정의됩니다.
X: 입력 임베딩 행렬 (n x Dmodel)
WQ, WK, WV: 각각 Dmodel x Dhead 크기의 가중치 행렬 (Dhead은 헤드별 임베딩 차원)
이 가중치 행렬들은 모델 학습ㅂ을 통해 업데이트되는 파라미터입니다.
주의 메커니즘의 핵심은 각 토큰의 쿼리 벡터와 다른 토큰들의 키 벡터 간의 유사도를 계산하는 것입니다. 유사도는 벡터 간 내적(dot product)을 통해 구합니다. 예를 들어, n개의 토큰이 있을 때, 모든 토큰 쌍에 대해 쿼리와 키의 내적을 계산하면 n x n크기 행렬이 만들어집니다. 이 행렬의 (i, j)원소는 i번째 토큰의 쿼리 벡터와 j번째 토큰의 키 벡터간 내적 결과입니다.
이 행렬을 얻으려면, Q와 K의 행렬곱을 이용해야 합니다. Q와 K는 둘다 n x Dhead 크기이므로, 곱셈이 가능하도록 K를 전치
는 n x n크기의 행렬이 되며, 각 원소는 해당 토큰 쌍의 내적을 의미합니다.
내적값 자체는 벡터 크기에 따라 달라지므로, 이를 직접 비교하기에는 적절하지 않습니다. 따라서 소프트맥스(softmax) 함수를 이용해 행 단위로 정규화합니다. 소프트맥스는 각 행의 합이 1이 되도록 모든 원소를 변환하여 확률로 해석가능하게 만듭니다. 이렇게 정규화된 행렬은 토큰 간 연관성을 나타내는 확률 분포로 볼 수 있습니다.
마지막으로 정규화된 행렬 (n x n)에 V행렬 (n x Dhead)를 곱해, 각 토큰에 대해 연관성이 높은 토큰들의 값 벡터를 가중합산합니다. 이 연산결과 n x Dhead 크기의 출력 행렬이 생성됩니다. 이를 수식으로 표현하면,
이렇게 얻은 Attention(Q, K, V)는 n x Dhead 크기의 행렬이며, 각 토큰의 출력 임베딩 벡터를 의미합니다.
위와 같은 주의 메커니즘의 계산과정은 토큰 간 연관성을 반영한 가중합으로서 올바르게 동작하지만, 두가지 문제가 남아 있습니다.
내적값이 너무 커지는 문제: 쿼리와 키 벡터의 차원이 클수록 내적 값이 지나치게 커질 수 있습니다.
미래 정보 참조 문제: 시퀀스 생성(특히 디코더)에서 미래 토큰 정보를 참조하지 않도록 해야 합니다.
이후에는 위 2가지 문제에 대한 해결책을 순차적으로 살펴봅니다.
위 식의 계산에서는 내적
로 나눕니다. 내적은 Dhead 차원의 벡터까지 각 요소의 곱을 모두 합한 것입니다. 만약, Q와 K의 요소들이 평균 0, 분산 1이라고 가정하면, 각 요소곱의 분산은 1이고, 이들의 합의 분산은 Dhead가 됩니다. 분산은 제곱합의 평균으로 계산되므로, 그 제곱근인
로 사용됩니다. 이 결과에 값(Value) 행렬 V를 곱함으로써 주의 메커니즘의 최종 출력을 얻습니다. 수식으로 표현하면 다음과 같습니다.
남은 문제는 미래의 정보를 참조하는 현상입니다. 이에 대해서는 다음의 마스크 기법을 통해 해결하는 방법을 설명합니다.
위 식의 어텐션 계산에서는 미래의 정보를 참조하는 문제가 발생합니다. 아래 그림은 이 문제를 시각적으로 나타냅니다. 예를 들어, 4번째 토큰인 '차'에 관련된 토큰을 찾을 때, 그 이후의 토큰까지 참조하게 되는 것이 문제입니다. 학습 시에는 5번째 토큰인 '를'나 6번째 토큰인 '사다'등 미래 정보를 사용할 수 있지만, 추론(생성) 시에는 미래 정보를 참조할 수 없습니다. 따라서 학습과 추론 모두에서 4번째 토큰과 관련된 정보를 계산할 때 5번째 이후의 토큰은 무시해야 합니다.
이 문제를 해결하기 위해 '마스크(Mask)'를 도입합니다. 마스크는 특정 토큰과 연관된 가중치가 0이 되도록 설정하여 미래의 토큰 정보가 어텐션 계산에 영향을 주지 않도록 합니다. 아래 그림에서는 4번째 이후의 토큰에 해당하는 값(Value)이 마스크 처리되어 무효화된 모습을 보여줍니다.
i번째 토큰에 대해 i + 1번째 이후의 토큰을 무시한다는 것은, 소프트맥스 함수로 얻은 행렬의 i번째 행에서 i+1열이후의 요소들을 0으로 만드는 것과 동일합니다. 즉, 행렬의 대각선보다 위쪽에 위치한 요소들을 0으로 처리하여 미래의 정보를 무시할 수 있습니다.
소프트맥스 함수는 입력 행렬의 각 요소를 0에서 1사이의 값으로 변환하는 함수로 원래 값이 클수록 변환 후에도 큰 값을 작을수록 작은 값을 갖게 됩니다. 따라서 대각선 위쪽의 요소들을 음의 무한대(또는 매우 큰 음수)로 대체하면, 소프트맥스 함수의 출력에서는 해당 값들이 거의 0이 되어 마스크 효과를 얻을 수 있습니다. 이는 미래 정보를 나타내는 요소에 음의 무한대를 더하는 것과 같습니다.
이러한 작업은 소프트맥스 함수에 입력하기 전에 i번째 행의 i+1열 이후가 음의 무한대가 되도록 하는 마스크 행렬 M을 더함으로써 구현됩니다. 수식으로 표현하면 마스크드 어텐션은 다음과 같이 나타낼 수 있습니다.
여기서 M은 마스크 행렬로 행렬의 대각선보다 위쪽(즉, 상삼각 부분)의 모든 요소가 음의 무한대이고, 그 외의 요소는 0인 행렬입니다. 마스크를 적용한 후의 어텐션 행렬은 각 토큰에 대해 해당 토큰보다 이전에 등장한 토큰들의 정보만 집계하게 됩니다.
하나의 어텐션 메커니즘은 특정 관점에서 토큰 간의 연관성을 반영합니다. 이번에는 '지시대명서가 무엇을 가르키는지' 또는 '형용사가 무엇인지'와 같은 관점을 예로 들었지만, 실제로는 디코더 위치나 어텐션 헤드의 종류에 따라 다양한 관점에서 토큰간의 연관성이 계산됩니다.
레이어 정규화는 뉴럴 네트워크의 학습을 안정화하기 위해 각 층의 출력을 정규화하는 기법입니다. 구체적으로 각 토큰 임베딩 벡터의 요소별로 평균과 분산을 계산한 후, 이를 사용해 정규화를 수행합니다. 이를 통해 기울기 소실이나 폭발을 방지하고 학습 효율을 향상시킬 수 있습니다.
입력 임베딩 벡터
에 대한 레이어 정규화는 다음과 같이 계산됩니다.
여기서
�는 x의 평균,
σ2는 분산,
ϵ은 분모가 0이 되는 것을 방지하기 위한 아주 작은 값입니다.
또한, 정규화된 결과에 대해 학습 가능한 스케일링 파라미터
을 곱하고 더하여 최종 출력을 얻습니다.
피드포워드 뉴럴 네트워크(FFNN)는 마스크드 멀티헤드 어텐션 기법으로 정보를 추가한 토큰 임베딩 벡터를 입력받아 새롭게 추가해야 할 정보를 생성합니다. 따라서 입력은 n X Dmodel 크기의 행렬이고, 출력 역시 n x Dmodel 크기의 행렬입니다. 이 FFNN은 전결합(fully connected)층 두 개로 이루어진 비교적 단순한 구조를 갖고 있으며, 전형적인 뉴럴 네트워크인 MLP(Multi-Layer Perceptron)라고도 합니다. 아래 그림은 이 FFNN의 구성을 나타냅니다. 전형적인 뉴럴 네트워크 표기법에 따라 입력을 왼쪽, 출력을 오른쪽에 배치하였습니다.
아래 그림에서 각 원(노드)은 뉴럴(neuron)을, 각 화살표는 뉴런 간의 가중치가 있는 연결을 의미합니다. 이 뉴럴 네트워크는 입력층, 은닉층, 출력층의 세 개층으로 구성되며, 입력층 노드 수는 Dmodel, 은닉층 노드수는 Dff, 출력층 노드 수는 Dmodel입니다. 일반적으로 Dff는 Dmodel의 약 4배 정도가 됩니다. 파라미터를 갖는 층은 은닉층과 출력층 2개이므로, 이 뉴럴 네트워크는 2층짜리 뉴럴 네트워크라고 하 수 있습니다.
첫번쨰층의 계산은 다음 수식으로 나타낼 수 있습니다.
ReLU 활성화 함수는 뉴럴 네트워크에서 비선형성을 도입하는 역할을 합니다. 선형변환만 사용할 경우, 입력과 출력 간 관계가 단순한 선형결합에 국한됩니다. 그러나, ReLU같은 비선형 함수를 도입함으로써, 네트워크는 훨씬 복잡한 함수를 표현할 수 있게 됩니다. 구체적으로 ReLU는 입력이 0이상이면 그대로 출력하고, 0미만이면 0을 출력합니다. 이 단순한 처리 과정을 통해 네트워크는 필요한 정보만 선택적으로 통과시키고, 불필요한 정보는 차단할 수 있습니다. 결과적으로 데이터 내 복잡한 패턴을 학습하여 더 높은 정확도의 예측이 가능해집니다.
출력층의 계산은 다음 수식으로 표현할 수 있습니다.
또한, FFNN에는 드롭아웃(Dropout)층도 포함됩니다. 드롭아웃은 학습시 무작위로 뉴런을 비활성화하여 과적합(overfitting)을 방지하는 정규화 기법입니다. 특정 특징에 모델이 과도하게 의존하지 않도록 만들어, 일반화 성능을 높여줍니다.
즉, n개의 토큰으로 이러우진 입력 행렬 X (n x Dmodel)이 FFNN을 통과하면, 동일한 차원을 갖는 출력 행렬 Y (n x Dmodel)이 생성됩니다.
이 층은 모델의 최종 출력을 생성합니다. n개의 입력 토큰 각각에 대해 다음 토큰의 확률 분포가 출력되며, 확률 분포는 어휘수 |V|의 벡터로 표현되므로, 이 층의 출력은 n x |V|크기의 행렬이 됩니다. 한편, 입력은 n개의 Dmodel 차원 벡터, 즉, n x Dmodel 크기의 행렬로 주어지며, 각 행은 토큰 임베딩을 보유합니다.
아래 그림은 출력 선형층의 구성을 나타냅니다. 출력선형층은 선형 변환층과 소프트맥스 함수의 두 부분으로 구성되어 있으며, 이후에서는 이 두 부분에 대해 설명합니다.
선형변환층은 디코더 스택의 출력(n x Dmodel 행렬)을 어휘크기 차원(n x |V| 행렬)으로 매핑합니다. 이 변환은 다음 수식으로 표현할 수 있습니다.
선형변환의 출력은 어휘 크기 차원을 가지는 행렬이며, 이 행렬의 각 행은 해당 토큰의 스코어를 나타냅니다. 다만, 이 스코어들은 확률 분포가 아니므로, 확률 분포를 얻기 위해 선형 변환의 출력에 소프트맥스 함수가 적용됩니다.
소프트맥스 함수는 실수 벡터를 확률 분포로 변환하는 함수입니다. 이 함수는 벡터의 각 요소를 0에서 1사이의 값으로 변환하고, 이들 합이 1이 되도록 정규화합니다. n x |V| 행렬 Z가 주어졌을 때, 소프트맥스 함수는 다음 수식으로 표현할 수 있습니다.
이 함수는 다음 단계로 계산됩니다.
1. 행렬 Z의 각 요소에 대해 지수함수
를 적용합니다. 이를 통해 모든 요소가 양의 값이 됩니다.
2. 각 행마다 지수 함수 값을 모두 합산하여, 해당 행의 정규화 상수로 사용합니다.
3. 행렬 Z의 각 요소에 대해 지수 함수 값을 정규화 상수로 나눕니다. 이로서 각 행의 요소 합의 1이 됩니다.
소프트맥스 함수는 행렬의 각 행에 독립적으로 적용되므로, 각 행은 독립적인 확률 분포로 해석할 수 있습니다.
지금까지 Transformer의 구조를 자세히 설명했습니다. 임베딩층, 디코더스택층, 출력선형층등 각 층에는 다수의 파라미터가 포함되어 있습니다. 이러한 파라마터들을 정리한 내용을 아래 표로 정리했습니다. 공개된 정보를 바탕으로 표에는 GPT-3의 파라미터 수 추정치가 기재되어 있습니다. 총합은 약 1750억개의 파라미터로 이는 공개된 수치와 거의 일치합니다. 따라서 이번에 설명한 Transformer 구조는 GPT-3와 같은 대규모 모델에서도 대체로 동일하게 적용된다고 볼 수 있습니다.
Transformer 파라미터 목록 (GPT-3 추정)
이번에는 Transformer의 오리지널 논문을 기반으로 한 아키텍처를 소개합니다. 상용화된 최신 모델들도 기본적으로 Transformer 아키텍처를 다르고 있다고 알려져 있습니다. 이 글의 마지막에는 오픈소스로 공개된 LLM인 Llama 3의 아키텍처를 소개하며, 앞서 설명한 Transformer 아키텍처와의 차이점을 설명합니다. 물론 이 글을 준비하면서 자료를 정리하던 중에도 Llama 3.3까지 나오면서 세부적으로 개선이 이루어졌지만, 기본적인 아키텍처는 동일함을 알 수 있습니다.
주요 차이점은 다음과 같습니다.
위치 인코딩 방식
정규화 기법
활성화 함수
주의 메커니즘의 키와 값의 헤드수
선형층에서 바이어스 항의 제어
이후에는 이러한 차이점들에 대해 순차적으로 설명합니다.
원래 Transformer에서는 위치 정보를 임베딩 벡터에 덧셈하는 방식의 위치 임베딩(Positional Encoding)을 사용했습니다. 구체적으로 삼각함수를 이용해 각 위치마다 벡터를 생성한 후, 이를 토큰의 임베딩 벡터에 더했습니다.
반면에, Llama 3에서는 RoPE(Rotary Positional Embeddings)이라는 방식을 채택하고 있습니다. RoPE에서는 위치 정보를 임ㅂㅂ베딩 벡터에 단순히 더하는 대신, 어텐션 메커니즘 내에서 쿼리 벡터와 키 벡터의 각 요소에 회전 연산을 적용합니다. 이 회전 연산의 특성상, 회전된 쿼리 벡터와 키 벡터의 내적 값은 거리가 멀어도 급격히 감소하지 않게 되어, 입력 토큰 시퀀스가 길더라도 토큰간 관계를 잘 포착할 수 있습니다.
RoPE의 계산 처라는 다음과 같습니다.
이 과정을 통해 입력 벡터는 위치에 따라 회전되며, 효과적으로 위치 정보가 통합됩니다.
원래의 Transformer에서는 레이어 정규화(Layer Normalization)를 도입하여 각 층의 출력을 정규화했습니다. 반면에 Llama 3에서는 RMSNorm(Root Mean Square Layer Normalization)을 채택하고 있습니다. RMSNorm은 평균과 분산을 사용하지 않고, 제곱평균의 제곱근(RMS)을 이용해 정규화를 수행합니다. RMSNorm은 계산량이 적으면서도 레이어 정규화와 동등한 정확도를 구현할 수 있는 것으로 평가됩니다. RMSNorm의 계산식은 다음과 같습니다.
원래 Transformer에서는 피드포워드 네트워크의 활성화 함수로 ReLU(Rectified Linear Unit)를 사용했습니다. 반면, Llama 3에서는 SwiGLU(Switchable Gated Linear Unit)라는 활성화 함수를 채택하고 있습니다. SwiGLU는 다음과 같은 계산을 수행합니다.
또한, SiLU함수는 다음과 같이 정의되어 x의 각 요소에 적용됩니다.
여기서 σ(x)는 sigmoid 함수로 정의되며, 통계학 분야에서는 로지스틱 함수라고도 합니다.
음의 값처리: ReLU는 입력이 음수일 때, 항상 0을 출력하여 기울기가 0이 되면 반면, SiLU는 음의 값에 대해서도 약간 음의 값을 출력하여 기울기 수실문제를 완화합니다.
포화 방지: sigmoid함수를 곱함으로써, 입력값이 커지면 출력이 포화되어 기울기 폭발 문제를 예방할 수 있습니다.
표현력 강화: SiLU는 음수 영역에서도 기울기를 가지므로, ReLU보다 더 다양한 표현력을 제공합니다.
SwiGLU는 SiLU와 게이트 메커니즘을 결합하여 Transformer의 표현력을 더욱 향상시킵니다. 즉, W1을 통해 입력 x에 변환을 가하고 SiLU함수를 적용한 후, W2에 의한 선형변환을 결합함으로써 입력에 따라 게이트를 열고 닫아 중요한 정보를 선택적으로 통과시킬 수 있습니다.
원래 Transformer에서는 쿼리, 키, 값 모두 동일한 헤드수 h를 사용했습니다. 그러나 Llama 3에서는 키와 값의 헤드수(hkv)를 쿼리의 헤드수(h)보다 적게 설정합니다. 이는 GQA(Grouped Query Attention)이라 불리는 기법입니다.
GQA에서는 쿼리를 그룹화하여 동일 그룹에 속하는 쿼리에 대해 동일한 키와 값을 사용합니다. 이를 통해 키와 값에 대한 파라미터를 여러 쿼리 헤드간에 공유함으로써, 계산량과 메모리 사용량을 줄일 수 있습니다. 또한, 쿼리에 대해 키와 값의 수가 적더라도 추론 성능은 유지할 수 있음이 알려져 있습니다.
원래의 Transformer에서는 선형층에 바이어스항이 포함되어 있었습니다. 일반적인 선형변환은 다음과 같이 계산됩니다.
그러나, Llama 3에서는 바이어스항을 제거하여 선형변환을 다음과 같이 계산합니다.
바이어스항을 제거함으로써 파라미터 수가 줄어들고 계산 효율성이 향상됩니다. 특히 대규모 모델에서는 바이어스 항의 영향이 상대적으로 작아, 성능에 미치는 영향은 제한적이라고 볼 수 있습니다.
©2024-2025 GAEBAL AI, Hand-crafted & made with Damon Jaewoo Kim.
GAEBAL AI 개발사: https://gaebalai.com
AI 강의 및 개발, 컨설팅 문의: https://talk.naver.com/ct/w5umt5