自定义氛围编码探索第二部分:🚙 微调 Gemma 3 以实现代码推理

社区文章 发布于2025年4月1日

image/gif

上周我开始了一项构建自定义编码模型的探索。我受到整个氛围编码的启发,但专注于个性化和微调。我在上一篇博文中写了更多关于这项探索的内容。

本周,我将发布第一个模型,并分享这段旅程的起起伏伏。

总结:该模型在硬推理方面表现更好(LiveCodeBench 上提升了 11%),但在延迟方面有所损失。模型地址:https://huggingface.co/burtenshaw/GemmaCoder3-12B

Gemma 3 的 Open R1 任务

第一个任务是构建核心编码大型语言模型(LLM),我可以用它来处理大多数代码生成和理解任务。这个模型需要承担启动新项目和生成功能性补全的繁重工作。

最近发布的 Gemma-3 似乎是一个很好的起点。如果您想了解 Gemma 3 的更多细节,请查看这篇博文。Gemma 3 表现出强大的通用性能,并且具有 128K 的长上下文长度。这非常适合处理大型代码库和复杂的代码实体。

如果我要将其用于代码特定项目,我注意到有两个可以改进的方面。首先,与 Qwen 系列不同,它没有代码变体,只有一个通用模型,我预计其中包含大量我不需要的参数。其次,它不使用推理标记来“思考”难题。OpenAI 和 Anthropic 都发布了其核心模型的思考变体,它们在处理难题时非常方便。因此,拥有一个能够思考的模型会很有用。

提醒一下,我完成这项探索的先决条件是:可以在 Google Colab 中训练并在我的笔记本电脑上运行。因此,我将从 Gemma 3 的 120 亿参数变体开始。它可以在一块 A100 上进行训练,并且在量化为 4 位或 8 位时,可以在 32GB 内存下运行。这可能是我们希望处理的上限,因为我们无法进行太多消融实验,但这值得一试!

监督微调以思考代码

我使用监督微调(SFT)来调整预训练的 Gemma 3 模型。SFT 涉及在指令和响应对数据集上训练模型——在本例中,数据集专注于代码推理。

SFT 不是后训练工具包中最先进的算法,但它易于实现和解释。根据Deepseek R1 论文等研究,我们知道模型能够通过 SFT 在推理链上从复杂模型中学习。

我们将使用基于 Google Deepmind 的 CodeForces 挑战问题的 codeforces-cot 数据集。在 codeforces-cots 数据集上进行微调将使模型能够学习对困难编码问题进行推理。Open-R1 团队已经使用相同的策略创建了 Olympic Coder 系列模型。

评估代码和通用性能

我们需要某种方式来测试生成的代码,这就是 LiveCodeBench 的用武之地。LiveCodeBench 使用一个不断更新的代码挑战列表,旨在测试 Codeforces 等代码竞赛中的竞争者。这对我们的实验有两个重要影响:

  1. 这些挑战是无污染且不断更新的,这与 HumanEval 和 MBPP 等替代方案不同。
  2. 我们的训练集和评估集非常相似,但可能不适合实际使用。我将在下面探讨这一点,并在Olympic Coder 系列模型中进行了扩展。

GemmaCoder 在 LiveCodeBench 问题上的平均通过率为 32.9%。这比基础模型的 21.9% 性能有了显著提高,但仍低于 OlympicCoder-7B(达到了 40.7%)。我们可以将这种改进归因于两个主要原因:

  • OlympicCoder 7B 基于 QueenCoder-2.5-7B,后者针对特定代码任务进行了几轮额外的预训练和后训练。
  • OlympicCoder 7B 使用 codeforces-cot 进行了微调,上下文长度为 32k 标记,而我在这里受限于 8k 标记。

并非一无所有,OlympicCode-7B 针对 codeforces-cot 数据集中的竞技编程进行了优化,而我们正试图构建一个通用编码模型。该模型有可能保留了我们所需的一些有用技能。让我们在更通用的基准上探索 GemmaCoder 的性能,以评估其代码之外的能力。

基准测试 OlympiCoder-7B GemmaCoder-12B Gemma3-12B-it
ARC 36.2% 49.6% 54.2%
Winogrande 52.9% 63.9% 63.5%
MMLU 52.9% 61.0% 69.5%
HellaSwag 44.2% 54.0% 63.5%
LiveCodeBench 40.7% 32.9%* 21.9%

*评估时“思考”模式始终开启

我们可以看到 GemmaCoder 除了编码之外的能力因其微调而有所下降,但它在通用基准上仍然具有合理的性能。为了评估,我通过在提示前加上 `` 标记,将 LiveCodeBench 的思考模式设置为“始终开启”。Olympic Coder 系列发现这可以提高 LiveCodeBench 问题的性能。在其他基准测试中,模型无需 `` 标记即可生成响应。

氛围测试

让我们跳出分数,在 IDE 中尝试 GemmaCoder,以了解它的氛围。

任务 1:生产环境的 Base64 编码函数

我想要尝试的第一个任务是一个简单的日常编码任务,它从一个非常基本的提示开始,并且留下了很大的解释和风格空间。

Write a python function to Base64 an image in production

Gemma3-12B-IT 完成得很好,它生成了一个带有通用错误捕获的函数。这在生产环境中可能不是你想要的,但它能很好地编码你的图像路径。

import base64

def image_to_base64(image_path: str) -> str:
    """
    Encodes an image file in Base64.

    Parameters:
    image_path (str): The path to the image file.

    Returns:
    str: The Base64 encoded string of the image, or None if an error occurred.
    """
    try:
        with open(image_path, "rb") as image_file:
            encoded_bytes = base64.b64encode(image_file.read())
            return encoded_bytes.decode('utf-8')
    except Exception as e:
        print(f"An error occurred: {e}")
        return None

GemmaCoder(无思考)采用了类似的方法,从文件路径和字符串开始。它确实添加了一些针对 `FileNotFoundError` 的错误处理,这很方便,还有更多行内注释,这可能符合你的喜好,也可能不符合。

import base64

def image_to_base64(image_path):
  """
  Encodes an image file into a Base64 string.

  Args:
    image_path: The path to the image file.

  Returns:
    A Base64 encoded string representation of the image, or None if there was an error.
  """
  try:
    with open(image_path, "rb") as image_file:
      encoded_string = base64.b64encode(image_file.read()).decode('utf-8') # Decode to string for easier handling
      return encoded_string
  except FileNotFoundError:
    print(f"Error: Image file not found at {image_path}")
    return None
  except Exception as e:
    print(f"An error occurred: {e}")
    return None

如果让 GemmaCoder 思考,它会更进一步,实现一个带有更多错误处理、文件类型检查以及针对图像对象而非路径的函数脚本。就我个人而言,我不确定所有这些是否都是必要的,但也许我们在这里过度使用了思考能力。

import base64
from PIL import Image
from io import BytesIO

def image_to_base64(image_object):
  """
  Encodes a PIL Image object into a Base64 string.

  Args:
    image_object: A PIL Image object.

  Returns:
    A Base64 encoded string representation of the image, or None if there was an error.
  """
  try:
    buffered = BytesIO()
    image_object.save(buffered, format=image_object.format if image_object.format else "PNG") # Save to BytesIO object
    img_byte = buffered.getvalue()
    encoded_string = base64.b64encode(img_byte).decode('utf-8') # Decode to string for easier handling
    return encoded_string
  except Exception as e:
    print(f"An error occurred: {e}")
    return None

if __name__ == '__main__':
  # Example usage:
  try:
    # Open an image using PIL
    img = Image.open("your_image.jpg") # Replace "your_image.jpg" with an actual image path

    # Convert the PIL Image object to Base64
    base64_string = image_to_base64(img)

    if base64_string:
      print("Image encoded to Base64 successfully!")
      # You can now use the base64_string (e.g., embed in HTML, send in JSON)
      # print(base64_string[:50]) # Print the first 50 characters as an example

      # To decode the Base64 string back to an image (example):
      img_data = base64.b64decode(base64_string)
      img_from_base64 = Image.open(BytesIO(img_data))
      img_from_base64.show() # This will open the image using your default image viewer

  except FileNotFoundError:
    print("Error: Example image file not found.")
  except Exception as e:
    print(f"An error occurred during example usage: {e}")

这里思考的主要缺点是延迟,GemmaCoder 在思考时需要额外一分钟才能响应。

image/gif

任务 2:独角兽!

从定性上看,OlympicCode-7B 在处理需要逻辑推理和多步解决方案的问题方面表现最佳,正如我们在 LiveCodeBench 分数中看到的那样。然而,它在涉及布局的任务上仍然表现不佳。让我们探索 GemmaCoder 的视觉能力。我是 Adam K Dean GPT 独角兽项目的粉丝,他挑战 LLM 画独角兽。我认为这将是对 GemmaCoder 的一个有趣的测试。

首先,这是来自 Gemma3-12B-IT 的一个例子。

Gemma Unicorn

那显然不是独角兽,但如果我们眯着眼睛看,可以看到一些相关特征。

  • 它有一个身体
  • 身体下方有四条腿
  • 它有一条尾巴
  • 头部上方有一个类似角的东西

当然,我们在这里有点牵强附会,但如果你看看 GPT Unicorn,你会发现这并不是最糟糕的尝试。

GemmaCoder unicorn

现在,让我们看看 GemmaCoder-12B。它在绘制独角兽方面做得稍好一些,改进如下:

  • 独角兽是白色的
  • 它有独立的腿、头和身体

我还用 GemmaCoder-12B 尝试了许多竞技编程问题。我无法识别 Gemma-12B-Codeforces 相对于 Gemma 或 OlympicCoder 的缺点。我预计我可能没有像 LiveCodeBench 评估那样将其推理能力发挥到极致。

开发设置

为了在我的 IDE 中使用本地模型,我使用了以下设置,这在我这篇博文中也有描述:

  • VSCode 用于开发
  • LMStudio 用于本地推理
  • Continue.dev VSCode 扩展用于聊天、代理和补全。

我还定制了聊天模板,使其包含或不包含 `` 标记,并将这两种变体作为单独的模型加载到 LMStudio 中。在 Continue 中,我 then 可以定义两个模型,并根据需要更多思考的问题进行切换。

[
 {
    "apiBase": "https://:1234/v1/",
    "model": "gemmacoder3-12b-thinking",
    "title": "gemmacoder3-12b-thinking",
    "provider": "lmstudio"
 },{
    "apiBase": "https://:1234/v1/",
    "model": "gemmacoder3-12b",
    "title": "gemmacoder3-12b",
    "provider": "lmstudio"
 },
]

结论

这个小型实验表明,监督微调可以显著提高 Gemma 3 在困难的竞技编程问题上的代码推理能力,它从 Codeforces-CoT 中学习。此外,我们可以保持我们正在调整的语言模型的一些基本属性,这很棒。

话虽如此,这种模型是否是我每天都想使用的类型,目前仍悬而未决。思考对于解决难题非常有用,但在大多数情况下,我宁愿快速获得一个“足够好”的答案,而不是一分钟后才得到一个完美的答案。在下一轮探索中,我将探索实用性和延迟,看看能否获得一个能够快速准确地完成代码的模型。

社区

注册登录 以评论