已合并
【feature】model_type支持transformers #891
zhangz200102创建于 9 天前
【feature】model_type支持transformers #891
已合并
共 14 个文件变更+781-8
| @@ -4,6 +4,7 @@ question_and_answer_url = https://gitcode.com/Ascend/msmodelslim/blob/master/doc | |||
| 4 | 4 | ||
| 5 | [ModelAdapter] | 5 | [ModelAdapter] |
| 6 | default = default | 6 | default = default |
| 7 | +transformers = transformers | ||
| 7 | deepseek_v3 = DeepSeek-V3, DeepSeek-V3-0324, DeepSeek-R1, DeepSeek-R1-0528, DeepSeek-V3.1, DeepSeek-V3.1-Terminus | 8 | deepseek_v3 = DeepSeek-V3, DeepSeek-V3-0324, DeepSeek-R1, DeepSeek-R1-0528, DeepSeek-V3.1, DeepSeek-V3.1-Terminus |
| 8 | deepseek_v3_2 = DeepSeek-V3.2-Exp, DeepSeek-V3.2 | 9 | deepseek_v3_2 = DeepSeek-V3.2-Exp, DeepSeek-V3.2 |
| 9 | deepseek_v4 = DeepSeek-V4-Flash, DeepSeek-V4-Pro | 10 | deepseek_v4 = DeepSeek-V4-Flash, DeepSeek-V4-Pro |
| @@ -51,6 +52,7 @@ mimo_v2_flash = MiMo-V2-Flash | |||
| 51 | 52 | ||
| 52 | [ModelAdapterEntryPoints] | 53 | [ModelAdapterEntryPoints] |
| 53 | default = msmodelslim.model.default.loader:DefaultAdapterLoader | 54 | default = msmodelslim.model.default.loader:DefaultAdapterLoader |
| 55 | +transformers = msmodelslim.model.transformers.loader:TransformersAdapterLoader | ||
| 54 | deepseek_v3 = msmodelslim.model.deepseek_v3.loader:DeepseekV3AdapterLoader | 56 | deepseek_v3 = msmodelslim.model.deepseek_v3.loader:DeepseekV3AdapterLoader |
| 55 | deepseek_v3_2 = msmodelslim.model.deepseek_v3_2.loader:DeepseekV3_2AdapterLoader | 57 | deepseek_v3_2 = msmodelslim.model.deepseek_v3_2.loader:DeepseekV3_2AdapterLoader |
| 56 | deepseek_v4 = msmodelslim.model.deepseek_v4.loader:DeepseekV4AdapterLoader | 58 | deepseek_v4 = msmodelslim.model.deepseek_v4.loader:DeepseekV4AdapterLoader |
| @@ -25,7 +25,7 @@ msmodelslim quant [--model_type <model_type>] --model_path <model_path> --save_p | |||
| 25 | 25 | ||
| 26 | | 参数 | 别名 | 类型 | 传入形式 | 必选/可选 | 默认值 | 取值范围或格式 | 含义 | | 26 | | 参数 | 别名 | 类型 | 传入形式 | 必选/可选 | 默认值 | 取值范围或格式 | 含义 | |
| 27 | |------|------|------|----------|-----------|--------|----------------|------| | 27 | |------|------|------|----------|-----------|--------|----------------|------| |
| 28 | -| `--model_type` | 无 | `string` | 单值 | 条件必选(普通量化路径必选;`--config` 指向 `apiversion: modelslim_convert` 的配置时可省略) | 无 | 模型类型名称,如 `Qwen2.5-7B-Instruct` | 指定待量化模型类型,用于加载对应模型适配器并匹配最佳实践;仅当 `--config` 指向 `apiversion: modelslim_convert` 的配置时可省略。 | | 28 | +| `--model_type` | 无 | `string` | 单值 | 条件必选(普通量化路径必选;`--config` 指向 `apiversion: modelslim_convert` 的配置时可省略) | 无 | 模型类型名称,如 `Qwen2.5-7B-Instruct`、`transformers` | 指定待量化模型类型,用于加载对应模型适配器并匹配最佳实践;`transformers`支持加载基于 transformers 的通用基础模型适配器,当前仅适用于大语言模型量化;仅当 `--config` 指向 `apiversion: modelslim_convert` 的配置时可省略。 | |
| 29 | | `--model_path` | 无 | `string` | 单值 | 必选 | 无 | 原始模型权重目录(需存在且可读) | 待量化模型的权重目录。 | | 29 | | `--model_path` | 无 | `string` | 单值 | 必选 | 无 | 原始模型权重目录(需存在且可读) | 待量化模型的权重目录。 | |
| 30 | | `--save_path` | 无 | `string` | 单值 | 必选 | 无 | 输出目录(需可写) | 量化权重与描述文件的保存目录。 | | 30 | | `--save_path` | 无 | `string` | 单值 | 必选 | 无 | 输出目录(需可写) | 量化权重与描述文件的保存目录。 | |
| 31 | | `--device` | 无 | `string` | 单值 | 可选 | `npu` | `npu`、`cpu` | 运行设备类型;多卡索引请用 `--device_id` 指定。 | | 31 | | `--device` | 无 | `string` | 单值 | 可选 | `npu` | `npu`、`cpu` | 运行设备类型;多卡索引请用 `--device_id` 指定。 | |
| @@ -46,6 +46,7 @@ msmodelslim quant [--model_type <model_type>] --model_path <model_path> --save_p | |||
| 46 | - 两者都不传时,按默认量化类型 `w8a8` 匹配最佳实践;未匹配到最佳实践时会给出提示并等待确认(输入 `y` 继续,否则退出)。 | 46 | - 两者都不传时,按默认量化类型 `w8a8` 匹配最佳实践;未匹配到最佳实践时会给出提示并等待确认(输入 `y` 继续,否则退出)。 |
| 47 | - 指定 `--config` 后直接采用该配置,`--quant_type` 与 `--tags` 的最佳实践匹配均被忽略。 | 47 | - 指定 `--config` 后直接采用该配置,`--quant_type` 与 `--tags` 的最佳实践匹配均被忽略。 |
| 48 | - `--model_type` 在普通量化路径下必须提供;仅当 `--config` 指向 `apiversion: modelslim_convert` 的配置时可省略。 | 48 | - `--model_type` 在普通量化路径下必须提供;仅当 `--config` 指向 `apiversion: modelslim_convert` 的配置时可省略。 |
| 49 | +- `--model_type` 指定 `transformers` 时,若使用 `--quant_type`, 仅支持 `w8a16`/`w8a8`;若使用 `--config`,当前仅支持大语言模型量化配置(`apiversion: modelslim_v1`)。 | ||
| 49 | - `--tags` 指定多个值时须同时出现在同一已验证场景;未提供硬件类型标签时自动匹配当前设备类型。 | 50 | - `--tags` 指定多个值时须同时出现在同一已验证场景;未提供硬件类型标签时自动匹配当前设备类型。 |
| 50 | - `--debug` 启用后量化上下文写入 `${SAVE_PATH}/debug_info/`。 | 51 | - `--debug` 启用后量化上下文写入 `${SAVE_PATH}/debug_info/`。 |
| 51 | 52 | ||
| @@ -81,7 +82,17 @@ msmodelslim quant \ | |||
| 81 | 82 | ||
| 82 | `${MODEL_TYPE}` 为模型类型名称(如 `Qwen2.5-7B-Instruct`),`${MODEL_PATH}` 为浮点权重目录,`${SAVE_PATH}` 为量化输出目录。未指定 `--quant_type` 与 `--config` 时,默认按量化类型 `w8a8` 匹配最佳实践并执行量化;若模型加载需要模型目录内的自定义代码,再补充 `--trust_remote_code true`。 | 83 | `${MODEL_TYPE}` 为模型类型名称(如 `Qwen2.5-7B-Instruct`),`${MODEL_PATH}` 为浮点权重目录,`${SAVE_PATH}` 为量化输出目录。未指定 `--quant_type` 与 `--config` 时,默认按量化类型 `w8a8` 匹配最佳实践并执行量化;若模型加载需要模型目录内的自定义代码,再补充 `--trust_remote_code true`。 |
| 83 | 84 | ||
| 84 | -### 7.2 显式指定量化类型与设备 | 85 | +### 7.2 使用 transformers 通用基础模型适配器量化(仅适用于大语言模型) |
C | |||
| 86 | + | ||
| 87 | +```bash | ||
| 88 | +msmodelslim quant \ | ||
| 89 | + --model_type transformers \ | ||
| 90 | + --model_path "${MODEL_PATH}" \ | ||
| 91 | + --save_path "${SAVE_PATH}" \ | ||
| 92 | + --quant_type w8a16 | ||
| 93 | +``` | ||
| 94 | + | ||
| 95 | +### 7.3 显式指定量化类型与设备 | ||
| 85 | 96 | ||
| 86 | ```bash | 97 | ```bash |
| 87 | msmodelslim quant \ | 98 | msmodelslim quant \ |
| @@ -95,7 +106,7 @@ msmodelslim quant \ | |||
| 95 | 106 | ||
| 96 | `--quant_type w8a8c8` 表示权重8bit、激活8bit、KVCache 8bit 量化;`--device_id 0 1 2 3` 使用4个 NPU 设备(索引列表形式在 `apiversion: modelslim_v1` 配置下受支持)。 | 107 | `--quant_type w8a8c8` 表示权重8bit、激活8bit、KVCache 8bit 量化;`--device_id 0 1 2 3` 使用4个 NPU 设备(索引列表形式在 `apiversion: modelslim_v1` 配置下受支持)。 |
| 97 | 108 | ||
| 98 | -### 7.3 使用自定义配置文件 | 109 | +### 7.4 使用自定义配置文件 |
| 99 | 110 | ||
| 100 | ```bash | 111 | ```bash |
| 101 | msmodelslim quant \ | 112 | msmodelslim quant \ |
| @@ -107,7 +118,7 @@ msmodelslim quant \ | |||
| 107 | 118 | ||
| 108 | `${CONFIG_PATH}` 指向符合 V1 等协议的量化 YAML;指定后直接采用该配置,不再做最佳实践匹配。字段说明见引用的配置文档。 | 119 | `${CONFIG_PATH}` 指向符合 V1 等协议的量化 YAML;指定后直接采用该配置,不再做最佳实践匹配。字段说明见引用的配置文档。 |
| 109 | 120 | ||
| 110 | -### 7.4 使用场景标签匹配最佳实践 | 121 | +### 7.5 使用场景标签匹配最佳实践 |
| 111 | 122 | ||
| 112 | ```bash | 123 | ```bash |
| 113 | msmodelslim quant \ | 124 | msmodelslim quant \ |
| @@ -103,10 +103,17 @@ msmodelslim quant \ | |||
| 103 | 103 | ||
| 104 | **操作**: | 104 | **操作**: |
| 105 | 105 | ||
| 106 | -- **尚未接入的模型**:须先完成适配器开发与注册,再进入步骤 3。 | 106 | +- **尚未接入的模型**:通常需要先自主接入模型,完成适配器开发与注册,再进入步骤 3。特例:对于基于 transformers 的大语言模型,使用 `--model_type transformers` 可匹配通用基础模型适配器,无需自行适配。 |
| 107 | - **支持矩阵中已接入的模型**:可跳过本步骤,直接进入步骤 3 编写量化配置;确认所用 `--model_type`(即 `${MODEL_TYPE}`)名称即可。 | 107 | - **支持矩阵中已接入的模型**:可跳过本步骤,直接进入步骤 3 编写量化配置;确认所用 `--model_type`(即 `${MODEL_TYPE}`)名称即可。 |
| 108 | 108 | ||
| 109 | -尚未接入时按下列操作执行: | 109 | +`--model_type transformers` 使用限制: |
| 110 | + | ||
| 111 | +- 该模型适配器仅具备基础量化能力,未实现任何算法。 | ||
| 112 | +- 若使用 `--quant_type`,当前仅支持 `w8a16`、`w8a8(MXFP8)`;若使用 `--config`,仅支持大语言模型量化配置(`apiversion: modelslim_v1`)。 | ||
| 113 | +- 仅推荐用于一键量化,若用于敏感层分析、自动调优等其他功能,用户需自行确保相关接口行为符合预期。 | ||
| 114 | +- 仅支持 `runner: model_wise` ,因而显存要求需足以容纳完整模型。 | ||
| 115 | + | ||
| 116 | +自主接入时按下列操作执行: | ||
| 110 | 117 | ||
| 111 | 1. 按模型类别阅读对应接入指南,并按其要求完成适配器实现与注册(细节以接入指南为准): | 118 | 1. 按模型类别阅读对应接入指南,并按其要求完成适配器实现与注册(细节以接入指南为准): |
| 112 | 119 | ||
| @@ -0,0 +1,28 @@ | |||
| 1 | +apiversion: modelslim_v1 | ||
| 2 | + | ||
| 3 | +metadata: | ||
| 4 | + config_id: llm_transformers_w8a16 | ||
| 5 | + score: 50 | ||
| 6 | + verified_model_types: | ||
| 7 | + - transformers | ||
| 8 | + label: | ||
| 9 | + w_bit: 8 | ||
| 10 | + a_bit: 16 | ||
| 11 | + is_sparse: False | ||
| 12 | + kv_cache: False | ||
| 13 | + | ||
| 14 | +spec: | ||
| 15 | + runner: "model_wise" | ||
| 16 | + process: | ||
| 17 | + - type: "linear_quant" | ||
| 18 | + qconfig: | ||
| 19 | + weight: | ||
| 20 | + scope: "per_channel" | ||
| 21 | + dtype: "int8" | ||
| 22 | + symmetric: True | ||
| 23 | + method: "minmax" | ||
| 24 | + include: [ "*" ] | ||
| 25 | + | ||
| 26 | + save: | ||
| 27 | + - type: "ascendv1_saver" | ||
| 28 | + part_file_size: 4 | ||
| @@ -0,0 +1,35 @@ | |||
| 1 | +apiversion: modelslim_v1 | ||
| 2 | + | ||
| 3 | +metadata: | ||
| 4 | + config_id: llm_transformers_w8a8_mxfp8 | ||
| 5 | + score: 50 | ||
| 6 | + verified_model_types: | ||
| 7 | + - transformers | ||
| 8 | + label: | ||
| 9 | + w_bit: 8 | ||
| 10 | + a_bit: 8 | ||
| 11 | + is_sparse: False | ||
| 12 | + kv_cache: False | ||
| 13 | + | ||
| 14 | +default_w8a8_dynamic: | ||
| 15 | + act: | ||
| 16 | + scope: "per_block" | ||
| 17 | + dtype: "mxfp8" | ||
| 18 | + symmetric: True | ||
| 19 | + method: "minmax" | ||
| 20 | + weight: | ||
| 21 | + scope: "per_block" | ||
| 22 | + dtype: "mxfp8" | ||
| 23 | + symmetric: True | ||
| 24 | + method: "minmax" | ||
| 25 | + | ||
| 26 | +spec: | ||
| 27 | + runner: "model_wise" | ||
| 28 | + process: | ||
| 29 | + - type: "linear_quant" | ||
| 30 | + qconfig: | ||
| 31 | + include: [ "*" ] | ||
| 32 | + | ||
| 33 | + save: | ||
| 34 | + - type: "ascendv1_saver" | ||
| 35 | + part_file_size: 4 | ||
| @@ -156,6 +156,20 @@ def validate_device_index(device_index: Optional[List[int]], device_type: Device | |||
| 156 | ) | 156 | ) |
| 157 | 157 | ||
| 158 | 158 | ||
| 159 | +def check_model_type_transformers(model_adapter: IModel, config: PracticeConfig): | ||
| 160 | + model_type = model_adapter.get_model_type() | ||
| 161 | + if model_type == "transformers" and config.apiversion == "multimodal_vlm_modelslim_v1": | ||
| 162 | + raise UnsupportedError( | ||
| 163 | + "VLM quantization is not supported for model_type transformers", | ||
| 164 | + action="Please use a dedicated model adapter for multimodal models", | ||
| 165 | + ) | ||
| 166 | + if model_type == "transformers" and config.apiversion == "multimodal_sd_modelslim_v1": | ||
| 167 | + raise UnsupportedError( | ||
| 168 | + "DiT quantization is not supported for model_type transformers", | ||
| 169 | + action="Please use a dedicated model adapter for multimodal models", | ||
| 170 | + ) | ||
| 171 | + | ||
| 172 | + | ||
| 159 | 173 | ||
| 160 | class NaiveQuantizationApplication: | 174 | class NaiveQuantizationApplication: |
| 161 | def __init__( | 175 | def __init__( |
| @@ -235,6 +249,7 @@ class NaiveQuantizationApplication: | |||
| 235 | if config_path is not None: | 249 | if config_path is not None: |
| 236 | config = PracticeConfig.model_validate(yaml_safe_load(str(config_path))) | 250 | config = PracticeConfig.model_validate(yaml_safe_load(str(config_path))) |
| 237 | get_logger().info("Naive Quant apply config_path: %s", config_path) | 251 | get_logger().info("Naive Quant apply config_path: %s", config_path) |
| 252 | + check_model_type_transformers(model_adapter, config) | ||
| 238 | return config | 253 | return config |
| 239 | 254 | ||
| 240 | if not isinstance(model_adapter, ModelInfoInterface): | 255 | if not isinstance(model_adapter, ModelInfoInterface): |
| @@ -0,0 +1,20 @@ | |||
| 1 | +#!/usr/bin/env python | ||
| 2 | +# -*- coding: UTF-8 -*- | ||
| 3 | + | ||
| 4 | +""" | ||
| 5 | +------------------------------------------------------------------------- | ||
| 6 | +This file is part of the MindStudio project. | ||
| 7 | +Copyright (c) 2026 Huawei Technologies Co.,Ltd. | ||
| 8 | + | ||
| 9 | +MindStudio is licensed under Mulan PSL v2. | ||
| 10 | +You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 11 | +You may obtain a copy of Mulan PSL v2 at: | ||
| 12 | + | ||
| 13 | + http://license.coscl.org.cn/MulanPSL2 | ||
| 14 | + | ||
| 15 | +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 16 | +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 17 | +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 18 | +See the Mulan PSL v2 for more details. | ||
| 19 | +------------------------------------------------------------------------- | ||
| 20 | +""" | ||
| @@ -0,0 +1,96 @@ | |||
| 1 | +#!/usr/bin/env python | ||
| 2 | +# -*- coding: UTF-8 -*- | ||
| 3 | + | ||
| 4 | +""" | ||
| 5 | +------------------------------------------------------------------------- | ||
| 6 | +This file is part of the MindStudio project. | ||
| 7 | +Copyright (c) 2026 Huawei Technologies Co.,Ltd. | ||
| 8 | + | ||
| 9 | +MindStudio is licensed under Mulan PSL v2. | ||
| 10 | +You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 11 | +You may obtain a copy of Mulan PSL v2 at: | ||
| 12 | + | ||
| 13 | + http://license.coscl.org.cn/MulanPSL2 | ||
| 14 | + | ||
| 15 | +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 16 | +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 17 | +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 18 | +See the Mulan PSL v2 for more details. | ||
| 19 | +------------------------------------------------------------------------- | ||
| 20 | +""" | ||
| 21 | + | ||
| 22 | +from enum import Enum | ||
| 23 | +from pathlib import Path | ||
| 24 | +from typing import Any, Dict | ||
| 25 | + | ||
| 26 | +from msmodelslim.utils.exception import UnsupportedError, SecurityError | ||
| 27 | +from msmodelslim.utils.security import json_safe_load | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +class ModelClass(str, Enum): | ||
| 31 | + LLM = "llm" | ||
| 32 | + VLM = "vlm" | ||
| 33 | + DIT = "dit" | ||
| 34 | + | ||
| 35 | + | ||
| 36 | +LLM_ADAPTER_CLASS_PATH = "msmodelslim.model.transformers.model_adapter:LLMTransformersModel" | ||
| 37 | + | ||
| 38 | +_VLM_CONFIG_KEYS = ("vision_config",) | ||
| 39 | + | ||
| 40 | +_DIT_CONFIG_KEYS = ("_diffusers_version",) | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +def _is_vlm_config(config: Dict[str, Any]) -> bool: | ||
| 44 | + if any(key in config for key in _VLM_CONFIG_KEYS): | ||
| 45 | + return True | ||
| 46 | + return False | ||
| 47 | + | ||
| 48 | + | ||
| 49 | +def _is_dit_config(config: Dict[str, Any]) -> bool: | ||
| 50 | + if any(key in config for key in _DIT_CONFIG_KEYS): | ||
| 51 | + return True | ||
| 52 | + return False | ||
| 53 | + | ||
| 54 | + | ||
| 55 | +def detect_transformers_kind(model_path: Path) -> ModelClass: | ||
| 56 | + """Classify a HuggingFace / diffusers directory as llm, vlm, or dit.""" | ||
| 57 | + config_path = model_path / "config.json" | ||
| 58 | + try: | ||
| 59 | + config = json_safe_load(str(config_path)) | ||
| 60 | + except SecurityError: | ||
| 61 | + raise UnsupportedError( | ||
| 62 | + f"The path {config_path} doesn't exist or isn't a file. " | ||
| 63 | + "Only standard transformers-based LLM model is supported currently " | ||
| 64 | + "when using model_type transformers.", | ||
| 65 | + action=(f"Please check (1) files in {model_path} are complete. (2) the model is a LLM model."), | ||
| 66 | + ) | ||
| 67 | + | ||
| 68 | + if _is_vlm_config(config): | ||
| 69 | + return ModelClass.VLM | ||
| 70 | + | ||
| 71 | + if _is_dit_config(config): | ||
| 72 | + return ModelClass.DIT | ||
| 73 | + | ||
| 74 | + return ModelClass.LLM | ||
| 75 | + | ||
| 76 | + | ||
| 77 | +def resolve_adapter_class_path(model_path: Path) -> str: | ||
| 78 | + """Return adapter class path for ``--model_type transformers``. | ||
| 79 | + | ||
| 80 | + VLMTransformersModel is not registered yet; VLM/DiT raise until a dedicated | ||
| 81 | + generic adapter exists. | ||
| 82 | + """ | ||
| 83 | + | ||
| 84 | + kind = detect_transformers_kind(model_path) | ||
| 85 | + | ||
| 86 | + if kind == ModelClass.VLM: | ||
| 87 | + raise UnsupportedError( | ||
| 88 | + "VLM quantization is not supported for model_type transformers", | ||
| 89 | + action="Please use a dedicated model adapter for multimodal models (VLM/DiT)", | ||
| 90 | + ) | ||
| 91 | + if kind == ModelClass.DIT: | ||
| 92 | + raise UnsupportedError( | ||
| 93 | + "DiT quantization is not supported for model_type transformers", | ||
| 94 | + action="Please use a dedicated model adapter for multimodal models (VLM/DiT)", | ||
| 95 | + ) | ||
| 96 | + return LLM_ADAPTER_CLASS_PATH | ||
| @@ -0,0 +1,20 @@ | |||
| 1 | +# -*- coding: UTF-8 -*- | ||
| 2 | + | ||
| 3 | +from pathlib import Path | ||
| 4 | + | ||
| 5 | +from msmodelslim.model.plugin_factory.base_loader import BaseModelAdapterLoader | ||
| 6 | + | ||
| 7 | +from .detect import LLM_ADAPTER_CLASS_PATH, resolve_adapter_class_path | ||
| 8 | + | ||
| 9 | + | ||
| 10 | +class TransformersAdapterLoader(BaseModelAdapterLoader): | ||
| 11 | + ADAPTER_CLASS_PATH = LLM_ADAPTER_CLASS_PATH | ||
| 12 | + | ||
| 13 | + def load( | ||
| 14 | + self, | ||
| 15 | + model_type: str, | ||
| 16 | + model_path: Path, | ||
| 17 | + trust_remote_code: bool = False, | ||
| 18 | + ): | ||
| 19 | + self.ADAPTER_CLASS_PATH = resolve_adapter_class_path(model_path) | ||
| 20 | + return super().load(model_type, model_path, trust_remote_code) | ||
| @@ -0,0 +1,103 @@ | |||
| 1 | +#!/usr/bin/env python | ||
| 2 | +# -*- coding: UTF-8 -*- | ||
| 3 | + | ||
| 4 | +""" | ||
| 5 | +------------------------------------------------------------------------- | ||
| 6 | +This file is part of the MindStudio project. | ||
| 7 | +Copyright (c) 2026 Huawei Technologies Co.,Ltd. | ||
| 8 | + | ||
| 9 | +MindStudio is licensed under Mulan PSL v2. | ||
| 10 | +You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 11 | +You may obtain a copy of Mulan PSL v2 at: | ||
| 12 | + | ||
| 13 | + http://license.coscl.org.cn/MulanPSL2 | ||
| 14 | + | ||
| 15 | +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 16 | +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 17 | +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 18 | +See the Mulan PSL v2 for more details. | ||
| 19 | +------------------------------------------------------------------------- | ||
| 20 | +""" | ||
| 21 | + | ||
| 22 | +from typing import Any, Generator, List | ||
| 23 | + | ||
| 24 | +from torch import nn | ||
| 25 | + | ||
| 26 | +from msmodelslim.core.base.protocol import ProcessRequest | ||
| 27 | +from msmodelslim.core.const import DeviceType | ||
| 28 | +from msmodelslim.utils.logging import logger_setter | ||
| 29 | +from msmodelslim.utils.exception import InvalidModelError | ||
| 30 | +from msmodelslim.utils.exception_decorator import exception_handler | ||
| 31 | +from ..common.layer_wise_forward import ( | ||
| 32 | + generated_decoder_layer_visit_func, | ||
| 33 | + transformers_generated_forward_func, | ||
| 34 | +) | ||
| 35 | +from ..common.transformers import TransformersModel | ||
| 36 | +from ..interface_hub import ( | ||
| 37 | + ModelInfoInterface, | ||
| 38 | + ModelSlimPipelineInterfaceV1, | ||
| 39 | +) | ||
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +class LLMTransformersModel( # pylint: disable=too-many-ancestors | ||
| 44 | + TransformersModel, | ||
| 45 | + ModelInfoInterface, | ||
| 46 | + ModelSlimPipelineInterfaceV1, | ||
| 47 | +): | ||
| 48 | + """Generic HuggingFace LLM adapter for ``--model_type transformers``. | ||
| 49 | + | ||
| 50 | + Suitable for standard decoder-layer LLMs. MoE / VLM / custom structures | ||
| 51 | + still need dedicated adapters. | ||
| 52 | + """ | ||
| 53 | + | ||
| 54 | + def get_model_type(self) -> str: | ||
| 55 | + return self.model_type | ||
| 56 | + | ||
| 57 | + def get_model_pedigree(self) -> str: | ||
| 58 | + return "llm_transformers" | ||
| 59 | + | ||
| 60 | + def handle_dataset(self, dataset: Any, device: DeviceType = DeviceType.NPU) -> List[Any]: | ||
| 61 | + with exception_handler( | ||
| 62 | + 'You are handling dataset with llm transformers model adapter but failed', | ||
| 63 | + ms_err_cls=InvalidModelError, | ||
| 64 | + action='Please ensure llm transformers model adapter match your model', | ||
| 65 | + ): | ||
| 66 | + return self._get_tokenized_data(dataset, device) | ||
| 67 | + | ||
| 68 | + def init_model(self, device: DeviceType = DeviceType.NPU) -> nn.Module: | ||
| 69 | + with exception_handler( | ||
| 70 | + 'You are initializing model with llm transformers model adapter but failed', | ||
| 71 | + ms_err_cls=InvalidModelError, | ||
| 72 | + action='Please ensure llm transformers model adapter match your model. ' | ||
| 73 | + 'For multimodal model (VLM/DiT) quantization, please implement a dedicated model adapter', | ||
| 74 | + ): | ||
| 75 | + return self._load_model(device) | ||
| 76 | + | ||
| 77 | + def generate_model_visit(self, model: nn.Module) -> Generator[ProcessRequest, Any, None]: | ||
| 78 | + with exception_handler( | ||
| 79 | + 'You are generating model visit with llm transformers model adapter but failed', | ||
| 80 | + ms_err_cls=InvalidModelError, | ||
| 81 | + action='Please ensure llm transformers model adapter match your model', | ||
| 82 | + ): | ||
| 83 | + yield from generated_decoder_layer_visit_func(model) | ||
| 84 | + | ||
| 85 | + def generate_model_forward( | ||
| 86 | + self, | ||
| 87 | + model: nn.Module, | ||
| 88 | + inputs: Any, | ||
| 89 | + ) -> Generator[ProcessRequest, Any, None]: | ||
| 90 | + with exception_handler( | ||
| 91 | + 'You are generating model forward with llm transformers model adapter but failed', | ||
| 92 | + ms_err_cls=InvalidModelError, | ||
| 93 | + action='Please ensure llm transformers model adapter match your model', | ||
| 94 | + ): | ||
| 95 | + yield from transformers_generated_forward_func(model, inputs) | ||
| 96 | + | ||
| 97 | + def enable_kv_cache(self, model: nn.Module, need_kv_cache: bool) -> None: | ||
| 98 | + with exception_handler( | ||
| 99 | + 'You are enabling kv cache with llm transformers model adapter but failed', | ||
| 100 | + ms_err_cls=InvalidModelError, | ||
| 101 | + action='Please ensure llm transformers model adapter match your model', | ||
| 102 | + ): | ||
| 103 | + return self._enable_kv_cache(model, need_kv_cache) | ||
| @@ -24,13 +24,13 @@ import shutil | |||
| 24 | import tempfile | 24 | import tempfile |
| 25 | import unittest | 25 | import unittest |
| 26 | from pathlib import Path | 26 | from pathlib import Path |
| 27 | -from unittest.mock import MagicMock | 27 | +from unittest.mock import MagicMock, patch |
| 28 | 28 | ||
| 29 | from testing_utils.mock import mock_init_config | 29 | from testing_utils.mock import mock_init_config |
| 30 | 30 | ||
| 31 | from msmodelslim.core.const import QuantType | 31 | from msmodelslim.core.const import QuantType |
| 32 | from msmodelslim.core.practice.interface import Metadata | 32 | from msmodelslim.core.practice.interface import Metadata |
| 33 | -from msmodelslim.utils.exception import SchemaValidateError, ToDoError | 33 | +from msmodelslim.utils.exception import SchemaValidateError, ToDoError, UnsupportedError |
| 34 | 34 | ||
| 35 | mock_init_config() | 35 | mock_init_config() |
| 36 | 36 | ||
| @@ -225,6 +225,38 @@ class TestCheckConfig(TestNaiveQuantizationAppBase): | |||
| 225 | class TestGetBestPractice(TestNaiveQuantizationAppBase): | 225 | class TestGetBestPractice(TestNaiveQuantizationAppBase): |
| 226 | """测试 get_best_practice""" | 226 | """测试 get_best_practice""" |
| 227 | 227 | ||
| 228 | + def _make_app(self): | ||
| 229 | + from msmodelslim.app.naive_quantization.application import NaiveQuantizationApplication | ||
| 230 | + | ||
| 231 | + return NaiveQuantizationApplication( | ||
| 232 | + practice_manager=MagicMock(), | ||
| 233 | + quant_service=MagicMock(), | ||
| 234 | + model_factory=MagicMock(), | ||
| 235 | + ) | ||
| 236 | + | ||
| 237 | + def _get_best_practice_with_mocked_yaml(self, model_type, apiversion, config_id="test_w8a8"): | ||
| 238 | + mock_model_adapter = MagicMock() | ||
| 239 | + mock_model_adapter.model_type = model_type | ||
| 240 | + mock_model_adapter.get_model_type.return_value = model_type | ||
| 241 | + app = self._make_app() | ||
| 242 | + mock_config = MagicMock() | ||
| 243 | + mock_config.apiversion = apiversion | ||
| 244 | + mock_config.metadata.config_id = config_id | ||
| 245 | + with ( | ||
| 246 | + patch( | ||
| 247 | + "msmodelslim.app.naive_quantization.application.yaml_safe_load", | ||
| 248 | + return_value={}, | ||
| 249 | + ), | ||
| 250 | + patch( | ||
| 251 | + "msmodelslim.app.naive_quantization.application.PracticeConfig.model_validate", | ||
| 252 | + return_value=mock_config, | ||
| 253 | + ), | ||
| 254 | + ): | ||
| 255 | + return app.get_best_practice( | ||
| 256 | + model_adapter=mock_model_adapter, | ||
| 257 | + config_path=Path("config.yaml"), | ||
| 258 | + ) | ||
| 259 | + | ||
| 228 | def test_get_best_practice_with_config_path(self): | 260 | def test_get_best_practice_with_config_path(self): |
| 229 | """测试指定 config_path 时直接返回配置""" | 261 | """测试指定 config_path 时直接返回配置""" |
| 230 | from msmodelslim.app.naive_quantization.application import NaiveQuantizationApplication | 262 | from msmodelslim.app.naive_quantization.application import NaiveQuantizationApplication |
| @@ -290,6 +322,36 @@ spec: {} | |||
| 290 | quant_type=QuantType.W8A8, | 322 | quant_type=QuantType.W8A8, |
| 291 | ) | 323 | ) |
| 292 | 324 | ||
| 325 | + def test_get_best_practice_raises_unsupported_error_when_transformers_and_vlm_apiversion(self): | ||
| 326 | + with self.assertRaises(UnsupportedError) as ctx: | ||
| 327 | + self._get_best_practice_with_mocked_yaml("transformers", "multimodal_vlm_modelslim_v1") | ||
| 328 | + | ||
| 329 | + self.assertIn("VLM", str(ctx.exception)) | ||
| 330 | + self.assertIn("transformers", str(ctx.exception)) | ||
| 331 | + | ||
| 332 | + def test_get_best_practice_raises_unsupported_error_when_transformers_and_sd_apiversion(self): | ||
| 333 | + with self.assertRaises(UnsupportedError) as ctx: | ||
| 334 | + self._get_best_practice_with_mocked_yaml("transformers", "multimodal_sd_modelslim_v1") | ||
| 335 | + | ||
| 336 | + self.assertIn("DiT", str(ctx.exception)) | ||
| 337 | + self.assertIn("transformers", str(ctx.exception)) | ||
| 338 | + | ||
| 339 | + def test_get_best_practice_returns_config_when_transformers_and_modelslim_v1(self): | ||
| 340 | + config = self._get_best_practice_with_mocked_yaml("transformers", "modelslim_v1") | ||
| 341 | + | ||
| 342 | + self.assertEqual(config.metadata.config_id, "test_w8a8") | ||
| 343 | + self.assertEqual(config.apiversion, "modelslim_v1") | ||
| 344 | + | ||
| 345 | + def test_get_best_practice_returns_config_when_other_model_type_and_vlm_apiversion(self): | ||
| 346 | + config = self._get_best_practice_with_mocked_yaml( | ||
| 347 | + "Qwen2.5-VL-7B-Instruct", | ||
| 348 | + "multimodal_vlm_modelslim_v1", | ||
| 349 | + config_id="vlm_ok", | ||
| 350 | + ) | ||
| 351 | + | ||
| 352 | + self.assertEqual(config.metadata.config_id, "vlm_ok") | ||
| 353 | + self.assertEqual(config.apiversion, "multimodal_vlm_modelslim_v1") | ||
| 354 | + | ||
| 293 | 355 | ||
| 294 | class TestQuantParameterValidation(TestNaiveQuantizationAppBase): | 356 | class TestQuantParameterValidation(TestNaiveQuantizationAppBase): |
| 295 | """测试 quant 方法参数校验""" | 357 | """测试 quant 方法参数校验""" |
| @@ -0,0 +1,89 @@ | |||
| 1 | +#!/usr/bin/env python | ||
| 2 | +# -*- coding: UTF-8 -*- | ||
| 3 | + | ||
| 4 | +""" | ||
| 5 | +------------------------------------------------------------------------- | ||
| 6 | +This file is part of the MindStudio project. | ||
| 7 | +Copyright (c) 2026 Huawei Technologies Co.,Ltd. | ||
| 8 | + | ||
| 9 | +MindStudio is licensed under Mulan PSL v2. | ||
| 10 | +You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 11 | +You may obtain a copy of Mulan PSL v2 at: | ||
| 12 | + | ||
| 13 | + http://license.coscl.org.cn/MulanPSL2 | ||
| 14 | + | ||
| 15 | +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 16 | +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 17 | +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 18 | +See the Mulan PSL v2 for more details. | ||
| 19 | +------------------------------------------------------------------------- | ||
| 20 | +""" | ||
| 21 | + | ||
| 22 | +import unittest | ||
| 23 | +from pathlib import Path | ||
| 24 | +from unittest.mock import patch | ||
| 25 | + | ||
| 26 | +from msmodelslim.model.transformers.detect import ( | ||
| 27 | + LLM_ADAPTER_CLASS_PATH, | ||
| 28 | + ModelClass, | ||
| 29 | + detect_transformers_kind, | ||
| 30 | + resolve_adapter_class_path, | ||
| 31 | +) | ||
| 32 | +from msmodelslim.utils.exception import SecurityError, UnsupportedError | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +class TestDetectTransformersKind(unittest.TestCase): | ||
| 36 | + def setUp(self): | ||
| 37 | + self.model_path = Path("/tmp/transformers-model") | ||
| 38 | + | ||
| 39 | + def test_detect_transformers_kind_returns_llm_when_config_has_no_hints(self): | ||
| 40 | + with patch("msmodelslim.model.transformers.detect.json_safe_load", return_value={"model_type": "llama"}): | ||
| 41 | + self.assertEqual(detect_transformers_kind(self.model_path), ModelClass.LLM) | ||
| 42 | + | ||
| 43 | + def test_detect_transformers_kind_returns_vlm_when_vision_config_present(self): | ||
| 44 | + with patch("msmodelslim.model.transformers.detect.json_safe_load", return_value={"vision_config": {}}): | ||
| 45 | + self.assertEqual(detect_transformers_kind(self.model_path), ModelClass.VLM) | ||
| 46 | + | ||
| 47 | + def test_detect_transformers_kind_returns_dit_when_diffusers_version_present(self): | ||
| 48 | + with patch( | ||
| 49 | + "msmodelslim.model.transformers.detect.json_safe_load", | ||
| 50 | + return_value={"_diffusers_version": "0.30.0"}, | ||
| 51 | + ): | ||
| 52 | + self.assertEqual(detect_transformers_kind(self.model_path), ModelClass.DIT) | ||
| 53 | + | ||
| 54 | + def test_detect_transformers_kind_raises_unsupported_error_when_config_missing(self): | ||
| 55 | + with patch( | ||
| 56 | + "msmodelslim.model.transformers.detect.json_safe_load", | ||
| 57 | + side_effect=SecurityError("invalid path"), | ||
| 58 | + ): | ||
| 59 | + with self.assertRaises(UnsupportedError) as ctx: | ||
| 60 | + detect_transformers_kind(self.model_path) | ||
| 61 | + | ||
| 62 | + self.assertIn("config.json", str(ctx.exception)) | ||
| 63 | + self.assertIn("transformers", str(ctx.exception)) | ||
| 64 | + | ||
| 65 | + | ||
| 66 | +class TestResolveAdapterClassPath(unittest.TestCase): | ||
| 67 | + def setUp(self): | ||
| 68 | + self.model_path = Path("/tmp/transformers-model") | ||
| 69 | + | ||
| 70 | + def test_resolve_adapter_class_path_returns_llm_path_when_llm_config(self): | ||
| 71 | + with patch("msmodelslim.model.transformers.detect.json_safe_load", return_value={}): | ||
| 72 | + self.assertEqual(resolve_adapter_class_path(self.model_path), LLM_ADAPTER_CLASS_PATH) | ||
| 73 | + | ||
| 74 | + def test_resolve_adapter_class_path_raises_unsupported_error_when_vlm_config(self): | ||
| 75 | + with patch("msmodelslim.model.transformers.detect.json_safe_load", return_value={"vision_config": {}}): | ||
| 76 | + with self.assertRaises(UnsupportedError) as ctx: | ||
| 77 | + resolve_adapter_class_path(self.model_path) | ||
| 78 | + | ||
| 79 | + self.assertIn("VLM", str(ctx.exception)) | ||
| 80 | + | ||
| 81 | + def test_resolve_adapter_class_path_raises_unsupported_error_when_dit_config(self): | ||
| 82 | + with patch( | ||
| 83 | + "msmodelslim.model.transformers.detect.json_safe_load", | ||
| 84 | + return_value={"_diffusers_version": "0.30.0"}, | ||
| 85 | + ): | ||
| 86 | + with self.assertRaises(UnsupportedError) as ctx: | ||
| 87 | + resolve_adapter_class_path(self.model_path) | ||
| 88 | + | ||
| 89 | + self.assertIn("DiT", str(ctx.exception)) | ||
| @@ -0,0 +1,130 @@ | |||
| 1 | +#!/usr/bin/env python | ||
| 2 | +# -*- coding: UTF-8 -*- | ||
| 3 | + | ||
| 4 | +""" | ||
| 5 | +------------------------------------------------------------------------- | ||
| 6 | +This file is part of the MindStudio project. | ||
| 7 | +Copyright (c) 2026 Huawei Technologies Co.,Ltd. | ||
| 8 | + | ||
| 9 | +MindStudio is licensed under Mulan PSL v2. | ||
| 10 | +You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 11 | +You may obtain a copy of Mulan PSL v2 at: | ||
| 12 | + | ||
| 13 | + http://license.coscl.org.cn/MulanPSL2 | ||
| 14 | + | ||
| 15 | +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 16 | +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 17 | +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 18 | +See the Mulan PSL v2 for more details. | ||
| 19 | +------------------------------------------------------------------------- | ||
| 20 | +""" | ||
| 21 | + | ||
| 22 | +import unittest | ||
| 23 | +from pathlib import Path | ||
| 24 | +from types import SimpleNamespace | ||
| 25 | +from unittest.mock import patch | ||
| 26 | + | ||
| 27 | +from msmodelslim.model.transformers.detect import LLM_ADAPTER_CLASS_PATH | ||
| 28 | +from msmodelslim.model.transformers.loader import TransformersAdapterLoader | ||
| 29 | +from msmodelslim.model.transformers.model_adapter import LLMTransformersModel | ||
| 30 | +from msmodelslim.utils.exception import UnsupportedError, VersionError | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +def _mock_adapter_init(self, model_type, model_path, trust_remote_code=False): | ||
| 34 | + self.model_type = model_type | ||
| 35 | + self.model_path = model_path | ||
| 36 | + self.trust_remote_code = trust_remote_code | ||
| 37 | + | ||
| 38 | + | ||
| 39 | +class TestTransformersAdapterLoader(unittest.TestCase): | ||
| 40 | + def setUp(self): | ||
| 41 | + self.model_type = "transformers" | ||
| 42 | + self.model_path = Path("/tmp/transformers-model") | ||
| 43 | + self.loader = TransformersAdapterLoader() | ||
| 44 | + | ||
| 45 | + def test_adapter_class_path_points_to_llm_transformers_model_when_defined(self): | ||
| 46 | + self.assertEqual( | ||
| 47 | + TransformersAdapterLoader.ADAPTER_CLASS_PATH, | ||
| 48 | + LLM_ADAPTER_CLASS_PATH, | ||
| 49 | + ) | ||
| 50 | + self.assertEqual( | ||
| 51 | + TransformersAdapterLoader.ADAPTER_CLASS_PATH, | ||
| 52 | + "msmodelslim.model.transformers.model_adapter:LLMTransformersModel", | ||
| 53 | + ) | ||
| 54 | + | ||
| 55 | + def _load_with_patches(self, config=None, **load_kwargs): | ||
| 56 | + if config is None: | ||
| 57 | + config = {} | ||
| 58 | + with patch("msmodelslim.model.transformers.detect.json_safe_load", return_value=config): | ||
| 59 | + with patch("msmodelslim.model.plugin_factory.base_loader.DependencyChecker.set_plugin"): | ||
| 60 | + with patch("msmodelslim.model.plugin_factory.base_loader.DependencyChecker._check_single"): | ||
| 61 | + with patch("msmodelslim.model.plugin_factory.base_loader.get_require_packages", return_value={}): | ||
| 62 | + with patch("msmodelslim.model.plugin_factory.base_loader.import_module") as mock_import: | ||
| 63 | + mock_import.return_value = SimpleNamespace(LLMTransformersModel=LLMTransformersModel) | ||
| 64 | + with patch( | ||
| 65 | + "msmodelslim.model.transformers.model_adapter.TransformersModel.__init__", | ||
| 66 | + _mock_adapter_init, | ||
| 67 | + ): | ||
| 68 | + return self.loader.load( | ||
| 69 | + model_type=self.model_type, | ||
| 70 | + model_path=self.model_path, | ||
| 71 | + **load_kwargs, | ||
| 72 | + ) | ||
| 73 | + | ||
| 74 | + def test_load_returns_llm_transformers_model_when_trust_remote_code_true(self): | ||
| 75 | + adapter = self._load_with_patches(trust_remote_code=True) | ||
| 76 | + | ||
| 77 | + self.assertIsInstance(adapter, LLMTransformersModel) | ||
| 78 | + self.assertEqual(adapter.model_type, self.model_type) | ||
| 79 | + self.assertEqual(adapter.model_path, self.model_path) | ||
| 80 | + self.assertTrue(adapter.trust_remote_code) | ||
| 81 | + | ||
| 82 | + def test_load_passes_false_when_trust_remote_code_omitted(self): | ||
| 83 | + adapter = self._load_with_patches() | ||
| 84 | + self.assertFalse(adapter.trust_remote_code) | ||
| 85 | + | ||
| 86 | + def test_load_raises_unsupported_error_when_vlm_config(self): | ||
| 87 | + with self.assertRaises(UnsupportedError) as ctx: | ||
| 88 | + self._load_with_patches(config={"vision_config": {}}) | ||
| 89 | + | ||
| 90 | + self.assertIn("VLM", str(ctx.exception)) | ||
| 91 | + self.assertIn("transformers", str(ctx.exception)) | ||
| 92 | + | ||
| 93 | + def test_load_raises_unsupported_error_when_dit_config(self): | ||
| 94 | + with self.assertRaises(UnsupportedError) as ctx: | ||
| 95 | + self._load_with_patches(config={"_diffusers_version": "0.30.0"}) | ||
| 96 | + | ||
| 97 | + self.assertIn("DiT", str(ctx.exception)) | ||
| 98 | + self.assertIn("transformers", str(ctx.exception)) | ||
| 99 | + | ||
| 100 | + def test_precheck_sets_plugin_name_when_model_type_valid(self): | ||
| 101 | + with patch( | ||
| 102 | + "msmodelslim.model.plugin_factory.base_loader.msmodelslim_config", | ||
| 103 | + SimpleNamespace(model_adapter_dependencies={}), | ||
| 104 | + ): | ||
| 105 | + with patch("msmodelslim.model.plugin_factory.base_loader.DependencyChecker.set_plugin") as mock_set: | ||
| 106 | + with patch("msmodelslim.model.plugin_factory.base_loader.DependencyChecker._check_single"): | ||
| 107 | + self.loader.precheck( | ||
| 108 | + model_type=self.model_type, | ||
| 109 | + model_path=self.model_path, | ||
| 110 | + ) | ||
| 111 | + | ||
| 112 | + plugin_name = mock_set.call_args[0][0] | ||
| 113 | + self.assertEqual(plugin_name, f"msmodelslim.model_adapter.plugins:{self.model_type}") | ||
| 114 | + | ||
| 115 | + def test_precheck_sets_is_match_false_when_dependency_check_fails(self): | ||
| 116 | + self.loader._require_packages = {"numpy": ">=1.26"} | ||
| 117 | + with patch( | ||
| 118 | + "msmodelslim.model.plugin_factory.base_loader.msmodelslim_config", | ||
| 119 | + SimpleNamespace(model_adapter_dependencies={}), | ||
| 120 | + ): | ||
| 121 | + with patch( | ||
| 122 | + "msmodelslim.model.plugin_factory.base_loader.DependencyChecker._check_single", | ||
| 123 | + side_effect=VersionError("dependency mismatch"), | ||
| 124 | + ): | ||
| 125 | + self.loader.precheck( | ||
| 126 | + model_type=self.model_type, | ||
| 127 | + model_path=self.model_path, | ||
| 128 | + ) | ||
| 129 | + | ||
| 130 | + self.assertFalse(self.loader._is_match) | ||
| @@ -0,0 +1,155 @@ | |||
| 1 | +#!/usr/bin/env python | ||
| 2 | +# -*- coding: UTF-8 -*- | ||
| 3 | + | ||
| 4 | +""" | ||
| 5 | +------------------------------------------------------------------------- | ||
| 6 | +This file is part of the MindStudio project. | ||
| 7 | +Copyright (c) 2026 Huawei Technologies Co.,Ltd. | ||
| 8 | + | ||
| 9 | +MindStudio is licensed under Mulan PSL v2. | ||
| 10 | +You can use this software according to the terms and conditions of the Mulan PSL v2. | ||
| 11 | +You may obtain a copy of Mulan PSL v2 at: | ||
| 12 | + | ||
| 13 | + http://license.coscl.org.cn/MulanPSL2 | ||
| 14 | + | ||
| 15 | +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, | ||
| 16 | +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, | ||
| 17 | +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. | ||
| 18 | +See the Mulan PSL v2 for more details. | ||
| 19 | +------------------------------------------------------------------------- | ||
| 20 | +""" | ||
| 21 | + | ||
| 22 | +import unittest | ||
| 23 | +from pathlib import Path | ||
| 24 | +from unittest.mock import MagicMock, patch | ||
| 25 | + | ||
| 26 | +from msmodelslim.core.const import DeviceType | ||
| 27 | +from msmodelslim.model.transformers.model_adapter import LLMTransformersModel | ||
| 28 | +from msmodelslim.utils.exception import InvalidModelError | ||
| 29 | + | ||
| 30 | + | ||
| 31 | +class TestLLMTransformersModel(unittest.TestCase): | ||
| 32 | + def setUp(self): | ||
| 33 | + self.model_type = "transformers" | ||
| 34 | + self.model_path = Path(".") | ||
| 35 | + | ||
| 36 | + def _make_adapter(self): | ||
| 37 | + with patch( | ||
| 38 | + "msmodelslim.model.transformers.model_adapter.TransformersModel.__init__", | ||
| 39 | + return_value=None, | ||
| 40 | + ): | ||
| 41 | + adapter = LLMTransformersModel(model_type=self.model_type, model_path=self.model_path) | ||
| 42 | + adapter.model_type = self.model_type | ||
| 43 | + adapter.model_path = self.model_path | ||
| 44 | + return adapter | ||
| 45 | + | ||
| 46 | + def test_get_model_type_returns_transformers_when_called(self): | ||
| 47 | + adapter = self._make_adapter() | ||
| 48 | + self.assertEqual(adapter.get_model_type(), "transformers") | ||
| 49 | + | ||
| 50 | + def test_get_model_pedigree_returns_llm_transformers_when_called(self): | ||
| 51 | + adapter = self._make_adapter() | ||
| 52 | + self.assertEqual(adapter.get_model_pedigree(), "llm_transformers") | ||
| 53 | + | ||
| 54 | + def test_init_model_returns_model_when_load_succeeds(self): | ||
| 55 | + adapter = self._make_adapter() | ||
| 56 | + mock_model = MagicMock() | ||
| 57 | + adapter._load_model = MagicMock(return_value=mock_model) | ||
| 58 | + | ||
| 59 | + result = adapter.init_model(device=DeviceType.NPU) | ||
| 60 | + | ||
| 61 | + self.assertIs(result, mock_model) | ||
| 62 | + adapter._load_model.assert_called_once_with(DeviceType.NPU) | ||
| 63 | + | ||
| 64 | + def test_init_model_raises_invalid_model_error_when_load_fails(self): | ||
| 65 | + adapter = self._make_adapter() | ||
| 66 | + adapter._load_model = MagicMock(side_effect=Exception("Loading failed")) | ||
| 67 | + | ||
| 68 | + with self.assertRaises(InvalidModelError) as ctx: | ||
| 69 | + adapter.init_model(device=DeviceType.NPU) | ||
| 70 | + | ||
| 71 | + self.assertIn("dedicated model adapter", ctx.exception.action) | ||
| 72 | + self.assertIn("VLM/DiT", ctx.exception.action) | ||
| 73 | + | ||
| 74 | + def test_handle_dataset_returns_tokenized_data_when_helper_succeeds(self): | ||
| 75 | + adapter = self._make_adapter() | ||
| 76 | + mock_dataset = ["data1", "data2"] | ||
| 77 | + adapter._get_tokenized_data = MagicMock(return_value=mock_dataset) | ||
| 78 | + | ||
| 79 | + result = adapter.handle_dataset(dataset="test_data", device=DeviceType.CPU) | ||
| 80 | + | ||
| 81 | + self.assertEqual(result, mock_dataset) | ||
| 82 | + adapter._get_tokenized_data.assert_called_once_with("test_data", DeviceType.CPU) | ||
| 83 | + | ||
| 84 | + def test_handle_dataset_raises_invalid_model_error_when_helper_fails(self): | ||
| 85 | + adapter = self._make_adapter() | ||
| 86 | + adapter._get_tokenized_data = MagicMock(side_effect=Exception("Processing failed")) | ||
| 87 | + | ||
| 88 | + with self.assertRaises(InvalidModelError): | ||
| 89 | + adapter.handle_dataset(dataset="test_data", device=DeviceType.CPU) | ||
| 90 | + | ||
| 91 | + | ||
| 92 | + def test_generate_model_visit_yields_requests_when_helper_succeeds(self, mock_visit): | ||
| 93 | + mock_request = MagicMock() | ||
| 94 | + mock_visit.return_value = iter([mock_request]) | ||
| 95 | + adapter = self._make_adapter() | ||
| 96 | + mock_model = MagicMock() | ||
| 97 | + | ||
| 98 | + result = list(adapter.generate_model_visit(mock_model)) | ||
| 99 | + | ||
| 100 | + self.assertEqual(result, [mock_request]) | ||
| 101 | + mock_visit.assert_called_once_with(mock_model) | ||
| 102 | + | ||
| 103 | + | ||
| 104 | + def test_generate_model_visit_raises_invalid_model_error_when_helper_fails(self, mock_visit): | ||
| 105 | + mock_visit.side_effect = Exception("visit failed") | ||
| 106 | + adapter = self._make_adapter() | ||
| 107 | + | ||
| 108 | + with self.assertRaises(InvalidModelError): | ||
| 109 | + list(adapter.generate_model_visit(MagicMock())) | ||
| 110 | + | ||
| 111 | + | ||
| 112 | + def test_generate_model_forward_yields_requests_when_helper_succeeds(self, mock_forward): | ||
| 113 | + mock_request = MagicMock() | ||
| 114 | + mock_forward.return_value = iter([mock_request]) | ||
| 115 | + adapter = self._make_adapter() | ||
| 116 | + mock_model = MagicMock() | ||
| 117 | + mock_inputs = MagicMock() | ||
| 118 | + | ||
| 119 | + result = list(adapter.generate_model_forward(mock_model, mock_inputs)) | ||
| 120 | + | ||
| 121 | + self.assertEqual(result, [mock_request]) | ||
| 122 | + mock_forward.assert_called_once_with(mock_model, mock_inputs) | ||
| 123 | + | ||
| 124 | + | ||
| 125 | + def test_generate_model_forward_raises_invalid_model_error_when_helper_fails(self, mock_forward): | ||
| 126 | + mock_forward.side_effect = Exception("forward failed") | ||
| 127 | + adapter = self._make_adapter() | ||
| 128 | + | ||
| 129 | + with self.assertRaises(InvalidModelError): | ||
| 130 | + list(adapter.generate_model_forward(MagicMock(), MagicMock())) | ||
| 131 | + | ||
| 132 | + def test_enable_kv_cache_delegates_to_helper_when_need_kv_cache_true(self): | ||
| 133 | + adapter = self._make_adapter() | ||
| 134 | + mock_model = MagicMock() | ||
| 135 | + adapter._enable_kv_cache = MagicMock(return_value=None) | ||
| 136 | + | ||
| 137 | + adapter.enable_kv_cache(model=mock_model, need_kv_cache=True) | ||
| 138 | + | ||
| 139 | + adapter._enable_kv_cache.assert_called_once_with(mock_model, True) | ||
| 140 | + | ||
| 141 | + def test_enable_kv_cache_delegates_to_helper_when_need_kv_cache_false(self): | ||
| 142 | + adapter = self._make_adapter() | ||
| 143 | + mock_model = MagicMock() | ||
| 144 | + adapter._enable_kv_cache = MagicMock(return_value=None) | ||
| 145 | + | ||
| 146 | + adapter.enable_kv_cache(model=mock_model, need_kv_cache=False) | ||
| 147 | + | ||
| 148 | + adapter._enable_kv_cache.assert_called_once_with(mock_model, False) | ||
| 149 | + | ||
| 150 | + def test_enable_kv_cache_raises_invalid_model_error_when_helper_fails(self): | ||
| 151 | + adapter = self._make_adapter() | ||
| 152 | + adapter._enable_kv_cache = MagicMock(side_effect=Exception("Enable KV cache failed")) | ||
| 153 | + | ||
| 154 | + with self.assertRaises(InvalidModelError): | ||
| 155 | + adapter.enable_kv_cache(model=MagicMock(), need_kv_cache=True) | ||
【review】序号未更新,和下方序号重复。需要更新下方及之后的序号