背景痛点为什么“下模型”比“跑模型”还累第一次用 ChatTTS 做语音合成 Demo 时我把脚本跑到服务器上结果卡在 1.8 GB 的chattts-v1.pt整整两天——不是 502 就是下到 99 % 断线重来。更尴尬的是同组小伙伴用迅雷手动拖下来一份MD5 却对不上推理直接报size mismatch版本号看似一致内部权重 key 却悄悄升了级。总结下来常见坑位就这三点网络超时单连接 TCP 一旦丢包整个文件前功尽弃大文件传输不稳定1 GB 的权重NAS、家用宽带晚高峰速率抖动分分钟掉线版本冲突GitHub Release 里 tag 一样但作者偷偷 re-upload不 checksum 根本发现不了技术方案三种下载姿势的权衡场景限定在公司开发机无外网 P2P 白名单于是我只对比了“HTTP 直拖 vs 分块断点续传”两条路线顺手把 P2P 思路也列出来方便读者扩展。HTTP 直接下载代码最少适合小文件失败成本高大文件几乎不可用分块下载Range-request把 1.8 GB 切成 64 MB 一块任意一块失败只重试该块支持多线程能把 20 Mbps 小水管跑满P2PBitTorrent/IPFS带宽利用率高校验自带但需要 tracker 网络或额外网关企业内网常被封端口综合下来分块断点续传是“能落地 改动小 不依赖运维”的最优解。核心实现带校验的断点续传思路一句话“本地先建一个和远程同大小的空文件 → 按块请求写入 → 每完成一块记 checkpoint → 全部块 OK 后做 MD5 比对 → 失败块自动重试”。关键点用requests.get(..., headers{Range: bytesstart-end})取块块大小建议 8~64 MB过小 HTTP 握手开销大过大失去断点意义校验放在最后避免每块 MD5 把磁盘 I/O 打满重试采用“指数退避 最大 5 次”超次写入日志并抛异常防止死循环代码时间Python 3.8 可直接跑下面这份脚本我放在项目scripts/pull_model.pyCI 里调用Windows / Linux 行为一致。#!/usr/bin/env python3 chattts_pull.py 分块下载 断点续传 MD5 校验 用法: python chattts_pull.py --url https://github.com/xxx/chattts-v1.pt/releases/download/v1/chattts-v1.pt \ --md5 8f04c8bf6ea2b8e83a... \ -o ./models/chattts-v1.pt import os, sys, time, hashlib, requests from tqdm import tqdm CHUNK_SIZE 32 * 1024 * 1024 # 32 MB MAX_RETRY 5 SESSION requests.Session() SESSION.headers.update({User-Agent: chattts-pull/1.0}) def download_chunk(url, start, end, fd, pbar): 拉取并写入单块带重试 headers {Range: fbytes{start}-{end}} for attempt in range(1, MAX_RETRY 1): try: r SESSION.get(url, headersheaders, streamTrue, timeout30) r.raise_for_status() for piece in r.iter_content(chunk_size1024 * 64): fd.write(piece) pbar.update(end - start 1) return except Exception as e: wait 2 ** attempt print(f[warn] chunk {start}-{end} fail {attempt}/{MAX_RETRY}: {e}, retry in {wait}s) time.sleep(wait) raise RuntimeError(fchunk {start}-{end} still fail after {MAX_RETRY} retries) def already_done(checkpoint, total_size): 判断本地文件是否已完整 return os.path.exists(checkpoint) and os.path.getsize(checkpoint) total_size def pull(url, expect_md5, out_file): # 0. 基本路径准备 tmp_file out_file .downloading os.makedirs(os.path.dirname(out_file), exist_okTrue) # 1. 拿文件大小 head SESSION.head(url, allow_redirectsTrue) total_size int(head.headers[Content-Length]) print(fremote size{total_size 20} MB) # 2. 已存在且大小对则跳过 if already_done(out_file, total_size): print(file already exists, skip download) return # 3. 建立空文件并逐块写 with open(tmp_file, wb) as fd, tqdm(totaltotal_size, unitB, unit_scaleTrue) as bar: for start in range(0, total_size, CHUNK_SIZE): end min(start CHUNK_SIZE - 1, total_size - 1) download_chunk(url, start, end, fd, bar) # 4. MD5 校验 print(running md5 checksum ...) calc hashlib.md5() with open(tmp_file, rb) as f: for chunk in iter(lambda: f.read(1024 * 1024), b): calc.update(chunk) if calc.hexdigest() ! expect_md5: os.remove(tmp_file) raise ValueError(fmd5 mismatch! expect{expect_md5} got{calc.hexdigest()}) # 5. 成功原子替换 os.replace(tmp_file, out_file) print(download verify done -, out_file) if __name__ __main__: import argparse ap argparse.ArgumentParser() ap.add_argument(--url, requiredTrue, helpdirect download url) ap.add_argument(--md5, requiredTrue, helpexpected md5 hex) ap.add Avenue(-o, --output, requiredTrue, helplocal path) args ap.parse_args() pull(args.url, args.md5, args.output)脚本依赖只有requests与tqdmpip install即可。CI 里调用后模型落盘路径固定下游推理脚本直接torch.load()不再担心“下到一半”的残片。性能优化把 20 Mbps 小水管跑满多线程上面代码是单线程顺序块若带宽富余可把range()拆成若干任务丢进concurrent.futures.ThreadPoolExecutor。经验值4~8 线程即可再多会触发远程 CDN 限流本地缓存对同一模型不同项目统一软链到/mnt/shared/models/避免重复落盘下载前先HEAD对比Last-Modified作者没更新就跳过节省 100 % 流量磁盘预分配Linux 下fallocate -l 1.8G file秒建空文件防止下载中途磁盘占满导致写入失败避坑指南版本、空间与依赖版本兼容ChatTTS 官方 Release 页面只给 tag不给 commit。拉取后把config.json中的transformers_version字段与本地环境比对不一致就新建虚拟环境别硬怼磁盘空间记得留 2× 模型大小的余量下载 解压/重命名中间会同时存在两份优雅降级如果跑 CI 的节点在海外而模型源国内镜像更快脚本里加--mirror选项先测延迟再选源失败时自动回退主站保证流水线稳定性延伸思考把套路搬到 Stable Diffusion、Llama 2这套“分块 断点 校验”思路其实通用块大小可按文件尺寸动态算如 1 GB 以内 8 MB10 GB 以上 128 MB校验可换成 SHA256更安全若仓库自带.sha256文件脚本里直接读列表即可多线程数做成自适应先跑 1 线程测速逐步上调直到带宽不再增长或出现 429 就停止下次再遇到“大模型下载”需求把脚本拷过去改两行配置就能用真正做到“一次编写到处偷懒”。折腾完这一圈我最大的感受是让 AI 帮你写代码之前得先让代码把 AI 的“粮草”稳稳拉到本地。把下载流程做成可靠、可追踪、可复现的脚本看似边角料却能在真正落地时帮团队省下大把等待与重试时间。希望这份小笔记也能让你的下一次部署少一点熬夜多一点从容。