SWIFT GRPO 实战:Countdown 数学任务的完整强化微调流程——从数据集定义到自定义奖励与训练观测
【免费下载链接】swiftUse PEFT or Full-parameter to CPT/SFT/DPO/GRPO 600+ LLMs (Qwen3.6, DeepSeek-V4, GLM-5.1, InternLM3, Llama4, ...) and 300+ MLLMs (Qwen3-VL, Qwen3-Omni, InternVL3.5, Ovis2.5, GLM4.5v, Gemma4, Llava, Phi4, ...) (AAAI 2025).项目地址: https://gitcode.com/GitHub_Trending/swift1/swift
本文基于 SWIFT 仓库的最佳实践文档docs/source_en/BestPractices/GRPO.md,以一个具体的数学任务“Countdown Game(数字游戏)”为主线,完整走通 GRPO 训练的全流程:如何定义数据集与预处理器、如何编写并注册自定义奖励函数、如何在三卡环境下配置 vLLM rollout 服务与训练进程、如何设计关键超参数(学习率、beta、批大小等),以及如何通过分析训练曲线和采样输出判断训练是否收敛。读完本文,你可以掌握在 SWIFT 中落地一个“数据集 + 可验证奖励 + GRPO 训练”完整实验的方法论,并能把它迁移到自己的推理任务上。
一、实验背景与整体流程
Countdown Game 的规则很直观:给定一组数字(例如 3~4 个数),要求模型仅用+ - * /四则运算和括号,把每个数字恰好使用一次,构造出一个等于目标值的算式。任务定义与训练参数参考了社区中经典的 mini-deepseek-r1 复现实验。选择这个任务的原因在于它具备 RL 训练的理想属性:
- 奖励可程序化验证:答案对不对,用正则提取 + 表达式求值即可判断,无需额外的奖励模型;
- 难度适中:Qwen2.5-3B-Instruct 规模即可完成部分任务,便于观察训练过程中的能力演进;
- 格式与正确性可分离:可以分别用“格式奖励”和“正确性奖励”两个信号驱动训练,符合 DeepSeek-R1 类方法的常见做法。
实验硬件布局为3 张 GPU:最后 1 张 GPU(CUDA_VISIBLE_DEVICES=2)专门部署 vLLM 推理服务用于 rollout 采样,前 2 张 GPU 起 2 个训练进程(NPROC_PER_NODE=2)负责梯度更新。基座模型选用Qwen2.5-3B-Instruct——文档指出,选用已经过 instruct 对齐的模型可以让格式奖励更快起效,从而把训练预算留给正确性本身。
整体流程分为四步:
- 定义数据集(预处理器 +
register_dataset); - 定义奖励函数(内置
format+ 自定义external_countdown); - 启动 rollout 推理服务(
swift rollout); - 执行 GRPO 训练(
swift rlhf --rlhf_type grpo)。
二、任务与数据集定义
数据集的核心是一个自定义预处理器。原始数据集(ModelScope 上的zouxuhong/Countdown-Tasks-3to4)每行包含nums(可用数字)和response(目标值)两个字段。预处理器要做两件事:把nums和target拼成一段自然语言query供模型采样;同时保留target和nums两列,供后续奖励函数计算使用。文档给出的完整代码如下:
class CoundownTaskPreprocessor(ResponsePreprocessor): def preprocess(self, row: Dict[str, Any]) -> Dict[str, Any]: numbers = row['nums'] target = row.pop('response', None) query = f""" Using the numbers {numbers}, create an equation that equals {target}. You can use basic arithmetic operations (+, -, *, /) and each number can only be used once. Show your work in <think> </think> tags. And return the final equation and answer in <answer> </answer> tags, for example <answer> (1 + 2) / 3 * 4 = 4 </answer>. """ row.update({'target': target, 'query': query}) return super().preprocess(row) register_dataset( DatasetMeta( ms_dataset_id='zouxuhong/Countdown-Tasks-3to4', subsets=['default'], preprocess_func=CoundownTaskPreprocessor(), tags=['math']))注:类名
CoundownTaskPreprocessor中的 “Coundown” 是原文档中的拼写,照此保留即可正常运行。
ResponsePreprocessor 在源码中的行为
从源码结构看(ResponsePreprocessor),ResponsePreprocessor是 SWIFT 数据集预处理器体系中的兼容层,它做如下事情:
- 将
query/prompt/input/instruction/question/problem等键统一映射为query,将response/answer/output/solution等键统一映射为response; - 在
preprocess中pop掉query、response、system与history,拼装成messages字段写回该行。
也就是说,子类在调用super().preprocess(row)之前放进row里的额外列(本例中的target和nums)不会被删除,会原样保留在数据集的每一行中。这正是 GRPO 奖励函数“数据集列透传”机制的入口:训练时这些列会作为关键字参数直接传给奖励函数的__call__。预处理器通过 register_dataset 注册后,训练命令里直接写数据集 ID 即可加载。
三、奖励函数定义
本任务使用两个奖励函数:格式奖励(DeepSeek-R1 中提到的 format reward,SWIFT 已内置,直接写--reward_funcs format即可)和Countdown 正确性奖励(需要自定义,通过external_plugin机制提供)。
3.1 内置 format 奖励的校验逻辑
内置format奖励函数对应 orms 注册表 中的Format类,其实现是对每条 completion 做一次正则匹配(源码):
class Format(ORM): def __call__(self, completions, **kwargs) -> List[float]: """Reward function that checks if the completion has a specific format.""" pattern = r'^<think>.*?</think>\s*<answer>.*?</answer>(?![\s\S])' matches = [re.match(pattern, content, re.DOTALL | re.MULTILINE) for content in completions] return [1.0 if match else 0.0 for match in matches]即:输出必须从<think>开始,先有完整的think推理块,再以</think>结束,最后紧跟且只跟一个<answer>...</answer>块((?![\s\S])保证 answer 标签后没有其他内容),否则记 0 分。这与第二节 query 中要求的输出格式完全对应——数据集 prompt 里教给模型的格式,就是格式奖励函数所校验的格式,二者必须保持一致,否则模型会收到互相矛盾的奖励信号。
3.2 自定义 CountdownORM 的完整实现
正确性奖励函数放在 examples/train/grpo/plugin/plugin.py,完整代码如下:
class CountdownORM(ORM): def __call__(self, completions, target, nums, **kwargs) -> List[float]: """ Evaluates completions based on Mathematical correctness of the answer Args: completions (list[str]): Generated outputs target (list[str]): Expected answers nums (list[str]): Available numbers Returns: list[float]: Reward scores """ rewards = [] for completion, gt, numbers in zip(completions, target, nums): try: # Check if the format is correct match = re.search(r"<answer>(.*?)<\/answer>", completion) if match is None: rewards.append(0.0) continue # Extract the "answer" part from the completion equation = match.group(1).strip() if '=' in equation: equation = equation.split('=')[0] # Extract all numbers from the equation used_numbers = [int(n) for n in re.findall(r'\d+', equation)] # Check if all numbers are used exactly once if sorted(used_numbers) != sorted(numbers): rewards.append(0.0) continue # Define a regex pattern that only allows numbers, operators, parentheses, and whitespace allowed_pattern = r'^[\d+\-*/().\s]+$' if not re.match(allowed_pattern, equation): rewards.append(0.0) continue # Evaluate the equation with restricted globals and locals result = eval(equation, {'__builtins__': None}, {}) # Check if the equation is correct and matches the ground truth if abs(float(result) - float(gt)) < 1e-5: rewards.append(1.0) else: rewards.append(0.0) except Exception as e: # If evaluation fails, reward is 0 rewards.append(0.0) return rewards orms['external_countdown'] = CountdownORM逐行拆解其校验逻辑,可以看出一个典型的“可验证奖励”防御式写法:
- 格式检查:先用正则提取
<answer>块,提取不到直接记 0——与内置 format 奖励形成呼应,但这里是硬门槛; - 取等式左半边:模型输出形如
(78 + 9) - (58 - 44) = 73,取=前面的表达式参与验证; - 数字使用校验:用
\d+抽出等式中所有数字,sorted(used_numbers) != sorted(numbers)说明没有“每个数字恰好使用一次”,记 0。注意这里的numbers来自数据集nums列(列表形式的整数),这就是第二节中保留nums列的原因; - 字符白名单:只允许数字、
+ - * /、括号和空白,杜绝__import__之类的注入; - 受限求值:
eval(equation, {'__builtins__': None}, {})关闭内建函数,再与gt比较,容差1e-5,匹配得 1 分; - 全兜底:任何异常一律记 0 分,保证奖励计算永不抛错中断训练。
3.3 注册机制与奖励函数的输入契约
plugin.py文件头部有一段官方的三步定制指南(源码注释):
- 定义奖励类,把计算逻辑写在
__call__中; - 把类挂到
orms注册表:orms['my_reward_function'] = MyRewardFunction; - 运行时用
--external_plugins /path/to/plugin.py --reward_funcs my_reward_function启用。
其输入契约值得注意:
- 奖励函数收到的
completions、target、nums都是列表,支持一次批量计算多条 completion(与一个训练 batch 的所有 rollout 对齐); - 除
completions外的参数全部由数据集字段透传而来(作为**kwargs传入)。任务变化时,需要同时调整数据集预处理器中保留的列名和奖励函数的形参名; - 基类 ORM 是同步接口;如果奖励计算涉及网络/沙箱等 I/O(例如调用外部评分 API、执行代码沙箱),可以改用 AsyncORM 基类,框架会用
asyncio.gather并行执行。plugin.py中还提供了CodeReward(E2B 沙箱)、AsyncGenRMReward(调用swift deploy部署的生成式奖励模型)等更多形态的完整示例,可供参考。
除format和自定义函数外,内置orms注册表还包括math、accuracy(基于 math_verify 的数学正确性)、toolbench、react_format、cosine、repetition、soft_overlong等,覆盖了常见 RLHF 场景(注册表源码)。
四、GRPO 损失函数
文档先给出了 GRPO 的目标函数,理解它有助于理解后面的参数设计:
$$ {\scriptstyle \begin{aligned} \mathcal{J}{G R P O}(\theta) & =\mathbb{E}\left[q \sim P(Q),\left{o_i\right}{i=1}^G \sim \pi{\theta_{o l d}}(O \mid q)\right] \ & \frac{1}{G} \sum{i=1}^G \frac{1}{\left|o_i\right|} \sum_{t=1}^{\left|o_i\right|}\left{\min \left[\frac{\pi_\theta\left(o_{i, t} \mid q, o_{i,<t}\right)}{\pi_{\theta_{o l d}}\left(o_{i, t} \mid q, o_{i,<t}\right)} \hat{A}{i, t}, \operatorname{clip}\left(\frac{\pi\theta\left(o_{i, t} \mid q, o_{i,<t}\right)}{\pi_{\theta_{o l d}}\left(o_{i, t} \mid q, o_{i,<t}\right)}, 1-\varepsilon, 1+\varepsilon\right) \hat{A}_{i, t}\right]-\beta \mathbb{D}_{K L}\left[\pi_\theta| | \pi_{r e f}\right]\right} \end{aligned} } $$
其中 $G$ 是每条 prompt 的采样数(实验中为num_generations=8),$\hat{A}_{i,t}$ 是组内归一化后的优势估计,$\beta$ 控制 KL 散度项的权重。GRPO 的核心思想是用同一 prompt 的一组采样做组内相对比较来估计优势,省去独立的 value/critic 网络。
五、训练参数设计与完整命令
5.1 关键参数是怎么定下来的
max_completion_length:本任务较简单,设置为 1024。文档强调:更复杂的任务可以适当调大模型输出长度,但该参数越大显存占用越高、训练越慢,单步训练时间与max_completion_length基本呈线性关系。批大小:总 batch size 为
num_processes * per_device_train_batch_size * gradient_accumulation_steps = 2 * 8 * 8 = 128单卡 batch size 与显存容量强相关,应按显存上限取合适的值。
总步数:可用如下公式估算,并据此规划学习率与 warmup:
$$ \text{num_steps} = \text{epochs} \times \text{len(datasets)} \times \text{num_generations} \div \text{batch_size} $$
学习率与 $\beta$:学习率语义直观;$\beta$ 是公式中 KL 散度项的权重系数。文档结论:二者调大可以加快收敛,但可能引起训练不稳定。本实验取学习率
5e-7、beta=0.001;若训练中出现不稳定或震荡,应适当回调这两个参数。关于 GRPO 中 KL 项的作用,社区有大量讨论(例如“为什么 GRPO 要保留 KL 散度项”等中文技术文章),可结合本节实验记录理解其影响。num_iterations 1:每个 rollout 采样只做一次参数更新(on-policy)。其余参数(save/eval 间隔、wandb 上报等)未做深入探索,直接采用文档给出的配置。
5.2 完整训练命令
第一步,在独立 GPU 上启动 rollout 推理服务(vLLM 后端,默认监听 8000 端口):
CUDA_VISIBLE_DEVICES=2 \ swift rollout \ --model Qwen/Qwen2.5-3B-Instruct第二步,在其余 GPU 上启动 GRPO 训练(2 进程,DeepSpeed ZeRO-3 全参训练):
CUDA_VISIBLE_DEVICES=0,1 \ WANDB_API_KEY=your_wandb_key \ NPROC_PER_NODE=2 \ swift rlhf \ --rlhf_type grpo \ --model Qwen/Qwen2.5-3B-Instruct \ --external_plugins examples/train/grpo/plugin/plugin.py \ --reward_funcs external_countdown format \ --use_vllm true \ --vllm_mode server \ --vllm_server_host 127.0.0.1 \ --vllm_server_port 8000 \ --tuner_type full \ --torch_dtype bfloat16 \ --dataset 'zouxuhong/Countdown-Tasks-3to4#50000' \ --load_from_cache_file true \ --max_length 2048 \ --max_completion_length 1024 \ --num_train_epochs 1 \ --per_device_train_batch_size 8 \ --per_device_eval_batch_size 8 \ --learning_rate 5e-7 \ --gradient_accumulation_steps 8 \ --eval_steps 500 \ --save_steps 100 \ --save_total_limit 20 \ --logging_steps 1 \ --output_dir output/GRPO_COUNTDOWN \ --warmup_ratio 0.01 \ --dataloader_num_workers 4 \ --num_generations 8 \ --temperature 1.0 \ --system 'You are a helpful assistant. You first thinks about the reasoning process in the mind and then provides the user with the answer.' \ --deepspeed zero3 \ --log_completions true \ --report_to wandb \ --beta 0.001 \ --num_iterations 1关键参数速览(均以上述命令为准):
| 参数 | 取值 | 作用 |
|---|---|---|
--rlhf_type grpo | grpo | 启用 GRPO 算法 |
--external_plugins | examples/train/grpo/plugin/plugin.py | 加载自定义奖励函数插件 |
--reward_funcs | external_countdown format | 正确性奖励 + 内置格式奖励,同时生效 |
--use_vllm/--vllm_mode/--vllm_server_host/--vllm_server_port | true/server/127.0.0.1/8000 | rollout 走外部 vLLM 服务(对应上面的swift rollout) |
--dataset | 'zouxuhong/Countdown-Tasks-3to4#50000' | 加载数据集并采样 5 万条(#N为采样条数语法) |
--tuner_type/--deepspeed | full/zero3 | 全参训练 + ZeRO-3 切分 |
--num_generations | 8 | 每条 prompt 采样 8 个 completion,即公式中的 $G$ |
--temperature | 1.0 | rollout 采样温度 |
--max_length/--max_completion_length | 2048/1024 | prompt 与 completion 的长度上限 |
--learning_rate/--beta | 5e-7/0.001 | 见 5.1 的稳定性讨论 |
--num_iterations | 1 | 每组采样只做一次梯度更新 |
--log_completions | true | 日志中记录采样输出,便于人工检查失败案例 |
--system | 固定 system prompt | 引导模型“先思考后作答”,与 think/answer 格式奖励对齐 |
--vllm_mode server表示训练进程不内嵌推理引擎,而是通过 host/port 连接独立部署的swift rollout服务,这正是“1 张推理卡 + 2 张训练卡”布局的参数体现。
六、训练 2000 步后的实验观测
训练进行到 2000 步后,文档记录了以下观察:
- 自定义 CountdownORM 奖励持续上升:证明模型确实在学会完成任务,最终任务成功率约 50%;
- 格式奖励保持高位:稳定在 0.9~1 之间波动;
- 奖励标准差稳定在 0.2~0.3,但未完全收敛,说明模型在奖励上仍有提升空间(组内方差是 GRPO 优势估计的信息来源,方差过低意味着信号消失);
- 失败案例分析:通过检查模型输出发现,许多失败案例源于简单的算术错误,因此模型最终收敛的方向是“一步一步地解”,换用更大模型可能出现不同的收敛方向;
- completion 长度先降后升,在约 200 步触底后回升到 350 左右,暗示模型经历了推理方式的转变;
- 推理模式演化:模型最终发现“做排列 + 逐步计算”是完成此任务最有效的方式,最终采样大多遵循该模式。
不同训练步长的 completion 演化示例
Step 1(初期:试错混乱,甚至出现错误等式):
We need to create an equation using the given numbers [70, 5, 42, 30] exactly once and make it equal to 77. First, let's consider the possibility of multiplication or division to get closer to 77. Notice that 77 is close to 70 and 42. If we can use these numbers together, it might work. Multiplying 70 by 1.1 would give us approximately 77, which is very close. So, let's try to make 1.1 using the remaining numbers 5 and 30. If we take 30/5 = 6, and then 5/6 = 0.8333..., this isn't useful here, so let's look at other possibilities. Next, let's consider if there's another way to achieve 77 using addition, subtraction, or another combination of our numbers. Given the numbers provided, it seems that multiplication or division will be key. We can try subtracting 5 from 30 to get 25. Then, if we divide 42 by 25, we don't get a whole number, but we can round it to the nearest whole number, 1. Now, we need to make the equation fit 77. Let's multiply 25 by 3 and add 7 (since 77 = 25*3 + 7). However, we only have one 7 available and we need to find a way to use the remaining numbers to get 7. Let's try: 70 - 42 = 28 Then, 28 + 5 = 33 Next, 33 + 30 = 63 Finally, 77 - 63 = 14, which doesn't help. Considering different combinations, one viable way is: (70 - 42) * (30 / 5) = 28 * 6 = 168 / 2 = 84 / 2 = 42 / 2 = 21 / 3 = 7 So, (70 - 42) * (30 / 5) = 77. (70 - 42) * (30 / 5) = 77Step 200(开始意识到数字间的组合关系):
We need to use all the given numbers in a way that the resultant value of the calculation is 99. Given these restrictions, it's clear we should look into how the numbers relate and the mathematical operations that will bring them together to meet our condition. Firstly, let's evaluate the sums, differences, and multiplication/division among the numbers and their relations to see how this could lead us towards an outcome of 99. - Summing the three numbers: 96 + 70 + 73 = 239, which is far from 99. This shows addition isn't practical. - Subtracting numbers: For instance, subtracting the lowest two (i.e., 73 - 70 = 3) and combining with the highest one (96) does not work. Also, there is no other combination of subtraction that would result in the number needing to add up to 99. - Multiplying or dividing the numbers alone wouldn't reach any near solution since none of the products or division results of the numbers themselves is very close to 99. We might think more carefully about the arrangement of them using a combination of the operations provided. Therefore, considering the possible arrangement, a combination like taking (73 - 70 + 96) may produce an effective solution as this will sum the values in such a manner that it aligns with the goal of 99. So we conclude that the operation: 73 - 70 + 96 gives the right sequence to equal 99. 73 - 70 + 96 = 99Step 500(开始按候选算式逐一排除):
Let's begin by exploring various possible combinations of the given numbers using the arithmetic operators we have access to. - If I add the numbers, I get: 56 + 73 + 51 = 180 which is not equal to our target number (68) - If I subtract one number from another I will still exceed our target value as well since the maximum difference is: |(difference between max-min):| | (73 - 51) or | (73 - 56), etc.| - Let's multiply the numbers individually and see if the division operation can bring us closer to the goal: - (73 * 51) - 56 = 3723 - 56 = 3667 which isn't the goal either. - The only operator left is addition and subtraction; I'll try the combination: ((73 - 56) + 51) gives me (17 + 51) = 68. Thus, our solution fits the provided problem constraints and meets the required outcome. (73 - 56) + 51 = 68Step 1000(结构化的枚举尝试):
We need to form an expression using the given numbers (50, 86, and 49) such that it equals 85. Let's try different combinations: (86 - 50) + 49 = 76 + 49 = 125 (too big) (86 - 49) + 50 = 37 + 50 = 87 (too big) (86 - 50) + 49 = 37 + 49 = 86 (too big) (86 + 50) - 49 = 136 - 49 = 87 (too big) (86 - 50) + (49 - x)? Not working! (50 + 86) - 49: this is 136 which isn't our aim. 50 + (86 - 49) = 50 + 37 = 87 which wasn't correct. 50 - 86 + 49 = -16 + 49: This doesn't match up as well. 86 - (50 + 49) = 86 - 99 which doesn't work too. We try one more: (86 + 49) - 50 = 135 - 50 = 85! That's it! (86 + 49) - 50 = 85Step 2000(最终形态:清晰的“排列 + 逐步计算”推理):
We need to form an expression using the numbers 58, 9, 78, and 44 which evaluates to 73. Let's try different combinations: (78 - 58) + (9 + 44) = 10 + 53 = 63 (too low) (78 - 58) + (9 - 44) = 20 - 35 = -15 (too low) (78 - 58) + (44 - 9) = 20 + 35 = 55 (too low) (78 + 58) - (9 + 44) = 136 - 53 = 83 (too high) (78 + 58) - (9 - 44) = 136 + 35 = 171 (too high) (78 + 58) - (44 + 9) = 136 + 53 = 189 (too high) (78 + 9) - (58 + 44) = 87 - 102 = -15 (too low) (78 + 9) - (58 - 44) = 87 - 14 = 73 So our solution is: (78 + 9) - (58 - 44) = 73 (78 + 9) - (58 - 44) = 73从 Step 1 到 Step 2000 的对照,可以看到奖励驱动的“策略演化”过程:早期输出充满错误的心算(如28 * 6 = 168 / 2 = 84...这类链条),而最终输出收敛为“列出若干候选组合、逐个计算并标注 too low/too high”的确定性搜索模式——这正是可验证奖励函数塑造推理策略的直观例证。仓库中还提供对应该次稳定训练过程的曲线图,见 docs/resources/grpo_countdown.png。
七、不稳定的实验记录:学习率与 beta 的敏感性
作为对照,文档还记录了一次失败配置:学习率设为1e-6、beta设为0.04时,模型在约 step 200 处出现明显震荡,格式奖励与 CountdownORM 奖励同时大幅下跌。该次实验的训练曲线保存在仓库的 docs/resources/grpo_countdown_1.png 中,可结合本节结论查看。
这条失败记录给出的经验与第四节的公式分析一致:
- 学习率翻倍(
5e-7 → 1e-6)放大了策略更新幅度; - KL 权重调高(
0.001 → 0.04)使正则项的梯度对参数扰动更敏感; - 两者叠加后,奖励曲线在 200 步左右失去稳定,之后难以恢复。
因此文档的建议是:训练中若出现不稳定或震荡,应优先回调学习率和beta,而不是调整其他参数。
八、小结:把这套流程迁移到自己的任务
本文的全部步骤可以压缩成一张“GRPO 实验检查单”:
- 任务定义:把任务编码为“自然语言 query + 可程序化验证的标准答案”;用预处理器(如继承 ResponsePreprocessor)拼出
query,并把验证所需的列(如target、nums)保留在数据集中,供奖励函数透传使用; - 奖励函数:格式类需求优先复用内置函数(
orms注册表中的format、math、accuracy等,见 swift/rewards/orm.py);任务正确性按 plugin.py 的三步法自定义:实现ORM.__call__→ 注册orms['xxx']→ 命令行--external_plugins+--reward_funcs启用; - rollout 部署:
swift rollout独立起 vLLM 服务,训练侧用--vllm_mode server连接; - 参数规划:用
num_processes × per_device_train_batch_size × gradient_accumulation_steps核算总 batch,用步数公式规划 epoch/学习率/warmup;max_completion_length直接决定显存与单步耗时; - 稳定性监控:盯住各奖励函数的均值与组内标准差、completion 长度曲线,并定期用
--log_completions抽查失败样本;一旦出现震荡,先回调学习率与beta。
如需进一步深入,仓库中还有配套的 GRPO 参数文档目录 docs/source_en/Instruction/GRPO、代码任务与多模态任务的 GRPO 最佳实践(GRPO-Code-Training.md、GRPO-Multi-Modal-Training.md),以及 examples/train/grpo 下的大量现成训练脚本,可作为本实验的直接扩展起点。
【免费下载链接】swiftUse PEFT or Full-parameter to CPT/SFT/DPO/GRPO 600+ LLMs (Qwen3.6, DeepSeek-V4, GLM-5.1, InternLM3, Llama4, ...) and 300+ MLLMs (Qwen3-VL, Qwen3-Omni, InternVL3.5, Ovis2.5, GLM4.5v, Gemma4, Llava, Phi4, ...) (AAAI 2025).项目地址: https://gitcode.com/GitHub_Trending/swift1/swift
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考