基础思路

prompt: 我们公司目前在搞 AI 提效,现在我即将负责一个点,就是“AI辅助代码检视”。我们的代码都是放在开源平台 gitcode。我给你讲下我们的开发流程。我们公司分 3 个区,分别是B区,Y区和 G区,信息流通的方式是:B区 -> G区 -> Y区。只能单向流动,而不能反过来。正常的开发流程是在 B 区 fork 开源仓代码到自己的仓,记为 private-repo,然后 clone 下来开发。开发好后,提交到 private-repo。然后去 G 区/Y区,在设备上 clone 下来自己的 private-repo,进行验证。如果需要修改,则还需要在 B 区修改,然后 push 到 private-repo,再到 G区/Y区 clone 下来验证。直到验证Ok。然后从 private-repo 提 PR 到公共的仓库,接受大家检视,最后合入。你帮我看看,这种开发流程中,我在哪些点可以使用到 “AI 辅助代码检视”来提升效率呢?

这边和豆包交流了一下,我理解基于我们当前的情况,应该是在 2 个点去部署 AI 检视:


 +++++++++++++++++     1. fork     +++++++++++++++++        4. pull
 |               | --------------> |               | ---------------------+
 | public-repo   |     5. pr       | private-repo  |                      |
 |               | <-------------- |               |                      |
 +++++++++++++++++                 +++++++++++++++++                      |
                                        |    ^                            |
                               2.  pull |    | 3. push                    |
                                        v    |                            v
                                   +++++++++++++++++                   +++++++++++++++++
                                   |               |                   |               |
                                   | B zone        |                   | Y/G zone      |
                                   |               |                   |               |
                                   +++++++++++++++++                   +++++++++++++++++

在第 3 处和第 5 处部署即可:

  • 第一步:B 区 commit 前 pre-commit 钩子 → 拦基础规范 / 语法问题(和 3 步版一致);
  • 第二步:提 PR 后 webhook → 全维度检视(增加跨区适配检视项),一次检视覆盖「基础问题 + 跨区问题 + 核心问题」; 适配点:gitcode 的 PR 增量 diff 能完整识别所有修改,合并检视项后,仅需 1 次 AI 调用

在决定在哪里部署后,接下来要看具体如何实现。

首先,我觉得代码开发主要分为这 3 类问题:

  • 规范类问题,比如函数和变量命名是否规范,使用 try...catch 语法时是否符合业界的最佳实践等等。
  • 业务功能性问题,比如当前的实现是否有场景遗漏
  • 程序架构上的问题,比如函数和类的组织是否合理,代码是否满足面向对象的七大原则(单一职责,开闭原则,依赖倒置,里氏替换法则,迪米特法则,接口隔离,合成复用),是否可以使用设计模式去重构。

有了这 3 类问题后,我们如何针对这 3 类问题做 AI 辅助检视呢?

对于规范类问题,主要的步骤如下:

  • 编程标准结构化,而不是纯文本投喂,把它拆解成“规则条目 + 示例 + 反例”的结构化 prompt。
  • 规范类问题可以结合静态分析工具,做“AI+工具”双校验,意思是用现成工具先扫一遍,AI 再做补充检验。这样做可以减少 AI 的计算成本。

对于业务功能类问题,核心是让 AI 懂业务,主要是因为 AI 缺乏上下文。主要的步骤如下:

  • 第一步,提前沉淀“业务知识库”,作为 AI 的固定 skill,意思就是说,让 AI 先懂我们的业务场景,我理解类似于 Claude Code 使用 /init 命令先生成一个 Claude.md,作为它对整个项目的理解,这个理解不会太细,但是会对项目有个整体性的把握。
  • 第二步,检视时绑定“代码对应的业务场景”,这一步可以要求在 commit 信息/PR 描述中注明“本次修改对应的业务场景”,AI 结合这个场景,就可以精准判断了。第三步;结合“历史问题库”,做场景化的查漏补缺。

对于架构类问题,主要的步骤是:

  • 第一步,让 AI 自动生成“项目架构文档”,而非手动编写。
  • 第二步,架构检视分 2 层,“局部代码合规性“ + ”全局架构一致性“。让 AI 基于架构文档,做 2 层检视,局部层,查看当前修改是否OK;整体层,查看当前修改是否影响项目的整体结构- 第三步,聚焦”增量架构问题“,不做全量复盘。每次 PR 来了只看增量代码。

一些具体实施的注意事项: 1、无论哪类问题,AI 输出的结果都要包含:

  • 问题类型(规范/功能/架构)
  • 问题代码行 + 问题描述
  • 风险等级(高/中/低)
  • 具体修改建议(详细说明)

2、分阶段开放检视能力,避免初期误报过多

3、建议“反馈闭环”,让 AI 越用越准

接下来,我需要了解一些工具的用法:

AI 检视代码的原理

很多人都有一个疑问,那就是说,AI 不懂特定的领域知识,为什么它一上来就可以 Review 的这么好?

经过和豆包讨论,我理解可以这样回答这个问题:

第一点,AI 模型在训练过程中,已经学习过世界上优秀的代码的最佳实践,以及常见问题的定位思路,相当于它已经拥有了全球程序员的代码经验,主要有这 3 类的通用能力:

  • 语言规范与语法
  • 代码逻辑与模式
  • 最佳实践

第二点,对于业务代码来说,它有明确的语法规则,依赖关系,执行流程和结构。AI 即使不懂业务,它也可以通过这几个代码的通用维度去进行 review。

第三点,用户可以通过 memory/skills/prompt 这 3 种方式给 AI 提供领域特定的上下文信息,比如在 /init 得到全局上下文 CLAUDE.md,通过 skills 去提供在特定场景下的做事的指导手册;再辅以 prompt 去提供精准的即时上下文。

了解了原理后,那我们可以得出影响 Review 效果的因素:

第一点,模型本身的能力越好,模型的代码理解、逻辑推理能力越强,Review 的精准度和深度越好

第二点,业务代码质量越好,高内聚,低耦合,SOLID 原则等等,(符合高内聚低耦合、SOLID 原则等),命名越清晰(拼写正确、动宾结构/语义化命名等),AI越易识别逻辑和架构问题,Review效果越好;

第三点,上下文供给质量:用户提供的 memory/skills/prompt 质量越高(指令越清晰、规则越结构化、上下文越贴合项目),AI 的定制化检视效果越精准。

那这里有个好问题,如果这 3 个影响因素对最终 review 结果的总合是 100% 的话,那么它们分别占百分之多少?

因为这个没有办法做严格的消融实验,我们可以根据业界的一些经验来估计一下:

  • 模型本身的能力:40%,模型是 AI Review 本身的“大脑”,直接决定了基础理解能力,逻辑推理深度,问题识别广度,是所有效果的前提,它决定了 Review 的下限
  • 业务代码质量:35%,业务代码质量 AI Review 的“原料”,原料的质量决定了模型能“发挥多少能力”,正所谓:代码质量决定了模型能力的发挥上限 —— 好的代码能让普通模型 “超常发挥”,差的代码会让顶级模型 “束手无策”
  • 上下文供给质量(memory/skills/prompt):25%,上下文供给质量则是效果的精准放大器,它决定了 Review 效果的精准度和贴合度,是“从可用到好用”的关键,但是它无法替代模型的基础能力和代码的底层质量。

我们可以怎么做

当前蓝区的模型是 GLM-4.7,更好的模型还有 Kimi k2.5,甚至是 claude 的官方模型,后续可能会接入我们自己的模型。这块跟着政策走,我们的发力点并不多。

第二点业务代码质量的话,使用 AI 辅助 Review 后,效果应该会比较明显,至少保证新增的代码不会引入规范类和架构类的问题,减少引入功能类问题。

第三点的话,是我们的重点发力点,主要包括:

  • memory 可以由 /init 去生成全局上下文,这个依赖于 CodeBase 的质量和文档完善程度
  • skills 这个依赖于领域用户自己去把优秀的实验沉淀下来,这个比较灵活,初步设想是有一些系统级的 skill,用于通用的场景,用户在使用过程中也尝试去总结自己的 skills
  • prompt,这里我们可以沉淀一些优秀的 prompt 模板,供大家去学习使用。这里我想提醒一点,LLM 的非常擅长和人聊天,只要我们把它当成一个正常人聊天,随意一点,啰嗦一点都没有关系,write as speak,只要把事情说清楚,一般效果都不会差

那么工作流应该如何建设呢?

既然本地的 ClaudeCode 可用,而且上述的分析显示代码 Review 的 3 类问题都可以在本地去检视,那么如何要求开发者在本地使用 ClaudeCode 检视就是一个流程上需要改进的点。

一个初步的想法是,在 PR 合入时,强制要求提供 ClaudeCode 等 AI 工具的 Review 记录文件(里面包含 AI 对增量代码的分析,发现的问题,及意见),committer 会根据这个记录文件来检视合入。

如:

echo '从【规范类/功能类/架构类问题】帮我检视一下最新的一个 commit 点的提交修改' | claude  >out

一个用 ClaudeCode 去检视代码的例子

下面用一个例子去展示 ClaudeCode 工具在仓库 guo-sj/omni_infer 这样一个 python 代码仓库对 python 代码的 review 情况。

在仓库的目录下启动 claude,并运行 /init 初始化去生成 CLAUDE.md 文件,如果需要,我们可以通过 /memory 去编辑它:

 ▐▛███▜▌   Claude Code v2.1.29
▝▜█████▛▘  GlmMoe · API Usage Billing
  ▘▘ ▝▝    /home/g00919896/omniinfer

  /model to try Opus 4.5

❯ hi

● Hi! I'm here to help you with your software engineering tasks. What would you like to work on today?

❯ /init

● Explore(Explore codebase architecture)
  ⎿  Done (73 tool uses · 0 tokens · 4m 9s)

● Let me read some key files to get more specific details for the CLAUDE.md file.

● Read 7 files (ctrl+o to expand)

● Let me read a few more files to understand the vLLM integration and key architecture patterns.

● Read 3 files (ctrl+o to expand)

● Now I have enough information to create the CLAUDE.md file. Let me write it.

● Write(CLAUDE.md)
  ⎿  Wrote 209 lines to CLAUDE.md
     # CLAUDE.md

     This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

     ## Project Overview

     Omni-Infer is a suite of inference accelerators for the Ascend NPU platform (Huawei hardware). It provides enterprise-grade LLM inference with full compatibility with vLLM, featuring P/D
     (Prefill/Decode) disaggregation, MoE optimization, and global proxy scheduling.

     **Key Concepts:**
     - **P/D Disaggregation**: Splits prefill and decode stages for optimized scheduling and scale-out
     … +199 lines (ctrl+o to expand)

● I've created the CLAUDE.md file for the Omni-Infer repository. The file includes:

  1. Project Overview - Describes Omni-Infer as an Ascend NPU inference accelerator with vLLM integration, P/D disaggregation, and MoE optimization
  2. Build and Installation - Commands for building the project, including:
    - bash build/build.sh - Full build
    - bash build/build.sh --ci - CI build with mock models
    - python -m build - Direct Python build
  3. Running Tests - Test commands using the pytest-based framework:
    - bash tests/run_tests.sh - All tests
    - bash tests/run_tests.sh --unit - Unit tests only
    - bash tests/run_tests.sh --integrated - Integration tests only
    - pytest tests/unit_tests/.../test_xxx.py - Run specific test file
  4. Architecture Overview - Key components organized under:
    - omni/accelerators/ - Performance accelerators (PD, placement, scheduling)
    - omni/adaptors/ - Framework adaptors (vLLM, SGLang, LMCache)
    - omni/layers/ - Neural network layers
    - omni/models/ - Model implementations
  5. vLLM Integration - Explains the forked vLLM at infer_engines/vllm/ with 60+ patches applied
  6. Key Configuration - Environment variables and model configuration via JSON
  7. Adding Custom Models - Brief summary referencing the detailed documentation
  8. Parallelism - Supported strategies (PP/TP/EP/DP) and communication utilities
  9. PR Classification - Required prefixes for pull requests

✻ Crunched for 5m 25s

❯ /memory
  ⎿  Opened memory file at ./CLAUDE.md

     To use a different editor, set the $EDITOR or $VISUAL environment variable.

接下来,我们计划让它去检视最近的一次提交的代码,首先我们先让它总结一下这一次提交commit 6309109f47fdecfe664d94cd304734cc637be654

❯ 你看下最近的一个提交记录修改了什么?

● Bash(git show --stat HEAD)
  ⎿  commit 6309109f47fdecfe664d94cd304734cc637be654
     Author: yinxiaoyun_hw <yinxiaoyun@huawei.com>
     Date:   Thu Feb 5 01:25:25 2026 +0000
     … +12 lines (ctrl+o to expand)

● Bash(git show HEAD --no-stat)
  ⎿  Error: Exit code 128
     /etc/profile.d/colorxzgrep.sh: line 1: /usr/libexec/grepconf.sh: No such file or directory
     fatal: unrecognized argument: --no-stat

● Bash(git show HEAD)
  ⎿  commit 6309109f47fdecfe664d94cd304734cc637be654
     Author: yinxiaoyun_hw <yinxiaoyun@huawei.com>
     Date:   Thu Feb 5 01:25:25 2026 +0000
     … +812 lines (ctrl+o to expand)

● 根据最近的提交记录(commit 6309109f),这次修改主要是关于 导出 MoE 专家功能的验证脚本。具体修改了以下文件:
  ┌──────────────────────────────────────────────────────────┬──────┬──────────────────────────────────────┐
  │                           文件                           │ 类型 │                 说明                 │
  ├──────────────────────────────────────────────────────────┼──────┼──────────────────────────────────────┤
  │ omni/adaptors/vllm/patches/export_moe_experts_fix.patch  │ 新增 │ vLLM 修复补丁                        │
  ├──────────────────────────────────────────────────────────┼──────┼──────────────────────────────────────┤
  │ omni/adaptors/vllm/sched/routed_experts_capturer.py      │ 修改 │ 专家路由捕获器                       │
  ├──────────────────────────────────────────────────────────┼──────┼──────────────────────────────────────┤
  │ omni/adaptors/vllm/sched/test_routed_experts_capturer.py │ 修改 │ 测试文件                             │
  ├──────────────────────────────────────────────────────────┼──────┼──────────────────────────────────────┤
  │ tools/accuracy/export_moe_accuracy_comparison.py         │ 新增 │ MoE 专家导出准确率对比脚本(573 行) │
  └──────────────────────────────────────────────────────────┴──────┴──────────────────────────────────────┘
  让我读取一下新增的主要文件看看具体内容:

● Read 2 files (ctrl+o to expand)

● 最近提交修改总结

  提交信息:!2023 Add the verification script for the export moe experts feature.

  修改的文件(共 4 个,+629/-21 行):
  ┌──────────────────────────────────────────────────────────┬──────┬──────────────┐
  │                           文件                           │ 变更 │     行数     │
  ├──────────────────────────────────────────────────────────┼──────┼──────────────┤
  │ omni/adaptors/vllm/patches/export_moe_experts_fix.patch  │ 新增 │ +18          │
  ├──────────────────────────────────────────────────────────┼──────┼──────────────┤
  │ omni/adaptors/vllm/sched/routed_experts_capturer.py      │ 修改 │ 修改约 43 行 │
  ├──────────────────────────────────────────────────────────┼──────┼──────────────┤
  │ omni/adaptors/vllm/sched/test_routed_experts_capturer.py │ 修改 │ 修改约 16 行 │
  ├──────────────────────────────────────────────────────────┼──────┼──────────────┤
  │ tools/accuracy/export_moe_accuracy_comparison.py         │ 新增 │ +573 行      │
  └──────────────────────────────────────────────────────────┴──────┴──────────────┘
  主要功能说明:

  新增文件:tools/accuracy/export_moe_accuracy_comparison.py

  这是一个 MoE 专家导出准确率对比验证脚本,主要功能包括:

  1. API 客户端 - 提供统一的 API 请求处理
  2. 支持流式和非流式请求 - /v1/completions 和 /v1/chat/completions
  3. MoE 专家路由数据捕获 - 在推理过程中捕获哪些专家被路由
  4. 准确率对比 - 验证导出的 MoE 专家路由数据是否正确

  新增补丁:export_moe_experts_fix.patch

  修复了 vLLM 中 serving_chat.py 的问题,确保在响应中正确添加 routed_experts 字段信息。

  用途: 这个功能用于在 MoE(Mixture of Experts)模型推理时,验证每个 token 路由到了哪些专家,用于调试和性能分析。

✻ Cogitated for 40s

看着它读的不错,那我来让它去检视一下这个 commit:

❯ 好,那你看它的修改,聚焦【规范类/功能性/架构类】问题,做一次代码 review,看下它有哪些问题?

● 让我完整读取这次提交的所有修改内容,进行深入的代码审查。

● Bash(git show HEAD --format="")
  ⎿  diff --git a/omni/adaptors/vllm/patches/export_moe_experts_fix.patch b/omni/adaptors/vllm/patches/export_moe_experts_fix.patch
     new file mode 100644
     index 00000000..3b9db660
     … +803 lines (ctrl+o to expand)

● Read 1 file (ctrl+o to expand)

● 根据完整的提交 diff,我进行了详细的代码审查。以下从【规范类/功能性/架构类】三个维度分析发现的问题:

  ---
  Code Review 报告

  【规范类问题】

  1. 全局变量污染风险

  routed_experts_capturer.py:42-43
  SAVE_DIR = os.getenv("EXPORT_MOE_EXPERTS_TEST_PATH", None)
  ROLE = os.getenv("ROLE", None)  # 模块级全局变量在导入时求值

  问题:模块级全局变量 ROLE 在导入时就求值,测试环境如果设置了 ROLE 环境变量会导致测试污染。测试代码中硬编码 part_key = 'total',如果环境变量设置了 ROLE=prefill,测试可能会失败。

  建议:使用延迟初始化或提供测试专用的 setter 方法。

  ---
  2. 文件末尾缺少换行符

  \ No newline at end of file

  两个文件缺少末尾换行符:test_routed_experts_capturer.py 和 export_moe_accuracy_comparison.py

  ---
  3. 魔法字符串/硬编码值过多

  export_moe_accuracy_comparison.py:
  FILE_EXTEN_LEN = 4      # 为什么是 4?应该命名为 FILE_EXTENSION_LEN
  DIM_THREE = 3           # 应命名为 THREE_DIM 或使用枚举
  INCLUDE_USAGE = False   # 模块级变量作为全局开关
  IGNORE_EOS = True       # 模块级变量作为全局开关

  硬编码路径:
  dir_path = './test'      # 第 555 行
  os.makedirs("saved_req_arrays", exist_ok=True)  # 第 432 行
  filename = os.path.join("dump_arrays", prefix + f'{request_id}.txt')  # 第 582 行

  ---
  4. 格式不一致

  test_routed_experts_capturer.py:165
  part_key  = 'total'  # 双空格

  export_moe_experts_fix.patch:16
  expert_ids = getattr(res, \'routed_experts\', None)  # 单引号转写,与其他风格不一致

  ---
  5. 命名不规范

  export_moe_accuracy_comparison.py:597
  for p in prefixes:  # 单字母命名 p 不够清晰

  ---
  【功能性问题】

  1. 会话资源未正确关闭

  export_moe_accuracy_comparison.py:733-745
  def run_scripts_parallel(max_workers=3, ...):
      with ThreadPoolExecutor(max_workers=max_workers) as executor:
          futures = [
              executor.submit(run_all_request, ip_addr, port, max_tokens, model_name)
              for _ in range(max_workers)
          ]

          for future in futures:
              try:
                  future.result()  # 如果 run_all_request 抛出异常,processor 可能未 close
              except Exception as e:
                  print(f"脚本执行失败: {str(e)[:100]}")  # 错误信息被截断

  APIClient 虽然有 close() 方法,但在异常路径下可能不会被调用。

  ---
  2. 异常处理不当,静默吞掉错误

  export_moe_accuracy_comparison.py:723-730
  for f in base.glob(pattern):
      try:
          if f.is_file():
              f.unlink()
          else:
              shutil.rmtree(f)
      except Exception:  # 捕获所有异常且静默处理
          pass

  静默忽略所有异常可能掩盖文件权限问题、文件被占用等实际问题。

  ---
  3. 环境变量未检查

  routed_experts_capturer.py:54
  EXPORT_MOE_EXPERTS_TMP_DIR = os.getenv("EXPORT_MOE_EXPERTS_TMP_DIR", "./tmp_lock_dir")
  Path(EXPORT_MOE_EXPERTS_TMP_DIR).mkdir(exist_ok=True)  # 如果环境变量为 None,会抛异常

  当 EXPORT_MOE_EXPERTS_TMP_DIR 环境变量设置为 None 时,Path(None).mkdir() 会抛出 TypeError。

  ---
  4. 数据保存逻辑存在时间戳竞争

  routed_experts_capturer.py:64, 76
  save_name = os.path.join(save_dir, f"data_{time.time()}.pkl")
  save_name = os.path.join(save_dir, f"indices_{time.time()}.pkl")

  使用 time.time() 作为文件名,高并发下可能产生相同文件名,导致文件覆盖或数据丢失。

  ---
  5. 字符串格式化不一致混用

  export_moe_accuracy_comparison.py:568-579
  def save_data():
      list_data = read_pkl("indices_")
      dict_data = read_pkl("data_")
      ...
      for f in files:  # 又是用 f

  混用 f-string、format 和 % 格式化风格。

  ---
  【架构类问题】

  1. 单一职责原则违反

  export_moe_accuracy_comparison.py (573行) 包含:
  - API 客户端 (APIClient, UnifiedAPIHandler)
  - 数据处理 (merge, read_pkl, save_data)
  - 文件处理 (find_prefix_files, parse_array_content, merge_3d_arrays)
  - 主测试逻辑 (main)

  建议:拆分为多个模块,如 api_client.py, data_processor.py, file_operations.py。

  ---
  2. 全局状态管理混乱

  routed_experts_capturer.py:
  _global_experts_capturer: Optional["RoutedExpertsCapturer"] = None
  _global_experts_reader: Optional["RoutedExpertsReader"] = None
  SAVE_DIR = os.getenv("EXPORT_MOE_EXPERTS_TEST_PATH", None)
  ROLE = os.getenv("ROLE", None)

  模块级全局变量过多,状态管理不清晰,依赖隐式共享状态。

  ---
  3. 条件判断逻辑复杂,可读性差

  routed_experts_capturer.py:106-111
  if self.is_pd_disaggregation:
      part_key = 'prefill' if self.is_pd_prefill else 'decode'
  else:
      part_key = 'total'
  save_data(indices, data, SAVE_DIR, part_key)

  这段相同逻辑在两个地方出现:

  routed_experts_capturer.py:142-147
  if self.is_pd_disaggregation:
      part_key = 'prefill' if is_prefill else 'decode'
  else:
      part_key = 'total'
  save_indices(indices, request.request_id, SAVE_DIR, part_key)

  建议:抽取为方法 def _get_part_key(self, is_prefill):。

  ---
  4. 测试与生产代码耦合

  routed_experts_capturer.py 中的 save_data() 和 save_indices() 直接写入文件系统,且依赖于环境变量 SAVE_DIR。这使得单元测试难以隔离。

  ---
  5. 数据序列化/反序列化效率问题

  export_moe_accuracy_comparison.py:419-423
  @staticmethod
  def _decode_numpy_array(encoded_str, shape, dtype):
      decoded_bytes = base64.b64decode(encoded_str)
      decompressed_bytes = zlib.decompress(decoded_bytes)
      arr = np.frombuffer(decompressed_bytes, dtype=dtype)
      return arr.reshape(shape).tolist()  # 转换为 list,失去 numpy 的高效性

  将 numpy 数组转换为 list 后再序列化,失去了 numpy 的高效存储和计算优势。

  ---
  6. 目录创建的边界情况处理不足

  export_moe_accuracy_comparison.py:684-711
  def setup_paths(mode="create"):
      ...
      if mode == "create":
          for p in paths:
              p.mkdir(parents=True, exist_ok=True)
      elif mode == "delete":
          for p in paths:
              if p.exists():
                  if p.is_dir():
                      shutil.rmtree(p)  # 如果目录正在被使用(如文件句柄未关闭),会失败
                  else:
                      p.unlink()

  删除目录时没有处理目录被占用的场景(如文件未关闭、进程权限不足等)。

  ---
  【总结】
  ┌────────┬──────────┬──────────┐
  │  类别  │ 问题数量 │ 严重程度 │
  ├────────┼──────────┼──────────┤
  │ 规范类 │ 5        │ 中       │
  ├────────┼──────────┼──────────┤
  │ 功能性 │ 5        │ 高       │
  ├────────┼──────────┼──────────┤
  │ 架构类 │ 6        │ 高       │
  └────────┴──────────┴──────────┘
  建议优先处理:

  1. 功能性 1:修复会话资源未关闭问题(可能导致资源泄漏)
  2. 功能性 3:修复 mkdir(None) 的异常
  3. 功能性 4:修复时间戳竞争问题
  4. 架构类 1:拆分大文件,提高可维护性
  5. 架构类 3:抽取重复的 part_key 逻辑
  6. 规范类 1:重构全局变量,提高可测试性

✻ Crunched for 1m 18s

❯ /context
  ⎿  Context Usage
     ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛀ ⛁ ⛁   GlmMoe · 0k/200k tokens (0%)
     ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁
     ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁ ⛁   Estimated usage by category
     ⛁ ⛁ ⛁ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ System prompt: 2.6k tokens (1.3%)
     ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ System tools: 13.1k tokens (6.6%)
     ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ Memory files: 6.0k tokens (3.0%)
     ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛁ Messages: 44.7k tokens (22.4%)
     ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶ ⛶   ⛶ Free space: 101k (50.3%)
     ⛶ ⛶ ⛶ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝   ⛝ Autocompact buffer: 33.0k tokens (16.5%)
     ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝ ⛝

     Memory files · /memory
     └ ~/.claude/CLAUDE.md: 4.1k tokens
     └ CLAUDE.md: 1.8k tokens

看下来它检视的很完善,接下来,我们挑一个问题让它修改下:

❯ 我觉得你说的很对,现在帮我修改 1 个问题吧,把你刚才说的`routed_experts_capturer.py:106-111` 和 `routed_experts_capturer.py:142-147` 这 2 段重复的判断逻辑抽取为方法 def _get_part_key(self, is_prefill)

● Read 1 file (ctrl+o to expand)

● 现在我来进行修改。我需要:

  1. 在 _RoutedExpertsCapturer 类中添加 _get_part_key 方法
  2. 将第 267-270 行的重复逻辑替换为方法调用
  3. 在 _RoutedExpertsReader 类中添加 _get_part_key 方法
  4. 将第 429-432 行的重复逻辑替换为方法调用

● Update(omni/adaptors/vllm/sched/routed_experts_capturer.py)
  ⎿  Added 6 lines
      106      """Concrete implementation of routed experts capturer."""
      107
      108      def __init__(self) -> None:
      109 +
      110 +    def _get_part_key(self) -> str:
      111 +        """Get the partition key for saving data based on PD disaggregation mode."""
      112 +        if self.is_pd_disaggregation:
      113 +            return 'prefill' if self.is_pd_prefill else 'decode'
      114 +        return 'total'
      115          self._experts_capturer_device_buffer: Optional[torch.Tensor] = None
      116          self._shm: Optional[shared_memory.SharedMemory] = None
      117          self._host_buffer_view: Optional[np.ndarray] = None

● Update(omni/adaptors/vllm/sched/routed_experts_capturer.py)
  ⎿  Error editing file

● Read 1 file (ctrl+o to expand)

● 我的第一次编辑破坏了文件结构。让我重新修复这个文件:

● Read 1 file (ctrl+o to expand)

● 文件已经被我之前的编辑破坏了。让我先恢复文件:

● Bash(git checkout omni/adaptors/vllm/sched/routed_experts_capturer.py)
  ⎿  /etc/profile.d/colorxzgrep.sh: line 1: /usr/libexec/grepconf.sh: No such file or directory
     Updated 1 path from the index

● 文件已恢复。现在让我正确地进行修改:

● Read 1 file (ctrl+o to expand)

● 文件已恢复。现在让我正确地进行修改。首先在 _RoutedExpertsCapturer 类中添加 _get_part_key 方法:

● Update(omni/adaptors/vllm/sched/routed_experts_capturer.py)
  ⎿  Added 6 lines
      118          self.max_num_kv_tokens: int = 0
      119          self.lock_file: Optional[str] = None
      120
      121 +    def _get_part_key(self) -> str:
      122 +        """Get the partition key for saving data based on PD disaggregation mode."""
      123 +        if self.is_pd_disaggregation:
      124 +            return 'prefill' if self.is_pd_prefill else 'decode'
      125 +        return 'total'
      126 +
      127      def init_buffer(
      128          self,
      129          max_num_batched_tokens: int,

● 现在替换第一处重复逻辑(第 267-271 行):

● Update(omni/adaptors/vllm/sched/routed_experts_capturer.py)
  ⎿  Added 1 line, removed 5 lines
      270                      and self._experts_capturer_device_buffer is not None
      271                  ):
      272                      self._host_buffer_view[indices, :, :] = data
      273 -                    if self.is_pd_disaggregation:
      274 -                        part_key = 'prefill' if self.is_pd_prefill else 'decode'
      275 -                    else:
      276 -                        part_key = 'total'
      277 -                    save_data(indices, data, SAVE_DIR, part_key)
      273 +                    save_data(indices, data, SAVE_DIR, self._get_part_key())
      274                  if self._shm_total_token_num is not None:
      275                      current_count = struct.unpack_from(
      276                          FMT, self._shm_total_token_num.buf, 0

● 现在在 _RoutedExpertsReader 类中添加 _get_part_key 方法,先找到它的 __init__ 位置:

● Read 1 file (ctrl+o to expand)

● 在 _RoutedExpertsReader 类中添加 _get_part_key 方法:

● Update(omni/adaptors/vllm/sched/routed_experts_capturer.py)
  ⎿  Added 6 lines
      355          self.lock_file: Optional[str] = None
      356          self.np_dtype: np.dtype = np.uint8
      357
      358 +    def _get_part_key(self, is_prefill: bool) -> str:
      359 +        """Get the partition key for saving data based on PD disaggregation mode."""
      360 +        if self.is_pd_disaggregation:
      361 +            return 'prefill' if is_prefill else 'decode'
      362 +        return 'total'
      363 +
      364      def attach_buffer(
      365          self,
      366          hf_config,

● 现在查找并替换第二处重复逻辑:

● Read 1 file (ctrl+o to expand)

● 替换第二处重复逻辑:

● Update(omni/adaptors/vllm/sched/routed_experts_capturer.py)
  ⎿  Added 1 line, removed 5 lines
      434              + np.arange(self.block_size)
      435          ).flatten()[start_tokens:num_tokens]
      436          if indices is not None and stopped:
      437 -            if self.is_pd_disaggregation:
      438 -                part_key = 'prefill' if is_prefill else 'decode'
      439 -            else:
      440 -                part_key = 'total'
      441 -            save_indices(indices, request.request_id, SAVE_DIR, part_key)
      437 +            save_indices(indices, request.request_id, SAVE_DIR, self._get_part_key(is_prefill))
      438          if not is_prefill and is_stream:
      439              indices = indices[-num_new_token:]
      440          expect_token_num = indices.size

● 修改完成。让我验证一下修改结果:

● Read 1 file (ctrl+o to expand)

● 让我再验证两处调用的修改是否正确:

● Read 1 file (ctrl+o to expand)

● 修改已完成。让我总结一下所做的更改:

  修改总结

  1. 在 _RoutedExpertsCapturer 类中添加 _get_part_key() 方法

  位置: routed_experts_capturer.py:121-125

  def _get_part_key(self) -> str:
      """Get the partition key for saving data based on PD disaggregation mode."""
      if self.is_pd_disaggregation:
          return 'prefill' if self.is_pd_prefill else 'decode'
      return 'total'

  2. 替换第一处重复逻辑

  位置: routed_experts_capturer.py:273

  修改前:
  if self.is_pd_disaggregation:
      part_key = 'prefill' if self.is_pd_prefill else 'decode'
  else:
      part_key = 'total'
  save_data(indices, data, SAVE_DIR, part_key)

  修改后:
  save_data(indices, data, SAVE_DIR, self._get_part_key())

  3. 在 _RoutedExpertsReader 类中添加 _get_part_key() 方法

  位置: routed_experts_capturer.py:358-362

  def _get_part_key(self, is_prefill: bool) -> str:
      """Get the partition key for saving data based on PD disaggregation mode."""
      if self.is_pd_disaggregation:
          return 'prefill' if is_prefill else 'decode'
      return 'total'

  4. 替换第二处重复逻辑

  位置: routed_experts_capturer.py:437

  修改前:
  if self.is_pd_disaggregation:
      part_key = 'prefill' if is_prefill else 'decode'
  else:
      part_key = 'total'
  save_indices(indices, request.request_id, SAVE_DIR, part_key)

  修改后:
  save_indices(indices, request.request_id, SAVE_DIR, self._get_part_key(is_prefill))

  ---
  这段重构代码消除了两处重复的条件判断逻辑,提高了代码的可维护性。如果未来需要修改 part_key 的计算规则,只需要在 _get_part_key() 方法中修改一处即可。

✻ Churned for 2m 5s

最后我们使用 git diff 去看下它的修改:

[root@056e04b4b4db omniinfer]# git diff
diff --git a/omni/adaptors/vllm/sched/routed_experts_capturer.py b/omni/adaptors/vllm/sched/routed_experts_capturer.py
index 2636da92..2a128fe4 100644
--- a/omni/adaptors/vllm/sched/routed_experts_capturer.py
+++ b/omni/adaptors/vllm/sched/routed_experts_capturer.py
@@ -118,6 +118,12 @@ class _RoutedExpertsCapturer:
         self.max_num_kv_tokens: int = 0
         self.lock_file: Optional[str] = None

+    def _get_part_key(self) -> str:
+        """Get the partition key for saving data based on PD disaggregation mode."""
+        if self.is_pd_disaggregation:
+            return 'prefill' if self.is_pd_prefill else 'decode'
+        return 'total'
+
     def init_buffer(
         self,
         max_num_batched_tokens: int,
@@ -264,11 +270,7 @@ class _RoutedExpertsCapturer:
                     and self._experts_capturer_device_buffer is not None
                 ):
                     self._host_buffer_view[indices, :, :] = data
-                    if self.is_pd_disaggregation:
-                        part_key = 'prefill' if self.is_pd_prefill else 'decode'
-                    else:
-                        part_key = 'total'
-                    save_data(indices, data, SAVE_DIR, part_key)
+                    save_data(indices, data, SAVE_DIR, self._get_part_key())
                 if self._shm_total_token_num is not None:
                     current_count = struct.unpack_from(
                         FMT, self._shm_total_token_num.buf, 0
@@ -353,6 +355,12 @@ class _RoutedExpertsReader:
         self.lock_file: Optional[str] = None
         self.np_dtype: np.dtype = np.uint8

+    def _get_part_key(self, is_prefill: bool) -> str:
+        """Get the partition key for saving data based on PD disaggregation mode."""
+        if self.is_pd_disaggregation:
+            return 'prefill' if is_prefill else 'decode'
+        return 'total'
+
     def attach_buffer(
         self,
         hf_config,
@@ -426,11 +434,7 @@ class _RoutedExpertsReader:
             + np.arange(self.block_size)
         ).flatten()[start_tokens:num_tokens]
         if indices is not None and stopped:
-            if self.is_pd_disaggregation:
-                part_key = 'prefill' if is_prefill else 'decode'
-            else:
-                part_key = 'total'
-            save_indices(indices, request.request_id, SAVE_DIR, part_key)
+            save_indices(indices, request.request_id, SAVE_DIR, self._get_part_key(is_prefill))
         if not is_prefill and is_stream:
             indices = indices[-num_new_token:]
         expect_token_num = indices.size