Case Study · Hands-on

多线程分片与镜像下载
国内网络下载 HuggingFace 模型实例

一次真实的 1.3GB 模型下载走查:从镜像选型、绕过官方库,到多线程 Range 分片把速度拉高 20 倍以上。

发布于 2026-08 Windows / PowerShell 案例对象:Anima 模型组件
本页目录
  1. 01案例背景与目标
  2. 02第一关:镜像选型与诊断
  3. 03第二关:绕过官方库的坑
  4. 04第三关:单连接续传先下 VAE
  5. 05第四关:多线程分片下大文件
  6. 06脚本与跑法全解
  7. 07实测数据与结论
  8. 08速查卡与 FAQ

01案例背景与目标

一句话结果:同一台机器,单连接实测 273 KB/s;改用镜像 hf-mirror.net + 8 线程 Range 分片后,1.09GB 文件峰值冲到 157 MB/s,约 3 分钟下完。全程的核心就三件事:换对源、别用兼容不了的库、用多线程分片。

要在 ComfyUI 里跑新的开源模型 Anima,需要先从 HuggingFace 拉取它的几个组件文件。

这个仓库把大型派生文件拆在 split_files/ 目录下,我们实际要下的是其中两块:

组件文件大小目录
VAEqwen_image_vae.safetensors242 MBsplit_files/vae
文本编码器qwen_3_06b_base.safetensors1.09 GBsplit_files/text_encoders

难点不在模型本身,而在“从国内把这两个文件拉下来”。下面这是我们一步一步走过的完整过程。

  • 先试探官方库 + 常用镜像 —— 速度慢、甚至断流。
  • 诊断镜像源 —— 发现默认推荐源大文件其实是境外回源。
  • 绕过官方库 —— 改用 HTTP Range 直连 resolve/main/
  • 单连接续传下 242MB VAE —— 稳,但只有几百 KB/s。
  • 多线程分片下 1.09GB 编码器 —— 8 线程并行,速度暴涨。

02第一关:镜像选型与诊断

2.1默认推荐的 hf-mirror.com 这里失灵

社区最常推的国内加速是设环境变量指向 hf-mirror.com

$env:HF_ENDPOINT = "https://hf-mirror.com"

对元数据、小仓库它很稳;但当我们下到这份大文件时,连接被反复 重置。原因是这个源会把大文件 302 重定向回境外对象存储,你名义上走镜像、实际源头还在国外,于是国内网络继续断流。

2.2换 hf-mirror.net 并用一条命令确认

改试完整缓存节点 hf-mirror.net[1]先用 HEAD 探测响应,确认内容在本站直连、且支持字节段请求:

curl.exe -sI "https://hf-mirror.net/circlestone-labs/Anima/resolve/main/split_files/text_encoders/qwen_3_06b_base.safetensors"
# 关注两行:HTTP/1.1 200  与  content-length: 1192135096

诊断结论

返回 200 说明内容直接在本镜像;Content-Length: 1192135096 拿到文件大小并证明可做分片。这个源能让我下。

响应含义我们的情况
200 / 206内容本站直连、支持分片hf-mirror.net
301/302回源到境外hf-mirror.com

03第二关:绕过官方库的坑

拿到可用镜像后,最初尝试用官方库一把梭:

python -c "from huggingface_hub import snapshot_download; snapshot_download('circlestone-labs/Anima', local_dir='AnimaVAE')"

这里踩了坑

这种写法会在元数据阶段抛 FileMetadataError——huggingface_hub 当前版本与 hf-mirror.net 的 API 不兼容。其实连下载环节都还没进就中断了。

解决办法不是升级库,而是干脆绕过库、纯 HTTP 直连。既然我们只差“把文件传下来”,直接按 resolve/main/<路径> 发 Range 请求即可,用 curl 或 Python 都能拿到,兼容问题自动消失。

经验

见到“元数据 / 元数据字段”类报错时,先别怀疑网络,优先检查库与镜像的版本兼容性,必要时改用纯 HTTP 下载。

04第三关:单连接续传先下 VAE

确认源可用后,第一份 242MB 的 VAE 用最简单的方式拿下——curl 单连接断点续传:

curl.exe -L -C - -o "AnimaVAE\split_files\vae\qwen_image_vae.safetensors" \
  "https://hf-mirror.net/circlestone-labs/Anima/resolve/main/split_files/vae/qwen_image_vae.safetensors"
  • -C -:从已下载偏移继续,中断了重跑即可续传。
  • 实测速度约 273 KB/s,242MB 需 10 分钟以上,够用但不够快
  • 地址里的 resolve/main/ 会跟随分支指向实际文件。

取舍

对一两百 MB 的文件,单连接 + 断点续传是性价比最高的——命令短、零依赖。真正逼我们上多线程的,是下一个 1.09GB 的大块头。

05第四关:多线程分片下大文件

1.09GB 用单连接按 273KB/s 要 约 70 分钟,不可接受。原理想清楚了就动手:

8 段
把文件按字节切块
并行
每段独立 Range 请求
157 MB/s
实测峰值速度

把 1.19GB ÷ 8 = 每段约 149MB,8 条连接同时拉各自的段,最后按序合并成一个完整文件。因为每段都是独立请求、独立落盘到 .partN既能并行加速,又天然断点续传

我们用一份 60 行的 Python 标准库脚本实现(见下一节)。启动后日志实时打印:

progress=36.5% 435000000/1192135096 speed=102400KB/s
progress=52.1% 621000000/1192135096 speed=136000KB/s
...
DONE size=1192135096

结果

整份 1.09GB 约 3 分钟下完,末尾 DONE size=1192135096 与期望完全一致,文件完整。

06脚本与跑法全解

6.1完整脚本(Python 标准库,零依赖)

# download_multi.py —— 多线程 Range 分片下载
import os, sys, math, time
import urllib.request
from concurrent.futures import ThreadPoolExecutor

def log(m): print(m, flush=True)

def main():
    url    = sys.argv[1]                      # resolve/ 直连地址
    out    = sys.argv[2]                      # 输出路径
    total  = int(sys.argv[3])                 # 文件总字节数(来自 Content-Length)
    threads= int(sys.argv[4] if len(sys.argv) > 4 else 8)

    chunk  = int(math.ceil(total / threads))
    os.makedirs(os.path.dirname(out), exist_ok=True)
    parts  = [f"{out}.part{i}" for i in range(threads)]

    def fetch(i):
        start = i * chunk
        end   = min(total, start + chunk) - 1
        part  = parts[i]
        have  = os.path.getsize(part) if os.path.exists(part) else 0
        if have >= (end - start + 1): return   # 该段已完成
        tries = 0
        while have < (end - start + 1):        # 断点续传循环
            req = urllib.request.Request(url)
            req.add_header("Range", f"bytes={start + have}-{end}")
            try:
                with urllib.request.urlopen(req, timeout=60) as r:
                    with open(part, "ab") as f:
                        while True:
                            b = r.read(1 << 16)
                            if not b: break
                            f.write(b); have += len(b)
                return
            except Exception:
                tries += 1
                if tries > 30: raise
                time.sleep(2)

    with ThreadPoolExecutor(max_workers=threads) as ex:
        futs = [ex.submit(fetch, i) for i in range(threads)]
        last = 0
        while True:
            done = sum(os.path.getsize(p) if os.path.exists(p) else 0 for p in parts)
            if all(f.done() for f in futs): break
            if done < total:
                log(f"progress={done*100.0/total:.1f}% {done}/{total} speed={(done-last)/1024:.0f}KB/s")
                last = done
            time.sleep(5)

    # 按序合并并清理分片
    with open(out, "wb") as of:
        for p in parts:
            with open(p, "rb") as pf:
                while True:
                    b = pf.read(1 << 16)
                    if not b: break
                    of.write(b)
            os.remove(p)
    log(f"DONE size={os.path.getsize(out)}")

main()

6.2取总字节数,然后跑

# 1) 拿到 Content-Length
curl.exe -sI "<resolve地址>" | findstr /i "content-length"

# 2) 8 线程开跑
python download_multi.py \
  "https://hf-mirror.net/circlestone-labs/Anima/resolve/main/split_files/text_encoders/qwen_3_06b_base.safetensors" \
  "AnimaVAE\split_files\text_encoders\qwen_3_06b_base.safetensors"  1192135096  8
  • 并发数:4–16 均可,别设太大,避免触发限流。
  • 续传:中途断掉直接重跑,脚本会按各 .partN 已下大小接着传。
  • 校验:看最终 DONE size=... 是否等于 Content-Length

07实测数据与结论

本次 hf-mirror.net 同一源、同一台机器:

下 1.09GB 文本编码器,两种方案耗时对比(分钟)
单连接
≈ 70
8 线程分片
≈ 3

单连接按实测 273KB/s 折算 ≈ 70 分钟;多线程为实跑结果(峰值速度,过程中有波动)。

可复用的三条结论

① 大文件优先确认 源真的在国内(看响应码,别被 302 骗);② 用纯 HTTP Range,别跟镜像的库版本兼容较劲;③ 大文件上多线程分片,几 MB/s 是常态。

08速查卡与 FAQ

8.1四步速查

步骤命令 / 动作
取大小 + 确认源curl -sI <resolve> | findstr content-length
小文件(≤几百MB)curl -L -C - -o out <resolve>
大文件python download_multi.py <resolve> <out> <size> 8
校验核对 DONE sizeContent-Length

8.2FAQ

Q1 · 不知道文件大小?

curl -sIContent-Length

Q2 · 下了一半怎么续?

curl 用 -C -;分片脚本会按 .partN 自动续传,别删临时文件。

Q3 · 镜像不通 / 被限流?

降并发、错峰重试;或用国内源兜底(如 ModelScope 魔搭)[2]

Q4 · 这个方法只限 HuggingFace?

不限。凡是支持 HTTP Range 的下载(GitHub Releases、模型站、CDN),分片思路都通用。


参考来源

  1. 社区镜像 hf-mirror.net —— 快速开始与使用说明。 https://hf-mirror.net/docs/quick-start
  2. ModelScope 魔搭 —— 国内模型托管,可作备用下载源。 https://modelscope.cn
  3. Hugging Face Hub 官方文档 —— 仓库结构与 resolve 下载约定。 https://huggingface.co/docs/hub/models-downloading