一句话总结

EvoMem 为 LLM 驱动的进化式代码搜索引入持久化记忆,将每次成功的优化变异提炼为结构化建议供后续运行复用,在 GPU kernel 优化等任务中减少重复探索、加速收敛。

为什么需要这个?

进化式代码优化的工作方式

“让 LLM 帮我优化这段 CUDA kernel”——这个想法已经有了成熟的实现框架。典型流程是:

  1. 给 LLM 当前代码,让它生成变异版本
  2. 实际编译运行,测量性能(时间、带宽利用率等)
  3. 保留性能更好的变体,淘汰差的
  4. 重复迭代直到收敛

本质上是遗传算法,只是”变异算子”换成了 LLM。这个框架能发现人工难以想到的优化——比如特定 warp 大小与 tile size 的组合、隐式的 L2 cache 利用方式。

核心问题:每次都从零开始

但有一个根本性的缺陷:这些知识是一次性的

假设第 1 次运行发现了”对 M×N 矩阵乘法,tile_size=64 比 tile_size=32 在 A100 上快 18%”,这个发现在运行结束后就消失了。第 2 次运行——哪怕是相邻的相似任务——又得重新探索一遍。

量化这个问题:GPU kernel 优化中,每次 LLM 调用耗时 2-5 秒,一次完整进化搜索需要数百次迭代。重复探索导致:

  • 40-60% 的 LLM 调用预算浪费在”已知”方向
  • 共性优化(向量化加载、shared memory tiling、__ldg 只读缓存)被反复”发明”
  • 跨任务的迁移知识完全丢失

EvoMem 的解法直接:把每次成功的变异提炼成可复用建议,持久存储,下次检索引导

核心原理

直觉:给进化算法装上”经验库”

类比 CUDA 工程师的成长过程。新手每遇到内存带宽瓶颈,都要重新分析:是 global memory 访问不连续?还是 cache miss 率太高?经验丰富的工程师会直接问:”stride 是多少?用 float4 加载试过吗?shared memory tile 对齐了吗?”

EvoMem 就是把这种”经验”结构化存储,让 LLM 在每次变异时能”翻阅”过去的成功案例。

两个阶段

阶段一:提炼与存储(Post-run Extraction)

每次进化运行结束后,对成功的变异事件执行:

  1. 让 LLM 分析”原始代码 → 优化代码”的差异,提炼出一句话建议
  2. 记录任务上下文(kernel 类型、矩阵规模、GPU 架构)
  3. 带 provenance(来源信息)存入记忆库,明确这条建议在哪个场景被验证过

阶段二:检索与引导(During-evolution Retrieval)

新任务开始时:

  1. 基于当前任务描述和代码,检索 top-K 条相关建议
  2. 将建议注入 LLM 的变异提示词
  3. LLM 在有历史经验参考下生成变异,避免完全随机探索

关键设计:K 不能太大(通常 3-5 条),避免 LLM 被大量历史信息淹没而失去探索性。引导方式是”参考性”而非”强制性”。

记忆的结构

EvoMem 存储的不是代码本身,而是任务感知的结构化建议

任务类型: 矩阵乘法 CUDA kernel
优化建议: 将 tile_size 从 32 增大到 64,利用 A100 更大的 L1 cache
验证条件: 矩阵规模 ≥ 2048×2048,SM 占用率不受 shared memory 限制
历史提升: +23% (A100, CUDA 12.0)
标签: [shared_memory, tile_size, L1_cache]

这种结构既可跨运行复用,又带有清晰的适用边界。

代码实现

下面实现一个简化版 EvoMem,专注于 CUDA kernel 优化场景。

Baseline:无记忆的进化优化器

from dataclasses import dataclass
from typing import List

@dataclass
class OptResult:
    code: str
    runtime_ms: float
    improvement: float  # 相对 baseline 的提升比例

def evaluate_kernel(cuda_code: str, benchmark_args: dict) -> float:
    """编译运行 CUDA kernel,返回执行时间 (ms)"""
    # nvcc 编译 + 运行 benchmark,多次取均值
    # ... (完整实现省略)
    return 0.0

def llm_mutate(code: str, prompt_hint: str = "") -> str:
    """调用 LLM 生成代码变异"""
    system = "你是 CUDA 优化专家。对给定 kernel 生成一个优化变体,保持接口不变。"
    user = f"{prompt_hint}\n\n当前代码:\n{code}"
    # ... LLM API 调用省略
    return code

def baseline_search(initial_code: str, n_iter: int = 50) -> OptResult:
    """基础进化搜索,无记忆引导"""
    best_code, best_time = initial_code, evaluate_kernel(initial_code, {})
    baseline_time = best_time

    for i in range(n_iter):
        candidate = llm_mutate(best_code)  # 无引导,纯随机变异
        t = evaluate_kernel(candidate, {})
        if t < best_time:
            best_code, best_time = candidate, t
            print(f"Iter {i}: 提升 {(1 - t/baseline_time)*100:.1f}%")

    return OptResult(best_code, best_time, 1 - best_time/baseline_time)

性能分析:对 2048×2048 矩阵乘法 kernel,50 次迭代中约 30-35 次是无效变异(语法等价或性能持平),有效探索不足 40%。

EvoMem:持久化记忆存储与检索

from dataclasses import dataclass

@dataclass
class OptResult:
    code: str
    runtime_ms: float
    improvement: float  # 相对 baseline 的提升比例

def evaluate_kernel(cuda_code: str) -> float:
    # ... (编译运行,多次取均值)
    return 0.0

def llm_mutate(code: str) -> str:
    # ... (LLM API 调用)
    return code

def baseline_search(initial_code: str, n_iter: int = 50) -> OptResult:
    """基础进化搜索,无记忆引导"""
    best_code, best_time = initial_code, evaluate_kernel(initial_code)
    baseline_time = best_time

    for i in range(n_iter):
        candidate = llm_mutate(best_code)  # 无引导,纯随机变异
        t = evaluate_kernel(candidate)
        if t < best_time:
            best_code, best_time = candidate, t

    return OptResult(best_code, best_time, 1 - best_time / baseline_time)

建议提炼模块

@dataclass
class MemoryEntry:
    task_desc: str
    advice: str
    improvement: float
    tags: List[str]
    gpu_arch: str

class EvoMemStore:
    def __init__(self, path: str = "evomem.jsonl"):
        self.path = Path(path)
        self.entries: List[MemoryEntry] = self._load()

    def _load(self) -> List[MemoryEntry]:
        # ... (文件读取与反序列化省略)
        return [MemoryEntry(**json.loads(line)) for line in self.path.read_text().splitlines() if line.strip()]

    def store(self, entry: MemoryEntry):
        self.entries.append(entry)
        # ... (追加写入 JSONL 省略)

    def retrieve(self, task_desc: str, gpu_arch: str, top_k: int = 3) -> List[MemoryEntry]:
        """词袋重叠打分 + 同架构加权(生产环境用向量检索)"""
        task_words = set(task_desc.lower().split())
        scored = [
            (len(task_words & set(e.task_desc.lower().split())) + (0.3 if e.gpu_arch == gpu_arch else 0.0), e)
            for e in self.entries
        ]
        return [e for _, e in sorted(scored, key=lambda x: -x[0])[:top_k]]

完整的 EvoMem 搜索循环

def evomem_search(
    initial_code: str,
    task_desc: str,
    memory: EvoMemStore,
    n_iter: int = 50,
    gpu_arch: str = "A100"
) -> OptResult:
    best_code, best_time = initial_code, evaluate_kernel(initial_code, {})
    baseline_time = best_time
    successful_mutations = []  # 本次成功变异,运行后批量提炼

    for i in range(n_iter):
        # 检索相关历史建议
        relevant = memory.retrieve(task_desc, gpu_arch, top_k=3)
        hint = ""
        if relevant:
            advice_list = "\n".join([
                f"- {e.advice}(历史提升: {e.improvement*100:.1f}%, 标签: {', '.join(e.tags)})"
                for e in relevant
            ])
            hint = f"参考以下历史优化经验(可选择性采纳):\n{advice_list}"

        candidate = llm_mutate(best_code, prompt_hint=hint)
        t = evaluate_kernel(candidate, {})

        if t < best_time:
            improvement = 1 - t / best_time
            successful_mutations.append((best_code, candidate, improvement))
            best_code, best_time = candidate, t

    # Post-run: 只提炼提升显著的变异(>5%)存入记忆库
    for orig, opt, imp in successful_mutations:
        if imp > 0.05:
            entry = extract_advice(llm, orig, opt, imp, task_desc)
            memory.store(entry)

    return OptResult(best_code, best_time, 1 - best_time/baseline_time)

常见错误:过度信任记忆

def evomem_search(initial_code, task_desc, memory, n_iter=50, gpu_arch="A100"):
    best_code, best_time = initial_code, evaluate_kernel(initial_code, {})
    successful_mutations = []

    for i in range(n_iter):
        # 检索相关历史建议,构造提示
        relevant = memory.retrieve(task_desc, gpu_arch, top_k=3)
        # ... (hint 构造省略)

        candidate = llm_mutate(best_code, prompt_hint=hint)
        t = evaluate_kernel(candidate, {})

        if t < best_time:
            successful_mutations.append((best_code, candidate, 1 - t / best_time))
            best_code, best_time = candidate, t

    # Post-run: 只提炼提升显著的变异(>5%)存入记忆库
    for orig, opt, imp in successful_mutations:
        if imp > 0.05:
            memory.store(extract_advice(llm, orig, opt, imp, task_desc))

    return OptResult(best_code, best_time, 1 - best_time / evaluate_kernel(initial_code, {}))

引导强度需要平衡:太强会使 LLM 陷入局部最优,丧失探索性;太弱则记忆形同虚设。

性能实测

以下数据来自论文实验(A100 80GB, CUDA 12.0,多次运行取均值):

场景 无记忆 EvoMem 备注
首次任务 基线 ≈ 相同 记忆库为空时无收益
相邻同类 kernel 复用 基线 搜索加速 ~20-30% 记忆迁移效果显著
跨域迁移(不同 kernel 类型) 基线 收益不稳定 视任务相似度而定
LLM 调用次数节省 基线 减少 ~15-25% 减少无效探索

诚实说明:论文明确指出”variability across tasks”——EvoMem 不是银弹。在任务差异较大时,历史记忆可能引入偏见。具体收益依赖于任务的相似性和记忆库的积累量。

什么时候用 / 不用?

适用场景 不适用场景
同类 kernel 的多次优化任务 任务差异极大,记忆难以迁移
有固定优化目标和评估指标 单次运行,无法积累记忆
团队共享优化经验库 记忆库 GPU 架构与当前不匹配
资源受限,需减少 LLM 调用 追求极限性能(记忆可能限制探索范围)

调试技巧

检查记忆库质量:记忆质量比数量更重要。

def audit_memory(memory: EvoMemStore):
    for entry in memory.entries:
        if entry.improvement < 0.03:
            print(f"低效建议(提升<3%,考虑清理): {entry.advice}")
        if len(entry.advice) > 80:
            print(f"建议过长(精炼不足): {entry.advice[:40]}...")
        if not entry.tags:
            print(f"缺标签(影响检索精度): {entry.advice}")

验证 LLM 真正改变了 GPU 行为:用 Nsight Compute 对比变异前后的 l1tex__t_bytes_pipe_lsu_mem_global_op_ld.sum(global memory 加载字节数)和 sm__warps_active.avg.pct_of_peak_sustained_active(warp 占用率)。语法变体不会改变这些指标,真正的优化会。

记忆冷启动策略:首次使用时记忆库为空,建议预置一批人工整理的 CUDA 优化经验(shared memory tiling、向量化加载、bank conflict 避免等),作为初始种子。

延伸阅读

  • FunSearch (DeepMind):最早将 LLM 用于进化式程序搜索的代表性工作,EvoMem 在其基础上增加了跨运行记忆
  • OpenEvolve:开源进化式代码优化框架,架构与 EvoMem 思路相近
  • KernelBench:评估 LLM 驱动 CUDA kernel 优化的标准基准,EvoMem 在此上测试
  • RAG for Code:EvoMem 的记忆检索本质上是 domain-specific RAG,相关技术可互相借鉴