Table of contents
Open Table of contents
들어가는 글
많은 기대를 걸고 있는 Upstage가 Solar-Open2를 출시했다는 소식을 접하고 가장 먼저 확인하고 싶었던 건 벤치마크 점수가 아니었습니다.
더 궁금했던 건 체감 한국어 성능이었습니다. 한국 파운데이션 모델을 표방하는 만큼, 실제로 써 봤을 때 문장이 얼마나 자연스러워졌는지 먼저 보고 싶었습니다. 잠깐 만져 본 인상은 꽤 좋았습니다. 이전 Solar 계열 모델보다 한국어 문장이 훨씬 부드러웠고, 답변 호흡도 더 안정되어 보였습니다.
그런데 쓰다 보니 문득 잊고 있던 이슈가 하나 떠올랐습니다.
Solar-Open-100B에서 보고된 토크나이저 이슈입니다. 한국어 회계 용어처럼 긴 어절이 토큰 하나로 들어갈 때, 모델이 그 토큰 의미를 안정되게 처리하지 못하는 현상이었습니다. 단순히 한국어를 못한다거나 회계 지식이 부족하다고 말하기에는 묘했습니다. 같은 단어를 조금 다르게 쓰면 답이 정상으로 돌아왔기 때문입니다.
자연스럽게 궁금해졌습니다.
이 현상은 아직 남아 있을까?
아니면 Solar-Open2에선 개선됐을까?
만약 어떤 모델에서는 발생하고 어떤 모델에서는 발생하지 않는다면, 차이는 어디서 오는 걸까?
처음에는 단순한 재현 실험으로 시작했습니다. 조사하다 보니 이 현상은 생각보다 흥미로웠습니다. 프롬프트 하나에서 생긴 예외나 일회성 hallucination이라기보다, 토크나이저 학습 코퍼스, 도메인 데이터 비율, BPE merge rank, long-tail token embedding이 한꺼번에 얽힌 현상처럼 보였습니다.
이번 글은 분석 과정을 정리한 리포트입니다.
처음 본 현상
출발점이 된 표현은 법인세효과가입니다.
한국어 회계 문서에서 흔히 쓰는 표현입니다. 다만 일상 한국어에서 자주 쓰는 말은 아닙니다. 더구나 법인세효과가처럼 띄어쓰기 없이 조사까지 붙은 형태는 일반 대화에서는 거의 나오지 않습니다.
문제는 이 표현이 문장 중간에 들어갈 때 나타납니다.
어떤 경우 법인세효과가 있을까?사람은 당연히 이렇게 이해합니다.
어떤 경우 [법인세 효과]가 있을까?보고된 Solar-Open-100B 답변은 법인세 문맥으로 가지 않았습니다.
에너지 절감 효과가 나타날 수 있는 대표적인 경우는 다음과 같습니다....질문은 법인세 효과를 묻고 있는데, 답변은 에너지 절감 효과 쪽으로 이동했습니다. 단순히 회계 지식이 부족해서라기엔 이상했습니다. 같은 질문을 한 칸 띄어 쓰면 답변이 정상으로 돌아오기 때문입니다.
어떤 경우 법인세 효과가 있을까?이 경우 답변은 법인세 문맥으로 돌아왔습니다. 법인세 과세, 손금산입, 세액공제, 이연법인세처럼 질문에서 기대한 회계·세무 항목을 중심으로 설명했습니다.
핵심은 단순한 오타나 회계 지식 부족이 아니었습니다. 특정 문자열이 어떤 tokenization path를 타느냐에 따라 법인세라는 단서를 제대로 활용하지 못하는 경로가 생긴다는 점이었습니다.
어디까지 재현되는가
먼저 Upstage가 공개한 Hugging Face 모델과 tokenizer repository를 전수 조사했습니다. 조사 대상은 upstage/models에 공개된 24개 repository였습니다.
확인한 문자열은 주로 다음과 같습니다.
' 법인세효과가'' 매도가능증권평가손익'앞 공백이 중요합니다. Byte-level BPE 계열 토크나이저에서는 문장 중간 단어가 보통 앞 공백을 포함한 형태로 토큰화되기 때문입니다. GPT 계열 토크나이저에서 word와 word가 다른 토큰인 구조와 같습니다.
전수 조사 결과 같은 tokenization 조건은 두 repository에서 확인됐습니다.
| 모델 또는 tokenizer | 법인세효과가 tokenization |
|---|---|
upstage/Solar-Open-100B | [195496] 단일 토큰 |
upstage/solar-pro3-tokenizer | [195496] 단일 토큰 |
upstage/solar-pro2-tokenizer | [' 법인세', '효과가'] |
SOLAR-10.7B 계열 | 여러 토큰으로 분해 |
| TinySolar 계열 | 여러 토큰으로 분해 |
Solar-Pro3 모델 weight는 공개되어 있지 않지만, solar-pro3-tokenizer는 공개되어 있었고 Solar-Open-100B와 동일한 단일 토큰을 가지고 있었습니다.
이 시점에서 확인할 질문은 더 뚜렷해졌습니다.
Solar-Pro3 tokenizer가 같은 token path를 가지고 있다면, 실제 Solar-Pro3 답변도 같은 방식으로 달라질까?
Console에서 직접 실험했습니다.
Upstage Console에서 재현
Console에서 가장 먼저 실행한 질문입니다.
어떤 경우 법인세효과가 있을까?공개된 Solar-Pro3 tokenizer 기준으로 이 문장은 다음처럼 들어갑니다.
[41170, 5577, 195496, 51490, 4126]= ['어떤', ' 경우', ' 법인세효과가', ' 있을까', '?']눈에 띄는 부분은 [195496]입니다. 토큰 하나가 법인세효과가 전체를 담당합니다.
Console 답변은 이렇게 시작했습니다.
잘 이해하지 못했어요!
“Kami”가 어떤 의미인지 정확히 알려주시면,해당 상황에 대해 자세히 설명해 드릴 수 있어요.반대로 대조군은 정상입니다.
어떤 경우 법인세 효과가 있을까?이 문장은 다음처럼 들어갑니다.
[41170, 5577, 9915, 23183, 51490, 4126]= ['어떤', ' 경우', ' 법인세', ' 효과가', ' 있을까', '?']이번에는 법인세와 효과가가 분리됩니다. 이 경로에서는 모델이 법인세 문맥에 맞는 답변을 출력합니다.
법인세 효과란?법인세(소득세) 효과는 기업이 회계상 발생시킨 이익에 대해 실제로 납부해야 할 세금이 회계상 비용과 어떻게 연결되는지를 의미합니다. 회계에서는 법인세 비용 · 법인세 부채 · 이연법인세 등이 기록되고, 세무에서는 과세소득, 세액 공제·감면, 세금 손실 이월 등이 적용됩니다. 이 두 영역이 일치하지 않을 때 세무‑회계 차이(temporary difference, permanent difference)가 발생하고, 그 차이가 재무제표에 법인세 효과를 만들어 줍니다.또 다른 대조군도 있습니다.
법인세효과가 발생하는 경우를 설명해줘.이 경우 법인세효과가가 문장 처음에 오기 때문에 앞 공백이 없습니다. 토크나이저는 다음처럼 분해합니다.
[7131, 32372, 11049, 23502, 49330, 38635, 4109]= ['법인세', '효과가', ' 발생하는', ' 경우를', ' 설명해', '줘', '.']이 경우도 답변은 정상입니다.
법인세 효과가 발생하는 경우(법인세 = 기업(법인)이 매년 발생하는 소득에 대해 납부해야 하는 소득세)
법인세 효과는 “세금이 회계·세무 양쪽에서 어떻게, 언제, 어느 정도 영향을 미치는가” 를 의미합니다.아래에서는 회계상(재무제표·법인세 회계)과 세무상(세법·세무조정) 두 관점에서 법인세 효과가 나타나는 대표적인 상황을 정리하고, 실제 기업·경영 의사결정에 어떤 의미를 갖는지 설명합니다.현상은 단순합니다.
앞 단어 뒤에 일반 공백이 붙은 경우-> ' 법인세효과가'-> [195496]-> 의미 이탈
문장 처음에 오거나 띄어 쓴 경우-> '법인세' + '효과가'-> 의미 유지공백 하나가 왜 이렇게 크게 작용할까
겉으로 보면 공백 하나 차이일 뿐입니다. 그러나 tokenizer 입장에서는 법인세효과가와 법인세효과가는 서로 다른 입력입니다.
Solar-Pro3 tokenizer는 custom byte-level BPE입니다. normalizer는 None이고, pre-tokenizer는 공백을 보존합니다. 일반 ASCII space는 뒤따르는 단어와 함께 span 하나로 들어갑니다.
실제 문장을 보겠습니다.
새로운 법률 해석에 따르면 법인세효과가pre-tokenizer는 마지막 부분을 이렇게 잡습니다.
| offset | original slice |
|---|---|
[14, 21] | 법인세효과가 |
문장 안 법인세효과가는 bare 법인세효과가가 아니라, 앞 공백을 포함한 법인세효과가입니다.
이제 BPE merge rank를 보면 왜 결과가 갈리는지 알 수 있습니다.
| pair | merge rank | 결과 |
|---|---|---|
법인세 + 효과 | 25,233 | 법인세효과 |
효과 + 가 | 28,020 | 효과가 |
법인세효과 + 가 | 191,144 | 법인세효과가 |
법인세 + 효과 | 45,360 | 법인세효과 |
법인세효과 + 가 | 없음 | 없음 |
법인세 + 효과가 | 없음 | 없음 |
문장 처음에 오는 bare 형태는 이렇게 분해됩니다.
법인세효과가-> '법인세' + '효과' + '가'-> '법인세' + '효과가'-> [7131, 32372]효과 + 가 merge가 먼저 일어나고, 그 뒤 법인세 + 효과가를 합치는 merge가 없기 때문입니다.
반면 선행 공백 형태는 이렇게 합쳐집니다.
' 법인세효과가'->' 법인세' + '효과' + '가'->' 법인세효과' + '가'->' 법인세효과가'-> [195496] 법인세 + 효과 rank가 효과 + 가보다 빠르기 때문에 법인세효과가 먼저 만들어집니다. 아주 늦은 merge인 rank 191,144에서 가까지 붙어 [195496]이 됩니다.
여기서 봐야 할 대목은 [195496]이 vocab 거의 끝에 있는 tail token이라는 사실입니다. vocab size는 196,608이고, 이 토큰 ID는 195,496입니다. merge rank도 191,144로 매우 늦습니다.
이 토큰은 자주 쓰이는 중심 토큰이라기보다, 큰 vocab 말단에 들어간 긴 도메인 어절에 가깝습니다.
Long-tail token이 민감해지는 이유
토크나이저 학습에서 어떤 문자열이 토큰 하나가 되었다고 해서, 모델이 그 의미를 충분히 배웠다는 뜻은 아닙니다.
BPE는 빈도를 바탕으로 움직입니다. 어떤 문자열 조각이 반복해서 등장하면 merge 후보가 됩니다. tokenizer 학습 corpus에서 merge될 만큼 등장했다는 사실과 모델 pretraining/SFT에서 해당 atomic token embedding이 충분히 학습되었다는 사실은 전혀 다른 문제입니다.
특히 법인세효과가 같은 토큰은 주의가 필요합니다.
법인세효과가이 토큰 하나가 법인세, 효과, 가를 모두 하나로 합칩니다. 모델 입장에서는 더 이상 법인세라는 잘 학습된 하위 토큰을 볼 수 없습니다. [195496]라는 embedding 하나만 볼 뿐입니다.
반대로 분해된 경로에서는 해석이 더 안정됩니다.
법인세효과가법인세 + 효과가법인세 + 효과 + 가이 경우 모델은 법인세라는 꽤 명확한 의미 단위를 볼 수 있습니다. 효과, 효과가도 대체로 충분히 학습된 토큰입니다.
이 현상은 이렇게 정리할 수 있습니다.
긴 도메인 어절이 atomic tail token으로 합쳐지면 token count는 줄어든다.
그 token embedding이 충분히 학습되지 않으면 semantic composition이 사라진다.
이번 사례도 이 흐름에 가깝습니다.
다른 모델은 어땠나
이 현상이 한국어 모델 전반에서 흔한 문제인지 보려면, 먼저 한국어 중심 모델과 비교해야 합니다. 네이버 HyperCLOVAX SEED, LG AI Research EXAONE, SKT A.X-K1, KORMo를 같은 기준으로 확인했습니다. Gemma는 글로벌 대조군으로만 마지막에 두었습니다.
결과부터 말하면 이 모델들은 모두 법인세효과가를 Solar-Pro3처럼 토큰 하나로 만들지 않았습니다.
Solar-Pro3: 법인세효과가-> [' 법인세효과가']
HyperCLOVAX SEED 1.5B: 법인세효과가-> ids [48765, 67361, 42529, 103285, 20565]-> readable pieces [' 법', '인', '세', '효과', '가']-> decoded_all ' 법인세효과가'
EXAONE 3.5: 법인세효과가-> [' 법인세', '효과', '가']
A.X-K1: 법인세효과가-> [' 법인세', '효과가']
KORMo: 법인세효과가-> [' 법', '인', '세', '효과', '가']tokenizer vocab을 훑어보면 차이가 더 분명해집니다.
| tokenizer | 법인세효과가 단일 토큰 여부 | token count | 주의 후보 전체 | score ≥ 8 후보 | 도메인 힌트 |
|---|---|---|---|---|---|
| Solar-Pro3 | 예 | 1 | 3,819 | 114 | 806 |
| HyperCLOVAX SEED 1.5B | 아니오 | 5 | 80 | 1 | 1 |
| EXAONE 3.5 7.8B | 아니오 | 3 | 197 | 0 | 0 |
| A.X-K1 | 아니오 | 2 | 1,855 | 4 | 14 |
| KORMo 10B SFT | 아니오 | 5 | 2,197 | 2 | 6 |
| Gemma 3 4B | 아니오 | 6 | 8 | 0 | 1 |
이 비교는 모델 생성 품질 평가가 아닙니다. tokenizer 구조에서 주의해야 할 지점을 본 결과입니다. 주의 후보 전체는 휴리스틱 스캔1 결과일 뿐입니다. A.X-K1이나 KORMo처럼 후보 수가 어느 정도 있어도, 곧 이번 현상과 같은 생성 실패를 뜻하지는 않습니다.
한 가지는 분명합니다. 이번 현상은 한국어를 하는 모델이면 모두 같은 방식으로 겪는 문제가 아닙니다. 적어도 확인한 한국어 중심 공개 모델에서는 법인세효과가가 Solar-Pro3처럼 atomic tail token 하나로 들어가지 않았습니다. Solar-Pro3 tokenizer가 이 표현을 훨씬 더 공격적으로 합친 쪽에 가깝습니다.
Pro2에서 Pro3로 넘어가며 무엇이 바뀌었나
처음에는 Solar-Pro2와 Solar-Pro3가 같은 계열이니 tokenizer도 비슷할 것으로 생각했습니다. 실제 artifact를 비교해 보니 그렇지 않았습니다.
둘 다 vocab size는 196,608입니다. 내부는 크게 다릅니다.
| 비교 항목 | Pro2 vs Pro3 |
|---|---|
| raw token overlap | 55.3% |
| same raw token + same id | 791개 |
| decoded token overlap | 55.3% |
| same decoded token + same id | 806개 |
| merge overlap | 30.7% |
| same merge + same rank | 2개 |
vocab size만 같을 뿐, 사실상 다른 tokenizer라고 봐야 합니다.
반면 Pro3 tokenizer와 Solar-Open-100B tokenizer는 완전히 동일했습니다.
| 비교 항목 | Pro3 vs Solar-Open-100B |
|---|---|
| raw token overlap | 100% |
| same raw token + same id | 196,608 |
| merge overlap | 100% |
| same merge + same rank | 192,256 |
해당 phrase에서도 차이가 분명합니다.
Pro2:어떤 경우 법인세효과가 있을까?-> ['어떤', ' 경우', ' 법인세', '효과가', ' 있을까', '?']
Pro3:어떤 경우 법인세효과가 있을까?-> ['어떤', ' 경우', ' 법인세효과가', ' 있을까', '?']Pro2에서는 법인세효과가가 법인세 + 효과가로 남습니다. Pro3에서는 [195496] 하나로 합쳐집니다.
금융/회계 표현에서도 같은 경향이 보입니다.
Pro2: 매도가능증권평가손익-> [' 매', '도가', '능', '증권', '평가', '손익']
Pro3: 매도가능증권평가손익-> [' 매도가능증권평가손익']Pro3는 도메인 복합어를 훨씬 많이 단일 토큰화합니다.
코퍼스 비율이 남기는 영향
Upstage가 공개한 Solar Open Technical Report에서 먼저 볼 대목은 tokenizer를 별도로 설계했다는 점입니다. Solar Open tokenizer는 vocab size 196,608의 custom byte-level BPE이며, 학습 corpus에서 한국어와 target domain을 의도적으로 과대표집했다고 설명합니다. tokenizer 학습 데이터 구성표에서 Korean은 22%, Domain Specific 문서, 즉 Finance·Legal·Medical 문서는 4%입니다.
이 4%를 pretraining 비율처럼 읽으면 안 됩니다. tokenizer 학습 corpus는 vocabulary를 만들기 위한 데이터이고, pretraining corpus는 모델 본체를 학습하기 위한 데이터입니다. 다만 비교해 볼 지점은 있습니다. 리포트가 밝힌 pretraining 전체 19.7T tokens 가운데 domain-specific Finance/Medical/Legal 데이터는 0.4T tokens, 약 2%입니다. 목적이 다른 두 표를 단순 비교할 수는 없지만, tokenizer 학습 단계에서 domain-specific 문서가 더 강하게 반영된 설계였다는 점은 확인할 수 있습니다.
이 사실만으로 법인세효과가 문제의 원인을 확정할 수는 없습니다. 다만 공개된 tokenizer 구성, Pro2/Pro3 vocab 차이, Solar-Pro3 재현 결과를 함께 보면 도메인 문서 비율이 긴 한국어 전문어절 token 형성에 영향을 주었을 가능성을 검토할 이유는 충분합니다.
한국어 corpus를 만들 때도 비슷한 문제를 자주 마주칩니다. 개인 경험을 조금 덧붙이면, 보통 한국어 모델을 만들 때 AI Hub 자료를 먼저 살펴보게 됩니다. 자료 규모가 크고 출처와 라이선스가 꽤 명확하며, 분야별로 정리되어 있어 필요한 코퍼스 후보를 찾기 좋습니다. 문제는 정부 주도로 진행한 프로젝트다 보니 정부 문서, 법률 문서, 정책 자료, 국회 회의록, 공공기관 보고서, 전문 분야 문서가 과대표집되기 쉽다는 점입니다. 전체를 큰 덩어리로 가져와 쓰면 일반 한국어 분포와는 다른 방향으로 코퍼스가 기울 수 있습니다.
금융, 법률, 회계 자료에서는 이런 기울기가 더 쉽게 드러납니다. 이 분야 문서는 일반 한국어 문장보다 긴 명사구가 많고, 조사까지 붙은 전문 표현이 반복됩니다.
법인세비용차감전순이익과기타포괄손익누계액으로당기손익인식금융자산으로상각후원가측정금융자산으로독점규제및공정거래에관한법률상의BPE 입장에선 이런 문자열이 매력적입니다. 자주 반복되고, 긴 span을 하나로 줄이면 token count가 줄어듭니다. 196K처럼 큰 vocab을 쓰면 이런 tail phrase를 많이 담을 수 있습니다.
문제는 압축이 곧 학습 가능성을 보장하지 않는다는 데 있습니다. 토크나이저는 코퍼스를 보고 “어떤 단위로 볼 것인가”를 정합니다. 언어 비율만큼이나 각 언어 안 도메인 비율도 중요합니다. 일반 한국어에선 거의 쓰이지 않는 긴 전문 어절이 tail token으로 들어가면, 모델은 법인세와 효과를 따로 보지 못하고 [195496] 같은 단일 embedding만 보게 됩니다.
여기서 조심할 점은 분명합니다. 공개 정보만으로 Upstage가 특정 AI Hub 데이터셋을 사용했다고 단정할 수는 없습니다. 이 글에서 말하는 것은 특정 데이터셋 지목이 아니라, 일반 한국어 분포보다 도메인 문서 비율이 높게 반영된 tokenizer 학습이 어떤 종류의 tail token을 만들 수 있는지에 대한 분석입니다.
현재 가장 그럴듯한 설명
현재까지 결과를 하나로 묶으면 이렇게 볼 수 있습니다.
1. Pro3/Solar-Open 계열 tokenizer는 Pro2보다 한국어 도메인 phrase를 훨씬 많이 합친다.2. 그 결과 ' 법인세효과가' 같은 긴 선행 공백 어절이 high-rank tail token이 되었다.3. 이 token은 vocab에는 있지만, 현재 API 출력만 보면 모델이 이를 `법인세 + 효과가`의 조합 의미처럼 안정되게 사용하지 못하는 듯하다.4. 앞 단어 뒤에 이어질 때는 이 token path를 타고, 문장 처음에 오거나 띄어 쓰면 분해된 subtoken path를 탄다.5. 공백 하나로 답변의 의미가 크게 바뀐다.여기서 1, 2, 4, 5는 공개 tokenizer와 API 출력으로 직접 확인한 내용입니다. 3은 같은 결과에서 도출한 해석입니다. 실제 embedding norm, 학습 빈도, hidden state를 직접 보지 않았으므로 원인 확정보다는 현재 증거와 가장 잘 맞는 설명에 가깝습니다.
이 현상을 일반 hallucination으로만 부르면 핵심을 놓칩니다. 모델은 법인세를 모르는 상태가 아닙니다. 같은 모델이 법인세 효과가에는 정상으로 답합니다.
더 정확한 이름은 tokenizer-model alignment issue에 가깝습니다.
tokenizer는 압축을 위해 긴 도메인 어절을 atomic token으로 만들었다.현재 API 출력만 보면 model은 그 atomic token을 충분히 안정된 의미 단위로 사용하지 못하는 듯하다.그렇다면 Solar-Open2에서는 어땠나
다시 출발점으로 돌아가 보겠습니다. 이 글을 쓰게 된 계기는 Solar-Open2였습니다. Solar-Open2는 독자 AI 파운데이션 모델 사업에 참여한 Upstage가 개발한 최신 모델입니다. 실험은 timelyai.io를 사용했습니다.
Solar-Open2에 같은 질문을 던져봤습니다.
어떤 경우 법인세효과가 있을까?응답은 Solar-Pro3처럼 Kami 같은 전혀 무관한 단어로 이동하지는 않았지만, 여전히 법인세 효과를 성명하진 못 했습니다. 답변은 이렇게 시작했습니다.
좋은 질문입니다. "세액효과"라는 표현이 약간 모호할 수 있어,일반적으로 세금 관련 효과(조세효과)가 발생하는 주요 상황들을 중심으로 설명드리겠습니다.모델은 법인세효과가를 곧바로 법인세 효과로 처리하기보다, 세액효과 또는 조세효과라는 더 넓은 말로 바꾸어 처리했습니다. 이후 답변은 소득세, 양도소득세, 부가가치세, 법인세, 국제조세, 이연법인세 등을 설명했습니다. 세금 문맥 안에는 남아 있었지만, 질문의 초점인 법인세 효과를 설명했다고 보기는 어려웠습니다.
대조 질문은 다르게 동작했습니다.
어떤 경우 법인세 효과가 있을까?이 경우 Solar-Open2는 법인세 과세효과, 손금산입, 세액공제, 합병·분할, 이연법인세, 국제조세, 자금조달 관련 tax shield까지 정상적인 법인세 문맥으로 자세히 답했습니다.
Solar-Open2 결과는 중간 정도로 볼 수 있습니다.
Solar-Pro3: 법인세효과가 -> Kami / 효과성 / 누적효과 등으로 강하게 어긋남
Solar-Open2: 법인세효과가 -> 세액효과 / 조세효과로 넓게 바꾸어 해석 법인세 효과가 -> 정확한 법인세 문맥으로 답변Solar-Open2에서도 해당 현상이 완전히 사라졌다고 말하기는 어렵습니다. Solar-Pro3처럼 의미가 전혀 다른 방향으로 크게 벗어나지는 않고, 세금이라는 큰 범주 안에서 질문을 넓게 다시 해석하는 정도에 가깝습니다.
이 차이는 오히려 원인 분석과 잘 맞습니다. 현상을 tokenizer path 하나만으로 설명할 수는 없습니다. tokenizer가 민감한 경로를 만들더라도, 모델 본체가 그 token을 얼마나 잘 학습했는지, instruction tuning이 그 표현을 어떻게 다뤘는지에 따라 출력 양상은 달라질 수 있습니다. Solar-Open2는 세금이라는 상위 문맥은 유지했지만, 법인세효과가를 정확한 법인세 표현으로 바로 처리했다고 보기는 어려웠습니다.
어떻게 해결할 수 있을까
먼저 구분할 대목이 있습니다.
이번 실험에서 실제로 확인한 내용은 해당 token path를 피하면 답변이 안정화된다는 점입니다. 반대로 모델 weight나 서버 tokenizer를 직접 수정하는 방법은 제가 실험으로 검증하지 못했습니다. 원인 분석에서 도출한 구조 차원 해결 제안입니다.
검증한 내용은 다음입니다.
어떤 경우 법인세효과가 있을까?-> [41170, 5577, 195496, 51490, 4126]-> Console/API에서 의미가 달라짐
어떤 경우 법인세 효과가 있을까?-> [41170, 5577, 9915, 23183, 51490, 4126]-> 정상 답변
법인세효과가 발생하는 경우를 설명해줘.-> [7131, 32372, ...]-> 정상 답변
어떤 경우법인세효과가 있을까?-> [..., 4294, 7131, 32372, ...]-> 정상 답변또 NBSP(non-breaking space: U+00A0)를 이용한 대조 실험에서도 [195496]를 피했다는 점을 확인했습니다.
어떤 경우 법인세효과가 있을까?-> [41170, 5577, 4905, 7131, 32372, 51490, 4126]운영 단계에서 당장 쓸 수 있는 완화책은 띄어쓰기를 명확히 하거나, 줄 바꿈 등으로 [195496] 경로를 피하는 방식입니다. 이는 입력 단계 완화책입니다. 하지만 특정 문자열 하나만 고치는 방식으로는 같은 구조의 다른 token을 충분히 다루기 어렵습니다.
아래 해결책은 검증 여부를 나누어 봐야 합니다.
1. 검증된 완화책: 안전한 token path로 보내기
가장 당장 쓸 수 있는 대응은 모델 호출 전에 입력을 검사하고, 해당 token path를 피하도록 바꾸는 방식입니다.
예를 들어:
법인세효과가 -> 법인세 효과가또는:
어떤 경우 법인세효과가 있을까?-> 어떤 경우 법인세효과가 있을까?이 방식은 실제 API/Console 실험에서 정상 답변으로 돌아오는 점을 확인했습니다. 사용자 입력을 바꾸는 방식이므로, 제품에 적용하려면 원문 보존과 표시 문자열을 어떻게 처리할지 별도로 설계해야 합니다.
2. 일부 확인 가능한 제안: inference-time merge blocklist
입력 문자열을 직접 바꾸는 대신, tokenizer의 BPE merge 과정에서 민감한 merge를 관리하는 방법을 생각할 수 있습니다.
예를 들어 이런 token을 우선 검토 대상으로 삼을 수 있습니다.
decoded token이 ASCII space로 시작한글 포함길이 5자 이상high-rank tail tokenbare counterpart 없음도메인 긴 명사구 또는 조사 결합형이 방향은 tokenizer trace로는 타당성을 확인할 수 있습니다. 실제로 [195496]는 다음 late merge 때문에 생깁니다.
법인세효과 + 가 -> 법인세효과가이 merge를 막으면 법인세효과가는 더 작은 하위 token sequence로 남게 됩니다. 앞서 확인했듯 하위 token sequence로 들어간 질문은 정상 답변을 냅니다.
이를 실제 Upstage 서버 tokenizer에 적용해 보지는 못했습니다. 이 항목은 “검증된 해결책”이라기보다, 현재 증거로 보아 provider-side에서 가장 직접 쓸 수 있는 완화책 후보입니다.
3. 검증하지 못한 구조 차원 제안: compositional embedding repair
모델 제공자라면 민감한 token embedding을 보정하는 방법도 검토할 수 있습니다.
[195496] = " 법인세효과가"safe path = [" 법인세", "효과가"] 또는 [" 법인세", "효과", "가"]그다음 [195496]의 embedding을 safe path embedding 조합에 가깝게 보정합니다.
이 방법은 이론으로는 원인에 잘 맞습니다. 관찰된 현상이 [195496]가 법인세와 효과가의 조합 의미를 충분히 보존하지 못하는 데서 나오기 때문입니다. 이 방법은 모델 embedding matrix에 접근해야 검증할 수 있습니다. 공개 API만으로는 확인할 수 없습니다.
4. 검증하지 못한 구조 차원 제안: tokenization-invariance distillation
가장 중요한 대목은 tokenization path가 달라도 같은 의미를 내도록 학습하는 일입니다.
어떤 경우 법인세효과가 있을까?어떤 경우 법인세 효과가 있을까?법인세효과가 발생하는 경우를 설명해줘.이 세 질문은 같은 의미를 가져야 합니다. 모델 next-token distribution 또는 최종 답변이 서로 비슷해지도록 SFT/KL distillation을 걸 수 있습니다.
이 역시 공개 API만으로 검증한 해결책은 아닙니다. 현상의 성격상 단순히 회계 corpus를 더 넣는 방법보다 더 맞는 처방일 수 있습니다. 관찰한 현상은 지식 부족이라기보다 tokenization path 간 의미 불변성 부족에 가깝기 때문입니다.
5. 장기 제안: 다음 tokenizer의 학습 정책 변경
장기 과제로는 tokenizer 학습 단계에서 이런 후보를 관리해야 합니다.
특히 다음 형태를 조심해야 합니다.
' X'는 단일 token인데'X'는 단일 token이 아니거나 안정된 짧은 sequence가 아닌 경우이런 token을 orphan leading-space token이라고 부를 수 있습니다. 한국어에서는 더 민감합니다. 조사와 어미가 붙은 긴 어절이 많고, 공시/법률 문서에는 같은 template 문구가 반복되기 때문입니다.
merge 후보를 고를 때 raw frequency만 보면 안 됩니다. 문서 다양성, source 다양성, 문맥 entropy, suffix 다양성, domain 편중을 같이 봐야 합니다.
마무리
처음에는 작은 띄어쓰기 문제처럼 보였습니다. 법인세효과가와 법인세효과가의 차이는 공백 하나밖에 없습니다. 우리가 읽기에는 거의 같은 질문입니다.
토큰화 결과는 꽤 달랐습니다. 한쪽은 법인세와 효과가라는 익숙한 단위의 token sequence가 되었고, 다른 한쪽은 법인세효과가라는 token 하나가 되었습니다. 작은 공백 하나가 토큰화 경로를 바꾼 셈입니다.
이 사례가 흥미로웠던 이유는 모델이 법인세를 모르는 것이 아니었기 때문입니다. 지식 자체보다, 그 지식에 닿는 입력 경로가 문제였을 가능성이 더 커 보였습니다.
토크나이저를 이야기할 때는 보통 효율을 먼저 봅니다. 같은 문장을 더 적은 token으로 표현하면 context를 아끼고 추론 비용도 줄어듭니다. 그러나 압축은 공짜가 아닙니다. 어떤 단어를 하나로 합치고, 어떤 단어를 조합 가능한 단위로 남길지는 생각보다 중요한 선택입니다.
법인세효과가는 그 선택의 결과를 들여다보게 만든 작은 단서였습니다. Pro2에서는 법인세 + 효과가로 남아 있던 표현이 Pro3에서는 token 하나로 합쳐졌습니다. token count는 줄었지만, 의미를 조합할 수 있는 경로도 함께 줄었습니다.
저는 이 현상을 단일 버그로만 보기보다는, tokenizer corpus와 model corpus 사이의 비율 문제, compression efficiency와 learnability 사이의 균형 문제로 보는 편이 더 자연스럽다고 느꼈습니다. 데이터의 양과 출처만큼, 그 데이터가 tokenizer에서 어떤 단위로 남는지도 함께 봐야 합니다.
토크나이저는 눈에 잘 띄지 않습니다. 하지만 모델이 문장을 처음 만나는 방식은 토크나이저가 정합니다. 이번에는 그 차이가 법인세효과가라는 작은 조각에서 드러났습니다.
Footnotes
-
이 글에서 사용한
주의 후보스캔 기준은 다음과 같습니다.↩decoded token이 ASCII space로 시작한다.공백을 뗀 뒤 한글을 포함하고 길이가 5자 이상이다.bare 형태가 없거나, bare 형태가 여러 token으로 분해된다.vocab 뒤쪽에 있는 tail token이면 점수를 더한다.금융/법률/회계 관련 힌트 단어를 포함하면 점수를 더한다.