""" Adapted from https://gist.github.com/xenova/a452a6474428de0182b17605a98631ee Generator of mlc-chat-config.json and tokenizer configuration. """ # isort: off import json import os from typing import Dict, List, Optional # noqa: UP035 def bpe( mergeable_ranks: Dict[bytes, int], # noqa: UP006 token: bytes, max_rank: Optional[int] = None, ) -> List[bytes]: # noqa: UP006 """Adapted from https://github.com/openai/tiktoken/issues/60#issuecomment-1499977960""" parts = [bytes([b]) for b in token] while True: min_idx = None min_rank = None for i, pair in enumerate(zip(parts[:-1], parts[1:])): rank = mergeable_ranks.get(pair[0] + pair[1]) if rank is not None and (min_rank is None or rank < min_rank): min_idx = i min_rank = rank if min_rank is None or (max_rank is not None and min_rank >= max_rank): break assert min_idx is not None parts = [*parts[:min_idx], parts[min_idx] + parts[min_idx + 1], *parts[min_idx + 2 :]] return parts def generate_vocab_and_merges(encoder, mergeable_ranks): """Generate vocab and merges in huggingface tokenizers format""" from transformers.models.gpt2.tokenization_gpt2 import ( bytes_to_unicode, ) byte_encoder = bytes_to_unicode() def token_bytes_to_string(b): """Convert a token from bytes to a string""" return "".join([byte_encoder[ord(char)] for char in b.decode("latin-1")]) merges = [] vocab = {} for token, rank in mergeable_ranks.items(): vocab[token_bytes_to_string(token)] = rank if len(token) == 1: continue merged = tuple(bpe(mergeable_ranks, token, max_rank=rank)) assert len(merged) == 2 merges.append(" ".join(map(token_bytes_to_string, merged))) # Also add special tokens vocab.update(encoder._special_tokens) return vocab, merges def convert_tiktoken(model_path, output_dir, context_window_size=None): """Convert tiktoken tokenizers to huggingface tokenizers style""" try: from transformers import AutoTokenizer except ImportError: raise ImportError( 'Converting tiktoken tokenizer requires the "transformers" package.' 'Please install the "transformers" package to convert toktoken tokenizer' ) tiktoken_tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) encoder = tiktoken_tokenizer.tokenizer vocab, merges = generate_vocab_and_merges(encoder, tiktoken_tokenizer.get_vocab()) added_tokens = [ { "id": id, "content": content, "single_word": False, "lstrip": False, "rstrip": False, "normalized": False, "special": True, } for content, id in encoder._special_tokens.items() ] tokenizer_template = { "version": "1.0", "truncation": None, "padding": None, "added_tokens": added_tokens, "normalizer": None, "pre_tokenizer": { "type": "ByteLevel", "add_prefix_space": False, "trim_offsets": True, "use_regex": True, }, "post_processor": { "type": "ByteLevel", "add_prefix_space": True, "trim_offsets": False, "use_regex": True, }, "decoder": { "type": "ByteLevel", "add_prefix_space": True, "trim_offsets": True, "use_regex": True, }, "model": { "type": "BPE", "dropout": None, "unk_token": None, "continuing_subword_prefix": "", "end_of_word_suffix": "", "fuse_unk": False, "byte_fallback": False, "vocab": vocab, "merges": merges, }, } tokenizer_config_template = { "add_prefix_space": False, "bos_token": "<|endoftext|>", "clean_up_tokenization_spaces": True, "eos_token": "<|endoftext|>", "unk_token": "<|endoftext|>", } tokenizer_name = type(tiktoken_tokenizer).__name__ tokenizer_config_template["tokenizer_class"] = tokenizer_name if context_window_size: tokenizer_config_template["model_max_length"] = context_window_size tokenizer_config_template = dict(sorted(tokenizer_config_template.items(), key=lambda x: x[0])) os.makedirs(output_dir, exist_ok=True) # Save to files with open(os.path.join(output_dir, "vocab.json"), "w", encoding="utf-8") as fp: json.dump(vocab, fp, indent=2, ensure_ascii=False) with open(os.path.join(output_dir, "tokenizer.json"), "w", encoding="utf-8") as fp: json.dump(tokenizer_template, fp, indent=2, ensure_ascii=False) with open(os.path.join(output_dir, "tokenizer_config.json"), "w", encoding="utf-8") as fp: json.dump(tokenizer_config_template, fp, indent=2, ensure_ascii=False) with open(os.path.join(output_dir, "special_tokens_map.json"), "w", encoding="utf-8") as fp: json.dump( { "bos_token": "<|endoftext|>", "eos_token": "<|endoftext|>", "unk_token": "<|endoftext|>", }, fp, indent=2, ensure_ascii=False, ) with open(os.path.join(output_dir, "merges.txt"), "w", encoding="utf-8") as fp: fp.write("#version: 0.2\n") fp.write("\n".join(merges))