Skip to content

fix processing_utils.py: avoid deepcopying tokenizer in ProcessorMixin to improve performance - #44894

Merged
ydshieh merged 1 commit into
mainfrom
debug_processor
Mar 23, 2026
Merged

ydshieh merged 1 commit into
mainfrom
debug_processor

Conversation

@ydshieh

@ydshieh ydshieh commented Mar 20, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

ProcessorMixin.to_dict() was calling copy.deepcopy(self.__dict__) on the entire processor, including the tokenizer, even though the tokenizer is always deleted from the output immediately after (since tokenizers are saved separately via their own files).

For tokenizers with large vocabularies (e.g. CohereTokenizer), this caused ~2.5 million unnecessary recursive deepcopy calls, adding ~2.5s of overhead every time to_dict() was called (e.g. during save_pretrained).

Fix

Identify and exclude tokenizer attributes before the deepcopy, so only the attributes that are actually needed in the output are copied.

Impact

Primitive deepcopy calls deepcopy total time
Before 1,272,277 2.446s
After 1,335 0.002s

~1000x reduction in deepcopy calls. No behavior change — the resulting output dict is identical in both cases.

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

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.

@ydshieh

ydshieh commented Mar 20, 2026 •

Copy link
Copy Markdown
Collaborator Author

Profiling script

To investigate the issue, we used cProfile to measure deepcopy activity during AyaVisionProcessor.from_pretrained(). The script profiles the load call and reports copy.py stats, callers of deepcopy, and a summary of total calls and time — making it easy to compare before and after the fix.

import cProfile
import pstats
import io
from transformers import AyaVisionProcessor
from transformers.models.got_ocr2.image_processing_got_ocr2 import GotOcr2ImageProcessor
from transformers.models.cohere.tokenization_cohere import CohereTokenizer

model_id = "hf-internal-testing/namespace-CohereForAI-repo_name_aya-vision-8b"

pr = cProfile.Profile()
pr.enable()

processor = AyaVisionProcessor.from_pretrained("local")

pr.disable()

# --- Human-readable table filtered to copy.py ---
print("=" * 60)
print("COPY.PY STATS (sorted by tottime)")
print("=" * 60)
s = io.StringIO()
ps = pstats.Stats(pr, stream=s)
ps.sort_stats("tottime")
ps.print_stats(r"copy\.py")
print(s.getvalue())

# --- Callers of deepcopy ---
print("=" * 60)
print("WHO CALLS deepcopy (top callers)")
print("=" * 60)
s2 = io.StringIO()
ps2 = pstats.Stats(pr, stream=s2)
ps2.sort_stats("tottime")
ps2.print_callers(r"copy\.py:.*\(deepcopy\)")
print(s2.getvalue())

# --- Programmatic summary for deepcopy specifically ---
print("=" * 60)
print("DEEPCOPY SUMMARY")
print("=" * 60)
for entry in pr.getstats():
    if hasattr(entry.code, "co_filename") and \
       "copy" in entry.code.co_filename and \
       entry.code.co_name == "deepcopy":
        total_calls = entry.callcount + entry.reccallcount
        print(f"  primitive calls : {entry.callcount:>12,}")
        print(f"  recursive calls : {entry.reccallcount:>12,}")
        print(f"  total calls     : {total_calls:>12,}")
        print(f"  inline time     : {entry.inlinetime:>12.3f}s")
        print(f"  total time      : {entry.totaltime:>12.3f}s")

Results

Without fix:

============================================================
DEEPCOPY SUMMARY
============================================================
  primitive calls :    1,272,277
  recursive calls :    1,272,255
  total calls     :    2,544,532
  inline time     :        1.036s
  total time      :        2.769s

With fix:

============================================================
DEEPCOPY SUMMARY
============================================================
  primitive calls :        1,335
  recursive calls :        1,313
  total calls     :        2,648
  inline time     :        0.001s
  total time      :        0.002s

@ydshieh ydshieh changed the title fix(processing_utils): avoid deepcopying tokenizer in ProcessorMixin.… fix processing_utils.py: avoid deepcopying tokenizer in ProcessorMixin to improve performance Mar 20, 2026

@ArthurZucker ArthurZucker left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GGs

@ydshieh
ydshieh added this pull request to the merge queue Mar 23, 2026
Merged via the queue into main with commit a868375 Mar 23, 2026
29 checks passed
@ydshieh
ydshieh deleted the debug_processor branch March 23, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants