mirror of
https://github.com/datawhalechina/self-llm.git
synced 2026-08-28 15:11:50 +08:00
docs: add Qwen3.5 tutorials
This commit is contained in:
@@ -86,6 +86,7 @@
|
||||
<tr>
|
||||
<td valign="top" width="25%">
|
||||
• <a href="./support_model.md#minicpm5">MiniCPM5</a><br>
|
||||
• <a href="./support_model.md#qwen35">Qwen3.5</a><br>
|
||||
• <a href="./support_model.md#kimi-k25">Kimi-K2.5</a><br>
|
||||
• <a href="./support_model.md#step-35-flash">Step-3.5-Flash</a><br>
|
||||
• <a href="./support_model.md#glm-47-flash">GLM-4.7-Flash</a><br>
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
<tr>
|
||||
<td valign="top" width="25%">
|
||||
• <a href="./support_model.md#minicpm5">MiniCPM5</a><br>
|
||||
• <a href="./support_model.md#qwen35">Qwen3.5</a><br>
|
||||
• <a href="./support_model.md#kimi-k25">Kimi-K2.5</a><br>
|
||||
• <a href="./support_model.md#step-35-flash">Step-3.5-Flash</a><br>
|
||||
• <a href="./support_model.md#glm-47-flash">GLM-4.7-Flash</a><br>
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
# 01-Qwen3.5-4B vLLM 部署调用
|
||||
|
||||
## vLLM 简介
|
||||
|
||||
`vLLM` 框架是一个高效的大语言模型**推理和部署服务系统**,具备以下特性:
|
||||
|
||||
- **高效的内存管理**:通过 `PagedAttention` 算法,`vLLM` 实现了对 `KV` 缓存的高效管理,减少了内存浪费,优化了模型的运行效率。
|
||||
- **高吞吐量**:`vLLM` 支持异步处理和连续批处理请求,显著提高了模型推理的吞吐量,加速了文本生成和处理速度。
|
||||
- **易用性**:`vLLM` 与 `HuggingFace` 模型无缝集成,支持多种流行的大型语言模型,简化了模型部署和推理的过程。兼容 `OpenAI` 的 `API` 服务器。
|
||||
- **多模态**:`vLLM` 同时支持文本与多模态(图像/视频)推理,`Qwen3.5-4B` 作为统一视觉-语言底座,可在 `vLLM` 中直接提供图文服务。
|
||||
|
||||
> `Qwen3.5` 官方明确说明模型权重同时兼容 `Hugging Face Transformers`、`vLLM`、`SGLang`、`KTransformers` 等推理框架。本教程使用 `vLLM` 进行部署,**文中的启动日志与接口返回均为实测真实输出**。
|
||||
|
||||
## 关于 Qwen3.5-4B 架构
|
||||
|
||||
`Qwen3.5-4B` 采用**高效的混合架构**:将 **Gated Delta Network(门控增量网络,一种线性注意力)** 与传统全注意力(Full Attention)层交错堆叠(每 4 层中 3 层线性注意力 + 1 层全注意力),在保持强大能力的同时大幅降低长序列的推理显存与延迟。同时它默认开启**思维链(Thinking)**模式,在最终回答前生成 `<think> ... </think>` 推理过程。
|
||||
|
||||
> 由于该架构较新,请确保安装较新版本的 `vLLM`(本教程实测 `vLLM 0.23.0`)与 `transformers>=4.57`,以保证对 `qwen3_5` 模型类型的支持。vLLM 启动时会自动识别并选用 `Triton/FLA GDN` 线性注意力算子。
|
||||
|
||||
## 环境准备
|
||||
|
||||
本文实测基础环境如下:
|
||||
|
||||
```
|
||||
----------------
|
||||
ubuntu 22.04
|
||||
python 3.12
|
||||
NVIDIA 驱动 580.105.08(支持 CUDA 13.0)
|
||||
GPU: RTX 4090 D (24G)
|
||||
torch 2.11.0+cu128
|
||||
vllm 0.23.0
|
||||
----------------
|
||||
```
|
||||
|
||||
> 本文默认学习者已配置好 `Pytorch (cuda)` 环境,如未配置请先自行安装。
|
||||
|
||||
首先 `pip` 换源加速下载并安装依赖包:
|
||||
|
||||
```bash
|
||||
python -m pip install --upgrade pip
|
||||
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
|
||||
pip install modelscope
|
||||
pip install "transformers>=4.57"
|
||||
pip install openai
|
||||
```
|
||||
|
||||
安装 `vLLM`。`vLLM 0.23.0` 是基于 **CUDA 13** 编译的版本,其编译扩展 `vllm._C` 依赖 `libcudart.so.13`;而默认从镜像源安装的 `torch` 是 CPU 版本,无法使用 GPU。因此需要先从 PyTorch 官方源安装带 CUDA 的 `torch 2.11.0`:
|
||||
|
||||
```bash
|
||||
# 先装带 CUDA 的 torch(vLLM 0.23 需要 torch==2.11.0)
|
||||
pip install torch==2.11.0 torchvision==0.26.0 torchaudio==2.11.0 \
|
||||
--index-url https://download.pytorch.org/whl/cu128
|
||||
|
||||
# 再装 vLLM(会自动拉取 flashinfer、cutlass-dsl、humming-kernels 等依赖)
|
||||
pip install "vllm==0.23.0"
|
||||
```
|
||||
|
||||
> **重要:设置 CUDA 库搜索路径**。由于 `vLLM 0.23.0` 是 CUDA 13 构建,而上面装的是 `torch+cu128`,启动时会报 `ImportError: libcudart.so.13: cannot open shared object file`。`vLLM` 的依赖已经把 CUDA 13 运行库装到了 `site-packages/nvidia/` 下,只需把这些路径加入 `LD_LIBRARY_PATH` 即可:
|
||||
>
|
||||
> ```bash
|
||||
> # 写入 ~/.bashrc 永久生效
|
||||
> NVLIB=$(find /root/miniconda3/lib/python3.12/site-packages/nvidia -type d -name lib | tr '\n' ':')
|
||||
> echo "export LD_LIBRARY_PATH=\${NVLIB}\${LD_LIBRARY_PATH:-}" >> ~/.bashrc
|
||||
> source ~/.bashrc
|
||||
>
|
||||
> # 验证 vLLM 可正常导入
|
||||
> python -c "import vllm; print(vllm.__version__)"
|
||||
> ```
|
||||
>
|
||||
> 若你的显卡驱动支持 CUDA 13(如本文 580.105.08),也可以直接安装 `torch+cu130`(`--index-url https://download.pytorch.org/whl/cu130`),与 `vLLM 0.23.0` 完全匹配,则无需上述 `LD_LIBRARY_PATH` 设置。
|
||||
|
||||
> 考虑到部分同学配置环境可能会遇到一些问题,我们在 AutoDL 平台准备了 Qwen3 的环境镜像,点击下方链接并直接创建 Autodl 示例即可。
|
||||
> ***https://www.codewithgpu.com/i/datawhalechina/self-llm/Qwen3***
|
||||
|
||||
## 模型下载
|
||||
|
||||
使用 modelscope 中的 `snapshot_download` 函数下载模型,第一个参数为模型名称,参数 `cache_dir` 为模型的下载路径。
|
||||
|
||||
新建 `model_download.py` 文件并在其中输入以下内容,粘贴代码后记得保存文件。
|
||||
|
||||
```python
|
||||
# model_download.py
|
||||
from modelscope import snapshot_download
|
||||
|
||||
model_dir = snapshot_download('Qwen/Qwen3.5-4B', cache_dir='/root/autodl-tmp')
|
||||
print(f"模型下载完成,保存路径为:{model_dir}")
|
||||
```
|
||||
|
||||
然后在终端中输入 `python model_download.py` 执行下载,这里需要耐心等待一段时间直到模型下载完成。
|
||||
|
||||
> 注意:记得修改 `cache_dir` 为你的模型下载路径哦~
|
||||
|
||||
## 创建兼容 OpenAI API 接口的服务器
|
||||
|
||||
`Qwen3.5-4B` 兼容 `OpenAI API` 协议,我们可以直接使用 `vLLM` 创建 `OpenAI API` 服务器。默认会在 `http://localhost:8000` 启动服务器,实现模型列表、`completions` 和 `chat completions` 端口。
|
||||
|
||||
常用启动参数:
|
||||
|
||||
- `--host` / `--port`:指定地址与端口
|
||||
- `--model`:模型路径
|
||||
- `--served-model-name`:服务对外的模型名称
|
||||
- `--max-model-len`:模型最大上下文长度(4B 模型在 24G 显存上建议 `4096`,显存富余可调大)
|
||||
- `--gpu-memory-utilization`:GPU 显存占用比例(默认 0.9,显存紧张可调低)
|
||||
- `--trust-remote-code`:信任远程代码
|
||||
|
||||
复制以下命令到终端,即可启动 Qwen3.5-4B 的 API 服务:
|
||||
|
||||
```bash
|
||||
vllm serve /root/autodl-tmp/Qwen/Qwen3.5-4B \
|
||||
--served-model-name Qwen3.5-4B \
|
||||
--max-model-len 4096 \
|
||||
--gpu-memory-utilization 0.9 \
|
||||
--trust-remote-code \
|
||||
--host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
启动过程中会打印大量日志,关键的实测启动日志如下(vLLM 识别出 `Qwen3_5ForConditionalGeneration` 架构,并为线性注意力层选用 GDN 算子):
|
||||
|
||||

|
||||
|
||||
对应的关键日志行(已去除颜色码):
|
||||
|
||||
```bash
|
||||
(APIServer) INFO [model.py:611] Resolved architecture: Qwen3_5ForConditionalGeneration
|
||||
(APIServer) INFO [model.py:1745] Using max model len 4096
|
||||
(EngineCore) INFO [core.py:113] Initializing a V1 LLM engine (v0.23.0) ...
|
||||
(EngineCore) INFO [topk_topp_sampler.py:55] Using FlashInfer for top-p & top-k sampling.
|
||||
(EngineCore) INFO [gpu_model_runner.py:5092] Starting to load model /root/autodl-tmp/Qwen/Qwen3.5-4B...
|
||||
(EngineCore) INFO [qwen_gdn_linear_attn.py:228] Using Triton/FLA GDN prefill kernel (requested=auto, head_k_dim=128).
|
||||
(EngineCore) INFO [cuda.py:378] Using FLASH_ATTN attention backend out of potential backends: ['FLASH_ATTN', 'FLASHINFER', 'TRITON_ATTN', 'FLEX_ATTENTION'].
|
||||
(EngineCore) INFO [default_loader.py:397] Loading weights took 2.35 seconds
|
||||
(EngineCore) INFO [gpu_model_runner.py:5187] Model loading took 8.61 GiB memory and 3.16 seconds
|
||||
(EngineCore) INFO [monitor.py:53] torch.compile took 48.51 s in total
|
||||
(EngineCore) INFO [gpu_worker.py:480] Available KV cache memory: 10.21 GiB
|
||||
(EngineCore) INFO [kv_cache_utils.py:1744] GPU KV cache size: 235,706 tokens
|
||||
(EngineCore) INFO [core.py:306] init engine (profile, create kv cache, warmup model) took 286.39 s (compilation: 48.51 s)
|
||||
(APIServer) INFO: Application startup complete.
|
||||
```
|
||||
|
||||
> 说明:首次启动会触发 `torch.compile` 编译与 profiling warmup(实测初始化耗时约 286s),编译结果会缓存到 `~/.cache/vllm/`,后续启动会明显加快。出现 `Application startup complete.` 即说明服务成功启动。
|
||||
|
||||
- 通过 `curl` 命令查看当前的模型列表
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/v1/models
|
||||
```
|
||||
|
||||
实测返回值如下所示:
|
||||
|
||||
```json
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "Qwen3.5-4B",
|
||||
"object": "model",
|
||||
"created": 1781610820,
|
||||
"owned_by": "vllm",
|
||||
"root": "/root/autodl-tmp/Qwen/Qwen3.5-4B",
|
||||
"parent": null,
|
||||
"max_model_len": 4096
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 思考模式与非思考模式
|
||||
|
||||
`Qwen3.5` 默认开启思考模式。在 `chat/completions` 接口中,可通过 `chat_template_kwargs.enable_thinking` 按**请求**级别控制:
|
||||
|
||||
- **默认(思考模式)**:模型先输出 `<think> ... </think>` 推理过程,再给出最终答案
|
||||
- **非思考模式**:传入 `enable_thinking=false`,模型不输出 `<think>` 标签
|
||||
|
||||
### 用 curl 测试 Chat Completions(思考模式)
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "Qwen3.5-4B",
|
||||
"messages": [
|
||||
{"role": "user", "content": "5的阶乘是多少?"}
|
||||
],
|
||||
"temperature": 1.0,
|
||||
"top_p": 0.95,
|
||||
"max_tokens": 768,
|
||||
"extra_body": {"chat_template_kwargs": {"enable_thinking": true}}
|
||||
}'
|
||||
```
|
||||
|
||||
实测返回值如下所示(`content` 中先是 `<think> ... </think>` 思考过程,其后是最终答案,`finish_reason` 为 `stop` 表示正常结束):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "chatcmpl-984a75743a984720",
|
||||
"object": "chat.completion",
|
||||
"model": "Qwen3.5-4B",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "Here's a thinking process that leads to the answer:\n\n1. **Analyze the Request:** 用户问的是 5 的阶乘 ...\n2. **Define Factorial:** n! = n × (n-1) × ... × 1\n3. **Calculate 5!:** 5 × 4 = 20, 20 × 3 = 60, 60 × 2 = 120, 120 × 1 = 120\n...\n</think>\n\n5 的阶乘(记作 5!)是 **120**。\n\n计算过程如下:\n$$5! = 5 \\times 4 \\times 3 \\times 2 \\times 1 = 120$$"
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 16,
|
||||
"completion_tokens": 590,
|
||||
"total_tokens": 606
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
可以看到,开启思考模式时,模型先在 `<think> ... </think>` 中给出推理过程,再输出最终答案 `5 的阶乘是 120`。
|
||||
|
||||
### 用 Python 脚本请求(非思考模式)
|
||||
|
||||
```python
|
||||
# vllm_openai_chat_completions.py
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
api_key="sk-xxx", # 随便填写,只是为了通过接口参数校验
|
||||
base_url="http://localhost:8000/v1",
|
||||
)
|
||||
|
||||
# 非思考模式:传入 enable_thinking=false
|
||||
chat_outputs = client.chat.completions.create(
|
||||
model="Qwen3.5-4B",
|
||||
messages=[{"role": "user", "content": "用一句话介绍深度学习。"}],
|
||||
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
|
||||
)
|
||||
print(chat_outputs.choices[0].message.content)
|
||||
```
|
||||
|
||||
```bash
|
||||
python vllm_openai_chat_completions.py
|
||||
```
|
||||
|
||||
> 实测发现:`Qwen3.5-4B` 即使在非思考模式下,也可能在 `content` 开头先输出一段简短的「思考过程」文字(不再用 `<think>` 标签包裹),随后才给出最终回答,且小模型容易在 `max_tokens` 较小时被截断(`finish_reason: length`)。如需直接、简短的回答,可适当调大 `max_tokens` 或换用更大的型号。
|
||||
|
||||
### 运行时日志
|
||||
|
||||
在请求处理过程中,`API` 后端会持续打印对应的日志与统计信息(吞吐、显存占用等),便于观测服务状态。实测的运行时日志如下:
|
||||
|
||||

|
||||
|
||||
```bash
|
||||
(EngineCore) INFO [core.py:306] init engine (profile, create kv cache, warmup model) took 286.39 s (compilation: 48.51 s)
|
||||
(APIServer) INFO [base.py:227] Multi-modal warmup completed in 25.118s
|
||||
(APIServer) INFO: Application startup complete.
|
||||
(APIServer) INFO: 127.0.0.1:34042 - "POST /v1/chat/completions HTTP/1.1" 200 OK
|
||||
(APIServer) INFO [loggers.py:271] Engine 000: Avg prompt throughput: 0.9 tokens/s, Avg generation throughput: 6.3 tokens/s, Running: 1 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.6%
|
||||
(APIServer) INFO: 127.0.0.1:41708 - "POST /v1/chat/completions HTTP/1.1" 200 OK
|
||||
```
|
||||
|
||||
### 多模态(图文)调用示例
|
||||
|
||||
由于 `Qwen3.5-4B` 自带视觉编码器,`vLLM` 部署后也支持图像输入:
|
||||
|
||||
```python
|
||||
# vllm_multimodal.py
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(api_key="sk-xxx", base_url="http://localhost:8000/v1")
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="Qwen3.5-4B",
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}},
|
||||
{"type": "text", "text": "请描述这张图片的内容。"},
|
||||
],
|
||||
}],
|
||||
)
|
||||
print(response.choices[0].message.content)
|
||||
```
|
||||
|
||||
> 提示:多模态请求需要 vLLM 加载模型的视觉部分。若只需文本服务、希望进一步节省显存,可使用 `--limit-mm-per-prompt '{"image": 0}'` 关闭图像输入。
|
||||
|
||||
## 离线推理(可选)
|
||||
|
||||
除启动服务外,也可以直接用 `vLLM` 的 `LLM` 引擎做离线推理:
|
||||
|
||||
```python
|
||||
# vllm_model.py
|
||||
from vllm import LLM, SamplingParams
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
model = '/root/autodl-tmp/Qwen/Qwen3.5-4B'
|
||||
tokenizer = AutoTokenizer.from_pretrained(model, use_fast=False)
|
||||
|
||||
messages = [{"role": "user", "content": "你是谁?"}]
|
||||
text = tokenizer.apply_chat_template(
|
||||
messages, tokenize=False, add_generation_prompt=True,
|
||||
enable_thinking=False, # 关闭思考模式
|
||||
)
|
||||
|
||||
# 官方推荐非思考模式参数:temperature=0.7, top_p=0.8, top_k=20, presence_penalty=1.5
|
||||
sampling_params = SamplingParams(temperature=0.7, top_p=0.8, top_k=20,
|
||||
max_tokens=512, presence_penalty=1.5)
|
||||
llm = LLM(model=model, max_model_len=4096, trust_remote_code=True)
|
||||
outputs = llm.generate([text], sampling_params)
|
||||
print(outputs[0].outputs[0].text)
|
||||
```
|
||||
|
||||
> **采样参数建议(来自 Qwen 官方)**:
|
||||
> - 思考模式(通用任务):`temperature=1.0, top_p=0.95, top_k=20, presence_penalty=1.5`
|
||||
> - 思考模式(精确编码):`temperature=0.6, top_p=0.95, top_k=20, presence_penalty=0.0`
|
||||
> - 非思考模式(通用任务):`temperature=0.7, top_p=0.8, top_k=20, presence_penalty=1.5`
|
||||
>
|
||||
> 注意:不同推理框架对采样参数的支持情况略有差异,请以实际为准。
|
||||
@@ -0,0 +1,329 @@
|
||||
# 02-Qwen3.5-4B SGLang 部署调用
|
||||
|
||||
## SGLang 简介
|
||||
|
||||
`SGLang` 是一款专为大语言模型(LLM)/多模态模型设计的高性能推理与服务框架。它在提升大模型在复杂任务编排、长上下文处理及高并发请求下的执行效率方面表现出色,是连接底层硬件算力与上层 AI 应用的高效桥梁。对开发者而言,SGLang 极大简化了部署流程:
|
||||
|
||||
- **后端一键启动**:无需复杂的配置文件,一条命令即可完成环境适配与服务发布。
|
||||
- **前端无缝对接**:直接沿用现有的 `OpenAI SDK` 或标准 `HTTP` 调用,无需额外的学习与适配成本。
|
||||
- **高性能**:支持 `RadixAttention`(前缀复用)、连续批处理、CUDA Graph 等加速技术。
|
||||
- **多模态**:原生支持视觉-语言模型,`Qwen3.5-4B` 可在其中提供图文服务。
|
||||
|
||||
> `Qwen3.5` 官方明确说明模型权重同时兼容 `Hugging Face Transformers`、`vLLM`、`SGLang`、`KTransformers` 等推理框架。本教程使用 `SGLang` 进行部署,**文中的启动日志与接口返回均为实测真实输出**。
|
||||
|
||||
## 关于 Qwen3.5-4B 架构
|
||||
|
||||
`Qwen3.5-4B` 采用**高效的混合架构**:将 **Gated Delta Network(门控增量网络,一种线性注意力)** 与传统全注意力(Full Attention)层交错堆叠(每 4 层中 3 层线性注意力 + 1 层全注意力)。SGLang 启动时会自动识别该混合架构,并为其选用 **Triton GDN 算子**(`TritonGDNKernel`),无需手动配置。
|
||||
|
||||
## 环境准备
|
||||
|
||||
本文实测基础环境如下:
|
||||
|
||||
```
|
||||
----------------
|
||||
ubuntu 22.04
|
||||
python 3.12
|
||||
NVIDIA 驱动 580.105.08(支持 CUDA 13.0)
|
||||
GPU: RTX 4090 D (24G, sm89)
|
||||
torch 2.11.0+cu128
|
||||
sglang 0.5.13.post1
|
||||
----------------
|
||||
```
|
||||
|
||||
> 本文默认学习者已配置好 `Pytorch (cuda)` 环境,如未配置请先自行安装。
|
||||
|
||||
首先 `pip` 换源加速下载并安装依赖包:
|
||||
|
||||
```bash
|
||||
python -m pip install --upgrade pip
|
||||
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
|
||||
pip install modelscope
|
||||
pip install "transformers>=4.57"
|
||||
pip install openai
|
||||
```
|
||||
|
||||
安装 `SGLang`。`SGLang 0.5.13` 依赖 `torch==2.11.0`、`flashinfer==0.6.12` 等较重的栈,建议先装好带 CUDA 的 `torch`,再装 `sglang`:
|
||||
|
||||
```bash
|
||||
# 1. 先装带 CUDA 的 torch(sglang 需要 torch==2.11.0)
|
||||
pip install torch==2.11.0 torchvision==0.26.0 torchaudio==2.11.0 \
|
||||
--index-url https://download.pytorch.org/whl/cu128
|
||||
|
||||
# 2. 安装 sglang(会自动拉取 flashinfer、cutlass-dsl、humming-kernels 等依赖)
|
||||
pip install "sglang[all]==0.5.13.post1"
|
||||
```
|
||||
|
||||
> **关键:为 RTX 4090(sm89)安装匹配的 sglang-kernel**。默认从 PyPI 装的 `sglang-kernel` 是 CUDA 13 / sm90+(Hopper/Blackwell)构建,在 4090(sm89)上会报 `Could not load any common_ops library! Expected variant: SM89`。需要从 SGLang 官方的 `cu129` 索引安装 sm89 兼容版本:
|
||||
>
|
||||
> ```bash
|
||||
> pip install sglang-kernel==0.4.3 --index-url https://docs.sglang.ai/whl/cu129/
|
||||
> ```
|
||||
>
|
||||
> 若上述官方源下载较慢(GitHub releases),可借助代理镜像下载对应 wheel 后本地安装。
|
||||
|
||||
> **设置 CUDA 库搜索路径**。`SGLang` 的部分算子依赖 CUDA 运行库,需要把 `nvidia` 与 `torch` 的库目录加入 `LD_LIBRARY_PATH`:
|
||||
>
|
||||
> ```bash
|
||||
> NVLIB=$(find /root/miniconda3/lib/python3.12/site-packages/nvidia -type d -name lib | tr '\n' ':')
|
||||
> TLIB=/root/miniconda3/lib/python3.12/site-packages/torch/lib
|
||||
> echo "export LD_LIBRARY_PATH=\${NVLIB}\${TLIB}:\${LD_LIBRARY_PATH:-}" >> ~/.bashrc
|
||||
> source ~/.bashrc
|
||||
> ```
|
||||
|
||||
> 考虑到部分同学配置环境可能会遇到一些问题,我们在 AutoDL 平台准备了运行的环境镜像,点击下方链接并直接创建 Autodl 示例即可。 https://www.autodl.art/i/datawhalechina/self-llm/Qwen3
|
||||
|
||||
## 模型下载
|
||||
|
||||
使用 modelscope 中的 `snapshot_download` 函数下载模型,第一个参数为模型名称,参数 `cache_dir` 为模型的下载路径。
|
||||
|
||||
新建 `model_download.py` 文件并在其中输入以下内容,粘贴代码后记得保存文件。
|
||||
|
||||
```python
|
||||
# model_download.py
|
||||
from modelscope import snapshot_download
|
||||
|
||||
model_dir = snapshot_download('Qwen/Qwen3.5-4B', cache_dir='/root/autodl-tmp')
|
||||
print(f"模型下载完成,保存路径为:{model_dir}")
|
||||
```
|
||||
|
||||
然后在终端中输入 `python model_download.py` 执行下载,这里需要耐心等待一段时间直到模型下载完成。
|
||||
|
||||
> 注意:记得修改 `cache_dir` 为你的模型下载路径哦~
|
||||
|
||||
## 启动 SGLang 服务
|
||||
|
||||
`Qwen3.5-4B` 为 4B 模型,单张 24G 显卡(如 RTX 4090)即可部署,无需张量并行。
|
||||
|
||||
### 命令行直接启动
|
||||
|
||||
```bash
|
||||
python3 -m sglang.launch_server \
|
||||
--model-path /root/autodl-tmp/Qwen/Qwen3.5-4B \
|
||||
--served-model-name Qwen3.5-4B \
|
||||
--host 0.0.0.0 \
|
||||
--port 8000 \
|
||||
--mem-fraction-static 0.85 \
|
||||
--context-length 4096 \
|
||||
--trust-remote-code
|
||||
```
|
||||
|
||||
> 新版 SGLang 推荐使用 `sglang serve ...` 入口(与 `python -m sglang.launch_server` 等价)。
|
||||
|
||||
常用参数说明:
|
||||
|
||||
- `--model-path`:模型路径
|
||||
- `--served-model-name`:服务对外的模型名称
|
||||
- `--mem-fraction-static`:静态显存占用比例(默认 0.88,显存紧张可调低,如 0.8)
|
||||
- `--context-length`:最大上下文长度(4B 模型在 24G 显存上建议 `4096`,显存富余可调大)
|
||||
- `--tp-size`:张量并行数,单卡部署无需设置;多卡可设为 GPU 数量
|
||||
- `--trust-remote-code`:信任远程代码
|
||||
|
||||
启动过程中会打印大量日志,关键的实测启动日志如下(SGLang 识别出 `Qwen3_5ForConditionalGeneration`,并为混合架构的线性注意力层选用 Triton GDN 算子):
|
||||
|
||||

|
||||
|
||||
对应的关键日志行(已去除颜色码):
|
||||
|
||||
```bash
|
||||
[20:52:05] Load weight end. elapsed=2.57 s, type=Qwen3_5ForConditionalGeneration, avail mem=14.47 GB, mem usage=8.62 GB.
|
||||
[20:52:05] Memory pool end. avail mem=3.40 GB
|
||||
[20:52:05] Linear attention kernel backend: decode=triton, prefill=triton
|
||||
[20:52:05] Using hybrid linear attention backend for hybrid GDN models.
|
||||
[20:52:05] GDN kernel dispatcher: decode=TritonGDNKernel, extend=TritonGDNKernel, verify=TritonGDNKernel packed_decode=True
|
||||
[20:52:05] Capture cuda graph begin. This can take up to several minutes. avail mem=2.98 GB
|
||||
[20:53:03] INFO: Application startup complete.
|
||||
[20:53:40] The server is fired up and ready to roll!
|
||||
```
|
||||
|
||||
> 说明:首次启动会进行 CUDA graph 捕获(约 1 分钟),完成后出现 `The server is fired up and ready to roll!` 即说明服务成功启动。
|
||||
|
||||
### Python 启动脚本
|
||||
|
||||
也可用脚本启动,便于固定参数。新建 `start_server.py`:
|
||||
|
||||
```python
|
||||
# start_server.py
|
||||
from sglang.utils import launch_server_cmd, wait_for_server
|
||||
|
||||
cmd = (
|
||||
"python3 -m sglang.launch_server "
|
||||
"--model-path /root/autodl-tmp/Qwen/Qwen3.5-4B "
|
||||
"--served-model-name Qwen3.5-4B "
|
||||
"--host 0.0.0.0 --port 8000 "
|
||||
"--mem-fraction-static 0.85 "
|
||||
"--context-length 4096 "
|
||||
"--trust-remote-code"
|
||||
)
|
||||
|
||||
server_process, port = launch_server_cmd(cmd, port=8000)
|
||||
wait_for_server(f"http://127.0.0.1:{port}")
|
||||
print(f"SGLang Server started: http://127.0.0.1:{port}")
|
||||
```
|
||||
|
||||
```bash
|
||||
python start_server.py
|
||||
```
|
||||
|
||||
## 调用示例
|
||||
|
||||
以下示例均使用 `OpenAI` 官方 Python SDK 调用 SGLang 的 OpenAI 兼容接口。
|
||||
|
||||
### 查看模型列表
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/v1/models
|
||||
```
|
||||
|
||||
实测返回值如下所示(`owned_by` 为 `sglang`):
|
||||
|
||||
```json
|
||||
{
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "Qwen3.5-4B",
|
||||
"object": "model",
|
||||
"created": 1781614385,
|
||||
"owned_by": "sglang",
|
||||
"root": "Qwen3.5-4B",
|
||||
"parent": null,
|
||||
"max_model_len": 4096
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 聊天对话(思考模式)
|
||||
|
||||
`Qwen3.5` 默认开启思考模式。在请求中通过 `chat_template_kwargs.enable_thinking` 控制:默认为 `True`(思考),设为 `False` 则不输出 `<think>` 标签。
|
||||
|
||||
```python
|
||||
# test_chat.py
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1")
|
||||
|
||||
# 思考模式(默认):会先输出 <think> ... </think> 推理过程,再给出答案
|
||||
response = client.chat.completions.create(
|
||||
model="Qwen3.5-4B",
|
||||
messages=[{"role": "user", "content": "5的阶乘是多少?"}],
|
||||
temperature=1.0,
|
||||
top_p=0.95,
|
||||
max_tokens=768,
|
||||
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
|
||||
)
|
||||
print(response.choices[0].message.content)
|
||||
```
|
||||
|
||||
```bash
|
||||
python test_chat.py
|
||||
```
|
||||
|
||||
实测输出包含 `<think> ... </think>` 思考过程与最终答案:
|
||||
|
||||
```
|
||||
Here's a thinking process that leads to the answer:
|
||||
|
||||
1. **Analyze the Request:** 用户问的是 5 的阶乘 ...
|
||||
2. **Define Factorial:** n! = n × (n-1) × ... × 1
|
||||
3. **Calculate 5!:** 5 × 4 = 20, 20 × 3 = 60, 60 × 2 = 120, 120 × 1 = 120
|
||||
...
|
||||
</think>
|
||||
|
||||
5 的阶乘(记作 5!)是 **120**。
|
||||
|
||||
计算过程如下:
|
||||
$$5! = 5 \times 4 \times 3 \times 2 \times 1 = 120$$
|
||||
```
|
||||
|
||||
### 非思考模式
|
||||
|
||||
```python
|
||||
response = client.chat.completions.create(
|
||||
model="Qwen3.5-4B",
|
||||
messages=[{"role": "user", "content": "用一句话介绍深度学习。"}],
|
||||
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
|
||||
)
|
||||
print(response.choices[0].message.content)
|
||||
```
|
||||
|
||||
> 实测发现:`Qwen3.5-4B` 即使在非思考模式下,也可能在 `content` 开头先输出一段简短的「思考过程」文字(不再用 `<think>` 标签包裹),随后才给出最终回答,且 `max_tokens` 较小时容易被截断(`finish_reason: length`)。如需直接、简短的回答,可适当调大 `max_tokens` 或换用更大的型号。
|
||||
|
||||
### 运行时日志
|
||||
|
||||
在请求处理过程中,SGLang 后端会持续打印解码批次的统计信息(运行请求数、token 用量、生成吞吐等)。实测运行时日志如下:
|
||||
|
||||

|
||||
|
||||
```bash
|
||||
[20:53:40] INFO: 127.0.0.1:34422 - "POST /v1/chat/completions HTTP/1.1" 200 OK
|
||||
[20:53:40] The server is fired up and ready to roll!
|
||||
[20:53:41] Decode batch, #running-req: 1, #full token: 96, full token usage: 0.00, mamba num: 2, mamba usage: 0.02, cuda graph: True, gen throughput (token/s): 87.62, #queue-req: 0
|
||||
[20:53:43] Decode batch, #running-req: 1, #full token: 256, ..., gen throughput (token/s): 86.93, #queue-req: 0
|
||||
[20:53:46] INFO: 127.0.0.1:34442 - "POST /v1/chat/completions HTTP/1.1" 200 OK
|
||||
```
|
||||
|
||||
> 日志中的 `mamba num: 2` 反映了混合架构中线性注意力(GDN/Mamba 式)状态的使用情况;`gen throughput (token/s): 87.62` 为实测单请求解码吞吐。
|
||||
|
||||
### 流式输出(Streaming)
|
||||
|
||||
```python
|
||||
# test_streaming.py
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1")
|
||||
|
||||
stream = client.chat.completions.create(
|
||||
model="Qwen3.5-4B",
|
||||
messages=[{"role": "user", "content": "请用三句话介绍量子计算。"}],
|
||||
stream=True,
|
||||
temperature=1.0,
|
||||
top_p=0.95,
|
||||
max_tokens=2048,
|
||||
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
|
||||
)
|
||||
|
||||
for chunk in stream:
|
||||
delta = chunk.choices[0].delta
|
||||
if delta and delta.content:
|
||||
print(delta.content, end="", flush=True)
|
||||
```
|
||||
|
||||
```bash
|
||||
python test_streaming.py
|
||||
```
|
||||
|
||||
### 多模态(图文)调用示例
|
||||
|
||||
`Qwen3.5-4B` 自带视觉编码器,SGLang 部署后可直接接收图像输入:
|
||||
|
||||
```python
|
||||
# test_multimodal.py
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1")
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="Qwen3.5-4B",
|
||||
messages=[{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}},
|
||||
{"type": "text", "text": "请描述这张图片的内容。"},
|
||||
],
|
||||
}],
|
||||
)
|
||||
print(response.choices[0].message.content)
|
||||
```
|
||||
|
||||
> 提示:若只需文本服务、希望进一步节省显存,可在启动时通过 `--limit-mm-per-prompt` 关闭图像输入。
|
||||
|
||||
## 小结
|
||||
|
||||
| 推理框架 | 适用场景 | 特点 |
|
||||
| --- | --- | --- |
|
||||
| **vLLM** | 高吞吐文本/多模态服务 | PagedAttention、生态成熟、OpenAI 兼容 |
|
||||
| **SGLang** | 复杂编排、长上下文、高并发 | RadixAttention 前缀复用、CUDA Graph、结构化输出 |
|
||||
|
||||
`Qwen3.5-4B` 作为高效的混合架构模型,在 `vLLM` 与 `SGLang` 中均可一键部署。两者都能自动识别其 GDN 线性注意力层(vLLM 用 `Triton/FLA GDN`,SGLang 用 `TritonGDNKernel`),并结合其默认的思维链能力与多模态底座,支撑对话、推理与图文理解等多种应用场景。
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,399 @@
|
||||
# Qwen3.5-4B-LoRA 及 SwanLab 可视化记录
|
||||
|
||||
> 本教程配套 notebook:[03-Qwen3.5-4B-LoRA.ipynb](./03-Qwen3.5-4B-LoRA.ipynb)
|
||||
|
||||
## Qwen3.5-4B 简介
|
||||
|
||||
`Qwen3.5-4B` 是通义千问团队推出的新一代基础模型,具备以下核心特点:
|
||||
|
||||
- **高效的混合架构**:将 **Gated Delta Network(门控增量网络,一种线性注意力)** 与传统全注意力(Full Attention)层交错堆叠——每 4 层中前 3 层为线性注意力、第 4 层为全注意力。线性注意力大幅降低了长序列的推理成本与显存占用。
|
||||
- **统一的多模态底座**:模型内置视觉编码器(Vision Encoder),在文本能力与 Qwen3 持平的同时,兼顾视觉理解。
|
||||
- **思维链(Thinking)能力**:默认开启思考模式,在最终回答前生成 `<think> ... </think>` 包裹的推理过程;也可通过 `enable_thinking=False` 关闭,直接给出回答。
|
||||
- **长上下文**:支持最长 262144(256K)上下文。
|
||||
|
||||
由于其架构特殊性,加载与微调 Qwen3.5-4B 需要 `transformers>=4.57`。本教程使用官方推荐的纯 `transformers + peft` 方案完成 LoRA 微调,并使用 **SwanLab** 进行训练过程可视化。
|
||||
|
||||
## 环境配置
|
||||
|
||||
```bash
|
||||
# 换清华镜像源
|
||||
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
|
||||
# 核心依赖(Qwen3.5 需要 transformers>=4.57)
|
||||
pip install "transformers>=4.57"
|
||||
pip install accelerate datasets peft swanlab modelscope
|
||||
```
|
||||
|
||||
### 强烈建议:为线性注意力(Gated Delta Network)安装加速算子
|
||||
|
||||
Qwen3.5-4B 的 32 层中有 24 层是 GDN 线性注意力。**不装加速算子时这些层会回退到纯 PyTorch 实现,训练明显偏慢**(实测 3729 条数据跑 3 个 epoch 约 80 分钟)。装上 `flash-linear-attention`(fla)后可走 Triton/tilelang 加速路径,显著提速。
|
||||
|
||||
```bash
|
||||
# 1) fla —— Triton 实现,直接 pip 安装即可,无需编译
|
||||
pip install flash-linear-attention
|
||||
|
||||
# 2) causal-conv1d —— 线性注意力里的 conv1d 算子,需要 nvcc 编译(首次几分钟)
|
||||
# 先确认系统有 CUDA toolkit(nvcc),AutoDL 上一般在 /usr/local/cuda-12.8
|
||||
export CUDA_HOME=/usr/local/cuda-12.8 # 按你的实际 cuda 路径修改
|
||||
export PATH=$CUDA_HOME/bin:$PATH
|
||||
pip install causal-conv1d
|
||||
```
|
||||
|
||||
> **运行训练前同样要设好 `CUDA_HOME`**(建议写进 `~/.bashrc` 永久生效):
|
||||
> ```bash
|
||||
> echo 'export CUDA_HOME=/usr/local/cuda-12.8' >> ~/.bashrc
|
||||
> echo 'export PATH=$CUDA_HOME/bin:$PATH' >> ~/.bashrc
|
||||
> source ~/.bashrc
|
||||
> ```
|
||||
> 否则 fla 底层的 tilelang 可能调用错误的 nvcc(报 `CUDA compiler and CUDA toolkit headers are incompatible`),导致训练卡住。
|
||||
>
|
||||
> 若你的环境里同时装了 vLLM / SGLang(会带入 CUDA 13 的 nvidia 包),fla 的 tilelang JIT 可能因 cu12/cu13 冲突出错——**建议 LoRA 微调单独用一个干净环境**(只装 torch/transformers/peft/fla 等),不要和 vLLM/SGLang 混装。
|
||||
>
|
||||
> 想进一步缩短训练时间,也可把 `num_train_epochs` 从 3 降到 1~2,或先用部分数据跑通。
|
||||
|
||||
> 考虑到部分同学配置环境可能会遇到一些问题,我们在 AutoDL 平台准备了 Qwen3 的环境镜像,点击下方链接并直接创建 Autodl 示例即可。
|
||||
> ***https://www.codewithgpu.com/i/datawhalechina/self-llm/Qwen3***
|
||||
|
||||
## 模型下载
|
||||
|
||||
使用 modelscope 中的 `snapshot_download` 函数下载模型,第一个参数为模型名称,参数 `cache_dir` 为模型的下载路径。
|
||||
|
||||
新建 `model_download.py` 文件并在其中输入以下内容,粘贴代码后记得保存文件。
|
||||
|
||||
```python
|
||||
# model_download.py
|
||||
from modelscope import snapshot_download
|
||||
|
||||
model_dir = snapshot_download('Qwen/Qwen3.5-4B', cache_dir='/root/autodl-tmp')
|
||||
print(f"模型下载完成,保存路径为:{model_dir}")
|
||||
```
|
||||
|
||||
然后在终端中输入 `python model_download.py` 执行下载,这里需要耐心等待一段时间直到模型下载完成。
|
||||
|
||||
> 注意:记得修改 `cache_dir` 为你的模型下载路径哦~
|
||||
|
||||
## 数据集构建
|
||||
|
||||
对大语言模型进行 `supervised-finetuning`(`sft`,有监督微调)的数据格式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"instruction": "回答以下用户问题,仅输出答案。",
|
||||
"input": "1+1等于几?",
|
||||
"output": "2"
|
||||
}
|
||||
```
|
||||
|
||||
其中,`instruction` 是用户指令,告知模型其需要完成的任务;`input` 是用户输入,是完成用户指令所必须的输入内容;`output` 是模型应该给出的输出。
|
||||
|
||||
有监督微调的目标是让模型具备理解并遵循用户指令的能力。因此,在构建数据集时,我们应针对目标任务,针对性构建数据。比如,我们的目标是通过大量人物的对话数据微调得到一个能够 role-play 甄嬛对话风格的模型,因此在该场景下的数据示例如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"instruction": "你父亲是谁?",
|
||||
"input": "",
|
||||
"output": "家父是大理寺少卿甄远道。"
|
||||
}
|
||||
```
|
||||
|
||||
本教程使用的甄嬛对话示例微调数据集位于 [/dataset/huanhuan.json](../../dataset/huanhuan.json)(共 3729 条),数据格式为 `instruction / input / output` 的 Alpaca 格式。
|
||||
|
||||
## 数据准备
|
||||
|
||||
`LoRA`(`Low-Rank Adaptation`)训练的数据需要经过格式化、编码之后再输入给模型进行训练的,我们需要先将输入文本编码为 `input_ids`,将输出文本编码为 `labels`。这里我们直接使用 tokenizer 自带的 `apply_chat_template` 方法构造对话模板,避免手写特殊 token 出错。
|
||||
|
||||
### 认识 Qwen3.5 的 Chat Template
|
||||
|
||||
`Qwen3.5` 默认开启思考模式(`enable_thinking=True`),会在回答前生成 `<think>\n ... </think>\n\n` 推理过程;对于「角色扮演」这类任务,我们通常**关闭思考模式**(`enable_thinking=False`),让模型直接给出符合角色设定的回答。
|
||||
|
||||
```python
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
model_id = '/root/autodl-tmp/Qwen/Qwen3.5-4B'
|
||||
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": "现在你要扮演皇帝身边的女人--甄嬛"},
|
||||
{"role": "user", "content": "你父亲是谁?"},
|
||||
{"role": "assistant", "content": "家父是大理寺少卿甄远道。"},
|
||||
]
|
||||
|
||||
# 关闭思考模式
|
||||
text = tokenizer.apply_chat_template(
|
||||
messages, tokenize=False, add_generation_prompt=False, enable_thinking=False
|
||||
)
|
||||
print(text)
|
||||
```
|
||||
|
||||
输出如下:
|
||||
|
||||
```
|
||||
<|im_start|>system
|
||||
现在你要扮演皇帝身边的女人--甄嬛<|im_end|>
|
||||
<|im_start|>user
|
||||
你父亲是谁?<|im_end|>
|
||||
<|im_start|>assistant
|
||||
<think>
|
||||
|
||||
</think>
|
||||
|
||||
家父是大理寺少卿甄远道。<|im_end|>
|
||||
```
|
||||
|
||||
可以看到,关闭思考模式后,模板会自动插入一个空的 `<think>\n\n</think>\n\n` 占位,模型据此直接输出最终回答。
|
||||
|
||||
### 构造处理函数
|
||||
|
||||
我们定义一个预处理函数 `process_func`:对每条样本,分别对「前缀(system + user,带 generation prompt)」和「完整对话」进行 tokenize,再通过**token 级别的切片**精确切出 `response` 部分——这样 `labels` 中只有回答部分参与 loss 计算,system 与 user 部分(以及 `<think></think>` 占位)被置为 `-100` 屏蔽。
|
||||
|
||||
```python
|
||||
def process_func(example):
|
||||
MAX_LENGTH = 1024 # 最大序列长度
|
||||
SYS = "现在你要扮演皇帝身边的女人--甄嬛"
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": SYS},
|
||||
{"role": "user", "content": example["instruction"] + example["input"]},
|
||||
{"role": "assistant", "content": example["output"]},
|
||||
]
|
||||
# 前缀部分(system + user,带 generation prompt),不计算 loss
|
||||
prompt_ids = tokenizer.apply_chat_template(
|
||||
messages[:2], tokenize=True, add_generation_prompt=True,
|
||||
enable_thinking=False, return_dict=False,
|
||||
)
|
||||
# 完整对话(含 assistant 回答)
|
||||
full_ids = tokenizer.apply_chat_template(
|
||||
messages, tokenize=True, add_generation_prompt=False,
|
||||
enable_thinking=False, return_dict=False,
|
||||
)
|
||||
# token 级别切片得到 response
|
||||
response_ids = full_ids[len(prompt_ids):]
|
||||
|
||||
input_ids = prompt_ids + response_ids
|
||||
labels = [-100] * len(prompt_ids) + response_ids
|
||||
attention_mask = [1] * len(input_ids)
|
||||
|
||||
if len(input_ids) > MAX_LENGTH: # 超长截断
|
||||
input_ids = input_ids[:MAX_LENGTH]
|
||||
attention_mask = attention_mask[:MAX_LENGTH]
|
||||
labels = labels[:MAX_LENGTH]
|
||||
|
||||
return {
|
||||
"input_ids": input_ids,
|
||||
"attention_mask": attention_mask,
|
||||
"labels": labels,
|
||||
}
|
||||
```
|
||||
|
||||
> 说明:`transformers` 新版本中 `apply_chat_template(tokenize=True)` 默认返回 `BatchEncoding`,传入 `return_dict=False` 可直接得到 token id 列表,便于做 token 级切片。
|
||||
|
||||
读入数据集并应用处理函数:
|
||||
|
||||
```python
|
||||
import json
|
||||
from datasets import Dataset
|
||||
|
||||
with open("/root/autodl-tmp/huanhuan.json", "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
ds = Dataset.from_list(data)
|
||||
tokenized_id = ds.map(process_func, remove_columns=ds.column_names)
|
||||
tokenized_id
|
||||
```
|
||||
|
||||
可以解码查看处理后的样本,确认格式正确:
|
||||
|
||||
```python
|
||||
# 查看完整输入
|
||||
print(tokenizer.decode(tokenized_id[0]["input_ids"]))
|
||||
# 查看 labels(过滤掉 -100 后即为模型需要学习的回答)
|
||||
print(tokenizer.decode(list(filter(lambda x: x != -100, tokenized_id[0]["labels"]))))
|
||||
```
|
||||
|
||||
```
|
||||
<|im_start|>system
|
||||
现在你要扮演皇帝身边的女人--甄嬛<|im_end|>
|
||||
<|im_start|>user
|
||||
小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——<|im_end|>
|
||||
<|im_start|>assistant
|
||||
<think>
|
||||
|
||||
</think>
|
||||
|
||||
嘘——都说许愿说破是不灵的。<|im_end|>
|
||||
```
|
||||
|
||||
## 加载模型和 tokenizer
|
||||
|
||||
由于 Qwen3.5-4B 是多模态模型(`Qwen3_5ForConditionalGeneration`),当我们只需要文本能力时,使用 `AutoModelForCausalLM` 加载会自动得到文本语言模型 `Qwen3_5ForCausalLM`(不加载视觉塔,显存更省)。
|
||||
|
||||
```python
|
||||
import torch
|
||||
from transformers import AutoModelForCausalLM
|
||||
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
'/root/autodl-tmp/Qwen/Qwen3.5-4B',
|
||||
dtype=torch.bfloat16,
|
||||
device_map="auto",
|
||||
)
|
||||
|
||||
# 开启梯度检查点时需要调用该方法
|
||||
model.enable_input_require_grads()
|
||||
model.dtype # torch.bfloat16
|
||||
```
|
||||
|
||||
## LoRA Config
|
||||
|
||||
`LoraConfig` 中比较重要的参数如下:
|
||||
|
||||
- `task_type`:模型类型,绝大部分 `decoder-only` 的模型都是因果语言模型 `CAUSAL_LM`
|
||||
- `target_modules`:需要训练的层名
|
||||
- `r`:`LoRA` 的秩,决定低秩矩阵的维度,较小的 `r` 意味着更少的参数
|
||||
- `lora_alpha`:缩放参数,与 `r` 一起决定 `LoRA` 更新的强度,实际缩放比例为 `lora_alpha/r`
|
||||
- `lora_dropout`:应用于 `LoRA` 层的 `dropout rate`,用于防止过拟合
|
||||
|
||||
> **关于 Qwen3.5 的混合架构**:模型的 32 层中,每 4 层有 3 层是线性注意力(`linear_attn`),1 层是全注意力(`self_attn`)。
|
||||
> - 全注意力层包含 `q_proj / k_proj / v_proj / o_proj`
|
||||
> - 线性注意力层包含 `in_proj_qkv / in_proj_z / in_proj_a / in_proj_b / out_proj`
|
||||
> - 每一层都包含 MLP:`gate_proj / up_proj / down_proj`
|
||||
>
|
||||
> 下面我们采用与 Qwen3 一致的目标模块 `["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]`——它们覆盖了所有全注意力层和**每一层的 MLP**,已经能让每一层都参与到 LoRA 训练中。如果你想更充分地适配线性注意力层,也可以把 `in_proj_qkv`、`in_proj_z`、`out_proj` 加入 `target_modules`。
|
||||
|
||||
```python
|
||||
from peft import LoraConfig, TaskType, get_peft_model
|
||||
|
||||
config = LoraConfig(
|
||||
task_type=TaskType.CAUSAL_LM,
|
||||
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
|
||||
inference_mode=False, # 训练模式
|
||||
r=8, # Lora 秩
|
||||
lora_alpha=32, # Lora alpha,缩放系数 = 32/8 = 4
|
||||
lora_dropout=0.1, # Dropout 比例
|
||||
)
|
||||
|
||||
model = get_peft_model(model, config)
|
||||
model.print_trainable_parameters()
|
||||
```
|
||||
|
||||
输出(仅训练约 0.25% 的参数):
|
||||
|
||||
```
|
||||
trainable params: 10,616,832 || all params: 4,216,368,128 || trainable%: 0.2518
|
||||
```
|
||||
|
||||
## Training Arguments
|
||||
|
||||
- `output_dir`:模型输出路径
|
||||
- `per_device_train_batch_size`:每张卡上的 `batch_size`
|
||||
- `gradient_accumulation_steps`:梯度累计步数
|
||||
- `num_train_epochs`:训练轮数
|
||||
|
||||
```python
|
||||
from transformers import TrainingArguments, Trainer, DataCollatorForSeq2Seq
|
||||
|
||||
args = TrainingArguments(
|
||||
output_dir="./output/Qwen3_5_4B_LoRA",
|
||||
per_device_train_batch_size=4,
|
||||
gradient_accumulation_steps=4,
|
||||
logging_steps=10,
|
||||
num_train_epochs=3,
|
||||
save_steps=100,
|
||||
learning_rate=1e-4,
|
||||
save_on_each_node=True,
|
||||
gradient_checkpointing=True,
|
||||
report_to="none",
|
||||
)
|
||||
```
|
||||
|
||||
## SwanLab 简介
|
||||
|
||||

|
||||
|
||||
[SwanLab](https://github.com/swanhubx/swanlab) 是一个开源的模型训练记录工具,面向 AI 研究者,提供了训练可视化、自动日志记录、超参数记录、实验对比、多人协同等功能。在 `SwanLab` 上,研究者能基于直观的可视化图表发现训练问题,对比多个实验找到研究灵感,并通过在线链接的分享与基于组织的多人协同训练,打破团队沟通的壁垒。
|
||||
|
||||
**为什么要记录训练**
|
||||
|
||||
相较于软件开发,模型训练更像一门实验科学。一个品质优秀的模型背后,往往是成千上万次实验。研究者需要不断尝试、记录、对比,积累经验,才能找到最佳的模型结构、超参数与数据配比。在这之中,如何高效进行记录与对比,对于研究效率的提升至关重要。
|
||||
|
||||
## 实例化 SwanLabCallback
|
||||
|
||||
建议先在 [SwanLab 官网](https://swanlab.cn/) 注册账号,然后在初始化阶段选择 `(2) Use an existing SwanLab account` 并使用 private API Key 登录。
|
||||
|
||||
```python
|
||||
import swanlab
|
||||
from swanlab.integration.transformers import SwanLabCallback
|
||||
|
||||
# 首次使用会提示登录,输入你在 SwanLab 官网获取的 API Key
|
||||
swanlab_callback = SwanLabCallback(
|
||||
project="Qwen3.5-Lora",
|
||||
experiment_name="Qwen3.5-4B-LoRA",
|
||||
)
|
||||
```
|
||||
|
||||
## 使用 Trainer 训练
|
||||
|
||||
```python
|
||||
trainer = Trainer(
|
||||
model=model,
|
||||
args=args,
|
||||
train_dataset=tokenized_id,
|
||||
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
|
||||
callbacks=[swanlab_callback],
|
||||
)
|
||||
|
||||
trainer.train()
|
||||
```
|
||||
|
||||
训练完成后,打开 SwanLab 即可查看训练过程中记录的参数与 loss 曲线:
|
||||
|
||||

|
||||
|
||||
## 加载 LoRA 权重推理
|
||||
|
||||
得到 checkpoint 之后,加载基础模型并挂载 LoRA 权重进行推理:
|
||||
|
||||
```python
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
import torch
|
||||
from peft import PeftModel
|
||||
|
||||
model_id = '/root/autodl-tmp/Qwen/Qwen3.5-4B' # 基础模型路径
|
||||
lora_path = './output/Qwen3_5_4B_LoRA/checkpoint-XXX' # 训练得到的 LoRA 权重路径,按实际填写
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
||||
|
||||
# 加载基础模型
|
||||
model = AutoModelForCausalLM.from_pretrained(model_id, dtype=torch.bfloat16, device_map="auto")
|
||||
|
||||
# 挂载 LoRA 权重
|
||||
model = PeftModel.from_pretrained(model, model_id=lora_path)
|
||||
model.eval()
|
||||
|
||||
# 构造对话
|
||||
messages = [
|
||||
{"role": "system", "content": "现在你要扮演皇帝身边的女人--甄嬛"},
|
||||
{"role": "user", "content": "你是谁?"},
|
||||
]
|
||||
inputs = tokenizer.apply_chat_template(
|
||||
messages,
|
||||
add_generation_prompt=True,
|
||||
enable_thinking=False, # 关闭思考模式,直接输出角色回答
|
||||
tokenize=True,
|
||||
return_dict=True,
|
||||
return_tensors="pt",
|
||||
).to(model.device)
|
||||
|
||||
gen_kwargs = {"max_new_tokens": 128, "do_sample": True, "top_p": 0.8, "temperature": 0.7}
|
||||
with torch.no_grad():
|
||||
outputs = model.generate(**inputs, **gen_kwargs)
|
||||
outputs = outputs[:, inputs["input_ids"].shape[1]:]
|
||||
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
|
||||
```
|
||||
|
||||
输出示例:
|
||||
|
||||
```
|
||||
我是甄嬛,家父是大理寺少卿甄远道。
|
||||
```
|
||||
|
||||
可以看到,经过 LoRA 微调后,模型已经学会了甄嬛的说话风格与人物设定。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
@@ -5,6 +5,7 @@
|
||||
## 目录
|
||||
|
||||
- [MiniCPM5](#minicpm5)
|
||||
- [Qwen3.5](#qwen35)
|
||||
- [Kimi-K2.5](#kimi-k25)
|
||||
- [Step-3.5-Flash](#step-35-flash)
|
||||
- [GLM-4.7-Flash](#glm-47-flash)
|
||||
@@ -71,6 +72,13 @@
|
||||
- [x] [MiniCPM5-1B LoRA 及 SwanLab 可视化记录](./models/minicpm5/03-MiniCPM5-1B-LoRA及SwanLab可视化记录.md)
|
||||
- [x] [MiniCPM5-1B LoRA Docker 镜像](https://www.codewithgpu.com/i/datawhalechina/self-llm/MiniCPM5)
|
||||
|
||||
### Qwen3.5
|
||||
|
||||
[Qwen3.5-4B](https://www.modelscope.cn/models/Qwen/Qwen3.5-4B)
|
||||
- [x] [Qwen3.5-4B vLLM 部署调用](./models/Qwen3.5/01-Qwen3.5-4B-vLLM%20部署调用.md)
|
||||
- [x] [Qwen3.5-4B SGLang 部署调用](./models/Qwen3.5/02-Qwen3.5-4B-SGLang%20部署调用.md)
|
||||
- [x] [Qwen3.5-4B LoRA 及 SwanLab 可视化记录](./models/Qwen3.5/03-Qwen3.5-4B-LoRA及SwanLab可视化记录.md)
|
||||
|
||||
### Kimi-K2.5
|
||||
|
||||
[Kimi-K2.5](https://huggingface.co/moonshotai/Kimi-K2.5)
|
||||
|
||||
Reference in New Issue
Block a user