Repository navigation
fix: improve processor loading performance by avoiding redundant tokenizer parsing - #44927
Conversation
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
BenchmarkScript used ( import time
import os
import sys
from transformers import (
CohereTokenizer,
LlamaTokenizer,
GemmaTokenizer,
)
N = 100
TOKENIZERS = {
"CohereTokenizer": (CohereTokenizer, "hf-internal-testing/namespace-CohereForAI-repo_name_aya-vision-8b"),
"LlamaTokenizer": (LlamaTokenizer, "huggyllama/llama-7b"),
"GemmaTokenizer": (GemmaTokenizer, "google/gemma-7b"),
}
if len(sys.argv) < 2:
print(f"Usage: python bench.py <TokenizerClassName>")
print(f"Available: {', '.join(TOKENIZERS)}")
sys.exit(1)
name = sys.argv[1]
if name not in TOKENIZERS:
print(f"Unknown tokenizer: {name}. Available: {', '.join(TOKENIZERS)}")
sys.exit(1)
cls, hub_id = TOKENIZERS[name]
local_path = f"local_bench_{name}"
# Download from hub and save locally if not cached
if not os.path.isdir(local_path):
print(f"Downloading {name} from {hub_id}...")
obj = cls.from_pretrained(hub_id)
obj.save_pretrained(local_path)
print(f"Saved to {local_path}")
# Run benchmark
times = []
for _ in range(N):
t0 = time.perf_counter()
cls.from_pretrained(local_path)
times.append(time.perf_counter() - t0)
total = sum(times)
print(f"{name} N={N} total={total:.3f}s avg={total/N:.3f}s min={min(times):.3f}s max={max(times):.3f}s")Run with: Results (N=100)
|
| minimal_model["vocab"] = {} | ||
| minimal_model["merges"] = [] | ||
| else: | ||
| minimal_model["vocab"] = {} |
There was a problem hiding this comment.
like this one can be initialized out of this if block since it's always done
| # "type" field in the "model" section, so we cannot determine the model type from JSON. | ||
| # In both cases we fall back to the original from_file path (no performance improvement). | ||
| model_type = tokenizer_json.get("model", {}).get("type") | ||
| if model_type in ("BPE", "WordPiece", "WordLevel"): |
There was a problem hiding this comment.
this list could be a constant alongside MODEL_TO_TRAINER_MAPPING
There was a problem hiding this comment.
I will change it to model_type != "Unigram"
|
|
||
| local_kwargs["post_processor"] = tok_from_file.post_processor | ||
| local_kwargs["tokenizer_padding"] = tok_from_file.padding | ||
| local_kwargs["tokenizer_truncation"] = tok_from_file.truncation |
There was a problem hiding this comment.
for me this should be a feature request for tokenizers to allows post_processor.from_str().
Its kinda hacky but I guess should work well!
099e875 to
05b3467
Compare
…file when only post_processor/padding/truncation are needed
… type is missing in tokenizer.json
…zation to BPE/WordPiece/WordLevel only
…m and older formats fall back to from_file
05b3467 to
9bea0f9
Compare
| model_type = tokenizer_json.get("model", {}).get("type") | ||
| if model_type != "Unigram": | ||
| minimal_tokenizer_json = dict(tokenizer_json) | ||
| minimal_model = dict(tokenizer_json["model"]) | ||
| minimal_model["vocab"] = {} | ||
| if model_type == "BPE": | ||
| minimal_model["merges"] = [] |
There was a problem hiding this comment.
@tarekziade applied your suggestions, thx (force pushed after git rebase)
Problem
In
TokenizersBackend.convert_to_native_format(), when a tokenizer has a custom__init__(theelifbranch),tokenizer.jsonwas parsed twice:TokenizerFast.from_file(fast_tokenizer_file)— full Rust parse including the entire vocab (~0.6s for large tokenizers likeCohereTokenizerwith 256k entries) — used only to extractpost_processor,padding,truncationjson.load(tokenizer_handle)— Python parse of the same file — to extractvocabandmergesFix
Read the file once with
json.load, then build a minimal tokenizer (same structure but empty vocab/merges) viaTokenizerFast.from_strto cheaply obtain the Rustpost_processor/padding/truncationobjects — without parsing the full vocab.Benchmark
Measured over 300 runs of
AyaVisionProcessor.from_pretrained("local")(AyaVisionProcessorusesCohereTokenizerwhich has a custom__init__):