11import argparse
22import json
3+ from copy import deepcopy
34from pathlib import Path
45from tempfile import TemporaryDirectory
56from typing import Optional , Tuple , Union
2930
3031from .constants import HF_WEIGHTS_NAME , HF_SAFE_WEIGHTS_NAME , HF_CONFIG_NAME
3132from .factory import create_model_from_pretrained , get_model_config , get_tokenizer
32- from .tokenizer import HFTokenizer , SigLipTokenizer
33+ from .tokenizer import HFTokenizer , SigLipTokenizer , TikTokenTokenizer
34+
35+
36+ _HF_TOKENIZER_FILE_PATTERNS = (
37+ 'tokenizer.json' ,
38+ 'tokenizer_config.json' ,
39+ 'special_tokens_map.json' ,
40+ 'added_tokens.json' ,
41+ 'vocab.json' ,
42+ 'merges.txt' ,
43+ 'spiece.model' ,
44+ 'sentencepiece.bpe.model' ,
45+ )
46+
47+
48+ def _get_text_config (model_config : Optional [dict ]) -> dict :
49+ if not model_config :
50+ return {}
51+ return model_config .get ('text_cfg' ) or model_config .get ('multimodal_cfg' ) or {}
52+
53+
54+ def _uses_tiktoken (tokenizer , model_config : Optional [dict ]) -> bool :
55+ text_cfg = _get_text_config (model_config )
56+ return isinstance (tokenizer , TikTokenTokenizer ) or text_cfg .get ('tokenizer_type' ) == 'tiktoken'
57+
58+
59+ def _with_tiktoken_assets (model_config : Optional [dict ], tokenizer_assets : Optional [dict ]) -> Optional [dict ]:
60+ if not model_config or not isinstance (tokenizer_assets , dict ):
61+ return model_config
62+ model_config = deepcopy (model_config )
63+ text_cfg = model_config .get ('text_cfg' ) or model_config .get ('multimodal_cfg' )
64+ if text_cfg is not None :
65+ text_cfg .update ({
66+ k : v for k , v in tokenizer_assets .items ()
67+ if k in ('tiktoken_bpe_path' , 'tiktoken_config_path' ) and v
68+ })
69+ return model_config
3370
3471
3572def save_config_for_hf (
3673 model ,
3774 config_path : str ,
3875 model_config : Optional [dict ],
3976):
40- preprocess_cfg = {
41- 'mean' : model .visual .image_mean ,
42- 'std' : model .visual .image_std ,
43- }
44- other_pp = getattr (model .visual , 'preprocess_cfg' , {})
45- if 'interpolation' in other_pp :
46- preprocess_cfg ['interpolation' ] = other_pp ['interpolation' ]
47- if 'resize_mode' in other_pp :
48- preprocess_cfg ['resize_mode' ] = other_pp ['resize_mode' ]
4977 hf_config = {
5078 'model_cfg' : model_config ,
51- 'preprocess_cfg' : preprocess_cfg ,
5279 }
80+ visual = getattr (model , 'visual' , None )
81+ if visual is not None :
82+ preprocess_cfg = {
83+ 'mean' : visual .image_mean ,
84+ 'std' : visual .image_std ,
85+ }
86+ other_pp = getattr (visual , 'preprocess_cfg' , {})
87+ if 'interpolation' in other_pp :
88+ preprocess_cfg ['interpolation' ] = other_pp ['interpolation' ]
89+ if 'resize_mode' in other_pp :
90+ preprocess_cfg ['resize_mode' ] = other_pp ['resize_mode' ]
91+ hf_config ['preprocess_cfg' ] = preprocess_cfg
5392
5493 with config_path .open ('w' ) as f :
5594 json .dump (hf_config , f , indent = 2 )
@@ -76,10 +115,17 @@ def save_for_hf(
76115 if safe_serialization is False or safe_serialization == "both" :
77116 torch .save (tensors , save_directory / HF_WEIGHTS_NAME )
78117
79- tokenizer .save_pretrained (save_directory )
118+ tokenizer_assets = None
119+ save_pretrained = getattr (tokenizer , 'save_pretrained' , None )
120+ if callable (save_pretrained ):
121+ tokenizer_assets = save_pretrained (save_directory )
80122
81123 config_path = save_directory / config_filename
82- save_config_for_hf (model , config_path , model_config = model_config )
124+ save_config_for_hf (
125+ model ,
126+ config_path ,
127+ model_config = _with_tiktoken_assets (model_config , tokenizer_assets ),
128+ )
83129
84130
85131def push_to_hf_hub (
@@ -95,7 +141,9 @@ def push_to_hf_hub(
95141 model_card : Optional [dict ] = None ,
96142 safe_serialization : Union [bool , str ] = 'both' ,
97143):
98- if not isinstance (tokenizer , (HFTokenizer , SigLipTokenizer )):
144+ uses_tiktoken = _uses_tiktoken (tokenizer , model_config )
145+ delete_patterns = _HF_TOKENIZER_FILE_PATTERNS if uses_tiktoken else None
146+ if not isinstance (tokenizer , (HFTokenizer , SigLipTokenizer )) and not uses_tiktoken :
99147 # FIXME this makes it awkward to push models with new tokenizers, come up with better soln.
100148 # default CLIP tokenizers use https://huggingface.co/openai/clip-vit-large-patch14
101149 tokenizer = HFTokenizer ('openai/clip-vit-large-patch14' )
@@ -150,6 +198,7 @@ def push_to_hf_hub(
150198 revision = revision ,
151199 create_pr = create_pr ,
152200 commit_message = commit_message ,
201+ delete_patterns = delete_patterns ,
153202 )
154203
155204
0 commit comments