毕业论文idea随手记

毕业论文idea随手记

工作量思路

历史特征引导

覆盖率引导(这个可能不太好搞)

大模型生成


20260516

最近读了 AlignGuard 这篇工作,该工作分析了 torch.compile 的一百多个 issue,总结了一些容易致错的模式,将它们转化成了 (动作, 条件) 对(结构化自然语言),利用这个东西去引导对种子程序进行变异,使得变异后的程序尽量包含致错模式。其中,种子程序是从 Pytorch 官方的测试代码中扒出来的,这部分代码有 1.2w 行,总量其实是很大的。

这给我们一些启发,首先就是基于致错模式去生成类似的测试用例,是真的能再测出 bug 的。然后,如果你的变异器或者生成器是基于 LLM 的,那么变异规则用自然语言描述其实是自然的,特征用自然语言描述也是自然的;如果你的变异器/生成器是基于其他自己写的模型,那么引导信号可能就得用特征向量了,变异规则以及触发条件可能也要用一些可计算的量来表示。

目前,参照 AlignGuard 的做法,我从 pytorch 官方的测试代码中提取出了一个索引,并且实现了根据索引去具体拿里面某个测试用例,不过这个测试用例如果单拿出来这几行是没法跑的,我其实很好奇 AlignGuard 是怎么把提取出的测试用例变得独立可跑的,难道是手动改的吗?

pytorch 的 issue 我爬取了 1000 条和 torch.compile 相关的,通过看这些 issue 中是否包含我关心的某些信息,给这些 issue 进行了打分,满分 7 分,5 分以及以上的我筛出来 200+ 个,并使用 LLM 进行了初步分析和标注,后边会对这些 issue 进行逐个分析,整理成结构化的东西,我看了看前几个,觉得这几个 issue 确实挺不错的。

FreeFuzz 是给所有的调用外面套了一个装饰器,从而能够采集到代码运行时的调用信息,比如某个算子的所有参数的形状之类的,从而得到的所谓的”真实程序的调用轨迹“。每次挑选一个真实的调用轨迹,这个东西当成是种子,然后对里面的参数做变异(比如从别的轨迹里面找一个名字一样的参数,把它的值拿过来),以及类型变异之类的,对单个 API 测试,也能测出来不少问题。它的代码里面处理了很多脏活累活,可以作为参考。


20260528

目前已经提取了 test_torchinductor.py 这个测试代码中的 200+ 个可以单独执行的测试用例了,测试用例大概的格式是:

引入各种依赖

# 定义结构
def fn():
	...
# 定义参数
def build_inputs():
	...
	
def run():
	# 运行两种模式并比对,手写了一套比对逻辑
	
def main():
	# 运行测试

其中 fnbuid_inputs 是直接提取的,前面的引入依赖和后边的执行算是模板代码。

fnbuild_inputs 直接提取也未必能用,我先分析了直接提取之后的里面,那些不能跑的到底报了什么错,然后发现名字未定义的错误最多,就先写了一个只解决这个问题的修复脚本。我是觉得这种报错原因应该比较有限,可以分成几个类,所以分类去分别修复就行,可能用不到大模型去做。

和 DeepSeek 聊了聊,目前得到一个初步的对测试用例进行变异的提示词模板,非常粗糙,我们后边需要仔细去打磨一下,看看提示词怎么写:

你是一个深度学习框架测试用例变异器。给定一个PyTorch测试用例,请按照以下要求生成变异版本:

1. **原始代码**:
​```python

​```

2. **变异规则**(按优先级选择1-2个应用):
   - 算子替换:将当前算子替换为功能相似但数值特性不同的算子(如 avg_pool -> max_pool,或 adaptive_avg_pool1d -> avg_pool1d)
   - 参数变异:修改算子参数(如 output_size、kernel_size、stride等),在保持合理取值范围的情况下,可以尝试极限值和边界值
   - 张量形状变异:调整输入张量的某维度大小(如 4x4x3 -> 8x4x3),保持维度兼容
   - 数据类型变异:改变dtype(float64 -> float32 或 bfloat16)
   - 插入无关操作:添加 view/reshape/contiguous 等不影响最终结果但可能改变内存布局的操作
   - 操作顺序变异:调整`fn`内部语句顺序(若语义允许)
   - 等价语句替换:如 torch.argmax(x) -> torch.max(x)[1]

3. **约束**:
   - 生成的代码必须是完整、可直接运行的Python脚本,不能有语法错误
   - 保持 `fn` 的输入输出类型一致性
   - 变异后的 `fn` 应当与原函数**在数学上不完全等价**(目标:暴露框架差异)
   - 保持 `build_inputs` 返回的 tensor shape 与 `fn` 期望的输入兼容

4. **输出格式**:
   - 只输出变异后的完整代码,不要添加解释
   - 代码开头添加注释说明应用了哪些变异规则

请开始变异。

通过讨论,我们觉得需要构建一个算子语义知识库,辅助 LLM 选择变异后还能正常运行的算子(最稳妥的可能是选一个性质差不多的算子),不能有语法错误或者明显的语义错误。

为了引入历史的经验,还需要构建致错模式知识库,使用这个知识库可以在变异时朝着特定的模式去变异。

由于大语言模型是语言模型,所以用自然语言去指导它可能是更好的,扔给它结构化数据可能并非很好的策略。我们存知识库的时候是结构化的存的,但是每条最好都给一个固定的自然语言描述,投喂给大模型用的是这个自然语言描述,但知识库检索可以检索结构化的那部分。

用自然语言引导 LLM 做事这一步我们都知道,由于我们不练模型,所以我们能做的就是设计好的提示词就可以了。

现在的问题就是,怎样在知识库里检索到要用什么知识呢?这大概需要我们算一些即将给大模型的测试用例的度量,比如是否包含某类的算子、算子个数、连接方式、参数边界特征、形状特征、是否 in-place 之类的。用这样一个“特征向量”去查库,找到适合使用的知识,然后构建到提示词里去做变异。

如何算这个“特征向量”呢?有一些是可以静态分析的,有一些可能不太好搞。

特征向量算好了,如何匹配呢?这个就是要研究匹配算法了。

上面有一个问题可能是工程味道太浓了,写文章不能这样写。deepseek 给出了一个可能的写文章的目录:

3.1 问题定义与形式化(2页)
   3.1.1 测试用例变异问题形式化
   - 定义测试用例 T = (f, I, oracle)
   - 定义变异操作 M: T → T'
   - 定义变异有效性:语义改变 & 语法正确
   
   3.1.2 模式驱动变异的形式化
   - 定义模式 P = (trigger, action, weight)
   - 定义模式感知变异:T' = M(T | P)
   
   3.1.3 研究问题分解
   - RQ1: 如何构建可被LLM理解的知识库?
   - RQ2: 如何为给定测试用例检索相关知识?
   - RQ3: 如何利用检索到的知识引导LLM变异?

3.2 方法概述(2页)
   3.2.1 整体框架
   - 一张大图:离线阶段 vs 在线阶段
   - 四个模块:知识库构建、特征提取、知识检索、LLM变异生成
   
   3.2.2 工作流程
   - 步骤1-5的文字描述 + 数据流图
   
   3.2.3 设计原则
   - 分离存储与使用
   - 规则驱动检索,LLM驱动生成
   - 闭环反馈优化

3.3 双知识库的离线构建(4页)
   3.3.1 知识表示设计
   - 结构化表示(用于检索):算子字段、触发条件、权重
   - 自然语言表示(用于LLM):指令模板、代码示例
   - 两种表示的映射关系
   
   3.3.2 算子语义知识库构建
   - 知识来源(PyTorch文档、源码、社区bug报告)
   - 知识抽取方法(规则抽取 or LLM辅助标注)
   - 知识条目示例与统计
   - 质量保证(人工校验、交叉验证)
   
   3.3.3 致错模式知识库构建
   - 模式来源(历史bug分析、文献调研)
   - 模式分类体系(算子连接/参数边界/数值不稳定/动态shape陷阱)
   - 模式优先级与权重初始化
   - 知识条目示例与统计
   
   3.3.4 知识库规模与覆盖度分析
   - 覆盖了多少PyTorch算子
   - 覆盖了多少已知bug类型

3.4 测试用例特征提取(3页)
   3.4.1 特征设计
   - 特征分类:算子级、序列级、参数级、数值级、动态级
   - 特征向量定义 F = (f1, f2, ..., fn)
   - 特征取值空间(布尔/枚举/数值)
   
   3.4.2 静态分析提取方法
   - AST遍历算法
   - 算子识别规则表
   - 参数边界检测规则
   - 连接顺序提取算法
   - 动态shape变量识别
   
   3.4.3 复杂特征处理策略
   - 数据流依赖的处理(可选,可降级)
   - 跨函数调用的处理(可忽略)
   - 准确性与效率的权衡
   
   3.4.4 特征提取示例
   - 用一个具体测试用例,展示提取出的特征向量

3.5 基于特征的知识检索与匹配(4页)
   3.5.1 检索问题形式化
   - 给定特征向量F和知识库K,找到最相关的知识子集K* ⊆ K
   - 目标函数:max relevance(K*, F)
   
   3.5.2 两阶段匹配算法
   - 第一阶段:布尔条件过滤(粗筛)
   - 第二阶段:加权打分排序(精排)
   - 算法伪代码(Algorithm 1)
   
   3.5.3 打分函数设计
   - 匹配度计算:score = Σ(wi × I(cond_i satisfied))
   - 历史权重融合:final_score = α·match_score + (1-α)·historical_weight
   - 参数α的确定方法
   
   3.5.4 检索结果多样性保证
   - 避免返回相似度过高的知识
   - Top-K策略(K=2或3)
   - 多样性惩罚项(可选)

3.6 基于检索增强的LLM变异生成(3页)
   3.6.1 变异生成流程
   - 流程图:检索结果 → 提示词构建 → LLM生成 → 验证 → 反馈
   
   3.6.2 提示词模板设计
   - 模板结构(角色/任务/知识/约束/输出格式)
   - 知识注入方式(自然语言+示例)
   - 不同知识类型的提示词变体
   
   3.6.3 执行反馈与迭代修复
   - 验证器设计(编译/运行/等价性检查)
   - 错误分类与反馈消息生成
   - 修复策略与重试上限
   
   3.6.4 变异生成示例
   - 输入测试用例 → 检索到知识 → 构建提示词 → LLM输出 → 验证通过

3.7 反馈驱动的知识库权重更新(3页)
   3.7.1 更新问题形式化
   - 目标:让更有效的模式获得更高权重
   - 在线学习框架
   
   3.7.2 基于Beta分布的贝叶斯更新
   - Beta分布建模模式有效性
   - 更新公式:α ← α + success, β ← β + failure
   - 权重计算:weight = α/(α+β)
   
   3.7.3 探索与利用的平衡
   - ϵ-greedy策略
   - 新模式的探索机制
   
   3.7.4 收敛性与稳定性分析
   - 权重更新曲线示例
   - 避免过拟合的策略

3.8 算法总结与复杂度分析(2页)
   3.8.1 主算法伪代码
   - 完整的变异生成算法(Algorithm 2)
   
   3.8.2 复杂度分析
   - 特征提取:O(|AST|)
   - 知识检索:O(|K|)
   - LLM生成:O(L)
   - 总体复杂度
   
   3.8.3 方法局限性讨论
   - 静态分析的局限
   - LLM的不确定性
   - 适用场景边界
   
   
第4章 实验验证

4.1 实验设置
   - 数据集:从PyTorch测试套件中选取200个种子用例
   - 基线:随机变异、零样本LLM
   - 评价指标:生成成功率、语义差异率、覆盖率提升、平均耗时

4.2 研究问题
   RQ1:我们的方法能否生成更多可运行的变异用例?
   RQ2:我们的方法能否产生更大比例的语义差异?
   RQ3:我们的方法能否提升测试覆盖率?
   RQ4:知识库各组件(算子库、模式库)的贡献如何?

4.3 RQ1结果:生成成功率
   - 结果表格:方法 vs 基线
   - 分析:为什么我们的方法更好(知识库避免语法错误)

4.4 RQ2结果:语义差异率
   - 结果表格 + 典型案例展示
   - 分析:模式库定向变异的效果

4.5 RQ3结果:覆盖率提升
   - 分支覆盖率/行覆盖率对比
   - 分析:哪些类型的代码被新覆盖

4.6 RQ4结果:消融实验
   - 去掉算子库 vs 去掉模式库 vs 完整方法
   - 分析:两个库各自贡献

4.7 案例研究
   - 展示2-3个成功触发已知bug的案例

4.8 讨论与局限性
   - 方法的适用范围
   - 为什么没有发现新bug(可能:数据集小、时间有限、PyTorch已经很稳定)

关于如何尽可能增大测出 bug 的概率,和 deepseek 聊过之后,有下面一些实验上的建议。首先是看看有没有现成的 bug 数据集,看看能不能测出来已知的 bug。然后是可以聚焦在某个小集合的算子上,专门去测这些,可能会更好。还有就是选好的种子,比如 PyTorch 官方的单元测试代码,目前只搞了 test_torchinductor.py 里的种子,其实还可以挖。

变异规则可以是偏宽泛的通用的规则(请修改xx参数),也可以是具体的规则(请将xx参数改成 1),具体的规则更可能触发已有的 bug,可能实验效果上会更好。

目前这个提示词,明显无法增加子图结构,充其量是做一些算子的替换以及 API 参数的替换。要想修改图结构,我个人的感觉是有三个方面的做法:

  1. 基于规则变异/生成。大概就是 reborn2 以及 NNSmith 那套做法,就没有大模型什么事儿了。
  2. 种子就足够复杂,这样就不变异结构了,只替换算子或者改参数。复杂的种子可以靠其他的生成器生成?比如 reborn2?通过修改他们的参数,得到较为复杂的种子程序。或者靠官方的复杂测试用例?
  3. 参照模板插入。这个大概就是我们设计一些变异模板,比如某个 f(a, b, c) 可以通过怎样的 a = g(x, y), b = h(z, w), c = 1 之类的东西拼出来,然后 x, y, z, w 之类的再递归下去,看看是直接赋值还是再调用算子。这个模板的意义在哪里?在于让大模型知道这样变异是合法的?

如果要有变异模板的话,那么就应该有一个类似子图模式库的东西了。这样大概需要按照下面的方式去协作:

flowchart TB
    subgraph Input["输入"]
        CODE[原始测试用例]
    end

    subgraph Phase1["阶段1:代码理解"]
        FE[特征提取]
        OS[算子语义库]
        FE --> OS
    end

    subgraph Phase2["阶段2:策略决策"]
        FPM[致错模式库]
        DECIDE{选择变异方向}
        FE --> FPM
        FPM --> DECIDE
    end

    subgraph Phase3["阶段3:代码生成"]
        TEM[子图模式库]
        LLM[大语言模型]
        DECIDE --> TEM
        DECIDE --> LLM
        OS --> LLM
    end

    subgraph Output["输出"]
        RESULT[变异代码]
    end

    TEM --> RESULT
    LLM --> RESULT

三个库各自的定位:

flowchart TB
    subgraph L1["第1层:算子语义知识库"]
        O1["算子功能"]
        O2["输入输出shape规则"]
        O3["参数类型/范围"]
        O4["dtype兼容性"]
        O5["可替换候选"]
    end

    subgraph L2["第2层:致错模式库"]
        F1["算子连接型:池化→索引"]
        F2["参数边界型:output_size=1"]
        F3["数值不稳定型:小除数"]
        F4["动态shape陷阱型:ceil边界"]
    end

    subgraph L3["第3层:子图模式库"]
        S1["残差连接插入模板"]
        S2["算子重排模板"]
        S3["激活函数替换链模板"]
        S4["Conv-BN融合模板"]
    end

    O1 --> F1
    O2 --> F1
    O3 --> F2
    O5 --> S1
    
    F1 --> S1
    F2 --> S2
    F3 --> S3
    F4 --> S4

    style L1 fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    style L2 fill:#fff3e0,stroke:#e65100,stroke-width:2px
    style L3 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

这样似乎更乱了

实验设计

比较几个工作时,如果要比较发现缺陷的能力,可以用已经被确定的缺陷为”考卷“,看看其他工作能不能发现这些问题,以及我的工作能不能发现这些问题。

一个方法可能在种子和变异规则上都有一些工作,那么可能就要分别做实验去评定,种子质量和变异规则分别贡献多少。

如果害怕测不出来 bug,可以对 DL 框架本身进行变异,然后看自己的方法能否找到若干变异体里的 bug,需要和未变异的框架进行差分测试。这种变异测试也是检验自己生成的测试用例的质量的标准之一。

但是这个思路要产生很多个版本的 DL 框架,都需要编译,这可能会导致很慢且很浪费空间,这是需要解决的问题。并且,普通的变异可能 level 太低了,自己的方法未必能测出来,测出来也未必是自己的方法起的作用。

预先生成大量 DL 框架的变异体,预先生成一组测试用例,然后看 bug 的检测能力,这是可行的。

也可以对一个 DL 框架变异体一直 fuzzing,直到找到 bug,这个事情也是可行的,并且如果你是覆盖率引导的,那也只能这样去测,我们关心的是用了多少用例或者多久,测出来的 bug。

对 DL 框架本身进行变异根本没人做,可能工程上实现难度比较大吧,这个应该是不可做。

赞赏