> 本文记录一次真实的单卡海光 K100-aiDCU 部署。目标不是让设备“看起来像 CUDA”,而是把厂商差异压缩在 DTK/HIP 边界内,让上层尽量保持 PyTorch、Transformers、TRL、PEFT、Unsloth、vLLM 和 llama.cpp 的通用技术路线。文中的结果、错误和性能数据均来自 2026 年 8 月 12 日的现场测试。
## 一、最终得到了什么
这次交付不是一个只能运行厂商示例的容器,而是一套可以日常使用的 Unsloth Studio:
* 用本地 Transformers/safetensors 模型进行 BF16 LoRA 或全参数训练;
* 从 Studio 的 Train 产品接口提交任务,观察加载、训练、保存和历史记录;
* 用训练后的 LoRA checkpoint 直接进入 Preview;
* 用 GGUF、safetensors 或 LoRA 进行 Chat;
* 支持普通响应、SSE 流式输出、多轮历史、重新生成和会话持久化;
* 支持知识库、TXT/PDF/DOCX 附件、词法/向量/混合检索;
* 支持 Web Search、Python 和终端工具调用;
* 提供 OpenAI 风格 API、API Key 生命周期和模型加载/卸载接口;
* 保留 SDPA 稳定回滚镜像,并提供隔离的 FlashAttention 增强镜像。
当前推荐服务位于 `127.0.0.1:18887`,使用 DTK 26.04、PyTorch HIP、厂商匹配的 FlashAttention 和 gfx928/HIP llama.cpp。稳定回滚服务位于 `127.0.0.1:18888`,默认关闭服务端工具并保留 SDPA 路线。两个端口、状态目录和安全策略互相隔离。
这套适配没有刷 VBIOS,没有改 PCI Vendor ID,没有伪造 `torch.version.cuda`,也没有把厂商 demo 当成产品入口。PyTorch HIP 仍然通过 `torch.cuda.\\\*` 兼容语义工作,这是 PyTorch 在 HIP 平台上的标准设计;对外审计字段则明确写作 `rocm`,避免把兼容 API 误解成原生 CUDA。
## 二、Unsloth Studio 是什么
很多人只把 Unsloth 理解为一个更快、更省显存的微调库。Studio 的边界更大:它把模型发现、数据集处理、训练、导出、推理、Chat、知识库、工具调用、历史记录、监控和权限管理放在一个 Web 产品中。
### 1\. 训练工作台
Studio 的训练层可以配置 LoRA、全参数微调和继续预训练,管理本地或 Hugging Face 数据集,记录 loss、学习率、梯度范数、checkpoint 和最终产物。训练不在 Web 进程中直接执行,而是通过独立子进程运行,这使模型版本切换、失败隔离和 GPU 回收更容易控制。
本次 K100 部署认证了以下链路:
```text
登录鉴权
-> POST /api/train/start
-> 数据集和模型安全预检
-> 单卡 GPU 选择
-> 训练子进程
-> BF16 LoRA step
-> adapter/checkpoint/trainer state 保存
-> metrics/history
-> checkpoint Preview
```
它与直接运行一段 `SFTTrainer` 脚本的区别在于:产品层的鉴权、路径约束、任务身份、异步状态、历史数据库和产物发现也被一起验证了。
### 2\. Chat 与推理
Studio 可以在同一界面管理 Transformers 模型、LoRA adapter 和 GGUF。对于 K100:
* safetensors/LoRA 走 PyTorch HIP;
* GGUF 走 gfx928 编译的 HIP llama.cpp;
* vLLM 作为独立推理 profile 使用 eager 模式;
* CUDA-only 或不稳定模型优先使用 PyTorch HIP 的 `torch.cuda` 兼容接口、SDPA fallback,或者禁用有问题的 kernel;
* 不把 `flash\\\_attn` 能导入等同于每一种 head dimension 都能运行。
实际测试包括官方 Qwen2.5-0.5B-Instruct GGUF、本地 Qwen3 0.6B 基础权重、训练后的 LoRA,以及本地 Gemma 26B GGUF。
### 3\. RAG 与附件
Studio 自带知识库、文档解析、分块、向量化、检索和签名预览。此次部署使用体积约 68 MiB、已经缓存的 `sentence-transformers/paraphrase-MiniLM-L3-v2`,通过普通 PyTorch HIP 运行。
认证范围包括:
* TXT 上传和异步索引;
* PDF、DOCX 文本解析;
* lexical、dense、hybrid 三种检索;
* 签名原文件预览;
* 知识库清理;
* Chat 附件控件和 RAG scope。
### 4\. Search、Code 与 API
Studio 可以让模型调用 Web Search、Python 和 terminal。工具能力是安全敏感功能,因此本次配置只在回环地址 `18887` 开启;`18888` 默认关闭。API Key 可创建、调用、撤销,撤销后的 key 返回 401。
小模型的工具规划质量和工具执行环境是两件事。Qwen2.5 0.5B 能完成显式 Python 调用,但可能生成错误的自主搜索参数;Gemma 26B 已完成模型自主生成 Python 和 Web Search 参数的闭环测试。
## 三、与常见方案相比,优势在哪里
没有一个工具在所有维度都最好。下面的比较重点是产品边界,而不是简单地给竞品排座次。
|方案|强项|与本方案的关系|
|-|-|-|
|Unsloth Studio|训练效率、LoRA、训练与 Chat/RAG/Preview 一体化|本文的产品主体;优势是从数据到训练产物再到 Chat 的闭环|
|LLaMA-Factory|模型/数据集覆盖广、训练配置成熟、中文社区活跃|更偏训练平台;Studio 在训练产物 Preview、Chat、RAG 和工具整合上更完整|
|Axolotl|YAML 可复现、适合工程化训练流水线|适合纯训练和集群自动化;Studio 更适合希望通过 UI 完成完整模型生命周期的团队|
|Open WebUI|Chat、RAG、用户管理和多后端体验成熟|更偏推理前端;不以本地高效微调和训练产物管理为核心|
|vLLM|高吞吐服务和 OpenAI API|是推理引擎而非训练产品;本方案把它作为可替换后端而不是整个产品|
|Ollama / LM Studio|本地模型开箱体验好|更适合桌面推理;训练、LoRA 生命周期和服务器级可审计部署不是主要目标|
|厂商 demo|最容易证明硬件能跑某个算子或模型|可移植性和可维护性弱;本文只把厂商层保留在 DTK/HIP 与必要二进制边界|
本次部署最重要的竞品优势并不是某一个 microbenchmark,而是以下组合:
1. **训练到推理的连续性。** 同一个产品可以提交训练、查看 metrics、保存 adapter,然后直接 Preview 或放入 Chat。
2. **显存效率与小改动原则。** 保留 Unsloth 的 LoRA 优化,同时没有为 K100 维护一个大规模 fork。
3. **后端可替换。** Transformers、FlashAttention、SDPA、llama.cpp、vLLM 各自有明确边界和回滚路径。
4. **适合非 CUDA 设备。** 上层继续使用 PyTorch HIP 的标准兼容语义,对外监控和历史则诚实报告 ROCm/HIP。
5. **产品功能完整。** Chat、RAG、附件、工具、历史、API、账号安全和浏览器 UI 都经过真实测试,而不仅是一个 notebook 成功运行。
代价也很明确:K100 目前没有可直接信任的通用 bitsandbytes 4/8-bit 路线;某些只提供 CUDA 扩展的模型仍需 fallback;图片、视频和音频功能还需要相应模型资产逐项认证。
## 四、硬件和软件基线
现场环境如下:
|项目|观测值|
|-|-|
|GPU|单卡 K100\_AI,PCI `1d94:6210`|
|ISA|`gfx928:sramecc+:xnack-`,120 CU|
|显存|65,520 MiB|
|容器系统|Ubuntu 22.04 系列基础环境|
|DTK|26.04|
|编译器|dcc/clang 17|
|HIP runtime|6.3.26045/26093 系列|
|PyTorch|2.9.0 DTK vendor build,`USE\\\_ROCM=ON`|
|Python|CPython 3.10|
|FlashAttention wheel ABI|DTK 26.04 + Torch 2.9 + CPython 3.10 + x86\_64|
|GPU 设备节点|`/dev/kfd`、`/dev/mkfd`、`/dev/dri`|
|驱动边界|`libamdhip64` 最终解析到 `libgalaxyhip`|
主机上的 DTK 24.04.3 用户态与已加载的内核/驱动代际不匹配,因此主机 `rocminfo` 失败,而 DTK 26.04 容器正常。这也是为什么不能随意换成普通上游 ROCm 镜像:当前驱动需要海光提供的 `libgalaxyhip` 边界。
所以需要讲清楚“通用路线”的含义:上层尽量通用,不等于底层驱动 ABI 可以凭空替换。此次唯一不可消除的厂商依赖是 DTK 26.04 基础环境,以及按该 ABI 构建的 FlashAttention/llama.cpp 二进制。
## 五、适配过程与核心改动
### 1\. 先证明 HIP,而不是先编译整个 PyTorch
现场审计依次检查系统、PCI、设备节点、DTK、`hipcc`、`rocminfo`、动态链接库、gfx ISA、显存、温度和功耗。随后运行三个最小测试:
* HIP runtime vector add;
* hipBLAS 1024×1024 SGEMM;
* Python `ctypes` 直接加载 HIP runtime。
结果分别为 0.729 ms、17.393 TFLOP/s 的短时 warm GEMM,以及 runtime/driver/device/VRAM 全部可读取。只有这三层成立后,才继续 PyTorch、Unsloth 和 Studio。
这种顺序能快速区分驱动 ABI、runtime、BLAS、Python 包和上层框架问题。否则一个 `model.generate()` 错误很容易被错误归因于硬件。
### 2\. PyTorch 从零补丁 configure 开始
重新 clone 干净 PyTorch v2.9.0,首先执行零补丁 configure。遇到的问题不是算子源码,而是 DTK CMake 导出差异:
* CMake 3.29 不接受 `hipcc` 作为 `CMAKE\\\_HIP\\\_COMPILER`;
* DTK 缺少上游期望的 `hiprtcConfig.cmake`;
* PyTorch 查找 `hip::amdhip64`,DTK 导出的是 `hip::galaxyhip`。
最终补丁范围只有一个外部 `Findhiprtc.cmake` 和 CMake 参数,没有修改 PyTorch operator、ATen 或 CUDA 身份逻辑。去掉 `CMAKE\\\_MODULE\\\_PATH` 即可回滚。
### 3\. 明确禁用不可用的 bitsandbytes
通用 bitsandbytes wheel 先声称找不到 ROCm 6.3,再尝试加载其他版本对象并出现解压错误,却可能让上层误以为量化可用。对此不能只捕获 import error,因为“能 import”和“原生 kernel 可运行”不是一回事。
适配增加了显式环境开关:
```bash
UNSLOTH\\\_DISABLE\\\_BITSANDBYTES=1
```
它让 Unsloth 不再宣传 4/8-bit 能力,同时保留一个不可调用的类型 stub,满足上游某些无条件类型导入。BF16、FP16、LoRA 和全参数训练不受影响。只有拿到 DTK 26.04 对应的原生 bitsandbytes 并完成算子测试后才应重新开启。
### 4\. FlashAttention 必须精确匹配 ABI
选用的文件是:
```text
flash\\\_attn-2.8.3+das.opt1.dtk2604.torch290-cp310-cp310-manylinux\\\_2\\\_28\\\_x86\\\_64.whl
```
选择依据逐项对应:
* `dtk2604`:DTK 26.04;
* `torch290`:PyTorch 2.9;
* `cp310`:Python 3.10;
* `x86\\\_64`:主机架构。
安装必须使用 `--no-deps`,否则 pip 可能用通用 PyPI torch 替换厂商 PyTorch:
```bash
python3 -m pip install --no-deps flash\\\_attn-...whl
```
wheel 的 distribution metadata 是 2.8.3,但内部 `version.py` 报告 2.6.1。我们保留并披露该不一致,没有增加版本伪装。其动态对象链接到 `libgalaxyhip.so.5`,FP16/BF16、causal/non-causal、varlen 和 Qwen GQA forward/backward 均通过。
FlashAttention forward microbenchmark 在 `\\\[2,1024,16,64]` BF16 上约 0.169 ms,SDPA math 为 3.911 ms,约 23.2 倍。但这是固定形状 forward microbenchmark,不应外推为完整训练提速。
### 5\. RAG 的 sqlite-vec 兼容修复
RAG 安装上游所需依赖后,dense search 返回:
```text
A LIMIT or 'k = ?' constraint is required on vec0 knn queries.
```
独立 sqlite-vec 0.1.9 复现实验证明:带 partition key 时,无论参数化还是字面量 `LIMIT` 都被拒绝,而 `k=?` 可用。最终改动只有一条 SQL:
```python
# before
WHERE scope=? AND embedding MATCH ? ORDER BY distance LIMIT ?
# after
WHERE scope=? AND embedding MATCH ? AND k=? ORDER BY distance
```
没有修改数据库 schema、向量格式或检索算法。修复后 lexical、dense、hybrid 和签名预览均通过。
### 6\. 训练历史不能把 HIP 写成原生 CUDA
PyTorch HIP 内部使用 `DeviceType.CUDA` 和 `torch.cuda` 是正常兼容设计,但 Studio 直接持久化 `get\\\_device().value` 会让训练历史显示 `cuda`。硬件 API 已经有 ROCm-aware label,因此训练配置改为复用同一函数:
```python
config\\\["device\\\_backend"] = \\\_backend\\\_label(get\\\_device())
```
这是一处展示和审计语义修复,不改变 worker 的设备选择和 PyTorch 调用。复验后的历史明确记录 `device\\\_backend: rocm`。
### 7\. 训练产物 Preview 的双重自锁
真实训练完成后,`has\\\_preview\\\_model=true` 并不能证明 Preview 可生成。第一次调用签名 Preview URL 在 300 秒后超时,没有 worker、没有 OOM。
根因有两个:
1. keep-warm 中间件把 Preview Chat 识别为推理请求,并先持有进程级生命周期锁;
2. Preview 内部调用 `load\\\_model\\\_gated()`,再次获取同一个非可重入 `threading.Lock`;绕过重复获取后,model-switch drain 又把当前 Preview 请求本身当成“其他正在推理的请求”等待。
不能简单把锁改成 `RLock`:同一个事件循环中的不同协程运行在同一线程,`RLock` 会错误地允许两个独立请求同时进入。最终方案是给内部加载增加两个默认关闭的参数:
```python
await load\\\_model\\\_gated(
LoadRequest(model\\\_path=str(path)),
request,
DEFAULT\\\_ADMIN\\\_USERNAME,
lifecycle\\\_gate\\\_held=True,
current\\\_request\\\_counted=True,
)
```
只有 Preview 调用传入它们;普通 `/load` 路径仍获取原锁,跨 event loop 串行化不变。定向测试 19/19 通过。修复后 cold Preview 54.47 秒完成,warm 请求 4.682 秒,worker 明确报告 ROCm、K100、FlashAttention 和 LoRA adapter enabled。
## 六、实际测试结果
|测试|结果|数据|
|-|-|-|
|HIP vector add|PASS|1,048,576 元素,0.729 ms|
|hipBLAS SGEMM|PASS|1024 方阵,17.393 TFLOP/s 短测|
|PyTorch FP32/FP16/BF16|PASS|forward/backward 均通过|
|Triton|PASS|gfx928 vector add|
|Qwen3 0.6B BF16 LoRA|PASS|2 steps,loss 4.6922,峰值约 1.30 GiB|
|Studio 真实训练|PASS|1 step,loss 2.6198,产品墙钟 64.042 s|
|训练 adapter Preview|PASS(传输)|cold 54.47 s,warm 4.682 s|
|FlashAttention|PASS|dense/varlen/GQA forward/backward|
|Qwen2.5 0.5B GGUF|PASS|prompt 249.7 tok/s,生成 316.2 tok/s|
|Gemma 26B GGUF|PASS|warm 生成 65.6–68.3 tok/s|
|vLLM Qwen3 eager|PASS|22.31 output tok/s|
|RAG|PASS|TXT/PDF/DOCX,三种检索和签名预览|
|浏览器产品流程|PASS|7/7 Chat 200,历史、重生成、Recipes、账号轮换等|
|模型自主工具|PASS|Gemma 完成 Python 和 Web Search 闭环|
性能数字只用于说明当前环境可工作,并非跨机器 benchmark。特别是首次模型加载、缓存状态、prompt 长度和 GGUF 量化方式都会显著改变结果。
## 七、部署与使用
本文交付包有两种使用方式:
1. **离线镜像方式**:加载 `docker-images.tar.zst`,不需要重新构建 Python 环境;
2. **源码构建方式**:使用包内的优化源码、Dockerfile 和本地 FlashAttention wheel重建。
### 1\. 预检
```bash
cd unsloth-studio-k100-dtk2604-20260812
./bin/preflight.sh
```
必须满足:
* x86\_64 Linux;
* Docker Engine 和 Compose v2;
* `/dev/kfd`、`/dev/mkfd`、`/dev/dri`;
* `/opt/hyhal` 可挂载;
* 单卡 K100-ai/gfx928;
* 至少 32 GiB shared memory 配额和足够的镜像磁盘空间。
### 2\. 加载镜像并启动完整服务
```bash
./bin/load-images.sh
./bin/start.sh full
curl http://127.0.0.1:18887/api/health
```
首次启动会把 bootstrap 密码写到:
```text
state/full/auth/.bootstrap\\\_password
```
用 `unsloth` 登录后必须立即修改。密码未修改时,Studio 会按安全策略在一小时后停止。
### 3\. 稳定回滚服务
```bash
./bin/stop.sh full
./bin/start.sh stable
curl http://127.0.0.1:18888/api/health
```
不要在单卡上同时执行两个服务的训练任务。服务可以同时处于空闲状态,但模型加载和训练会争用同一张卡。
### 4\. 使用内置小模型
便携包附带官方 Qwen2.5-0.5B-Instruct Q4\_K\_M:
```text
/workspace/models/qwen2.5-0.5b-instruct-gguf/qwen2.5-0.5b-instruct-q4\\\_k\\\_m.gguf
```
它适合验证 Chat、SSE、历史和 UI,不适合代表大模型工具规划能力。可将自己的 Gemma/Qwen GGUF 放入包内 `models/`,容器会以 `/workspace/models` 只读/读写挂载方式发现它。
### 5\. 提交训练
Studio UI 中选择本地 safetensors 模型和上传数据集,关闭 4-bit,使用 BF16、LoRA、`adamw\\\_torch` 和 SDPA/自动 attention。API smoke 示例:
```bash
STUDIO\\\_PASSWORD='<修改后的密码>' \\\\
python3 smoke/studio\\\_training\\\_http\\\_smoke.py \\\\
--base http://127.0.0.1:18887 \\\\
--model /workspace/models/your-transformers-model
```
本地数据集必须通过 Studio 上传,或放在其受控 `assets/datasets` 根目录。不要为了方便放宽绝对路径校验;第一次测试对 `/workspace/data/...` 的 HTTP 400 正是正常的任意文件读取防护。
## 八、维护和安全注意事项
### 不要做的事
* 不要刷 VBIOS 或伪装 PCI Vendor;
* 不要把 `torch.version.cuda` 改成假值;
* 不要用 import 成功证明 bitsandbytes/FlashAttention kernel 可用;
* 不要让 pip 自动解析 FlashAttention 的 torch 依赖;
* 不要在脏 PyTorch 源码树上持续叠补丁;
* 不要把带 terminal/Python 工具的 Studio 直接绑定到 `0.0.0.0`;
* 不要在单卡上同时运行训练、大模型 Chat 和 vLLM 压测。
### FlashAttention 回滚
如果某个模型的 head dimension、GQA 或 backward 不稳定:
1. 切换到 stable profile;
2. 设置 `attn\\\_implementation="sdpa"`;
3. 禁用 compile 和不稳定 kernel;
4. 保留原始错误和模型形状,再决定是否增加模型级 allowlist。
### 版本约束
当前 FlashAttention wheel 不能随意用于:
* Python 3.11/3.12;
* PyTorch 2.5/2.7;
* DTK 24.x 或其他 DTK 版本;
* ARM64;
* 普通 CUDA torch。
任一项变化都应重新选择 wheel,而不是改文件名强行安装。
### 数据与许可证
离线包不包含现有用户的账号数据库、密码、Chat 历史、RAG 内容或 Gemma 大模型。Unsloth Studio 的相应源码包含 AGPL-3.0-only 标识;对外分发或提供网络服务前,应阅读包内上游许可证并履行相应义务。Qwen 模型和 llama.cpp 也保留各自的来源、commit 和许可证说明。
## 九、为什么这条路线更可维护
这次适配真正新增的行为代码很少:bitsandbytes 能力门控、一个 sqlite-vec 查询、一处 ROCm 历史标签、一个 Preview 生命周期修复和一个关机时序调整。硬件特有内容被控制在四处:
```text
DTK 26.04 base image
├── libgalaxyhip / hyhal
├── gfx928 target
├── matched FlashAttention wheel
└── matched llama.cpp HIP runtime
通用应用层
├── PyTorch HIP torch.cuda semantics
├── Transformers / TRL / PEFT / Unsloth
├── vLLM eager
└── Unsloth Studio Train / Chat / RAG / Tools
```
这意味着未来升级时可以逐层验证,而不是重新维护一个“海光专版 AI 平台”:先验证 DTK runtime,再验证 PyTorch,再验证 kernel,最后验证 Studio 产品链路。任何一层失败都有明确回滚点。
## 十、结语
非 CUDA 加速卡最容易掉进两个极端:要么完全依赖厂商 demo,要么为了兼容而伪造 CUDA 身份。K100-aiDCU 的实际结果说明还有第三条路:承认底层 ABI 的厂商边界,但让 PyTorch HIP、Transformers、Unsloth、vLLM 和 llama.cpp 各自保持清晰、可测试、可回滚的接口。
最终价值不只是“模型能跑”,而是训练任务能从 Studio 提交并保存,产物能 Preview,Chat 能在真实浏览器中多轮使用,RAG 和工具能工作,错误能保留,补丁能反向应用,服务能安全地留在回环地址。这才是一套可以继续维护的产品部署。