Fix tokenizer persistence using merged source implementation

#7
by edbeeching HF Staff - opened
Files changed (1) hide show
  1. tokenizer.py +44 -97
tokenizer.py CHANGED
@@ -80,20 +80,28 @@ class HybridDNATokenizer(PreTrainedTokenizer):
80
  self._build_combined_vocab()
81
 
82
  # Set special tokens
83
- self._eos_token = kwargs.pop('eos_token', None) or "<|endoftext|>"
84
- self._pad_token = kwargs.pop('pad_token', None) or self._base_tokenizer.pad_token or "<|endoftext|>"
85
 
86
  # Initialize parent class
87
  super().__init__(
88
  eos_token=self._eos_token,
89
  pad_token=self._pad_token,
 
 
 
90
  **kwargs
91
  )
92
 
93
  # Preserve the base tokenizer's AddedToken metadata. These tokens are
94
  # already present in the combined vocabulary at their original IDs, so
95
  # this only registers their metadata and does not grow the vocabulary.
96
- self.add_tokens(list(self._base_tokenizer.added_tokens_decoder.values()))
 
 
 
 
 
97
 
98
  self.special_tokens = self.dna_special_tokens + [self._eos_token, self._pad_token]
99
  self.auto_dna_tags = auto_dna_tags
@@ -711,14 +719,21 @@ class HybridDNATokenizer(PreTrainedTokenizer):
711
 
712
  return (vocab_file,)
713
 
714
- def save_pretrained(self, save_directory: str, **kwargs):
715
- os.makedirs(save_directory, exist_ok=True)
716
-
717
- # Save base tokenizer files with the wrapper's effective chat template.
718
- self._base_tokenizer.chat_template = self.chat_template
719
- self._base_tokenizer.save_pretrained(save_directory)
720
-
721
- # Save DNA config
 
 
 
 
 
 
 
722
  dna_config = {
723
  "k": self.k,
724
  "dna_start_id": self.dna_start_id,
@@ -727,103 +742,31 @@ class HybridDNATokenizer(PreTrainedTokenizer):
727
  "auto_dna_tags": self.auto_dna_tags,
728
  "tail": self.tail,
729
  }
730
-
731
  dna_config_path = os.path.join(save_directory, "dna_config.json")
732
  with open(dna_config_path, "w", encoding="utf-8") as f:
733
  json.dump(dna_config, f, indent=2)
734
-
735
- # Update tokenizer_config.json with auto_map
736
- config_path = os.path.join(save_directory, "tokenizer_config.json")
737
-
738
- if os.path.exists(config_path):
739
- with open(config_path, "r") as f:
740
- config = json.load(f)
741
- else:
742
- config = {}
743
-
744
- config.update({
745
- "tokenizer_class": "HybridDNATokenizer",
746
- "auto_map": {
747
- "AutoTokenizer": ["tokenizer.HybridDNATokenizer", None]
748
- },
749
- "k": self.k,
750
- "auto_dna_tags": self.auto_dna_tags,
751
- "tail": self.tail,
752
- })
753
-
754
- with open(config_path, "w", encoding="utf-8") as f:
755
- json.dump(config, f, indent=2, ensure_ascii=False)
756
-
757
- # Copy this tokenizer.py to save directory
758
- import shutil
759
- src_py = os.path.abspath(__file__)
760
- dst_py = os.path.join(save_directory, "tokenizer.py")
761
- if os.path.exists(src_py) and src_py != dst_py:
762
- shutil.copy2(src_py, dst_py)
763
-
764
- return (save_directory,)
765
 
766
  @classmethod
767
- def from_pretrained(
768
- cls,
769
- pretrained_model_name_or_path,
770
- *args,
771
- cache_dir=None,
772
- force_download=False,
773
- local_files_only=False,
774
- token=None,
775
- revision="main",
776
- trust_remote_code=False,
777
- **kwargs,
778
- ):
779
- # Custom loader: PreTrainedTokenizer.from_pretrained is incompatible with this
780
- # tokenizer's __init__ (it re-passes bos_token etc.), so construct directly from
781
- # bio_config/dna_config. That bypasses standard metadata loading, so also read
782
- # chat_template from tokenizer_config.json (vLLM / apply_chat_template need it).
783
  from transformers.utils.hub import cached_file
784
 
785
- proxies = kwargs.pop("proxies", None)
786
- subfolder = kwargs.pop("subfolder", None)
787
- commit_hash = kwargs.pop("_commit_hash", None)
788
  hub_kwargs = {
789
- "cache_dir": cache_dir,
790
- "force_download": force_download,
791
- "local_files_only": local_files_only,
792
- "proxies": proxies,
793
- "revision": revision,
794
- "subfolder": subfolder,
795
- "token": token,
796
- "_commit_hash": commit_hash,
797
- "_raise_exceptions_for_missing_entries": False,
798
  }
799
-
 
 
 
800
  cfg = {}
801
- for _name in ("bio_config.json", "dna_config.json"):
802
- _p = cached_file(pretrained_model_name_or_path, _name, **hub_kwargs)
803
- if _p is not None:
804
- with open(_p, encoding="utf-8") as _f:
805
- cfg = json.load(_f)
806
- break
807
 
808
- _init = {"base_tokenizer_path": pretrained_model_name_or_path}
809
- for _key in ("k", "tail", "auto_dna_tags"):
810
- if _key in cfg:
811
- _init[_key] = cfg[_key]
812
-
813
- tokenizer_config_path = cached_file(pretrained_model_name_or_path, "tokenizer_config.json", **hub_kwargs)
814
- if tokenizer_config_path is not None:
815
- with open(tokenizer_config_path, encoding="utf-8") as _f:
816
- tokenizer_config = json.load(_f)
817
- if tokenizer_config.get("chat_template"):
818
- _init["chat_template"] = tokenizer_config["chat_template"]
819
-
820
- chat_template_path = cached_file(pretrained_model_name_or_path, "chat_template.jinja", **hub_kwargs)
821
- if chat_template_path is not None:
822
- with open(chat_template_path, encoding="utf-8") as _f:
823
- _init["chat_template"] = _f.read()
824
-
825
- _init.update(kwargs)
826
- tokenizer = cls(**_init)
827
  # DNA ids start right after the base vocabulary, so a layout that differs from the saved one would misalign every
828
  # DNA embedding. Fail instead of tokenizing with shifted ids.
829
  for _key in ("dna_start_id", "dna_vocab_size"):
@@ -833,3 +776,7 @@ class HybridDNATokenizer(PreTrainedTokenizer):
833
  f"{getattr(tokenizer, _key)}, so its DNA token ids would not match the model."
834
  )
835
  return tokenizer
 
 
 
 
 
80
  self._build_combined_vocab()
81
 
82
  # Set special tokens
83
+ self._eos_token = kwargs.pop("eos_token", "<|endoftext|>")
84
+ self._pad_token = kwargs.pop("pad_token", self._base_tokenizer.pad_token or "<|endoftext|>")
85
 
86
  # Initialize parent class
87
  super().__init__(
88
  eos_token=self._eos_token,
89
  pad_token=self._pad_token,
90
+ k=k,
91
+ tail=tail,
92
+ auto_dna_tags=auto_dna_tags,
93
  **kwargs
94
  )
95
 
96
  # Preserve the base tokenizer's AddedToken metadata. These tokens are
97
  # already present in the combined vocabulary at their original IDs, so
98
  # this only registers their metadata and does not grow the vocabulary.
99
+ # Keep any metadata restored by the parent loader or supplied by the caller.
100
+ existing_ids = self.added_tokens_decoder
101
+ self.add_tokens([
102
+ existing_ids.get(token_id, token)
103
+ for token_id, token in self._base_tokenizer.added_tokens_decoder.items()
104
+ ])
105
 
106
  self.special_tokens = self.dna_special_tokens + [self._eos_token, self._pad_token]
107
  self.auto_dna_tags = auto_dna_tags
 
719
 
720
  return (vocab_file,)
721
 
722
+ def _save_pretrained(
723
+ self,
724
+ save_directory,
725
+ file_names,
726
+ legacy_format=None,
727
+ filename_prefix=None,
728
+ ):
729
+ # The parent saves this wrapper's configuration and special-token metadata.
730
+ # Delegate only the BPE assets to the underlying tokenizer's save hook.
731
+ file_names = self._base_tokenizer._save_pretrained(
732
+ save_directory,
733
+ file_names,
734
+ legacy_format=legacy_format,
735
+ filename_prefix=filename_prefix,
736
+ )
737
  dna_config = {
738
  "k": self.k,
739
  "dna_start_id": self.dna_start_id,
 
742
  "auto_dna_tags": self.auto_dna_tags,
743
  "tail": self.tail,
744
  }
 
745
  dna_config_path = os.path.join(save_directory, "dna_config.json")
746
  with open(dna_config_path, "w", encoding="utf-8") as f:
747
  json.dump(dna_config, f, indent=2)
748
+ return file_names + (dna_config_path,)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
749
 
750
  @classmethod
751
+ def from_pretrained(cls, pretrained_model_name_or_path, *args, **kwargs):
752
+ tokenizer = super().from_pretrained(pretrained_model_name_or_path, *args, **kwargs)
753
+
 
 
 
 
 
 
 
 
 
 
 
 
 
754
  from transformers.utils.hub import cached_file
755
 
 
 
 
756
  hub_kwargs = {
757
+ key: kwargs[key]
758
+ for key in ("cache_dir", "force_download", "local_files_only", "token", "revision", "subfolder", "_commit_hash")
759
+ if key in kwargs
 
 
 
 
 
 
760
  }
761
+ config_path = cached_file(
762
+ pretrained_model_name_or_path, "dna_config.json",
763
+ _raise_exceptions_for_missing_entries=False, **hub_kwargs,
764
+ )
765
  cfg = {}
766
+ if config_path is not None:
767
+ with open(config_path, encoding="utf-8") as f:
768
+ cfg = json.load(f)
 
 
 
769
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
770
  # DNA ids start right after the base vocabulary, so a layout that differs from the saved one would misalign every
771
  # DNA embedding. Fail instead of tokenizing with shifted ids.
772
  for _key in ("dna_start_id", "dna_vocab_size"):
 
776
  f"{getattr(tokenizer, _key)}, so its DNA token ids would not match the model."
777
  )
778
  return tokenizer
779
+
780
+
781
+ # Include the custom implementation and AutoTokenizer mapping in saved checkpoints.
782
+ HybridDNATokenizer.register_for_auto_class("AutoTokenizer")