模型调优简介

更新时间:
复制 MD 格式

当您在尝试如 Prompt 工程、插件调用等优化方法后,模型表现仍然不及预期时,请使用阿里云百炼的模型调优。模型调优作为改进模型表现的核心策略,可以很好地提升模型在特定行业/业务的表现,对齐人类偏好,降低输出延迟。模型调优包含模型微调(SFT)、继续预训练(CPT)、模型偏好训练(DPO)三种模型训练方式。

重要本文档仅适用于华北2(北京)地域。

模型调优介绍

模型调优作为重要的模型效果优化方式,可以:

  • 提升模型在特定行业/业务表现
  • 降低模型输出延迟
  • 抑制模型幻觉
  • 对齐人类的价值观或偏好
  • 使用调优后的轻量级模型替代规模更大的模型

模型在调优过程中,会学习训练数据中的知识、语气、表达习惯、自我认知等业务/场景特征。也由于已经在训练过程中学习到了大量特定行业/场景的样例,训练后模型 One-Shot 或者 Zero-Shot 的 Prompt 效果会比训练前 Few-Shot 效果更好,这样可以节省大量输入 token,从而降低模型输出延迟。

模型调优流程

image

详情参见:

支持的模型

支持的模型

文本生成

说明Qwen3.7-Plus-2026-05-26 调优后部署请联系商务经理。

模型服务

模型代码

CPT全参训练(cpt)

SFT全参训练(sft)

SFT高效训练(efficient_sft)

DPO全参训练(dpo_full)

DPO高效训练(dpo_lora)

Qwen3.7-Plus-2026-05-26

qwen3.7-plus-2026-05-26

×

支持

×

×

×

Qwen3.6-Flash-2026-04-16

qwen3.6-flash-2026-04-16

×

支持

×

×

×

Qwen3.6-Plus-2026-04-02

qwen3.6-plus-2026-04-02

×

支持

×

×

×

Qwen3.5-27B

qwen3.5-27b

×

支持

支持

×

×

Qwen3.5-9B

qwen3.5-9b

×

支持

支持

×

×

Qwen3.5-Flash-2026-02-23

qwen3.5-flash-2026-02-23

×

支持

×

×

×

Qwen3.5-Plus-2026-02-15

qwen3.5-plus-2026-02-15

×

支持

×

×

×

Qwen3-32B

qwen3-32b

支持

支持

支持

支持

支持

Qwen3-30B-A3B-Instruct-2507

qwen3-30b-a3b-instruct-2507

支持

支持

支持

×

×

Qwen3-14B

qwen3-14b

×

支持

支持

支持

支持

Qwen3-8B

qwen3-8b

×

支持

支持

支持

支持

Qwen3-4B-Instruct-2507

qwen3-4b-instruct-2507

支持

支持

支持

支持

支持

Qwen3-1.7B

qwen3-1.7b

支持

支持

支持

支持

支持

Qwen3-0.6B

qwen3-0.6b

支持

支持

支持

支持

支持

Qwen2.5-72B-Instruct

qwen2.5-72b-instruct

支持

支持

支持

支持

支持

Qwen2.5-32B-Instruct

qwen2.5-32b-instruct

支持

支持

支持

支持

支持

Qwen2.5-14B-Instruct

qwen2.5-14b-instruct

支持

支持

支持

支持

支持

Qwen2.5-7B-Instruct

qwen2.5-7b-instruct

支持

支持

支持

支持

支持

千问-Plus-Character-2025-11-06

qwen-plus-character-2025-11-06

×

支持

支持

支持

支持

视觉理解(千问VL)

模型服务

模型代码

CPT全参训练(cpt)

SFT全参训练(sft)

SFT高效训练(efficient_sft)

DPO全参训练(dpo_full)

DPO高效训练(dpo_lora)

Qwen3-VL-8B-Instruct

qwen3-vl-8b-instruct

×

支持

支持

×

×

Qwen3-VL-8B-Thinking

qwen3-vl-8b-thinking

×

支持

支持

×

×

Qwen3-VL-4B-Instruct

qwen3-vl-4b-instruct

×

支持

支持

×

×

Qwen2.5-VL-72B-Instruct

qwen2.5-vl-72b-instruct

×

支持

支持

×

×

Qwen2.5-VL-32B-Instruct

qwen2.5-vl-32b-instruct

×

支持

支持

×

×

Qwen2.5-VL-7B-Instruct

qwen2.5-vl-7b-instruct

×

支持

支持

×

×

调优方法对比

特性

CPT(持续预训练)

SFT (监督微调)

DPO (直接偏好优化)

一句话总结

补知识注入领域知识

学做事学会遵循指令

做得更好对齐人类偏好

输入数据

1000万+ Token

无标签的领域文本

1000+ 条

高质量的“问-答”对

100+ 组

同一指令下的“更好-更差”回答对

核心目标

领域适应,学习专业词汇和事实

教会模型对话格式和任务执行能力

使模型输出更符合人类价值观和偏好

学习方式

自监督学习(预测下一个词

监督学习模仿标准答案

直接偏好学习增大好答案概率,降低坏答案概率

模型阶段

通常在 SFT 之前

CPT 之后,DPO 之前

通常在 SFT 之后,作为对齐的最后一步

训练模式对比

全参训练

高效训练 (LoRA,推荐)

适用场景

• 需要模型学习新能力

• 追求全局效果最优

• 优化模型特定场景下的效果

• 对训练时间和成本敏感的场景

训练时间

较长,收敛速度较慢。

较短,收敛速度快。

计费说明

计费方式

按训练的数据量计费

计费公式

模型训练费用 = (训练数据 Token 总数 + 混合训练数据 Token 总数)× 循环次数 × 训练单价(最小计费单位:1 token)

您可以查看模型调优控制台底部的预估训练费用,并单击计算详情,查看训练 Token 总数、循环次数和训练单价

训练单价

以下为预置模型的训练单价,自定义模型的训练单价与对应的预置模型单价相同。

千问VL

模型服务

模型代码

价格

Qwen3-VL-8B-Instruct

qwen3-vl-8b-instruct

¥0.012/千Token

Qwen3-VL-8B-Thinking

qwen3-vl-8b-thinking

¥0.012/千Token

Qwen3-VL-4B-Instruct

qwen3-vl-4b-instruct

¥0.006/千Token

Qwen2.5-VL-72B-Instruct

qwen2.5-vl-72b-instruct

¥0.05/千Token

Qwen2.5-VL-32B-Instruct

qwen2.5-vl-32b-instruct

¥0.02/千Token

Qwen2.5-VL-7B-Instruct

qwen2.5-vl-7b-instruct

¥0.01/千Token

千问

模型服务

模型代码

价格

Qwen3.7-Plus-2026-05-26

qwen3.7-plus-2026-05-26

¥0.35/千Token

Qwen3.6-Flash-2026-04-16

qwen3.6-flash-2026-04-16

¥0.05/千Token

Qwen3.6-Plus-2026-04-02

qwen3.6-plus-2026-04-02

¥0.3/千Token

Qwen3.5-27B

qwen3.5-27b

¥0.05/千Token

Qwen3.5-9B

qwen3.5-9b

¥0.02/千Token

Qwen3.5-Flash-2026-02-23

qwen3.5-flash-2026-02-23

¥0.05/千Token

Qwen3.5-Plus-2026-02-15

qwen3.5-plus-2026-02-15

¥0.3/千Token

Qwen3-32B

qwen3-32b

¥0.04/千Token

Qwen3-30B-A3B-Instruct-2507

qwen3-30b-a3b-instruct-2507

¥0.03/千Token

Qwen3-14B

qwen3-14b

¥0.03/千Token

Qwen3-8B

qwen3-8b

¥0.006/千Token

Qwen3-4B-Instruct-2507

qwen3-4b-instruct-2507

¥0.006/千Token

Qwen3-1.7B

qwen3-1.7b

¥0.0045/千Token

Qwen3-0.6B

qwen3-0.6b

¥0.003/千Token

Qwen2.5-72B-Instruct

qwen2.5-72b-instruct

¥0.15/千Token

Qwen2.5-32B-Instruct

qwen2.5-32b-instruct

¥0.03/千Token

Qwen2.5-14B-Instruct

qwen2.5-14b-instruct

¥0.03/千Token

Qwen2.5-7B-Instruct

qwen2.5-7b-instruct

¥0.006/千Token

千问-Plus-Character-2025-11-06

qwen-plus-character-2025-11-06

¥0.15/千Token

点击此处查看更多千问 VL 模型使用限制

使用限制
输入文件限制

图像限制

  • 图像分辨率:
    • 最小尺寸:图像的宽度和高度均须大于10像素。

    • 宽高比:原图及缩放后的图像,长边与短边的比值不得超过 200:1

      图像缩放逻辑请参见 计算图像的Tokensmart_resize 函数。

    • 像素上限:

      • 推荐将图像分辨率控制在8K(7680x4320)以内。超过此分辨率的图像可能因文件过大、网络传输耗时过长而导致API调用超时。
      • 自动缩放机制:模型可通过max_pixelsmin_pixels调整图像大小;因此,提供超高分辨率的图像并不会提升识别精度,反而会增加调用失败的风险,建议在客户端提前将图像缩放至合理大小。
  • 支持的图像格式
    • 分辨率在4K (3840x2160)以下,支持的图像格式如下:

      图像格式

      常见扩展名

      MIME Type

      BMP

      .bmp

      image/bmp

      JPEG

      .jpe, .jpeg, .jpg

      image/jpeg

      PNG

      .png

      image/png

      TIFF

      .tif, .tiff

      image/tiff

      WEBP

      .webp

      image/webp

      HEIC

      .heic

      image/heic

    • 分辨率处于4K(3840x2160)8K(7680x4320)范围,仅支持 JPEG、JPG 、PNG 格式

  • 图像大小:
    • 以公网URL传入时:Qwen3.8系列、Qwen3.7系列、Qwen3.6系列、Qwen3.5系列、Qwen3-VL系列单个图像不超过 20MB,其他模型单个图像不超过10MB
    • 以本地路径传入时:单个图像不超过10MB
    • 以 Base64 编码传入时(OpenAI 兼容接口或DashScope):Qwen3.8系列、Qwen3.7系列、Qwen3.6系列、Qwen3.5、Qwen3-VL系列编码前的原始图像文件不超过 20MB,其他模型不超过10MB;且编码后的 Data URI 字符串不超过 20MB
    • 以 Base64 编码传入时(Anthropic 兼容接口):受请求体整体不超过6MB 的限制,多张图像时需共享此额度。

    上述限制取决于所选模型,不支持通过购买高级套餐或升级模型版本提升。

    如需压缩文件体积请参见 如何将图像或视频压缩到满足要求的大小

  • 图片数量限制:多图输入时根据传入方式不同,支持的图片数量上限有所区别:

    • 以公网URL或本地路径传入时:

      • Qwen3.8-Max、Qwen3.7-Plus:最多 2048 张
      • Qwen3.7-Flash、Qwen3.6-Plus、Qwen3.6-Flash、Qwen3.5-Plus、Qwen3.5-Flash、Qwen3-VL、Qwen-VL、QVQ系列:最多 256 张
    • 以 Base64 编码传入时:最多 250 张

    Qwen-Omni系列模型请参考全模态

同时受模型图文总 Token 上限(即最大输入)的限制,所有图片的总 Token 数必须小于模型的最大输入。

视频限制

  • 以图像列表传入,图像列表的数量有如下限制:
    • qwen3.8系列、qwen3.7系列、qwen3.6系列、qwen3.5 系列:最少传入 4 张图片,最多 8000 张图片
    • qwen3-vl-plus 系列、qwen3-vl-flash 系列、qwen3-vl-235b-a22b-thinkingqwen3-vl-235b-a22b-instruct:最少传入 4 张图片,最多 2000 张图片
    • 其他Qwen3-VL开源、Qwen2.5-VL(包括商业版和开源版)和QVQ系列模型:最少传入 4 张图片,最多 512 张图片
    • 其他模型:最少传入 4 张图片,最多 80 张图片
  • 以视频文件传入时:
    • 视频大小:
      • 以公网URL传入时:

        • qwen3.8系列、qwen3.7系列、qwen3.6系列、qwen3.5 系列、Qwen3-VL系列、qwen-vl-max :不超过 2GB;
        • qwen-vl-plus 系列、其他qwen-vl-max模型、Qwen2.5-VL开源系列及QVQ系列模型:不超过 1GB;
        • 其他模型不超过 150MB
      • 以 Base64 编码传入时:编码后的字符串小于 10MB;

      • 以本地文件路径传入时:视频本身不超过 100MB。

      如需压缩文件体积请参见 如何将图像或视频压缩到满足要求的大小

    • 视频时长:
      • qwen3.8系列、qwen3.7系列、qwen3.6系列、qwen3.5 系列:2秒至2小时;
      • qwen3-vl-plus系列、qwen3-vl-flash系列、qwen3-vl-235b-a22b-thinkingqwen3-vl-235b-a22b-instruct:2 秒至 1 小时;
      • 其他Qwen3-VL开源系列、qwen-vl-max :2 秒至 20 分钟;
      • qwen-vl-plus系列、 其他qwen-vl-max模型、Qwen2.5-VL开源系列及QVQ系列模型:2 秒至 10 分钟;
      • 其他模型:2 秒至 40 秒。
    • 视频格式: MP4、AVI、MKV、MOV、FLV、WMV 等。

    • 视频尺寸:无特定限制,模型可通过max_pixelsmin_pixels自动调整视频尺寸,更大尺寸的视频文件不会有更好的理解效果。

    • 视频数量限制:最多可传入 64 个视频。

    • 音频理解:不支持对视频文件的音频进行理解。

文件传入方式
  • 公网URL:提供一个公网可访问的文件地址,支持HTTPHTTPS协议。为获得最佳稳定性和性能,可将文件上传至OSS上传文件获取临时URL,获取公网 URL。百炼服务无法访问 OSS 内网地址(endpoint 中带 -internal,如 https://<bucket>.oss-cn-hangzhou-internal.aliyuncs.com/image.jpg),传入内网地址会导致文件下载失败,返回 InvalidParameter,message 为 Failed to download multimodal content。请改用 OSS 公网域名(如 https://<bucket>.oss-cn-hangzhou.aliyuncs.com/image.jpg)或 OSS 临时签名 URL 传入文件。

    重要为确保模型能成功下载文件,提供的公网URL的响应头中必须包含 Content-Length(文件大小)和 Content-Type(媒体类型,如 image/jpeg)。任一字段缺失或者错误将会导致文件下载失败。

  • Base64编码传入:将文件转换为 Base64 编码字符串再传入。

  • 本地文件路径传入(仅限 DashScope SDK):传入本地文件的路径。

关于文件传入方式的建议,请参见 如何选择文件上传方式?

计算图像与视频的Token

图像

计算公式:图像 Token = h_bar * w_bar / token_pixels + 2

  • h_bar、w_bar:缩放后的图像长宽,模型在处理图像前会进行预处理,会将图像缩小至特定像素上限内,像素上限与max_pixelsvl_high_resolution_images参数的取值有关。

  • token_pixels:每视觉Token对应的像素值,不同模型情况不同:

    • qwen3.7系列qwen3.6系列qwen3.5系列Qwen3-VLqwen-vl-maxqwen-vl-plus每个Token对应 32x32像素
    • QVQ及其他Qwen2.5-VL模型每个Token对应28x28像素

以下代码演示了模型内部对图像的大致缩放逻辑,可用于估算一张图像的Token,实际计费请以API响应为准。

点此查看图像 token 估算示例

import math
from PIL import Image  # pip install Pillow

def smart_size(image_path, max_pixels, vl_high_resolution_images):
    """根据模型参数,计算图像缩放后的尺寸,用于估算图像 Token。"""
    image = Image.open(image_path)
    height, width = image.height, image.width

    # Qwen3.6、Qwen3.5、Qwen3-VL 等模型的缩放因子为 32;使用其他模型时,请将 factor 改为 28
    factor = 32
    h_bar = round(height / factor) * factor
    w_bar = round(width / factor) * factor

    # Token 下限:4 个 Token
    min_pixels = 4 * factor * factor

    # vl_high_resolution_images=True 时,Token 上限固定为 16384,忽略 max_pixels
    if vl_high_resolution_images:
        max_pixels = 16384 * factor * factor

    # 将总像素数约束在 [min_pixels, max_pixels] 范围内
    if h_bar * w_bar > max_pixels:
        beta = math.sqrt((height * width) / max_pixels)
        h_bar = math.floor(height / beta / factor) * factor
        w_bar = math.floor(width / beta / factor) * factor
    elif h_bar * w_bar < min_pixels:
        beta = math.sqrt(min_pixels / (height * width))
        h_bar = math.ceil(height * beta / factor) * factor
        w_bar = math.ceil(width * beta / factor) * factor

    return h_bar, w_bar

if __name__ == "__main__":
    # 注意:max_pixels 和 vl_high_resolution_images 的值需要与调用模型时传入的参数保持一致
    h_bar, w_bar = smart_size("xxx/test.jpg", max_pixels=2560 * 32 * 32, vl_high_resolution_images=False)
    print(f"缩放后的图像尺寸:高度 {h_bar},宽度 {w_bar}")

    # 每张图像额外包含 <vision_bos> 和 <vision_eos> 各 1 个 Token
    token = int(h_bar * w_bar / (32 * 32)) + 2
    print(f"图像的 Token 数:{token}")

视频

  • 视频文件:

    模型处理视频文件时,会先进行抽帧,然后计算所有视频帧的总 Token 数。由于该计算过程较为复杂,可使用以下代码,通过传入视频路径来估算视频消耗的总 Token 数:

# 使用前安装:pip install opencv-python
import math
import os
import logging
import cv2

logger = logging.getLogger(__name__)

FRAME_FACTOR = 2

# Qwen3.6、Qwen3.5、Qwen3-VL、qwen-vl-max-0813、qwen-vl-plus-0815、qwen-vl-plus-0710等模型,图像缩放因子为32
IMAGE_FACTOR = 32

#  其他模型,图像缩放因子为28
# IMAGE_FACTOR = 28

# 视频帧的最大长宽比
MAX_RATIO = 200
# 视频帧的像素下限
VIDEO_MIN_PIXELS = 4 * 32 * 32
# 视频帧的像素上限,使用Qwen3-VL-Plus模型,VIDEO_MAX_PIXELS为640 * 32 * 32,其他模型为768 * 32 * 32
VIDEO_MAX_PIXELS = 640 * 32 * 32

# 用户未传入FPS参数,则fps使用默认值
FPS = 2.0
# 最少抽取帧数
FPS_MIN_FRAMES = 4
# 最大抽取帧数(根据模型选择设置值)
FPS_MAX_FRAMES = 2000

# 视频输入的最大像素值,使用Qwen3-VL-Plus模型,请将VIDEO_TOTAL_PIXELS设置为131072 * 32 * 32,其他模型设置为65536 * 32 * 32
VIDEO_TOTAL_PIXELS = int(float(os.environ.get('VIDEO_TOTAL_PIXELS', 131072 * 32 * 32)))

def round_by_factor(number: int, factor: int) -> int:
    """返回与”number“最接近的整数,该整数可被”factor“整除。"""
    return round(number / factor) * factor

def ceil_by_factor(number: int, factor: int) -> int:
    """返回大于或等于“number”且可被“factor”整除的最小整数。"""
    return math.ceil(number / factor) * factor

def floor_by_factor(number: int, factor: int) -> int:
    """返回小于或等于“number”且可被“factor”整除的最大整数。"""
    return math.floor(number / factor) * factor

def extract_vision_info(conversations):
    vision_infos = []
    if isinstance(conversations[0], dict):
        conversations = [conversations]
    for conversation in conversations:
        for message in conversation:
            if isinstance(message["content"], list):
                for ele in message["content"]:
                    if (
                        "image" in ele
                        or "image_url" in ele
                        or "video" in ele
                        or ele.get("type","") in ("image", "image_url", "video")
                    ):
                        vision_infos.append(ele)
    return vision_infos

def smart_nframes(ele,total_frames,video_fps):
    """用于计算抽取的视频帧数。

    Args:
        ele (dict): 包含视频配置的字典格式
            - fps: fps用于控制提取模型输入帧的数量。
        total_frames (int): 视频的原始总帧数。
        video_fps (int | float): 视频的原始帧率

    Raises:
        nframes应该在[FRAME_FACTOR,total_frames]间隔内,否则会报错

    Returns:
        用于模型输入的视频帧数。
    """
    assert not ("fps" in ele and "nframes" in ele), "Only accept either `fps` or `nframes`"
    fps = ele.get("fps", FPS)
    min_frames = ceil_by_factor(ele.get("min_frames", FPS_MIN_FRAMES), FRAME_FACTOR)
    max_frames = floor_by_factor(ele.get("max_frames", min(FPS_MAX_FRAMES, total_frames)), FRAME_FACTOR)
    duration = total_frames / video_fps if video_fps != 0 else 0
    if duration-int(duration)>(1/fps):
        total_frames = math.ceil(duration * video_fps)
    else:
        total_frames = math.ceil(int(duration)*video_fps)
    nframes = total_frames / video_fps * fps
    if nframes > total_frames:
        logger.warning(f"smart_nframes: nframes[{nframes}] > total_frames[{total_frames}]")
    nframes = int(min(min(max(nframes, min_frames), max_frames), total_frames))
    if not (FRAME_FACTOR <= nframes and nframes <= total_frames):
        raise ValueError(f"nframes should in interval [{FRAME_FACTOR}, {total_frames}], but got {nframes}.")

    return nframes

def get_video(video_path):
    # 获取视频信息
    cap = cv2.VideoCapture(video_path)

    frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    # 获取视频高度
    frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

    video_fps = cap.get(cv2.CAP_PROP_FPS)
    return frame_height, frame_width, total_frames, video_fps

def smart_resize(ele, path, factor=IMAGE_FACTOR):
    # 获取原视频的宽和高
    height, width, total_frames, video_fps = get_video(path)
    # 视频帧的Token下限
    min_pixels = VIDEO_MIN_PIXELS
    total_pixels = VIDEO_TOTAL_PIXELS
    # 抽取的视频帧数
    nframes = smart_nframes(ele, total_frames, video_fps)
    max_pixels = max(min(VIDEO_MAX_PIXELS, total_pixels / nframes * FRAME_FACTOR),int(min_pixels * 1.05))

    # 视频的长宽比不应超过200:1或1:200
    if max(height, width) / min(height, width) > MAX_RATIO:
        raise ValueError(
            f"absolute aspect ratio must be smaller than {MAX_RATIO}, got {max(height, width) / min(height, width)}"
        )

    h_bar = max(factor, round_by_factor(height, factor))
    w_bar = max(factor, round_by_factor(width, factor))
    if h_bar * w_bar > max_pixels:
        beta = math.sqrt((height * width) / max_pixels)
        h_bar = floor_by_factor(height / beta, factor)
        w_bar = floor_by_factor(width / beta, factor)
    elif h_bar * w_bar < min_pixels:
        beta = math.sqrt(min_pixels / (height * width))
        h_bar = ceil_by_factor(height * beta, factor)
        w_bar = ceil_by_factor(width * beta, factor)
    return h_bar, w_bar

def token_calculate(video_path, fps):
    # 传入视频路径和fps抽帧参数
    messages = [{"content": [{"video": video_path, "fps": fps}]}]
    vision_infos = extract_vision_info(messages)[0]

    resized_height, resized_width = smart_resize(vision_infos, video_path)

    height, width, total_frames, video_fps = get_video(video_path)
    num_frames = smart_nframes(vision_infos, total_frames, video_fps)
    print(f"原视频尺寸:{height}*{width}, 输入模型的尺寸:{resized_height}*{resized_width},视频总帧数:{total_frames},fps等于{fps}时,抽取的总帧数:{num_frames}", end=",")
    video_token = int(math.ceil(num_frames / 2) * resized_height / 32 * resized_width / 32)
    video_token += 2   # 系统会自动添加<|vision_bos|>和<|vision_eos|>视觉标记(各计1个Token)
    return video_token

video_token = token_calculate("xxx/test.mp4", 1)
print("视频tokens:", video_token)
  • 图像列表:

    当以图像列表形式传入视频时,表示已预先完成视频抽帧,可使用以下代码,通过传入图像的路径和数量来计算传入图像列表时消耗的Token数:

# 使用前安装:pip install Pillow
import math
import os
import logging
from typing import Tuple
from PIL import Image

logger = logging.getLogger(__name__)

# ==================== 常量定义 ====================
FRAME_FACTOR = 2
# Qwen3-VL、qwen-vl-max-0813、qwen-vl-plus-0815、qwen-vl-plus-0710模型,缩放因子为32
IMAGE_FACTOR = 32

#  其他模型,缩放因子为28
# IMAGE_FACTOR = 28

# Token计算相关常量
TOKEN_DIVISOR = 32  # token计算时的除数
VISION_SPECIAL_TOKENS = 2  # <|vision_bos|>和<|vision_eos|>标记

# 视频帧的最大长宽比
MAX_RATIO = 200
# 视频帧的像素下限
VIDEO_MIN_PIXELS = 4 * 32 * 32
# 视频帧的像素上限,使用Qwen3-VL-Plus模型,VIDEO_MAX_PIXELS为640 * 32 * 32,其他模型为768 * 32 * 32
VIDEO_MAX_PIXELS = 640 * 32 * 32

# 视频输入的最大像素值,使用Qwen3-VL-Plus模型,请将VIDEO_TOTAL_PIXELS设置为131072 * 32 * 32,其他模型设置为65536 * 32 * 32
VIDEO_TOTAL_PIXELS = int(float(os.environ.get('VIDEO_TOTAL_PIXELS', 131072 * 32 * 32)))

def round_by_factor(number: int, factor: int) -> int:
    """返回与”number“最接近的整数,该整数可被”factor“整除。"""
    return round(number / factor) * factor

def ceil_by_factor(number: int, factor: int) -> int:
    """返回大于或等于“number”且可被“factor”整除的最小整数。"""
    return math.ceil(number / factor) * factor

def floor_by_factor(number: int, factor: int) -> int:
    """返回小于或等于“number”且可被“factor”整除的最大整数。"""
    return math.floor(number / factor) * factor

def get_image_size(image_path: str) -> Tuple[int, int]:
    if not os.path.exists(image_path):
        raise FileNotFoundError(f"图像文件不存在: {image_path}")

    try:
        image = Image.open(image_path)
        height = image.height
        width = image.width
        image.close()  # 及时关闭文件
        return height, width
    except Exception as e:
        raise ValueError(f"无法读取图像文件 {image_path}: {str(e)}")

def smart_resize(height: int, width: int, nframes: int, factor: int = IMAGE_FACTOR) -> Tuple[int, int]:
    """
    计算图像缩放后的尺寸

    Args:
        height: 原始图像高度
        width: 原始图像宽度
        nframes: 视频帧数
        factor: 缩放因子,默认为IMAGE_FACTOR

    Returns:
        (resized_height, resized_width) 缩放后的高度和宽度

    Raises:
        ValueError: 长宽比超过限制
    """
    # 视频帧的Token下限
    min_pixels = VIDEO_MIN_PIXELS
    total_pixels = VIDEO_TOTAL_PIXELS
    # 抽取的视频帧数
    max_pixels = max(min(VIDEO_MAX_PIXELS, total_pixels / nframes * FRAME_FACTOR), int(min_pixels * 1.05))

    # 视频的长宽比不应超过200:1或1:200
    aspect_ratio = max(height, width) / min(height, width)
    if aspect_ratio > MAX_RATIO:
        raise ValueError(
            f"图像长宽比必须小于 {MAX_RATIO}:1,当前为 {aspect_ratio:.2f}:1"
        )

    h_bar = max(factor, round_by_factor(height, factor))
    w_bar = max(factor, round_by_factor(width, factor))
    if h_bar * w_bar > max_pixels:
        beta = math.sqrt((height * width) / max_pixels)
        h_bar = floor_by_factor(height / beta, factor)
        w_bar = floor_by_factor(width / beta, factor)
    elif h_bar * w_bar < min_pixels:
        beta = math.sqrt(min_pixels / (height * width))
        h_bar = ceil_by_factor(height * beta, factor)
        w_bar = ceil_by_factor(width * beta, factor)
    return h_bar, w_bar

def calculate_video_tokens(image_path: str, nframes: int = 1, factor: int = IMAGE_FACTOR, verbose: bool = True) -> int:
    """

    Args:
        image_path: 视频帧文件路径
        nframes: 视频帧数,
        factor: 缩放因子,默认为IMAGE_FACTOR
        verbose: 是否打印详细信息

    Returns:
        所消耗的token数量

    Raises:
        FileNotFoundError: 文件不存在
        ValueError: 文件格式无效或长宽比超限
    """
    # 获取原始图像尺寸(只读取一次)
    height, width = get_image_size(image_path)

    # 计算缩放后的尺寸
    resized_height, resized_width = smart_resize(height, width, nframes, factor)

    # 计算token数量
    # 公式:⌈帧数/2⌉ × (高度/TOKEN_DIVISOR) × (宽度/TOKEN_DIVISOR) + VISION_SPECIAL_TOKENS
    video_token = int(
        math.ceil(nframes / 2) *
        (resized_height / TOKEN_DIVISOR) *
        (resized_width / TOKEN_DIVISOR)
    )
    # 添加视觉标记token(<|vision_bos|>和<|vision_eos|>)
    video_token += VISION_SPECIAL_TOKENS

    if verbose:
        print(f"原视频帧尺寸:{height}×{width},输入模型的尺寸:{resized_height}×{resized_width},", end="")

    return video_token

if __name__ == "__main__":
    try:
        video_token = calculate_video_tokens("xxx/test.jpg", nframes=30)
        print(f"视频tokens: {video_token}\n")
    except Exception as e:
        print(f"错误: {str(e)}\n")

模型调优前必读

  • 文本生成模型调优虽然能在特定业务/场景取得非常好的效果,但有以下限制:

    • 耗时较长,包括:拥有一个大规模(最少 0.5亿 token)CPT 数据集、构建一个有效(1000+)SFT 数据集、收集足够的(100+)Bad Case 构建有效 DPO 数据集、模型优化迭代速度慢等。
    • 费用较高,调优后的模型部署后才能使用,模型部署计费较高。
  • 阿里云百炼推荐您在考虑使用文本生成模型调优前先尝试使用Prompt 工程Prompt Engineering)或插件调用Function Calling定制化您的应用,模型调优也通常作为改进模型表现“最后的手段”。因为:

    1. 在许多任务中,模型最初可能表现不佳,但通过应用正确的 Prompt 技巧可以改进结果,不一定需要使用模型调优。
    2. 迭代优化 Prompt、插件,比模型调优的迭代更敏捷、成本更低,因为模型调优的迭代可能需要重新收集数据、清洗优化数据、收集 bad case、发起客户调研等。
    3. 即使最后一定要进行模型调优,最初的 Prompt 工程、插件迭代优化相关工作也不会浪费。您的这些前期工作可以充分地在构建调优数据集时复用(用于构建数据集的输入)。

调优效果展示

快速开始

使用控制台进行模型调优

调优步骤

控制台截图

步骤一:在模型调优页面点击创建训练任务

image

步骤二:训练配置

  • 训练方式SFT微调训练

  • 选择模型: 千问3-8B

  • 训练方式高效训练

  • 参数配置:保持默认即可,百炼对微调超参提供了推荐配置。

这个组合训练时间短,数据要求低。

步骤三:数据配置

  • 训练集: 在平台上选择构建模型所需的已上传调优数据集。

    数据样例:SFT-ChatML格式示例.jsonl

  • 混合训练: 不开启

  • 验证集:设置为自动切分,分割 10% 作为验证集

image

步骤四:配置模型参数快照(Checkpoint)保存参数

  • 模型名称:保持默认即可

  • 导出数量上限:保持默认即可

  • Checkpoint保存间隔:保持默认即可

在百炼平台上,模型调优完成后可以导出参数快照,导出后才能基于此版本的参数快照在百炼上进行模型部署。

导出的参数快照保存在云存储中,暂不支持访问或下载。

image

步骤五:点击“开始训练”后,等待模型训练完毕。

步骤六:使用阿里云百炼的模型部署功能部署训练好的自定义模型,部署好后就可以对调优好的模型进行评测。模型部署相关信息请参见模型部署

步骤七:使用阿里云百炼模型评测功能评估自定义模型的训练效果,相关信息请参见模型评测

典型的调优流程

百炼提供的三种调优方式并不互斥,而是递进的、相辅相成的。

CPT(可选)→ SFT → DPO(可选)

  1. CPT (持续预训练)- 补知识 (通用模型知识的“广度”和“浅度”,无法满足专业领域的“深度”和“精度”要求)

    • 金融模型: 学金融术语
    • 医疗模型: 记药品病理
    • 法律模型: 懂法条判例
  2. SFT (监督微调)- 学做事

    • 客服机器人: 学客服流程
    • 代码助手: 学编程范式
    • 工具调用 (Agent): 学使用 MCP
  3. DPO (直接偏好优化)- 做得更好

    • 安全与责任感: 拒有害建议
    • 简洁与有效性: 答干脆利落
    • 客观与中立: 评公正客观

调优数据格式

SFT/DPO/CPT 各训练方式的数据格式规格、打包规则、大小与数量限制、API 上传配额详见调优数据上传规则。各训练方式支持的文件格式概览:

训练方式

文件格式

SFT 文本生成

jsonl(ChatML messages 多轮结构)

SFT 多模态(图片/视频)

zip(data.jsonl + 图片/视频文件)

DPO

jsonl(chosen/rejected 偏好对比)

CPT

jsonl(纯文本 {text})

评测集

xlsx

数据集构建技巧

数据集的规模要求

对于CPT来说,数据集最少需要五千万Token优质预训练数据;对于 SFT 来说,数据集最少需要上千条优质调优数据;对于 DPO 来说,数据集一般需要上百条人类偏好数据。如果数据调优后的模型评测结果不佳,最简单的改进方法是收集更多数据进行训练。

如果您缺乏数据,建议构建智能体应用(Agent 1.0),使用知识库索引来增强模型能力。当然在很多复杂的业务场景,可以综合采用模型调优和知识库检索结合的技术方案。

以客服场景为例,可以借助模型调优解决客服回答的语气、表达习惯、自我认知等问题,场景涉及的专业知识可以结合知识库,动态引入到模型上下文中。

阿里云百炼推荐您可以先构建 RAG 应用试运行,在收集到足够的应用数据后再通过模型调优继续提升模型表现。

您也可以采用以下策略扩充数据集:

  1. 让大模型模拟生成特定业务/场景的相关内容,辅助您生成更多用于调优数据。(生成模型建议选取表现优异、规模更大的模型)
  2. 使用阿里云百炼的数据处理功能,对您的数据集进行数据清洗、数据增强。
  3. 通过应用场景收集、网络爬虫、社交媒体和在线论坛、公开数据集、合作伙伴与行业资源、用户贡献等各种方式,人工获取更多数据。

数据的多样性与均衡性

模型调优有不同场景,针对具体业务场景时,专业性更重要;而针对问答场景时通用性更重要。您需要根据模型负责的业务模块或使用场景进行数据用例设计。因此训练效果好坏并不是仅取决于数据量,更需要考虑针对场景的专业性和多样性。

这里以智能 AI 对话场景为例,介绍一个专业、多样的数据集应该包含的各种业务场景:

具体业务

多样化场景/业务

电商客服

活动推送、售前咨询、售中引导、售后服务、售后回访、投诉处理等。

金融服务

贷款咨询、投资理财顾问、信用卡服务、银行账户管理等。

在线医疗

病症咨询、挂号预约、就诊须知、药品信息查询、健康小建议等。

AI 秘书

IT 信息、行政信息、HR 信息、员工福利解答、公司日历查询等。

旅游出行助手

旅行规划、出入境指南、旅行保险咨询、目的地风土人情介绍等。

企业法律顾问

合同审核、知识产权保护、合规性检查、劳动法律答疑、跨境交易咨询、个案法律分析等。

还请特别注意的是各个场景/业务的数据数量应相对均衡,数据比例符合实际场景比例,避免某一类数据过多导致模型偏向于学习该类特征,影响模型的泛化能力。

训练集与验证集拆分

当您使用控制台进行模型调优时,支持

  • 自动将一个完整训练数据集拆分,随机抽取少量数据组成验证集。
  • 选择独立上传数据集。

控制台可以在训练时及时方便地显示验证集 Loss 和 Token Accuracy。

image

常见问题

是否支持调优自己的模型呢?

百炼不支持调优自己的模型,但可以通过我的模型(北京地域)导入safetensor 格式的千问系列开源模型,详情请参考模型导入。模型导入后可以在百炼平台上部署使用。