一个 `USE_V1` 环境变量背后:仓库里并存的两套实现

2026-08-09

本文所有事实以 hiyouga/LlamaFactory 官方仓库 2026-08-09 的内容为准。我们没有安装、训练或部署过任何模型,文中所有代码与目录都是仓库里读出来的。

很多项目的命令行入口文件是几百行的参数解析,LlamaFactory 的 src/llamafactory/cli.py 不是。这个文件除去顶部的许可证头之后,全部逻辑就是下面这几行——而这几行里藏着一件值得知道的事:这个仓库里同时存在两套实现。

先把这十几行原样摆出来

src/llamafactory/cli.py

def main():
    from .extras.misc import is_env_enabled

    if is_env_enabled("USE_V1"):
        from .v1 import launcher
    else:
        from . import launcher

    launcher.launch()


if __name__ == "__main__":
    from multiprocessing import freeze_support

    freeze_support()
    main()

一个入口文件短到这个程度,通常意味着它只干一件事:分发。这里分发的对象不是子命令(子命令是下一层 launcher.py 的事),而是整套实现

拆开看,这段代码给出三条可以直接引用的硬事实。

第一条,USE_V1 是一个环境变量开关。 is_env_enabled("USE_V1") 为真时,导入的是 .v1 包里的 launcher;否则导入的是 . 下的 launcher。两条分支的下一步完全一样,都是 launcher.launch()——也就是说,两个 launcher 模块都提供了一个可以这样无参调用的 launch(),切换发生在 import 那一行,而不是靠调用方改写命令。

第二条,入口里调了 multiprocessing.freeze_support() 这个调用放在 if __name__ == "__main__": 分支里,紧挨着 main()。它是把 Python 程序打包成可执行文件时 Windows 侧需要的那一步。仓库里为什么放它,我们没有依据,不推断;能确认的就是它在这儿。

第三条,这是一条”双轨并存”的证据。 一个仓库如果只有一套实现,不需要在入口处写这个 if。有了这行 if,说明旧实现和一套放在 v1/ 下的重写目前是同时躺在同一个包里的。

关于”环境变量取什么值才算启用”,判定逻辑在 extras.miscis_env_enabled 里,这篇不替它下结论。可以对照的是同一个仓库的另一处用法:launcher.py 里写的是 is_env_enabled("OPTIM_TORCH", "1"),第二个参数是默认值,也就是这个函数支持”不设也算开”的写法,而 USE_V1 这处没传默认值。

v1/ 目录里有什么(实读)

只说”有个 v1 重写”没什么信息量,把目录读出来才看得到它重写到哪一层:

src/llamafactory/v1/
  launcher.py
  accelerator/    helper.py  interface.py  profiler.py
  config/         arg_parser.py  arg_utils.py  data_args.py  model_args.py
                  sample_args.py  training_args.py
  core/           base_sampler.py  base_trainer.py  data_engine.py  model_engine.py
                  rendering/  (escape.py  format.py  rendering.py)
                  utils/      (batching.py  callback.py  checkpoint.py  collation.py
                               inference_engine.py)
  plugins/        data_plugins/   (converter.py  loader.py)
                  model_plugins/  (add_token.py  deepspeed_utils.py  initialization.py
                                   kernels/ ...)

kernels/ 下可见 base.pyinterface.pyliger_kernel_ops.py,以及 ops/mlp/ 下的 cuda_fused_moe.pynpu_fused_moe.pynpu_swiglu.pytriton_grouped_gemm.py

从文件名能看出来的只有一件事,但这件事很关键:它不是在旧实现旁边加了个小模块,而是把 launcher、配置解析、训练循环、数据引擎、模型引擎、插件这几层都各自有了一份同名对应物。 尤其是 config/ 下的 data_args.pymodel_args.pytraining_args.py——旧轨的参数定义在 src/llamafactory/hparams/ 下,v1 这边另起了一套 src/llamafactory/v1/config/。同名文件、不同路径,这正是后面要讲的那个实际麻烦的来源。

这里必须先划一条边界

我们核实了两件事:v1/ 的目录与文件名,以及 USE_V1 这个开关的存在。

所以下面这些问题,这篇一个都不回答:v1 完成到哪一步了、稳不稳、该不该切过去、和旧实现比性能如何、什么时候会成为默认。我们既没装过也没跑过,源码目录也不会告诉你这些。任何一篇文章要是从”有个 v1 目录”推到”新版更快""建议尝鲜”,那一步都是它自己加的。

同名文件多不等于重写完成度高,也不等于两边行为一致。这一段能给你的价值只有一个:知道有这条岔路存在,以后遇到对不上的现象时,先想到去确认对方说的是哪一轨。

另外两处配套,说明双轨不止在那一行 if 上

仓库里还有两处并列结构值得一提:

  • tests_v1/,与旧的 tests/ 并列;
  • examples/v1/,在 examples/ 之下单开一层。

也就是说,测试和示例配置也各自成套。你从别处复制一份 YAML 过来时,examples/train_lora/xxx.yamlexamples/v1/ 下的东西不是同一堆,路径里有没有 v1 这一段是要看清楚的。

旧轨那边的入口长什么样

对照着看一眼旧轨的 launcher.py,能帮你确认自己现在在哪条道上。

它里面有个 USAGE 常量,是不带参数时打出来的用法说明。这份说明一共列了八条子命令,我们另有一篇专门逐条讲它们分别干什么,这里只引用与”分辨轨道”直接相关的两行:

|   llamafactory-cli webui: launch LlamaBoard                        |
| Hint: You can use `lmf` as a shortcut for `llamafactory-cli`.      |

还有一条同样出自 launcher.py 的行为细节:命令解析写的是 command = sys.argv.pop(1) if len(sys.argv) > 1 else "help"不带任何参数时,command 取到的就是 "help"。也就是说,从代码看,只敲入口命令而不给子命令时,解析这一步不会因为缺参数而中断,而是落进 help 这条分支(实际打印出什么,以你本地执行的输出为准)。

launch() 里还构造了一个 WELCOME 横幅,格式化后包含这两行:

| Welcome to LLaMA Factory, version {VERSION}
| Project page: https://github.com/hiyouga/LLaMA-Factory |

这里出现了一处仓库内部的不一致:横幅里写的项目主页仍是带连字符的旧名 LLaMA-Factory,而仓库当前的真实路径是 hiyouga/LlamaFactory,README 正文里两种写法也是混用的。这是能核实到的新旧写法并存,说到这儿就够了——改名时间、原因、有没有重定向,我们都没核实过,不做推断,也不拿它评价任何东西。

这个环境变量在两种系统上怎么设

环境变量的设法本身是 shell 层的事,Windows 和 Linux/macOS 不一样,写混了就是”我明明设了却没生效”。README 里另一处环境变量(切下载源用的 USE_MODELSCOPE_HUB)给出的示范写法是这样的:

# Linux / macOS
export USE_MODELSCOPE_HUB=1
:: Windows(cmd)
set USE_MODELSCOPE_HUB=1

USE_V1 属于同一类开关,区别只在变量名。要注意两点:Windows 的 set 只对当前这个窗口有效,换个窗口就没了;PowerShell 又是另一套语法,不认 set VAR=1 这种写法(这属于 shell 层常识,不是该项目文档的内容,具体以你所用 shell 的文档为准)。

以上为按官方参数语义组合的示例,未逐项实测,以官方文档与 --help 的实际输出为准。

知道这条岔路,实际能省你什么

不是让你去切轨——是让你在下面这几种场合少走弯路。

看到一个文件路径时,先看有没有 v1 别人贴给你的是 src/llamafactory/v1/config/model_args.py,你在自己本地翻的是 src/llamafactory/hparams/,两边都叫得上名字,字段却未必对得上。同名不同路径是这个仓库现在的客观状态,先对齐路径再对齐内容。

issue、博客、教程没写清楚是哪一轨时,别默认它们说的是同一件事。 这是双轨仓库最常见的信息噪声来源。判定动作很简单:看它提到的文件路径、看它有没有提 USE_V1。两样都没有的,就当它没说。

如果你的环境里有人在 CI 或 Dockerfile 里统一注入过环境变量,值得去核一遍有没有 USE_V1 环境变量这种东西的麻烦之处就在于它不写在你手上那份 YAML 里,行为却会变。查的动作是去翻 CI 配置、镜像的 ENV、以及 shell 的 profile。

什么情况说明跟这条岔路无关? 如果你从没设过这个变量、CI 里也搜不到、而你遇到的现象在别人机器上能稳定复现,那就换个方向查,别在这儿耗着——它只是一个 if,不设就是走旧那条。

至于旧轨 launcher.py 里那些真正影响训练行为的默认值(什么时候会自动走分布式、NNODES / MASTER_PORT 这类变量各自默认是什么、哪几个被标为弹性启动),以及八个子命令的分工,我们各有专门篇目讲。这篇只负责把”仓库里有两套实现、靠一个环境变量分叉”这件事交代清楚。


本文依据 LlamaFactory 官方仓库(github.com/hiyouga/LlamaFactory)的 README、data/README.mdexamples/ 下的配置与 src/llamafactory/hparams/ 的参数定义整理,核对日 2026-08-09。本文内容为仓库源码与文档口径,我们没有安装、训练或部署过任何模型,文中显存数字均为官方标注的估算值(README 原文标 * estimated)而非实测占用。参数与默认值随版本变动,请以 llamafactory-cli train -h 的实际输出为准。许可条款请以官方 LICENSE 原文为准,本文不构成法律意见。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。