Skip to content

Emit progress_total events from PreTrainedModel.from_pretrained()#1609

Closed
JacobiusMakes wants to merge 4 commits intohuggingface:mainfrom
JacobiusMakes:feat/model-download-total-progress
Closed

Emit progress_total events from PreTrainedModel.from_pretrained()#1609
JacobiusMakes wants to merge 4 commits intohuggingface:mainfrom
JacobiusMakes:feat/model-download-total-progress

Conversation

@JacobiusMakes
Copy link
Copy Markdown
Contributor

Summary

Closes #1052.

When users call PreTrainedModel.from_pretrained() directly (rather than through pipeline()), they currently have no way to know the total size of all model files before downloads begin. This makes it impossible to render a single aggregate progress bar.

This PR adds the same progress_total event infrastructure that already exists in pipeline() to PreTrainedModel.from_pretrained():

  • After resolving the model config but before starting downloads, gathers file metadata (sizes) for all expected model files via lightweight Range/HEAD requests using the existing get_model_files() and get_file_metadata() utilities
  • Wraps the user's progress_callback to emit progress_total events on each progress update, containing aggregate loaded/total byte counts plus a per-file breakdown in files
  • Follows the exact same pattern already used in pipelines.js (lines 139-178)
  • Fails gracefully: if metadata fetching fails (e.g., local-only models), downloads proceed normally without total progress tracking

Example usage

const model = await AutoModel.from_pretrained('model-id', {
  progress_callback: (info) => {
    if (info.status === 'progress_total') {
      // info.progress  — 0..100 across all files
      // info.loaded    — total bytes loaded so far
      // info.total     — total bytes expected
      // info.files     — per-file { loaded, total } map
      updateProgressBar(info.progress);
    }
  },
});

Test plan

  • Verified prettier formatting passes
  • Ran tests/utils/hub.test.js — all pass
  • Ran tests/utils/model_registry.test.js — all pass
  • Syntax check (node -c) passes
  • Manual test with a multi-file model to verify progress_total events fire correctly

Changes

packages/transformers/src/models/modeling_utils.js — Added ~60 lines to PreTrainedModel.from_pretrained() that gather file metadata upfront and wrap the progress callback to emit progress_total events.

🤖 Generated with Claude Code

Closes huggingface#1052. When a progress_callback is provided, gather file
metadata (sizes) upfront via HEAD/Range requests before downloads
begin.  This lets consumers build a single aggregate progress bar
across all model files, matching the behavior already present in the
pipeline() API.

The implementation reuses the existing get_model_files() and
get_file_metadata() utilities and follows the same wrapping pattern
used in pipelines.js, emitting progress_total events that include
per-file and overall loaded/total byte counts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nico-martin
Copy link
Copy Markdown
Collaborator

I ran into the exact same ptoblem too yesterday :D
Thank you so much for making a PR. Will check it out asap!

@nico-martin nico-martin self-requested a review March 27, 2026 13:59
Copy link
Copy Markdown
Collaborator

@nico-martin nico-martin left a comment

Choose a reason for hiding this comment

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

Thanks for this PR, I like the direction. I noticed two issues while testing that I think we should fix before merging:

  1. Total progress will always stay below 100%
  • config.json is loaded first.
  • Then the total-progress tracker is built and includes config.json in the total bytes.
  • But that file is already finished, so its loaded bytes are never added to the new tracker.

So in practice, the final progress_total can get stuck under 100% even when all downloads are done.

  1. Duplicate progress_total events when using pipeline()
  • pipeline() already emits aggregated progress_total.
  • This PR also emits progress_total from model loading.
  • In pipeline(), both streams are forwarded/emitted, so clients receive both.

So users can see about 2x progress_total events for model download.

@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.

Fixes two issues raised in code review:

1. config.json is fetched by AutoConfig.from_pretrained() before the
   progress tracker is initialized. Pre-mark it as fully loaded in
   files_loading so total progress reaches 100%.

2. pipeline() already wraps the callback with progress_total events.
   When from_pretrained() wrapped it again, users got duplicate events.
   Fix: mark wrapped callbacks with _progress_total_wrapped flag.
   from_pretrained() checks the flag and skips wrapping if already set.
   pipeline()'s wrapper now also sets this flag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JacobiusMakes
Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review! Both issues fixed in e28ce2c:

1) config.json progress stuck below 100% — config.json is now pre-marked as fully loaded in files_loading since AutoConfig.from_pretrained() fetches it before the tracker is set up.

2) Duplicate progress_total with pipeline() — Wrapped callbacks now carry a _progress_total_wrapped flag. from_pretrained() checks for it and skips wrapping if pipeline() already set it up. pipeline()'s wrapper also sets this flag for consistency.

@nico-martin
Copy link
Copy Markdown
Collaborator

There is still a problem with the prod build:

tsc --build

Error: src/models/modeling_utils.js(430,53): error TS2339: Property '_progress_total_wrapped' does not exist on type 'ProgressCallback'.
Error: src/pipelines.js(180,33): error TS2339: Property '_progress_total_wrapped' does not exist on type '(info: ProgressInfo) => void'.
 ELIFECYCLE  Command failed with exit code 1.
/home/runner/work/transformers.js/transformers.js/packages/transformers:
 ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  @huggingface/transformers@4.0.0-next.10 build: `node scripts/build.mjs && pnpm typegen`
Exit status 1
 ELIFECYCLE  Command failed with exit code 1.
Error: Process completed with exit code 1.

https://github.com/huggingface/transformers.js/actions/runs/23668078336/job/69010757045?pr=1609

@xenova
Copy link
Copy Markdown
Collaborator

xenova commented Mar 28, 2026

thanks for the PR! I think a better way to handle this is with a wrapper function class instance which can detect if we need to wrap the object using an instanceof test. this means we don't need to attach hacky properties like _progress_total_wrapped to it.

@xenova
Copy link
Copy Markdown
Collaborator

xenova commented Mar 29, 2026

merged via #1615

@xenova xenova closed this Mar 29, 2026
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.

Provide file size information in advance to enable single file download progress bar

4 participants