# 视觉强化学习（VLM RL）

Unsloth 现在支持带有视觉/多模态强化学习的 [Qwen3-VL](https://unsloth.ai/docs/zh/mo-xing/tutorials/qwen3-how-to-run-and-fine-tune/qwen3-vl-how-to-run-and-fine-tune), [Gemma 3](https://unsloth.ai/docs/zh/mo-xing/tutorials/gemma-3-how-to-run-and-fine-tune) 以及更多。由于 Unsloth 独特的 [权重共享](https://unsloth.ai/docs/zh/kai-shi-shi-yong/reinforcement-learning-rl-guide/..#what-unsloth-offers-for-rl) 和自定义内核，Unsloth 使得 VLM 强化学习 **快 1.5–2×，** 使用 **90% 更少的显存**，并且启用 **比 FA2 配置长 15× 的上下文** 长度，而不会损失准确性。此更新还引入了 Qwen 的 [GSPO](#gspo-rl) 算法。

Unsloth 可以在免费 Colab T4 GPU 上使用 GSPO/GRPO 训练 Qwen3-VL-8B。其他 VLM 也可行，但可能需要更大的 GPU。Gemma 需要比 T4 更新的 GPU，因为 vLLM [限制为 Bfloat16](https://unsloth.ai/docs/zh/mo-xing/tutorials/gemma-3-how-to-run-and-fine-tune#unsloth-fine-tuning-fixes)，因此我们建议在 Colab 上使用 NVIDIA L4。我们的笔记本解决涉及图像和图示的数值数学问题：

* **Qwen-3 VL-8B** （vLLM 推理）**:** [Colab](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen3_VL_\(8B\)-Vision-GRPO.ipynb)
* **Qwen-2.5 VL-7B** （vLLM 推理）**:** [Colab](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen2_5_7B_VL_GRPO.ipynb) •[ Kaggle](https://www.kaggle.com/notebooks/welcome?src=https://github.com/unslothai/notebooks/blob/main/nb/Kaggle-Qwen2_5_7B_VL_GRPO.ipynb\&accelerator=nvidiaTeslaT4)
* **Gemma-3-4B** （Unsloth 推理）： [Colab](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Gemma3_\(4B\)-Vision-GRPO.ipynb)

我们还将 vLLM VLM 原生集成到 Unsloth 中，因此使用 vLLM 推理时你只需在初始化模型时启用 `fast_inference=True` 标志即可。特别感谢 [Sinoué GAD](https://github.com/unslothai/unsloth/pull/2752) 提供了 [第一个笔记本](https://github.com/GAD-cell/vlm-grpo/blob/main/examples/VLM_GRPO_basic_example.ipynb) 使得集成 VLM 强化学习更容易！

此 VLM 支持还整合了我们最新的更新，以实现更节省内存且更快的强化学习，包括我们的 [待机功能](https://unsloth.ai/docs/zh/kai-shi-shi-yong/memory-efficient-rl#unsloth-standby)，它在限制速度退化方面与其他实现相比具有独特优势。

{% hint style="info" %}
你只能对 `fast_inference` 为 vLLM 支持的 VLM 使用。有些模型，例如 Llama 3.2 Vision，因此只能在不使用 vLLM 的情况下运行，但它们仍然可以在 Unsloth 中工作。
{% endhint %}

```python
os.environ['UNSLOTH_VLLM_STANDBY'] = '1' # 启用与 vLLM 一起的节省内存的 GRPO
model, tokenizer = FastVisionModel.from_pretrained(
    model_name = "Qwen/Qwen2.5-VL-7B-Instruct",
    max_seq_length = 16384, # 必须这么大以将图像放入上下文
    load_in_4bit = True, # LoRA 16 位时为 False
    fast_inference = True, # 启用 vLLM 快速推理
    gpu_memory_utilization = 0.8, # 内存不足时降低该值
)
```

同样重要的是要注意，vLLM 不支持视觉/编码器层的 LoRA，因此在加载 LoRA 适配器时将 `finetune_vision_layers = False` 设置为 False。\
但是如果你通过 transformers/Unsloth 使用推理，你也可以训练视觉层。

```python
# 将 LoRA 适配器添加到模型以进行参数高效的微调
model = FastVisionModel.get_peft_model(
    model,

    finetune_vision_layers     = False,# fast_inference 目前还不支持 finetune_vision_layers :(
    finetune_language_layers   = True, # 如果不微调语言层则为 False
    finetune_attention_modules = True, # 如果不微调注意力层则为 False
    finetune_mlp_modules       = True, # 如果不微调 MLP 层则为 False

    r = lora_rank, # 选择任意大于 0 的数！建议 8、16、32、64、128
    lora_alpha = lora_rank*2, # *2 可加速训练
    use_gradient_checkpointing = "unsloth", # 降低内存使用
    random_state = 3407,
)
```

## :butterfly:Qwen 2.5 VL 视觉强化学习的问题与怪异行为

在 Qwen 2.5 VL 的强化学习过程中，你可能会看到以下推理输出：

{% code overflow="wrap" %}

```
 addCriterion
 <tool_call>\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n\n addCriterion\n\n 自动生成\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n addCriterion\n\n\n addCriterion\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
```

{% endcode %}

这被 [报告](https://github.com/QwenLM/Qwen2.5-VL/issues/759) 在 Qwen2.5-VL-7B-Instruct 输出意外结果 “addCriterion” 中也有提及。事实上我们也看到了这个！我们尝试了非 Unsloth、bfloat16 和 float16 的机器以及其他方法，但它似乎仍然存在。例如第 165 项 即 `train_dataset[165]` 来自 [AI4Math/MathVista](https://huggingface.co/datasets/AI4Math/MathVista) 数据集如下：

{% code overflow="wrap" %}

```
图为赛车手行驶路径的俯视图，在他的赛车与赛道护栏碰撞时。碰撞前，他以速度 v_i=70 m/s 沿一条与护栏成 30° 的直线行驶。碰撞后，他以速度 v_f=50 m/s 沿一条与护栏成 10° 的直线行驶。他的质量 m 为 80 kg。碰撞持续 14 ms。在碰撞期间，作用在车手身上的平均力的大小是多少？
```

{% endcode %}

<figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2Fgit-blob-61a659529171fcc10ed6398a15912b21d6b1a076%2FUntitled.png?alt=media" alt="" width="128"><figcaption></figcaption></figure>

然后我们得到上面的乱码输出。可以添加一个奖励函数以惩罚出现 addCriterion 的行为，或惩罚乱码输出。然而，另一种方法是让模型训练更长时间。例如大约在 60 步之后我们才看到模型通过强化学习实际学到东西：

<figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2Fgit-blob-5f34f66f0ac6508fd28343b16592c59b889ec5ca%2Fimage.webp?alt=media" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
强制 `<|assistant|>` 在生成期间将减少这些乱码结果的出现，这符合预期因为这是一个 Instruct 模型，不过最好还是如下一节所述添加奖励函数来惩罚不良生成。
{% endhint %}

## :medal:用于减少乱码的奖励函数

为了惩罚 `addCriterion` 和乱码输出，我们修改了奖励函数以惩罚过多的 `addCriterion` 和换行符。

```python
def formatting_reward_func(completions,**kwargs):
    import re
    thinking_pattern = f'{REASONING_START}(.*?){REASONING_END}'
    answer_pattern = f'{SOLUTION_START}(.*?){SOLUTION_END}'

    scores = []
    for completion in completions:
        score = 0
        thinking_matches = re.findall(thinking_pattern, completion, re.DOTALL)
        answer_matches = re.findall(answer_pattern, completion, re.DOTALL)
        if len(thinking_matches) == 1:
            score += 1.0
        if len(answer_matches) == 1:
            score += 1.0

        # 修复 addCriterion 问题
        # 参见 https://docs.unsloth.ai/new/vision-reinforcement-learning-vlm-rl#qwen-2.5-vl-vision-rl-issues-and-quirks
        # 对过多的 addCriterion 和换行符进行惩罚
        if len(completion) != 0:
            removal = completion.replace("addCriterion", "").replace("\n", "")
            if (len(completion)-len(removal))/len(completion) >= 0.5:
                score -= 2.0

        scores.append(score)
    return scores
```

## :checkered\_flag:GSPO 强化学习

此更新另外添加了 GSPO（[组序列策略优化](https://arxiv.org/abs/2507.18071)）这是由阿里巴巴 Qwen 团队提出的 GRPO 变体。他们注意到 GRPO 隐式地为每个标记导致了重要性权重，尽管显式的优势并不会随每个标记缩放或改变。

这导致了 GSPO 的创建，GSPO 现在将重要性分配给序列似然而不是各个标记的个体似然。这两种算法之间的差异可以在下面看到，均来自 Qwen 和阿里巴巴的 GSPO 论文：

<figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2Fgit-blob-45d743dd5dcd590626777ce09cfab61808aa8c24%2Fimage.png?alt=media" alt="" width="563"><figcaption><p>GRPO 算法，来源： <a href="https://arxiv.org/abs/2507.18071">Qwen</a></p></figcaption></figure>

<figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2Fgit-blob-ee755850cbe17482ce240dde227d55c62e9a3e64%2Fimage.png?alt=media" alt="" width="563"><figcaption><p>GSPO 算法，来源： <a href="https://arxiv.org/abs/2507.18071">Qwen</a></p></figcaption></figure>

在公式 1 中，可以看出优势在该张量求和之前将每一行缩放到标记的对数概率上。本质上，尽管该缩放是赋予整个序列而非每个单独标记，但每个标记都得到了相同的缩放。下面可以看到一个简单示意图：

<figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2Fgit-blob-b3c944808a15dde0a7ff45782f9f074993304bf1%2FCopy%20of%20GSPO%20diagram%20(1).jpg?alt=media" alt="" width="286"><figcaption><p>按行用优势缩放的 GRPO 对数概率比</p></figcaption></figure>

公式 2 显示了每个序列的对数概率比在计算完成后被求和并取指数，只有得到的序列比随后被按行乘以优势。

<figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2Fgit-blob-62fc5b50921e79cce155d2794201c9b96faf941e%2FGSPO%20diagram%20(1).jpg?alt=media" alt="" width="313"><figcaption><p>按行用优势缩放的 GSPO 序列比</p></figcaption></figure>

启用 GSPO 很简单，你只需在 GRPO 配置中设置 `importance_sampling_level = "sequence"` 标志即可。

```python
training_args = GRPOConfig(
    output_dir = "vlm-grpo-unsloth",
    per_device_train_batch_size = 8,
    gradient_accumulation_steps = 4,
    learning_rate = 5e-6,
    adam_beta1 = 0.9,
    adam_beta2 = 0.99,
    weight_decay = 0.1,
    warmup_ratio = 0.1,
    lr_scheduler_type = "cosine",
    optim = "adamw_8bit",
    # beta = 0.00,
    epsilon = 3e-4,
    epsilon_high = 4e-4,
    num_generations = 8,    
    max_prompt_length = 1024,
    max_completion_length = 1024,
    log_completions = False,
    max_grad_norm = 0.1,
    temperature = 0.9,
    # report_to = "none", # 如果你想将日志记录到 Weights & Biases，请设置为 "wandb"
    num_train_epochs = 2, # 用于快速测试运行，完整训练请增加
    report_to = "none"
    
    # GSPO 如下：
    importance_sampling_level = "sequence",
    
    # Dr GRPO / GAPO 等
    loss_type = "dr_grpo",
)
```

总体而言，Unsloth 现在通过 VLM vLLM 快速推理既实现了 90% 的内存使用减少，又在 GRPO 和 GSPO 下实现了 1.5-2x 的速度提升！

如果你想了解更多关于强化学习的内容，请查看我们的 RL 指南：

[](https://unsloth.ai/docs/zh/kai-shi-shi-yong/reinforcement-learning-rl-guide "mention")

***作者：** 特别感谢* [*Keith*](https://www.linkedin.com/in/keith-truongcao-7bb84a23b/) *和* [*Datta*](https://www.linkedin.com/in/datta0/) *为本文做出的贡献！*


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://unsloth.ai/docs/zh/kai-shi-shi-yong/reinforcement-learning-rl-guide/vision-reinforcement-learning-vlm-rl.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
