基于对全仓库的静态扫描(2026-08-16,main @ 20a95f7)。本文档只列问题与方向,不包含具体改动;所有条目在动手前应先在当前代码上复核 file:line 是否仍然成立。 约束:重构后功能保持不变。 文末单独列出了扫描中顺带发现的疑似 bug——它们的修复会改变行为,应与重构分开提交。
2026-08-16 第一批落地:§8 建议顺序中的第 2 步(纯删除)全部完成,第 3 步(公共设施)完成两项,第 6 步(性能)完成两项。净删约 2,500 行(+250/−2,700 量级),功能不变。
| Commit | 阶段 | 内容 |
|---|---|---|
e451bd2 |
§1.1 | 删除 torch_patch standalone 不可达路径(约 50 个定义、~1,200 行);_activate_upstream 改为显式 raise;_runtime 恒真探测简化;顺带删除与 upstream 逐字相同的 no-op 补丁 |
bc77d9f |
§1.2 | 删除零消费者的 fsdp_memory.py(970 行;可从 git 历史恢复) |
6b00725 |
§1.3–1.7 | 恒 None 遥测链整链删除(含 native_smi.cpp);死状态字段(memory_utilization_percent、reserved_stage_peaks);死 env 导出与 efficiency 死参数;各处局部死分支;_preflight_bootstrap 冗余兜底 |
432c21c |
§2.2 | 同体重复 except、不可能失败的 try、恒真 hasattr、setup.py 死 wheel 兜底、17 处"子类⊂基类"冗余异常项 |
91243b8 |
§5.2 | workspace catalog 每 FX 节点重载 → 每图一次;torch 版本串 lru_cache |
f6891c8 |
§3.4+§5.4 | structured_io.emit_json 收敛 8 处 print-or-write --json 块并消除双重序列化;dtype 表与 _iter_tensor_leaves 各去重一份 |
验证矩阵:每阶段本地全量 pytest(222 passed)+ ruff;scripts/test.sh smoke 完整通过(含 native C++ 重建,覆盖 native_smi.cpp 改动);CI 同款 manifest 校验(7+39 cases)通过;远端真机单测——406(Python 3.12 + torch 2.9.1+cu128,~/refactor-test/FakeGPU)与 gem12(torch 2.12.1+cu130)均 221 passed + 1 skipped(skip 为 CPU-only 构建专属测试,预期)。
2026-08-17 第二批落地:§8 第 3 步(公共设施)剩余项之一完成。
| Commit | 阶段 | 内容 |
|---|---|---|
a9da7f8 |
§3.3 | 新增 _api._FakeGpuRuntimeConfig(frozen dataclass,10 个 FAKEGPU_* 配置字段)收敛 _api.py/_runtime.py 内部转发;init()/env()/run() 三个公开入口的关键字签名保持逐字不变(外部调用方零感知),仅内部把 10 个散装 kwarg 换成一个 config 对象;新增 _build_env() 消除 run()→env() 的整段 10-kwarg 转发;_apply_env_inplace/_apply_config_env_inplace/_apply_config_env/_runtime._init_native_runtime 四处签名同步收敛。净减约 77 行(220 处/− 变化集中在 _api.py),行为不变 |
验证:本地全量 pytest(222 passed,与改动前一致)+ ruff clean;CI 同款 manifest 校验(7+39 cases)通过;scripts/test.sh smoke(含 native 重建)通过;额外手工冒烟——fakegpu.env()/fakegpu.init(runtime="native")/fakegpu.run() 三个公开入口直接调用验证派生的 FAKEGPU_* 环境变量、device_count 校验报错、子进程环境注入均与改动前一致。远端 406/gem12 真机复测见第一批的验证矩阵说明;f6891c8(emit_json,改动了 CLI --json 输出代码路径)与 a9da7f8 已于 2026-08-17 在两台机器上复跑通过(各 221 passed + 1 skipped)。
2026-08-17 §7 bug 批量修复:12 项中 10 项确认为真实缺陷并修复(各一个 fix: commit),1 项(#6)在修复过程中发现是未完成且有 bug 的半成品(假设了 catalog schema 里不存在的 "group" 字段,native_capability_report() 一调用就 KeyError)——已改正为正确实现;1 项(#12)复核后判定不是 bug,未改。
| Commit | Bug # | 内容 |
|---|---|---|
1a7a337 |
#1 | rank 正则去掉多余转义,ShardMetadata placement 重写恢复生效 |
3ab2fb8 |
#2 | 释放回调改为绑定分配时的 tracker 实例(storage finalizer + saved-tensor 两处),二次 patch() 后不再向新 tracker 误释放 |
14a9f5f |
#3 | successful_writes 改为写入前预增、失败回滚,去掉序列化里的 +1 特例 |
d519e97 |
#4/#5 | _evaluate_json_check 捕获元组补 IndexError;finished_at_ns/duration_seconds 改为同一时钟读数 |
2f483f5 |
#6 | 过滤下沉到 native_capability_report(),在算 summary 之前过滤 apis(并修正了 WIP 里假设不存在的 "group" 关联字段导致的 KeyError);group_count 保持目录级、不随 API 过滤变化(匹配既有测试 == 5) |
6a87c50 |
#7 | 超时 rank 仍读取已写出的诊断 JSON;顺序 join 改为共享 deadline,避免 N 个慢 rank 把超时叠加放大 |
a270e59 |
#8 | 文本编码器均未暴露宽度且 denoiser 也未声明时改为 raise(而非静默取 1);真正无文本编码器角色的管线不受影响 |
19e1999 |
#9 | _bool_value 只保留已通过测试验证的 "auto"(DeepSpeed 真实哨兵值,不是 bug,保留);仅对词表外的未知字符串收紧为 raise,不再默认 True |
97dc3f6 |
#10 | assert 改为显式 if...raise,避免 python -O 下失效 |
4a3ae3b |
#11 | _process_name 只取入口脚本 basename,不再拼接可能含路径/密钥的后续 argv |
#12 复核结论(未改):_validate_collector_environment_contract 只在校验的 FAKEGPU_SERVING_* 环境变量已设置时才比对,起初怀疑"未设置应报错"。但 build_cuda_serving_sample 是文档化的公开 API(README "The same adapter is available as fakegpu.build_cuda_serving_sample(...) for use inside a Python benchmark"),供 vLLM/自定义 runner 在 FakeGPU 自身采集子进程之外直接调用;tests/test_analysis_extensions.py:757 就是在这些环境变量全部未设置的情况下调用并断言成功。若改成"未设置即报错"会直接打破这个文档化用例和对应测试。当前"仅在存在时比对"是该函数双重用途设计下的正确行为,不是缺陷,保留不动。
验证:本轮改动后本地全量 pytest(222 passed)+ ruff clean + CI 同款 manifest 校验(7+39 cases)通过;_process_name/native_capability_report 过滤逻辑另有手工冒烟验证。本轮未做远端 406/gem12 复测——多数改动是纯逻辑修正(正则、计数器时序、assert→raise、字符串校验)或范围明确的行为收紧,本地+CI 已提供等价确认;如需更保险可再补一次远端跑。
2026-08-20 第三批落地:§8 第 1 步(待决策项)全部拍板,第 3 步(公共设施)收尾,第 6 步(兜底收敛与性能)剩余项完成。
| Commit | 阶段 | 内容 |
|---|---|---|
f35018d |
§1.6 / 待决策 | 删除 privateuse1/(866 行)与 init_privateuse1 导出;其余四项拍板为保留并补上缺失覆盖 |
9a4d327 |
§2.3 + §5 | 静默兜底收窄(SMI publisher/catalog、workspace_profiles、repository_analyzer、calibration sample 定位改为 warning 或 raise);allocator 分类记账与自由块索引、metrics 深拷贝、trace_replay 按 rank 建索引、topology 链路计数、preflight 复用子进程输出与预编译正则、repository_analyzer 单遍解析、catalog 缓存键归一化 |
672695c |
§5.4 | decode_steps 闭式聚合 + --exclude-decode-steps / include_decode_steps=False(默认行为与报告 shape 不变) |
c660cdb |
§3.6 | _cli.py 承载命令注册表、共享 flag 工厂与 --json/--strict/退出码约定;21 个命令模块的 prog= 改为从注册表派生,4 种 --json 拼法收敛为 2 种 |
待决策项的拍板结论(原文档列的 5 项,逐项复核仓库证据后):
privateuse1/:删除。它经私有torch._C._accAPI 注册设备模块,而支持窗口内(torch 2.6–2.13)没有任何版本提供该 API——init_privateuse1()在任何已安装环境里都跑不起来,仓库内也无其他调用路径;原测试只断言导出可调用,从未执行。可从c31fee1恢复。MatmulFlopCounterMode:保留。实测可用(aten::mm计数正确),是文档化公开导出,缺的只是覆盖——已补执行级测试。capabilities.audit_native_exports:保留。fakegpu capabilities --build-dir是真实可达路径。schemas/*.schema.json:保留。复核时三个 schema 均已有契约或测试用途,删除会破坏现有功能(与 2026-08-16 扫描时的"完全孤立"结论不同)。- legacy SMI schema:保留。v1 兼容读取仍有价值,
test_virtual_smi.py是其现行消费者。
§3.6 中有意保留的差异:--json 的两种形态(可选路径 vs 布尔开关)不合并——布尔那组命令的全部输出就是 JSON 文档本身,改成可选路径会让紧跟的位置参数被 argparse 吞掉;--strict 的退出码各命令不同(1/2/3),都写在各自 --help 里,统一会破坏已声明的行为,只把声明方式收敛到一处。capabilities --strict 的 2 与"用法错误"的 2 重合,是既有取值,未动。
验证:本地全量 pytest(233 passed,含 3 个新增测试)+ ruff clean;CI 同款 manifest 校验(7+39 cases)通过。本轮尚未跑 scripts/test.sh smoke/cpu 与远端 406/gem12 复测——改动集中在 Python 侧 CLI 与纯逻辑路径,未触碰 src/;如需最终确认可按 §3 建议补一次。
2026-08-20 第四批落地:§8 第 4 步(大合并)与第 5 步(机械拆分)完成主体。
| Commit | 阶段 | 内容 |
|---|---|---|
5d71400 |
§3.2 上半 | 两条 serving 主路径共享容量解析、vLLM/通用准入分支、limiting_factor 阶梯与 inputs 回显(_ServingCapacity/_ServingAdmission 等 4 个 helper) |
242aa02 |
§3.2 下半 | _serving_batch_memory 与 _serving_request_set_memory 共享驻留量与相位峰值计算(_serving_memory_breakdown) |
c6a1866 |
§3.1 | 删除 torch_patch 对 _upstream process-group 补丁的逐字复刻;先补了该生命周期此前完全缺失的测试 |
f2f0039 |
§4.2 | torch_patch → _allocator.py(990 行) |
9f632c4 |
§4.2 | smi → _smi_environment.py + _smi_state.py;metrics 改为从 _smi_state 取 inventory,不再 import smi 私有函数 |
cd57d21 |
§4.2 | calibration → _calibration_protocol.py + _calibration_cuda.py;顺带把懒加载的 llm_estimator import 提到模块级(复核确认根本不存在循环 import) |
eaef076 |
§4.2 | serving_plan → _serving_types/_serving_kv/_serving_vllm/_serving_speculative,并补 __all__ 明确公开面 |
f8510a4 |
§4.2 | diffusion_estimator → _diffusion_types.py + _diffusion_pipeline.py + _diffusion_activation.py |
2ebb7bc |
§4.2 | smi → _smi_query.py + _smi_render.py |
f52e6bd |
§4.2 | torch_patch → _profiles.py + _cross_device.py |
四个超大文件的规模变化:smi.py 4353→957、torch_patch.py 4346→2073、serving_plan.py 3722→2501、calibration.py 2873→1871、diffusion_estimator.py 2327→779。全包最大文件从 4353 行降到 2501 行。
§3.2 合并的实际结果与文档预期不同:文档估计"约 −400 行净减",实际两个 commit 合计净增 17 行(planner 主体 −218、memory 函数 −24,共享 helper +300 左右)。原因是真正逐字相同的片段与模式特有片段交错,抽出来的每个 helper 都要写一份显式关键字签名,签名开销吃掉了去重收益。去重本身是达成的(准入逻辑、limiting_factor、inputs 回显、相位峰值各只剩一份),但不要指望它减行。两个入口没有进一步合并成单个函数体——那会把重复换成一个贯穿全函数的 mode 标志,不是改善。
等价性验证:§3.2 两个 commit 各跑了一轮 2,734 例全报告比对(两条路径 × generic/vLLM × 容量扫描 × 三种 KV 策略 × utilization × speculative × profile + 10 个非法输入),与合并前实现逐字节一致,覆盖全部 6 种 limiting_factor 分支。
拆分中发现与文档不符处:
_PROFILE_SUPPORTED_TYPES(§1.7 列为"从未读取")实际被tests/test_cli_commands.py:196读取,不是死代码,已随_profiles.py保留。calibration里llm_estimator的函数级懒加载(§4.2 说是"为躲循环 import")实际上没有循环可躲——llm_estimator不 import 包内任何模块,纯粹是位置放错。torch_patch的_NUM_DEVICES/_DEVICE_PROFILES/_DEVICE_NAME/_TOTAL_MEMORY是_refresh_runtime_profile_state会重新绑定的可变模块状态,不能随 profile 解析函数一起搬走(搬走后 from-import 拿到的是旧值副本,刷新不可见),已刻意留在torch_patch。- 移动私有函数会让 monkeypatch 目标失效:
tests/test_analysis_extensions.py对_transformers_serving_workload的 patch 已改指新模块(继续 patch 旧名字不会报错,只会静默失去拦截)。
验证:本地全量 pytest(234 passed)+ ruff clean + CI 同款 manifest 校验(7+39 cases)+ scripts/test.sh smoke(含 native 重建)+ scripts/test.sh cpu 全部通过。远端 406/gem12 尚未复测。
2026-08-22 第五批落地:继续完成 §4.2 的 torch_patch 机械拆分,保持旧私有导入路径可用。
| Commit | 阶段 | 内容 |
|---|---|---|
b1916fe |
§4.2 | 将 Accelerate、distributed、FSDP 兼容补丁移到 _ecosystem_compat.py;将架构名、字节格式化和终端内存报告移到 _torch_reporting.py。torch_patch.py 从 2,087 行降至 1,753 行;原模块保留 _patch_*、_arch_name、_fmt_bytes、_dump_terminal_summary 兼容入口,参数、异常和报告文本不变。 |
验证:本地全量 pytest(262 passed)+ ruff clean + scripts/test.sh smoke + scripts/test.sh cpu 全部通过;抽出的 6 个兼容函数与拆分前 AST 源码逐字一致。推送 00a6c4d 后按 nvidb 队列复测:406(job 1486,RTX PRO 5000 72GB,torch 2.8.0+cu128)真实 CUDA/FakeGPU 集成探针通过;gem12(job 1485,RTX 3090 Ti,torch 2.12.1+cu130)全量 pytest 为 261 passed + 1 skipped,随后 patch probe 通过。406 环境没有 pytest,因此采用同一 CUDA/torch 环境的集成探针,未改动远端环境。
§4/§5 剩余未做:preflight.py(1362 行,四个关注点未分离);calibration 的比较/验证/bundle 与 CLI 未再分;serving_plan 的两个 pool model(约 1,100 行)与 CLI 未再分;smi/各 CLI 的 main 仍是大函数(§4.1 的超大函数清单只处理了其中一部分)。§3.6 遗留:llm_cli 与 serving_plan 约 12 个同名 flag 默认值不同,本轮只统一了声明方式。另注意 diffusion_estimator 的 _local_profile 产物绕过 _validate_profile 的漂移风险(§4.2 提到)现在两者同在 _diffusion_pipeline.py,修它会改变行为,属独立事项。
复核后有意不改的条目(与文档原建议不同处):
_aggregate_report的 error 分支(§1.5/2.3):rc=0 但报告为 error 的边缘情况下可达,保留。_stage.py重复 env 赋值(§2.2):嵌套 stage 退出时有恢复语义,保留。_format_bytes11 份(§3.4):实测 6 份实现 5 种输出格式(负数/None/整除格式化各不同),全量合并会改 CLI 输出,仅 demo/doctor 两份本来相同,不值得为此建共享模块。放弃。_percentile2 份(§3.4):calibration 是线性插值、bandwidth_worker 是就近排名,不同算法,不可合并。_ceil_div3 份(§3.4):单行惯用式,零漂移风险,合并收益不抵跨模块依赖,保留。--include-exited(smi/metrics)、distributed_cli的--markdown-report等:有真实工作路径,属缺测试而非死代码,保留。- smi 发布的 state JSON 不再含恒 null 的
telemetry.*、topology.numa_node/pcie_generation键:死数据移除,typical_power_usage_mw等真实值字段保留。
待决策项:已于 2026-08-20 全部拍板,结论见上方第三批落地一节(删 1 留 4)。
| 部分 | 规模 |
|---|---|
fakegpu/(主包) |
约 37,100 行 Python(含 privateuse1/ 866 行) |
src/(C/C++ stubs) |
约 34,600 行 |
tests/ |
约 9,200 行 |
| 最大的文件 | smi.py 4353 行、torch_patch.py 4346 行、serving_plan.py 3722 行、calibration.py 2873 行、diffusion_estimator.py 2327 行 |
ruff(F401/F811/F841)检查是干净的——没有未使用的 import / 变量这类低级冗余。真正的体积问题集中在四类:成块的不可达/无消费者代码、大面积复制粘贴、多层重复校验与兜底、超大文件/函数。粗略估计,前两类合计可以删掉或合并 4,000–5,000 行 而不改变任何可观察行为。
_activate_upstream(torch_patch.py:3333-3384)只有在 import torch.fakegpu 和 from . import _upstream 同时失败时才返回 None。而 _upstream.py 是随包内置(vendored)的模块,正常安装下必然可导入,因此:
patch()的 standalone 分支(torch_patch.py:3857-4341)在正常安装下永远走不到;- 只被该分支引用的约 43 个
_stub_*函数(:2354-2691)、standalone 版_FakeStream/_FakeEvent/_FakeStreamCtx/_FakeDeviceProperties(:2207-2346, 2699-2736)、_patched_tensor_to/_patched_module_cuda等(:2056-2199)随之全部变成死代码; - 例外:
_stub_is_bf16_supported、_stub_get/set_rng_state*、_normalize_device_index也被 upstream 路径使用,需保留。
同类问题:_runtime.py:108-136 的 _detect_custom_torch_fakegpu_available() 末尾 find_spec("fakegpu._upstream") is not None 恒为 True,导致 init(runtime="auto") 永远选不到 "native",前面的 sys.path 扫描与 torch.fakegpu 探测(:109-127)全部不可达。tests/test_runtime_init.py:71-82 只在 mock 掉该函数的情况下测试,所以测试抓不到。
处置建议:要么整体删除 standalone 分支并同步修改模块 docstring(torch_patch.py:16-19),要么用显式环境变量把它变成可达、可测的路径——现在它既不可达也无覆盖,是最差的状态。
4 个公开函数(build_full_shard_plan:7、build_fully_shard_plan:74、estimate_full_shard_sft_memory:461、estimate_fully_shard_sft_memory:598)在 fakegpu/、tests/、scripts/、README、__init__.py 中零引用。docstring(:467)声明的唯一消费者 qwen_sft_memory_worker.py 已不在仓库里。git 历史显示它曾被积极维护(CHANGELOG 记录过 real-GPU 验证),属于"消费者被删掉后遗留"而非从未用过。
处置建议:先确认是否有仓库外的消费者;没有则删除,有则补 __init__.py 导出 + 测试。二者取一,不要维持现状。
gpu_utilization_percent / temperature_c / fan_speed_percent / power_usage_mw 从未被写入非 None 值(Python 侧 smi.py:1391-1397,C++ 侧 src/core/native_smi.cpp:874-879 均硬编码 None),却被 smi.py:3308-3320 setdefault、:153-173 暴露为查询字段、:2881-2888 渲染,_format_percent/_format_temperature(:4264-4279)几乎专为它们存在。同类:"永远是 None"的 topology.numa_node/pcie_generation(smi.py:578-579, 3781-3782 及查询/渲染点)。
处置建议:如果近期没有实现真实遥测的计划,整链删除;否则至少收敛到一处 TELEMETRY_FIELDS 表,避免 5 处手写 None。
reserved_stage_peaks:smi.py:3087初始化、:3162-3165累加,无任何读取方。memory_utilization_percent(smi.py:1353-1357):读取方自己重算(:3367-3369);同类还有free_memory、headroom_bytes/percent、identity_source(恒为"synthetic",:1331)。trace_replay.py:452-453的event["memory_bytes"]写后无读;:488-489的excluded_from_aggregate/exclusion_reason打上标记后立刻在:490-491被过滤丢弃,整个复制遍历可以简化为一个 filter。
preflight --steps(preflight.py:69)导出的FAKEGPU_PREFLIGHT_STEPS与FAKEGPU_PREFLIGHT_TARGET_STAGE(:639-643)全仓库无读取方,是完整的 no-op。FAKEGPU_PREFLIGHT_MEMORY_SAFETY_FACTOR/_MARGIN(preflight.py:839, 853)只有读取处,无任何写入/文档/测试。distributed_cli.py的--markdown-report、--cluster-markdown-report、--interconnect-*、--ranks-per-node无文档无测试,且部分组合下被静默忽略(:706-718);_aggregate_report的 error 分支(:499)因上游先 raise 而不可达,_print_bandwidth_summary恒打印 PASS(:555)。smi.py --include-exited、metrics.py --include-exited无任何测试或脚本使用。performance_model.py的efficiency参数(:98)无任何调用方传入,其约 36 行 override 校验(:298-333)全部是死代码。
MatmulFlopCounterMode(flop_counter.py,88 行整个模块):__init__.py导出,但无内部调用、无测试。capabilities.audit_native_exports(capabilities.py:222-328+ 6 个 helper,约 150 行):仅--build-dir一条路径可达,无测试无 CI 调用。privateuse1/子包(866 行):仅经init_privateuse1暴露,测试只断言 callable(tests/test_runtime_init.py:54),从未真正执行——属于实验性平行后端,需要决定去留。validation.expand_validation_cases/render_validation_markdown、workspace_profiles.default_workspace_profile_path、profile_catalog.CatalogValidation/profile_directory等公开命名但仅模块内使用;calibration.py/repository_analyzer.py的__all__与__init__.py实际导出不一致——建议统一"什么算公开 API"的口径,缩小公开面。
diffusion_estimator.py:1416-1429:StableDiffusion分支与 fallback 返回完全相同的 dict。serving_plan.py:3150-3164、:1796-1806、llm_estimator.py:862-864:结构上不可能触发的防御性 raise。kernel_analysis.py:150-152:左侧 opcode 集合被右侧正则完全覆盖;:287的limiting = []立即被重新赋值。torch_patch.py:78-81_PROFILE_SUPPORTED_TYPES导入时构建、从未读取;:2690_stub_cudart从未绑定;_orig_tensor_cuda/_orig_tensor_pin_memory/_orig_torch_compile(:2051-2053等)被保存"以便恢复"但不存在任何 unpatch 路径。_preflight_bootstrap.py:127-158:32 行 fallback 伸手进torch_patch私有属性,重新推导公开函数memory_snapshot()已经返回的内容。schemas/*.schema.json三个 JSON Schema 完全孤立——无代码、无 MANIFEST.in、无 CI 引用,而validation.py:278-318手写了一份更弱且已漂移的校验。要么让代码真正使用 schema,要么删掉。
全包 61 处宽 except,其中 torch_patch.py 独占 60 个 try、39 个 except Exception、27 个 hasattr。原则建议:版本兼容性 guard 保留并注释清楚;"不可能失败"的 guard 删除;会吞掉真实故障的宽捕获收窄并留诊断。
torch.distributed.fsdp 私有模块 import guard(torch_patch.py:3205-3258)、torch._C._cuda_* 的 CUDA/CPU 构建差异探测(:2812, 4104-4128)、allow_tf32 的 property 分叉(:2840-2853)、hasattr(os, "RTLD_NOW")(_api.py:168)等。这类保留,但建议统一加一行"为哪个 torch 版本/构建差异服务"的注释,防止未来被误删或被继续模仿。
- 完全相同的重复 handler:
torch_patch.py:1372-1375与:1603-1608,窄捕获后紧跟同体宽捕获。 - 不可能抛异常的操作外套 try:
getattr(x, k, default)(:1684-1690)、os.path.dirname(:1461-1469)、torch.nn.Module属性访问(:1808-1812, 1909-1912)、已在patch()里无条件 import 过的子模块再次 try-import(:3642-3662, 3743-3751, 1536-1539)。 - 在声明支持窗口(torch 2.6–2.11)内恒为 True 的
hasattr:torch.compile(:2130)、torch.amp.autocast(:3299, 3329)、对自带 vendored 模块_upstream的 4 次属性探测(:3358-3374, 3808-3810)。 _stage.py:26, 30两次无条件重复赋同一个环境变量。setup.py:12-15, 114-130对wheel的 try/except——pyproject.toml已在 build-system.requires 里声明它。- 各 CLI
except元组里"子类 ⊂ 基类"的冗余项:FileNotFoundError ⊂ OSError、json.JSONDecodeError ⊂ ValueError、自定义*Error ⊂ ValueError(serving_plan.py:2202-2208、training_plan.py:431-437、topology.py:483-489、capabilities.py:365-370、smi.py:1862等 6+ 处)。副作用:所有命令实际都在裸捕ValueError,内部 bug 会被当作用户输入错误报出。
torch_patch.py:3350-3356:vendored 模块 import 失败被静默降级到(不可达的)standalone 路径——打包坏了却无任何告警;:3344-3347应只捕ImportError,否则损坏的 custom-torch 构建与"未安装"不可区分。torch_patch.py:1759-1776, 1794-1804:saved-tensor 追踪安装/恢复失败被吞,激活内存追踪静默关闭且 hooks 永久丢失。workspace_profiles.py:371-379:import torch失败退化成空字符串作为 profile 匹配键参与 fnmatch——一次 import 失败静默改变匹配结果;:363-368把get_profile的一切失败折叠成"unknown target GPU profile"。repository_analyzer.py:558-564, 600-605:损坏的pyproject.toml静默视为"无依赖/无入口",最终却报告static_analysis_complete: True;:326-358git 失败静默退化为os.walk(ignore 策略不同,文件清单会变)。smi.py:1495-1500:publisher 主循环裸捕后continue,状态文件持续超限会永远失败而无信号;:1226-1229stop()吞掉最终写入失败,进程可能永远显示 running。smi.py:4019-4053:坏掉的 profile catalog(ProfileCatalogError是ValueError)被_catalog_metadata折叠为{},所有输出静默降级为 N/A。calibration.py:2548-2561:定位 sample 的第四层 fallback 会捡起任意一行日志 JSON,随后在下游报出令人困惑的 schema 错误,而不是"未找到 sample"。demo.py:101-155:54 行主体套一个except Exception折叠为 exit 2,torch import 失败与算术错误不可区分。metrics.py:1207-1218_number把负数/NaN 一律钳到 0(时钟回拨产生的负 age 变成 0,无告警)。
- "patch 某属性、防重复打补丁"六连块(
torch_patch.py:1813-1941, 3260-3294)→ 一个once_patched(target, name)装饰器,约省 90 行。 - "invalid device ordinal" raise 有 11 份拷贝、3 种消息格式(
:1991-4028各处)→ 一个_require_valid_device_index(),顺带统一报错文案。 - "
_memory_tracker非 None 则委托否则默认值"10 连块(:2443-2506)→ 表驱动。 - 多层 Optional 传播链(
calibration.py_first_integer→_memory_points→_canonical_point→_match_points四层 None/空集合接力才 raise 一个错误;workspace_profiles同型)→ 在边界一次性 raise,中间层不再传 Optional。
一次 estimate_serving_plan 调用中,prompt_tokens 等参数在 serving_plan.py:570-573、:62-65、llm_estimator.py:66-82 各验一遍;kv_cache_strategy 验 4 遍(argparse choices + 三层函数),且每层抛不同的异常类型,还都在二分搜索内部重复执行。training_plan.py:48-373 对 normalize_training_config 已经归一化过的输出再套一层 x or default 链(:84-358 多处)。_normalize_serving_requests(108 行)对同一份数据在 serving_plan.py:1027, 2648, 2670, 2693 反复全量归一化。build_cuda_serving_sample 甚至校验自己刚构造的输出(calibration.py:399-414)。写侧/读侧独立重算同一派生值的还有 smi.py 的 MIG mode、health status、NVLink 汇总(写 :1091-1097 等 vs 读 :3631-3715 等)。
方向:确立"入口边界校验一次,内部信任"的约定;内部函数用注释或类型标注声明前置条件,删除重复层。
_patched_copy_collective_tensor(torch_patch.py:3041-3063)与_upstream.py:483-499语义逐字相同——整个补丁是 no-op,直接删。_patched_dist_init/destroy_process_group(:3135-3181)复刻_upstream.py:381-438,仅状态读取方式不同。- 12 个
_tracked_*内存闭包在 upstream 路径(:3581-3662)与 standalone 路径(:3909-3981)写了两遍(约 150 行);跨设备 wrapping 块两遍(:3682-3736vs:4191-4249,其中_BINARY_DUNDERS列表实际有三份);_FakeGradScaler两遍(:3669-3677vs:4293-4301)。若按 1.1 删除 standalone 路径,这些自动消失。 torch_patch影子实现了_upstream已有的_FakeStream/_FakeEvent/_FakeDeviceProperties/_normalize_device_index/_install_legacy_cuda_types/torch_load 处理等 8 组类与函数——统一改为复用。
estimate_serving_plan(:537-993,457 行)与estimate_serving_request_set(:996-1494,499 行)经机械 diff 有 375 行逐字相同(capacity 解析、vLLM 分支、limiting_factor 九连 if、报告尾部全部一致)。_serving_batch_memory(:2276-2626)与_serving_request_set_memory(:2629-3083)64% 相同。- 三份 token 级相同的二分搜索(
:3086-3140)→ 一个_largest_fitting(limit, predicate)。
方向:抽出共享的 _build_serving_report(...),两个入口收敛为薄封装。这是全仓库杠杆最大的单项重构(涉及约 1760 行)。
init/env/run/_apply_env* 加 _runtime 两处,共 8 处完整签名 + 6 处逐参转发调用,约 200 行纯转发代码。引入一个 frozen 的 FakeGpuConfig dataclass 作为单参数即可全部收敛;_apply_config_env_inplace 是 _apply_env_inplace 的严格子集,合并为带 flag 的一个函数。
| 重复项 | 份数 | 位置举例 |
|---|---|---|
_format_bytes 字节格式化 |
11 | doctor.py:21 与 demo.py:13 逐字节相同;serving_plan.py:3717、llm_cli.py:141、smi.py:4244、torch_patch.py:1203、preflight.py:1346、distributed_cli.py:543、diffusion_estimator.py:2280、2 个 scripts |
| JSON/TOML/YAML 结构化读取 | 3 | structured_io.load_mapping、validation.py:617、workspace_profiles.py:188(各带一份"install fakegpu[validation]"提示) |
--json 输出块(含双重序列化 bug,见 §7) |
8+ | calibration.py 一个函数内 4 份、trace_replay.py:170、kernel_analysis.py:344、repository_analyzer.py:287 等 |
write_json 内联复刻 |
7 | llm_cli.py:105、distributed_cli.py:860、preflight.py:160、serving_plan.py:2210、performance_model.py:254 等(structured_io.write_json 已存在) |
| dtype→bytes 表 | 2(逐字节相同) | llm_estimator.py:16-32 == diffusion_estimator.py:31-47(后者已 import 前者的其他函数) |
_positive_integer/_nonnegative_integer 校验 |
3+ | serving_plan.py:3695、diffusion_estimator.py:2164、training_plan.py:694(仅异常类不同);calibration.py 内部同型判断约 15 处 |
_ceil_div |
3 | training_plan.py:733、diffusion_estimator.py:2234、topology.py:924 |
_percentile |
2 | calibration.py:2022、_bandwidth_worker.py:32 |
git rev-parse 子进程封装 |
3 | preflight.py:1329、validation.py:700、repository_analyzer.py:1000 |
_iter_tensor_leaves/_tensor_bytes |
2(逐字节相同) | memory_estimator.py:1771 == workspace_profiles.py:633 |
_positive_float/非负 int 钳制 |
3 | smi.py:4332、metrics.py:1261、topology.py:894 |
| 字节数量解析(单位表还不一致) | 2 | distributed_cli.py:34-49 vs preflight.py:859-886 |
| 区间合并算法(同文件内两份) | 2 | trace_replay.py:736-742 == :768-774 |
| severity 等级表 | 3 | smi.py:44、metrics.py:1239(每次调用重建 dict)、native_smi.cpp:502 |
| 手写 YAML 迷你解析器 | 3 | profile_catalog.py:423(并强迫整数字段必须是 str)、structured_io(真 YAML)、tests/native/...:88;另有 scripts/lib/fakegpu_uv_deps.py:18-99 的 82 行手写 TOML 解析器和 scripts/validation/check_preflight_report.py:84-135 的手写迷你 JSON-Schema 校验器(CI 已装 jsonschema) |
smi.py:433-1116 的三个 _modeled_* 环境解析器(约 680 行)与 src/core/device.cpp:271-702 逐条重复,错误字符串逐字节一致;JSON 发布再与 src/core/native_smi.cpp:374-880 重复。短期至少要加一个共享 fixture 断言两侧输出一致;长期考虑单侧生成或以数据文件驱动。smi.py 内部这三个解析器本身也是同构骨架,"可选 + 前缀整数解析"复制了 6 遍(:483-984 各处),可先在 Python 侧内聚。
21 个独立 main(argv) 各建 ArgumentParser,prog="fakegpu <name>" 字符串须与 __main__.py:12-33 的注册表手工同步;--build-dir/--profile/--devices 在 5+ 处重复声明;--json 有 4 种互不兼容的拼法(store_true / dest 字符串 / type=Path);llm_cli.py 与 serving_plan.py 重复声明约 12 个同名 flag 且默认值不同(dynamic/eager vs paged/sdpa);退出码约定 5 种。preflight/doctor/demo 三者独立校验 profile catalog,preflight.py:716-745 甚至用两个独立循环解析同一个 --devices 字符串。方向:一个小型 register(name, build_parser, handler) 注册器 + 共享 flag 工厂,统一 --json/退出码约定。CMakeLists.txt:198-282 的 APPLE/else 双分支中 5 个相同的 set_target_properties 块(约 85 行→30 行)同理。
| 行数 | 位置 | 说明 |
|---|---|---|
| 666 | torch_patch.py:466 _DeviceMemoryTracker |
混杂字节记账、allocator segment 模型、stage 记账、报告格式化 |
| 573 | torch_patch.py:3769 patch() |
含 485 行不可达 standalone 安装 |
| 499/457 | serving_plan.py:996 / :537 |
两条 82% 相同的主路径 |
| 498 | diffusion_estimator.py:311 |
内含 156 行字面量返回 dict |
| 474 | memory_estimator.py:107 estimate_module_memory |
|
| 421 | metrics.py:123 render_prometheus |
9 个 accumulator 机械重复 |
| 412 | calibration.py:1104 main |
argparse 构建 + 六路 dispatch |
| 376 | serving_plan.py:1898 main |
同型 |
| 375 | torch_patch.py:3387 _apply_enhancements_over_upstream |
已有 0–8 节编号,逐节提取即可 |
| 365 | smi.py:2623 render_detail |
runtime 段与 device 段互相独立 |
| 322 | smi.py:1503 main |
|
| 269 | calibration.py:748 verify_calibration_reports |
8 个 gate 手写 if,天然表驱动 |
各 CLI 的 main 都可按同一模板拆为 _build_parser() + 每个子命令一个 _run_<action>(args),顺带消掉 dispatch 长梯子和重复 JSON 输出块。
torch_patch.py(4346 → ~6 个模块):_profiles.py(profile 解析,无 torch 依赖,:63-226);_cross_device.py(:228-368);_allocator.py(_DeviceMemoryTracker及其自由函数,约 810 行,:371-1184,是最干净的一刀);_reporting.py(:1187-1332,注意_dump_terminal_summary直接摸 tracker 私有字段,先给 tracker 一个公开report_rows());_ecosystem_compat.py(HF/accelerate/FSDP/NCCL 第三方 shim,:2777-3330);standalone 部分按 §1.1 处置。smi.py(4353 → ~6 个模块):查询 schema(:53-403+:3845-3961);modeled 环境解析(:406-1146,纯函数);publisher(:1149-1500,torch_patch唯一依赖的部分);inventory/归一化(:1841-2046+:3042-3833,metrics.py真正需要的部分——现在metrics.py:18-23在 importsmi的私有函数,是缺共享模块的最明确信号);renderers(约 900 行);CLI。calibration.py(2873 → 4 个模块):比较/验证/bundle;serving observation 协议;transformers real-CUDA 适配器(唯一 import torch/transformers 的部分,现在为躲循环 import 在:2131懒加载llm_estimator——强烈暗示它本就该在别处);CLI。serving_plan.py(3722 → ~5 个模块):KV-pool 数学、vLLM 预算、speculative decoding、request manifest、CLI(详见 §3.2 合并后再拆)。diffusion_estimator.py(2327 → 4 个模块):pipeline inspection、profile 加载/校验(注意_local_profile产物绕过_validate_profile,两套 schema 编码会漂移)、activation 模型、CLI。preflight.py(1355):报告组装 / 状态分类 / 经验校准(约 260 行)/ Markdown 渲染四个关注点分离;render_markdown_report(:305-503)约 200 行。
torch_patch.py内 20 处函数内import torch,其中_wrap_tensor_binary_op.wrapper:343在每次+/*/@上执行,_DeviceMemoryTracker.allocate:527在每次分配上执行(只为拿torch.cuda.OutOfMemoryError)。模块级绑定一次即可。同类:memory_estimator.py:1771/workspace_profiles.py:633的_iter_tensor_leaves在每层递归都 try-import torch。_allocate_allocator_block(torch_patch.py:929-946)每次分配对全部 segment×block 做嵌套扫描(持锁),随分配数近似二次增长;_free_allocator_block:1011线性扫段(已有segment_id却不建索引)。snapshot()(:790-855)O(设备×存活分配),被后台线程每 250ms 持锁驱动,直接与分配热路径抢锁——应改为在 allocate/release 时维护增量汇总。- 每个被追踪张量 5 次
os.environ读取(:565, 1414, 1424, 1428, 1507),其中仅 stage 一个真正会变。
memory_estimator.py:1034-1044 在节点循环内调 match_workspace_profile,后者每次 load_workspace_profiles:每个 catalog 文件一次 stat()、每个 profile 一次 dict() 拷贝、外加 _software_stack 的 try-import torch(workspace_profiles.py:51-74, 91-92)。数千节点的图 = 数千次 syscall 与目录重建。把 catalog + software stack 提升到循环外是这批文件里单项收益最大的优化。
- 每个 request 的 transient 只依赖自身(
batch_size=1),却在每次 prefix 二分探测中全量重算(:2718-2804×:3105),6N·logN → 6N。 - request-set 路径有
memory_by_request_count缓存(:1120-1147),homogeneous 路径的batch_memory(:665-687)却没有,且每次探测都带着 §2.5 的多层校验重跑完整 KV 估算栈。 prefill_chunk_tokens is None(默认)时unchunked_prefill_transient与 target 逐位相同却算两遍(:2381-2388, 2758-2765)。_worst_concurrent_transient:3438-3469对 6 个分量各做一次全排序取前 k(k 通常 1–2)→heapq.nlargest。
metrics.py:history 双端队列最多 1440 个快照,snapshots()每次GET /api/v1/history深拷贝整个队列(:565);一次/metrics抓取额外 2 次深拷贝(:673-682);health_payload:712为读 6 个标量深拷贝整个快照。trace_replay.py:每个 event dict 拷贝两遍(:429, :475);_rank_summaryO(ranks×events) 外加每 rank 3 次重扫;replay_trace后续约 10 遍全量扫描可合并为一遍累加。repository_analyzer.py:每个.py读两遍、pyproject.toml读三遍解析两遍;每文件约 7 次ast.walk(一个 NodeVisitor 可合一);files 列表 8 遍扫描。compare_memory_reports(calibration.py:101-193)对 comparisons 约 10 遍生成器;verify_calibration_reports又从原始数据重推一遍 summary 里已有的统计量(两套公式需人工保持同步,见 §3 scripts 里还有第三、四份)。- 8+ 处
--json块的双重序列化:payload = json.dumps(...)算完即弃,write_json再序列化一遍——大报告(trace_replay 内嵌全部 event)CPU 和峰值内存×2。 llm_estimator.py:485-506:decode_steps每生成 token 一个 dict 全量嵌入报告(4096 token = 4096 个 dict),且逐步重算本可闭式求和的 FLOPs——建议聚合 +include_decode_steps开关(报告 shape 变化需过一次消费者确认)。topology.py:_simulate_flows每轮对每条链路重扫全部 flow 路径(:556-567, 658-664);ring collective 对相同 (src,dst,payload) 重复跑 Dijkstra2*(N-1)轮无 memo(trace_replay.py:57反而有缓存,可对齐)。- 小项:
profile_catalog.official_compute_capabilities无缓存每次读文件(:220-230);preflight._contains_oom_marker每行 stderr 编译 4 个正则(:1223-1232);preflight.build_report:178从磁盘重读自己刚写的日志;capabilities.pylru_cache 因调用形参不同缓存同一文件两份(:29-32)。
__main__.py:8 的 from ._api import env 触发 fakegpu/__init__.py 全量 eager import(实测 import fakegpu 165ms,其中 calibration 82ms),使 :42 的 importlib 懒加载完全失效——每个子命令(包括只需 _api 的 passthrough)都付这笔钱。__init__.py 改为 __getattr__ 懒导出或让 __main__ 绕开包 __init__ 即可。
- 先补保护网再动刀:
distributed_cli.py(876 行)零测试覆盖,是 CLI 层最大的裸奔面;torch_patchstandalone 路径、preflight --steps、--include-exited、llm_cli多个 flag 均无覆盖。重构前对将保留的路径补冒烟测试。 scripts/validation/三个 bespoke assert 脚本与fakegpu validate的声明式 manifest 机制重叠,多数可转成 manifest case;静态内存统计在calibration.py、两个 scripts 里共有 4 份独立实现(§3.4),收敛后 CI 校验逻辑只剩一份。tests/test_virtual_smi.py是唯一让LEGACY_SCHEMA_VERSION和render_table重建路径活着的消费者——确认 legacy 支持是否还需要,不需要则连测试一起删。
这些修复会改变行为,不应混入"功能不变"的重构 commit;已逐一确认、单独提交(状态见下,详见"实施进度"2026-08-17 §7 批量修复一节):
- ✅ 已修复(
1a7a337)torch_patch.py:3088:re.sub(r"rank:\\d+/", ...)在 raw string 里\\d是字面反斜杠+d,替换恒为 no-op——ShardMetadata placement 重写从未生效。 - ✅ 已修复(
3ab2fb8)torch_patch.py:3817-3820:重复调用patch()时新建_DeviceMemoryTracker,但旧 tracker 注册的weakref.finalize仍会对新 tracker 调release(),二次init()后内存记账漂移。 - ✅ 已修复(
14a9f5f)smi.py:1453-1456:successful_writes在写入之前+1 序列化进状态,写失败时计数超前。 - ✅ 已修复(
d519e97)validation.py:539捕获元组缺IndexError,而_json_pointer:583对 list 索引越界正是抛它——错一个 pointer 会让整次验证崩溃而非记为 case 失败。 - ✅ 已修复(
d519e97)validation.py:163-164:finished_at_ns与duration_seconds来自两次独立取时钟,报告内部自相矛盾。 - ✅ 已修复(
2f483f5)capabilities.py:373-388:--library/--classification过滤发生在 summary 计算之后,打印的汇总与表格互相矛盾。注:中途一版未完成的修复曾假设apis/groups间存在实际不存在的"group"关联字段,会导致KeyError——已改正为正确实现,过滤下沉到native_capability_report()内部、summary 计算之前。 - ✅ 已修复(
6a87c50)distributed_cli.py:448:rank 超时后continue发生在读取 report 之前,超时 rank 的诊断 JSON 被丢弃;:438顺序 join 使 N 个慢 rank 串行放大超时。 - ✅ 已修复(
a270e59)diffusion_estimator.py:1106-1107:text encoder 均未暴露 hidden_size 时 conditioning width 静默取 1,产出接近 0 的错误估算而非报错(扫描发现的最高风险静默兜底)。 - ✅ 已修复(
19e1999)training_plan.py:695-696, 730:_positive_int("auto")确认是 DeepSpeed 真实哨兵值且有测试覆盖,保留不改;_bool_value对词表外未知字符串默认 True 才是真问题,已改为 raise。 - ✅ 已修复(
97dc3f6)calibration.py:713-714:用assert做控制流,python -O下消失后None会被嵌进 observation。 - ✅ 已修复(
4a3ae3b)smi.py:4322-4325:_process_name拼接sys.argv[:3]写入状态文件与 Prometheus label,可能泄露命令行中的路径/敏感参数,且是无界 label 基数来源。 - ⛔ 复核后判定不是 bug,未改。
calibration.py:2250-2272:_validate_collector_environment_contract只在 env var 已设置时比对。起初怀疑应改成"未设置即报错",但build_cuda_serving_sample是文档化公开 API,供 vLLM/自定义 runner 在 FakeGPU 采集子进程之外直接调用(README 明确写了这个用法),tests/test_analysis_extensions.py:757就是在这些变量全部未设置时调用并断言成功——"未设置即报错"会打破这个文档化用例。当前行为是该函数双重用途设计下的正确实现。
原建议顺序中,第 1 步的决策项已部分拍板(fsdp_memory.py、standalone 路径已删),第 2 步已全部完成,第 3、6 步各完成一部分。剩余工作按以下优先级推进:
决策项✅ 已完成(2026-08-20):privateuse1/删除(f35018d),其余四项拍板保留并补覆盖,逐项理由见"实施进度"。纯删除(§1 + §2.2)✅ 已完成(e451bd2/bc77d9f/6b00725/432c21c)。- 公共设施(剩余,中风险):
✅ 已完成(FakeGpuConfigdataclass 收敛_api.py/_runtime.py的 8 处 10 参数签名_FakeGpuRuntimeConfig,2026-08-17,−77 行)。CLI 注册器 + 共享 flag 工厂 + 统一✅ 已完成(--json/退出码约定c660cdb,2026-08-20)。--json输出块已由emit_json收敛(f6891c8)。剩余:llm_cli与serving_plan约 12 个同名 flag 默认值不同(dynamic/eagervspaged/sdpa),本轮只统一了声明方式,默认值差异原样保留,需单独确认哪些属于有意为之。 大合并✅ 已完成(5d71400/242aa02/c6a1866,2026-08-20):golden 测试先行,再抽共享 helper;_patched_dist_init/destroy复刻已整体删除。净减行数未达预期,原因见"实施进度"第四批。- 机械拆分(§4.2):主体已完成(2026-08-20,9 个新模块,全部只做移动 + 旧模块 re-export)。剩余项见"实施进度"第四批末尾——
preflight.py、各 CLI 的main、torch_patch的_reporting/_ecosystem_compat、serving_plan的两个 pool model。 - 兜底收敛与性能(剩余):§2.3 逐个评估(
workspace_profiles的 import 失败退化、smi.pypublisher 主循环裸捕、calibration第四层 sample fallback 等——行为敏感,建议转 warning 而非静默);§5.1 allocatorsnapshot()增量汇总与_allocate_allocator_block索引化(需 benchmark);§5.4 metrics 深拷贝三处、decode_steps聚合 + 开关(报告 shape 变化需过消费者)。 bug 修复(§7,与重构完全分开走✅ 已完成(2026-08-17):12 项中 10 项修复、1 项(#6)修复过程中改正了一处 WIP 引入的 KeyError、1 项(#12)复核后判定不是 bug,保留不动。详见"实施进度"与 §7 逐项状态。fix:commit)
原第 2–5 步预计的 4,000–5,000 行削减目标,目前已完成约 2,500 行;剩余大头集中在第 4 步(serving_plan 合并,约 −400 行净减 + 消除最大维护面)与第 5 步拆分(不减行数但改善结构)。§7 的 bug 修复批次已收尾,后续如无新发现不再单独安排。