Service
GitHub - brody-0125/dart_sentencepiece_tokenizer: A lightweight, pure Dart implementation of SentencePiece tokenizer. Supports BPE (Gemma) and Unigram (Llama) algorithms.
brody-0125
2026.02.08
·GitHub·by 권준호#BPE#Dart#SentencePiece#Tokenizer#Unigram
핵심 포인트
- 1`dart_sentencepiece_tokenizer`는 SentencePiece 알고리즘(BPE, Unigram)을 순수 Dart로 구현하여 경량화 및 메모리 효율성을 극대화한 토크나이저 라이브러리입니다.
- 2이 라이브러리는 인코딩/디코딩, 배치 처리, 스트리밍 등 LLM 토크나이징에 필요한 포괄적인 API와 HuggingFace 호환성을 제공하며, 최적화된 BPE 병합으로 높은 성능을 자랑합니다.
- 3웹, 서버, Flutter 등 다양한 Dart 환경에서 활용 가능하며, ONNX Runtime 통합 및 초당 50만 토큰 이상의 처리량을 통해 LLM 애플리케이션 개발을 효과적으로 지원합니다.
dart_sentencepiece_tokenizer는 SentencePiece 토크나이저의 경량의 순수 Dart 구현체입니다. 이는 Dart 기반의 애플리케이션(Flutter, 서버, CLI, 웹)에서 어떠한 외부 의존성 없이 작동하도록 설계되었습니다.
핵심 기능 및 방법론:
- 지원 알고리즘:
- BPE (Byte Pair Encoding): Gemma 모델에서 사용되는 알고리즘을 지원합니다. 병합(merge) 연산을 O(1) 시간 복잡도로 수행하기 위해 연결 리스트(linked list)와 병합 캐싱(merge caching)을 활용하는 최적화된 방식을 구현하여 성능을 향상시켰습니다.
- Unigram: Llama 모델에서 사용되는 알고리즘을 지원합니다.
- 메모리 효율성:
Int32List및Uint8List와 같은 Dart의 Typed Arrays를 적극적으로 사용하여 메모리 사용량을 50-70% 절감했습니다. 특히, 토큰 ID(ids)는Int32List(토큰당 4바이트), 토큰 타입 ID(typeIds), 어텐션 마스크(attentionMask), 스페셜 토큰 마스크(specialTokensMask)는Uint8List(토큰당 1바이트)로 저장됩니다.
- 포괄적인 API:
- 인코딩 (Encoding):
- 단일 텍스트 인코딩:
encode(text)는Encoding객체를 반환하며, 여기에는 토큰 문자열(tokens), 토큰 ID(ids), 어텐션 마스크(attentionMask), 토큰 타입 ID(typeIds), 문자 오프셋(offsets), 단어 인덱스(wordIds), 시퀀스 인덱스(sequenceIds) 등이 포함됩니다. - 쌍 인코딩:
encodePair(textA, textB)를 통해 질의응답(QA)이나 문장 유사도 태스크를 위한 텍스트 쌍을 인코딩할 수 있으며, 이때typeIds와sequenceIds가 생성됩니다. - 배치 인코딩:
encodeBatch(texts)는 순차적으로,encodeBatchParallel(texts)는 DartIsolate를 활용하여 병렬로 배치 인코딩을 수행하여 대규모 데이터 처리 효율성을 높였습니다. - 최대 입력 길이는 500,000자로 제한하여 메모리 부족(OOM) 오류를 방지합니다.
- 단일 텍스트 인코딩:
- 디코딩 (Decoding):
decode(ids, skipSpecialTokens)를 통해 토큰 ID를 원래 텍스트로 복원하며, 스페셜 토큰 제외 여부를 선택할 수 있습니다.decodeBatch(idsBatch)로 배치 디코딩도 지원합니다. - 패딩 (Padding):
enablePadding()메서드를 통해 길이, 방향(SpPaddingDirection.right) 등을 설정하여 자동 패딩을 활성화하거나,withPadding()및withPaddingToMultipleOf()를 통해 수동으로 패딩을 적용할 수 있습니다. - 자르기 (Truncation):
enableTruncation()메서드로 최대 길이(maxLength), 방향(SpTruncationDirection.right)을 설정하여 자동 자르기를 활성화하거나,withTruncation()을 통해 수동으로 자를 수 있습니다. 텍스트 쌍 자르기에는longestFirst,onlyFirst,onlySecond,doNotTruncate와 같은 다양한 전략(TruncationStrategy)을 제공합니다. - 오프셋 매핑 (Offset Mapping):
charToToken(),tokenToChars(),wordToTokens(),tokenToWord(),tokenToSequence()등 다양한 유틸리티 메서드를 통해 문자 위치, 토큰 인덱스, 단어 인덱스, 시퀀스 인덱스 간의 매핑 정보를 제공합니다. - 어휘 접근 (Vocabulary Access): 어휘 크기(
vocabSize), 특수 토큰 ID(unkId,bosId,eosId,padId) 접근, 토큰과 ID 간 변환(convertTokensToIds,convertIdsToTokens), 어휘 포함 여부 확인(vocab.contains()), Hugging Face 호환 어휘 맵(getVocab()) 제공 등 폭넓은 어휘 관련 기능을 제공합니다.
- 인코딩 (Encoding):
- Hugging Face 호환성:
tokenize(text)및tokenizeBatch(texts)메서드는 Hugging Face의 토크나이저 API와 호환되는 형태의 토큰 문자열을 반환합니다.- JSON 직렬화 및 역직렬화(
toJson(),saveToJson(),TokenizerJsonLoader.fromJson())를 통해 Hugging Facetokenizer.json형식과 호환됩니다. - 벤치마크 스크립트를 통해 Hugging Face
sentencepiece라이브러리와의 호환성을 검증합니다.
- 동적 토큰 추가:
addTokens()메서드로 새로운 토큰을 어휘에 추가하거나,addSpecialTokens()를 통해[PAD],[MASK],[CLS],[SEP]등과 같은 특수 토큰을 동적으로 추가할 수 있습니다.
- 스트리밍 API (v1.3.0+): Hugging Face의
TextStreamer와 호환되는 실시간 LLM(Large Language Model) 출력 디코딩 기능을 제공합니다.createTextStreamer()를 통해 스트리머를 생성하고,put(tokenId)으로 토큰을 전달하면onFinalizedText콜백을 통해 텍스트 청크를 받을 수 있습니다.decodeStream()또는decodeWithCallback()을 통해 스트림 기반 디코딩도 지원합니다. 프롬프트 토큰 건너뛰기(skipPrompt) 기능도 포함되어 있습니다.
- 설정 유연성:
SentencePieceConfig를 통해addBosToken및addEosToken추가 여부를 세밀하게 제어할 수 있어 Gemma (BOS, EOS 모두 추가) 및 Llama (BOS만 추가) 모델과 같은 다양한 시나리오에 맞춰 구성할 수 있습니다.
성능 특성:
- 처리량 (Throughput): 초당 약 50만 개 이상의 토큰 처리.
- 모델 로딩 시간: 32K 어휘 기준 약 50ms.
- 메모리 (어휘): 약 3MB.
- 검색 복잡도 (Lookup complexity): 토큰당 (여기서 는 토큰의 길이).
- BPE 병합 (Merge): 병합 연산당 .
이 구현체는 Dart 생태계 내에서 효율적이고 유연한 SentencePiece 토크나이징 기능을 제공하며, 특히 LLM 관련 애플리케이션 개발에 유용합니다.