SQL 500줄로 구현된 GPT
(explainextended.com)- PostgreSQL과 pgvector만으로 GPT-2 추론 흐름을 SQL에 옮겨, 토큰화부터 Transformer 블록과 다음 토큰 생성까지 재현함
- 생성형 LLM은 같은 입력에 같은 후보 토큰 확률을 내는 결정적 함수에 가깝고, 응답이 달라지는 지점은 후보 토큰을 뽑는 확률적 선택 단계임
- 구현은 GPT-2의 BPE 토크나이저, 50257개 토큰, 768차원 임베딩, 1024 토큰 컨텍스트, 12개 블록, 12개 attention head, GELU 기반 feedforward를 SQL 쿼리와 테이블로 표현함
- PostgreSQL 정규식의 Unicode 속성 미지원과
EXP의 작은 값 처리 한계처럼, 데이터베이스 환경에 맞춘 현실적인 우회가 필요했음 - 예제는
"Happy New Year! I wish you"에서 10개 토큰을 생성해"Happy New Year! I wish you all the best in your new year!"를 출력했고, 작성자 환경에서 2분 44초 걸림
SQL로 GPT-2 추론 파이프라인 만들기
- ChatGPT는 SQL이 대규모 언어 모델 구현에 적합하지 않다고 답했지만, PostgreSQL SQL로 GPT-2 추론 파이프라인을 구현함
- 참고한 구현 설명은 Jay Mody의 GPT in 60 Lines of NumPy이며, 같은 구성요소를 데이터베이스 테이블과 쿼리로 옮김
- 생성형 LLM은
llm(prompt: str) -> list[tuple[str, float]]형태의 함수처럼 볼 수 있음- 입력은 텍스트 프롬프트
- 출력은 다음에 올 문자열 후보와 확률 배열
- 내부 수학과 파라미터가 같다면 같은 입력에 같은 결과를 반환함
- ChatGPT 같은 제품이 같은 질문에 다른 답을 줄 수 있는 이유는 모델 자체보다 다음 토큰 선택 단계의 확률적 선택에 있음
텍스트 생성 루프
- 생성 과정은 프롬프트를 토큰 배열로 바꾼 뒤, 모델을 반복 호출해 다음 토큰을 고르고 프롬프트 뒤에 붙이는 구조임
- 기본 흐름은 다음 단계로 구성됨
tokenize(prompt)로 문자열을 토큰 ID 배열로 변환gpt2(tokens)가 50257개 토큰에 대한 확률을 계산select_next_token(candidates)가 다음 토큰을 선택- 선택한 토큰을 배열에 추가
- 정해진 토큰 수, 타임아웃, stopword 같은 조건에서 중단
detokenize(tokens)로 토큰 배열을 문자열로 복원
- 이렇게 누적된 토큰 시퀀스는 문법, 구문, 의미, 추론처럼 보이는 특성을 가진 자연어 텍스트가 될 수 있음
BPE 토크나이저를 SQL로 구현
- 신경망 입력 전 텍스트는 숫자 목록으로 바뀌어야 하지만, Unicode 코드포인트를 그대로 쓰면 토큰 공간과 길이가 비효율적임
- GPT-2는 Byte pair encoding의 변형을 사용함
- 토큰 사전은 50257개 코드포인트를 사용
- UTF-8 바이트 시퀀스와 “end of text” 토큰을 포함
- 처음에는 256개 바이트 토큰에서 시작해, 자주 등장하는 인접 토큰 쌍을 새 토큰으로 추가함
- 이 병합을 50000번 반복해 50256개 토큰을 만들고, 마지막에 end-of-text 토큰을 추가함
- GPT-2 토크나이저에는 바이트를 문자열 문자로 매핑하는 추가 계층이 있으며, 이 매핑은 OpenAI GPT-2의
encoder.py에 정의돼 있음 - SQL 구현에서는 OpenAI에서 내려받은 토큰 사전을
tokenizer테이블에 넣고, 바이트-문자 매핑을encoder테이블에 저장함 "Mississippilessly"예제는 recursive CTE로 단일 바이트에서 시작해 병합 가능한 가장 좋은 인접 쌍을 반복 병합함- 예제에서 토큰 수는 17개에서 5개로 줄어듦
- Unicode의 약 150k 코드포인트 공간 대신 GPT-2의 약 50k 토큰 공간을 사용함
- 여러 단어를 처리할 때 GPT-2는 정규식으로 텍스트를 나눠 각 단어 내부에서 병합함
- PostgreSQL은 정규식에서 Unicode 문자 속성을 지원하지 않아 원래 GPT-2 정규식을 일부 수정함
- 이 수정은 적절한 Unicode 지원을 손상시켰을 가능성이 있음
"PostgreSQL is great"는 SQL 토크나이저에서[6307, 47701, 318, 1049]로 변환됨- 토큰 클러스터는
Post,greSQL,Ġis,Ġgreat Ġ는 공백을 나타냄
- 토큰 클러스터는
임베딩과 컨텍스트 윈도우
- 토큰 ID는 곧바로 모델 계산에 쓰이지 않고 임베딩 벡터로 변환됨
- GPT-2는 토큰과 위치를 각각 임베딩함
WTE는 word token embedding이며 50257×768 행렬WPE는 word position embedding이며 1024×768 행렬
- 각 토큰 위치마다
WTE벡터와WPE벡터를 더해 다음 단계의 입력 벡터를 만듦 WPE가 1024개 위치만 갖기 때문에 GPT-2 프롬프트에 사용할 수 있는 최대 토큰 수는 1024개임- 이 수가 LLM의 컨텍스트 윈도우에 해당함
- 모델 설계 시 정해지는 하이퍼파라미터이며 학습으로 바뀌지 않음
- SQL 구현은 pgvector를 사용함
- 배열에 벡터 연산을 직접 정의해 순수 SQL로도 가능하지만 성능이 낮아짐
- 초기 버전은 순수 SQL 함수로 동작했지만 느렸음
self-attention을 SQL 쿼리로 전개
- Transformer의 핵심은 self-attention 메커니즘이며, 2017년 논문 Attention is all you need에 기반함
- attention은 토큰 벡터들이 서로 영향을 주도록 만들어, 프롬프트의 앞부분 정보가 마지막 벡터로 전달되게 함
- GPT-2 구현은 12세트의
Q,K,V행렬을 사용함- 각 세트는 하나의 attention head
- 각 head는 64차원
c_attn은 768×2304 선형 변환이며, 결과는Q,K,V가 가로로 쌓인 2304차원 벡터임- 가중치와 bias는
c_attn_w,c_attn_b테이블에 저장됨
- attention 계산 전에는 layer normalization이 적용됨
- scale과 shift 파라미터는
ln_1_g,ln_1_b테이블에 저장됨
- scale과 shift 파라미터는
- causal self-attention에서는 뒤쪽 토큰이 앞쪽 토큰에 영향을 주지 못하도록 causal mask를 적용함
- 모델의 다음 토큰 후보는 최종적으로 마지막 임베딩에서 결정됨
- 정보 흐름은 마지막 벡터 쪽으로 향해야 하며, 마지막 벡터의 중간값이 이전 벡터에 영향을 주면 안 됨
- SQL 구현은 softmax 계산에서 PostgreSQL
EXP가 매우 작은 수에 실패하는 문제를 피하기 위해 입력이 -745.13보다 작으면 0으로 처리함 - causal mask 덕분에 새 토큰을 프롬프트에 붙여도 이전 토큰들에 대한 계산 결과는 변하지 않음
- 원래 GPT-2 구현은 이 성질을 활용함
- SQL 구현은 단순성을 위해 이 재사용을 쓰지 않음
multi-head attention과 잔차 연결
- 12개 head의 attention 결과는 각각 64차원이며, 이를 가로로 쌓아 다시 768차원으로 만듦
- 이어서
c_proj_w,c_proj_b에 저장된 학습된 선형 변환으로 attention 출력을 투영함 - multi-headed attention 결과에는 원래 입력이 다시 더해짐
- 이 잔차 연결은 원래 Transformer 논문에 포함된 기법
- 학습 중 vanishing gradient와 exploding gradient 문제를 완화하기 위한 설계로 소개됨
feedforward 단계와 Transformer 블록
- attention 뒤에는 feedforward 신경망이 이어짐
- GPT-2의 feedforward 단계는 3개 층의 multi-layer perceptron으로 구성됨
- 차원은 768 → 3072 → 768
- 활성화 함수는 GELU를 사용함
- 선형 변환 파라미터는 다음 테이블에 저장됨
mlp_c_fc_w,mlp_c_fc_bmlp_c_proj_w,mlp_c_proj_b
- feedforward 입력도 먼저
ln_2파라미터로 정규화됨ln_2_g,ln_2_b가 scale과 shift를 저장함
- feedforward 출력에도 입력을 다시 더하는 잔차 연결이 적용됨
- 이 attention + feedforward 조합이 하나의 block이며, GPT-2는 12개 block을 파이프라인처럼 연결함
- 각 block은 자체 학습 파라미터 세트를 가짐
- SQL에서는 recursive CTE로 block들을 연결함
- 마지막 block 출력은
ln_f파라미터로 다시 정규화됨
다음 토큰으로 되돌리기
- 최종 출력 중 마지막 위치의 768차원 벡터가 다음 토큰의 임베딩임
- 이 벡터를 다시 토큰으로 바꾸기 위해 처음 토큰 임베딩에 사용한
WTE행렬을 다시 사용함 - 정확한 역변환은 보통 불가능함
- 예측 임베딩이
WTE의 특정 행과 정확히 같지 않을 수 있음 - 따라서 각 토큰 임베딩과의 dot product를 계산해 가까운 토큰을 찾음
- 예측 임베딩이
WTE와 예측 임베딩의 dot product 결과는 50257개 점수, 즉 logit이 됨- 이 점수들은 softmax를 거쳐 확률로 바뀜
- top 후보 수는
top_n - 확률 분포를 조절하는 값은 temperature
- temperature가 높을수록 1순위가 아닌 토큰이 선택될 가능성이 커지고, 추론이 덜 예측 가능해짐
- top 후보 수는
"PostgreSQL is great"예제에서 상위 5개 다음 토큰 후보는 다음과 같음Ġfor,.ĠatĠto
- temperature가 0.5, 1, 2로 바뀌면 같은 후보들의 softmax 확률 분포가 달라짐
실제 추론 결과와 코드
- 최종 SQL은 토큰을 확률에 따라 선택하고 프롬프트에 붙이는 과정을 반복함
- 모델 자체는 결정적이며, 비결정적 요소는 토큰 선택에 포함된 난수뿐임
- 예제 설정은 다음과 같음
- 프롬프트:
"Happy New Year! I wish you" - 생성 토큰 수: 10
- temperature: 2
- top_n: 1
SETSEED(0.20231231)사용
- 프롬프트:
- 작성자 환경에서 쿼리는 2분 44초 실행됨
- 출력 결과는
"Happy New Year! I wish you all the best in your new year!" - 쿼리와 설치 코드는 GitHub 저장소 quassnoi/explain-extended-2024에 있음
댓글과 토론
Hacker News 의견들
-
아름답다. 나도 SQLite로 비슷한 토끼굴을 파고 있었는데, 아직 신경망까지 끌어들이는 단계까진 못 갔음
makemore 강의 시리즈[0]에서 영감을 받았고, 1시간쯤 지나면 카운팅 방식에서 신경망으로 넘어가는데 내가 도달한 것도 대략 거기까지였음
이를 관계형 모델로 쪼개 보는 건 정말 좋은 연습임
[0] https://www.youtube.com/watch?v=PaCmpygFfXo- 계속 보면 신경망이 카운팅 방식과 정확히 같은 테이블을 도출하고, 생성할 때도 정확히 같은 결과를 냄
-
좋은 데모지만, 글에서 인과적 마스킹 설명은 학습과 추론을 섞어 놓은 듯함
인과적 마스킹은 학습 중 미래 토큰을 “엿보지” 못하게 하는 목적이 있고, GPT류 구조에서는 추론 중 자기회귀성을 강제하는 목적도 있음
추론 때는 어차피 마지막 토큰만 사용하므로 그 토큰은 전체 입력 시퀀스에 주의를 기울이며, 따라서 다음 토큰이 마지막 토큰의 임베딩만으로 결정되는 것은 아님 -
이게 GPT의 드라이버 루프를 정확히 표현한 건지 궁금함: 프롬프트를 토큰화하고,
gpt2(tokens)로 50257개 토큰 확률을 얻고, 다음 토큰을 고르고, 토큰 목록에 붙이고, 중단 조건을 확인한 뒤, 마지막에 역토큰화하는 구조처럼 보임
그런데 이건 상태 머신이 Shlemiel the painter 알고리즘을 구현하는 것처럼 보여서, 생성 작업의 본질적 계산 비용에 의문이 생김- 사람들이 대형 언어 모델에서 말하는 컨텍스트 창은 유지되는 토큰 수에 최대치가 있고, 가장 오래된 토큰이 버려진다는 뜻으로 이해함
그 창은 슬라이딩 윈도우임 - 맞음, 그게 루프이고 마법은 전부
gpt2함수 안에 있음 - 이건 알고리즘의 아주 작은 부분임
생성한 토큰들을 문장으로 모으는 방식만 보여주는 것에 가깝다
- 사람들이 대형 언어 모델에서 말하는 컨텍스트 창은 유지되는 토큰 수에 최대치가 있고, 가장 오래된 토큰이 버려진다는 뜻으로 이해함
-
관련 자료: A GPT in 60 Lines of NumPy - https://news.ycombinator.com/item?id=34726115 - 2023년 2월, 댓글 146개
- 이 자료는 글 초반부에 이미 나와 있음
-
비슷한 맥락에서 GPT를 전부 스프레드시트 함수로 구현했고, 함께 볼 수 있는 영상 튜토리얼도 만들었음
https://spreadsheets-are-all-you-need.ai/- 첫 번째 영상이 훌륭함
LLM이 꽤 멋지다고 생각하지만 실제 작동 방식을 직업적으로 배울 필요는 없었던 입장에서, 10분짜리 영상이 난해한 HN 댓글과 얕은 주류 매체 글을 몇 년 읽은 것보다 더 많이 가르쳐 줬음
엄청난 수의 부동소수점 숫자가 계산을 기다리며 쌓여 있는 모습을 보니 이 기술이 왜 GPU를 그렇게 많이 먹는지도 훨씬 직관적으로 이해됨 - 스프레드시트는 LLM 설명에 자연스러운 방식임
각 학습 예제에서 각 매개변수의 도함수를 계산하고, 그것이 해당 매개변수에 어떻게 매핑되는지 명시적으로 보여주면 학습 과정도 잘 설명할 수 있을 듯함
- 첫 번째 영상이 훌륭함
-
좋다. 1년 전만 해도 일종의 마법처럼 보이던 것이 이제 이렇게 잘, 거의 어린아이도 따라갈 수 있을 만큼 설명되고 있음
- 이 마법이 1년 전에 시작된 건 아님
글에서 설명하는 모델은 2019년 초에 공개된 GPT-2임 - “어린아이도 따라갈 수 있을 만큼”은 아님
이 글을 제대로 이해하려면 컴퓨터 과학 배경지식이 탄탄해야 하고, 제목 자체도 인류의 99%에게는 접근성이 낮음
- 이 마법이 1년 전에 시작된 건 아님
-
GPT와 LLM을 완전히 피해 왔는데, 이 방식은 텍스트 출력에 어느 정도 유창함은 만들 수 있어도 질문을 해석하고 답하는 능력까지는 없어 보임
실제로 어떻게 작동하는지 설명하거나 Python 같은 장난감 엔진을 보여주는 단순한 블로그 글이나 교육 과정이 있는지 궁금함
지금까지 본 교육 자료는 플랫폼을 어떻게 쓰는지에 치우쳐 있고, 내부 작동 방식은 잘 다루지 않았음- Jay Alammar의 튜토리얼 흐름이 기본 신경망 수학부터 GPT-2까지 이어져서 가장 좋았음
특히 [0], [1], [2]가 좋음
[0] http://jalammar.github.io/illustrated-transformer/
[1] http://jalammar.github.io/illustrated-gpt2/
[2] https://jalammar.github.io/visualizing-neural-machine-transl... - 단단히 준비하고 보면 됨. 지금까지 본 것 중 압도적으로 좋은 자료는 이 영상임: https://www.youtube.com/watch?v=kCc8FmEb1nY
- Jay Alammar의 튜토리얼 흐름이 기본 신경망 수학부터 GPT-2까지 이어져서 가장 좋았음
-
흥미롭게도 현대 기계학습은 튜링 완전성을 요구하지 않음
그런데도 AGI 가능성을 고려하고 있으니, 튜링 완전성이 필요 없다는 결론이 나온다면 꽤 재미있을 듯함- 튜링 완전성은 필요해 보임
단순한 이유로, 나는 머릿속에서 튜링 완전한 코드의 실행을 따라갈 수 있기 때문임 - 토큰 추론 자체는 튜링 완전하지 않지만, 출력이 부작용을 만들 수 있다면, 예를 들어 다음 반복의 프롬프트를 수정할 수 있다면 전혀 다른 이야기가 됨
- 튜링 완전성은 필요해 보임
-
글은 훌륭했고, 각 구성 요소 설명이 명확하고 꽤 철저해서 읽기 좋았음
다만 실수로 “+ expand source”를 눌렀다가 그 놀라운 괴물을 보고 나니, ChatGPT가 “SQL은 대형 언어 모델 구현에 적합하지 않다”고 하는 데 공감하게 됨- 나도 눌렀고 다시 접는 방법을 찾지 못했음
-
“평범한 Unicode는 신경망과 잘 맞지 않는다”는 말은 사실이 아님. 예를 들어 ByT5를 보면 됨
글에서 “알파벳”이라고 부르는 것은 보통 어휘집이라고 하며, UTF-8 바이트를 어휘집으로 쓰면 토큰이 149186개가 아니라 256개가 됨
ByT5가 바로 그렇게 함- 요점은 전혀 안 된다는 게 아니라, 우리가 가진 다른 접근보다 잘 작동하지 않는다는 것임
시장에서 성능이 가장 좋은 모델들이 모두 토큰화를 쓰는 사실이 그 증거임
토큰화가 근본적으로는 해킹에 가깝고, 이상적으로는 언젠가 어떤 방식으로든 없애고 싶어 한다는 건 비밀이 아님 (https://twitter.com/karpathy/status/1657949234535211009)
원칙적으로는 바이트 수준 토큰화의 결함을 더 큰 모델과 더 큰 컨텍스트로 보완할 수 있지만, 실제로는 같은 수준의 지능을 가진 모델을 학습하는 데 훨씬 많은 자원이 들어감
물론 단어의 글자 수 세기처럼 토큰화가 오히려 지능에 해가 되는 특정 과제도 있음
- 요점은 전혀 안 된다는 게 아니라, 우리가 가진 다른 접근보다 잘 작동하지 않는다는 것임