环境准备与项目结构
对于一个项目,最好的办法是最快的把它跑起来.在这个过程中,我们可以先不纠结细节,先把环境搭建好,把代码跑起来,再慢慢去理解它的细节和原理.
项目结构
一个适合长期学习和反复实验的项目,首先要有清晰的目录结构。原始的 MiniMind 仓库已经比较清楚,而这个仓库又在它的基础上做了一层适合学习和维护的整理。
原始项目里比较核心的部分大致是:
model:模型定义trainer:不同训练阶段的脚本dataset:数据集与数据加载逻辑scripts:转换、评估等辅助脚本
在这个仓库里,我们进一步改成了更适合 Python 工程化维护的 src layout,并补充了一些学习笔记和测试目录。现在比较重要的目录可以这样理解:
src:项目的核心代码,包含模型定义、训练代码、数据集实现等scripts:辅助脚本,例如转换、评估和实验工具notes:学习笔记,后续会组织成 mdBookminimind_upstream:上游参考代码,使用 submodule 管理configs:配置文件tests:测试代码
如果只是第一次进入这个仓库,建议先把注意力放在 src、notes 和 minimind_upstream 这三个目录上:前者是当前可运行代码,中间是学习记录,后者用于对照原始实现。
项目管理工具
这个项目推荐使用 uv 作为依赖管理工具,并配合 src layout 组织代码。
# 同步项目依赖
uv sync
# 以开发模式安装当前项目
uv pip install -e .
uv 是一个现代的 Python 包管理工具,安装速度快,依赖解析也更稳定。不过它同时提供了两套常见接口:
uv add/uv remove:会修改pyproject.toml,适合把依赖正式加入项目uv pip install/uv pip uninstall:只操作当前环境,不修改项目声明
两者的区别可以简单理解为:
- 如果你想把一个包正式纳入项目依赖,使用
uv add - 如果你只是临时试验某个包,使用
uv pip install
例如:
# 正式修改项目依赖
uv add package_name
uv remove package_name
uv add -r requirements.txt
# 仅操作当前环境(只需要把传统的pip 换成uv pip)
uv pip install package_name
uv pip install -r requirements.txt
uv pip uninstall package_name
查看当前环境依赖时,也可以直接使用:
uv tree
uv pip list
uv pip show package_name
用 uv 配置 PyTorch
PyTorch 的安装和一般 Python 包不完全一样。很多 CUDA 版本的 PyTorch 目前不再支持类似 torch==2.2.0+cu121 这样的版本后缀来选择,而是通过不同的软件源安装。
也就是说,如果你直接执行:
uv add torch
默认通常会从 PyPI 安装 CPU 版本。
如果你想安装 CUDA 版本,需要在 pyproject.toml 里为 torch 和 torchvision 单独指定源。这个思路和 PyTorch 官网用 pip --index-url 安装 CUDA 版本本质上是一样的。
可参考 uv 的官方说明: Configuring accelerators with environment markers
本项目当前采用 optional-dependencies + tool.uv.sources 的方式,分别适配 CPU 和 CUDA 环境:
# 安装 CPU 版本
uv sync --extra cpu
# 安装 CUDA 版本
uv sync --extra cuda
这里有一个容易踩坑的点
uv 的 extra 机制在实际使用中有一个容易让人困惑的地方:
如果你之前通过 uv sync --extra cuda 安装了 CUDA 相关依赖,之后再执行普通的 uv sync,而默认依赖里又没有包含这些包,那么 extra 对应的依赖可能会被移除。
这也是为什么这里建议把 CPU 和 CUDA 共有的核心依赖版本同时写进 dependencies,再通过 sources 控制不同平台实际从哪个索引下载。
下面是当前项目采用的一种可行配置:
dependencies = [
"swanlab>=0.6.13",
"transformers>=4.57.1",
"torch>=2.9.0",
"torchvision>=0.24.0",
]
[tool.uv]
conflicts = [
[
{ extra = "cpu" },
{ extra = "cuda" },
],
]
[project.optional-dependencies]
cpu = [
"torch>=2.9.0",
"torchvision>=0.24.0",
]
cuda = [
"torch>=2.9.0",
"torchvision>=0.24.0",
]
[[tool.uv.index]]
name = "pytorch-cu126"
url = "https://download.pytorch.org/whl/cu126"
explicit = true
[[tool.uv.index]]
name = "pytorch-cu126_c"
url = "https://mirrors.nju.edu.cn/pytorch/whl/cu126"
explicit = true
[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
explicit = true
[tool.uv.sources]
torch = [
{ index = "pytorch-cu126_c", extra = "cuda" },
{ index = "pytorch-cpu", extra = "cpu" },
]
torchvision = [
{ index = "pytorch-cu126_c", extra = "cuda" },
{ index = "pytorch-cpu", extra = "cpu" },
]
简化理解 uv 在这里做了什么
注: uv的extra机制有一点混乱,上面的官方示例有一些问题,使用uv sync --extra flag 安装包,之后每次添加或者移除包执行uv add/remove 或者uv sync会导致extra对应的包被移除,需要重新执行一遍uv sync --extra flag 来安装对应的包.
解决办法是再dependencies里面添加torch cpu 和 CUDA 共通的依赖版本.如下面案例所示.
注: UV extra的解析机制,UV 会做这么几件事情:
-
Configuration Validation(配置校验)
- 检查 extras 是否互斥
- 检查 conflicts 是否自洽
- 检查 pyproject.toml 是否结构正确
- 检查 index 配置是否冲突
-
Dependency Resolution(依赖解析)
- 合并依赖
- 合并 extras(已启用的), dependencies中的依赖会被默认启用,但是extra中的默认不启用,只有加入extra flag才会启用.
- 选择版本
- 生成锁定图
- 类似的命令 uv pip compile pyproject.toml
-
Installation(安装)
- 下载 wheel/sdist
- 安装到 .venv
uv remove
会修改 pyproject.toml uv 必须确保修改后的项目配置是“合法的”所以会先执行 Configuration Validation这一步会检查 extra conflicts即使 extras 没启用,也会检查 conflicts 是否自洽,所以会报错.但是这个报错又不影响安装.
uv sync
不修改 pyproject.toml
uv 不需要重新验证配置
uv 默认不启用 extras
所以不会触发 conflicts 检查
直接进入 dependency resolution → installation
但是dependencies中是最小依赖,所以不会重新安装,而如果dependencies中没有torch,默认的uv sync 不会解析extra,就会移除extra中的torch,所以需要重新执行uv sync --extra flag 来安装对应的包.
(这实在是有点混乱了,也许以后会改,总而言之下面的配置是目前最佳实践)
可以把它粗略分成三步:
- 配置校验:检查
pyproject.toml、extras、conflicts和索引配置是否合理。 - 依赖解析:合并默认依赖与启用的
extra,然后解析版本。 - 执行安装:下载 wheel 或 sdist,并安装到虚拟环境中。
一个很重要的区别是:
uv remove会修改项目声明,因此会先做完整的配置校验uv sync不修改项目声明,默认也不会启用额外的extra
这就会导致看起来“不报错但包没了”的现象。理解这一点以后,很多看似奇怪的行为就容易解释了。
检查当前 PyTorch 是否支持 CUDA
import torch
print(torch.cuda.is_available())
print(torch.version.cuda)
登录 SwanLab
训练脚本里虽然保留了 --use_wandb 这个参数名,但实际导入和使用的是 swanlab。因此同步完依赖后,建议先完成登录:
swanlab login
这样后续运行训练脚本时,实验日志才能正常记录。