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

上周我开始了一项构建自定义编码模型的探索。我受到整个氛围编码的启发,但专注于个性化和微调。我在上一篇博文中写了更多关于这项探索的内容。
本周,我将发布第一个模型,并分享这段旅程的起起伏伏。
总结:该模型在硬推理方面表现更好(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 等代码竞赛中的竞争者。这对我们的实验有两个重要影响:
- 这些挑战是无污染且不断更新的,这与 HumanEval 和 MBPP 等替代方案不同。
- 我们的训练集和评估集非常相似,但可能不适合实际使用。我将在下面探讨这一点,并在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 除了编码之外的能力因其微调而有所下降,但它在通用基准上仍然具有合理的性能。为了评估,我通过在提示前加上 `
氛围测试
让我们跳出分数,在 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 在思考时需要额外一分钟才能响应。
任务 2:独角兽!
从定性上看,OlympicCode-7B 在处理需要逻辑推理和多步解决方案的问题方面表现最佳,正如我们在 LiveCodeBench 分数中看到的那样。然而,它在涉及布局的任务上仍然表现不佳。让我们探索 GemmaCoder 的视觉能力。我是 Adam K Dean GPT 独角兽项目的粉丝,他挑战 LLM 画独角兽。我认为这将是对 GemmaCoder 的一个有趣的测试。
首先,这是来自 Gemma3-12B-IT 的一个例子。
那显然不是独角兽,但如果我们眯着眼睛看,可以看到一些相关特征。
- 它有一个身体
- 身体下方有四条腿
- 它有一条尾巴
- 头部上方有一个类似角的东西
当然,我们在这里有点牵强附会,但如果你看看 GPT Unicorn,你会发现这并不是最糟糕的尝试。
现在,让我们看看 GemmaCoder-12B。它在绘制独角兽方面做得稍好一些,改进如下:
- 独角兽是白色的
- 它有独立的腿、头和身体
我还用 GemmaCoder-12B 尝试了许多竞技编程问题。我无法识别 Gemma-12B-Codeforces 相对于 Gemma 或 OlympicCoder 的缺点。我预计我可能没有像 LiveCodeBench 评估那样将其推理能力发挥到极致。
开发设置
为了在我的 IDE 中使用本地模型,我使用了以下设置,这在我这篇博文中也有描述:
- VSCode 用于开发
- LMStudio 用于本地推理
- Continue.dev VSCode 扩展用于聊天、代理和补全。
我还定制了聊天模板,使其包含或不包含 `
[
{
"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 中学习。此外,我们可以保持我们正在调整的语言模型的一些基本属性,这很棒。
话虽如此,这种模型是否是我每天都想使用的类型,目前仍悬而未决。思考对于解决难题非常有用,但在大多数情况下,我宁愿快速获得一个“足够好”的答案,而不是一分钟后才得到一个完美的答案。在下一轮探索中,我将探索实用性和延迟,看看能否获得一个能够快速准确地完成代码的模型。