Files
frankenstallm/source/tokenizer/convert_sp_to_hf.py

135 lines
4.6 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""
tokenizer/convert_sp_to_hf.py SentencePiece 모델을 HuggingFace tokenizers.json으로 변환.
prepare.py의 load_tokenizer() Tokenizer.from_file() 사용하므로
SentencePiece .model을 직접 읽지 못함 HF tokenizers 포맷으로 변환 필요.
Usage:
python tokenizer/convert_sp_to_hf.py \
--model tokenizer/korean_sp/tokenizer.model \
--output tokenizer/korean_sp/tokenizer.json
Requirements:
pip install --break-system-packages sentencepiece tokenizers transformers
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
def convert(model_path: Path, output_path: Path) -> None:
"""SentencePiece Unigram 모델을 HuggingFace tokenizers.json으로 변환."""
# 방법 1: transformers의 XLNetTokenizer 계열 변환기 활용
# (더 완전한 변환, special token 처리 포함)
try:
from transformers.convert_slow_tokenizer import SpmConverter
from tokenizers import Tokenizer
from tokenizers.models import Unigram
print(f"변환 중: {model_path}{output_path}")
# SpmConverter는 tokenizers 라이브러리의 Unigram 모델로 변환
# sentencepiece 모델 로드
import sentencepiece as spm
sp = spm.SentencePieceProcessor()
sp.load(str(model_path))
vocab_size = sp.vocab_size()
print(f"어휘 크기: {vocab_size:,}")
# Unigram vocab 추출: (piece, score) 목록
vocab: list[tuple[str, float]] = []
for i in range(vocab_size):
piece = sp.id_to_piece(i)
score = sp.get_score(i)
vocab.append((piece, score))
# HuggingFace Unigram 모델 생성
# unk_id 확인
unk_id = sp.unk_id()
tokenizer = Tokenizer(Unigram(vocab, unk_id=unk_id))
# Pre-tokenizer: Metaspace (SentencePiece 방식 — 공백을 ▁로 변환)
# tokenizers >= 0.14: add_prefix_space → prepend_scheme='always'
from tokenizers.pre_tokenizers import Metaspace
tokenizer.pre_tokenizer = Metaspace(replacement="", prepend_scheme="always")
# Decoder: Metaspace (역변환)
from tokenizers.decoders import Metaspace as MetaspaceDecoder
tokenizer.decoder = MetaspaceDecoder(replacement="", prepend_scheme="always")
# Special token 설정 (SP 모델과 동일한 ID)
from tokenizers import AddedToken
pad_id = sp.pad_id() if sp.pad_id() >= 0 else 0
bos_id = sp.bos_id() if sp.bos_id() >= 0 else 1
eos_id = sp.eos_id() if sp.eos_id() >= 0 else 2
tokenizer.add_special_tokens([
AddedToken("<pad>", special=True),
AddedToken("<s>", special=True),
AddedToken("</s>", special=True),
AddedToken("<unk>", special=True),
])
output_path.parent.mkdir(parents=True, exist_ok=True)
tokenizer.save(str(output_path))
# 저장 후 검증
loaded = Tokenizer.from_file(str(output_path))
test_text = "안녕하세요, 한국어 언어 모델입니다."
encoded = loaded.encode(test_text)
print(f"\n검증 통과:")
print(f" 테스트 문자: {test_text!r}")
print(f" 토큰 수: {len(encoded.ids)}")
print(f" 토큰: {encoded.tokens[:15]}{'...' if len(encoded.tokens) > 15 else ''}")
print(f"\n저장 완료: {output_path}")
except ImportError as e:
print(f"ERROR: 필요한 라이브러리 없음: {e}", file=sys.stderr)
print(" pip install --break-system-packages sentencepiece tokenizers transformers", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"ERROR: 변환 실패: {e}", file=sys.stderr)
import traceback
traceback.print_exc()
sys.exit(1)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="SentencePiece 모델 → HuggingFace tokenizers.json 변환",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--model",
type=Path,
required=True,
help="SentencePiece .model 파일 경로",
)
parser.add_argument(
"--output",
type=Path,
required=True,
help="출력 tokenizers.json 경로",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
if not args.model.exists():
print(f"ERROR: 모델 파일 없음: {args.model}", file=sys.stderr)
sys.exit(1)
convert(args.model, args.output)
if __name__ == "__main__":
main()