Adaptation을 진행할 언어(Ex. 한국어)에 대해, 기존의 byte 단위의 토큰을 사용하는 것이 아니라, 새로 BPE를 학습시켜 만들어진 토큰 단위를 사용하도록 Tokenizer를 바꾸어 주는 것
# As-Is
old_tokenizer(’애국가’) → ["<0xE6>", "<0xE7>", "<0xE8>", "<0xE9>", ...]
# To-Be
new_tokenizer(’애국가’) → ["애국", "##가"]
But, new_tokenizer를 그대로 사용할 수는 없음
그렇기 때문에, 한국어 Tokenizer Expansion은 크게 세 가지 단계를 거치게 됨
추가적인 학습 양은 기존 LLM의 pretraining corpus token size를 참고하여 정하는 hyperparameter가 됨
#### original tokenizer (llama_tokenizer)
```jsonc
{ ... // Some configurations
"model": {
"type": "BPE",
"dropout": null,
"unk_token": "<unk>",
"continuing_subword_prefix": null,
"end_of_word_suffix": null,
"fuse_unk": true,
"byte_fallback": true,
"vocab": {
"<unk>": 0,
"<s>": 1,
"</s>": 2,
"<0x00>": 3,
"<0x01>": 4,
"<0x02>": 5,
"<0x03>": 6,
"<0x04>": 7,
"<0x05>": 8,
"<0x06>": 9,
"<0x07>": 10,
"<0x08>": 11,
"<0x09>": 12,
"<0x0A>": 13,
"<0x0B>": 14,
"<0x0C>": 15,
"<0x0D>": 16,
"<0x0E>": 17,
"<0x0F>": 18,
"<0x10>": 19,
"<0x11>": 20,
"<0x12>": 21,
"<0x13>": 22,
"<0x14>": 23,
"<0x15>": 24,
"<0x16>": 25,
"<0x17>": 26,
"<0x18>": 27,
"<0x19>": 28,
... // all byte-pairs' input ids
"<0xFC>": 255,
"<0xFD>": 256,
"<0xFE>": 257,
"<0xFF>": 258,
"▁▁": 259,
"▁▁": 259,
"▁t": 260,
"er": 261,
"in": 262,
"▁a": 263,
"en": 264,
"on": 265,
"▁th": 266,
"es": 267,
"▁▁▁▁": 268,
"▁s": 269,
"▁d": 270,
"at": 271,
"or": 272,
"an": 273,
"▁c": 274,
"is": 275,
"re": 276,
"it": 277,
"▁the": 278,
"ar": 279,
... // all characters' (except unigram) input ids <-- are only tokens made of other 2 tokens
"▁nev": 29865,
"Daniel": 29866,
"▁tends": 29867,
"▁compagnie": 29868,
"▁livres": 29869,
"lub": 29870,
"▁": 29871,
"e": 29872,
"t": 29873,
"a": 29874,
... //all unigram characters' input ids
"边": 31993,
"还": 31994,
"黃": 31995,
"왕": 31996,
"收": 31997,
"弘": 31998,
"给": 31999
},
"merges": [
"▁ t",
"e r",
"i n",
"▁ a",
"e n",
"o n",
"▁t h",
"▁ th",
"e s",
"▁ s",
"▁ d",
"a t",
"o r",
"a n",
"▁ c",
"i s",
"r e",
"i t",
"▁t he",
"▁th e",
"▁ the",
"a r",
... // all merge rules of input_ids(c.f. [..., "▁techn ical", "▁techni cal", ...])
"lu b",
"l ub",
"▁ ▁",
"▁▁ ▁▁",
"▁▁▁ ▁",
"▁ ▁▁▁",
... // all merge rules of normalizers
"▁▁▁▁▁▁▁▁▁ ▁▁▁▁▁▁",
"▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁▁",
"▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁",
"▁ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁"
]
}
}
```json
#### reference tokenizer (beomi_tokenizer)
```jsonc
{ ... // Some configurations
"model": {
"type": "BPE",
"dropout": null,
"unk_token": "<unk>",
"continuing_subword_prefix": null,
"end_of_word_suffix": null,
"fuse_unk": true,
"byte_fallback": true,
"vocab": {
"<unk>": 0,
"<s>": 1,
"</s>": 2,
"<0x00>": 3,
"<0x01>": 4,
"<0x02>": 5,
"<0x03>": 6,
"<0x04>": 7,
"<0x05>": 8,
"<0x06>": 9,
"<0x07>": 10,
"<0x08>": 11,
"<0x09>": 12,
"<0x0A>": 13,
"<0x0B>": 14,
"<0x0C>": 15,
"<0x0D>": 16,
"<0x0E>": 17,
"<0x0F>": 18,
"<0x10>": 19,
"<0x11>": 20,
"<0x12>": 21,
"<0x13>": 22,
"<0x14>": 23,
"<0x15>": 24,
"<0x16>": 25,
"<0x17>": 26,
"<0x18>": 27,
"<0x19>": 28,
... // same as llama till 32000
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
... // same as llama till 32000
"收": 31997,
"弘": 31998,
"给": 31999,
"<pad>": 32000,
"▁이": 32001,
"▁대": 32002,
"▁사": 32003,
"▁그": 32004,
"▁있": 32005,
"으로": 32006,
"▁지": 32007,
"▁아": 32008,
... // added <pad>(but unused), korean token (except unigram)
"▁가이드": 44808,
"▁지수는": 44809,
"▁내수": 44810,
"▁언론의": 44811,
"▁어느나라": 44812,
"으": 44813,
"게": 44814,
"있": 44815,
... // added unigram korean token
"앎": 46327,
"챘": 46328,
"컹": 46329,
"묽": 46330,
"<|sep|>": 46331,
"<|endoftext|>": 46332,
"<|acc|>": 46333,
"<|rrn|>": 46334,
"<|tel|>": 46335
},
"merges": [
"▁ t",
"e r",
"i n",
"▁ a",
"e n",
"o n",
"▁t h",
"▁ th",
... // same as llama till normalizers
"▁▁▁▁▁▁▁▁▁ ▁▁▁▁▁▁",
"▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁▁",
"▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁",
"▁ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁"
"▁ 이",
"▁ 대",
"▁ 사",
"▁ 그",
"▁ 있",
"으 로",
"▁ 지",
"▁ 아",
"니 다",
"▁ 정",
"▁ 하",
"에 서",
"▁ 기",
"▁ 전",
"▁ 가",
"▁ 한",
"▁ 수",
"했 다",
"▁ 나",
... // all merge rules of korean input_ids
"▁리 모델링",
"▁가 이드",
"▁지 수는",
"▁지수 는",
"▁ 지수는",
"▁내 수",
"▁언론 의",
"▁어느 나라"
]
}
}
- 실제 토큰과 그에 해당하는 `input_ids` 는 `vocab`에, `BPE merge rule`은 `merges`에 저장하는 구조이므로, 아래와 같은 작업을 할 수 있음
- 새로 학습한 tokenizer의 vocab을 순회,
- if 기존 tokenizer의 vocab에 없는 토큰을 발견,
- 기존 tokenizer의 vocab에 이어서 토큰을 추가
- 기존 tokenizer의 merges에 이 토큰에 해당하는 merge rule을 찾아서 extend
### 3-0. TL; DR
```markdown
1. 기준이 될 Tokenizer(Ex. `llama_tokenizer`)의 `토큰 정보`(tokenizer.json)를 load
2. 추가할 토큰을 학습한 Tokenizer(Ex. `new_tokenizer`)의 `토큰 정보`(tokenizer.json)를 load
3. 2번의 토큰들을 하나씩 iterate하면서, if 해당 토큰이 1번에 없다면, 토큰 추가