news 2026/10/2 2:21:09

SK²Decompile 强化学习实战:基于 VERL/GRPO 的双阶段二进制反编译 RL 训练完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SK²Decompile 强化学习实战:基于 VERL/GRPO 的双阶段二进制反编译 RL 训练完整指南
  • 人工智能
  • 大模型
  • 逆向工程
  • 微调
  • 代码模型

【免费下载链接】LLM4Decompile

Reverse Engineering: Decompiling Binary Code with Large Language Models

项目地址:https://gitcode.com/GitHub_Trending/ll/LLM4Decompile
点击查看免费下载

SK²Decompile 是 LLM4Decompile 项目推出的两阶段二进制反编译方案(Skeleton→Skin,即先恢复结构骨架、再恢复标识符语义)。本文以仓库内 RL 训练文档 为骨架,结合 reward_functions 与 scripts 的参考实现源码,完整讲解如何在 VERL 框架上复现 SK²Decompile 的 GRPO 强化学习管线。读完本文你将掌握:两阶段奖励函数(编译反馈 + 占位符 Jaccard、嵌入余弦相似度)的数学设计与代码对应关系、奖励函数接入 VERL 的路由注册方法、Parquet 训练数据格式、两个参考训练脚本的逐参数含义,以及 OOM、嵌入服务连接失败、编译超时三类典型问题的排查方法。

背景:SFT 之后,为什么要用强化学习再对齐

SK²Decompile 的训练管线分为三个阶段:数据预处理(伪代码规范化)、两阶段监督微调(SFT),以及本文重点的强化学习(RL)。在 SFT 阶段,两个模型分别学习:

  • Skeleton 模型(Structure Recovery):将二进制/伪代码转换为规范化中间表示(IR);
  • Skin 模型(Identifier Naming):将 IR 还原为带语义标识符的可读源码。

SFT 的目标是模仿数据集,而 RL 的目标是直接优化"任务特定目标"。SK²Decompile 在 SFT 之后,对每个阶段模型应用GRPO(Group Relative Policy Optimization)算法(DeepSeek-AI et al., 2025),并用各自的奖励信号做对齐:

  • Structure Recovery(Skeleton):奖励基于编译器反馈——生成的 IR 只有成功编译才给予正向奖励,并叠加占位符恢复正确性分量(论文 Eq. 3);
  • Identifier Naming(Skin):奖励为生成代码与参考源码的嵌入余弦相似度,鼓励语义对齐的标识符预测而非精确词法匹配(论文 Eq. 4)。

仓库中提供的奖励函数与训练脚本是参考实现,用于复现 RL 训练管线;精确的奖励公式与设计动机请参阅论文第 3.5 节(arXiv:2509.22114)。

目录结构与模块职责

sk2decompile/verl/SK2DECOMPILE/ ├── README.md # RL 训练文档(本文依据) ├── data/ │ └── sk2decompile-rl-examples.jsonl # RL 训练数据示例 ├── reward_functions/ # 参考奖励实现 │ ├── __init__.py │ ├── exe_type.py # 编译可行性 + 占位符 Jaccard │ ├── sim_exe.py # 编译可行性 + 词级相似度 │ ├── embedding_gte.py # 嵌入标识符相似度(GTE) │ └── embedding_qwen3.py # 嵌入标识符相似度(Qwen3) └── scripts/ ├── run_struct_rl.sh # Structure Recovery RL 参考脚本 └── run_ident_rl.sh # Identifier Naming RL 参考脚本

其中 reward_functions/init.py 将四个奖励函数统一导出为exe_type、sim_exe、embedding_gte、embedding_qwen3,并明确说明接入 VERL 的方式:把文件复制到verl/utils/reward_score/后在路由分发器中注册分支(详见下文 Step 2)。

奖励公式与参考实现逐行解读

Structure Recovery 奖励(Eq. 3):编译可行性 + 占位符恢复

结构恢复奖励由两部分组成:

  1. 可编译性(Compilability):用 ground-truth 头文件编译生成的 IR,仅在编译成功时给予 1.0 奖励(头文件生成由 Psyche-C 验证);
  2. 占位符恢复(Placeholder Recovery):生成占位符集合 I_gen 与真实集合 I_IR 之间的 Jaccard 相似度:
r_placeholder = |I_gen ∩ I_IR| / |I_gen ∪ I_IR| r_structure = { 0.0, if IR 无法编译 { 1.0 + r_placeholder, if IR 可编译

仓库参考实现 exe_type.py 与该公式严格对应,核心逻辑如下:

def compute_score(solution_str, ground_truth, extra_info=None): type_score_value, _ = type_score(solution_str, ground_truth, extra_info) compileable_score_value = compileable_score(solution_str, ground_truth, extra_info) if compileable_score_value == 0.0: return 0.0 return type_score_value + compileable_score_value
  • type_score:通过四个正则模式r'\bfunc\w*\b'、r'\btype\w*\b'、r'\bvar\w*\b'、r'\bfield\w*\b'分别提取候选代码与 ground truth 中的占位符标识符集合,再计算 Jaccard 相似度。这里对应的是规范 IR 中func*/type*/var*/field*前缀的通用占位符体系——这是 SK²Decompile "语言无关中间表示" 的关键设计。
  • compileable_score:在临时目录中把extra_info携带的 C 头文件声明与候选代码拼接写入temp.c,调用subprocess.run(['gcc', '-c', source_file, '-o', object_file], timeout=5, check=True)做gcc -c只编译不链接检查,返回 1.0/0.0。注意两点:每个样本 5 秒超时,且异常(如语法错误、死循环导致超时)一律返回 0.0。

extra_info中的header字段(C 头文件声明)是可编译性检查的前提,这正是训练数据中必须携带该字段的原因。

可选变体:词级 Jaccard + 编译可行性(sim_exe)

sim_exe.py 是结构恢复奖励的替代参考实现:先计算候选与 ground truth 的词级 Jaccard 相似度(对字符串lower().split()后取集合交并比),再叠加编译分数:

def compute_score(solution_str, ground_truth, extra_info=None): sim_score = jaccard_similarity(solution_str, ground_truth) compile_score = compileable_score(solution_str, ground_truth, extra_info) if sim_score > 0.5: return sim_score + compile_score return 0

与exe_type的差异在于:它以 0.5 为词级相似度阈值,低于阈值直接给 0 分,避免奖励被低质量输出污染。compileable_score与exe_type.py中完全相同(同为 5 秒超时的gcc -c检查)。

Identifier Naming 奖励(Eq. 4):嵌入余弦相似度

标识符命名奖励用嵌入向量之间的余弦相似度衡量生成代码与参考源码的语义相似度:

r_identifier = cos(e_gen, e_src) = (e_gen · e_src) / (||e_gen|| · ||e_src||)

其中e_gen、e_src分别为生成代码与参考代码的嵌入。论文实验中使用的嵌入模型是 qwen-embedding-0.6B(Zhang et al., 2025)。

仓库提供了两个参考实现:embedding_qwen3.py(默认模型Qwen3-Embedding-0.6B)与 embedding_gte.py(默认模型gte-large-en-v1.5),两者代码结构完全一致,流程为:

  1. tree-sitter 解析 C 代码并分类提取标识符:_classify_node将节点分为四类——type_identifier→type、field_identifier→field、函数声明符/调用表达式中的identifier→func、声明类节点中的identifier→var(每类最多 64 个);
  2. 构建命名摘要串:_build_summary_text生成形如"func: foo bar || type: my_type || field: field1 field2 || var: i j k"的摘要;
  3. 单次 API 调用同时嵌入两个摘要:_embed_two通过 OpenAI 兼容客户端一次请求返回两段文本的向量;
  4. 返回余弦相似度的平方:compute_score返回name_score * name_score,文档注释说明这是为了锐化奖励信号(放大高低分差异)。

环境变量配置(默认值见源码 docstring):

环境变量默认值说明
QWEN3_EMBEDDING_MODEL_PATH/GTE_EMBEDDING_MODEL_PATHQwen3-Embedding-0.6B/gte-large-en-v1.5嵌入模型名/路径
QWEN3_EMBEDDING_API_KEY/GTE_EMBEDDING_API_KEY(或OPENAI_API_KEY)noneAPI Key
QWEN3_EMBEDDING_API_BASE/GTE_EMBEDDING_API_BASEhttp://127.0.0.1:8000/v1OpenAI 兼容 API 地址

注意:reward_functions/中的实现是演示奖励设计的参考代码,完整的公式与设计动机请以论文 Section 3.5 为准。

复现指南:五步跑通 RL 训练

Step 1:安装 VERL

RL 训练基于VERL v0.4.1(HybridFlow,Sheng et al., 2024)。为保证可复现性,建议使用相同版本:

git clone <VERL 仓库地址> cd verl git checkout v0.4.1 # 或最接近 v0.4.1 的 commit pip install -e .

Step 2:集成奖励函数到 VERL

将四个奖励函数复制进 VERL 的奖励模块,并在路由分发器中注册:

# 复制奖励函数 cp reward_functions/exe_type.py <VERL_DIR>/verl/utils/reward_score/sk2d_exe_type.py cp reward_functions/sim_exe.py <VERL_DIR>/verl/utils/reward_score/sk2d_sim_exe.py cp reward_functions/embedding_gte.py <VERL_DIR>/verl/utils/reward_score/sk2d_embedding_gte.py cp reward_functions/embedding_qwen3.py <VERL_DIR>/verl/utils/reward_score/sk2d_embedding_qwen3.py

然后在<VERL_DIR>/verl/utils/reward_score/__init__.py的default_compute_score()函数中添加路由分支:

# Structure Recovery 奖励(示例) elif data_source == "sk2decompile_structure": from . import sk2d_exe_type res = sk2d_exe_type.compute_score(solution_str, ground_truth, extra_info) # Identifier Naming 奖励(示例) elif data_source == "sk2decompile_identifier": from . import sk2d_embedding_qwen3 res = sk2d_embedding_qwen3.compute_score(solution_str, ground_truth, extra_info)

训练 Parquet 文件中的data_source字段决定了每个样本被分发到哪个奖励函数。从源码可见,compute_score的统一签名是(solution_str, ground_truth, extra_info=None),extra_info透传header、index、split等附加信息——这一统一接口正是让四个变体能够即插即用的基础。

Step 3:准备训练数据

训练数据为 Parquet 格式,每行包含以下字段:

字段说明
promptChat 格式消息,例如[{"role": "user", "content": "<pseudocode>... What is the source code?"}]
data_source奖励函数路由键(必须与 Step 2 注册的分支匹配)
reward_model.ground_truth期望输出(Structure Recovery 为 IR,Identifier Naming 为源码)
reward_model.style"rule"(基于规则的奖励)
extra_info.headerC 头文件声明,用于可编译性检查(仅 Structure Recovery)

仓库中的 data/sk2decompile-rl-examples.jsonl 提供了具体格式示例。观察示例数据可以发现两个值得注意的细节:

  1. 每条记录的data_source为"llm4decompile_exe",与 README 中的示例分支名(sk2decompile_structure/sk2decompile_identifier)不同——实际训练时需要把该字段改成与你在 Step 2 注册的分支一致的键,这再次印证了data_source即"路由键"的设计;
  2. extra_info中还包含index(样本序号)与split(train/valid)字段,可用于数据溯源;多数样本携带详细的header(typedef、struct 定义、函数声明),而少数样本(如 index 87208 的示例)header为null——这类样本无法走编译检查类奖励。

训练前需要把 JSONL 转换为 Parquet 格式。

Step 4:启动训练

参考训练脚本位于 scripts/,启动前先编辑脚本顶部的配置变量。

Structure Recovery RL:

# 编辑 scripts/run_struct_rl.sh 设置: # VERL_DIR, VENV_PATH, MODEL_PATH, TRAIN_DATA, VAL_DATA, WANDB_* bash scripts/run_struct_rl.sh

Identifier Naming RL(需要运行中的嵌入服务):

# 1. 启动嵌入服务 python -m vllm.entrypoints.openai.api_server \ --model Qwen3-Embedding-0.6B --port 8000 --dtype float16 # 2. 编辑 scripts/run_ident_rl.sh 设置: # VERL_DIR, VENV_PATH, MODEL_PATH, TRAIN_DATA, VAL_DATA, WANDB_* bash scripts/run_ident_rl.sh

从脚本源码可以补充两个关键开关:run_struct_rl.sh顶部用REWARD_VARIANT="exe_type"(可选"exe_type"或"sim_exe")选择结构恢复奖励变体;run_ident_rl.sh用EMBEDDING_VARIANT="gte"(可选"gte"或"qwen3")选择嵌入模型。两个脚本均基于 VERL 的python3 -m verl.trainer.main_ppo --config-path=config --config-name='ppo_trainer-lm4dc.yaml'启动 GRPO 训练,并通过algorithm.adv_estimator=grpo显式指定优势估计器。

脚本中值得关注的训练运行参数(以run_struct_rl.sh为例):

  • data.filter_overlong_prompts=True、data.truncation='error':对超长 prompt 做过滤与截断错误处理;
  • actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=4:默认微批大小(OOM 时可调小);
  • actor_rollout_ref.actor.use_kl_loss=True与kl_loss_coef:开启 KL 惩罚约束策略偏离;
  • actor_rollout_ref.rollout.name=vllm与rollout.gpu_memory_utilization=0.80:使用 vLLM 做 rollout、显存占用上限 0.80;
  • actor_rollout_ref.rollout.n=16:GRPO 每组采样 16 条;
  • actor_rollout_ref.ref.fsdp_config.param_offload=True:参考模型参数卸载到 CPU 以省显存;
  • trainer.default_local_dir=${VERL_DIR}/checkpoints/${TASK_NAME}:checkpoint 输出目录;
  • 脚本会通过tee同时把标准输出/错误写入${VERL_DIR}/logs/${TASK_NAME}.log与.err文件;
  • NUM_NODES与GPUS_PER_NODE默认不同:结构恢复脚本默认 2 节点 × 8 卡,标识符命名脚本默认 1 节点 × 8 卡,请按实际硬件调整。

Step 5:安装附加依赖

# 编译类奖励(Structure Recovery) apt install gcc pip install psychec # 或从 Psyche-C 仓库源码构建 # 嵌入类奖励(Identifier Naming) pip install tree-sitter==0.24.0 tree-sitter-c==0.23.4 openai

注意:tree-sitter-c==0.23.4与tree-sitter==0.24.0的版本组合是嵌入类奖励源码中Language(tsc.language())正常工作所要求的固定搭配,不建议随意升级。

参考超参数配置

训练脚本中使用的参考超参数如下:

参数Structure RecoveryIdentifier Naming
train_batch_size128128
max_prompt_length10241024
max_response_length20482048
lr1e-61e-6
kl_loss_coef0.010.02
kl_loss_typelow_var_kllow_var_kl
rollout.n(GRPO 采样数)1616
total_epochs22

两者的主要差异在于kl_loss_coef:Identifier Naming 使用 0.02,略高于 Structure Recovery 的 0.01。脚本中还可以看到actor_rollout_ref.actor.entropy_coeff=0(不显式加熵奖励)与algorithm.use_kl_in_reward=False(KL 不进奖励、单独作为损失项)。这些配置共同构成了 GRPO 训练的标准设置,实际复现时可根据硬件与数据规模调整。

常见问题排查

OOM(显存不足):

  • 调小ppo_micro_batch_size_per_gpu(默认 4);
  • 开启actor.fsdp_config.param_offload=True;
  • 调低rollout.gpu_memory_utilization(默认 0.80)。

脚本源码中与显存相关的默认值还包括:actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=1(rollout log-prob 微批为 1)与actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=4,以及默认关闭的enable_gradient_checkpointing——如果 OOM 仍然严重,可以尝试开启梯度检查点。

嵌入服务连接错误(仅 Identifier Naming):

  • 确认 vLLM 嵌入服务运行在 8000 端口;
  • 检查环境变量QWEN3_EMBEDDING_API_BASE(默认http://127.0.0.1:8000/v1),GTE 变体则检查GTE_EMBEDDING_API_BASE。

奖励中编译超时(仅 Structure Recovery):

  • gcc -c每个样本有 5 秒超时(源码中timeout=5的直接体现);
  • 若大量样本超时,检查生成代码是否包含死循环。

结合仓库快速对照

如果你想基于本文档做源码级深挖,建议按以下顺序阅读仓库文件:

  1. 奖励实现:reward_functions/exe_type.py、sim_exe.py、embedding_qwen3.py、embedding_gte.py;
  2. 训练脚本:scripts/run_struct_rl.sh、scripts/run_ident_rl.sh;
  3. 训练数据格式:data/sk2decompile-rl-examples.jsonl;
  4. 上游完整管线:SK²Decompile 的 SFT 与数据预处理见 sk2decompile/README.md(其中 RL 章节同样指向本文档),RL 训练数据在sk2decompile/verl/SK2DECOMPILE/data/下。

理解清楚"奖励设计(Eq. 3/Eq. 4)→ 参考实现(4 个 reward 模块)→ 路由注册(data_source)→ GRPO 训练(2 个脚本)→ 排错"这条链路后,你就能基于本仓库在自己的数据集与硬件上复现 SK²Decompile 的强化学习训练,并根据任务特点替换奖励变体或调整超参数。

  • 人工智能
  • 大模型
  • 逆向工程
  • 微调
  • 代码模型

【免费下载链接】LLM4Decompile

Reverse Engineering: Decompiling Binary Code with Large Language Models

项目地址:https://gitcode.com/GitHub_Trending/ll/LLM4Decompile
点击查看免费下载

相关推荐

上一篇:Home Assistant OSO Energy 集成:使用 `osoenergy.turn_off` 关闭热水器加热
下一篇:Rerun 可视化 ControlNet 图像生成全流程:逐扩散时间步记录 Canny 边缘条件扩散过程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 2:20:48

Python ag-funutils 包详解与实战案例

1. 引言ag-funutils 是一个面向 Python 的函数式编程工具包&#xff0c;旨在为开发者提供一组轻量、易用且可组合的工具函数&#xff0c;帮助简化日常开发中的数据处理、集合操作和函数组合等任务。它借鉴了函数式编程语言中的常用模式&#xff0c;同时保持了 Python 的简洁风格…

作者头像 李华
网站建设 2026/10/2 2:20:48

5分钟上手Label Studio:多模态数据标注完全指南

5分钟上手Label Studio&#xff1a;多模态数据标注完全指南 【免费下载链接】label-studio Label Studio is a multi-type data labeling and annotation tool with standardized output format 项目地址: https://gitcode.com/GitHub_Trending/la/label-studio 标注队列…

作者头像 李华
网站建设 2026/10/2 2:20:00

Windows下安装Redis的四种方式与配置排查指南

在Linux上装Redis&#xff0c;一句apt install redis-server就完事&#xff0c;连配置文件都不用动。到了Windows就完全是另一番景象&#xff1a;你去Redis官网的Download页翻一遍&#xff0c;Linux、macOS、Docker的安装说明都列得清清楚楚&#xff0c;唯独没有Windows安装包。…

作者头像 李华
网站建设 2026/10/2 2:16:03

ArcGIS矢量化与拓扑检查:从扫描图到干净SHP的完整链路

简介&#xff1a;这份资源面向GIS初学者与测绘、规划、自然资源等行业的从业者&#xff0c;围绕ArcGIS矢量化与拓扑检查两大核心技能&#xff0c;提供一套可直接上手练习的完整数据包&#xff0c;帮助解决栅格转矢量、空间关系校验与数据质量修复等实际问题。压缩包共37个文件&…

作者头像 李华