char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
1bfbdb134
char/aliceai-t5-moe
1from __future__import annotations 2 3from enum import Enum 4import re 5import logging 6import json 7import os 8from pathlib import Path 9from typing import Any ,Callable ,Sequence ,Mapping ,Iterable ,Protocol ,ClassVar ,runtime_checkable 10 11try : 12from sentencepiece import SentencePieceProcessor 13except ImportError : 14SentencePieceProcessor :Any = None 15 16try : 17from mistral_common .tokens .tokenizers .mistral import MistralTokenizer # type: ignore[import-not-found, ty:unresolved-import] 18from mistral_common .tokens .tokenizers .tekken import Tekkenizer # type: ignore[import-not-found, ty:unresolved-import] 19from mistral_common .tokens .tokenizers .utils import (# type: ignore[import-not-found, ty:unresolved-import] 20_filter_valid_tokenizer_files , 21 ) 22from mistral_common .tokens .tokenizers .sentencepiece import (# type: ignore[import-not-found, ty:unresolved-import] 23SentencePieceTokenizer , 24 ) 25except ImportError : 26_mistral_common_installed = False 27MistralTokenizer :Any = None 28Tekkenizer :Any = None 29SentencePieceTokenizer :Any = None 30_filter_valid_tokenizer_files :Any = None 31else : 32_mistral_common_installed = True 33 34try : 35from mistral_common .tokens .tokenizers .utils import (# type: ignore[import-not-found, ty:unresolved-import] 36get_one_valid_tokenizer_file , 37 ) 38except ImportError : 39# We still want the conversion to work with older mistral-common versions. 40get_one_valid_tokenizer_file :Any = None 41 42 43import gguf 44 45from .gguf_writer import GGUFWriter 46 47logger = logging .getLogger (__name__ ) 48 49 50class SpecialVocab : 51merges :list [str ] 52add_special_token :dict [str ,bool ] 53special_token_ids :dict [str ,int ] 54chat_template :str | Sequence [Mapping [str ,str ]]| None 55normalizer_lowercase :bool | None 56normalizer_strip_accents :bool | None 57 58def __init__ ( 59self ,path :str | os .PathLike [str ],load_merges :bool = False , 60special_token_types :Iterable [str ]| None = None , 61n_vocab :int | None = None , 62 ): 63self .special_token_ids = {} 64self .add_special_token = {} 65self .n_vocab = n_vocab 66self .load_merges = load_merges 67self .merges = [] 68self .chat_template = None 69self .normalizer_lowercase = None 70self .normalizer_strip_accents = None 71if special_token_types is not None : 72self .special_token_types = special_token_types 73else : 74self .special_token_types = ('bos' ,'eos' ,'unk' ,'sep' ,'pad' ,'cls' ,'mask' ) 75self ._load (Path (path )) 76 77def __repr__ (self )-> str : 78return '<SpecialVocab with {} merges, special tokens {}, add special tokens {}>' .format ( 79len (self .merges ),self .special_token_ids or "unset" ,self .add_special_token or "unset" , 80 ) 81 82def add_to_gguf (self ,gw :GGUFWriter ,quiet :bool = False )-> None : 83if self .merges : 84if not quiet : 85logger .info (f'Adding { len ( self . merges ) } merge(s).' ) 86gw .add_token_merges (self .merges ) 87elif self .load_merges : 88logger .warning ('Adding merges requested but no merges found, output may be non-functional.' ) 89for typ ,tokid in self .special_token_ids .items (): 90id_handler :Callable [[int ],None ]| None = getattr (gw ,f'add_ { typ } _token_id' ,None ) 91if id_handler is None : 92logger .warning (f'No handler for special token type { typ } with id { tokid } - skipping' ) 93continue 94if not quiet : 95logger .info (f'Setting special token type { typ } to { tokid } ' ) 96id_handler (tokid ) 97for typ ,value in self .add_special_token .items (): 98add_handler :Callable [[bool ],None ]| None = getattr (gw ,f'add_add_ { typ } _token' ,None ) 99if add_handler is None : 100logger .warning (f'No handler for add_ { typ } _token with value { value } - skipping' ) 101continue 102if not quiet : 103logger .info (f'Setting add_ { typ } _token to { value } ' ) 104add_handler (value ) 105if self .chat_template is not None : 106if not quiet : 107logger .info (f'Setting chat_template to { self . chat_template } ' ) 108gw .add_chat_template (self .chat_template ) 109if self .normalizer_lowercase is not None : 110if not quiet : 111logger .info (f'Setting normalizer_lowercase to { self . normalizer_lowercase } ' ) 112gw .add_normalizer_lowercase (self .normalizer_lowercase ) 113if self .normalizer_strip_accents is not None : 114if not quiet : 115logger .info (f'Setting normalizer_strip_accents to { self . normalizer_strip_accents } ' ) 116gw .add_normalizer_strip_accents (self .normalizer_strip_accents ) 117 118def _load (self ,path :Path )-> None : 119self ._try_load_from_tokenizer_json (path ) 120self ._try_load_from_config_json (path ) 121if self .load_merges and not self .merges : 122self ._try_load_merges_txt (path ) 123 124def _try_load_merges_txt (self ,path :Path )-> bool : 125merges_file = path / 'merges.txt' 126if not merges_file .is_file (): 127return False 128with open (merges_file ,'r' ,encoding = 'utf-8' )as fp : 129first_line = next (fp ,'' ).strip () 130if not first_line .startswith ('#' ): 131fp .seek (0 ) 132line_num = 0 133else : 134line_num = 1 135merges = [] 136for line in fp : 137line_num += 1 138line = line .strip () 139if not line : 140continue 141parts = line .split (None ,3 ) 142if len (parts )!= 2 : 143logger .warning (f' { merges_file . name } : Line { line_num } : Entry malformed, ignoring' ) 144continue 145merges .append (f' { parts [ 0 ] } { parts [ 1 ] } ' ) 146self .merges = merges 147return True 148 149def _set_special_token (self ,typ :str ,tid :Any )-> None : 150if not isinstance (tid ,int ): 151return 152if tid < 0 : 153raise ValueError (f'invalid value for special token type { typ } : { tid } ' ) 154if self .n_vocab is None or tid < self .n_vocab : 155if typ in self .special_token_ids : 156return 157self .special_token_ids [typ ]= tid 158return 159logger .warning (f'Special token type { typ } , id { tid } out of range, must be under { self . n_vocab } - skipping' ) 160 161def _parse_normalizer (self ,normalizer :dict )-> None : 162# ref: https://huggingface.co/docs/tokenizers/api/normalizers 163# 164# Extracts normalizer flags from three possible formats: 165# 1. Standalone: {"type": "Lowercase"} 166# 2. BertNormalizer attrs: {"type": "BertNormalizer", ...} 167# 3. Nested in Sequence: {"type": "Sequence", "normalizers": [...]} 168 169normalizer_type = normalizer .get ('type' ) 170if normalizer_type == 'Lowercase' : 171self .normalizer_lowercase = True 172elif normalizer_type == 'StripAccents' : 173self .normalizer_strip_accents = True 174elif normalizer_type == 'BertNormalizer' : 175if 'lowercase' in normalizer : 176self .normalizer_lowercase = normalizer ['lowercase' ] 177if 'strip_accents' in normalizer : 178self .normalizer_strip_accents = normalizer ['strip_accents' ] 179elif normalizer_type == 'Sequence' : 180for norm in normalizer .get ('normalizers' , []): 181self ._parse_normalizer (norm ) 182 183def _try_load_from_tokenizer_json (self ,path :Path )-> bool : 184tokenizer = None 185tokenizer_file = path / 'tokenizer.json' 186if tokenizer_file .is_file (): 187with open (tokenizer_file ,encoding = 'utf-8' )as f : 188tokenizer = json .load (f ) 189if self .load_merges : 190merges = tokenizer .get ('model' , {}).get ('merges' ) 191if isinstance (merges ,list )and merges : 192if isinstance (merges [0 ],str ): 193self .merges = merges 194elif isinstance (merges [0 ],list )and len (merges [0 ])== 2 and isinstance (merges [0 ][0 ],str ): 195# New format since transformers 4.45 to support spaces in merges 196# ref: https://github.com/ggml-org/llama.cpp/issues/9692 197# TODO: internally store as the new format instead of converting to old 198if any (' ' in s for pair in merges for s in pair ): 199logger .warning (f'Spaces in merges detected, encoding as { chr ( ord ( " " ) + 256 )!r } ' ) 200self .merges = [ 201' ' .join ( 202 [ 203# ensure the spaces are properly encoded 204'' .join ( 205chr (ord (c )+ 256 )if c == ' ' else c 206for c in part 207 ) 208for part in pair 209 ] 210 ) 211for pair in merges 212 ] 213else : 214raise ValueError ("Unknown tokenizer merges format" ) 215# Parse normalizer configuration (e.g. Lowercase) into metadata 216if normalizer := tokenizer .get ('normalizer' ): 217self ._parse_normalizer (normalizer ) 218added_tokens = tokenizer .get ('added_tokens' , {}) 219else : 220added_tokens = {} 221tokenizer_config = None 222tokenizer_config_file = path / 'tokenizer_config.json' 223if tokenizer_config_file .is_file (): 224with open (tokenizer_config_file ,encoding = 'utf-8' )as f : 225tokenizer_config = json .load (f ) 226if tokenizer : 227special_bos = (tokenizer_config or {}).get ('bos_token' ) 228special_cls = (tokenizer_config or {}).get ('cls_token' ) 229special_eos = (tokenizer_config or {}).get ('eos_token' ) 230special_sep = (tokenizer_config or {}).get ('sep_token' ) 231if not special_bos and special_cls and tokenizer_config : 232tokenizer_config ['bos_token' ]= special_bos = special_cls 233if not special_eos and special_sep and tokenizer_config : 234tokenizer_config ['eos_token' ]= special_eos = special_sep 235if post_processor := tokenizer .get ('post_processor' ): 236for processor in post_processor .get ('processors' , [post_processor ]): 237if processor .get ('type' )== 'RobertaProcessing' : 238self .add_special_token ['bos' ]= True 239self .add_special_token ['eos' ]= True 240self .add_special_token ['sep' ]= True 241if not special_cls and tokenizer_config : 242special_cls = processor .get ('cls' , [special_bos ])[0 ] 243tokenizer_config ['cls_token' ]= special_cls 244if not special_sep and tokenizer_config : 245special_sep = processor .get ('sep' , [special_eos ])[0 ] 246tokenizer_config ['sep_token' ]= special_sep 247continue 248# Crude parsing of TemplateProcessing to determine if BOS/SEP/EOS should be added 249# Only works with simple templates, **will** get it wrong on unusual sequences 250if processor .get ('type' )== 'TemplateProcessing' : 251tmpl_single = processor .get ('single' , []) 252tmpl_pair = processor .get ('pair' , []) 253special_first = None 254special_last = None 255if len (tmpl_single )> 1 : 256if special_first := tmpl_single [0 ].get ('SpecialToken' , {}).get ('id' ): 257if not tokenizer_config : 258special_bos = special_first 259elif special_first not in (special_bos ,special_cls ): 260if not special_bos : 261tokenizer_config ['bos_token' ]= special_bos = special_first 262if not special_cls : 263tokenizer_config ['cls_token' ]= special_cls = special_first 264self .add_special_token ['bos' ]= True if special_first in (special_bos ,special_cls )else False 265if special_first not in (special_bos ,special_cls ): 266logger .warning (f'Unknown leading special token { special_first !r } in TemplateProcessing<single>' ) 267if special_last := tmpl_single [- 1 ].get ('SpecialToken' , {}).get ('id' ): 268if not tokenizer_config : 269special_eos = special_last 270elif special_last != special_eos : 271if 'eot' not in self .special_token_types : 272self .special_token_types = tuple (self .special_token_types )+ ('eot' , ) 273tokenizer_config ['eot_token' ]= special_eos 274elif 'eom' not in self .special_token_types : 275self .special_token_types = tuple (self .special_token_types )+ ('eom' , ) 276tokenizer_config ['eom_token' ]= special_eos 277else : 278logger .warning (f'Overriding EOS token { special_eos !r } with { special_last !r } without EOT/EOM fallback!' ) 279tokenizer_config ['eos_token' ]= special_eos = special_last 280self .add_special_token ['eos' ]= True if special_last == special_eos else False 281if special_last != special_eos : 282logger .warning (f'Unknown trailing special token { special_last !r } in TemplateProcessing<single>' ) 283if tmpl_pair : 284seq_start = 1 if special_first and tmpl_pair [0 ].get ('SpecialToken' , {}).get ('id' )== special_first else 0 285seq_stop = - 1 if special_last and tmpl_pair [- 1 ].get ('SpecialToken' , {}).get ('id' )== special_last else None 286if (special_first and seq_start == 0 )or (special_last and seq_stop is None ): 287logger .warning ('TemplateProcessing<single> leading/trailing special tokens do not match TemplateProcessing<pair>' ) 288if tmpl_pair := tmpl_pair [slice (seq_start ,seq_stop )]: 289tmpl_a = tmpl_pair [0 ].get ('Sequence' , {}).get ('id' ) 290tmpl_b = tmpl_pair [- 1 ].get ('Sequence' , {}).get ('id' ) 291if tmpl_a != 'A' or tmpl_b != 'B' : 292logger .warning (f'Unknown sequence { tmpl_a } ... { tmpl_b } in TemplateProcessing<pair>' ) 293# A [sep] [eos] B 294if tmpl_a == 'A' and tmpl_b == 'B' and (tmpl_pair := tmpl_pair [1 :- 1 ]): 295add_sep = False 296if special_entry := tmpl_pair [0 ].get ('SpecialToken' , {}).get ('id' ): 297if special_entry in (special_sep ,special_eos )and not special_last : 298add_sep = True 299if special_entry not in (special_sep ,special_eos ): 300logger .warning (f'Unknown separator token { special_entry !r } in TemplateProcessing<pair>' ) 301else : 302logger .warning (f'Unknown middle sequence { tmpl_pair [ 0 ]!r } in TemplateProcessing<pair>' ) 303if len (tmpl_pair )== 2 : 304if special_entry := tmpl_pair [1 ].get ('SpecialToken' , {}).get ('id' ): 305if special_entry in (special_sep ,special_eos ): 306add_sep = True 307if special_entry not in (special_sep ,special_eos ): 308logger .warning (f'Unknown second separator token { special_entry !r } in TemplateProcessing<pair>' ) 309else : 310logger .warning (f'Unknown second middle sequence { tmpl_pair [ 1 ]!r } in TemplateProcessing<pair>' ) 311self .add_special_token ['sep' ]= add_sep 312if add_sep and not special_sep and tokenizer_config : 313tokenizer_config ['sep_token' ]= special_eos 314continue 315if not tokenizer_config : 316return True 317chat_template_alt = None 318chat_template_json = path / 'chat_template.json' 319chat_template_jinja = path / 'chat_template.jinja' 320if chat_template_jinja .is_file (): 321with open (chat_template_jinja ,encoding = 'utf-8' )as f : 322chat_template_alt = f .read () 323if additional_templates := list ((path / 'additional_chat_templates' ).glob ('*.jinja' )): 324chat_template_alt = [{'name' :'default' ,'template' :chat_template_alt }] 325for template_path in additional_templates : 326with open (template_path ,encoding = 'utf-8' )as fp : 327chat_template_alt .append ({'name' :template_path .stem ,'template' :fp .read ()}) 328elif chat_template_json .is_file (): 329with open (chat_template_json ,encoding = 'utf-8' )as f : 330chat_template_alt = json .load (f ).get ('chat_template' ) 331chat_template = tokenizer_config .get ('chat_template' ,chat_template_alt ) 332if chat_template is None or isinstance (chat_template , (str ,list )): 333self .chat_template = chat_template 334else : 335logger .warning (f'Bad type for chat_template field in { tokenizer_config_file !r } - ignoring' ) 336for typ in self .special_token_types : 337add_entry = tokenizer_config .get (f'add_ { typ } _token' ) 338if isinstance (add_entry ,bool ): 339self .add_special_token [typ ]= add_entry 340entry = tokenizer_config .get (f' { typ } _token' ) 341if isinstance (entry ,str ): 342tc_content = entry 343elif isinstance (entry ,dict ): 344entry_content = entry .get ('content' ) 345if not isinstance (entry_content ,str ): 346continue 347tc_content = entry_content 348else : 349continue 350# We only need the first match here. 351maybe_token_id = next ( 352 (atok .get ('id' )for atok in added_tokens if atok .get ('content' )== tc_content ), 353None , 354 ) 355self ._set_special_token (typ ,maybe_token_id ) 356return True 357 358def _try_load_from_config_json (self ,path :Path )-> bool : 359config_file = path / 'config.json' 360if not config_file .is_file (): 361return False 362with open (config_file ,encoding = 'utf-8' )as f : 363config = json .load (f ) 364for typ in self .special_token_types : 365token_id = config .get (f' { typ } _token_id' ) 366# If not found at root, check in text_config (for multimodal models like Kimi-VL) 367if token_id is None and 'text_config' in config : 368token_id = config ['text_config' ].get (f' { typ } _token_id' ) 369self ._set_special_token (typ ,token_id ) 370return True 371 372 373@runtime_checkable 374class BaseVocab (Protocol ): 375tokenizer_model :ClassVar [str ] 376name :ClassVar [str ] 377 378 379@runtime_checkable 380class Vocab (BaseVocab ,Protocol ): 381vocab_size :int 382added_tokens_dict :dict [str ,int ] 383added_tokens_list :list [str ] 384fname_tokenizer :Path 385 386def __init__ (self ,base_path :Path ): ... 387def all_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: ... 388 389 390class NoVocab (BaseVocab ): 391tokenizer_model = "no_vocab" 392name = "no_vocab" 393 394def __repr__ (self )-> str : 395return "<NoVocab for a model without integrated vocabulary>" 396 397 398class BpeVocab (Vocab ): 399tokenizer_model = "gpt2" 400name = "bpe" 401 402def __init__ (self ,base_path :Path ): 403added_tokens :dict [str ,int ]= {} 404 405if (fname_tokenizer := base_path / 'vocab.json' ).exists (): 406# "slow" tokenizer 407with open (fname_tokenizer ,encoding = "utf-8" )as f : 408self .vocab = json .load (f ) 409 410try : 411# FIXME: Verify that added tokens here _cannot_ overlap with the main vocab. 412with open (base_path / 'added_tokens.json' ,encoding = "utf-8" )as f : 413added_tokens = json .load (f ) 414except FileNotFoundError : 415pass 416else : 417# "fast" tokenizer 418fname_tokenizer = base_path / 'tokenizer.json' 419 420# if this fails, FileNotFoundError propagates to caller 421with open (fname_tokenizer ,encoding = "utf-8" )as f : 422tokenizer_json = json .load (f ) 423 424tokenizer_model :dict [str ,Any ]= tokenizer_json ['model' ] 425if ( 426tokenizer_model ['type' ]!= 'BPE' or tokenizer_model .get ('byte_fallback' ,False ) 427or tokenizer_json ['decoder' ]['type' ]!= 'ByteLevel' 428 ): 429raise FileNotFoundError ('Cannot find GPT-2 BPE tokenizer' ) 430 431self .vocab = tokenizer_model ["vocab" ] 432 433if (added := tokenizer_json .get ('added_tokens' ))is not None : 434# Added tokens here can be duplicates of the main vocabulary. 435added_tokens = {item ['content' ]:item ['id' ] 436for item in added 437if item ['content' ]not in self .vocab } 438 439vocab_size = len (self .vocab ) 440expected_ids = list (range (vocab_size ,vocab_size + len (added_tokens ))) 441actual_ids = sorted (added_tokens .values ()) 442if expected_ids != actual_ids : 443expected_end_id = vocab_size + len (actual_ids )- 1 444raise ValueError (f"Expected the { len ( actual_ids ) } added token ID(s) to be sequential in the range " 445f" { vocab_size } - { expected_end_id } ; got { actual_ids } " ) 446 447items = sorted (added_tokens .items (),key = lambda text_idx :text_idx [1 ]) 448self .added_tokens_dict = added_tokens 449self .added_tokens_list = [text for (text ,idx )in items ] 450self .vocab_size_base = vocab_size 451self .vocab_size = self .vocab_size_base + len (self .added_tokens_list ) 452self .fname_tokenizer = fname_tokenizer 453 454def bpe_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 455reverse_vocab = {id :encoded_tok for encoded_tok ,id in self .vocab .items ()} 456 457for i ,_ in enumerate (self .vocab ): 458yield reverse_vocab [i ],0.0 ,gguf .TokenType .NORMAL 459 460def added_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 461for text in self .added_tokens_list : 462score = - 1000.0 463yield text .encode ("utf-8" ),score ,gguf .TokenType .CONTROL 464 465def all_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 466yield from self .bpe_tokens () 467yield from self .added_tokens () 468 469def __repr__ (self )-> str : 470return f"<BpeVocab with { self . vocab_size_base } base tokens and { len ( self . added_tokens_list ) } added tokens>" 471 472 473class SentencePieceVocab (Vocab ): 474tokenizer_model = "llama" 475name = "spm" 476 477def __init__ (self ,base_path :Path ): 478if SentencePieceProcessor is None : 479raise RuntimeError ("sentencepiece is not installed" ) 480 481added_tokens :dict [str ,int ]= {} 482if (fname_tokenizer := base_path / 'tokenizer.model' ).exists (): 483# normal location 484try : 485with open (base_path / 'added_tokens.json' ,encoding = "utf-8" )as f : 486added_tokens = json .load (f ) 487except FileNotFoundError : 488pass 489elif not (fname_tokenizer := base_path .parent / 'tokenizer.model' ).exists (): 490# not found in alternate location either 491raise FileNotFoundError ('Cannot find tokenizer.model' ) 492 493self .sentencepiece_tokenizer = SentencePieceProcessor () 494self .sentencepiece_tokenizer .LoadFromFile (str (fname_tokenizer )) 495vocab_size = self .sentencepiece_tokenizer .vocab_size () 496 497new_tokens = {id :piece for piece ,id in added_tokens .items ()if id >= vocab_size } 498expected_new_ids = list (range (vocab_size ,vocab_size + len (new_tokens ))) 499actual_new_ids = sorted (new_tokens .keys ()) 500 501if expected_new_ids != actual_new_ids : 502raise ValueError (f"Expected new token IDs { expected_new_ids } to be sequential; got { actual_new_ids } " ) 503 504# Token pieces that were added to the base vocabulary. 505self .added_tokens_dict = added_tokens 506self .added_tokens_list = [new_tokens [id ]for id in actual_new_ids ] 507self .vocab_size_base = vocab_size 508self .vocab_size = self .vocab_size_base + len (self .added_tokens_list ) 509self .fname_tokenizer = fname_tokenizer 510 511def sentencepiece_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 512tokenizer = self .sentencepiece_tokenizer 513for i in range (tokenizer .vocab_size ()): 514piece = tokenizer .IdToPiece (i ) 515text = piece .encode ("utf-8" ) 516score :float = tokenizer .GetScore (i ) 517 518toktype = gguf .TokenType .NORMAL 519if tokenizer .IsUnknown (i ): 520toktype = gguf .TokenType .UNKNOWN 521if tokenizer .IsControl (i ): 522toktype = gguf .TokenType .CONTROL 523 524# NOTE: I think added_tokens are user defined. 525# ref: https://github.com/google/sentencepiece/blob/master/src/sentencepiece_model.proto 526# if tokenizer.is_user_defined(i): toktype = gguf.TokenType.USER_DEFINED 527 528if tokenizer .IsUnused (i ): 529toktype = gguf .TokenType .UNUSED 530if tokenizer .IsByte (i ): 531toktype = gguf .TokenType .BYTE 532 533yield text ,score ,toktype 534 535def added_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 536for text in self .added_tokens_list : 537score = - 1000.0 538yield text .encode ("utf-8" ),score ,gguf .TokenType .USER_DEFINED 539 540def all_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 541yield from self .sentencepiece_tokens () 542yield from self .added_tokens () 543 544def __repr__ (self )-> str : 545return f"<SentencePieceVocab with { self . vocab_size_base } base tokens and { len ( self . added_tokens_list ) } added tokens>" 546 547 548class LlamaHfVocab (Vocab ): 549tokenizer_model = "llama" 550name = "hfft" 551 552def __init__ (self ,base_path :Path ): 553fname_tokenizer = base_path / 'tokenizer.json' 554# if this fails, FileNotFoundError propagates to caller 555with open (fname_tokenizer ,encoding = 'utf-8' )as f : 556tokenizer_json = json .load (f ) 557 558# pre-check so we know if we need transformers 559tokenizer_model :dict [str ,Any ]= tokenizer_json ['model' ] 560is_llama3 = ( 561tokenizer_model ['type' ]== 'BPE' and tokenizer_model .get ('ignore_merges' ,False ) 562and not tokenizer_model .get ('byte_fallback' ,True ) 563 ) 564if is_llama3 : 565raise TypeError ('Llama 3 must be converted with BpeVocab' ) 566 567if not is_llama3 and ( 568tokenizer_model ['type' ]!= 'BPE' or not tokenizer_model .get ('byte_fallback' ,False ) 569or tokenizer_json ['decoder' ]['type' ]!= 'Sequence' 570 ): 571raise FileNotFoundError ('Cannot find Llama BPE tokenizer' ) 572 573try : 574from transformers import AutoTokenizer 575except ImportError as e : 576raise ImportError ( 577"To use LlamaHfVocab, please install the `transformers` package. " 578"You can install it with `pip install transformers`." 579 )from e 580 581# Allow the tokenizer to default to slow or fast versions. 582# Explicitly set tokenizer to use local paths. 583self .tokenizer = AutoTokenizer .from_pretrained ( 584base_path , 585cache_dir = base_path , 586local_files_only = True , 587 ) 588assert self .tokenizer .is_fast # assume tokenizer.json is used # ty: ignore[unresolved-attribute] 589 590# Initialize lists and dictionaries for added tokens 591self .added_tokens_list = [] 592self .added_tokens_dict = dict () 593self .added_tokens_ids = set () 594 595# Process added tokens 596for tok ,tokidx in sorted ( 597self .tokenizer .get_added_vocab ().items (),key = lambda x :x [1 ]# ty: ignore[unresolved-attribute] 598 ): 599# Only consider added tokens that are not in the base vocabulary 600if tokidx >= self .tokenizer .vocab_size :# ty: ignore[unresolved-attribute] 601self .added_tokens_list .append (tok ) 602self .added_tokens_dict [tok ]= tokidx 603self .added_tokens_ids .add (tokidx ) 604 605# Store special tokens and their IDs 606self .specials = { 607tok :self .tokenizer .get_vocab ()[tok ]# ty: ignore[unresolved-attribute] 608for tok in self .tokenizer .all_special_tokens # ty: ignore[unresolved-attribute] 609 } 610self .special_ids = set (self .tokenizer .all_special_ids )# ty: ignore[unresolved-attribute] 611 612# Set vocabulary sizes 613self .vocab_size_base = self .tokenizer .vocab_size # ty: ignore[unresolved-attribute] 614self .vocab_size = self .vocab_size_base + len (self .added_tokens_list ) 615 616self .fname_tokenizer = fname_tokenizer 617 618def hf_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 619reverse_vocab = { 620id :encoded_tok for encoded_tok ,id in self .tokenizer .get_vocab ().items ()# ty: ignore[unresolved-attribute] 621 } 622 623for token_id in range (self .vocab_size_base ): 624# Skip processing added tokens here 625if token_id in self .added_tokens_ids : 626continue 627 628# Convert token text to bytes 629token_text = reverse_vocab [token_id ].encode ("utf-8" ) 630 631# Yield token text, score, and type 632yield token_text ,self .get_token_score (token_id ),self .get_token_type ( 633token_id ,token_text ,self .special_ids # Reuse already stored special IDs 634 ) 635 636def get_token_type (self ,token_id :int ,token_text :bytes ,special_ids :set [int ])-> gguf .TokenType : 637# Special case for byte tokens 638if re .fullmatch (br"<0x[0-9A-Fa-f]{2}>" ,token_text ): 639return gguf .TokenType .BYTE 640 641# Determine token type based on whether it's a special token 642return gguf .TokenType .CONTROL if token_id in special_ids else gguf .TokenType .NORMAL 643 644def get_token_score (self ,token_id :int )-> float : 645# Placeholder for actual logic to determine the token's score 646# This needs to be implemented based on specific requirements 647return - 1000.0 # Default score 648 649def added_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 650for text in self .added_tokens_list : 651if text in self .specials : 652toktype = self .get_token_type (self .specials [text ],b'' ,self .special_ids ) 653score = self .get_token_score (self .specials [text ]) 654else : 655toktype = gguf .TokenType .USER_DEFINED 656score = - 1000.0 657 658yield text .encode ("utf-8" ),score ,toktype 659 660def has_newline_token (self ): 661return "<0x0A>" in self .tokenizer .vocab or "\n" in self .tokenizer .vocab # ty: ignore[unresolved-attribute] 662 663def all_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 664yield from self .hf_tokens () 665yield from self .added_tokens () 666 667def __repr__ (self )-> str : 668return f"<LlamaHfVocab with { self . vocab_size_base } base tokens and { len ( self . added_tokens_list ) } added tokens>" 669 670 671class MistralTokenizerType (str ,Enum ): 672spm = "spm" 673tekken = "tekken" 674 675 676# Copied from Transformers (Apache 2.0) 677# https://github.com/huggingface/transformers/blob/main/src/transformers/convert_slow_tokenizer.py#L1544 678 679def bytes_to_unicode ()-> dict [int ,str ]: 680""" 681Returns list of utf-8 byte and a mapping to unicode strings. We specifically avoids mapping to whitespace/control 682characters the bpe code barfs on. 683 684The reversible bpe codes work on unicode strings. This means you need a large # of unicode characters in your vocab 685if you want to avoid UNKs. When you're at something like a 10B token dataset you end up needing around 5K for 686decent coverage. This is a significant percentage of your normal, say, 32K bpe vocab. To avoid that, we want lookup 687tables between utf-8 bytes and unicode strings. 688""" 689bs = ( 690list (range (ord ("!" ),ord ("~" )+ 1 )) 691+ list (range (ord ("¡" ),ord ("¬" )+ 1 )) 692+ list (range (ord ("®" ),ord ("ÿ" )+ 1 )) 693 ) 694cs = bs [:] 695n = 0 696for b in range (2 ** 8 ): 697if b not in bs : 698bs .append (b ) 699cs .append (2 ** 8 + n ) 700n += 1 701cs_str = [chr (n )for n in cs ] 702return dict (zip (bs ,cs_str )) 703 704 705class MistralVocab (Vocab ): 706tokenizer_model = "mistral" 707name = "mistral" 708 709added_tokens_dict :dict [str ,int ]= {} 710added_tokens_list :list [str ]= [] 711 712def __init__ (self ,base_path :Path ): 713if not _mistral_common_installed : 714raise ImportError ( 715"To use MistralVocab, please install the `mistral-common` package. " 716"You can install it with `pip install mistral-common`." 717 ) 718assert _filter_valid_tokenizer_files is not None ,"mistral_common is not installed" 719assert MistralTokenizer is not None ,"mistral_common is not installed" 720assert Tekkenizer is not None ,"mistral_common is not installed" 721 722logger .info (f"Loading Mistral tokenizer from { base_path } " ) 723 724# Find the tokenizer files 725all_files = [f .as_posix ()for f in base_path .glob ("**/*" )if f .is_file ()] 726 727if get_one_valid_tokenizer_file is not None : 728tokenizer_file_path = get_one_valid_tokenizer_file (all_files ) 729else : 730valid_tokenizer_files = _filter_valid_tokenizer_files (all_files ) 731 732if len (valid_tokenizer_files )== 0 : 733raise ValueError (f"No tokenizer file found in the directory: { base_path } " ) 734# If there are multiple tokenizer files, we use tekken.json if it exists, otherwise the versioned one. 735if len (valid_tokenizer_files )> 1 : 736if "tekken.json" in valid_tokenizer_files : 737tokenizer_file = "tekken.json" 738else : 739tokenizer_file = sorted (valid_tokenizer_files )[- 1 ] 740logger .warning ( 741f"Multiple tokenizer files found in { base_path } . Using { tokenizer_file } " 742 ) 743else : 744tokenizer_file = valid_tokenizer_files [0 ] 745 746tokenizer_file_path = base_path / tokenizer_file 747 748self .tokenizer :Any = MistralTokenizer .from_file ( 749tokenizer_file_path 750 ).instruct_tokenizer .tokenizer 751self .tokenizer_type = ( 752MistralTokenizerType .tekken 753if isinstance (self .tokenizer ,Tekkenizer ) 754else MistralTokenizerType .spm 755 ) 756self .vocab_size = self .tokenizer .n_words 757self .fname_tokenizer = tokenizer_file_path 758self ._name = ( 759"mistral-" + self .tokenizer_type .value + "-" + self .tokenizer .version 760 ) 761 762@property 763def tokenizer_name (self )-> str : 764return self ._name 765 766@property 767def gguf_tokenizer_model (self )-> str : 768return "llama" if self .tokenizer_type == MistralTokenizerType .spm else "gpt2" 769 770def _sentencepiece_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 771assert SentencePieceTokenizer is not None ,"mistral_common is not installed" 772assert isinstance (self .tokenizer ,SentencePieceTokenizer ), ( 773f"Expected SentencePieceTokenizer, got { type ( self . tokenizer ) } " 774 ) 775 776for i in range (self .tokenizer ._model .vocab_size ()): 777piece = self .tokenizer ._model .IdToPiece (i ) 778text = piece .encode ("utf-8" ) 779score :float = self .tokenizer ._model .GetScore (i ) 780 781toktype = gguf .TokenType .NORMAL 782if self .tokenizer ._model .IsUnknown (i ): 783toktype = gguf .TokenType .UNKNOWN 784if self .tokenizer ._model .IsControl (i ): 785toktype = gguf .TokenType .CONTROL 786 787if self .tokenizer ._model .IsUnused (i ): 788toktype = gguf .TokenType .UNUSED 789if self .tokenizer ._model .IsByte (i ): 790toktype = gguf .TokenType .BYTE 791 792yield text ,score ,toktype 793 794def _tekken_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 795assert Tekkenizer is not None ,"mistral_common is not installed" 796assert isinstance (self .tokenizer ,Tekkenizer ), ( 797f"Expected Tekkenizer, got { type ( self . tokenizer ) } " 798 ) 799 800byte_encoder = bytes_to_unicode () 801for token_id in range (self .tokenizer .num_special_tokens ): 802yield ( 803self .tokenizer .id_to_piece (token_id ).encode ("utf-8" ), 8040 , 805gguf .TokenType .CONTROL 806 ) 807for token in self .tokenizer ._tekken_token2id_nospecial : 808yield ( 809self .token_bytes_to_string (token ,byte_encoder ).encode ("utf-8" ), 8100 , 811gguf .TokenType .NORMAL , 812 ) 813 814def get_token_id (self ,token :str )-> int : 815assert SentencePieceTokenizer is not None and Tekkenizer is not None ,"mistral_common is not installed" 816if self .tokenizer_type == MistralTokenizerType .spm : 817assert isinstance (self .tokenizer ,SentencePieceTokenizer ) 818return self .tokenizer ._vocab .index (token ) 819elif self .tokenizer_type == MistralTokenizerType .tekken : 820assert isinstance (self .tokenizer ,Tekkenizer ) 821return ( 822self .tokenizer ._vocab .index (token )+ self .tokenizer .num_special_tokens 823 ) 824else : 825raise ValueError (f"Unknown tokenizer type: { self . tokenizer_type } " ) 826 827@property 828def bos_id (self )-> int : 829return self .tokenizer .bos_id 830 831@property 832def eos_id (self )-> int : 833return self .tokenizer .eos_id 834 835@property 836def pad_id (self )-> int : 837if self .tokenizer .pad_id == - 1 : 838return self .eos_id 839return self .tokenizer .pad_id 840 841@property 842def unk_id (self )-> int : 843return self .tokenizer .unk_id 844 845@property 846def bos_token (self )-> str : 847return self .tokenizer .id_to_piece (self .tokenizer .bos_id ) 848 849@property 850def eos_token (self )-> str : 851return self .tokenizer .id_to_piece (self .tokenizer .eos_id ) 852 853@property 854def pad_token (self )-> str : 855return self .tokenizer .id_to_piece (self .tokenizer .pad_id ) 856 857@property 858def unk_token (self )-> str : 859return self .tokenizer .id_to_piece (self .tokenizer .unk_id ) 860 861def all_tokens (self )-> Iterable [tuple [bytes ,float ,gguf .TokenType ]]: 862if self .tokenizer_type == MistralTokenizerType .spm : 863yield from self ._sentencepiece_tokens () 864 865elif self .tokenizer_type == MistralTokenizerType .tekken : 866yield from self ._tekken_tokens () 867 868else : 869raise ValueError (f"Unknown tokenizer type: { self . tokenizer_type } " ) 870 871@staticmethod 872def token_bytes_to_string (b ,byte_encoder ): 873return "" .join ([byte_encoder [ord (char )]for char in b .decode ("latin-1" )]) 874 875def extract_vocab_merges_from_model (self ): 876# Adapted from Transformers (Apache 2.0) 877# https://github.com/huggingface/transformers/blob/main/src/transformers/convert_slow_tokenizer.py 878assert Tekkenizer is not None and isinstance (self .tokenizer ,Tekkenizer ), ( 879f"Expected Tekkenizer, got { type ( self . tokenizer ) } " 880 ) 881mergeable_ranks = self .tokenizer ._model ._mergeable_ranks 882token_bytes_map = { 883rank :token_bytes for token_bytes ,rank in mergeable_ranks .items () 884 } 885merge_pairs = [] 886 887# Sort vocab by rank to ensure correct merge order 888for i in range (256 ,self .vocab_size - self .tokenizer .num_special_tokens ): 889merged_token = token_bytes_map [i ] 890local = [] 891for j in range (1 ,len (merged_token )): 892left = merged_token [:j ] 893right = merged_token [j :] 894if ( 895left in mergeable_ranks 896and right in mergeable_ranks 897and (left + right )in mergeable_ranks 898 ): 899local .append ((left ,right ,i )) 900if not local : 901raise ValueError ( 902f"Could not find valid merge for token at rank { i } : { merged_token . decode ( 'latin-1' ) } " 903 ) 904local = sorted ( 905local , 906key = lambda x : (mergeable_ranks [x [0 ]],mergeable_ranks [x [1 ]]), 907reverse = False , 908 ) 909merge_pairs .extend (local ) 910merge_pairs = sorted (merge_pairs ,key = lambda val :val [2 ],reverse = False ) 911 912byte_encoder = bytes_to_unicode () 913 914decoded_merge_pairs = [ 915 [ 916self .token_bytes_to_string (val [0 ],byte_encoder ), 917self .token_bytes_to_string (val [1 ],byte_encoder ), 918 ] 919for val in merge_pairs 920 ] 921 922merges = [ 923" " .join ( 924 [ 925# ensure the spaces are properly encoded 926"" .join (chr (ord (c )+ 256 )if c == " " else c for c in part ) 927for part in pair 928 ] 929 ) 930for pair in decoded_merge_pairs 931 ] 932 933return merges