FunReason 核心技术深度剖析

FunReason 核心技术深度剖析

--- - 用GPT-4o生成的CoT可能包含策略痕迹 - 需要验证生成数据的可执行性 - 需要确保格式统一性 --- --- 目标:区分function call和普通对话 技术实现: 示例: --- 目标:验证查询是否可用给定工具解决 核心逻辑: 典型Case: Case 1: 通过 Case ...

FunReason 核心技术深度剖析

📊 技术全景图

┌─────────────────────────────────────────────────────────┐
                    FunReason Framework                   
├─────────────────────────────────────────────────────────┤
                                                          
  ┌──────────────────────┐    ┌──────────────────────┐  
     FCDR Pipeline      │───▶│   SRML Training        
    (数据质量保证)              (训练优化方法)          
  └──────────────────────┘    └──────────────────────┘  
                                                       
                                                       
           └────── Self-Refinement ──────┘               
                   (迭代提升)                             
└─────────────────────────────────────────────────────────┘

第一部分:FCDR - Function Call Data Refinement

1. 设计动机:为什么需要FCDR?

问题1:现有数据集的质量问题

# 原始xLAM数据集的典型问题

问题类型1: CoT推理错误
User: "查询北京明天的天气"
CoT: <think>用户想查天气应该调用get_news函数...</think>  # ❌ 逻辑跳跃
Result: [get_news(city="北京")]  # ❌ 函数选错

问题类型2: 参数错误
User: "发邮件给张三"
Result: [send_email(to="张三")]  # ❌ 应该是邮箱地址

问题类型3: 格式错误
Result: [get_weather('city'="北京")]  # ❌ 参数名不应加引号

问题2:Token不平衡的根源

统计发现 (Table 1):
CoT部分平均token:    350.74 (中位数248)
Result部分平均token:   31.07 (中位数27)
比例: 约 11:1

影响: 传统SFT会让模型"夸夸其谈但执行不准"

问题3:合成数据的可靠性

  • 用GPT-4o生成的CoT可能包含策略痕迹
  • 需要验证生成数据的可执行性
  • 需要确保格式统一性

2. FCDR架构设计

2.1 整体流程图

                    ┌──────────────┐
                    │ 输入:xLAM-cot│
                    └───────┬──────┘
                            │
                    ┌───────▼──────────┐
                    │ Stage 1: 是FC吗? │
                    └───────┬──────────┘
                            │ Yes
                    ┌───────▼──────────────┐
                    │ Stage 2: 能用工具解决?│
                    └───────┬──────────────┘
                            │ Yes
                    ┌───────▼──────────────┐
                    │ Stage 3: CoT推理对吗?│
                    └───────┬──────────────┘
                            │ Yes
                    ┌───────▼──────────────┐
                    │ Stage 4: 函数名参数对?│◄─┐
                    └───────┬──────────────┘  │
                            │ Yes              │Regenerate
                    ┌───────▼──────────────┐  │
                    │ Stage 5: 格式对吗?   │──┘
                    └───────┬──────────────┘
                            │ Yes
                    ┌───────▼──────────────┐
                    │ 保存到FCDR数据集     │
                    └──────────────────────┘

注: Stage 1-3不通过 → Drop
    Stage 4-5不通过 → Regenerate

3. 五阶段详细剖析

Stage 1: Response Identification

目标:区分function call和普通对话

技术实现

# Prompt结构(简化版)
prompt = f"""
判断以下内容是function_tool调用还是普通回复:
常见格式:[func_name(param1=value1, param2=value2)]

输入:{reference_answer}
输出:<think>...</think><judge>True/False</judge>
"""

# QwQ-32B处理
response = qwq_model.generate(prompt)
is_function_call = parse_judge_tag(response)

if not is_function_call:
    save_as_dialogue_data()  # 保存为普通对话数据
    return

示例

输入1: "[get_weather(city='北京')]"
输出: <think>符合function call格式</think><judge>True</judge>

输入2: "今天北京天气不错,温度15度"
输出: <think>这是普通回复</think><judge>False</judge>

Stage 2: Query & Tool Identification

目标:验证查询是否可用给定工具解决

核心逻辑

def check_query_tool_match(query, tools, reference_answer):
    """
    检查三个关键点:
    1. 工具列表中是否有相关功能的函数
    2. 查询中是否包含足够的参数信息
    3. 参数值能否从query中提取
    """

    prompt = f"""
    分析以下内容:
    1. 函数名能从候选工具中找到吗?
    2. 参数值能从查询中分析出来吗?

    User Query: {query}
    Candidate Tools: {tools}
    Reference Answer: {reference_answer}

    输出:<think>...</think><judge>True/False</judge>
    """

    result = qwq_model.generate(prompt)
    return parse_judge(result)

典型Case

Case 1: 通过

Query: "查北京天气"
Tools: [get_weather(city, date), send_email(to, content)]
Reference: [get_weather(city="北京")]
→ ✅ 工具有get_weather,参数city能从query提取

Case 2: 不通过(工具不匹配)

Query: "帮我订机票"
Tools: [get_weather(city), send_email(to, content)]
Reference: [book_flight(from, to, date)]
→ ❌ 工具列表中没有订票功能 → Drop

Case 3: 不通过(参数不足)

Query: "发邮件"
Tools: [send_email(to, content)]
Reference: [send_email(to="unknown@example.com", content="...")]
  query中没有收件人信息reference硬编码了地址  Drop

Stage 3: CoT Identification

目标:验证推理过程的连贯性和正确性

三重检查机制

def verify_cot_quality(cot, reference_fc):
    """
    1. 起点检查:推理是否从合理的位置开始
    2. 步骤检查:每一步是否紧密跟随上一步
    3. 终点检查:最后一步是否指向正确答案
    """

    checks = {
        'start_valid': check_reasoning_start(cot),
        'steps_coherent': check_step_connections(cot),
        'conclusion_correct': check_final_step(cot, reference_fc)
    }

    return all(checks.values())

示例分析

Example 1: 优质CoT(通过)

Query: "检索一个长度至少7个字符的动词变位词"
CoT:
<think>
1. 用户需要一个词,所以要调用词典相关函数
2. 要求是"动词变位",参数verbeconjugue应该为True
3. 要求"长度至少7",参数minlong应该设为'7'
4. 需要定义,参数avecdef设为True
→ 应该调用get_random_word函数
</think>
Reference: [get_random_word(verbeconjugue=True, minlong='7', avecdef=True)]

验证结果:
✅ 起点:从"用户需要词"开始,合理
✅ 连贯:每步都基于上一步和需求推进
✅ 终点:正确导出函数和所有参数
→ Pass

Example 2: 劣质CoT(不通过)

Query: "查北京天气"
CoT:
<think>
1. 用户想知道天气
2. 应该搜索新闻
3. 调用get_news函数
</think>
Reference: [get_news(city="北京")]

验证结果:
✅ 起点:从"天气"开始,合理
❌ 连贯:步骤2跳跃到"新闻",逻辑断裂
❌ 终点:导出了错误函数
→ Drop

Stage 4: Function & Parameter Identification

目标:验证并修正函数名和参数

这是唯一会Regenerate的阶段(4和5)

检查流程

def verify_and_fix_function(query, tools, reference_fc):
    """
    1. 函数名是否在tools中?
    2. 参数名是否匹配函数定义?
    3. 参数值类型是否正确?
    4. 参数值是否合理?
    """

    prompt = f"""
    检查function call的函数名和参数:
    1. 函数名是否正确?
    2. 参数是否都有效?

    若有问题,生成修正后的function call。

    Query: {query}
    Tools: {tools}
    Reference FC: {reference_fc}

    输出:
    <think>...</think>
    <judge>True/False</judge>
    <NewFC>[修正后的函数调用]</NewFC>
    """

    response = qwq_model.generate(prompt)
    is_valid, corrected_fc = parse_response(response)

    return corrected_fc if not is_valid else reference_fc

修正案例

Case 1: 参数名错误

Reference: [get_weather(location="北京")]
Tools定义: get_weather(city: str, date: str)

QwQ-32B分析:
<think>
工具定义中参数名是city,不是location
应该修正为city
</think>
<judge>False</judge>
<NewFC>[get_weather(city="北京")]</NewFC> 自动修正并保存

Case 2: 参数值类型错误

Reference: [set_alarm(time=8)]
Tools定义: set_alarm(time: str)  # 要求格式"HH:MM"

QwQ-32B分析:
<think>
time参数应该是字符串格式"08:00"
当前是整数8,需要转换
</think>
<judge>False</judge>
<NewFC>[set_alarm(time="08:00")]</NewFC> 修正类型并保存

Case 3: 参数缺失

Reference: [get_weather(city="北京")]
Tools定义: get_weather(city: str, date: str = "today")

QwQ-32B分析:
<think>
date是可选参数,有默认值"today"
当前未提供date,但有默认值可用
可以接受
</think>
<judge>True</judge> 直接通过

Stage 5: Format Identification

目标:统一JSON格式规范

严格的格式要求

# 正确格式规范
CORRECT_FORMAT = """
[func_name(param_name=param_value, ...)]

关键规则:
1. 参数名(param_name)不加引号
2. 字符串参数值(param_value)加双引号
3. 数字/布尔值不加引号
4. 多个函数用逗号分隔
"""

# 常见错误格式
WRONG_FORMATS = [
    '[func("param_name"="value")]',      # ❌ 参数名加了引号
    "[func(param_name='value')]",        # ❌ 应该用双引号
    '[func(param_name=value)]',          # ❌ 字符串值漏了引号
]

格式修正示例

Example 1: 参数名引号问题

输入:
[getPrivacyViolationRisk("data"="personal_info", "purpose"="marketing")]

QwQ-32B修正:
<think>
参数名data和purpose不应该加引号
参数值是字符串,保留双引号
</think>
<judge>False</judge>
<NewFC>[getPrivacyViolationRisk(data="personal_info", purpose="marketing")]</NewFC> 修正保存

Example 2: 单引号问题

输入:
[get_weather(city='北京', date='today')]

QwQ-32B修正:
<think>
应该统一使用双引号
</think>
<judge>False</judge>
<NewFC>[get_weather(city="北京", date="today")]</NewFC> 修正保存

Example 3: 数字类型

输入:
[set_volume(level="50")]

QwQ-32B修正:
<think>
level是数字类型,不应该加引号
</think>
<judge>False</judge>
<NewFC>[set_volume(level=50)]</NewFC> 修正保存

4. FCDR的技术细节

4.1 为什么用QwQ-32B?

选择理由: 1. 强推理能力: 32B参数,专为推理优化 2. 自我纠错: 能够识别自己生成内容的问题 3. 遵循指令: 严格按照prompt格式输出

对比其他选择

GPT-4o:
✅ 推理能力强
❌ 闭源,无法完全控制
❌ API调用成本高(60K样本 × 5阶段)

Claude-3.5:
✅ 推理能力强
❌ 闭源
❌ 成本高

QwQ-32B:
✅ 开源可控
✅ 推理能力足够(见Table 2: 78.70%)
✅ 可本地部署(vLLM)
✅ 成本可控

4.2 Prompt Engineering技巧

关键设计原则

# 1. 明确的输出格式要求
prompt_template = """
<think>思考过程</think>
<judge>True/False</judge>
<NewFC>[修正结果]</NewFC>  # 仅Stage 4-5需要
"""

# 2. 具体的评判标准
"""
✅ 给出正面和负面示例
✅ 列出明确的检查点
✅ 说明边界情况处理
"""

# 3. 结构化的输入
"""
分块提供:Query、Tools、Reference
使用标签:<query>、<tools>、<refFC>

实际Prompt示例(Stage 4)

Judge whether the function names and parameters meet requirements:

1) Check if function names and parameter values are correct
2) If valid, output <judge>True</judge>
3) If invalid, output <judge>False</judge> and provide corrected version

Output format:
<think>analysis process</think>
<judge>True/False</judge>
<NewFC>[corrected_function_call]</NewFC>

Input:
User query: <query>查北京天气</query>
Candidate tools: <tools>
{
  "get_weather": {
    "parameters": {
      "city": {"type": "string", "required": true},
      "date": {"type": "string", "default": "today"}
    }
  }
}
</tools>
Reference FC: <refFC>[get_weather(location="北京")]</refFC>

4.3 数据流转机制

class FCDRPipeline:
    def __init__(self, model="QwQ-32B"):
        self.model = load_model(model)
        self.stats = {
            'input': 0,
            'stage1_drop': 0,
            'stage2_drop': 0,
            'stage3_drop': 0,
            'stage4_regenerate': 0,
            'stage5_regenerate': 0,
            'output': 0
        }

    def process(self, data):
        self.stats['input'] = len(data)

        # Stage 1: Response Identification
        data = [d for d in data if self.stage1(d)]
        self.stats['stage1_drop'] = self.stats['input'] - len(data)

        # Stage 2: Query & Tool Identification
        data = [d for d in data if self.stage2(d)]
        self.stats['stage2_drop'] = self.stats['input'] - len(data)

        # Stage 3: CoT Identification
        data = [d for d in data if self.stage3(d)]
        self.stats['stage3_drop'] = self.stats['input'] - len(data)

        # Stage 4: Function & Parameter (with regenerate)
        data = [self.stage4_fix(d) for d in data]

        # Stage 5: Format (with regenerate)
        data = [self.stage5_fix(d) for d in data]

        self.stats['output'] = len(data)
        return data

    def report(self):
        print(f"""
        输入: {self.stats['input']}
        Stage 1 丢弃: {self.stats['stage1_drop']}
        Stage 2 丢弃: {self.stats['stage2_drop']}
        Stage 3 丢弃: {self.stats['stage3_drop']}
        Stage 4 修正: {self.stats['stage4_regenerate']}
        Stage 5 修正: {self.stats['stage5_regenerate']}
        最终输出: {self.stats['output']}
        保留率: {self.stats['output']/self.stats['input']*100:.1f}%
        """)

5. FCDR的实验效果

5.1 数据质量提升

Table 3 实验结果(3种模型的对比):

模型 xLAM-cot FunReason-FCDR 提升
Llama-3.1-8B 79.50% 81.46% +1.96%
Llama-3.2-3B 67.38% 72.97% +5.59%
Qwen2.5-Coder-7B 78.70% 81.94% +3.24%

结论: - ✅ 所有模型都受益于FCDR - ✅ 小模型受益更明显(Llama-3.2-3B提升5.59%) - ✅ 证明数据质量比数量更重要

5.2 各阶段的过滤率(估算)

根据论文描述推测:

输入: 60,000 samples (xLAM-cot)

Stage 1: ~2% drop      58,800 samples
Stage 2: ~5% drop      55,860 samples  
Stage 3: ~8% drop      51,391 samples
Stage 4: ~10% regen    51,391 samples (修正约5,139)
Stage 5: ~5% regen     51,391 samples (修正约2,570)

最终输出: ~60,000 samples (FunReason-FCDR)
注:Stage 4-5修正后补齐到60K

第二部分:SRML - Self-Refinement Multiscale Loss

1. 设计动机:为什么需要新损失函数?

问题根源:Token不平衡导致的优化偏差

传统SFT的问题

# 一个典型的训练样本
sample = {
    "think": "<think>...</think>",  # 350 tokens
    "result": "[func()]"             # 31 tokens
}

# 传统Cross-Entropy Loss
total_tokens = 350 + 31 = 381
L_total = (1/381) × Σ[-log P(token)]

# 问题:350个"think" tokens的loss贡献远大于31个"result" tokens
# 导致梯度更新主要优化推理部分,而函数调用部分被"淹没"

数学上的表现

传统SFT隐含权重:
w_think = N_think / N_all = 350 / 381  0.92
w_result = N_result / N_all = 31 / 381  0.08

影响:
- 模型学会生成"看起来合理"的长推理
- 但对函数调用的准确性关注不足
- 类似于"夸夸其谈但不办实事"

2. SRML数学原理

2.1 问题形式化

定义符号: - M: Language Model - N_t: CoT部分的token数量 - N_f: Function call部分的token数量
- N_all = N_t + N_f - L_think: CoT部分的平均cross-entropy loss - L_result: Function call部分的平均cross-entropy loss

2.2 传统SFT损失函数推导

完整形式

L_total = (1/N_all) × Σ[i=1 to N_all] -log P(y_i | y_<i, x)

分解为两部分

L_total = (1/N_all) × [
    Σ[i=1 to N_t] -log P(y_i | y_<i, x)           # Think部分
    + Σ[i=N_t+1 to N_all] -log P(y_i | y_<i, x)   # Result部分
]

改写为加权形式

L_total = (N_t/N_all) × (1/N_t) × Σ[i=1 to N_t] -log P(...)
        + (N_f/N_all) × (1/N_f) × Σ[i=N_t+1 to N_all] -log P(...)

       = w_t × L_think + w_f × L_result

其中: - w_t = N_t / N_all ≈ 0.92 - w_f = N_f / N_all ≈ 0.08 - w_t + w_f = 1

问题明确:权重严重不平衡!


2.3 SRML改进方案

核心思想:手动设置权重α和β,打破token数量的束缚

L_MSL = α × L_think + β × L_result

约束: α + β = 1, α,β ∈ [0,1]

关键区别

传统SFT: 权重由数据决定 (α≈0.92, β≈0.08)
SRML:    权重由设计者决定 (α可调节)

2.4 梯度分析

对think部分的梯度

∂L_MSL/∂θ_think = α × ∂L_think/∂θ

传统SFT: 系数为 0.92
SRML:    系数为 α (可调,通常0.5-0.7)

对result部分的梯度

∂L_MSL/∂θ_result = β × ∂L_result/∂θ

传统SFT: 系数为 0.08
SRML:    系数为 β (可调,通常0.3-0.5)

效果: - ✅ 降低α:减少对长推理的过度关注 - ✅ 提高β:增强对函数调用准确性的重视 - ✅ 平衡优化:推理和执行都重要


3. 超参数α的选择

3.1 消融实验(Figure 4b)

实验设置: - 模型:Qwen2.5-Coder-7B / Llama-3.2-3B - 评估指标:Live Accuracy(真实场景准确率) - α范围:0.1 - 0.9

Qwen2.5-Coder-7B结果

α=0.1: 67.5%  # β太大,过度关注result,推理不足
α=0.2: 69.0%
α=0.3: 70.5%
α=0.4: 71.2%
α=0.5: 71.8%  # ← 最优点
α=0.6: 71.7%
α=0.7: 71.5%
α=0.8: 70.0%
α=0.9: 68.5%  # 接近传统SFT,result权重过低

Llama-3.2-3B结果

α=0.1: 65.0%
α=0.3: 68.0%
α=0.5: 71.5%  # ← 最优点
α=0.7: 71.0%
α=0.9: 67.0%

统计分析

传统SFT隐含α值:0.92
FunReason最优α值:0.5-0.7

改进幅度:
Qwen: 68.5% → 71.8% (+3.3%)
Llama: 67.0% → 71.5% (+4.5%)

3.2 α选择的理论指导

推荐公式

def recommend_alpha(N_think_mean, N_result_mean):
    """
    基于token统计推荐α值
    """
    ratio = N_think_mean / N_result_mean  # xLAM约为11:1

    if ratio > 10:
        # 极度不平衡,需要大幅调整
        alpha = 0.5
    elif ratio > 5:
        # 中度不平衡
        alpha = 0.6
    else:
        # 轻度不平衡
        alpha = 0.7

    return alpha

# xLAM case
alpha = recommend_alpha(350.74, 31.07)  # 返回 0.5

实验验证: - ✅ α=0.5在两个模型上都接近最优 - ✅ 与理论推荐一致 - ✅ 具有跨模型泛化性


4. SRML实现细节

4.1 代码实现(简化版)

import torch
import torch.nn as nn

class MultiscaleLoss(nn.Module):
    def __init__(self, alpha=0.5):
        super().__init__()
        self.alpha = alpha
        self.beta = 1 - alpha
        self.ce_loss = nn.CrossEntropyLoss(reduction='none')

    def forward(self, logits, labels, think_mask):
        """
        Args:
            logits: [batch, seq_len, vocab_size]
            labels: [batch, seq_len]
            think_mask: [batch, seq_len] # 1表示think部分,0表示result
        """
        # 计算逐token的loss
        token_loss = self.ce_loss(
            logits.view(-1, logits.size(-1)), 
            labels.view(-1)
        ).view(labels.size())

        # 分离think和result的loss
        think_loss = (token_loss * think_mask).sum() / think_mask.sum()
        result_loss = (token_loss * (1-think_mask)).sum() / (1-think_mask).sum()

        # 多尺度加权
        total_loss = self.alpha * think_loss + self.beta * result_loss

        return total_loss, {
            'think_loss': think_loss.item(),
            'result_loss': result_loss.item(),
            'total_loss': total_loss.item()
        }

# 使用示例
loss_fn = MultiscaleLoss(alpha=0.5)
loss, metrics = loss_fn(logits, labels, think_mask)

4.2 如何生成think_mask?

方法1:基于特殊token

def create_mask_from_tokens(input_ids, tokenizer):
    """
    假设格式: <think>...</think>\n[func()]
    """
    think_start_id = tokenizer.convert_tokens_to_ids('<think>')
    think_end_id = tokenizer.convert_tokens_to_ids('</think>')

    mask = torch.zeros_like(input_ids)
    in_think = False

    for i, token_id in enumerate(input_ids):
        if token_id == think_start_id:
            in_think = True
        elif token_id == think_end_id:
            in_think = False

        if in_think:
            mask[i] = 1

    return mask

方法2:基于位置索引

def create_mask_from_positions(seq_len, think_end_pos):
    """
    think_end_pos: CoT结束位置
    """
    mask = torch.zeros(seq_len)
    mask[:think_end_pos] = 1  # 前think_end_pos个token为think
    return mask

4.3 训练循环

def train_with_srml(model, dataloader, alpha=0.5, epochs=3):
    loss_fn = MultiscaleLoss(alpha=alpha)
    optimizer = torch.optim.AdamW(model.parameters(), lr=4e-5)

    for epoch in range(epochs):
        for batch in dataloader:
            # 前向传播
            logits = model(batch['input_ids'])

            # 计算SRML loss
            loss, metrics = loss_fn(
                logits, 
                batch['labels'], 
                batch['think_mask']
            )

            # 反向传播
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()

            # 记录metrics
            if step % 100 == 0:
                print(f"Epoch {epoch}, Step {step}")
                print(f"  Think Loss: {metrics['think_loss']:.4f}")
                print(f"  Result Loss: {metrics['result_loss']:.4f}")
                print(f"  Total Loss: {metrics['total_loss']:.4f}")

5. Self-Refinement策略

5.1 Self-Refinement循环

完整流程

┌──────────────────────────────────────────────────┐
              Self-Refinement Loop                
└──────────────────────────────────────────────────┘

Round 0: 初始训练
  xLAM原始数据  [用QwQ生成CoT]  xLAM-cot
  
  [FCDR清洗]  FunReason-FCDR-60K
  
  [SRML训练]  Model_v1

Round 1: 自我精炼
  xLAM原始数据  [Model_v1生成CoT]  Model_v1-cot
  
  [FCDR清洗]  FunReason-FCDR-v2
  
  [SRML训练]  Model_v2

Round 2+: 持续迭代
  ...可以继续但论文只做了1轮

5.2 为什么自我精炼有效?

理论基础

1. 初始模型已经学会基本的function calling
2. 让模型生成  暴露模型的弱点
3. FCDR识别并修正  针对性改进
4. 再次训练  强化正确行为

类比: 学生做题  老师批改  学生订正  再次练习

实验证据

Qwen2.5-Coder-7B:
- 初始训练(FCDR-SFT):  81.94%
- +SRML:                83.66% (+1.72%)
- +Self-Refinement:     (论文未单独测试但整体pipeline有效)

5.3 实现代码

def self_refinement_loop(base_model, xlam_data, num_rounds=2):
    """
    自我精炼训练循环
    """
    current_model = base_model

    for round_idx in range(num_rounds):
        print(f"\n=== Self-Refinement Round {round_idx} ===")

        # Step 1: 用当前模型生成CoT
        cot_data = []
        for sample in xlam_data:
            cot = current_model.generate_cot(
                query=sample['query'],
                tools=sample['tools']
            )
            cot_data.append({
                'query': sample['query'],
                'tools': sample['tools'],
                'cot': cot,
                'reference': sample['answer']
            })

        # Step 2: FCDR清洗
        fcdr_pipeline = FCDRPipeline()
        refined_data = fcdr_pipeline.process(cot_data)
        print(f"FCDR保留率: {len(refined_data)/len(cot_data)*100:.1f}%")

        # Step 3: SRML训练
        current_model = train_with_srml(
            model=current_model,
            data=refined_data,
            alpha=0.5,
            epochs=3
        )

        # Step 4: 评估
        score = evaluate_on_bfcl(current_model)
        print(f"Round {round_idx} BFCL Score: {score:.2f}%")

    return current_model

6. FCDR + SRML 协同效应

6.1 单独使用 vs 联合使用

对比实验(推测,论文未明确单独测试):

配置 Live Acc Overall 说明
Baseline (纯SFT) 77.60% 81.94% xLAM-cot + 传统loss
+FCDR only ~78.5% ~82.5% 数据清洗提升
+SRML only ~79.0% ~82.8% 损失改进提升
+FCDR+SRML 80.31% 83.66% 协同效应

协同原因

FCDR: 提供高质量数据  减少噪声
SRML: 平衡优化目标  提高效率

1 + 1 > 2 的效果

6.2 为什么不是简单叠加?

数据质量对SRML的影响

# 劣质数据 + SRML
数据: CoT逻辑错误但格式正确
SRML: 仍会学习错误的推理模式
结果: 损失函数平衡了但学习了错误知识

# 优质数据 + SRML  
数据: FCDR清洗过CoT和Result都正确
SRML: 在正确知识上做平衡优化
结果: 事半功倍

损失函数对数据利用的影响

# 优质数据 + 传统SFT
数据: FCDR清洗过的高质量数据
传统SFT: 过度关注think部分
结果: 浪费了result部分的高质量标注

# 优质数据 + SRML
数据: FCDR清洗过的高质量数据
SRML: 充分利用think和result
结果: 数据价值最大化

7. 对比其他方法

7.1 vs 传统SFT

维度 传统SFT FunReason
损失函数 统一CE loss 多尺度加权
数据清洗 无/简单过滤 5阶段自动验证
训练策略 单轮训练 Self-Refinement
灾难性遗忘 严重(-46%) 轻微(-3%)
Live Acc 77.60% 80.31%

7.2 vs RL-based方法

RL方法(如ToolRL)

# 强化学习范式
reward = execution_success + format_correctness
policy_gradient = log P(action) × reward

优点: - ✅ 直接优化成功率 - ✅ 可以处理不可微的奖励

缺点: - ❌ 需要设计reward function - ❌ 训练不稳定(高方差) - ❌ 样本效率低(需要大量rollout)

FunReason (SFT-based)

# 监督学习范式
loss = α × L_think + β × L_result

优点: - ✅ 训练稳定 - ✅ 样本效率高 - ✅ 实现简单

缺点: - ❌ 依赖高质量标注(通过FCDR解决) - ❌ 难以优化不可微指标(通过精心设计loss缓解)

性能对比

Tool-N1-7B (xLAM-RL): 82.01%
FunReason-7B:         83.66% (+1.65%)

结论: SFT配合好的数据和loss可以超越RL

7.3 vs Loss Masking方法

Loss Masking(如Instruction Tuning常用):

# 只计算response部分的loss
mask = create_response_mask(input_ids)
loss = CrossEntropyLoss(logits[mask], labels[mask])

问题: - 完全忽略instruction/CoT部分 - 可能导致推理能力下降

SRML vs Loss Masking

Loss Masking:  [思考过程不算loss] + [结果部分算loss]
SRML:          [思考过程loss×0.5] + [结果部分loss×0.5]

区别: SRML不完全丢弃think的学习,只是降低权重

8. 实践建议

8.1 如何应用到自己的任务?

Step 1: 数据统计

# 统计你的数据集
def analyze_dataset(dataset):
    think_tokens = []
    result_tokens = []

    for sample in dataset:
        think_len = count_tokens(sample['reasoning'])
        result_len = count_tokens(sample['function_call'])

        think_tokens.append(think_len)
        result_tokens.append(result_len)

    print(f"Think平均: {np.mean(think_tokens):.1f}")
    print(f"Result平均: {np.mean(result_tokens):.1f}")
    print(f"比例: {np.mean(think_tokens)/np.mean(result_tokens):.1f}:1")

Step 2: 选择α

ratio = mean_think_tokens / mean_result_tokens

if ratio > 10:
    alpha = 0.5
elif ratio > 5:
    alpha = 0.6
elif ratio > 2:
    alpha = 0.7
else:
    alpha = 0.8  # 轻微不平衡,接近传统SFT

Step 3: 实现FCDR(简化版)

# 如果没有QwQ-32B,可以用其他模型
def simple_fcdr(data, model="gpt-4o-mini"):
    """
    简化的3阶段清洗:
    1. 格式检查(规则)
    2. 参数检查(规则)
    3. 逻辑检查(LLM)
    """
    cleaned = []

    for sample in data:
        # Stage 1: 格式检查(纯规则)
        if not check_format(sample['function_call']):
            sample = fix_format(sample)

        # Stage 2: 参数检查(规则+schema)
        if not check_parameters(sample, schema):
            sample = fix_parameters(sample, schema)

        # Stage 3: 逻辑检查(用LLM)
        if not llm_verify_logic(sample, model):
            continue  # 逻辑不通就丢弃

        cleaned.append(sample)

    return cleaned

Step 4: 训练

# 完整训练脚本
model = load_base_model("Qwen2.5-7B-Instruct")

# FCDR清洗
cleaned_data = simple_fcdr(raw_data)

# SRML训练
alpha = 0.5  # 根据Step 1结果调整
trained_model = train_with_srml(
    model=model,
    data=cleaned_data,
    alpha=alpha,
    epochs=3,
    lr=4e-5
)

# 评估
score = evaluate(trained_model, test_set)

8.2 资源受限时的简化方案

方案A: 只用SRML(不用FCDR)

# 适用场景: 数据质量已经较好
# 节省: FCDR的计算成本

alpha = 0.5  # 根据token统计确定
model = train_with_srml(model, data, alpha)

方案B: 只用FCDR(不用SRML)

# 适用场景: token比例还算平衡(ratio<5)
# 节省: 不需要修改训练代码

cleaned_data = fcdr_pipeline.process(data)
model = train_standard_sft(model, cleaned_data)

方案C: 两者简化版

# FCDR简化: 只用Stage 4+5(格式和参数修正)
# SRML简化: 固定α=0.5,不做消融

quick_cleaned = format_and_param_check(data)
model = train_with_srml(model, quick_cleaned, alpha=0.5)

9. 未来改进方向

9.1 动态权重调整

当前: α固定为0.5 改进: 根据训练阶段动态调整

def dynamic_alpha(epoch, total_epochs):
    """
    早期: 更关注think(学习推理)
    后期: 更关注result(精确执行)
    """
    # 线性衰减
    alpha = 0.7 - 0.2 * (epoch / total_epochs)
    # epoch 0: α=0.7, β=0.3
    # epoch final: α=0.5, β=0.5
    return alpha

9.2 样本难度加权

当前: 所有样本权重相同 改进: 根据样本复杂度调整权重

def difficulty_weighted_loss(loss, difficulty):
    """
    difficulty: 样本难度(0-1)
    简单样本: 降低权重
    困难样本: 提高权重
    """
    weight = 0.5 + 0.5 * difficulty
    return loss * weight

9.3 多任务联合训练

当前: 只训练function calling 改进: 同时训练code generation

L_total = α × L_think 
        + β × L_result 
        + γ × L_code  # 额外的代码生成任务

10. 总结

核心takeaway

组件 核心思想 关键创新
FCDR 数据是根本 5阶段自动清洗,LLM辅助LLM
SRML 损失要平衡 打破token数量束缚,手动设权重
Self-Ref 自我提升 模型生成→清洗→再训练

可复用的方法论

  1. 数据驱动设计: 先统计→发现问题→针对性解决
  2. 分而治之: 复杂问题拆解为多阶段处理
  3. 质量优于数量: 60K高质量>100K低质量
  4. 自动化优先: 用LLM代替人工标注
  5. 理论与实验结合: 数学推导+消融实验

适用场景

适合用FCDR+SRML的场景: - 有推理过程+执行结果的任务(如function calling, code generation) - Token比例失衡(ratio>5) - 数据质量参差不齐

不适合的场景: - 纯生成任务(没有明确的"推理vs执行"划分) - Token比例本来就平衡 - 数据已经非常高质量


最后: 这两个技术的精髓在于"数据质量"和"损失设计"的协同,单用一个效果有限,组合使用才能发挥1+1>2的效果。

Thanks for Reading

If this article was helpful to you, feel free to connect with me!