
如果你曾经试图把一个训练好的模型权重文件、一段演示视频或者某个几十上百MB的依赖包推到 GitHub 上大概率见过这类报错remote: error: File xxx.pth is 312.50 MB; this exceeds GitHubs file size limit of 100 MB remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.第一次遇到时我还以为是网络或者权限问题查了半天才明白——GitHub 对单个文件大小有硬性限制不是网络慢是真的不让你传。这篇文章就把我这些年折腾GitHub 上传大文件的经验一次性讲清楚从官方限制到底层原因从 Git LFS 到各种绕路方案再到误提交大文件之后怎么清洗历史记录全部覆盖。无论你是数据科学方向要传模型文件还是客户端开发要放安装包又或者是把整个资源目录塞进仓库的新手都能找到可落地的方案。1. 先搞清楚 GitHub 的大文件门槛到底卡在哪很多人在动手折腾上传大文件之前根本没仔细看官方限制文档结果就是反复试、反复失败还以为是自己的操作有问题。其实 GitHub 的限制非常明确就几条线先记住再说。1.1 三档限制警告、拒绝、仓库体积建议GitHub 对文件大小有三档不同的反应文件大小GitHub 的反应实际后果25MB ~ 50MB推送时给出警告提示但仍然允许提交页面访问变慢仓库体积开始膨胀50MB ~ 100MB推送时给出明确警告不阻止提交伴随着明显的性能问题官方不推荐超过 100MB硬性拒绝推送直接失败必须改用 Git LFS 或其它方案单仓库超过 1GB建议值官方建议保持仓库在 1GB 以下超过后 clone 和 fetch 会变得极其缓慢这里有一个容易忽略的细节25MB 和 50MB 这两档并不是硬限制只是警告。也就是说你的仓库里可以存在 60MB 的文件也能正常推上去但官方会在提交记录里标记这个文件。真正会被拦截的是单文件超过 100MB这个连 push 都没法完成本地提交已经生成了服务器直接拒绝接收。其实还有一档更隐蔽的限制——GitHub 网页端和 API 上传单个文件只允许 25MB超过 25MB 的文件在网页上直接拖拽上传是不可能的。所以哪怕你的文件只有 30MB想通过浏览器页面上传也是不行的必须在本地用 Git 命令行推送。1.2 为什么 Git 对大文件这么不友好很多人不理解Git 不是版本控制工具吗放个大文件怎么了问题就出在 Git 的存储模型上。Git 保存的不是文件差异而是文件快照。每当你提交一次Git 会把当前所有已跟踪文件的内容做一次压缩快照存进对象库。一个小文件还好但 100MB 的文件每改动一点点Git 就要为这个文件生成一个新的快照对象。提交十次就是十份 100MB 的快照。这还只是一个人改如果是团队协作所有人 clone 时都要把这一堆快照完整拉下来。我见过一个真实案例某算法团队把一个 200MB 的模型权重文件直接放进仓库迭代了 20 多个版本后仓库体积膨胀到了 4.5GB。新同事 clone 一次要下载 4.5GB在普通网络下等半小时起步。最后他们不得不花一天时间重写历史把模型文件从所有提交中剥离出去。另外Git 在压缩大文件时的 CPU 和内存开销也非常可观。push 一个几百 MB 的文件本地要压缩服务器端要解压校验整个过程卡顿不说还经常因为网络波动中断。这就是为什么 Git 官方和 GitHub 都在引导用户使用专门为大文件设计的 LFS 方案而不是直接把大文件当作普通文件管理。2. 首选方案Git LFS 的上手流程与配置细节Git LFS 全称是 Git Large File Storage是 GitHub 官方推荐、也是业界最主流的大文件上传方案。它的核心思路很简单把真正的大文件内容存到专门的 LFS 存储服务器上而 Git 仓库里只保留一个几十字节的指针文件。这样仓库本身不会膨胀clone 速度快大文件也不会触碰 100MB 限制。2.1 安装 Git LFS 客户端Git LFS 是一个独立的命令行工具需要通过包管理器安装macOSbrew install git-lfsUbuntu / Debiansudo apt install git-lfsWindows直接去 git-lfs.github.com 下载安装包或者如果你装了 Chocolateychoco install git-lfs注意一个坑很多人以为装完了 Git LFS 就能直接用其实还需要在终端里执行一次全局初始化git lfs install这一步会往你的全局 Git 配置里写入 filter 规则告诉 Git 哪些文件要交给 LFS 处理。不执行的话后面git lfs track虽然能设置跟踪规则但实际提交时大文件可能还是被当成普通文件处理。2.2 初始化追踪规则与 .gitattributes安装完成后进入你的项目目录设置要跟踪的大文件类型cd your-project git lfs track *.pth git lfs track *.tar git lfs track *.zip这里有几个容易踩的坑。第一git lfs track支持的是 glob 通配符模式*.pth会匹配仓库任意位置的 .pth 文件。如果你只想追踪特定目录下的文件需要写成model/weights/*.pth。第二执行 track 命令后Git LFS 会在你的项目根目录生成一个.gitattributes文件里面记录了所有跟踪规则。这个文件必须和代码一起提交否则别人 clone 你的仓库时不知道这些文件应该按 LFS 方式处理。第三如果你是在已经提交过大文件的仓库里才开始配置 LFS光 track 还不够之前被普通方式提交进去的历史依然存在。需要用git rm --cached把文件从 Git 索引中移除再重新 add 一次让 LFS 接管。git rm --cached *.pth git add *.pth git commit -m migrate large files to LFS这一步的本质是让 Git 重新认识这些文件。git rm --cached把文件从索引中删除但保留本地文件重新 add 后 Git 会读取新的 filter 配置把文件交给 LFS 处理。如果跳过这步文件依然会被当作普通文件对待。2.3 推送包含 LFS 文件的仓库配置好之后正常提交推送即可git add . git commit -m add model weights with LFS git push origin mainpush 过程中你会看到类似这样的输出Uploading LFS objects: 100% (3/3), 468 MB | 12 MB/s, done.这说明 LFS 文件正在被上传到 GitHub 的 LFS 存储服务。上传完成后仓库里实际存在的只是一批几字节的指针文件内容如下version https://git-lfs.github.com/spec/v1 oid sha256:4d4e854c0a3a1b3f0b25f1e0a8b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c size 468000000真正常见的一个问题是为什么 push 的时候看不到 Uploading LFS objects 的输出这通常有两种情况一是你的文件没有被任何 track 规则匹配到Git 把它当成普通文件直接推送了二是 LFS 文件之前已经上传过这次没有变化Git 自然会跳过。如果看不到上传过程先检查.gitattributes里的规则是否覆盖了目标文件类型再用git lfs ls-files查看当前仓库里有哪些 LFS 文件。3. GitHub 端的 LFS 配额管理从免费额度到超限处理很多人不知道GitHub 上的 Git LFS 是有配额限制的而且免费额度很小。如果只是偶尔传一两个模型文件可能感觉不到但当你开始频繁更新大文件或者团队成员较多时配额很快会耗尽。3.1 免费配额具体是多少GitHub 对每个免费账号的 LFS 配额是项目免费额度LFS 存储空间1GBLFS 月度带宽1GB先别觉得 1GB 存储够用。这个存储空间统计的是你的仓库里所有 LFS 文件历史版本的总大小而不是当前版本大小。举例来说你上传了一个 200MB 的模型文件迭代了 5 次每次改动 100MB那仓库占用的 LFS 存储就是 200100100100100 600MB。如果你有多个仓库这个量还会往上叠加。带宽配额用满之后GitHub 不会立刻阻止你访问已有文件但新的 LFS 数据上传会被拒绝或者要求你购买数据包。具体的报错信息通常是Error: LFS upload failed: (error) The LFS operation timed out.3.2 怎么看自己的 LFS 用量在仓库首页点击Settings然后找到左侧的Storage Bandwidth选项就能看到当前仓库的 LFS 存储和带宽消耗详情。这里有四个关键指标需要关注Git LFS Storage当前仓库所有 LFS 文件历史版本的总大小Git LFS Bandwidth一个月内的下载/上传流量Total Storage仓库总存储包括 Git 对象和 LFS 文件Total Bandwidth账户所有仓库的总流量一个非常容易误会的地方是你删除一个 LFS 文件然后重新上传存储空间不会立刻释放。Git LFS 会保留这个文件的对象直到对应的 Git 历史引用也被移除。也就是说如果之前的提交记录里引用了这个对象它就会被一直保留。真正想清理配额必须重写历史或者从 LFS 官方工具中手动删除。小技巧定期检查仓库历史中是否还有已经不再需要的大文件对象。如果项目已经迭代了很久LFS 存储里可能躺着一堆旧版本模型每个都在占用配额。确认删除后用git lfs prune清理本地缓存。3.3 LFS 拉取时的常见问题前面都在讲上传实际上拉取也会遇到问题。最典型的是别人 clone 你的 LFS 仓库时会自动下载所有 LFS 文件。如果对方网络不好或者仓库里 LFS 文件很大clone 会非常慢甚至超时。解决办法有两个跳过 LFS 文件拉取只拿指针文件GIT_LFS_SKIP_SMUDGE1 git clone https://github.com/user/repo.git后续需要哪个 LFS 文件再单独拉取git lfs pull --includemodel/weights/*.pth只拉取 LFS 文件的指针Git 2.15 支持git clone --filterblob:none https://github.com/user/repo.git第二种方案在 clone 时不会下载任何文件内容包括 LFS 对象后续 checkout 到需要大文件的分支时Git 才会按需拉取 LFS 数据。这个方案对大型仓库特别友好初始 clone 只需要几秒钟。4. 不装 Git LFS 也能传大文件几条退路的取舍Git LFS 并不总是最佳方案。它的配额限制严格免费额度小而且要求所有协作成员都安装 LFS 客户端。如果你只是临时要传一个大文件或者仓库是公开分享用途有更轻量的选择。4.1 Release 附件分享二进制包的替代路径GitHub 的 Release 页面支持附加任意大小的文件单个文件上限是 2GB没有总容量限制实际受到仓库大小影响。这非常适合发布安装包、编译产物、模型文件等一次发布、长期不变的大文件。操作流程在 GitHub 仓库页面进入Releases区域点击Draft a new release填写版本号和发布说明在Attach binaries区域拖入大文件点击Publish releaseRelease 附件的优势在于它不占用 Git LFS 配额也不会让仓库本身膨胀。文件被存放在独立的附件存储区用户从 release 页面下载。缺点是没有版本管理能力附件一旦发布就不可覆盖只能添加新附件。我在发布一个包含 800MB 模型文件的演示项目时就用过这个方案代码和轻量数据走 Git 仓库模型文件走 Release 附件配合 README 里的下载链接整体体验比 LFS 好很多。前提是文件不会频繁更新否则每次都要新建 ReleaseGitHub 页面上会积累一堆历史附件。4.2 分卷压缩古老但偶尔有效的土办法如果你只是想把一个 300MB 的文件传到 GitHub 上又不想用 LFS、不想开 Release可以用压缩工具把文件切成小于 100MB 的分卷然后逐个上传。# Linux / macOS split -b 90M your_large_file.bin your_large_file.part_ # WindowsWinRAR / 7-Zip 图形化操作 # 压缩后会产生 # your_large_file.part_aa # your_large_file.part_ab # your_large_file.part_ac然后在仓库里提供一个合并脚本方便别人重建原始文件cat your_large_file.part_* your_large_file.bin这个方法的问题很明显文件被分割后无法预览、无法单独拉取其中某部分使用体验很差更新文件时需要重新分割、重新提交所有分卷Git 历史里会保留所有旧分卷别人查看项目时不知道这些分卷是什么需要额外的 README 说明我只建议在文件非常稳定、发布频率极低、且你不方便使用其它方案的场景下使用。比如把几个大图素材直接分卷传上去分享比 LFS 更直观。4.3 外部存储加链接最灵活的兜底方案其实很多时候你并不是真的需要把大文件放在 GitHub 上只是想让别人能拿到这个文件而已。这种情况下用一个公开的对象存储服务阿里云 OSS、腾讯云 COS、AWS S3或者网盘分享链接然后在 README 里放下载地址可能是最省事的方案。我有一次在一个开源项目里要分享一个 1.5GB 的数据集Git LFS 显然存不下Release 附件又只能放 2GB 以下够是够但更新麻烦。最后就是把数据集上传到一个公网对象存储桶然后命名带版本号README 里放三行说明## Dataset [版本 v1.0 下载链接](https://your-bucket.example.com/dataset-v1.0.zip) [版本 v2.0 下载链接](https://your-bucket.example.com/dataset-v2.0.zip)好处是自然不用操心 GitHub 配额坏处是你得自己维护这些大文件的生命周期过期了要自己删。如果项目是给技术社区用的这种方案完全能接受。4.4 Git AnnexLFS 之外的另一套大文件版本方案Git Annex 是一个更极客的方案它的设计理念是大文件内容不存到 Git 仓库里而是放在一个内容寻址的存储系统中Git 仓库只记录符号链接和内容标识。它支持各种后端存储AWS S3、Google Drive、本地磁盘等灵活性极高。但说实话Git Annex 的学习曲线非常陡。它的命令体系比 Git LFS 复杂得多配置文件也多社区使用率相对较低。除非你的项目对大文件存储后端有非常个性化的需求比如强制要求文件不能离开本地网络否则 Git LFS 更实用。这里就不展开讲了。5. 误把大文件提交进历史记录后的后悔药这个坑几乎每个在 GitHub 上折腾过大文件的人都踩过忘了配置 LFS直接把一个几百 MB 的文件提交进去了。推送时虽然被 GitHub 拒绝但本地提交记录里已经写入了这个大文件对象。麻烦的是这个大文件进历史的问题不会因为你后续改用 LFS 就自动消失——它继续存在于历史记录中导致仓库体积膨胀。5.1 用 git filter-repo 彻底抹除历史中的大文件清理历史记录首选工具是git filter-repo它专门用来重写 Git 历史比老旧的filter-branch快得多也安全得多。安装pip install git-filter-repo找出历史记录中超过指定大小如 100MB的文件git rev-list --objects --all | git cat-file --batch-check%(objecttype) %(objectname) %(objectsize) %(rest) | awk /^blob/ {print $3, $4} | sort -n | tail -10假设你要把model/weights.pth这个文件从所有历史提交中移除git filter-repo --path model/weights.pth --invert-paths这个命令的含义遍历所有提交凡是路径匹配model/weights.pth的改动全部剔除--invert-paths表示除了这个路径以外的其他路径全部保留也就相当于只删除这一个文件。执行完成后Git 会强制重写整个仓库历史所有提交的 hash 都会改变。所以在团队协作中操作前一定要打好招呼否则其他成员的本地分支会全部失效。5.2 清理之后的注意事项重写历史并不是故事的结束还有几件必须做的事第一本地重新提交并强推。既然历史 hash 变了普通git push会被拒绝需要强推git push --force origin main如果分支名不是 main改成你的实际分支名。第二GitHub 端可能不会马上释放存储。在重写历史并强推之后GitHub 的网页端偶尔还会继续显示仓库体积很大这是因为远程存储上的旧对象还没被垃圾回收。触发同步清理的方法是在仓库设置里禁用它再重新启用或者给 GitHub 官方 support 发工单。比较极端的做法是把仓库删了重建把所有分支和标签重新推上去旧仓库里的对象会被服务器端异步回收。第三清理完 Git 历史后要重新配置 LFS。我见过有的团队清完历史之后直接把大文件改为普通文件提交进去了下一次 push 照样被拒。正确做法是清理历史后立刻执行git lfs track把大文件重新纳入 LFS 追踪再正常提交。第四LFS 存储本身也需要清理。假设你之前用 LFS 传过一个大文件后来从仓库历史里删除了它GitHub 的 LFS 存储里可能还留着这个对象。这时候执行git lfs prune这条命令会清理本地 LFS 缓存中不再被任何提交引用的对象。但远程 GitHub LFS 存储里的对象需要在仓库 Settings 的 Storage Bandwidth 页面手动删除或者联系 GitHub support免费账号把不需要的 LFS 对象清除。6. 传大文件时我踩过的几个坑和最终建议光说不练假把式。最后把我的实战经验拆开说说有些坑真的是不自己走一遍根本不知道。6.1 网络不稳定导致的 LFS 上传中断Git LFS 在 push 大文件时如果网络不稳定会频繁出现超时或中断。Git LFS 本身是基于 HTTP 协议上传的支持断点续传但前提是你重新执行 push 时它会自动检测之前已上传的分块。实测下来几百 MB 的文件在普通宽带下中断一两次还能续上但如果中断次数太多Git 会直接报错需要重试。我的做法是大文件上传前先用git lfs push --dry-run origin main检查一下有哪些文件将被上传然后在网络相对稳定的时段执行真正的 push。另一个经验是把 HTTP 缓冲区调到足够大小避免大文件上传时内存溢出或失败git config http.postBuffer 524288000这个设置的含义是允许 Git 发送 500MB 的 HTTP 数据块通常能缓解大文件 push 时的某些错误。6.2 一个大文件不断变化导致仓库体积失控前面提到过Git LFS 存储统计的是历史版本总和。如果一个 300MB 的模型文件每次都全量替换这种情况在训练任务中非常常见迭代 5 次之后 LFS 存储就达到 1.5GB免费额度直接超了。这是 LFS 最容易被低估的隐性成本。正确的用法是不要把持续变化的大文件纳入版本管理。模型权重、中间产物这类东西应该把它们看作发布物而不是源码。源码更新走 Git模型权重用 Release 附件或者外部存储来分发只在关键节点保存一个稳定版本。这样既能在项目里复用大文件又不会把配额吃光。6.3 团队里有人没装 LFS 导致崩溃如果你在一个团队里使用 Git LFS必须确保每个 clone 过仓库的成员都安装了 LFS 客户端并且在本地执行过git lfs install。否则会出现一种诡异的现象有人拉取代码时报错提示找不到某些文件或者本地看到的模型文件只有几字节。具体原因很简单这些人没有安装 LFS 或在全局配置里没有 filter 规则Git 无法把指针文件转换成真实文件内容。遇到这种情况让他们重新安装 LFS 并执行git lfs pull很有效。另外还要注意git config --global filter.lfs.required这个选项。如果为 true未安装 LFS 的 Git 客户端在检出 LFS 文件时会报错提示安装了才能操作如果为 false则只会看到一个几字节的指针文件。我建议在团队内统一使用默认配置不要乱改动否则排查问题的时间比省下来的时间还多。从我这几年的实际体会看GitHub 上传大文件这件事没有银弹。Git LFS 适合需要版本管理、大小适中单个 100MB~2GB、更新频率低的场景Release 附件适合一次性分发、大小可能超过 2GB、不需要版本回溯的场景外部存储适合仓库完全不需要承载大文件、只想要一个稳定的下载地址的场景。最稳妥的做法是在项目初期先想清楚大文件的定位写进 README 或 CONTRIBUTING 文档里告诉所有协作者什么文件进 Git、什么文件进 LFS、什么文件走外部链接。不要等到仓库膨胀到几个 GB、配额用尽、历史重写好几轮之后才开始定规矩那代价真的很大。