ollama - 💡(How to fix) Fix Transfer-based pulls restart from scratch after interruption, unlike the standard downloader [1 participants]

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
ollama/ollama#15320Fetched 2026-04-08 02:44:12
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
0
Author
Participants
RAW_BUFFERClick to expand / collapse

Hi, I dug through the repo after hitting an interrupted large-model download and found what looks like a real inconsistency in pull behavior.

The standard downloader in server/download.go already has resumable behavior built in. It keeps partial state, tracks per-part progress, and resumes with HTTP Range requests. That part looks pretty solid.

But the transfer path seems different.

server/images.go routes some pulls through pullWithTransfer(...), and x/imagegen/transfer/transfer.go explicitly says:

No resume: If a transfer fails, it restarts from scratch.

From x/imagegen/transfer/download.go, the current behavior also seems to match that comment:

  • each blob is downloaded as a whole
  • data is written to dest + ".tmp"
  • the temp file is removed on failure
  • retries restart the blob from zero
  • there is no Range-based continuation for partial .tmp files

So depending on which pull path gets selected, interrupted downloads can behave very differently:

  • standard downloader: resumable
  • transfer downloader: restart from scratch

That makes pull recovery feel inconsistent, especially for larger downloads or unstable connections.

Why this seems worth fixing

For multi-GB model pulls, restarting from zero after a transient interruption is painful in time, bandwidth, and trust. Even if the transfer path was originally optimized for many small blobs, the user-facing behavior still ends up feeling unreliable compared to the standard path.

Possible direction

A relatively small step toward parity might be:

  1. keep the .tmp file on transient failure
  2. detect its current size on retry
  3. resume with Range: bytes=<offset>-
  4. append to the temp file
  5. verify digest and size at the end
  6. delete temp only on corruption or invalid state

That would keep the transfer path simple while avoiding full restart-from-zero behavior.

If resumability is intentionally out of scope for transfer, even clearer progress/log messaging would help a lot so users know that retries on this path are restarting the current blob rather than resuming.

Relevant files

  • server/download.go
  • server/images.go
  • x/imagegen/transfer/transfer.go
  • x/imagegen/transfer/download.go

I also found an older issue around resumable downloads, but this one seems narrower and more current: the main downloader already has resume logic, while the transfer path explicitly does not.

Happy to take a stab at this if the direction makes sense.

extent analysis

TL;DR

Modify the transfer downloader in x/imagegen/transfer/download.go to resume interrupted downloads by keeping the temporary file, detecting its size, and appending to it with Range requests.

Guidance

  • Review the pullWithTransfer function in server/images.go to understand when the transfer path is selected and how it differs from the standard downloader.
  • Implement the proposed 6-step resumability plan in x/imagegen/transfer/download.go to achieve parity with the standard downloader.
  • Consider adding clearer progress and log messaging to inform users about retry behavior on the transfer path.
  • Verify the changes by testing interrupted downloads and checking that the transfer path resumes correctly.

Example

// Pseudo-code example of resuming a download in x/imagegen/transfer/download.go
func downloadBlob(dest string, url string) {
    // ...
    tmpFile := dest + ".tmp"
    if fileExists(tmpFile) {
        // Detect current size of tmp file
        fileSize := getFileSize(tmpFile)
        // Resume with Range request
        httpReq.Header.Set("Range", "bytes="+strconv.Itoa(fileSize)+"-")
        // Append to tmp file
        file, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_APPEND, 0644)
        // ...
    }
    // ...
}

Notes

This solution assumes that the transfer path is intended to provide resumable downloads. If resumability is out of scope, clearer progress messaging can still improve user experience.

Recommendation

Apply the proposed workaround to modify the transfer downloader, as it provides a relatively small step toward parity with the standard downloader and improves user experience for large downloads.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING