from tokenizers import Tokenizer from tokenizers.models import WordLevel from tokenizers.pre_tokenizers import Whitespace from transformers import PreTrainedTokenizerFast from datasets import Dataset from datasets.fingerprint import Hasher def _make_mutable_backend_tokenizer() -> PreTrainedTokenizerFast: # Build a tiny tokenizer entirely locally (no network), backed by `tokenizers.Tokenizer`. vocab = {"[UNK]": 0, "[PAD]": 1, "hello": 2, "world": 3} backend = Tokenizer(WordLevel(vocab=vocab, unk_token="[UNK]")) backend.pre_tokenizer = Whitespace() return PreTrainedTokenizerFast(tokenizer_object=backend, unk_token="[UNK]", pad_token="[PAD]") def test_hasher_hash_tokenizer_stable_after_call(): tok = _make_mutable_backend_tokenizer() h0 = Hasher.hash(tok) _ = tok(["hello world"], truncation=True, padding="max_length", max_length=8) h1 = Hasher.hash(tok) assert h0 == h1 def test_map_cache_reused_with_tokenizer_after_call(tmp_path): # Regression test for https://github.com/huggingface/datasets/issues/3847 # # Tokenizers can mutate backend truncation/padding state when called, which used to make the # dataset transform fingerprint unstable and prevented cache reuse. tok = _make_mutable_backend_tokenizer() raw = Dataset.from_dict({"text": ["hello world"] * 1000}) stored = tmp_path / "stored" raw.save_to_disk(stored) raw = Dataset.load_from_disk(stored) def tokenize(examples): return tok(examples["text"], truncation=True, padding="max_length", max_length=8) res1 = raw.map(tokenize, batched=True, load_from_cache_file=True, remove_columns=["text"]) res2 = raw.map(tokenize, batched=True, load_from_cache_file=True, remove_columns=["text"]) assert res1.cache_files and res2.cache_files assert res1.cache_files[0]["filename"] == res2.cache_files[0]["filename"]