# Technical Documentation: ztxz16/fastllm

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/ztxz16/fastllm)

### Overview (`README.md`)

# FastLLM

[English](README_EN.md) · [快速开始](#快速开始) · [模型部署指南](#模型部署指南) · [Agent 部署速查](docs/model-deployment-agent.md) · [开发与验证记录](docs/development-validation.md) · [Flash-Next 用户手册](docs/qwen3.8-flash-next/README.md) · [Benchmark](docs/benchmark.md) · [常用参数](#常用参数) · [版本日志](docs/version.md)

FastLLM 是一个面向本地运行和服务部署的高性能大模型推理引擎。核心运行时使用 C++ 实现，不依赖 PyTorch，支持稠密模型与 MoE 模型，并提供 CUDA、ROCm、CPU、NUMA、磁盘混合推理以及多卡张量并行能力。

项目同时提供命令行对话、终端部署向导、WebUI、性能测试工具，以及兼容 OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages 的 API 服务。

## 核心能力

- **新模型快速适配**：当前主线覆盖 Qwen4-Exp / Qwen3.8-Flash-Next、Qwen3.5/3.6/3.8、DeepSeek-V4、Kimi-K3、GLM-5.3-Flash、Dots3-Note 等模型。
- **大规模 MoE 混合部署**：可将普通层和专家层分别放在 CUDA、CPU、NUMA 或磁盘上，也可按比例组合多种设备，适合显存有限但主机内存或 SSD 容量充足的机器。
- **多卡与高吞吐服务**：支持张量并行、奇数卡数量、动态批处理、流式输出、Paged KV Cache、前缀缓存、分块 Prefill 和 CUDA Graph。
- **投机解码**：为匹配的模型提供 MTP、内置或外部 DSpark，以及 DFlash2 等投机解码路径。
- **多种精度与格式**：支持 Hugging Face Safetensors、FastLLM 导出格式、AWQ 和部分 GGUF；可按模型与硬件使用 FP16、BF16、FP8、NVFP4、MXFP4、INT4、K-Quant 等路径。
- **完整服务接口**：支持思考内容分离、工具调用、流式响应、缓存命中统计、服务端采样参数和启动进度事件。
- **可扩展后端**：内置 CPU/CUDA/ROCm 算子，并提供 Triton 可选算子、自定义 Python 模型图和其他加速器后端的源码接入能力。

> 不同模型、量化格式和硬件后端支持的算子并不完全相同。正式部署前请用目标模型和目标硬件验证精度、显存占用及吞吐。

## 当前模型能力

下面列出当前开发主线重点，不再把早期模型作为首页介绍内容。

| 模型系列 | 当前重点能力 |
| --- | --- |
| Qwen | Qwen4-Exp / Qwen3.8-Flash-Next 文本解码、QSA、PLE n-gram 和 CPU/CUDA/NUMA 混合推理；Qwen3.8-Flash-Next MTP；Qwen3.5/3.6/3.8 MTP 和 DFlash2 |
| DeepSeek | DeepSeek-V4 / V4-Flash、稀疏注意力、内置 DSpark、多卡 CUDA 与 CPU/NUMA 混合 MoE |
| Kimi | Kimi-K3、KDA/MLA、外部 DSpark，以及 CUDA、NUMA、CPU/GPU 专家和磁盘专家 |
| GLM | GLM-5 DSA、GLM-5.3-Flash KDA 与分页缓存、GLM-5.2 量化 KV-B CPU 推理 |
| 其他 | Dots3-Note、Laguna、HY-V3、Step3.5/3.7、MiniMax-M2、Gemma4 等 |

Qwen4-Exp / Qwen3.8-Flash-Next 当前不加载视觉权重；Qwen3.8-Flash-Next 可通过 `--mtp` 按需加载 MTP 权重并启用推测解码。早期模型的兼容信息仍可在[支持模型列表](docs/models.md)中查询；最新适配和限制以[版本日志](docs/version.md)为准。

## 快速开始

### 安装

建议在独立的 Python 虚拟环境中安装。预编译包适用于以下常见环境：

| 环境 | 安装命令 | 说明 |
| --- | --- | --- |
| Linux + NVIDIA GPU | `python -m pip install -U ftllm` | 包含 Python 接口和常用 CUDA 运行时依赖；驱动需要与 CUDA 运行时兼容 |
| Windows + NVIDIA GPU | `python -m pip install -U ftllm` | 如果首次安装提示缺少 DLL，请先安装下方的 Windows 依赖包 |
| Linux + AMD GPU | [ROCm 安装与编译](docs/rocm.md) | 按显卡架构选择构建与安装方式 |
| CPU-only、特殊架构或其他加速器 | [源码安装](#源码安装) | 可按实际平台选择 CMake 后端 |

Windows 首次安装所需的依赖包：

~~~bash
python -m pip install https://www.modelscope.cn/models/huangyuyang/fastllmdepend-windows/resolve/master/ftllmdepend-0.0.0.2-py3-none-win_amd64.whl
python -m pip install -U ftllm
~~~

如果 Conda 环境出现动态库冲突，可尝试使用 `venv` 创建干净环境。安装或加载失败时先查看 [FAQ](docs/faq.md)。

### 验证安装

下面使用体积较小的 Qwen3-0.6B 做安装冒烟测试；它只是便于快速下载的测试模型，不代表当前模型主线。

~~~bash
ftllm run Qwen/Qwen3-0.6B
~~~

最常用的部署入口是 API Server：

~~~bash
# API Server，默认监听 0.0.0.0:8080
ftllm server Qwen/Qwen3-0.6B

# 命令行对话
ftllm run Qwen/Qwen3-0.6B

# WebUI，连接上面的 API Server，默认监听 127.0.0.1:1616
ftllm webui --api_base http://127.0.0.1:8080/v1

# 浏览器部署启动器；无参数时启动并自动打开本地管理页面
ftllm
ftllm launch  # 等价写法

# 终端部署向导
ftllm tui

# 性能测试
ftllm bench Qwen/Qwen3-0.6B \
  --device cuda --input_tokens 512 --output_tokens 128 --batch 4
~~~

`ftllm`（或 `ftllm launch`）默认仅监听 `127.0.0.1:8000`，并在服务就绪后自动打开浏览器；使用 `ftllm launch --no-browser` 可以关闭自动打开。页面可以从 ModelScope 下载模型、保存启动配置、预览命令，并选择托管 `ftllm server` 或聊天 `ftllm webui`。新增启动项选择本地模型后，会根据模型结构、权重规模以及本机 GPU、内存和 NUMA 拓扑自动推荐 TP、MoE 混合推理与 N-gram 存储参数，也可以手动重新分析或清空可选推理参数。界面支持简体中文和英文，会优先使用上次选择的语言，否则跟随浏览器语言；`ftllm launch` 的终端日志固定使用英文。需要从局域网访问时使用 `ftllm launch --host 0.0.0.0`；终端和 Launcher 页面随后会列出本机、局域网以及网卡上直接配置的公网访问地址（若有）。公网访问还需要放行主机防火墙及云安全组，经过 NAT 时还需配置端口映射；Launcher 不会自动探测 NAT 的公网地址。非本机监听使用未加密 HTTP，请仅在可信网络中使用。它与终端向导共用配置文件；关闭 Launcher 时，由它托管的下载和模型进程也会停止。使用 `ftllm launch --help` 查看其他选项。

Launcher 的「界面主题」提供浅色模式和黑夜模式，默认浅色并记住手动选择。工作室会同步切换主题，保留当前会话和输入草稿。

左侧导航分为「模型管理」和「agent」两组。agent 包含工作室、**DeepSeek Harness**、**OpenCode**、**Codex**、**Claude Code**；后四者可通过分组旁或页面中的“管理”安装、升级和删除独立运行环境，复用现有插件管理。安装无需启动模型，删除保留会话和工作目录。Harness 嵌入原生页面并连接当前模型 API，也可点击“安装并打开 Harness”。无需预装 Node/npm，会话保存位置和部署限制见 [Harness 接入说明](docs/launcher-harness.md)。

同一位置还提供 **OpenCode** 原生网页及 **Codex / Claude Code** 会话页面，支持连接本地模型、工具交互与独立会话。外部工具均不默认安装，需点击各自的安装按钮；OpenCode / Codex 可复用已有命令，Claude Code 使用独立 Agent SDK 运行环境，界面风格接近 Harness。安装、权限和部署说明见 [原生 agent 接入说明](docs/launcher-agents.md)。

Launcher 主导航中的「自定义界面」支持在模型 API 启动后，用自然语言对话定制页面、工作室功能、主界面状态栏和全局皮肤。独立子页面提供会话管理，每个会话分别保留需求、修改摘要与草稿，刷新浏览器后也可恢复；右侧显示整个应用的交互预览，支持连续修改、编辑文件、取消生成和确认应用。自定义内容保存在 `~/.fastllm/plugins`，支持热更新、停用、删除和恢复上一版；核心代码不参与编辑。使用说明与插件格式见 [自定义界面文档](docs/launcher-plugins.md)。

对话、Agent 回复和思考过程支持 Markdown 表格、标题、嵌套列表、任务列表、引用及链接，历史会话也会按相同格式显示。HTML 代码块右上角的「预览」可打开交互页面，支持 HTML、CSS 和内联 JavaScript。预览在隔离环境中运行，仅加载内嵌资源，页面存储在关闭后清空；关闭预览可继续原来的对话。Markdown 解析资源随安装包提供，无需联网下载。

API Server 就绪后，点击「打开工作室」即可在 Launcher 内容区直接使用聊天、历史会话、Markdown、附件、思考过程和智能体功能。模型管理导航始终保留，可随时切换到启动、下载、日志和硬件页面，返回「工作室」后继续当前会话。Launcher 与独立的 `ftllm webui` 共用聊天组件和后端，界面配色、尺寸及语言会适配 Launcher。组件自动连接当前模型并使用启动配置中的 API Key，无需另开 WebUI 服务或端口。会话沿用 WebUI 的本地存储，刷新页面后仍然保留；停止或切换模型时会取消正在运行的 WebUI 任务并清理旧组件。

WebUI 不会在自身进程内加载模型，请先启动 OpenAI 兼容 API Server。WebUI 的可选 `model` 位置参数只用于推导 API 模型名；省略时会从 `/v1/models` 自动发现。

代码分析和联网搜索默认使用 Pi 智能体运行时。Linux x86-64 用户可打开
`ftllm launch` →「工作室」→「安装 Agent 依赖」，通过 pip 安装
`ftllm-agent-runtime==0.3.3`，其中已包含 Pi 及 `rg`、`fd` 搜索工具。
安装需要联网下载约 43 MB，沿用当前 pip 的镜像、代理和缓存配置；不需要管理员权限、Node.js、npm 或 Bun。
安装包保存在 `${XDG_DATA_HOME:-~/.local/share}/ftllm/agent-runtime/`，不修改系统 Python 包。
安装期间显示状态，失败后可查看错误并重试；完成后当前工作室即可使用，后续启动也会自动识别。
已有完整运行时的绿色包无需重复安装。也可在 ftllm 所在的 Python 环境中执行
`python -m pip install ftllm-agent-runtime==0.3.3` 后重启 ftllm，或按
[`tools/ftllm_agent_runtime/`](tools/ftllm_agent_runtime/) 中的说明构建配套 wheel。尚未安装时可通过
`--agent-runtime builtin` 使用原有单轮链路。

Launcher 会自动使用已安装的 Pi 运行时；「新建 Agent」可选择工作目录。通过 `ftllm launch --agent-workspace-root /path/to/projects` 指定可选目录的根路径，默认为用户主目录。Launcher 的目录 Agent 默认启用，本机和远程监听均可使用，例如 `ftllm launch --host 0.0.0.0 --agent-workspace-root /path/to/projects`。使用 `--disable-workspace-agent` 可关闭目录 Agent，同时禁止目录浏览、新建目录 Agent 及继续执行已保存的目录 Agent 任务；普通对话仍可使用。目录 Agent 可修改文件和执行命令，请仅对可信用户开放。运行时缺失或目录 Agent 被关闭时，界面会显示原因。

对于 `run`、`server` 和 `export`，`model` 位置参数既可以是 Hugging Face 仓库 ID，也可以是本地 Hugging Face 模型目录、FastLLM 模型文件或配置文件。例如：

~~~bash
ftllm server /data/models/my-model --device cuda
~~~

### 调用 API

启动一个带固定服务名的本地模型：

~~~bash
ftllm server /data/models/my-model \
  --model_name local-model \
  --host 0.0.0.0 --port 8080 \
  --api_key local-key
~~~

通过 OpenAI Chat Completions 接口调用：

~~~bash
curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Authorization: Bearer local-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "local-model",
    "messages": [{"role": "user", "content": "你好，请介绍一下 FastLLM。"}],
    "stream": false
  }'
~~~

服务还提供 OpenAI Responses API 和 Anthropic Messages API。模型支持时，可使用思考内容分离、工具调用和图文输入等能力。

## 模型部署指南

如果由自动化 agent 部署，建议先把[模型簇与参数速查](docs/model-deployment-agent.md)交给 agent。Qwen3.8-27B-FP8 和已支持的混合模型可以使用 `--auto`，再根据日志做必要的参数覆盖；尚未验证的量化格式使用对应的手动配置。开发记录、测试入口和 Benchmark 记录规则见[开发与验证记录](docs/development-validation.md)。

不同模型在注意力结构、MoE 布局、投机解码和量化格式上差异很大。选择目标模型后，请先阅读对应部署指南，再使用后面的通用配置作为补充。

| 模型 | 部署指南 | 推荐配置入口 | Benchmark |
| --- | --- | --- | --- |
| Agent 快速部署 | [模型簇与参数速查](docs/model-deployment-agent.md) | `--auto`、按日志回退 | — |
| Qwen4-Exp / Qwen3.8-Flash-Next | [用户手册](docs/qwen3.8-flash-next/README.md) | 双卡串行 / TP2、专家缓存、固定 GPU 专家层、MTP | [本机实测性能](docs/qwen3.8-flash-next/README.md#performance) |
| Qwen3.5 / Qwen3.6 / Qwen3.8 | [Qwen 当前模型指南](docs/qwen3.md) | 单卡、TP、混合 MoE、MTP、DFlash2 | [Qwen3 Benchmark](docs/benchmarks/qwen3.md) |
| DeepSeek-V4 / V4-Flash | [DeepSeek-V4 指南](docs/deepseek.md) | CUDA + NUMA、磁盘专家、TP、内置 DSpark | [DeepSeek-V4 Benchmark](docs/benchmarks/deepseek_v4.md) |
| DeepSeek-V4.1-Flash 1017 | [DeepSeek-V4.1 指南](docs/deepseek_v41.md) | `--auto`、Engram、DSpark、资源限额 | [DeepSeek-V4.1 Benchmark](docs/benchmarks/deepseek_v41.md) |
| Kimi-K3 | [Kimi-K3 指南](docs/kimi_k3.md) | KDA/MLA、混合专家、磁盘专家、外部 DSpark | [Kimi-K3 Benchmark](docs/benchmarks/kimi_k3.md) |
| Dots3-Note | [Dots3-Note 指南](docs/dots3_note.md) | DSA、长上下文、CUDA + CPU/NUMA | [Dots3-Note Benchmark](docs/benchmarks/dots3_note.md) |
| GLM-5 / GLM-5.3-Flash | [GLM-5 指南](docs/glm5.md) | DSA/KDA、分页缓存、NUMA、量化 KV-B CPU | [GLM-5 Benchmark](docs/benchmarks/glm5.md) |
| Laguna | [Laguna 指南](docs/laguna.md) | 多卡 TP、混合 MoE、NVFP4、INT4_GROUP32 | [Laguna Benchmark](docs/benchmarks/laguna.md) |

[Benchmark 索引](docs/benchmark.md) 按模型分别记录测试硬件、完整启动命令、TTFT、Prefill、Decode 和并发吞吐。没有仓库实测的数据会明确标为“待实测”，不会从其他设备或模型外推。

## 典型部署配置

### 多卡张量并行

`--tp 0,1` 显式使用 0、1 号 GPU；`--tp 2` 表示使用前两张可见 GPU；`--tp auto` 自动使用检测到的 GPU。

~~~bash
ftllm server /data/models/my-model \
  --device cuda --tp 0,1 \
  --max_batch 16 --gpu_mem_ratio 0.9
~~~

### GPU + NUMA 混合 MoE

~~~bash
ftllm server /data/models/my-moe-model \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192
~~~

内存不足时还可以把少量专家层放到磁盘，例如 `--moe_device "{'cuda':1,'numa':8,'disk':1}"`。磁盘路径依赖 SSD 随机读取性能，详细配置参见[混合推理指南](docs/mixforward.md)。

### 长上下文与前缀缓存

`--max_context_length`（别名 `--max-context-length`）设置单会话输入与输出合计上限。扩大模型声明窗口时，还需要有效的 `--rope_scaling`（别名 `--rope-scaling`），接受 `yarn` 或 JSON；只缩小窗口时可省略 RoPE 参数。配置在加载时生效，不修改模型的 `config.json`。

例如，将 Qwen3-0.6B 扩展到 65536 token，并开启前缀缓存。其 YaRN 原始长度为 32768，应显式指定，不能用配置声明的 40960 代替：

~~~bash
ftllm server /data/models/Qwen3-0.6B \
  --device cuda --max_batch 1 --tokens 65536 \
  --max_context_length 65536 \
  --rope_scaling '{"rope_type":"yarn","factor":2,"original_max_position_embeddings":32768}' \
  --chunked_prefill_size 8192 \
  --prefix_cache true
~~~

Qwen3.8-27B-FP8 可以使用已知原始长度的 `yarn` 简写。下面配置双卡、FP4 KV 和 1,000,000 token 的目标窗口，解析得到 original=262144、factor=4：

~~~bash
ftllm server /data/models/Qwen3.8-27B-FP8 \
  --tp 2 --kv_cache_dtype fp4 \
  --max_context_length 1000000 --rope_scaling yarn
~~~

`--tokens` 是所有会话共享的 KV 池容量，未设置时自动预算。显式目标超过 RoPE 覆盖范围或 warmup 校准容量会启动失败；`/v1/models` 返回实际窗口、模型原声明和用户目标。上述 1M 命令需要足够显存，本机双 24GB 的测试配置无法容纳，完整 1M 输入尚未实测。

当前扩展支持 Qwen2、Qwen3、Qwen3.5 布局，以及基于 Qwen3.5 架构的 Qwen3.8；Launcher 高级参数中的「RoPE 扩展」使用相同配置。HF 目录和 GGUF 文件均可显式设置 `--rope_scaling`，仍受模型布局限制。GGUF、FLM 和自定义 GraphLLM 在 CLI 中仅设置长度时保留只缩小的行为；FLM 和自定义 GraphLLM 暂不支持 RoPE 扩展。更多参数、适配范围和验证结果见[上下文扩展说明](docs/context-length-extension.md)。

### 投机解码

以下功能只适用于结构和 checkpoint 匹配的模型：

~~~bash
# 以 Qwen3.5 为例使用内置 MTP，每轮最多配置 8 个 draft token
ftllm server /data/models/qwen3.5 --mtp 4

# 给不含 MTP 权重的 Qwen3.5 GGUF 挂载独立 MTP 模块
ftllm server /data/models/qwen3.5.gguf \
  --device cuda --cuda_embedding \
  --draft /data/models/qwen3.5-fp8/mtp.safetensors \
  --draft_tokens 5

# DeepSeek-V4 内置 DSpark
ftllm server /data/models/deepseek-v4 --dspark 7

# Qwen3.8 + 独立 DFlash2 draft checkpoint
ftllm server /data/models/qwen3.8 \
  --tp 2 \
  --draft /data/models/qwen3.8-dflash2 \
  --draft_tokens 7
~~~

`--draft` 会根据 draft checkpoint 自动识别 MTP、DFlash2 或 DSpark。独立 MTP 当前用于 Qwen3.5 GGUF，路径可指向包含 `config.json` 和 `mtp.safetensors` 的目录，也可直接指向 `mtp.safetensors`；省略 `--draft_tokens` 时默认使用 5。DFlash2 的 `--draft_tokens` 表示实际 draft token 数，不包含 anchor token。

DFlash2 的完整配置和验证结果见 [Qwen3.8 DFlash2 文档](docs/benchmarks/qwen38_27b_dflash2.md)。

### Qwen4 PLE 磁盘模式

Qwen4-Exp / Qwen3.8-Flash-Next 的 PLE 表较大。主机内存不足时可按需从 checkpoint 读取：

~~~bash
ftllm server /data/models/qwen4-exp \
  --device cuda --moe_device numa \
  --ngram_device disk
~~~

磁盘模式会降低常驻内存，但增加随机 I/O，建议使用高速 SSD。更多限制见 [Flash-Next 用户手册](docs/qwen3.8-flash-next/README.md)。

## 常用参数

CLI 会持续演进，`ftllm <command> --help` 是当前安装版本的最终依据。下面列出部署中最常用的参数。

### 模型、设备与精度

<a id="3-如何设定运行设备"></a>

| 参数 | 说明 |
| --- | --- |
| `model` / `-p, --path` | Hugging Face 仓库 ID、本地 HF 目录、FastLLM 模型文件或配置文件 |
| `--device` | 主计算设备，常用值为 `cpu`、`cuda`、`numa` |
| `--vision_device` | Qwen3.5 视觉编码器设备：`auto`（默认）、`cpu`、`cuda`、`cuda:N`；多卡 CUDA 跟随普通 TP，`cuda:N` 不单独覆盖；`cpu` 时视觉塔权重常驻内存，省显存但编码变慢 |
| `--tp` | CUDA 张量并行设备；支持 `0,1`、`2` 或 `auto` |
| `--moe_device` | MoE 专家层设备，可使用 `cpu`、`cuda`、`numa`、`disk` 或按比例组合 |
| `--moe_device_layers` | 仅让最后 N 个 MoE 层使用 `--moe_device`；`-1` 表示全部 |
| `-t, --threads` | CPU/NUMA 线程数；`-1` 表示自动选择 |
| `--dtype` | 加载 HF 权重时的权重类型；默认 `auto`，已量化模型通常不应覆盖 |
| `--moe_dtype` | 单独设置 MoE 权重类型 |
| `--atype` / `--moe_atype` | 设置普通层和 MoE 层的激活类型 |
| `--kv_cache_dtype` | KV Cache 类型：`auto`、`float16`、`bfloat16`、`fp8_e4m3` 或 `fp4`，需模型与后端支持 |
| `--dtype_config` | 动态量化配置文件，参见[动态量化说明](docs/dtype_config.md) |
| `--triton` | 启用可用的 Triton CUDA 算子 |

### 显存、上下文与调度

| 参数 | 默认值 | 说明 |
| --- | ---: | --- |
| `--gpu_mem_ratio` | `0.9` | 可用于模型与缓存的 GPU 显存比例 |
| `--auto_gpu_memory_limit` | 未设置 | `--auto` 时限制每张可见 GPU 的规划显存，例如 `20g`；未设置则读取实际可用显存 |
| `--auto_host_memory_limit` | 未设置 | `--auto` 时限制自动规划使用的主机内存总量，例如 `128g`；未设置则读取实际可用内存 |
| `--moe_cuda_cache` / `--moe-cuda-cache` | `0` | GPU 专家权重缓存预算，例如 `3g`（3 GiB）；`0` 关闭。配合 CUDA 主计算与 CPU/NUMA/disk 专家使用，KV Cache、普通权重及工作区另算；见[专家缓存说明](docs/cuda-expert-cache.md) |
| `--moe_cpu_cache` / `--moe-cpu-cache` | `0` | `--moe_device disk` 的专家内存缓存总预算，例如 `32g`；可与显存专家缓存同时使用，见[磁盘多级缓存](docs/cuda-expert-cache.md#disk-backed-multilevel-cache) |
| `--kv_cache_limit` | `auto` | KV Cache 最大使用量 |
| `--tokens` | 自动 | 用于计算 Paged KV Cache 容量的总 token 数 |
| `--page_size` | 后端决定 | Paged KV Cache 每页 token 数；多卡默认通常为 16 |
| `--max_batch` | 自动 | 每轮最多同时推理的请求数 |
| `--max_context_length` / `--max-context-length` | 自动 | 单会话输入与输出合计上限；HF 显式目标需通过 RoPE 与 KV 容量检查 |
| `--rope_scaling` / `--rope-scaling` | 沿用模型配置 | RoPE 扩展，接受 `yarn` 或 JSON；仅对已适配的 HF 模型布局生效 |
| `--chunked_prefill_size` | 关闭/模型决定 | 分块 Prefill 的切片大小，例如 `8192` |
| `--prefix_cache` | 模型/环境决定 | 是否开启前缀缓存，使用 `true` 或 `false` |
| `--cuda_slab` | `0` | CUDA 权重 slab 大小（MB）；`0` 为关闭 |
| `--ngram_device` | `cpu` | Qwen4 PLE 表放在 `cpu` 或 `disk` |

### 解码、模板与工具调用

<a id="工具调用"></a>

| 参数 | 说明 |
| --- | --- |
| `--enable_thinking` | 控制模型的思考模板开关，需要模型支持 |
| `--mtp` | 支持 MTP 的模型每轮生成的 draft token 数，`0` 关闭，当前最大为 8 |
| `--mtp_fp8_draft_head` / `--mtp-fp8-draft-head` | Qwen3.5 系列多卡 MTP 的 FP8 draft 输出头开关：`1` 开启，`0` 复用原输出头以节省显存，可能降低生成速度；不改变 MTP draft 数。显式参数优先于 `FASTLLM_MTP_FP8_DRAFT_HEAD`，未指定时沿用环境变量，默认开启 |
| `--dspark` | 启用模型内置 DSpark，并设置每轮 draft token 数 |
| `--draft` / `--draft_model_path` | 外部 MTP/DSpark/DFlash draft checkpoint；根据配置自动识别算法，MTP 可直接指定 `mtp.safetensors` |
| `--draft_tokens` | 每轮最多使用的 draft token 数；未指定时读取 draft 配置 |
| `--tool_call_parser` | 工具调用解析器；默认 `auto` |
| `--chat_template` | 自定义 Jinja chat template 文件 |
| `--cache_dir` | 在线模型的本地缓存目录 |
| `--ori` | 读取部分 GGUF 时指定原模型配置和 tokenizer 目录 |
| `--mmproj` | Qwen3.5 架构族 GGUF 的配套视觉模块文件；配置要求与示例见 [GGUF 多模态](docs/qwen3.md#gguf-multimodal) |

Qwen4 CUDA 推理将 MTP 草稿专家常驻 GPU：单卡使用该卡，PP 使用最后一个 CUDA 阶段，TP 在各卡常驻对应分片。草稿专家独立于 `--moe_cuda_cache` 的目标模型专家缓存预算，需要额外显存；CPU 推理沿用 MoE 设备配置。

Qwen3.5 系列的 MTP 和 DFlash 草稿支持以下设置。启用相应的草稿算法后，兼容路径默认使用 NVFP4；环境变量可在启动前覆盖默认值：

| 环境变量 | 默认值 | 说明 |
| --- | --- | --- |
| `FASTLLM_DRAFT_QUANT` | `nvfp4` | `off` 关闭 NVFP4 转换；`nvfp4_head` 只转换独立草稿输出头；`nvfp4` 转换草稿主干和输出头。目标模型输出头保持原权重，不支持的设备、类型或形状保留原路径 |
| `FASTLLM_CUDA_NVFP4_SWIGLU_MULTIROW` | `1` | 允许多行 NVFP4 Linear + SwiGLU 融合，`0` 关闭。当前多行内核仅支持 SM75、FP16 输入、M=2～8，并检查形状、布局和临时空间；其他情况回退 |
| `FASTLLM_TP_NVFP4_MLP_SWIGLU` | `1` | TP MLP 尝试 NVFP4 Linear + SwiGLU 融合，`0` 关闭；由底层能力检查选择内核，不支持时执行原 Linear + SwiGLU |
| `FASTLLM_TP_NATIVE_GREEDY` | `1` | Qwen3.5 TP 贪心采样支持原生类型 logits；eager 推测验证省去 FP16→FP32 转换，`0` 关闭。随机采样、返回 logits、Graph 和 GPU token handoff 仍保持 FP32 路径 |
| `FASTLLM_DFLASH_BATCH_PREFIX_SNAPSHOTS` | `1` | DFlash2 CUDA 批量验证按各请求接受长度恢复线性状态，避免拒绝后的主模型重算。max_batch≤4 使用逐位置状态快照；更大配置保存紧凑激活、批量恢复卷积/GDN 状态，实际 batch 缩小时仍沿用该路径，无 16 路上限。自动预留恢复缓冲与草稿滑窗 KV 显存，相应减少主模型 KV 容量。`0` 回退到完整前缀重算；单请求路径不变。不限定 SM；状态算子已在 SM75 验证到 32 路 |
| `FASTLLM_MTP_BATCH_SAMPLING` | `1` | Qwen3.5 CUDA MTP 将多请求的草稿采样和主模型拒绝采样合批，直接读取各请求独立的草稿缓存，合并结果回读。混合贪心/采样或不兼容验证长度保留原路径；`0` 恢复逐请求采样。保持采样分布，不保证与逐请求路径随机输出逐 token 相同 |
| `FASTLLM_MTP_FP8_MARLIN` | `1` | Qwen3.5 TP MTP 在初始化时为符合已有 Marlin 条件的 FP8 草稿分片准备计算布局，避免多行草稿首次调用错过布局转换。不改变权重量化格式，保留现有架构/后端选择；`0` 恢复延迟准备。已在 SM75/TP2 验证 |
| `FASTLLM_CUDA_GDN_SEQUENCE_PREPARE` | `1` | 批量短序列 GDN 在 K/V 维度均为 128、batch≥4 的 eager 路径预计算 Q/K 归一化与门控系数，避免各状态分块重复计算。保留逐 token FP16 状态舍入及前缀快照；小批量、其他 V 维度和 Graph 保留旧路径。`0` 关闭；已在 SM75/TP2 验证 |
| `FASTLLM_MTP_DRAFT_TOKEN_IDS` | 未设置 | 可选的单卡或多卡 MTP 草稿词表 token ID 文件，仅在启用 NVFP4 转换时使用；未设置或为 `0` 时使用完整词表。多卡按原词表分片筛选并映射回全局 token ID，对齐填充只重复已有候选。筛选词表只用于贪心草稿，随机采样使用完整输出头，目标模型仍使用完整词表验证 |
| `FASTLLM_DFLASH_DRAFT_TOKEN_IDS` | 未设置 | DFlash2 贪心请求的可选 token ID 文本清单（空白分隔）。单卡支持原编码 GGUF 或 NVFP4 草稿头，多卡支持 NVFP4；每个连续词表分片至少需有 selector top-k 个候选。对齐填充在 top-k 前移除，选择后恢复原 token ID。完整目标头保留，缩表额外占用草稿头显存，覆盖不足可能降低接受率；启用前应验证任务速度与输出一致性。随机采样使用完整词表；非法或不支持的清单回退完整词表。`FASTLLM_DRAFT_QUANT=off` 时不启用 |
| `FASTLLM_DFLASH_ATTENTION` | 未设置：SM75 开，其余关 | DFlash FP16 融合滑窗 attention 的统一开关：`0` 关闭，`1` 在支持的设备上开启。要求 head_dim=128、query 数 1～16、query 数×GQA 分组数≤64、query 数≤窗口≤4096，且 FlashInfer 可用；不匹配时回退原路径。SM80 及以上的 Q64 路径尚无实机正确性或速度验证；SM70 及以下始终保持原路径 |

DFlash2 的动态卷积、QKV 和 Gateup 准备会根据设备、类型和形状自动选择融合实现，不支持时回退到常规算子；TP selector 投影固定在 rank 0 输出头工作流中提前提交。旧开关 `FASTLLM_CUDA_DFLASH_FUSED_CONV`、`FASTLLM_CUDA_DFLASH_FUSED_QKV_PREPARE`、`FASTLLM_CUDA_DFLASH_FUSED_GATEUP_PREPARE` 和 `FASTLLM_DFLASH_TP_EARLY_SELECTOR` 已移除，设置它们不再影响执行路径。

未覆盖的设置使用表中默认值；内核本身的能力限制仍然有效。单卡和多卡均可使用草稿 NVFP4；多卡在切分后转换符合条件的分片。MTP 限于稠密模型、FP16 计算；DFlash 的量化 Linear 通过 FP16 适配后恢复原激活类型。量化会影响草稿接受率，独立输出头和视图副本也会改变显存占用，不保证所有模型提速；目标模型权重与完整词表验证保持原样。可用 `FASTLLM_DRAFT_QUANT=off` 保留原草稿精度，既有多卡 FP8 草稿输出头开关仍有效。

NVFP4 小矩阵解码默认在 SM75、68 个 SM 的设备（如 RTX 2080 Ti）上启用已验证的调优，覆盖 M=1～8、N×K 为 17408×5120 的融合 SwiGLU，以及 5120×8704、5120×3072 的 Linear。运行时还需满足线程块驻留条件；其他架构、形状和原始 M>8 的 prefill 保持原路径。可用 `FASTLLM_CUDA_NVFP4_SM75_DECODE_TUNE=0` 关闭，`1` 显式开启全部，或用 `linear` / `swiglu` 仅开启对应部分。多行 SwiGLU 的融合入口仍由 `FASTLLM_CUDA_NVFP4_SWIGLU_MULTIROW` 单独控制。

### API Server

| 参数 | 默认值 | 说明 |
| --- | ---: | --- |
| `--host` | `0.0.0.0` | 监听地址 |
| `--port` | `8080` | Server 端口；WebUI 默认端口为 1616 |
| `--model_name` | 自动 | API 中校验和返回的部署名称 |
| `--api_key` | 空 | 非空时开启 Bearer API Key 校验 |
| `--temperature` / `--top_p` / `--top_k` | 模型默认 | 覆盖服务端默认采样参数 |
| `--repeat_penalty` | 模型默认 | 覆盖重复惩罚参数，也支持 `--repetition_penalty` |
| `--hide_input` | 关闭 | 不在服务日志中显示请求内容 |
| `--startup-progress` | `off` | 设置为 `ndjson` 时向 stderr 输出模型加载与就绪事件 |

查看完整参数：

~~~bash
ftllm --help
ftllm server --help
ftllm bench --help
ftllm download --help
~~~

## 模型格式、下载与导出

### 支持的输入格式

- Hugging Face 原始 Safetensors 权重，包括模型自带的 FP16、BF16 或 FP8 权重。
- 已量化的 AWQ 模型。
- FastLLM 导出的定精度或动态量化模型。
- 部分 GGUF 格式；已适配的模型可直接读取内置配置和 tokenizer，也可通过 `--ori` 指定原模型目录。Qwen3.5 架构族的 GGUF 使用独立视觉模块时，还需指定 `--mmproj` 并提供匹配的视觉配置，见 [GGUF 多模态](docs/qwen3.md#gguf-multimodal)。

量化格式是否可用取决于模型结构、设备和对应 kernel。首次部署建议保留 `--dtype auto`；对于已经量化的 checkpoint，不要再次指定在线量化类型。

### 下载模型

~~~bash
ftllm download <repo-id> --local-dir /data/models/model-name
~~~

只下载配置和 tokenizer 文件时可以排除权重：

~~~bash
ftllm download <repo-id> \
  --exclude "*safetensors*" \
  --local-dir /data/models/model-config
~~~

### 导出模型

在线量化会增加每次启动的加载时间。可以预先导出 FastLLM 格式：

~~~bash
ftllm export /data/models/source-model \
  -o /data/models/source-model-int4 \
  --dtype int4 -t 16
~~~

MoE 模型可以分别设置普通层和专家层精度：

~~~bash
ftllm export /data/models/source-moe \
  -o /data/models/source-moe-mixed \
  --dtype float16 --moe_dtype int4 -t 16
~~~

动态量化配置见[动态量化说明](docs/dtype_config.md)。

## 源码安装

源码构建需要 C++17 编译器、Make 和 CMake；建议 GCC/G++ 9.4+、CMake 3.23+。Linux NUMA 构建通常还需要 `libnuma-dev`。CUDA 构建请预先安装兼容的 CUDA Toolkit 和 NCCL。

~~~bash
# Ubuntu/Debian 基础依赖
sudo apt-get install -y build-essential cmake libnuma-dev

# NVIDIA CUDA
bash install.sh -DUSE_CUDA=ON \
  -DCMAKE_CUDA_COMPILER="$(command -v nvcc)"

# 指定 CUDA 架构，例如 Ada 使用 89
bash install.sh -DUSE_CUDA=ON -DCUDA_ARCH=89 \
  -DCMAKE_CUDA_COMPILER="$(command -v nvcc)"

# CPU-only
bash install.sh
~~~

更多平台说明：

- [ROCm 编译与 wheel 打包](docs/rocm.md)
- [TFACC 平台](docs/tfacc.md)
- [示例程序、Android 和其他平台](example/README.md)
- [编译与运行 FAQ](docs/faq.md)

## 文档导航

| 主题 | 文档 |
| --- | --- |
| 发布说明 | [稳定版日志](docs/version.md) · [Nightly 使用](docs/nightly.md) · [Nightly 日志](docs/nightly_changelog.md) |
| 模型部署 | [Qwen3.8-Flash-Next](docs/qwen3.8-flash-next/README.md) · [Qwen3.5/3.6/3.8](docs/qwen3.md) · [DeepSeek-V4](docs/deepseek.md) · [Kimi-K3](docs/kimi_k3.md) · [Dots3-Note](docs/dots3_note.md) · [GLM-5](docs/glm5.md) · [Laguna](docs/laguna.md) |
| 混合推理 | [GPU、NUMA 与磁盘混合部署](docs/mixforward.md) |
| 性能与验证 | [按模型查看 Benchmark](docs/benchmark.md) |
| 量化 | [动态量化配置](docs/dtype_config.md) |
| 扩展开发 | [Python 自定义模型](docs/custom.md) · [自定义算子](docs/custom_op.md) |
| 平台与排错 | [ROCm](docs/rocm.md) · [TFACC](docs/tfacc.md) · [FAQ](docs/faq.md) |

## 社区与贡献

欢迎通过 [GitHub Issues](https://github.com/ztxz16/fastllm/issues) 报告问题，通过 [Pull Requests](https://github.com/ztxz16/fastllm/pulls) 参与开发。

部署交流 QQ 群：`831641348`

用户交流群（使用、部署）：

<img src="docs/wechat_group0.jpg" width="220" alt="FastLLM 用户交流群二维码">

社区开发群（贡献、开发讨论）：

<img src="docs/develop-group.png" width="220" alt="FastLLM 社区开发群二维码">

项目采用 [Apache License 2.0](LICENSE)。

## 参考代码和文章

FastLLM 的实现参考或使用了以下开源项目与文章中的思路或代码：

- [PyTorch](https://github.com/pytorch/pytorch)：底层算子实现思路。
- [Transformers](https://github.com/huggingface/transformers)：模型结构与参考实现。
- [llama.cpp](https://github.com/ggml-org/llama.cpp) 和 [ik_llama.cpp](https://github.com/ikawrakow/ik_llama.cpp)：GGUF 量化格式与 kernel。
- [KVMem / kvmem-llama.cpp](https://github.com/kvmem/kvmem-llama.cpp)：分层 KV 缓存与内容检索的设计参考，包括固定 GPU 工作集、主机 KV 备份和 pre-RoPE mean-K 检索。FastLLM 接入说明见 [KVMem 文档](docs/kvmem.md)。
- [FlashInfer](https://github.com/flashinfer-ai/flashinfer)：Attention、MLA 等算子。
- [TurboMind / LMDeploy 的 GEMM 内核](https://github.com/InternLM/lmdeploy/tree/main/src/turbomind/kernels/gemm)：仓内 `third_party/turbomind` 的 SM70 s884（`SM70_MMA_884` / HMMA 8x8x4）核心由其源码移植，并在其上接入 AWQ INT4、块缩放 FP8 与 NVFP4 Linear。
- [1Cat-vLLM 的 SM70 TurboMind 适配](https://github.com/1CatAI/1Cat-vLLM/tree/main/csrc/sm70_turbomind)：AWQ 接入及 FP8/NVFP4 类型、布局和小批量 tactic 的参考来源；FastLLM 侧另行实现了无 Torch 的原始指针桥接、模型权重格式转换、非对齐 padding、回退和调度。
- [KTransformers](https://github.com/kvcache-ai/ktransformers/blob/main/csrc/ktransformers_ext/cpu_backend/backend.cpp)：MoE 动态线程调度；另见[思路介绍](https://zhuanlan.zhihu.com/p/1900318746402329329)。
- [Lvllm](https://github.com/guqiong96/Lvllm/blob/main/csrc/lk/moe.cpp)：NUMA MoE 动态调度。
- [FreeToken](https://github.com/FlashML-org/FreeToken)：CUDA 专家缓存及相关混合推理优化的设计参考，包括 GPU 端路由与 LRU 缓存管理、主机专家权重按需回填，以及 CUDA Graph 兼容的执行流程。具体实现与扩展方式见 [CUDA 专家缓存说明](docs/cuda-expert-cache.md)。
- [vLLM](https://github.com/vllm-project/vllm/tree/main/vllm/entrypoints/openai/tool_parsers)：工具调用解析。
- [json11](https://github.com/dropbox/json11)：JSON 构造与解析。

感谢所有开源贡献者。如发现遗漏的代码来源或引用，请通过 Issue 告知。

---

### Overview (`desktop/README.md`)

# FastLLM Launcher 桌面绿色包

此目录把现有 `ftllm launch` 网页界面嵌入 Electron，并复用仓库根目录的
`make_portable.sh` 生成完整 FastLLM 运行时。最终用户不需要安装 Electron、Node.js、
Python、CUDA Toolkit、cuBLAS、NCCL 或 Python 包。
包内同时包含 Pi Agent 和 FastLLM 桥接扩展，启动模型后可直接使用“工作室”中的目录 Agent。
目录搜索所需的 ripgrep、fd 也会随包提供，不依赖目标机器预先安装这些工具。

## 使用入口

解压后双击根目录应用即可使用，无需先运行初始化命令：

```text
Fastllm-Launcher     桌面应用；FastLLM 速度 F + 显示器与启动箭头 + APP
ftllm-launch-webui   网页服务；FastLLM 速度 F + 浏览器与网络地球 + WEB
ftllm               命令行入口；无自定义图标，双击打开已配置环境的终端
launch.sh           兼容网页启动脚本
README.html         离线使用文档；双击用浏览器阅读
support/            运行时、依赖、图标和辅助工具
```

三个应用通过 `entrypoint.c` 编译为原生 ELF，执行权限随 tar 归档保留，
文件管理器可以直接启动。通过 `/proc/self/exe` 定位包目录，支持整体移动、
中文和空格路径，以及包外的符号链接。`support/entrypoint.sh` 负责启动实际程序；
在图形会话中无参数、无终端启动 `ftllm` 或 `ftllm-launch-webui` 时，自动打开系统终端。
带参数的命令行调用与无桌面运行保持原有语义。

不使用隐藏目录或 `.hidden` 列表。Electron/Python 运行时、动态库、辅助工具、
许可证和构建清单都位于 `support/`，Python 位于 `support/runtime/`。

首次在桌面会话中启动应用时，在后台注册 `support/icons/` 中的两枚 SVG 图标，
并通过 GIO 设置文件图标。启动不会等待图标初始化，不需要 sudo，也不更改包内文件内容。
只有需要手动修复图标时才使用 `./ftllm --setup-desktop`。Electron 窗口继续使用 PNG 图标。
辅助 `.desktop` 文件放在 `support/desktop/`，遵循
[Desktop Entry 规范](https://specifications.freedesktop.org/desktop-entry/latest/)，不作为首次启动入口。

只写 `.desktop` 的 `Icon` 字段不足以让所有文件管理器显示图标。例如 Nautilus 将
普通目录中的桌面文件按文档展示，它的自定义文件图标使用独立的
[元数据](https://github.com/GNOME/nautilus/blob/gnome-42/src/nautilus-file.c)。
这些用户元数据不能随 tar 包迁移，因此首次启动前可能显示系统通用应用图标。
原生入口可以直接运行；启动后自动配置并通知文件管理器刷新图标。
`ftllm` 保留系统默认图标；初始化时会清除旧版为它设置的自定义图标。
HTML 文档的样式和插图全部随包提供，断网也能正常阅读。
两枚图标沿用 `tools/fastllm_pytools/fastllm_icon.svg` 的 F 和速度线，强调 FastLLM 的快速推理特点。
文档中的“详细参数手册”链接到包内 `support/docs/parameters.html`，完全离线可读。
`build_parameter_docs.py` 使用包内 Python 和已安装的 CLI 解析器生成参数名、别名、默认值与取值类型，
再结合中文说明、逐项写法与完整启动示例，覆盖模型、设备、量化、缓存、MoE、投机解码、服务和工具参数。
页面模板为 `BUNDLE-PARAMETERS.html.in`；新增 CLI 选项也会出现在手册中。

无桌面服务器可直接使用 `./ftllm server ...`、`./launch.sh --no-browser`。
`source ./support/env.sh` 后可直接输入 `ftllm`、`ftllm-check` 等命令；
双击 `ftllm` 打开的终端会自动启用这个环境。完整使用示例见成品根目录 `README.html`。

## 构建

先生成当前源码对应的 wheel，再打桌面包：

```bash
./make_whl.sh
./desktop/package.sh --force
```

也可以显式指定已有 wheel，或让脚本先调用 `make_whl.sh`：

```bash
./desktop/package.sh --wheel /path/to/ftllm.whl
./desktop/package.sh --build-wheel
```

默认输出到 `portable-dist/`，产物是 `.tar.gz` 和对应的 `.sha256`。常用选项：

```text
--keep-dir       同时保留未压缩目录
--offline        只使用已有的 Python/Electron/Pi/搜索工具下载缓存和 wheelhouse
--skip-tests     跳过运行时冒烟测试
--format tar.zst 改用 zstd 压缩
```

构建机要求 Linux x86_64、C 编译器（默认 `cc`，可通过 `CC` 指定）、Python 3、
curl 或 wget、unzip、readelf、ldd 和常规 GNU 归档工具。建议在项目支持的最低发行版
Ubuntu 22.04 上构建，因为脚本会把 Electron
依赖的非 glibc 系统动态库一并收集，并拒绝高于 GLIBC 2.35 的 ELF。Electron 固定到
经过 SHA256 校验的官方预编译版本；升级时必须同时更新版本与校验值。

Pi 使用 `tools/ftllm_agent_runtime/scripts/fetch_pi.py` 固定并校验的版本。
打包时从当前仓库构建 Agent wheel，构建工具在临时环境中安装；下载缓存位于
`build-portable-cache/`。完整在线构建一次后可使用 `--offline` 复用缓存。
默认测试包括包内 Pi 的实际启动、临时项目文件读取及本地模拟模型的工具调用往返。
动态库收集同时包含 NSS 运行时加载的模块，以保证 Electron 能初始化证书和网络功能。

## 设计边界

- 包是可移动目录，不写系统安装路径；Electron 默认把数据放在包内 `support/data/`。
  浏览器 Launcher 和命令行沿用当前用户的配置目录。复制或迁移时应保留整个目录。
- Launcher 只监听 `127.0.0.1`，Electron 自动附加控制令牌并阻止主窗口跳转到外部页面。
- NVIDIA 驱动库和 glibc 不会打包。驱动必须与内核匹配，而 glibc 必须与宿主系统动态
  加载器保持一致；构建时会扫描整个成品并拒绝混入 `libcuda.so` 或其他 NVIDIA 驱动库。
- 最终用户需要满足基线的 Linux/glibc；仅 Electron 桌面窗口需要 X11/Wayland。
  `ftllm server`、`ftllm launch --no-browser` 和终端向导均可在无桌面的机器上使用。

---

### Overview (`docs/README.md`)

# 文档索引

- [快速开始](../README.md)
- [模型部署指南](../README.md#模型部署指南)
- [Benchmark](benchmark.md)：按模型保存最终结果及测量范围
- [开发与验证记录](development-validation.md)：文档分工、测试入口和 Benchmark 记录要求
- [Agent 部署速查](model-deployment-agent.md)：建议交给自动化 agent 先阅读
- [执行模式与模块边界](execution_modes.md)
- [混合推理](mixforward.md)与 [CUDA 专家缓存](cuda-expert-cache.md)
- [Benchmark 工具](../test/benchmark/README.md)
- [上下文扩展](context-length-extension.md)与 [KVMem](kvmem.md)
- [版本日志](version.md)

## 内容约定

部署文档说明当前参数、支持范围、回退行为和验证入口；性能结果集中在对应模型的 Benchmark 页面。
结果保留硬件、权重格式、被测版本（有记录时）、输入输出长度、并发、缓存/MTP 设置、计时口径和限制。没有源码版本的旧测量要明确注明，不能表述为当前 HEAD 的性能。

同一配置的结果更新时替换正文中的结论，不追加逐轮优化日记。未采用方案、临时调试命令、原始日志、profiler 波形、完整生成输出和跨框架性能对比不随文档提交；这些材料放在仓库外的实验归档中。
`test/` 保留可执行回归、必要夹具和运行说明。独立数值参考、第三方许可及来源说明保留，正式版本变更使用版本日志维护。

---

### Overview (`example/README.md`)

# example 示例项目

## Benchmark

测速示例程序，方便大家测试不同软硬件下的推理性能。作者测试的速度可以参考[这里](../docs/benchmark.md)。

由于实际使用时很难满足batch的条件，也并非贪婪解码，该速度与真实使用时的速度有一定差异。

### 使用方法：

CPU：

   `./benchmark -p chatglm-6b-int4.flm -f prompts.txt -t [线程数] --batch [Batch大小]`

GPU：

   `./benchmark -p chatglm-6b-int4.flm -f prompts.txt --batch [Batch大小]`


## Web UI 

由 Jacques CHEN 提供，鸣谢！

## Win32Demo (Windows平台)

Win32Demo，是windows平台上运行FastLLM程序的一个Visual Studio工程。
由于Windows控制台默认编码为ANSI（中文是GBK编码，code page 936），而FastLLM默认输入输出编码为UTF-8，故与`main`存在一些差异，特提供专门的版本。为防止部分token是半个字符（如BPE编码），目前连续的中文字符是一并输出的。

生成的exe位置为：`Win32Demo\bin\Win32Demo.exe`

请尽量编译Release版本，速度快！

除此之外提供了fastllm的.vcproj文件，带GPU支持，本项目最低可在Visual Studio 2015 Update 3 下编译通过。
（但是**编译pyfastllm至少需要 MSVC 2017**）

### 编译

fastllm工程目前分为CPU版本和GPU版本，为简单上手，在没有cmake时，本项目可以使用Visual Studio工程文件并配置预处理器定义开关功能项。默认使用CPU版本。

签出代码后，**修改 include/fastllm.h**，Visual Studio中点击”文件“ -> "高级保存选项"，在编码中选择”Unicode (UTF-8 **带签名**) -代码页 65001“，或在其他文本编辑器中转为”UTF-8 BOM“编码。（由于linux下gcc不识别BOM头，该修改只能手动处理。）

* **CPU版本**：
  * 如果本机没有安装CUDA，在Win32Demo项目“属性”中找到"链接器" -> "输入" -> "附加依赖项"，点击'从父级或项目设置继承'。

* **GPU版本**：
  - 需要正确安装CUDA及其中的Visual Studio Integration；
  - 正确配置CUDA_PATH环境变量，指向要编译的CUDA版本；
  - 在解决方案资源管理器中移除fastllm.vcproj，引入fastllm-gpu.vcproj，
  - 对fastllm-gpu项目，在”生成依赖项“ -> "生成自定义" 中手动添加已安装的CUDA的自定义项文件；
  - 对fastllm-gpu项目，在”属性“中找到"CUDA C/C++" -> "Device" -> "Code Generation" 中配置编译后支持的[GPU计算能力](https://developer.nvidia.com/cuda-gpus#compute)；
  - 在Win32Demo项目上选择”添加“ -> "引用“，勾选fastllm-gpu项目；
  - 在Win32Demo项目上配置预处理器定义”USE_CUDA“。

### 使用方法：

1. 打开命令提示符cmd；

2. `cd example\Win32Demo\bin` ；

3. 运行时参数与`main`基本一致。但多一个参数 `-w` ，表示启动webui,不加为控制台运行。如：

   `Win32Demo.exe -p c:\chatglm-6b-v1.1-int4.flm -w`

## Android (android平台)
Android，使用Android studio工具建立的一個Android平台上运行LLM程序的例子。

### 使用方法：

1.在Android Studio直接打开工程运行。

2.直接下载release目录里里面的apk体验。

3.可以通过CMake工具链编译main文件(具体步骤见主页的readme)，通过adb shell运行，

1. `adb push main /data/local/tmp` 将main文件放到手机的tmp文件夹，
2. `adb shell` ,
3. `cd /data/local/tmp` 
4. `./main` 运行。

注意：demo apk 会将模型文件复制到应用 data 目录以方便 native 读取，因此设备需准备至少两倍模型大小的空余空间。

---

### Overview (`pyfastllm/README.md`)

# pyfastllm 

by [wildkid1024](https://github.com/wildkid1024) 

pyfastllm是基于fastllm的python api接口实现，通过pyfastllm可以更加灵活地编码实现pythonic场景，满足更复杂更个性化的业务需求。

- 对接fastapi、flask等web框架，向外提供数据接口
- 利用python yield生成器语言特性，流式问答响应
- 类似于torch的python低级接口，目前支持到cpu版本
- 对接Lora、Ptuning等微调方法，下游任务可微调(开发中...)
- 无缝对接加速HugingFace模型库，无痛加速迁移原有业务代码(开发中...)
- 其他更多...

## 版本更新


### 已知BUG
1. 从cpp到python存在内存拷贝
2. 由于1的问题，fastllm后端采用的深拷贝策略，cuda data将被忽略
3. 每个op都将转化为Host端返回，GPU内存释放存在问题


### v0.2.1.1 2024-03-13
- 解决了numpy转换的一些bug
- 增加了一些Module
- 增加了op测试
- 完整测试并支持chatglm2


### v0.2.1 2024-03-08
- 增加了低级python接口
- 测试低级接口，实现了纯python版本的chatglm2
- 增加了一些新的op


### v0.2.0 2023-10-23

- 代码结构调整优化
- 增加了模型转换和量化接口

### v0.1.5 2023-10-13

- 修复wheel编译安装部分
- 文件合并，修复导入

### v0.1.4 2023-09-12

- 修复了一些后端接口变动的bug
- 增加了新的ops, 支持低级op操作


### v0.1.3 2023-07-08

- 增加使用和API接口文档
- 增加fastllm-convert模型转换接口及命令行转化工具
- 修复部分因为cpp新接口导致的bug


## 编译安装

本地编译安装fastllm的python接口，以两种方式编译运行：
1. 动态库方式：编译为动态库，需放在python运行加载目录下
2. wheel包方式：编译为wheel包，安装在python的site-packages下，但暂不支持cuda

### 动态库方式

> 动态库安装方式暂不支持模型转换

首先下载pybind11 c++依赖:

```shell
git submodule init 
git submodule update  # 下载pybind11依赖
```

Cpp手动编译：

```shell
mkdir build-py
cd build-py
cmake .. -DUSE_CUDA=ON -DPY_API=ON
make -j
cp fastllm*.so pyfastllm/examples/  # 或放置在$PYTHONPATH环境变量包含的的目录中
cd ../pyfastllm/examples/
python3 cli_simple.py -p chatglm-6b-int8.flm  # 与cpp编译的运行结果保持一致
```

### wheel包方式

> 注意wheel包安装方式暂不支持cuda 

首先下载pybind11：

```shell
pip install pybind11
```

- GPU
```shell
cd pyfastllm/
python3 setup.py build
python3 setup.py install 
cd examples/
python3 cli_simple.py -p chatglm-6b-int8.flm
```

- CPU
```shell
cd pyfastllm/
export USE_CUDA=OFF
python3 setup.py build
python3 setup.py install 
cd examples/
python3 cli_simple.py -p chatglm-6b-int8.flm -t 8
```

## 使用

### python 调用
在examples文件夹中存放了几种常见的代码示例：

- `examples/cli_simple.py`: 调用api接口示例(推荐)
- `examples/cli_low_api.py`: 底层API调用示例
- `examples/convert_model.py`: 模型转换示例
- `examples/web_api.py`, `examples/web_api_client.py`: fastapi webapi调用
- `examples/test_ops.py`: 部分op的使用样例及测试
- `examples/chatglm2.py`: 低级python接口下的chatglm2模型(目前仅支持cpu)

### 命令行工具

使用命令行工具对模型进行转换，使用方法与convert_model.py类似：

```sh
$ fastllm-convert --help
$ fastllm-convert -m chatglm6B -p hf_model_path -o output_flm_path  
```

### 动态batch使用示例
```sh
mkdir build-py
cd build-py && cmake .. -DPY_API=ON -DUSE_CUDA=ON && make -j && cd -
cd pyfastllm/examples
python web_api.py -m 0 -p path_for_chatglm --max_batch_size 32
```
可以使用locust进行压测。A100 40G，chatglm fp16 压测部分结果如下：
|    并发数 | 平均调用时间(s) | TP95(s) | TP99(s) |
|----------:|-------|------|------|
| 1         |  3.07 |  4.2 |  4.8 |
| 10        |  6.11 | 11.0 | 12.0 |
| 16        |  6.82 | 15.0 | 16.0 |
| 32        | 10.74 | 16.0 | 20.0 |
## API编程接口

### fastllm数据结构

> fattllm.Tensor数据类型
- fastllm.float32
- fastllm.bfloat16
- fastllm.int16
- fastllm.int8
- fastllm.int4
- fastllm.int2
- fastllm.float16

> fastllm.Tensor: fastllm基础张量结构
- fastllm.Tensor()
- fastllm.Tensor(Datatype)
- fastllm.Tensor(Datatype, Dims:list[int])
- fastllm.Tensor(Datatype, Dims:list[int], Data:list[float])
- fastllm.Tensor(Data:fastllm.Tensor)
- fastllm.Tensor.to_list() # 将Tensor转化list并返回
- fastllm.Tensor.to() # 将Tensor转移到对应设备上
- fastllm.Tensor.zeros(Dims:list[int]) # 按照Dims生成全零矩阵
- fastllm.Tensor.cat(Data:list[fastllm.Tensor], axis:int) # 将Tensor按照axis(默认为0)方向上拼接

### fastllm函数

> fastllm.get_llm_type(model_path:str)->str # 获取当前model的类型
> fastllm.set_threads(thread:int) -> None # 设置当前运行线程数，默认为4
> fastllm.get_threads()->int  # 获取当前运行线程数
> fastllm.set_low_memory(flag:bool) # 低内存模式下运行，默认为False
> fastllm.get_low_memory() # 查看当前是否为低内存运行模式
> fastllm.create_llm(model_path: str)-> fastllm.model  # 从本地权重文件生成对应的模型实例，基于规则匹配

### fastllm模块

> fastllm.Tokenizer: 分词及编解码工具
> Tips: 该类不可直接实例化，只可通过model.weight.tokenizer访问具体实例
- fastllm.Tokenizer.encode(prompt:str) # 将prompt分词并进行编码
- fastllm.Tokenizer.decode(output_ids:fastllm.Tensor) # 将fastllm.Tensor解码为对应字符串
- fastllm.Tokenizer.decode(output_ids: list[int]) # 将list[int]解码为对应的字符串
- fastllm.Tokenizer.decode_byte(output_ids: fastllm.Tensor) # 将Tensor解码对应字节流

> fastllm.WeightMap: 模型的权重词典
> Tips: 该类不可直接实例化，只可通过model.weight访问具体实例
- fastllm.WeightMap.tokenizer： 访问权重中的tokenizer实例
- fastllm.WeightMap.save_lowbit(output_path:str, bit:int)：量化并保存低bit的权重
- fastllm.WeightMap.set_kv(key:str, value:str)：设置模型的weight字典
- fastllm.WeightMap.set_weight(key:str, )：为weight添加具体Tensor
- .fastllm.WeightMap\['key'\]: 根据key的名称得到对应的Tensor

### fastllm模型

> fastllm.ChatGLMModel: 具体模型实例，其中chatglm可以更换为llama、alpaca、Moss等模型
- fastllm.ChatGLMModel.model_type: 模型类型属性，区分不同的模型
- fastllm.ChatGLMModel.weight：对应的weightmap
- fastllm.ChatGLMModel.block_cnt：模型中block的数量
- fastllm.ChatGLMModel() # 初始化模型实例
- __call__(input_ids:fastllm.Tensor, attention_mask:fastllm.Tensor, position_ids:fastllm.Tensor, penalty_factor:fastllm.Tensor, pastKeyValues:memory_view) # 以类call function的方式调用模型进行推理 
- fastllm.ChatGLMModel.load_weights(model_path:str) # 从文件路径中加载模型权重
- fastllm.ChatGLMMode.make_history(history:str, round:int, input:str, output:str) # 基于历史对话和当前输入输出构造送入模型的历史对话
- fastllm.ChatGLMMode.make_input(history:str, round:int, input:str) # 基于历史对话和当前输入构造送入模型的对话输入
- fastllm.ChatGLMModel.response(inputs:str, callback:function) # 发送字符串到模型中并使用callback函数接受处理返回的答案
- fastllm.ChatGLMModel.response_batch(inputs:list[str], callback:function) -> outputs:list[str] # 发送列表字符串到模型中并使用callback函数接受处理返回的答案
- fastllm.ChatGLMModel.warmup()  # GPU热身，填充GPU，防止冷启动 
- fastllm.ChatGLMModel.launch_response(inputs:str)->handle_id:int  # 多线程下使用，填充第一个token，并返回多线程的线程id
- fastllm.ChatGLMModel.fetch_response(handle_id:int) # 根据线程ID从消息队列中取出对应的消息并返回
- fastllm.ChatGLMModel.save_lowbit_model(model_path:str, q_bit:int) # 量化保持低bit的权重并保存模型


支持的模型列表：

| 模型名称 | 对应类 | 备注 |
| ---- | ---- | ---- |
| ChatGLM-6B | fastllm.ChatGLMModel |  |
| ChatGLM2-6B | fastllm.ChatGLMModel | 在权重中标注版本 |
| Moss | fastllm.MossModel |  |
| Alpaca | fastllm.LlamaModel |  |
| QWen | fastllm.QWenModel |  |

---

### Overview (`docs/qwen3.8-flash-next/README.md`)

# Qwen3.8-Flash-Next 用户手册

[English](README_EN.md) · [项目首页](../../README.md)

本手册介绍 Qwen4-Exp / Qwen3.8-Flash-Next 的文本服务部署，包括单卡混合推理、双卡串行与并行、专家缓存、固定 GPU 专家层、MTP 和 API 调用。自动化部署建议先把本页和 [模型部署速查](../model-deployment-agent.md) 交给 agent，并优先使用 `--auto`；下面的手动命令用于固定实验和故障定位。以下双卡示例使用 NVFP4 模型。

## 准备模型

先安装支持该模型的 FastLLM，并准备完整的本地模型目录，其中应包含配置、分词器和 Safetensors 权重。将命令中的 `/data/models/Qwen3.8-Flash-Next-NVFP4` 替换为实际路径。

混合推理需要足够的主机内存；使用磁盘 PLE 时，将模型存放在 SSD 上。

## 自动配置（推荐）

本地 HF/NVFP4 目录直接交给自动规划器即可：

```bash
ftllm server /data/models/Qwen3.8-Flash-Next-NVFP4 \
  --auto --model_name qwen3.8-flash-next \
  --host 0.0.0.0 --port 8080
```

GSQ GGUF 传入第一个分片，加载器会读取同目录的其余分片；下载目录和分片名称以本地文件为准：

```bash
ftllm server /data/models/Qwen3.8-Flash-Next-GSQ-RCO/Q2_0/Qwen3.8-Flash-Next-GSQ-RCO-Q2_0-00001-of-00002.gguf \
  --auto --model_name qwen3.8-flash-next \
  --host 0.0.0.0 --port 8080
```

显存或主机内存需要人为限额时，附加 `--auto_gpu_memory_limit 20g` 或
`--auto_host_memory_limit 128g`；省略时自动读取机器配置。启动日志会给出最终的设备、专家缓存和 chunk 设置。

## 手动配置参考

下面以一张 GPU 运行主干、CPU 的 NUMA 后端运行专家为例：

```bash
ftllm server /data/models/Qwen3.8-Flash-Next-NVFP4 \
  --model_name qwen3.8-flash-next \
  --host 0.0.0.0 --port 8080 \
  --device cuda --moe_device numa \
  --atype float16 --ngram_device disk --threads 28 \
  --chunked_prefill_size 4096 --gpu_mem_ratio 0.9 \
  --prefix_cache true --enable_thinking false
```

等待模型加载完成后，客户端使用 `http://服务器地址:8080/v1`，模型名填写 `qwen3.8-flash-next`。线程数应根据 CPU 物理核心数调整。

<a id="deployment"></a>

## 双卡混合部署

以下配置适用于两张 **22 GiB 显存版 RTX 2080 Ti**、AMD EPYC 7452 和约 126 GiB 主机内存的机器。标准 11 GiB 2080 Ti 需要降低专家缓存或 GPU 驻留层数。

先在 Bash 中设置公共参数，再选择一种部署命令：

```bash
MODEL=/data/models/Qwen3.8-Flash-Next-NVFP4
unset FASTLLM_TP
export CUDA_VISIBLE_DEVICES=0,1
export FASTLLM_CUDA_GRAPH=0

COMMON=(
  --model_name qwen3.8-flash-next
  --host 0.0.0.0 --port 8080
  --atype float16 --ngram_device disk --threads 28
  --mtp 3 --temperature 0 --top_k 1 --repeat_penalty 1
  --max_batch 1 --kv_cache_limit 1g --gpu_mem_ratio 0.95
  --chunked_prefill_size 4096
  --prefix_cache true --enable_thinking false
)
```

这组示例关闭 CUDA Graph，并开启适合多轮文本请求的前缀缓存。模型不含 MTP 权重或需要关闭推测解码时，将 `--mtp 3` 改为 `--mtp 0`。

### 双卡串行：专家缓存开关

双卡串行将主干按层放到两张显卡上。`cudapp=2` 为两卡均分主干层。

```bash
# 关闭专家缓存
ftllm server "$MODEL" "${COMMON[@]}" \
  --device cudapp=2 --moe_device numa --moe_cuda_cache 0

# 每卡使用 8 GiB 专家缓存
ftllm server "$MODEL" "${COMMON[@]}" \
  --device cudapp=2 --moe_device numa --moe_cuda_cache 8g
```

### 双卡并行：专家缓存开关

`--tp 2` 让两张显卡共同计算每一层。选择该模式时，不再同时指定 `cudapp`。

```bash
# 关闭专家缓存
ftllm server "$MODEL" "${COMMON[@]}" \
  --tp 2 --moe_device numa --moe_cuda_cache 0

# 每卡使用 8 GiB 专家缓存
ftllm server "$MODEL" "${COMMON[@]}" \
  --tp 2 --moe_device numa --moe_cuda_cache 8g
```

`--moe_cuda_cache 8g` 是**每卡**预算，两卡合计约 16 GiB。显存还需要容纳主干权重、KV 缓存和计算工作区，不能全部分配给专家缓存。

### 固定部分专家层在 GPU：双卡并行

关闭动态专家缓存，将前 12 层的全部专家固定在 GPU，剩余 36 层使用 NUMA：

```bash
ftllm server "$MODEL" "${COMMON[@]}" \
  --tp 2 --moe_device numa --moe_device_layers 36 --moe_cuda_cache 0
```

`--moe_device_layers 36` 指**最后 36 层**使用 `--moe_device numa`；前 12 层专家由两张 GPU 分片存放。减小这个数字会增加 GPU 驻留层数和显存占用。

### 固定部分专家层在 GPU：双卡串行

使用下列配套的主干和专家映射：

```bash
ftllm server "$MODEL" "${COMMON[@]}" \
  --device cudapp=1:7 \
  --moe_device "{'cuda:0':6,'cuda:1':6,'numa':36}" \
  --moe_cuda_cache 0
```

主干第 0～5 层放在 GPU0，第 6～47 层放在 GPU1；专家第 0～5 层放在 GPU0，第 6～11 层放在 GPU1，后 36 层和 MTP 专家使用 NUMA。调整层数时，应同时调整主干与专家映射，确保 GPU 专家所在层与对应主干使用同一张卡。

固定层会为该层全部专家分配显存，动态缓存则按访问情况保留专家。两种方式的速度和显存需求不同。

<a id="parameters"></a>

## 常用参数

| 参数 | 用途 |
| --- | --- |
| `--device cuda` | 单卡主干 |
| `--device cudapp=2` | 双卡主干按层串行 |
| `--tp 2` | 双卡张量并行 |
| `--moe_device numa` | 使用 CPU 的 NUMA 后端运行专家 |
| `--moe_cuda_cache 0` / `8g` | 关闭专家缓存 / 每卡设置 8 GiB 预算 |
| `--moe_device_layers 36` | 最后 36 层专家使用指定的 MoE 设备 |
| `--ngram_device disk` | 按需从磁盘读取 PLE 表，降低主机常驻内存 |
| `--ngram_device cpu` | PLE 表驻留主机内存 |
| `--threads 28` | CPU 计算线程数 |
| `--mtp 0` / `3` | 关闭 MTP / 每步最多提出 3 个草稿 token |
| `--chunked_prefill_size 4096` | 每块最多处理 4096 个输入 token |
| `--kv_cache_limit 1g` | KV 缓存预算，按上下文与并发需求调整 |
| `--gpu_mem_ratio 0.95` | GPU 可用内存的使用比例 |
| `--prefix_cache true` | 复用重复文本前缀，适合多轮对话 |
| `--max_context_length` | 上下文长度上限，仍受模型和缓存容量限制 |
| `--enable_thinking true` | 开启模型思考模式 |

### MTP

MTP 需要模型目录中包含匹配的 MTP 权重，主干运行在 CUDA，专家可使用 CUDA 或 NUMA。`--mtp` 最大为 8；可以先使用 3，实际收益取决于任务和草稿接受情况。双卡 TP 示例使用贪婪采样，即 `--temperature 0 --top_k 1 --repeat_penalty 1`。

### 内存与上下文

主机内存紧张时使用 `--ngram_device disk`。显存不足时，先减小专家缓存或 GPU 驻留层数，再根据请求长度调整 KV 预算、分块大小和并发数。

增大 `--chunked_prefill_size` 可减少长输入的分块次数，同时增加临时显存需求。前缀缓存与专家缓存相互独立，分别复用文本前缀和专家权重。

<a id="api"></a>

## 调用服务

```bash
curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.8-flash-next",
    "messages": [{"role": "user", "content": "请用三句话介绍你自己。"}],
    "max_tokens": 512,
    "temperature": 0,
    "stream": false
  }'
```

远程调用时，将 `127.0.0.1` 替换为服务器地址。若启动时设置了 `--api_key`，请求需增加 `Authorization: Bearer 你的密钥` 请求头。

也可以在浏览器中对话：

```bash
ftllm webui --api_base http://127.0.0.1:8080/v1
```

WebUI 连接已经启动的 API 服务。需要工具调用时，可在服务启动命令中加入 `--tool_call_parser auto`；API 请求使用标准 `tools` 字段。

<a id="performance"></a>

## 性能参考

测量配置、速度、内存和验证范围见[Benchmark](../benchmarks/qwen4_exp.md)。

<a id="gguf-resident-performance"></a>

### GGUF 专家常驻 GPU

两个分片放在同一目录，路径指向第一分片。此配置使用 TP2，全部专家在 GPU；embedding 在 CPU，PLE 在磁盘。显存不足时请使用前文的混合部署。


```bash
CUDA_VISIBLE_DEVICES=0,1 FASTLLM_CUDA_GRAPH=1 FT_NUMAS=1 \
ftllm server /data/models/Qwen3.8-Flash-Next-GSQ-RCO-IQ2_XS-00001-of-00002.gguf \
  --tp 2 --device cuda:0,1 --moe_device cuda:0,1 \
  --atype float16 --low_gpu_mem --ngram_device disk --threads 28 \
  --moe_cuda_cache 0 --mtp 0 --max_batch 1 --tokens 8192 \
  --kv_cache_limit 1g --gpu_mem_ratio 0.95 --chunked_prefill_size 1024 \
  --prefix_cache false --cache_history false --enable_thinking false \
  --temperature 0 --top_k 1 --repeat_penalty 1
```

---

### Overview (`test/agent/README.md`)

# Agent Tool-use API Evaluation

## 测试内容与目的

本测试通过 OpenAI-compatible `/v1/chat/completions` API 评估模型的轻量 Agent 工具使用能力。它不依赖服务端原生 function calling，而是要求模型按 JSON ReAct 协议选择本地内置工具、读取 observation，并给出最终答案。

主要覆盖：

- 工具选择与 JSON 参数生成
- 多步工具链
- policy 检索
- 计算器与日期工具使用
- 最终答案格式遵循

这是一套本地冒烟/回归测试，不是 WebArena、SWE-bench、tau-bench 这类公开重环境 benchmark。

MTP 反复思考问题的独立 API 复现、离线循环检测和真实输出夹具见 [mtp_loop/README.md](mtp_loop/README.md)。

## 目录结构

- `setup.sh`: 安装 Python 依赖。
- `download.sh`: 检查本地 baseline 数据；本测试没有外部数据下载。
- `run.sh`: 调用 `agent_tool_eval.py` 执行评测。
- `baseline/default_cases.jsonl`: 默认测试数据，当前 24 条。
- `results/`: 运行输出目录，JSONL/summary 默认被忽略，最终结论整理到 `docs/benchmarks/`，原始报告保存在仓库外。

## 1. Setup

```bash
bash test/agent/setup.sh
```

## 2. Download

```bash
bash test/agent/download.sh
```

该脚本会确认 `baseline/default_cases.jsonl` 存在，并打印 case 数量。

## 3. Run

快速冒烟：

```bash
bash test/agent/run.sh \
  --base-url http://127.0.0.1:1616 \
  --model ds \
  --limit 8
```

传入模型专属参数：

```bash
bash test/agent/run.sh \
  --base-url http://127.0.0.1:1616 \
  --model ds \
  --workers 4 \
  --max-steps 5 \
  --max-tokens 384 \
  --extra-body '{"chat_template_kwargs":{"enable_thinking":false}}'
```

`run.sh` 至少需要 `--base-url` 和 `--model`，并会把 `--temperature`、`--top-p`、`--max-tokens`、`--extra-body`、`--output-file`、`--resume` 等参数继续传给底层 runner。

## 输出

默认输出：

```text
test/agent/results/agent_tool_<model>_<timestamp>.jsonl
test/agent/results/agent_tool_<model>_<timestamp>.summary.json
```

summary 包含 accuracy、tool plan accuracy、invalid action case、tool error case、平均步数、延迟和 token 吞吐等基础结果。

---

### Overview (`test/benchmark/README.md`)

# Benchmark Scripts

这个目录包含以下测试：

- `prefill.py`：测长上下文 prefill 性能。
- `decode.py`：测多 batch 并发请求下的 decode 性能。
- `qwen4_prefix_cache.py`：用真实 Qwen4 checkpoint 验证普通解码和 MTP 的前缀快照。
- `qwen4_tp_reuse.py`：连接已启用 CUDA Graph 的 Qwen4 TP 服务，验证不同内容/长度、取消请求及并发时的缓存隔离：`python3 test/benchmark/qwen4_tp_reuse.py --url http://127.0.0.1:18080 --output reuse.json`。

本文主要说明 `decode.py` 和 `decode_config.example.json` 的用法。

## 1. `decode.py` 是做什么的

`decode.py` 会在本地拉起一个 FastLLM OpenAI 兼容服务，然后并发发起多条流式 `chat/completions` 请求，统计多 batch 场景下的 decode 吞吐。

它不是单个 HTTP 请求里塞多个 prompt，而是：

1. 启动服务端模型；
2. 构造统一的 prompt；
3. 同时发起 `batch_size` 条流式请求；
4. 等所有请求结束后，统计整体 decode 吞吐和单请求 decode 速度。

这种测法更接近实际服务端的多并发、多 batch 推理场景。

## 2. 依赖

先安装依赖：

```bash
pip install -r test/benchmark/requirements.txt
```

## 3. 最简单的单次测试

在仓库根目录执行：

```bash
python test/benchmark/decode.py /path/to/model \
  --batch-size 16 \
  --max_batch 16 \
  --prefill-length 512 \
  --max-tokens 256
```

参数含义：

- `model` 或 `--path`：模型路径，支持 fastllm 模型文件或 HF 模型目录。
- `--batch-size`：同时发起多少条请求。
- `--max_batch`：服务端允许同时推理的最大 batch。要测“真实多 batch decode”，通常应满足 `max_batch >= batch_size`。
- `--prefill-length`：按字符数构造 prompt。想把测试重点放在 decode 时，这个值不要太大。
- `--max-tokens`：每条请求最多生成多少 token。

例如测 `Qwen3-8B` 的 64 batch decode：

```bash
python test/benchmark/decode.py ~/hfmodels/Qwen/Qwen3-8B \
  --batch-size 64 \
  --max_batch 64 \
  --prefill-length 512 \
  --max-tokens 256
```

## 4. 用配置文件批量跑

如果要连续跑多组 batch，可以用 `decode_config.example.json`：

```bash
python test/benchmark/decode.py --config test/benchmark/decode_config.example.json
```

这个文件结构分两部分：

- `defaults`：所有 case 共用的默认参数；
- `cases`：每组 benchmark 的覆盖项。

示例：

```json
{
  "defaults": {
    "path": "/path/to/model",
    "prefill_length": 512,
    "max_tokens": 256
  },
  "cases": [
    {
      "name": "batch1-out256",
      "batch_size": 1,
      "max_batch": 1
    },
    {
      "name": "batch4-out256",
      "batch_size": 4,
      "max_batch": 4
    },
    {
      "name": "batch16-out256",
      "batch_size": 16,
      "max_batch": 16
    }
  ]
}
```

建议把 `path` 改成你自己的模型路径，再按需要增加 case。

## 5. 关键参数说明

- `--batch-size`
  同时发起的请求数，也是你想测的并发 batch。

- `--max_batch`
  FastLLM 服务端实际允许并行处理的上限。
  如果这个值小于 `batch_size`，结果会包含排队时间，不再是纯粹的多 batch decode 吞吐。

- `--prefill-length`
  直接控制 prompt 字符数，优先于 `--prompt-repeat`。
  decode benchmark 推荐用较短 prompt，比如 `256`、`512`、`1024`。

- `--prompt-repeat` / `--prompt-unit` / `--question`
  如果不想直接指定字符数，可以用这三个参数拼 prompt。

- `--request-stagger-ms`
  请求启动间隔，默认 `0` 表示尽量同时发起。

- `--skip-warmup`
  跳过预热。默认会先发一个很短的预热请求。

## 6. 输出怎么看

脚本会打印单次结果和 JSON 结果，重点看这些字段：

- `batch_decode_speed`
  所有请求合并后的 decode 吞吐，单位 `tokens/s`。这是最核心指标。

- `end_to_end_speed`
  以整批请求总耗时计算的整体吞吐，包含 TTFT 影响。

- `ttft_min / ttft_avg / ttft_max`
  首 token 延迟统计。

- `per_request_decode_speed_avg`
  单请求平均 decode 速度。

- `per_request_decode_speed_min / max`
  单请求 decode 速度分布。

- `finish_reason_counts`
  每个请求的结束原因统计，正常通常是 `stop`。

## 7. 实际使用建议

- 只想看 decode，不想让 prefill 干扰结果：
  用较小的 `prefill_length`，例如 `512`。

- 想比较不同 batch 的扩展性：
  固定 `prefill_length` 和 `max_tokens`，只改 `batch_size` 与 `max_batch`。

- 想看服务端是否已经到瓶颈：
  观察 `batch_decode_speed` 是否继续增长，以及 `per_request_decode_speed_avg` 是否开始明显下降。

- 想避免把排队时间算进去：
  保证 `max_batch >= batch_size`。

## 8. 和 `prefill.py` 的区别

- `prefill.py` 重点看长上下文首 token 延迟和 prefill 速度；
- `decode.py` 重点看多请求并发下的 decode 吞吐。

如果你在做服务压测，通常两个都值得跑：

- 用 `prefill.py` 看长上下文是否慢；
- 用 `decode.py` 看多 batch 下吞吐能到多少。

## Qwen4 TP 请求复用回归

对已启动的 TP 服务运行，先保存基线，再切换待测库：

```bash
python test/benchmark/qwen4_tp_reuse.py --output /tmp/tp-baseline.json
python test/benchmark/qwen4_tp_reuse.py --reference /tmp/tp-baseline.json \
  --output /tmp/tp-candidate.json
```

测试比较完整文本和 token usage，覆盖短输入图复用、跨 2048 token 边界、不同内容和长度、取消后重入及并发请求。TP 与 CUDA Graph 的职责划分见[Qwen4 模块边界](../../docs/execution_modes.md#qwen4-模块边界)。

## Qwen4 前缀缓存回归

使用已构建的 `ftllm` Python 包和支持 MTP 的 Qwen4 checkpoint，在空闲 GPU 上运行：

```bash
PYTHONPATH=build-proto/tools python test/benchmark/qwen4_prefix_cache.py \
  ~/Qwen3.8-Flash-Next-FP8 --output-dir /tmp/qwen4-prefix-check \
  --mtp 3,0 --cache-gib 50
```

默认使用 CUDA 计算、NUMA 专家、FP16 激活和 CUDA Graph；模型及专家主机快照需要足够的系统内存。可通过 `--cache-gib`、`--moe-device`、`--threads`、`--graph` 调整运行配置。输出目录须没有同模式的既有结果。

提示词预填充助手的代码块，减少选择输出格式带来的歧义，输出仍严格逐 token 比较。自适应专家调度可能改变 CPU/GPU 计算路径；若出现差异，应使用未修改版本对照。需要固定专家放置时可用 `--cache-gib 0`。NUMA 专家已在权重准备阶段集中注册并回收源缓冲区，无需启用 GPU 专家缓存来规避按需注册造成的主机内存膨胀；系统内存仍需容纳模型和请求状态。

双卡混合推理可增加 `--tp 2 --ngram-device disk`，自动覆盖首次并发请求、Graph 恢复、默认 2048 token 快照间隔，以及从普通解码切换到 MTP。需要缩短边界用例时，可设置 `--families 511:512,512:512,513:512,1025:512`。测试内部固定边界用例的快照间隔为 1 页，默认间隔用例单独设为 16 页，无须额外设置环境变量。

测试先关闭前缀缓存生成参考，再开启缓存，检查重复请求、真实前缀恢复、不同续写及共享快照复用；覆盖 4095/4096/4097 token 和 8193 token 的分块 prefill。它比较完整生成 token，并要求日志中出现预期长度的 `restore`，防止把未崩溃或仅查找到缓存误判为恢复成功。`mtp*.log` 保留原始日志，`mtp*.json` 保存各请求输出与首 token 耗时，`summary.json` 仅在所有检查通过后生成。

## Qwen3.8-Flash-Next 混合部署速度

`qwen38_hybrid.py` 用于对比串行分卡、TP、动态专家缓存和固定专家层驻留。
每种配置只测一轮：4096 输入、4096 分块的原生 prefill，以及独立的
512 输入、固定 512 输出的普通/MTP3 decode；每项先预热一次。
使用 `--mtp 3 --chunked_prefill_size 4096 --prefix_cache false --cache_history false`
和一个尚不存在的 `--output-dir`，其他参数按部署方式设置。
脚本保存原始 token、到达时间、配置及设备映射，终端 `[Prompt]` 行报告原生前向吞吐。
完整命令、指标口径和本机结果见 [Flash-Next Benchmark](../../docs/benchmarks/qwen4_exp.md)。

---

### Overview (`test/cmmlu/README.md`)

CMMLU是一个综合性的中文评估基准，专门用于评估语言模型在中文语境下的知识和推理能力。

项目官网网址为: https://github.com/haonan-li/CMMLU

本目录下的chatglm.py程序会调用fastllm框架进行测试

测试步骤如下：

- 1. 克隆CMMLU仓库

``` sh
git clone https://github.com/haonan-li/CMMLU
```

- 2. 测试

```
# chatglm测试脚本
# 这里model_name_or_path可以使用ChatGLM2-6b官方的原始模型、int4模型，dtype支持float16, int8, int4
python3 chatglm.py --model_name_or_path 此处填写模型路径 --save_dir 此处填写结果保存路径 --dtype float16

# baichuan13b测试脚本
# 这里model_name_or_path可以使用Baichuan13B-Base或Baichuan13B-Chat官方的原始模型，dtype支持float16, int8, int4
python3 baichuan.py --model_name_or_path 此处填写模型路径 --save_dir 此处填写结果保存路径 --dtype float16
```

测试数据较多，过程比较漫长，测试中途可以通过以下命令查看已完成的测试成绩

```
python3 eval.py 此处填写结果保存路径
```

- 3. 参考结果

|              模型        | Data精度 | Shot     |  CMMLU分数 |
|-----------------------: |-------- |----------|-----------|
| ChatGLM2-6b-fp16        | float32 |0         |  50.16    |
| ChatGLM2-6b-int8        | float32 |0         |  50.14    |
| ChatGLM2-6b-int4        | float32 |0         |  49.63    |
| QWen-7b-Base-fp16       | float32 |0         |  57.43    |
| QWen-7b-Chat-fp16       | float32 |0         |  54.82    |
| Baichuan-13b-Base-int8  | float32 |5         |  55.12    |
| Baichuan-13b-Base-int4  | float32 |5         |  52.22    |

---

### Overview (`test/cuda/README.md`)

# CUDA 分页与草稿采样回归检查

`test_paged_int_params.cpp` 检查分页上传的 0/256/512/1024/2048/4096 边界、最大元数据长度、越界保护、4097 页回退，以及修改主机数组后的 Graph 重放。MTP 分布检查使用 `test/basic/test_cuda_mtp_gumbel.cpp` 和 `test/basic/test_cuda_mtp_rejection.cpp`，覆盖实际草稿 q、完整分布拒绝采样、温度、联合 top-k/top-p、残差、bonus 和 Graph 随机性。旧候选集 MTP 采样接口及其专用测试已移除。

`test/basic/test_qwen35_mtp_sampling.cpp` 直接通过模型 `ForwardGPU` 验证目标采样和贪心草稿接受，包括重复状态的退出概率、目标分布之外的草稿、bonus、混合请求长度和无草稿请求。对应 CTest 为 `qwen35_mtp_sampling` 和 `qwen35_mtp_sampling_batch`，无需额外的公开采样辅助接口。

在已编译出 `libfastllm_tools.so` 的仓库根目录执行，按实际位置设置 `FASTLLM_TEST_LIBRARY_DIR` 和 `FASTLLM_TEST_CUDA_DIR`：

```sh
FASTLLM_TEST_LIBRARY_DIR="$PWD/build"
FASTLLM_TEST_CUDA_DIR=/usr/local/cuda
for test_source in test/cuda/test_paged_int_params.cpp test/basic/test_cuda_mtp_gumbel.cpp test/basic/test_cuda_mtp_rejection.cpp; do
    test_name=$(basename "$test_source" .cpp)
    g++ -O2 -std=c++17 -DUSE_CUDA -pthread -Iinclude -Ithird_party/json11 \
        -I"$FASTLLM_TEST_CUDA_DIR/include" "$test_source" \
        -L"$FASTLLM_TEST_LIBRARY_DIR" -lfastllm_tools \
        -L"$FASTLLM_TEST_CUDA_DIR/lib64" -lcudart \
        -Wl,-rpath,"$FASTLLM_TEST_LIBRARY_DIR" -Wl,-rpath,"$FASTLLM_TEST_CUDA_DIR/lib64" \
        -o "/tmp/$test_name"
    "/tmp/$test_name"
done
```

真实模型的单请求/批处理切换回归覆盖两条采样请求，以及采样/贪心混合请求。指定已安装本次构建的 Python 环境和模型路径，保证两张 GPU 空闲：

```sh
python test/cuda/test_mtp_scheduler_transition.py \
    -p /path/to/Qwen3.8-27B-FP8 --tp 2 --mtp 3 --max_batch 2 --threads 8 \
    --cuda_embedding --kv_cache_dtype float16 --prefix_cache false --cache_history false \
    --max_context_length 8192 --chunked_prefill_size 2048
```

测试应输出最后的 `PASS: MTP sampling and mixed requests across scheduler transitions`。采样请求默认使用随机草稿和完整分布拒绝采样，并在单请求与批处理切换时保持实际 q 同步。开启 CUDA Graph 时同时检查日志确实捕获了 `batch=2` 的 MTP 验证图，避免将普通解码回退误记为批处理验证通过。

`test_qwen35_speculative_kv_capacity.py` 覆盖 #754 的双卡 DFlash 容量边界：输入 4079 token，KV 池与上下文上限均为 4096，依次执行一次首次解码和两次前缀缓存复用。Prefill/前缀恢复后的线性注意力状态可能尚未转置，但 TP 验证能够转换后原地运行；调度器不应因此误算额外复制页并反复重建请求。

```sh
PYTHONPATH="$PWD/build-fastllm/tools" OMP_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 \
    MKL_NUM_THREADS=4 TOKENIZERS_PARALLELISM=false \
    numactl -C 0-31 -m 0 python3 test/cuda/test_qwen35_speculative_kv_capacity.py \
    /path/to/Qwen3.8-27B-FP8 --device cuda --tp 0,1 --threads 4 \
    --draft /path/to/Qwen3.8-27B-DFlash2 --kv_cache_dtype fp8_e4m3 \
    --max_batch 1 --gpu_mem_ratio 0.90 --tokens 4096 --max_context_length 4096 \
    --chunked_prefill_size 1024 --prefix_cache true --image_embedding_cache 0
```

按本机调整 Python 包路径、模型路径和 NUMA/CPU 绑定。测试检查实际 TP 设备数和 KV 池容量、首次解码不恢复前缀、缓存请求各恢复一次，以及生成过程中缓存统计稳定。每个请求申请 64 个输出，但受上下文上限约束只输出剩余 17 个。失败判定不依赖机器速度。将 `--tokens` 改为 `4224` 可对照多留一页的配置；增加 `--sampling` 可检查拒绝采样路径。各配置应串行运行。贪心结果的 token ID 会打印为 `KV_CAPACITY_RESULT`，可核对紧池与多一页配置的输出一致性。

编译兼容性检查使用支持 sm_60 的 nvcc，例如 CUDA 12.4，不需要 GPU：

```sh
python3 test/cuda/check_paged_params_build.py --nvcc /usr/local/cuda-12.4/bin/nvcc
```

它编译完整 sm_60 attention 翻译单元，并直接提取生产分页上传代码，编译同时包含 sm_60/sm_75 的目标，防止重新引入超出旧架构 4 KiB 参数上限的内核。

---

### Overview (`test/gsm8k/README.md`)

# GSM8K API Evaluation

## 测试内容与目的

本测试通过 OpenAI-compatible `/v1/chat/completions` API 跑 GSM8K 风格数学推理题，从模型输出中抽取最终数字答案，并生成 JSONL 明细和 summary。

它适合做算术、短链路数学推理和 CoT 输出解析的回归测试。`baseline/smoke.jsonl` 只是用于验证评测链路的小型样例，不代表官方 GSM8K 分数。

## 目录结构

- `setup.sh`: 安装 Python 依赖。
- `download.sh`: 从 HuggingFace 下载 GSM8K 数据到 `baseline/downloaded/`。
- `run.sh`: 调用 `gsm8k_api_eval.py` 执行评测。
- `baseline/smoke.jsonl`: 小型本地链路测试数据。
- `baseline/downloaded/`: 下载后的真实运行数据，默认 gitignored。
- `results/`: 运行输出目录，JSONL/summary 默认被忽略。

## 1. Setup

```bash
bash test/gsm8k/setup.sh
```

## 2. Download

下载完整 test split：

```bash
bash test/gsm8k/download.sh
```

只下载前 200 条：

```bash
bash test/gsm8k/download.sh \
  --limit 200 \
  --output test/gsm8k/baseline/downloaded/gsm8k_test_200.jsonl
```

为了完全复现实验，可以传入固定数据集 revision：

```bash
bash test/gsm8k/download.sh --dataset-revision <commit>
```

## 3. Run

默认使用 `baseline/smoke.jsonl` 跑链路冒烟：

```bash
bash test/gsm8k/run.sh \
  --base-url http://127.0.0.1:1616 \
  --model ds
```

使用下载后的数据运行 CoT：

```bash
bash test/gsm8k/run.sh \
  --base-url http://127.0.0.1:1616 \
  --model ds \
  --data-file test/gsm8k/baseline/downloaded/gsm8k_test.jsonl \
  --workers 8 \
  --cot \
  --max-tokens 512 \
  --extra-body '{"chat_template_kwargs":{"enable_thinking":false}}'
```

`run.sh` 至少需要 `--base-url` 和 `--model`，并会把 `--temperature`、`--top-p`、`--max-tokens`、`--cot`、`--extra-body`、`--output-file`、`--resume` 等参数继续传给底层 runner。

## 输出

默认输出：

```text
test/gsm8k/results/gsm8k_<model>_<split>_<mode>_<timestamp>.jsonl
test/gsm8k/results/gsm8k_<model>_<split>_<mode>_<timestamp>.summary.json
```

summary 包含 accuracy、有效答案 accuracy、错误数、延迟分位数、token 数和吞吐。

---

### Overview (`test/hle/README.md`)

# HLE API Evaluation

本目录为 Humanity's Last Exam（HLE）提供 OpenAI-compatible API 评测，并支持
target-only 与 DFlash2 的成对精度、速度对比。

## 本次推荐口径

正式对比固定使用官方 `cais/hle` test split 中的 50 道题：

- 仅 text-only，避免把多模态预处理差异混入 speculative decode 对比；
- 仅 `multipleChoice`，答案字母可确定性判分，不依赖外部 LLM judge；
- 先过滤再以 `Random(42)` 打乱，取前 50 道；
- 单并发 `workers=1`；
- `temperature=0`；runner 不主动发送 `max_tokens`，当前 FastLLM 服务端使用
  默认的 32768-token completion 上限，同时仍受模型原生 262144-token 总上下文
  上限约束；
- Qwen thinking 显式开启；
- 正式计时前用同一题生成 64 token 预热；
- DFlash2 使用 checkpoint 原生 block=8（每轮 7 个 draft token）。

这是一套可重复的 **HLE text-only multiple-choice 50 题子集分数**，不是完整 HLE
官方分数。完整 HLE 还包含图片题和 `exactMatch` 题；后者需要官方的能力较强的
LLM judge。runner 虽可对 exactMatch 输出归一化字符串下界，但不会把它冒充官方分数。

## 数据准备

默认从 ModelScope 的公开 `cais/hle` 镜像下载：

```bash
bash test/hle/setup.sh
bash test/hle/download.sh
```

下载脚本固定 ModelScope revision
`1ec1f1f25ed4ad891e3a81d1cbc08f261f5e77c6`，并校验 parquet SHA-256
`6d0ee0602e8aea6b159509577e884f48ecac7b8e3f6822a35f51335a446c726a`。
该文件与 Hugging Face 官方固定 revision
`5a81a4c7271a2a2a312b9a690f0c2fde837e4c29` 的文件大小和 SHA-256 一致。
筛选结果输出到：

```text
test/hle/baseline/downloaded/hle_test_text_mc_seed42_50.jsonl
```

下载内容和运行结果均已 gitignore。`baseline/smoke.jsonl` 是合成链路测试，不是
HLE 真题。

若要直接从 Hugging Face 下载，先接受 gated dataset 条款并执行
`hf auth login`，然后运行 `bash test/hle/download.sh --source huggingface`。

## Target-only

从仓库根目录启动当前构建：

```bash
PYTHONPATH="$PWD/build-fastllm/tools" \
FASTLLM_CUDA_GRAPH=0 FASTLLM_CUDA_CUSTOM_ALLREDUCE=0 \
ftllm server /root/hfmodels/Qwen3.8-27B-FP8 \
  --model_name qwen38-hle-target \
  --dtype auto --tp 2 --cuda_embedding --max_batch 1 \
  --gpu_mem_ratio 0.98 \
  --host 127.0.0.1 --port 18126 --hide_input
```

另一个终端运行：

```bash
bash test/hle/run.sh \
  --base-url http://127.0.0.1:18126 \
  --model qwen38-hle-target \
  --data-file test/hle/baseline/downloaded/hle_test_text_mc_seed42_50.jsonl \
  --workers 1 --temperature 0 --max-tokens 0 --request-timeout 0 \
  --warmup-tokens 64 \
  --extra-body '{"chat_template_kwargs":{"enable_thinking":true}}' \
  --output-file test/hle/results/qwen38_hle50_target_only.jsonl \
  --overwrite
```

## DFlash2 最快推荐配置

```bash
PYTHONPATH="$PWD/build-fastllm/tools" \
FASTLLM_CUDA_GRAPH=0 FASTLLM_CUDA_CUSTOM_ALLREDUCE=0 \
FASTLLM_DFLASH_BLOCK_SIZE=8 \
ftllm server /root/hfmodels/Qwen3.8-27B-FP8 \
  --model_name qwen38-hle-dflash2-b8 \
  --dtype auto --tp 2 --cuda_embedding --max_batch 1 \
  --gpu_mem_ratio 0.98 \
  --host 127.0.0.1 --port 18126 --hide_input \
  --speculative_algorithm dflash \
  --speculative_draft_model_path /root/hfmodels/Qwen3.8-27B-DFlash2 \
  --speculative_num_draft_tokens 8
```

```bash
bash test/hle/run.sh \
  --base-url http://127.0.0.1:18126 \
  --model qwen38-hle-dflash2-b8 \
  --data-file test/hle/baseline/downloaded/hle_test_text_mc_seed42_50.jsonl \
  --workers 1 --temperature 0 --max-tokens 0 --request-timeout 0 \
  --warmup-tokens 64 \
  --extra-body '{"chat_template_kwargs":{"enable_thinking":true}}' \
  --output-file test/hle/results/qwen38_hle50_dflash2_b8.jsonl \
  --overwrite
```

## 成对比较

```bash
python3 test/hle/compare_results.py \
  test/hle/results/qwen38_hle50_target_only.jsonl \
  test/hle/results/qwen38_hle50_dflash2_b8.jsonl \
  --output test/hle/results/qwen38_hle50_comparison.json
```

输出包括两边 accuracy、总输出 token、请求总耗时、output tok/s、DFlash2
加速比、成对正确性变化，以及逐题输出完全一致率。单并发下吞吐按
`sum(completion_tokens) / sum(request_latency)` 计算，不包含模型加载和预热。

两边都固定 `FASTLLM_CUDA_CUSTOM_ALLREDUCE=0`，避免每次进程启动时的自动微基准
因小幅抖动选择不同规约实现，从而把规约数值路径差异混入 DFlash2 精度 A/B。

---

### Overview (`test/mmlu_pro/README.md`)

# MMLU-Pro API Evaluation

## 测试内容与目的

本测试通过 OpenAI-compatible `/v1/chat/completions` API 跑 MMLU-Pro 风格的多选题评测，解析模型输出的答案字母，并生成 JSONL 明细和 summary。

它适合作为模型发布前的知识、考试型推理和格式遵循回归测试。`baseline/smoke.jsonl` 只是用于验证评测链路的极小样例，不代表官方 MMLU-Pro 分数。

## 目录结构

- `setup.sh`: 安装 Python 依赖。
- `download.sh`: 从 HuggingFace 下载 MMLU-Pro 数据到 `baseline/downloaded/`。
- `run.sh`: 调用 `api_eval.py` 执行评测。
- `baseline/smoke.jsonl`: 小型本地链路测试数据。
- `baseline/downloaded/`: 下载后的真实运行数据，默认 gitignored。
- `results/`: 运行输出目录，JSONL/summary 默认被忽略。

## 1. Setup

```bash
bash test/mmlu_pro/setup.sh
```

## 2. Download

下载完整 test split：

```bash
bash test/mmlu_pro/download.sh
```

下载每个 category 5 条，适合小规模验证：

```bash
bash test/mmlu_pro/download.sh \
  --sample-per-category 5 \
  --output test/mmlu_pro/baseline/downloaded/mmlu_pro_test_5_per_category.jsonl
```

为了完全复现实验，可以传入固定数据集 revision：

```bash
bash test/mmlu_pro/download.sh --dataset-revision <commit>
```

## 3. Run

默认使用 `baseline/smoke.jsonl` 跑链路冒烟：

```bash
bash test/mmlu_pro/run.sh \
  --base-url http://127.0.0.1:1616 \
  --model ds
```

使用下载后的数据运行：

```bash
bash test/mmlu_pro/run.sh \
  --base-url http://127.0.0.1:1616 \
  --model ds \
  --data-file test/mmlu_pro/baseline/downloaded/mmlu_pro_test.jsonl \
  --workers 8 \
  --extra-body '{"chat_template_kwargs":{"enable_thinking":false}}'
```

CoT 模式：

```bash
bash test/mmlu_pro/run.sh \
  --base-url http://127.0.0.1:1616 \
  --model ds \
  --data-file test/mmlu_pro/baseline/downloaded/mmlu_pro_test.jsonl \
  --cot \
  --max-tokens 512
```

`run.sh` 至少需要 `--base-url` 和 `--model`，并会把 `--temperature`、`--top-p`、`--max-tokens`、`--cot`、`--extra-body`、`--sample-per-category`、`--output-file`、`--resume` 等参数继续传给底层 runner。

## 输出

默认输出：

```text
test/mmlu_pro/results/mmlu_pro_<model>_<split>_<timestamp>.jsonl
test/mmlu_pro/results/mmlu_pro_<model>_<split>_<timestamp>.summary.json
```

summary 包含总 accuracy、有效答案 accuracy、错误数、平均延迟和按 category 的准确率。

---

### Overview (`test/multimodal/README.md`)

# Multimodal Tests

这里放通用的多模态测试脚本。

当前入口：

- `fastllm_multimodal_check.py`：通用 fastllm 图文 smoke test

示例：

```bash
python test/multimodal/fastllm_multimodal_check.py \
  --model-path /home/use/gemma-4-31B-it \
  --text-only-first
```

如果显存紧张，也可以切到 CPU：

```bash
python test/multimodal/fastllm_multimodal_check.py \
  --model-path /home/use/gemma-4-31B-it \
  --device cpu \
  --cpu-threads 32
```

自定义 messages：

```bash
python test/multimodal/fastllm_multimodal_check.py \
  --model-path /path/to/model \
  --messages-file /path/to/messages.json
```

支持的 profile：

- `auto`：按模型 architecture 自动推断
- `generic`：通用图文配置
- `gemma4`：Gemma4 默认配置
- `cogvlm`：CogVLM 默认配置

如果后续要支持新的多模态模型，优先在 `common.py` 里补一个新 profile，而不是再新写一份模型专用脚本。

---

### Overview (`test/ops/README.md`)

# SM70 FP8 TP deadlock regression

`fp8TpRepackDeadlockRegression` requires two visible SM70 GPUs (for example,
two V100s), CUDA, and NCCL. It runs without CUDA Graph. Exit code 77 means the
required GPUs or compiled SM70 FP8 kernels are unavailable.

Build from the repository root:

```bash
cmake -S . -B build-sm70-tests -DUSE_CUDA=ON -DCUDA_ARCH=70 -DUNIT_TEST=ON
cmake --build build-sm70-tests --target fp8TpRepackDeadlockRegression -j8
```

Run each case in a fresh process so the per-device GEMM runtime starts cold.
Use an external timeout because the regression deliberately exercises waits
inside CUDA/NCCL that cannot be cancelled by a test thread:

```bash
set -e
for mode in repack runtime warmup; do
    for rank in 0 1; do
        CUDA_VISIBLE_DEVICES=0,1 FASTLLM_CUDA_GRAPH=0 \
            timeout --kill-after=3s 20s \
            ./build-sm70-tests/fp8TpRepackDeadlockRegression "$mode" "$rank"
    done
done
```

| Mode | Scenario |
| --- | --- |
| `repack` | Lazy FP8 repack while one rank has queued an unmatched AllReduce. |
| `runtime` | Cold GEMM runtime initialization after weights are already packed. |
| `warmup` | Explicit weight preparation during synchronous startup, followed by cold GEMM with asynchronous NCCL. |

The second argument selects which rank queues the collective first. NCCL's
lazy transports are warmed before the asymmetric ordering is imposed. Each
case checks exact FP8 output with distinct data on both devices, AllReduce's
result, and eventual completion of any deferred repack. The warmup case also
checks unsupported row counts and repeated preparation of the same weight.

The original cross-device lock deadlocks in the `repack` and `runtime` cases
and is expected to hit the timeout (exit 124). A fixed build prints `PASS` and
exits 0 for all six cases.

# SM75 FP8 prefill dispatch regression

The SM75 packed FP8 block128 path borrows the existing FlashInfer float workspace
for dequantized FP16 weights. The separate 8 MiB cuBLAS workspace is created in
synchronous warmup, before the KV page budget is calibrated. The float arena is
shared under the existing per-device worker stream ordering; it does not provide
concurrent independent stream leases. The int arena retains attention plans.

`M` below is the current Linear input row count, not total context length:

| Condition | Backend |
| --- | --- |
| `M=1` | Existing GEMV |
| `M<1024`, or either matrix dimension `N`/`K` is below 1024 | Existing Marlin |
| Full FP16 weight fits the float workspace, or sliced workspace is at least 64 MiB | cuBLAS for `M>=1024` |
| Weight needs slices and workspace is 32–63 MiB | cuBLAS for `M>=1536` |
| Weight needs slices below 32 MiB, or the maximum N slice capacity is below 1024 rows | Marlin |
| CUDA Graph capture with `M>1` | Marlin |

Runtime SM must be exactly 75; other architectures keep their existing dispatch. Set
`FASTLLM_CUDA_FP8_PREFILL_CUBLAS=0` to retain the previous SM75 path. FP16 cuBLAS
accumulation is not bitwise equivalent to Marlin; tests compare each selected
backend to its own independent reference, including rounded FP8 block scales.
The final N slice may be smaller than 1024 rows. The row threshold was selected
using the complete dequantization-plus-GEMM time on representative SM75 matrices;
it is not a model-name or exact-shape special case.

Build and run from the repository root:

```bash
cmake -S . -B build-sm75-tests -DUSE_CUDA=ON -DCUDA_ARCH=75 -DUNIT_TEST=ON
cmake --build build-sm75-tests --target cudaFp8Sm75PrefillRegression -j8
ctest --test-dir build-sm75-tests -R '^cuda_fp8_sm75_prefill_' --output-on-failure
```

The regression checks actual routing by poisoning the float workspace and
observing whether inference overwrote it, then compares output bits with the
selected Marlin or CPU-dequant-plus-cuBLAS reference. It covers both sides of
the 1024/1536 row boundaries, bias, K tails, N slices, small matrices, the disable
switch, workspace address/capacity stability, and graph capture. The 4/32/64/256
MiB configurations run in separate processes; exit 77 skips non-SM75 GPUs.

# SM75 NVFP4 prefill dispatch regression

For Marlin-repacked NVFP4 block16 weights, SM75 uses dequantization plus cuBLAS
when the current Linear row count `M>=2048`, `N>=1024`, `K>=1024`, and the full
logical `N*K*sizeof(half)` weight fits the existing FlashInfer float workspace.
Otherwise it retains Marlin, including graph capture. The threshold applies to
the current prefill chunk, not total context length. Set
`FASTLLM_CUDA_NVFP4_PREFILL_CUBLAS=0` to disable this route.

The 64-by-256 transpose kernel reconstructs Marlin's normalized FP16 weights
exactly, including its S0E5M3 block scales. GEMM retains **FP32 accumulation**
and applies the existing **FP32 global scale after accumulation**. The tensor
scale is not folded into FP16 weights. Output is not guaranteed bitwise equal
to Marlin because GEMM reduction order differs; the regression checks both an
independent CPU-dequant-plus-FP32-cuBLAS reference and error against Marlin.
Logical N can be unaligned (for example 8240); padded rows are not written.

FP8 and NVFP4 share one per-device cuBLAS handle and 8 MiB workspace, plus one
FP32 zero scalar. These are initialized during synchronized warmup before KV
budget calibration. The float arena retains its original address and capacity;
no full weight cache or per-layer FP16 allocations are retained. Shared-arena
use requires the existing per-device worker stream ordering described above.
Host/device scalar modes are explicitly switched when alternating FP8/NVFP4.

```bash
cmake -S . -B build-sm75-tests -DUSE_CUDA=ON -DCUDA_ARCH=75 -DUNIT_TEST=ON
cmake --build build-sm75-tests --target cudaNvfp4Sm75PrefillRegression cudaFp8Sm75PrefillRegression -j8
ctest --test-dir build-sm75-tests -R '^cuda_(nvfp4|fp8)_sm75_prefill_' --output-on-failure
```

The NVFP4 regression covers eight shapes, M=4/1024/2047/2048/2049/4096, bias,
logical-N padding and K tails, all finite nonnegative FP8 block-scale codes, zero
and small/large signed inputs, alternating FP8/NVFP4, graph capture with the
registered FastLLM memory pool, and stable scratch address/capacity. Fresh
processes test 4/32/256 MiB arenas and the disable switch. Exit 77 skips non-SM75
GPUs. An optional device index selects the second card; `TEST_N` restricts N
for a targeted Compute Sanitizer run.

## Optional NVFP4 FP16 accumulation

Set `FASTLLM_CUDA_NVFP4_PREFILL_FP16_ACCUM=1` **before starting the process** to
use FP16 cuBLAS accumulation in eligible SM75 NVFP4 prefills. The default is
**off (FP32 accumulation)**. This flag retains all existing dispatch guards:
M>=2048, N/K>=1024, full FP16 weight fitting the float arena, and no graph
capture. `FASTLLM_CUDA_NVFP4_PREFILL_CUBLAS=0` still disables the entire route.
It does not affect FP8's accumulation policy.

The optional path uses FP16 GEMM with alpha=1 and beta=0, then applies the
existing FP32 tensor scale in a separate kernel. Bias is fused into this
kernel after rounding the scaled result to FP16, matching the existing bias
contract. This avoids rounding the global scale itself to FP16 and requires
no additional buffer. FP16 accumulation still loses precision and has a
smaller numerical range; it is not numerically equivalent to the FP32 path.

The added `cuda_nvfp4_sm75_prefill_fp16_*` CTest cases exercise 4/32/256 MiB
arenas and the master disable switch. They compare the selected FP16 backend
against an independent CPU-dequant, FP16-cuBLAS, CPU-FP32-scale/bias reference;
Marlin relative L2 is bounded at 1% for the constructed input data. Default
FP32 cases retain their tighter 0.02% bound. N=4161 additionally exercises
pairs crossing rows and an odd final output element with and without bias.

---

### Overview (`test/terminal_bench/README.md`)

# Terminal-Bench 2.0 for Qwen3.6-27B

## 测试内容与目的

本目录整理 Qwen/Qwen3.6-27B 模型卡中 Terminal-Bench 2.0 的测试方式，并提供一个 Harbor/Terminus-2 的本地运行入口。

这不是本仓库自带的轻量回归测试。Terminal-Bench 2.0 的任务集由 Harbor 下载和调度，任务在隔离容器中执行，适合评估模型作为终端 agent 完成多步骤任务的能力。

## Qwen3.6-27B 模型卡中的测试方式

- Benchmark: Terminal-Bench 2.0
- Reported score: 59.3
- Harness / agent: Harbor / Terminus-2
- Timeout: 3h
- Sandbox resources: 32 CPU / 48 GB RAM
- Generation: `temperature=1.0`, `top_p=0.95`, `top_k=20`, `max_tokens=80K`
- Context window: 256K
- Aggregation: 5 runs average

Terminal-Bench v2 公开说明为 89 个真实多步骤终端任务，覆盖编译、调试、系统管理、文件操作等场景；任务在隔离容器里执行，并用自动验证给出 0/1 分。

## 目录结构

- `setup.sh`: 安装 Harbor CLI。
- `run.sh`: 通过 Harbor 运行 Terminal-Bench 2.0。
- `qwen3_6_27b.env.example`: OpenAI-compatible endpoint 与 Qwen 测试参数模板。
- `qwen3_6_27b_methodology.json`: 结构化记录模型卡中的 Terminal-Bench 参数。
- `jobs/`: Harbor job 输出目录，默认 gitignored。

## 1. Setup

先确认 Docker 可用，再安装 Harbor：

```bash
bash test/terminal_bench/setup.sh
```

Harbor 当前版本通常需要较新的 Python；如果本机 `pip install harbor` 失败，优先使用 `uv tool install harbor`。

## 2. Run

以 FastLLM/OpenAI-compatible 服务为例，Harbor/LiteLLM 通常需要带 `/v1` 的 base URL：

```bash
export OPENAI_API_KEY=EMPTY
export OPENAI_BASE_URL=http://127.0.0.1:1616/v1

bash test/terminal_bench/run.sh \
  --model openai/ds \
  --api-base http://127.0.0.1:1616/v1 \
  --dataset terminal-bench-sample@2.0
```

`terminal-bench-sample@2.0` 适合先做链路冒烟。完整运行改为默认的 `terminal-bench@2.0`。

使用模板参数：

```bash
source test/terminal_bench/qwen3_6_27b.env.example
bash test/terminal_bench/run.sh --dataset terminal-bench-sample@2.0
```

更接近模型卡设置的运行方式：

```bash
source test/terminal_bench/qwen3_6_27b.env.example
TB_N_ATTEMPTS=5 \
bash test/terminal_bench/run.sh
```

Qwen 模型卡报告使用 `32 CPU / 48 GB RAM`。当前安装的 `harbor==0.1.28` 不在 `harbor run` CLI 中暴露 CPU/内存覆盖参数；需要通过宿主机 Docker、云 sandbox 或调度环境保证资源。

## 关键参数

- `--model`: Harbor/LiteLLM 使用的模型名。OpenAI-compatible 本地服务常用 `openai/<served-model-name>`。
- `--api-base`: 自定义 OpenAI-compatible endpoint，例如 `http://127.0.0.1:1616/v1`。
- `--dataset`: 默认 `terminal-bench@2.0`；小规模链路测试可用 `terminal-bench-sample@2.0`。
- `--agent`: 默认 `terminus-2`。
- `--n-concurrent`: 并发 trial 数，默认 1。
- `--n-attempts`: 每个任务运行次数，默认 1；Qwen 模型卡报告为 5 runs average。
- `--llm-call-kwargs`: 透传给 Terminus-2 的 LLM 调用参数，模板里包含 `top_p`、`max_tokens`、`top_k`。

Qwen 模型卡还要求 3h timeout。当前安装的 Harbor 暴露 `--timeout-multiplier`，可按任务默认 timeout 乘数做调整。

## 输出

默认输出到：

```text
test/terminal_bench/jobs/
```

Harbor job 通常包含 `config.json`、`result.json`、每个 trial 的 `agent/` 日志、`verifier/` 验证日志和 reward。可用：

```bash
harbor view test/terminal_bench/jobs
```

查看结果和轨迹。

## 资料来源

- ModelScope model card: https://modelscope.cn/models/Qwen/Qwen3.6-27B
- Hugging Face mirror: https://huggingface.co/Qwen/Qwen3.6-27B
- Harbor Terminal-Bench docs: https://www.harborframework.com/docs/tutorials/running-terminal-bench
- Terminal-Bench 2.0 repo: https://github.com/harbor-framework/terminal-bench-2
- EvalScope Terminal-Bench 2.0 notes: https://evalscope.readthedocs.io/en/v1.5.2/benchmarks/terminal_bench_v2.html

---

### Overview (`test/toolcall/README.md`)

# Toolcall Parser Golden Tests

These are deterministic golden tests for toolcall parsers. They exercise parser
inputs and OpenAI-compatible tool call shapes directly from the source tree.

They do not:

- start a server
- download a model
- require GPU access
- require new test dependencies

The current focus is the DeepSeek V4 DSML parser. These tests verify parser and
API formatting behavior; they do not verify whether a real model chooses the
right tool.

Run from the repository root:

```bash
python3 test/toolcall/test_parser_golden.py
```

Or run through unittest discovery:

```bash
python3 -m unittest discover -s test/toolcall -p 'test*.py'
```

The tests import the existing OpenAI server protocol/parser modules. If imports
fail because server-side Python dependencies such as `shortuuid` are missing,
install the existing server requirements:

```bash
pip install -r requirements-server.txt
```

Native generation constraints and parser validation have separate scopes; see the [constraint contract](CONSTRAINT_BACKEND.md).

## Software-Development Tool Matrix

The deterministic suite now includes coding-agent style tools in addition to
weather/time examples:

- `read_file(path, start_line, end_line)`
- `search_code(query, path, file_pattern)`
- `run_tests(command, cwd, timeout_seconds)`
- `apply_patch(path, patch)`
- `git_status()`
- `git_diff(path)`
- `list_files(path, recursive)`

These tests verify parser output, OpenAI response shape, streaming
`delta.tool_calls`, and native tool-name constraints with realistic argument
types: arrays, booleans, numbers, zero-argument calls, optional arguments, and
long patch strings containing quotes, braces, and newlines. The tests do not
read files, apply patches, run shell commands, or mutate the repository.

## Manual Live Smoke Tests

The live runner is a manual diagnostic tool for already-running
OpenAI-compatible servers. It does not start a server, download a model, or use
GPU commands. It is not part of unittest discovery and is not part of the
deterministic parser golden tests.

List manual cases without contacting a server:

```bash
python3 test/toolcall/run_live_toolcall_tests.py --list-cases
```

Run one live case against a server that you started separately:

```bash
python3 test/toolcall/run_live_toolcall_tests.py \
  --base-url http://127.0.0.1:8080/v1 \
  --model DeepSeek-V4-Flash \
  --case-id live_openai_baseline_get_weather \
  --verbose \
  --dump-dir /tmp/fastllm-toolcall-live
```

Run the manual matrix in report-only mode to measure model/tool adherence
without making failures fail the shell command:

```bash
python3 test/toolcall/run_live_toolcall_tests.py \
  --base-url http://127.0.0.1:8080/v1 \
  --model DeepSeek-V4-Flash \
  --repeat 3 \
  --report-only \
  --dump-dir /tmp/fastllm-toolcall-live
```

The matrix is meant to distinguish parser/server bugs, protocol bugs,
model-adherence failures, and generation-constraint effectiveness. It covers
baseline `get_weather`, short-name `weather`, required and named tool choice,
parallel calls, tool-result roundtrip, no-tool plain text, and strict-schema
missing-required diagnostics. The runner reports stable `error_code` values such
as `invalid_tool_name`, `no_tool_call`, `invalid_arguments_json`,
`missing_required_argument`, `stream_missing_done`, and `http_error`.

Manual software-development smoke cases are also available. They ask the model
to call coding-agent style tools but still do not execute those tools:
For roundtrip cases, mocked structured tool results are sent as JSON strings in
the tool-role message content for OpenAI-compatible server compatibility.
The `live_openai_opencode_todowrite` case mirrors an opencode `todowrite`
failure mode: the expected top-level argument is `todos`, and variants such as
`todo`, `todoList`, or `arguments: {}` should be diagnosed as missing the
required `todos` key rather than treated as valid tool input.

```bash
python3 test/toolcall/run_live_toolcall_tests.py \
  --base-url http://127.0.0.1:8080/v1 \
  --model DeepSeek-V4-Flash \
  --case-id live_openai_dev_search_code \
  --verbose \
  --dump-dir /tmp/fastllm-toolcall-live-dev
```

```bash
python3 test/toolcall/run_live_toolcall_tests.py \
  --base-url http://127.0.0.1:8080/v1 \
  --model DeepSeek-V4-Flash \
  --case-id live_openai_opencode_todowrite \
  --verbose \
  --dump-dir /tmp/fastllm-toolcall-live-dev
```

```bash
python3 test/toolcall/run_live_toolcall_tests.py \
  --base-url http://127.0.0.1:8080/v1 \
  --model DeepSeek-V4-Flash \
  --case-id live_openai_opencode_todowrite_stream \
  --verbose \
  --dump-dir /tmp/fastllm-toolcall-live-dev
```

```bash
python3 test/toolcall/run_live_toolcall_tests.py \
  --base-url http://127.0.0.1:8080/v1 \
  --model DeepSeek-V4-Flash \
  --case-id live_openai_dev_apply_patch_dry_run \
  --verbose \
  --dump-dir /tmp/fastllm-toolcall-live-dev
```

Coverage limits:

- `parameter name="arguments"` wrapper unwrapping is not covered because the
  current DeepSeek V4 parser treats it as a normal parameter.
- DeepSeek V4 streaming currently emits complete arguments after a full
  `invoke` closes; incremental argument streaming is not required by this suite.
- Streaming behavior for a complete `invoke` under the wrong outer block name is
  not fixed as a golden case yet.
- Deterministic tests do not cover real model quality. Use the manual live
  runner for model-side tool selection and constraint impact diagnostics.

---

### Overview (`third_party/deep_gemm/README.md`)

# DeepGEMM headers used by FastLLM

This directory contains a curated header-only subset of
[DeepGEMM](https://github.com/deepseek-ai/DeepGEMM).  The starting point was
the DeepGEMM tree bundled with the local vLLM 0.26.0 DeepSeek-V4 reference
environment.  FastLLM-specific SM120 BF16, MoE, and MQA integrations adapt
parts of that subset, so this directory is not a byte-for-byte upstream
snapshot.

Only the headers needed by FastLLM's optional SM90 and SM120 kernels are kept.
Each integration is compiled behind its own CMake architecture gate.

The SM90 FP8 linear integration adds `impls/sm90_fp8_gemm_1d2d.cuh`,
`mma/sm90.cuh`, and `ptx/wgmma.cuh` from the DeepGEMM headers bundled with
vLLM 0.28.0. Its common dependencies are identical to the existing headers.
The original SM90 kernel header SHA-256 is
`8667552aeb0f56be360bf71bcaeacd1d252cfc0cd12a7c555ae747e68bb64e8d`.
Local changes restrict device code to SM90, add FP16 epilogue stores alongside
BF16, and inline the cluster barrier to avoid unused MoE communication headers.
The FP8/FP32 mainloop is unchanged.

The launcher in `src/devices/cuda/linear/fastllm-linear-fp8-sm90.cu` requires
CUDA >= 12.8, a build architecture list containing 90, and runtime capability
9.0. Its separate object target uses `sm_90a`; other architectures retain their
existing dispatch. It supports row-major E4M3 weights with 128x128 scales and
FP16/BF16 activations, quantized dynamically in 1x128 groups. Unsupported shapes
and layouts fall back. Fresh eligible weights stay row-major for prefill and
decode; already packed Marlin weights keep their existing path.

The persistent grid has 114 CTAs, tuned for H800 PCIe. The scheduler uses the
same grid size independently of the physical SM count. Large M uses a
256x128x128 tile with two-CTA multicast; smaller M uses 64/128x128x128 tiles.
Per-thread, per-device scratch retains old buffers for CUDA Graph replay and
grows geometrically; allocations live for the process lifetime.

Run `python test/ops/check_fp8_sm90.py` after building. It needs PyTorch with
FP8 support, CUDA headers and g++, and builds a temporary test-only adapter.
Use `--lib /path/to/libfastllm_tools.so` for a non-default build directory.
It checks FP16/BF16 arithmetic against an independent FP32 reference, bias,
zero inputs, M tails, graph replay after buffer growth, and rejection of
unsupported shapes/layouts. Model accuracy evaluation is separate.

DeepGEMM is distributed under the MIT license; see `LICENSE`.

---

### Overview (`third_party/flashinfer/README.md`)

upstream: https://github.com/flashinfer-ai/flashinfer
version: v0.6.16.post1
commit: 5f3d1b3fc6e1ed8a79429986b3637802f1bd2b57
source: include/flashinfer

@article{ye2025flashinfer,
    title = {FlashInfer: Efficient and Customizable Attention Engine for LLM Inference Serving},
    author = {
      Ye, Zihao and
      Chen, Lequn and
      Lai, Ruihang and
      Lin, Wuwei and
      Zhang, Yineng and
      Wang, Stephanie and
      Chen, Tianqi and
      Kasikci, Baris and
      Grover, Vinod and
      Krishnamurthy, Arvind and
      Ceze, Luis
    },
    journal = {arXiv preprint arXiv:2501.01005},
    year = {2025},
    url = {https://arxiv.org/abs/2501.01005}
}

---

### Overview (`third_party/gguf/README.md`)

# GGUF 兼容程序

## Source
- https://github.com/ggml-org/llama.cpp
- https://github.com/ikawrakow/ik_llama.cpp

---

### Overview (`third_party/hipify_torch/README.md`)

# Hipify_torch

hipify_torch is a python utility to convert CUDA C/C++ code into HIP C/C++ code.
It is NOT a parser; it does a smart but basic search-and-replace based on CUDA-to-HIP mappings which are specified in the hipify_torch module.
It can also "hipify" the header include statements in your source code to ensure that it's the hipified header files that are included.



- [Interface](#interface)
  - [Through cmake](#through-cmake)
  - [Through python](#through-python)
- [Utilities](#utilities)
  - [CMake utility function](#cmake-utility-function)
- [Custom hipify mapping](#custom-hipify-mapping)
- [Intended users](#intended-users)



# Interface

## Through cmake

From the parent `CMakeLists.txt` file include the `Hipify.cmake` file from `./cmake/Hipify.cmake`

### API function -- ***hipify()***

This function executes the hipify conversion logic on an input directory recursively.

```
function(hipify CUDA_SOURCE_DIR HIP_CONFIG_DIR CONFIG_FILE)
```
- Takes 3 optional arguments, either CUDA_SOURCE_DIR or CONFIG_FILE argument is required
- `CUDA_SOURCE_DIR` - Full path of input cuda source directory which needs to be hipified.
- `HIP_SOURCE_DIR` - Full path of output directory where the hipified files will be placed.
                     If not provided, it is set to CUDA_SOURCE_DIR.
- `CONFIG_FILE` - JSON format file, which provides additional arguments for hipify_cli.py file.
                  When set, it is having higher precendence over CUDA_SOURCE_DIR/HIP_SOURCE_DIR.

#### Usage examples

```
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/hipify_torch/cmake")
include(Hipify)
# Example invocation - Provides cuda source dir and output hip source dir
hipify(CUDA_SOURCE_DIR ${PROJECT_SOURCE_DIR} HIP_SOURCE_DIR "${PROJECT_SOURCE_DIR}/hip")

# Example invocation - Only cuda source dir provide and output hip files into same dir.
hipify(CUDA_SOURCE_DIR "/home/usr/project_sources/")

# Example invocation - Through config file
hipify(CONFIG_FILE "project_hipify_config_file.json")
```
Note: Update the CMAKE_MODULE_PATH list accordingly, if the hipify_torch repo is cloned into a different directory.

Above lines trigger the hipify script for all sources & header files under the `CUDA_SOURCE_DIR`

## Through python
### hipify_cli.py 
hipify_cli.py takes arguments through command line or a json file(hipify_config.json)
```
python hipify_cli.py --config-json hipify_config.json
```
hipify_config.json
```json
{
    "project_directory": <absolute path of project_directory to config_json>,
    "output_directory" : <absolute path of output_directory to config_json>,
    "header_include_dirs": [<absolute path to proj_dir of directories where headers are present>],
    "includes": [<absolute path to proj_dir of directories to be hipified>],
    "ignores": [<absolute path to proj_dir of directories to explicitly not hipify>],
    "extra_files": [<absolute or relative paths of extra files to be hipified>],
    "hipify_extra_files_only": <true or false, whether to only hipify extra files>
}
```
### hipify.hipify_python
```
from <path to hipify_torch>.hipify.hipify_python import hipify
```
Note: We are in the process of making hipify_torch as an installable python package, then `<path to hipify_torch>` isn't required.

# Utilities

## CMake utility function

### API function -- ***get_hipified_list()***

This utility function can be used to get a list of hipified files from a list of cuda files.

```
function(get_hipified_list INPUT_LIST OUTPUT_LIST)
```
- `INPUT_LIST` - CMake list containing a list of cuda file names
- `OUTPUT_LIST` - Cmake list containing a list of hipified files names. If the cuda file name is not changed after hipify, then it is NOT replaced in the list.

#### Usage example

```
get_hipified_list("${TP_CUDA_SRCS}" TP_CUDA_SRCS)
```

Here the `TP_CUDA_SRCS` in the input list containing cuda source files and doing a inplace update with  output list `TP_CUDA_SRCS`
For the file suffix unique string, list variable name itself is passed as a string.

# Custom hipify mapping

Users can define their own custom mapping by adding a custom_hipify_mapping.json from project_directory from where the hipify() function is being called.
To use a JSON file from a different directory, users can pass in the JSON file path via ```custom_map``` argument in the hipify method.
The custom hipify mappings will be applied *before* any other default hipify mappings. 
The below is the sample JSON file:

```
{
    "custom_map" : {
        "src mapping 1" : "dst mapping 1",
        "src mapping 2" : "dst mapping 2",
        ...
    }
}
```

# Intended users

This module can be used to
- Build [PyTorch](https://github.com/pytorch/pytorch)
- PyTorch CUDA extensions such as [torchvision](https://github.com/pytorch/vision), [detectron2](https://github.com/facebookresearch/detectron2) etc.
- PyTorch submodules CMake-based such as [tensorpipe](https://github.com/pytorch/tensorpipe), etc.
- And any other repo having CUDA files requiring to hipify to build on ROCm.

---

### Overview (`tools/cuda_fp8_sm70/README.md`)

This change adds a Volta-specific FP8 weight-only MMA path for FP16 activations,
FP32 per-output-row scales and 1–8 tokens. It is selected only on SM70 and does
not change stored weights, vocabulary size or persistent model memory.

The general kernel computes 32 output columns and up to 8 token rows per CTA. Eight warps split K.
Coalesced 128-byte row reads are transposed within each warp's shared-memory
tile, then fed into Volta `mma.sync.m8n8k4` with FP32 accumulation. The 33-vector
tile stride reduces shared-memory bank conflicts. Each CTA uses 41,984 bytes
of static shared memory. Scale and optional bias are applied in the epilogue.

For the GDN input shape K=5120, N=16384, the SM70 path uses
`GdnQuadSplitKernel` for all T=1..8. Each CTA produces eight output columns;
the four independent Volta quad-pairs split each 128-byte K block into adjacent
32-byte slices. This coalesces the original weights directly, removing the
shared transpose without a persistent weight copy. Shared storage is 8,192 bytes
of FP32 partial sums. This shape selects the GDN kernel automatically. The
change reorders the partial-sum reduction; it does not promise bitwise equality
with the general kernel.

`test.cu` tests both kernels against the same independent FP64 oracle, including
finite FP8 codes, bias, tails, uneven K partitions and T=1..8 parity.
It also exercises the production GDN shape K=5120, N=16384 through `Try()`,
with the environment override unset, for every T=1..8 with and without bias.
All outputs are checked against an independent FP64 reference. Exact-sized
input allocations and output sentinels check token bounds; both `0` and
`false` fallback settings must reject the call without modifying output.

The companion paged-attention optimization is guarded by
`FASTLLM_PAGED_SM70_SMALL_T` (default on). It covers single-sequence FP16
Q/K/V, head_dim=256, GQA group=6, T=2..8 and cache lengths up to 4096 on SM70.
It reuses paged KV directly in a fused split softmax/PV kernel and merges each
query head separately. Longer eager contexts and other shapes retain their
previous dispatch. Set this flag to 0 for controlled comparisons. See
`test/basic/test_cuda_paged_sm70_small_t.cpp` for independent numeric checks,
fragmented page lists, causal-tail sentinels, local KV head counts 1/2/4, graph
capture and microbenchmarks. These local shapes do not test TP communication.

`FASTLLM_CUDA_FP8_SM70=0` restores the previous FP8 dispatch. The default is on.
`FASTLLM_CUDA_FP8_SMALL_T` still controls the existing generic small-T path.
Other GPUs, block-scaled weights, FP32/BF16 activations, larger token batches,
unaligned input views and overlapping buffers keep their existing paths.
K must be divisible by 128 and at least 1024. Tail output columns are supported.
Dispatch also verifies that the loaded kernel was compiled for SM70; a legacy
PTX-only build must fall back instead of executing an empty architecture stub.

The calculation uses FP16 MMA operands and FP32 accumulation before applying
the row scale. It is not bitwise identical to the previous native multi-row
GEMV, which scales and rounds intermediate FP16 weights/products. The test
compares against an independent FP64 reference and checks exact first-row
parity between T=1 and all supported batched widths. Finite E4M3 weights are
assumed, consistent with the source quantized model.

From the repository root, compile with CUDA 12.x on a V100:

```bash
nvcc -O3 -std=c++17 -arch=sm_70 --default-stream=per-thread \
  -Iinclude tools/cuda_fp8_sm70/test.cu -o /tmp/fastllm-fp8-sm70-test
/tmp/fastllm-fp8-sm70-test
compute-sanitizer --tool memcheck --error-exitcode 4 /tmp/fastllm-fp8-sm70-test
compute-sanitizer --tool synccheck --error-exitcode 4 /tmp/fastllm-fp8-sm70-test
```

Use `--production-only` to isolate the 16 real-shape `Try()` calls for profiling:

```bash
nsys profile --trace=cuda --sample=none --cpuctxsw=none -o /tmp/fp8-gdn-dispatch \
  /tmp/fastllm-fp8-sm70-test --production-only
nsys stats --report cuda_gpu_kern_sum /tmp/fp8-gdn-dispatch.nsys-rep
```

The kernel summary should contain 16 `GdnQuadSplitKernel` launches and no
`RowKernel` launches. This checks the actual launch selected by `Try()`; numeric
agreement alone cannot distinguish two valid kernel implementations.

NVCC 12.8 builds and V100 memcheck/synccheck pass. Racecheck reports
`Device not supported` on the test host, including for an intentional-race
control; it is not counted as a pass. Attention integration tests require CUDA
initialization before loading FastLLM's static constructors under the sanitizer.

Build the library with `-DUSE_CUDA=ON -DCUDA_ARCH=70`. Provenance and the upstream
Apache-2.0 / llama.cpp MIT licenses are retained under
`third_party/ninfer-fp8-sm70/`.

For Qwen3.8-27B on V100 with FP16 activations, the runtime selects these paths
without enabling environment variables. The same local FP8 dispatch can serve
TP shards, but the dedicated GDN shape is single-GPU-only under ordinary TP.
The small-T attention test covers 1/2/4 local KV heads; it does not establish
end-to-end multi-GPU speedups.

To reproduce the eager, CPU-embedding configuration used for validation in a
fresh shell (without inherited tuning overrides):

```bash
FASTLLM_CUDA_GRAPH=0 FASTLLM_GPU_TOKEN_HANDOFF=0 FASTLLM_CUDA_FP8_SMALL_T=0 \
  ftllm server /path/to/Qwen3.8-27B-NVFP4 --max_batch 1 --mtp 5 \
  --kv_cache_dtype float16 --tokens 69632 --chunked_prefill_size 512
```

The generic `FASTLLM_CUDA_FP8_SMALL_T` override is retained for its other
callers, including fused LinearAdd; it is independent of the SM70 kernels.
Draft NVFP4, short attention and eligible SM70 direct KV reads are already on
by default. The short-attention and direct-KV optimizations require FP16 KV;
FP8 KV remains on its existing dispatch.
The new four-KV-head direct-read path is restricted to SM70 even when
`FASTLLM_PAGED_CUBLAS_LINEAR_KV=1`; other architectures retain the existing
two-head eligibility and fragmentation budget.

---

### Overview (`tools/ftllm_agent_runtime/README.md`)

# ftllm-agent-runtime

Linux x86-64 companion wheel for FastLLM WebUI. It bundles the official Pi
standalone executable, ripgrep, and fd, and exposes a small Python API over Pi's JSONL RPC mode.
End users do not need Node.js, npm, or Bun.

Runtime requirements are CPython 3.9+, Linux x86-64, glibc 2.17 or newer, and
a CPU with SSE4.2 (the bundled Pi uses Bun's x64 baseline build).
Alpine/musl, ARM64, Windows, and macOS need separate runtime builds.
The wheel has no additional Python package dependencies. Runtime 0.3.3 bundles
Pi 0.84.4, ripgrep 14.1.1, and fd 10.2.0, including their license files.

The bridge keeps uploaded-file analysis isolated and read-only. It exposes
snapshot tools plus WebUI-mediated public-web tools:

- `runtime_info`
- `list_project_files`
- `read_project_file`
- `search_project_files`
- `web_search`
- `read_web_page`

Uploaded source is copied into an isolated temporary directory before Pi
starts. Web requests are delegated to FastLLM's SSRF-protected `WebAgent`
through an authenticated, request-scoped localhost bridge.

Callers can explicitly pass `working_directory` to create a coding-agent run
over a real directory. That opt-in mode starts Pi in the selected directory,
loads its `AGENTS.md`/`CLAUDE.md` context, and enables `read`, `bash`, `edit`,
`write`, `grep`, `find`, and `ls`. Those tools can modify files and execute
commands with the permissions of the hosting process, so applications should
validate and clearly display the selected directory before starting a run.
FastLLM WebUI limits selection to `--agent-workspace-root` (the current user's
home directory by default) and only enables this mode on a loopback listener.
Using it with a non-loopback `--host` additionally requires the explicit
`--allow-remote-workspace-agent` flag.

## Build the Linux wheel

```bash
python scripts/fetch_pi.py
python scripts/fetch_tools.py
python -m build --wheel --outdir build/wheels
```

For an offline/repeatable build, populate both archive caches once, then build
without network access:

```bash
python scripts/fetch_pi.py --cache-dir /path/to/cache/pi --offline
python scripts/fetch_tools.py --cache-dir /path/to/cache/agent-tools --offline
python -m build --wheel --no-isolation --outdir build/wheels
```

The fetch scripts verify upstream release archives using hard-coded SHA-256
digests and copy the corresponding licenses into the package tree.
Use `--cache-dir /path/to/cache` to retain the verified archive, and add
`--offline` to require local files only. The repository's `make_portable.sh`
and `desktop/package.sh` build and include this runtime automatically.
Wheel builds reject missing binaries, extensions, themes, and licenses.

Before publishing, install `build`, `auditwheel`, `patchelf`, and `twine` in
the build environment, then audit and label the wheel:

```bash
python -m auditwheel repair --plat manylinux_2_17_x86_64 \
  --wheel-dir dist build/wheels/ftllm_agent_runtime-0.3.3-py3-none-linux_x86_64.whl
python -m auditwheel show dist/*-manylinux*.whl
python -m twine check --strict dist/*-manylinux*.whl
```

Upload only the audited `manylinux` wheel, using Twine's token prompt:

```bash
python -m twine upload --username __token__ dist/*-manylinux*.whl
```

The distribution installs native executables into `platlib`, while keeping
the Python bridge's `py3-none` tag because it has no CPython extension ABI.
The manylinux audit checks shared-library compatibility; it does not replace
runtime tests on supported distributions and CPUs. The runtime has been
smoke-tested on Ubuntu 22.04 x86-64, including Pi's `read`, `find`, and `grep`.

## Install and enable in WebUI

Install the companion wheel into the same Python environment as FastLLM:

```bash
python -m pip install ftllm-agent-runtime==0.3.3

ftllm webui /path/to/model
```

For a local build, pass the audited wheel's path to `pip install` instead.
Restart an already-running FastLLM process after installing the package.

FastLLM builds that include **Launcher → Studio → Install Agent dependencies**
install this wheel with the Launcher's Python and pip. The installer uses pip's
configured index/mirror and cache, and installs into the current user's
`${XDG_DATA_HOME:-~/.local/share}/ftllm/agent-runtime/` directory with `--target`.
It verifies Pi and both search tools before activating the installation, then
enables the current Studio without restarting the model. Failed installations
leave any previous managed runtime intact and can be retried from the UI.

The companion wheel includes `rg` and `fd` beside Pi. The bridge prepends this
directory to the child process's `PATH`, so directory searches work even when
neither command is installed system-wide. Pi runs in offline mode and does not
download additional tools during use.

Pi is the default agent runtime for code tasks and Web Agent searches. Use
`--agent-runtime builtin` to force the original single-model-call paths, or
`--agent-runtime auto` to prefer Pi while allowing a missing package fallback.
Library callers can pass a `threading.Event` as `cancel_event` to
`PiAgentRuntime.stream()`; setting it terminates the request-scoped Pi process.
An `agent_end` event alone does not indicate success: the bridge checks the
final assistant response and raises `PiAgentError` for a token-limit stop,
backend error, aborted response, or thinking without a visible answer. Partial
text is preserved in the emitted events, but these outcomes do not emit `done`.
For Pi 0.84.4, an `agent_end` carrying `willRetry` is intermediate even when
that field is false: length recovery / compaction can still follow it. The
bridge waits for `agent_settled` before checking the final response, allowing
Pi's own retries to recover. A closed stream without this event is an error.
Legacy RPC streams with unannotated `agent_end` retain their older boundary.
The default bridge limits remain 4096 output tokens per response, 8 model turns
and 300 seconds per task. In FastLLM WebUI these are controlled by `--max_token`,
`--pi_agent_max_turns` and `--pi_agent_timeout`; the Pi path currently uses a
4096-token fallback when `--max_token` is nonpositive. These limits can end a
long coding task before its work is complete.
Tool activity is normalized into `tool_start`, `tool_update`, and `tool_end`
events with stable call IDs, bounded arguments, output text, and error state.
The optional `images` argument accepts image paths and MIME types and forwards
up to six images through Pi's RPC prompt format.

## Smoke test

```bash
ftllm-agent-runtime info
ftllm-agent-runtime probe \
  --api-base http://127.0.0.1:8080/v1 \
  --model your-model \
  --file README.md \
  "Read the project file and summarize its first heading."
```

To smoke-test the writable coding-agent mode in a disposable project:

```bash
ftllm-agent-runtime probe \
  --api-base http://127.0.0.1:8080/v1 \
  --model your-model \
  --directory /path/to/project \
  "Inspect the project and run its tests."
```

## Upstream

Pi Agent is distributed under the MIT License. The pinned upstream license is
committed at `src/ftllm_agent_runtime/licenses/PI_LICENSE` and included in
built wheels.
Search-tool license files and pinned version/source information are included
under `ftllm_agent_runtime/licenses/agent-tools/`.

---

### Overview (`tools/nccl_latency_bench/README.md`)

# Standalone NCCL latency benchmark

This benchmark calls NCCL directly from C++ in one process controlling two GPUs.
It reports three different latency definitions:

- `host enqueue`: CPU time spent submitting one grouped two-rank all-reduce.
- `CUDA event complete`: completion time observed on the CUDA streams.
- `host enqueue+wait`: host-visible latency when every collective is immediately
  synchronized. This includes CUDA/NCCL API and host scheduling overhead.
- `batched / operation`: many collectives are queued before one synchronization,
  which amortizes per-call host synchronization and is closest to the timing style
  commonly used by throughput-oriented collective benchmarks.

Build and run:

```bash
cd tools/nccl_latency_bench
make
NCCL_DEBUG=INFO ./nccl_latency_bench
```

Useful focused run for a 2048-element FP16 hidden state:

```bash
./nccl_latency_bench --bytes 4K --warmup 100 --iters 1000 --batch-iters 2000
```

Compare NCCL's automatic protocol selection with the low-latency protocol:

```bash
./nccl_latency_bench --bytes 4K
NCCL_PROTO=LL ./nccl_latency_bench --bytes 4K
```

To inspect the selected algorithm and protocol for a minimal run:

```bash
NCCL_DEBUG=TRACE NCCL_DEBUG_SUBSYS=COLL,TUNING \
  ./nccl_latency_bench --bytes 4K --warmup 1 --iters 1 --batch-iters 1
```

The benchmark prints CUDA peer-access capability. If it is disabled, NCCL may
select a shared-memory or network transport instead of direct GPU P2P, so results
should not be compared directly with an NVLink/P2P-enabled machine.

See [RESULTS.md](RESULTS.md) for measurements from the local dual-RTX-5090
machine that motivated this benchmark.

---

### Overview (`example/quant/qwen3_5/README.md`)

# Qwen3.5 / Qwen3.6 低比特量化配置

本目录提供面向 Qwen3.5 架构文本主干的实验性 GGUF K-quant 配置。

- `Q2_K_ALL_TEXT.json`：所有文本 Transformer 线性权重均使用 Q2_K。压缩最激进，适合测试极限，不建议直接用于要求准确性的任务。
- `Q2_K_MIXED_TEXT.json`：两个最大的 MLP 输入投影 `gate_proj`、`up_proj` 使用 Q2_K；更敏感的注意力投影、线性注意力主投影和 `down_proj` 使用 Q4_K；小型递归门控、embedding、lm_head、视觉权重等保留 FP16。推荐从此配置开始。
- `Q2_K_MIXED_TEXT_MTP_FP8.json`：主模型保持上述 Q2_K/Q4_K 配方，但 MTP 草稿层保留源 FP8。推荐用于单卡低延迟解码；约 456MB 的高精度草稿层可以显著提高主模型精确验收的接受率。

Q2_K 属于 2-bit 量化档位，但还包含分组 scale/min 等元数据，实际权重存储约为 2.625 bit/parameter。

## 推荐的高速配方

Qwen3.5 的 MTP 会先用轻量草稿层预测多个 token，再由量化后的主模型批量验收。贪心解码时只提交与主模型 argmax 一致的候选，因此草稿层精度只影响接受率和速度，不会直接替代主模型答案。

导出时分别量化主模型和草稿层：

```bash
ftllm export /path/to/model \
  --dtype auto \
  --dtype_config example/quant/qwen3_5/Q2_K_MIXED_TEXT_MTP_FP8.json \
  -t 32 \
  -o /path/to/output
```

RTX 50 系建议编译对应的原生架构，而不是依赖旧架构 PTX：

```bash
cmake -S . -B build-sm120 \
  -DUSE_CUDA=ON \
  -DCUDA_ARCH=120 \
  -DCMAKE_BUILD_TYPE=Release
cmake --build build-sm120 --target fastllm_tools -j 16
```

单卡贪心解码推荐 `--mtp 5`。它对应 1 个当前 token 加 5 个草稿 token，恰好位于当前 6-token 小序列快路径内；`--mtp 6` 会越过该边界，实测反而更慢。

```bash
ftllm server /path/to/output \
  --device cuda \
  --tp 0 \
  --mtp 5 \
  --temperature 0 \
  --top_k 1
```

`temperature=0`、`top_k=1`、`repeat_penalty=1` 时使用主模型精确验收。采样模式使用 typical acceptance，不属于逐 token 等价模式。

## 实测结果

Qwen3.6-27B、单张 RTX 5090、原生 `sm_120`、batch=1、输入/输出均为 128 token：

| 配方 | Decode | Prefill | 12 项客观题 |
| --- | ---: | ---: | ---: |
| 主模型 Q2_K/Q4_K，不启用 MTP | 84.59 tok/s | 1128.36 tok/s | 10/12 |
| 主模型 Q2_K/Q4_K + FP8 草稿，`--mtp 5` | 158.26 tok/s | 979.60 tok/s | 10/12 |

该配方使 decode 提升约 87.1%，代价是本组短输入 prefill 下降约 13.2%。512-token 长输出测试中 decode 约为 156 tok/s。导出目录约 19GB；单卡服务配置 8192-token KV 配额时，空闲显存实测 15,766MiB。客观题失败项与不启用 MTP 的混合量化基线相同，说明 MTP 没有进一步降低这组任务的准确率；这不代表主模型量化精度与源 FP8 完全相同。

量化规则按顺序匹配，后面的规则可以覆盖前面的规则。

---

### Overview (`example/quant/qwen3_next/README.md`)

本文件夹下为qwen3_next结构模型的通用动态量化配置

基本模板如下：

``` json
[
    {
        "key" : "(.)*",
        "dtype" : "float16",
        "comment": "default"
    },
    {
        "key" : "(.)*expert(.)*[gate|up]_proj(.)*",
        "dtype" : "ggml_q2_k",
        "comment": "mlp gate_up"
    },
    {
        "key" : "(.)*expert(.)*[down]_proj(.)*",
        "dtype" : "ggml_q4_k",
        "comment": "mlp down"
    },
    {
        "key" : "(.)*shared_expert(.)*[gate|up|down]_proj(.)*",
        "dtype" : "float16",
        "comment": "shared expert"
    }
]
```

可根据需要修改每部分的量化类型

如果对模型结构了解，也可以新增量化规则

---

### Overview (`test/agent/mtp_loop/README.md`)

# MTP 思考循环复现

通过已有的 OpenAI-compatible `/v1/chat/completions` 服务重放 Agent 场景，保存思考和正文，并检测连续精确重复。测试不导入 FastLLM、不调用 CUDA，也不依赖采样实现的新接口，可以独立于 MTP 修复提交和运行。

## 场景

`cases.json` 保留六类构造场景：缓存一致性与字数限制、矛盾测试、工具权限不足、任务已完成的长工具历史、取消/提交竞态、网络分区下冲突的订单要求。工具历史均为固定夹具，`tool_choice=none`，测试不会执行模型生成的工具调用。提示词没有要求模型重复。

## 运行

需要 Python 3.10 或更新版本。默认只用标准库，按 Unicode 字符分析；如需按模型 token 分析，另安装 `tokenizers` 并提供本地 tokenizer 文件：

```bash
python3 -m pip install tokenizers
```

在任意已启动的待测版本上运行最明确的循环场景：

```bash
python3 test/agent/mtp_loop/probe.py \
  --base-url http://127.0.0.1:8000/v1 \
  --model Qwen3.8-27B-FP8 \
  --cases circular_requirements_zh \
  --temperature 0.6 --max-tokens 16384 --repeats 3 \
  --out test/agent/mtp_loop/results/before
```

`--model` 必须与服务提供的模型 ID 一致。服务若需要认证，设置 `OPENAI_API_KEY`；密钥不会写入测试元数据。可加 `--tokenizer /path/to/model/tokenizer.json` 或模型目录；工具不会自动下载模型或 tokenizer。

依次启动旧版、新版及 `--mtp 0` 的服务，用相同参数和不同输出目录运行。脚本仅请求 API，不启动或停止服务、不选择 GPU，也不切换安装库。完整六场景筛查使用 `--temperature 0.6 1.0`，省略 `--cases`。

FastLLM 的思考强度对照可加 `--reasoning-effort medium`；省略时保持服务/模型默认值。`top_k=20`、`top_p=0.95` 默认显式传入。跨服务比较时需确认这些参数和思考开关被服务支持。

每条请求保留 `request.json`、`events.jsonl`、`reasoning.txt`、`content.txt`、`tool_calls.json`、`result.json`。思考/正文和 SSE 在接收过程中写入；请求失败时保留已收到的内容。运行目录保存 `results.json`、`summary.json` 和 API 地址等元数据。输出目录必须不存在，生成结果默认被 Git 忽略。

## 判定与退出状态

- `finish=length` 只表示触及长度上限，不直接判为循环。
- 16/64 单位片段的重复次数用于筛查，代码引用和正常修改也可能重复。
- `cycle` 表示至少 3 轮、覆盖至少 256 单位的**连续精确周期**；`unit` 明确区分字符与 token。起止位置从 0 开始，`end` 不包含在区间内。
- 默认候选周期上限为 16384 字符或 4096 tokens。候选搜索有界，报告的周期经过精确比较，但未检出不能证明没有循环；近义反复和很长周期仍需人工审阅。

默认退出 0 表示请求正常完成，即使检测到循环；传 `--fail-on-cycle` 时检测到循环退出 2。HTTP/SSE 错误或不完整流退出 1，优先于循环状态。随机生成不保证每次复现，少量构造场景不能估计线上发生率。

离线分析已有思考文本，无需连接服务：

```bash
python3 test/agent/mtp_loop/analyze.py \
  test/agent/mtp_loop/fixtures/legacy_reasoning.txt
```

同样可传 `--tokenizer`、`--output analysis.json` 或 `--fail-on-cycle`。不能将字符周期长度直接当作 token 周期长度。

## 已知复现夹具

`fixtures/legacy_reasoning.txt` 是 2026-09-11 在 Qwen3.8-27B-FP8、TP2/MTP3、temperature=0.6 下由最初旧采样库生成的真实思考输出。循环发生在中文订单需求评审中：先得出结论，准备结束，再重新比较相同方案，然后回到相同结论。

该输出的 token 区间 `[4046, 16384)` 包含周期为 2312 tokens 的内容：完整重复 5 轮，再重复 778 tokens 后被上限截断，最终答案为空。字符分析对应区间 `[17638, 69811)`、周期 9750 字符。`legacy_cycle.json` 保存参数、库和原文 SHA256 及预期周期。

同场景 16K 上限复测中，旧库三次有一次出现上述长精确循环，其余两次退出；当前修正版三次均最终退出。正确采样和关闭 MTP 时仍可能长时间思考或触及更低的输出上限。因此本夹具用于验证检测和复现流程，不表示所有思考不收尾都由同一个 MTP 问题引起。夹具中的模型方案不是实现建议。

## 本地回归

以下测试仅需 Python 标准库：真实旧输出的周期检测、整数 token 周期、非循环引用、截断响应、UTF-8 SSE 拆包、错误和原始请求保留。

```bash
python3 -m unittest discover -s test/agent/mtp_loop -p 'test_*.py' -v
```

此目录只包含用例、API 探测器、离线分析和小规模夹具。采样内核单元测试及 CMake 注册仍由对应实现改动维护；历史 GPU 编排脚本、旧共享库和完整批量输出不属于此测试包。

---

### Overview (`third_party/flashinfer/attention/sparse_mla_sm120/README.md`)

# FlashInfer SM120 sparse MLA headers

This directory is part of the complete FlashInfer `v0.6.16.post1` header
snapshot vendored from commit `5f3d1b3fc6e1ed8a79429986b3637802f1bd2b57`.
The snapshot source and version are documented in
`third_party/flashinfer/README.md`.

The NVIDIA-authored sparse-MLA files retain their BSD-3-Clause notices in each
source file.  FlashInfer's upstream Apache-2.0 license is vendored at
`third_party/flashinfer/LICENSE`.

---

### Benchmark (`docs/benchmark.md`)

# FastLLM Benchmark

[English](benchmark_en.md) · [返回 README](../README.md)

Benchmark 按模型分别整理。测试硬件、推荐启动命令、实测速度和数据限制均放在对应模型页面中。

## 模型

- [Qwen4-Exp / Qwen3.8-Flash-Next](benchmarks/qwen4_exp.md)
- [Qwen3.5 / Qwen3.6 / Qwen3.8](benchmarks/qwen3.md)
- [DeepSeek-V4 / DeepSeek-V4-Flash](benchmarks/deepseek_v4.md)
- [Kimi-K3](benchmarks/kimi_k3.md)
- [Dots3-Note](benchmarks/dots3_note.md)
- [GLM-5 / GLM-5.3-Flash](benchmarks/glm5.md)
- [Laguna](benchmarks/laguna.md)
- [DeepSeek-V4.1-Flash](benchmarks/deepseek_v41.md)
- [Naive-N0.5-Flash](benchmarks/naive_n05.md)

---

### Benchmark En (`docs/benchmark_en.md`)

# FastLLM Benchmarks

[中文](benchmark.md) · [Back to README](../README_EN.md)

Benchmarks are organized by model. Open a model page for its tested hardware, recommended launch commands, measured speed, and result limitations.

## Models

- [Qwen4-Exp / Qwen3.8-Flash-Next](benchmarks/qwen4_exp_en.md)
- [Qwen3.5 / Qwen3.6 / Qwen3.8](benchmarks/qwen3_en.md)
- [DeepSeek-V4 / DeepSeek-V4-Flash](benchmarks/deepseek_v4_en.md)
- [Kimi-K3](benchmarks/kimi_k3_en.md)
- [Dots3-Note](benchmarks/dots3_note_en.md)
- [GLM-5 / GLM-5.3-Flash](benchmarks/glm5_en.md)
- [Laguna](benchmarks/laguna_en.md)
- [DeepSeek-V4.1-Flash (Chinese)](benchmarks/deepseek_v41.md)
- [Naive-N0.5-Flash (Chinese)](benchmarks/naive_n05.md)

---

### Context Length Extension (`docs/context-length-extension.md`)

# FastLLM 多模型上下文扩展

支持适用模型的静态 YaRN 扩展，包含公共配置解析、CPU/CUDA 旋转、分页缓存写入、启动容量检查和服务元数据。本文同时说明已验证范围及尚未接入的布局。下述真实模型验证基于 `9d948ec6` 之后的上下文扩展实现，日期为 2026-09-06。

## 使用方法

`--max_context_length`（别名 `--max-context-length`）设置单会话输入与输出合计上限。`--rope_scaling`（别名 `--rope-scaling`）设置位置编码，接受 `yarn` 或 JSON。二者均为公共模型加载参数，CLI、server 和 Launcher 共用同一套 C++ 解析规则；不修改 checkpoint 的 `config.json`，也不引入扩展专用环境变量。

Qwen3.8-27B-FP8 的 1,000,000 token 配置：

```bash
numactl -C 0-31 -m 0 ftllm server /path/to/Qwen3.8-27B-FP8 \
  --tp 2 --kv_cache_dtype fp4 \
  --max_context_length 1000000 --rope_scaling yarn
```

这里 `1M` 为十进制 1,000,000，包含输出空间。该命令表达目标配置，**不代表双 24GB 显卡能够容纳它**；显存检查发生在 warmup 校准后、服务就绪前。不要为了通过检查随意增加 `--tokens`，它同样需要实际显存支持。

`yarn` 简写仅在已知原始 RoPE 长度时可用。当前 Qwen3.5 适配器确认声明长度 262144、theta=10000000、rotary_dim=64、交错三轴 sections=`[11,11,10]` 的配方后，可以采用 original=262144，并根据目标向上取整得到 factor=4。其他 checkpoint 应显式提供原始长度，例如 Qwen3：

```bash
numactl -C 0-31 -m 0 ftllm server /path/to/Qwen3-0.6B \
  --device cuda --tokens 65536 --chunked_prefill_size 8192 \
  --max_context_length 65536 \
  --rope_scaling '{"rope_type":"yarn","factor":2,"original_max_position_embeddings":32768}'
```

Qwen3 的配置声明可能是 40960，而官方 YaRN 原始长度为 32768，二者不能混用。省略 factor 时根据目标和原始长度计算；配置里已有 factor 时保留它，目标超过覆盖范围则报错。Qwen3.5 的完整写法为：

```json
{"rope_type":"yarn","factor":4,"original_max_position_embeddings":262144}
```

Launcher 的高级参数“RoPE 扩展”可填写同一个字符串或 JSON；JSON 作为一个 argv 元素传递。工作室通过 `/v1/models` 获取实际上下文。

Python 在创建模型时传入扩展参数，随后 warmup 校准容量：

```python
from ftllm import llm

llm.set_device_map("cuda:0")
llm.set_max_tokens(65536)
model = llm.model(
    "/path/to/Qwen3-0.6B", dtype="float16",
    max_context_length=65536,
    rope_scaling={"rope_type": "yarn", "factor": 2,
                  "original_max_position_embeddings": 32768},
)
model.warmup()
print(model.context_config)
```

旧 `set_max_context_length` 保留只缩小的兼容语义；扩大窗口必须在构造时配置。模型启动后不支持热改 RoPE，已有 KV、前缀和 CUDA Graph 始终使用本实例的固定参数。

## 支持范围与行为

| 模型/布局 | 支持范围 | 验证范围 |
| --- | --- | --- |
| Qwen2/2.5 普通文本 | 公共配置、普通旋转和融合分页写入 | Qwen2-7B 真实模型；普通布局 CPU/CUDA 算子 |
| Qwen3 dense | 普通、独立 CUDA、TP、分页及非分页入口；YaRN 不分配全长 sin/cos 表 | Qwen3-0.6B 默认回归、YaRN、41,000 token 输入 |
| Qwen3 MoE | 复用公共旋转和 TP helper | Qwen3-30B-A3B-FP8 双卡真实推理及共享算子回归 |
| Qwen3.5 dense/MoE，以及使用该架构的 Qwen3.8 | partial rotary、交错三轴 M-RoPE、TP、内置 MTP、融合 decode | Qwen3.5-2B、Qwen3.8-27B-FP8；不据此声明所有 MoE checkpoint 均完成实测 |
| 其他模型 | 无新参数时沿用原路径；不开放新的 RoPE 扩展 | 需要模型适配和相应路径验证 |

新 RoPE 扩展入口接入 **HF 模型目录和 GGUF 文件**，仍受上表的模型布局限制。GGUF 显式传入 `--rope_scaling` 时，在 `InitParams` 前应用公共配置；读取 GGUF 的窗口、theta、旋转维度、scaling 元数据和 Qwen3.5 交错 sections，原生加载路径保持不变。GGUF、FLM、自定义 GraphLLM 的 CLI 仅设置 `--max_context_length` 时保留旧的限长路径：可以缩小，超过模型窗口时仍取原上限。FLM 和自定义 GraphLLM 显式设置 `--rope_scaling` 仍会报不支持。Python 的新构造参数支持 HF 目录和 GGUF；其他格式继续使用旧长度 setter。上下文扩展不会额外增加某模型对 FP4、MTP 或其他后端的支持。结合主机 KV 备份的长上下文用法见 [KVMem](kvmem.md)。

当前支持 default、linear 与静态 YaRN。linear 扩大声明窗口同样需要明确原始长度。已有 dynamic NTK 在没有 RoPE 覆盖且没有扩大声明窗口时保留原路径，不开放新的动态扩展。外部 DSpark 与 YaRN 同用仍拒绝启动。Qwen3.5 的 DFlash2 可保留独立的原生 default RoPE 与目标 YaRN 同用，但草稿必须显式声明所有层均为滑窗注意力，窗口不超过其原生长度，且不使用缩放、partial rotary 或 M-RoPE。HF/GGUF 两个加载入口执行同一项检查。该组合不承诺短上下文相同的草稿接受率；主模型与草稿分别使用自己的旋转配置。

| HF 新入口的输入 | 行为 |
| --- | --- |
| 无新参数，模型为 default RoPE | 保持旧执行路径 |
| 模型配置已经声明 YaRN | 使用归一后的 YaRN，配置窗口不重复乘 factor |
| 显式目标小于或等于支持窗口 | 设置目标，并检查实际容量 |
| 目标超过声明窗口，缺少有效扩展配置 | 初始化报错，不再静默压回原上限 |
| 缺少 YaRN original、非法 factor、未知字段/算法或冲突别名 | 给出配置错误 |
| 位置目标超过 16777216 | 拒绝，当前 position IDs 为 FP32 |
| 校准后的 KV 容量小于显式目标 | warmup 失败，报告目标及校准容量 |
| 未显式设置目标 | 保留按物理 KV 容量限制实际窗口的行为 |

## 实现结构与模型接入

公共实现位于 [`include/contextconfig.h`](../include/contextconfig.h) 和 [`src/contextconfig.cpp`](../src/contextconfig.cpp)。

- `ContextOptions`：用户目标长度和 RoPE 覆盖。
- `ModelContextSpec`：由模型类声明是否支持、配置作用域和交错 M-RoPE 布局。
- `RopeConfig`：归一后的 theta、factor、原始长度、旋转维度、YaRN beta 区间、幅度和三轴 sections。
- `ContextPlan`：模型声明长度、用户目标、最终有效窗口及 RoPE 参数。模型实例持有，多个模型不共享可变配置。

HF loader 和显式配置上下文的 GGUF loader 在 `InitParams` **之前**调用 `ConfigureContext`，避免先按旧窗口分配资源再重建模型。模型的 `InitContextParams` 将计划应用到自身旋转参数；C API 保留旧入口，增加带 context options 的入口和已解析配置查询，错误不会跨 ctypes 直接抛出。

HF/GGUF loader 通过 RAII 管理初始化中的模型，HF loader 还管理 LoRA 资源；CLI/服务公共工厂在 warmup 或容量检查失败时释放已加载模型，避免重试启动遗留权重。直接使用 Python `llm.model` 的调用方仍负责在失败或使用结束后调用 `release_memory()`。

解析器区分顶层与 `text_config`，归一 `rope_parameters`、旧 `rope_scaling` 及 `type` 别名；不修改视觉编码器配置。新旧字段冲突必须显式覆盖消解。跨算法切换仅保留 theta 和布局等兼容字段，不继承旧算法的专属参数。当前接受裸单组配置及 `{"full_attention": {...}}` 的单组覆盖；**尚未实现多个 RoPE 组同时持有不同计划**，不能把此语法当成已支持 Laguna 的 full/sliding 多组布局。

新增标准布局模型时：

1. 在模型类提供 `GetContextSpec()`，选择配置作用域，核实原始长度来源、head_dim 和旋转布局。
2. 在初始化连接 `InitContextParams`，在全部 Q/K 旋转入口使用同一份配置。普通和融合路径都必须覆盖，包括 TP、图及投机分支；缺少融合实现时可以走语义一致的普通路径。
3. 添加该 checkpoint 的默认/扩展回归和容量测试。普通文本与 Qwen3.5 的实现提供两种已接入布局，无需再改 CLI、Launcher 或 API 长度字段。

特殊布局仍需要相应工作：MLA、不同幅度/score 规则、多组 RoPE、绝对位置表及新动态算法不能仅注册模型名称。首批模型均包含随 token 增长的全注意力，容量检查复用已有 `AutoWarmup` 校准后的 token 容量。纯滑窗、纯线性及压缩缓存模型接入时，需要扩展容量策略，不能直接套用此比较。当前错误报告目标与校准 token 容量，**尚未提供各卡“所需/可用字节”详细清单**。

## 旋转、KV 与执行路径

复用现有 `YarnRopeEncoding` 和 CUDA `FastllmYarnInvFreq`，完整应用频率插值、外推、beta 区间及 attention factor。原始位置长度独立保存，不会被服务窗口替换。

Qwen3.5 的 head_dim=256、partial rotary factor=0.25，实际仅旋转 64 维。CPU/CUDA 的 YaRN 增加与原 M-RoPE 一致的交错三轴选择。幅度只施加在旋转部分；K 旋转后再写入 FP16/BF16/FP8/FP4 KV，V 和 gate 保持原值。

Qwen3.5 专用融合 decode 接收同一份 YaRN 参数；普通 prefill、TP 与内置 MTP 的对应入口也连接该计划。专用融合 prefill 尚未增加 YaRN，启用扩展时使用现有拆分操作完成相同计算。这可能影响 prefill 性能。

算子按实际位置计算频率，不使用额外的设备端 `inv_freq` 缓存。Qwen3 的 YaRN 路径跳过全长 sin/cos 表及依赖该表的融合 attention。

## 服务长度和显存

三个长度分别展示：`model_context_window` 是 checkpoint 原声明，`configured_context_window_limit` 是用户目标，`context_window` 是实际接受窗口。C++ 计划在 warmup 后更新，Python 和服务元数据读取该结果；不再把新计划重新压回 checkpoint 声明长度。请求端仍按模板及多模态展开后的完整输入加输出限制检查长度。

`--tokens` 表示共享 KV 池容量，不是每条会话的单独预留。显式窗口必须能够放进校准后的池；并发仍受共享页和状态池限制。chunked prefill 限制中间工作区，不能消除最终 KV 存储需求。

Qwen3.8-27B-FP8 有 16 层全注意力、4 个 KV head、head_dim=256。按当前格式计算，其 1,000,000 token 全注意力 KV 为：

| 类型 | 每 token 字节 | 两卡合计 | TP=2 每卡 |
| --- | ---: | ---: | ---: |
| FP16/BF16 | 65536 | 61.04 GiB | 30.52 GiB |
| FP8 | 32768 | 30.52 GiB | 15.26 GiB |
| FP4，包含 block16 scale | 18432 | 17.17 GiB | 8.58 GiB |

还需要权重、线性注意力状态、MTP、图及工作区。本机两张 24GB 显卡的既有 8K FP4/TP2 配置常驻合计约 37GiB，换成 1M KV 的估算超过 54GiB，不能承诺完整 1M。已测自动预算为 **462080 token**（TP2、FP4、pageLen=128、MTP/图关闭、GPU 内存比例 0.9）；请求 1M 时明确失败。容量会随启动参数和空闲显存变化。

## 验证与复现

所有构建和测试限定在 NUMA0：`numactl -C 0-31 -m 0`。构建产物位于独立 build 目录，没有覆盖本机已安装的 Python 包。在仓库根目录可用 `PYTHONPATH=build-fastllm/tools numactl -C 0-31 -m 0 python3 -m ftllm.cli server /path/to/model` 运行构建版本，再追加上文的上下文参数。

```bash
numactl -C 0-31 -m 0 cmake --build build-fastllm \
  --target fastllm_tools contextExtensionRegression cuda_fp4_kv_test -j 16
numactl -C 0-31 -m 0 ctest --test-dir build-fastllm \
  -R 'context_extension|cuda_fp4_kv' --output-on-failure
numactl -C 0-31 -m 0 compute-sanitizer --tool memcheck --error-exitcode 99 \
  build-fastllm/contextExtensionRegression --cuda
```

测试目标需要 `-DUNIT_TEST=ON`；CPU-only 构建也可以运行 `context_extension_cpu`。`test/api/context_extension_model_probe.py` 是显式运行的真实模型探针，不下载模型；记录首 token logits、生成 token、耗时和解析后的窗口。MTP 探针使用普通 token 输出，避免 `output_logits` 自动禁用 MTP；`--no-logits` 可用于对应的非 MTP 对照。例如复现实际超过原声明的 Qwen3.5 输入：

```bash
numactl -C 0-31 -m 0 python3 test/api/context_extension_model_probe.py \
  --python-root build-fastllm/tools --model /path/to/Qwen3.5-2B \
  --output /tmp/context-qwen35 --context 300000 --tokens 300032 \
  --input-tokens 270000 --kv fp4 --rope yarn
```

## 验证范围

回归覆盖配置解析和冲突检查、独立标量 RoPE/M-RoPE 参考、融合分页写入、FP4 KV、CUDA Graph、启动容量不足时的释放和重试，以及 CPU-only 构建。
高位置算子输入覆盖 32768、262144、999999、1000000；这不等于完整 1M 请求验证。

真实模型检查覆盖 Qwen2-7B、Qwen3-0.6B、Qwen3.5-2B 和 Qwen3-30B-A3B-FP8 的短输入；Qwen3-0.6B 的 41000-token 输入、Qwen3.5-2B 的 270000-token FP4 输入均完成开头口令检索。
Qwen3.8-27B-FP8 的 TP2、FP4、MTP1 和 Graph 在短请求中通过；该双卡配置申请 1M 上下文时校准容量为 462080，按预期拒绝启动。Qwen3.5-2B 的图片请求完成，logits 有限。
这些是有限请求与回归检查，不是长上下文质量基准；更复杂检索、并发、长前缀复用和完整 1M 请求未据此验收。

## 模型配置参考

- [Qwen3 的原始长度和 YaRN 配方](https://huggingface.co/Qwen/Qwen3-8B#processing-long-texts)
- [Qwen3.8-27B-FP8 的 1M 配置](https://huggingface.co/Qwen/Qwen3.8-27B-FP8#best-practices)

声明长度、RoPE 配置和物理 KV 容量必须同时满足；放宽声明长度不能替代位置编码或显存支持。

---

### Cuda Expert Cache (`docs/cuda-expert-cache.md`)

# CUDA expert-cache building blocks

The cache metadata, record transport, and routing reductions are separate from
the model and quantization adapter. They use ordinary CUDA operations, with no
dispatch based on an SM version. Runtime launch choices use cache capacity,
record size, and device resource limits.

## Enabling the cache

Hybrid CLI startup also supports opt-in [automatic memory planning](hybrid-auto.md)
with `--moe_cuda_cache auto` and `--moe_cpu_cache auto`. Numeric defaults and
the native cache APIs retain their existing behavior.
Use `--auto` to also select devices, TP/PP, resident experts, chunk size and threads.

Use `--moe_cuda_cache 5g` (alias `--moe-cuda-cache`) with CUDA compute and
host/NUMA experts, or call `ftllm.llm.set_moe_cuda_cache(5 << 30)` before model
loading. Zero disables the cache by default. The budget covers expert records
on each CUDA device that runs the cache;
KV cache, common weights, workspaces and cache metadata are separate. Sizes
use binary units and must fit in uint64. No cache-specific environment
variables or experimental kernel switches are required. CUDA Graph uses the
existing application setting.

For unequal free memory, `FASTLLM_MOE_CUDA_CACHE_BYTES_<device>` overrides the
budget on that logical CUDA device (after `CUDA_VISIBLE_DEVICES` remapping).
Values are unsigned decimal **bytes**, without unit suffixes; unset or empty
inherits the CLI/API budget. Zero disables that device's cache; invalid or
overflowing values disable it with a warning. The global CLI/API budget must
still enable cache preparation and hold at least 16 expert records. Device
budgets are read when each cache is first used; change them before launching
the model. For example, 12 GiB on logical GPU 0 and 2 GiB on logical GPU 1:

```sh
FASTLLM_MOE_CUDA_CACHE_BYTES_0=12884901888 \
FASTLLM_MOE_CUDA_CACHE_BYTES_1=2147483648 \
ftllm server /path/to/model --device "{'cuda:0':1,'cuda:1':1}" \
  --moe_device numa --moe_cuda_cache 2g
```

The general adapters run SwiGLU experts for one to nine tokens with FP32, FP16
or BF16 activations. The V4.1 adapter has a separate BF16 contract:

| Expert weights | Host/cache record | Compute requirements |
| --- | --- | --- |
| GGUF `Q*`, `IQ*`, F32/F16/BF16 | Original ordinary GGUF blocks, including their embedded scales | Each projection width must contain complete quantization blocks; mixed gate/down types and different layer widths are supported |
| `NVFP4_BLOCK_16_E4M3` | Packed E2M1 weights, planar E4M3 block scales, global scales | Existing compact NVFP4 adapter |
| `FP8_E4M3` | Original E4M3 bytes and FP32 block-scale arrays for gate/up and down | Hidden/intermediate widths and column scale blocks divisible by 4 |
| `FP8_E4M3_BLOCK_128` | Original interleaved 128-byte weight blocks and FP32 scales | Hidden/intermediate widths divisible by 128 |
| GLM 5.3 / DeepSeek V4.1 `FP8_E4M3_BLOCK_128` | Native NUMA gate/up row pairs; 128 E4M3 bytes followed by one FP32 scale | BF16 scored decode and small-batch verification; widths divisible by 128; GLM also reuses resident weights during multi-row prefill |
| GLM 5.3 `NVFP4_BLOCK_16_E4M3_PACKED` | Native NUMA gate/up row pairs, packed E2M1 values, inline E4M3 block scales and per-row global scales | BF16 decode, 2–9-row verification and NUMA-assisted multi-row prefill; hidden/intermediate widths divisible by 128 |
| DeepSeek V4.1 `NVFP4_BLOCK_32_E8M0` | 16 packed E2M1 bytes and one UE8M0 scale per 32 weights | BF16 decode and 2–8-row verification; NUMA weights and widths divisible by 32 |
| DeepSeek V4.1 Q2_K gate/up + Q4_K down | Registered NUMA Q2_K_R4/Q4_K_R4 blocks, retaining cross-interleaved gate/up rows | BF16 decode and 2–8-row verification; widths divisible by 256 |

The scored FP8 adapter shares cache admission, NUMA storage and temporary
uploads with the general FP8 backend. It reads registered rows directly,
without a second host weight snapshot or dequantized resident weight copy.
Projection results round to BF16; clamp and route scores are applied before
down-activation FP8 quantization (128 values for GLM, 32 for V4.1). V4.1
retains its input quantization. Ordinary FP8 models such as Qwen retain the
general adapter's arithmetic. FP8 checkpoint weights become packed block-128
records during NUMA registration; no additional model-name or GPU-architecture
switch is needed.

The generic GGUF adapter accepts Q2_0, Q4_0/Q4_1, Q5_0/Q5_1,
Q8_0/Q8_1, Q2_K–Q6_K, IQ1_S/IQ1_M, IQ2_XXS/IQ2_XS/IQ2_S,
IQ3_XXS/IQ3_S, IQ4_NL/IQ4_XS, and F32/F16/BF16. Gate/up and down
may have different types; layers share an LRU pool with the largest record
stride, while each layer retains its own shapes, types and down offset.
Experts within a layer must have matching projection layouts. Gate/up rows
use ordinary concatenated `[gate, up]` order. CPU R4/interleaved layouts are
not admitted as ordinary GGUF blocks.

Temporary streamed experts can instead retain NUMA's gate/up row interleave
and IQ2_XXS/IQ2_XS/IQ2_S/IQ3_XXS R4 packing. The projection kernels read these
layouts directly, without a separate restoration kernel or a second weight
copy in host memory. Quantized values and projection arithmetic are unchanged.
Selection uses each projection's type, shape and batch dispatch; unsupported
R4 formats or batches retain lossless restoration. Persistent cache entries
remain canonical: promotion from a temporary NUMA record restores it in VRAM
without transferring the expert over PCIe again. GLM GGUF also reads its
IQ2_XXS/IQ2_S gate and IQ3_XXS/IQ4_XS down NUMA layouts directly, using its
own Q8_K/BF16 projections, clamp and score placement. V4.1 retains its
specialized path.

The GGUF fast path quantizes input and SwiGLU activations to Q8_1 and uses
DP4A integer dots for Q2_0, IQ1_M, IQ2_XXS, IQ2_XS and IQ2_S projections.
Each warp computes one output row; a block shares the activation and codebook.
Both projection types must be supported, widths must contain complete blocks,
and widths above 24,576 use the floating-point fallback. A per-device workspace
is allocated before graph capture and reused across layers and batch rows.
For hidden size 2,560 and intermediate size 640 it uses 353,600 bytes, separate
from the expert-record budget. Other formats retain the register-dequantized
floating-point kernels. Selection depends on projection types, dimensions and
available workspace, without an environment override. Q8_1 activation
quantization changes numerical results and can change generated tokens.

Both paths accumulate in FP32 and round projection/SwiGLU results to the
activation type. With registered NUMA weights, GGUF also uses the existing
hybrid decode scheduler: resident experts run on CUDA, while misses can be
split between CPU workers and streamed CUDA execution. Tensor parallel decode assigns expert ownership
across its GPUs and reduces unweighted per-expert results once. Each GPU has
its own cache budget; for example, `--moe_cuda_cache 2g` with two GPUs allows
approximately 4 GiB of expert records in total.
The low-level cache hit/miss counters describe CUDA cache lookups/refills;
hybrid CPU-only routes do not all reach those lookups. Use the expert-parallel
CPU/GPU route counters to measure the fraction of all routes served by GPUs.

Eager expert-parallel planning reads a mapped host residency mirror updated
by the GPU LRU kernel, including during graph replay. Each rank drains its
stream before reading the mirror, so prefill and ordinary cache calls may
safely change residency between decode calls. Devices without a host mapping
retain the device-lookup fallback. This removes the per-layer lookup kernel
and residency readback without changing slot ownership or eviction policy.
For single-row decode, NUMA gate/up jobs run while the caller submits CUDA
expert work; down-projection jobs retain their gate/up completion dependency.
The independent shared branch is submitted before cache admissions, so a
future-token refill does not hold up that branch. Multi-row CPU subsets keep
their existing grouped/row-wise scheduling.

The CPU adapter accepts its existing Q8_0, Q8_1, Q8_K and Q8_K32 activation
formats. Q8_0 SwiGLU quantization is fused into complete 32-value blocks;
other formats use the ordinary whole-row quantizer. CPU shard boundaries and
GEMM task boundaries must meet the corresponding block/row alignment rules.
FP32 hybrid decode and small batches reuse the FP8/NVFP4 scheduling paths;
unsupported CPU layouts or input types retain the existing fallback.

Adapters that cannot borrow registered NUMA storage take an immutable mapped host snapshot before destructive repacking.
This fallback adds host RAM usage, approximately the number of experts times the largest
record stride, in addition to CPU expert storage. Pure GPU expert execution
with on-demand refills remains available through the cache operator without
NUMA registration. Decode and verification batches of up to nine rows use
the cache; larger prefill batches retain the configured CPU/NUMA backend.

## GGUF dynamic decode and verification

Ordinary GGUF experts registered with NUMA can overlap resident GPU work,
PCIe copies of uncached experts, and CPU work. The scheduler uses per-layer
measurements of CPU time, resident/staged GPU time, transfer time and host
submission cost; occasional probes refresh a path that has stopped receiving
work. No fixed-split environment variable is needed. A staged expert executes
as soon as its own transfer and preceding GPU work finish. Staging uses
separate temporary records and does not admit or evict cache entries.

Single-row decode first measures the no-upload CPU baseline, keeps observed
CPU costs by route count, and periodically refreshes that baseline. CPU worker
completion timestamps separate CPU preparation/compute from callback-only
GPU submission stalls; submission cost overlaps the active CPU workers in
the planner. Scored GGUF decode uses the ordinary NUMA row workers with an
output-route map, preserving BF16 rounding, clamp and route-score boundaries
without constructing the multi-row verifier's per-expert task lists. Unscored
GGUF decode, including Qwen Flash-Next, keeps its specialized subset workers
and uses the same corrected timing and single-row planner. Small scored GGUF
verification batches with per-route output (up to 32 rows, subject to decode
kernel support) reuse these exact workers one input row at a time. The first
CPU row launches its workers before submitting GPU work; all-GPU batches still
submit once. Both scored and unscored GGUF verification measure worker CPU
time without callback-only stalls. Verification retains separate estimates
for each layer and row count and charges uploads per unique expert. Other
formats and larger batches retain their grouped path.

Single-GPU FP32 verification accepts two to nine rows. It groups misses by
expert identity, gives all routes of one expert the same CPU/GPU owner, and
copies each staged expert once even when several tokens select it. Experts
with more reused routes are considered first. Transfer cost is charged per
unique expert; compute cost per route. Estimates are kept separately for each
layer and verifier row count. CPU expert arithmetic and cache admission retain
their existing paths. Shared NUMA record layouts and multi-GPU verification
keep their previous resident-GPU/CPU routing.

Single-row decode reuses the Q8 input prepared by resident GPU experts when
computing staged experts. Verifier rows only reuse that scratch for consecutive
routes of the same input row. Qwen's independent shared expert is submitted
while the single-row NUMA jobs and expert transfers are active. GLM GGUF
ordinary decode starts and ends its frequency policy once per CUDA device per
token; speculative/replay calls retain their existing routing.

A zero per-device budget also supports this streamed path for ordinary GGUF
with registered NUMA experts. It allocates metadata and temporary staging
buffers, but zero resident slots and zero resident expert payload. The global
CLI/API budget must still enable preparation; `--moe_cuda_cache 0` by itself
retains its existing behavior of disabling the cache backend. For example:

```sh
FASTLLM_MOE_CUDA_CACHE_BYTES_0=0 \
ftllm server /path/to/model --device cuda --moe_device numa --moe_cuda_cache 2g
```

Check `llm.get_moe_cuda_cache_stats(0)` for actual `payload_bytes` and `slots`.
For the execution split, subtract snapshots from
`llm.get_moe_cuda_cache_route_stats(0)` taken between requests. Those counters
include single-GPU hybrid decode, ordinary GGUF and compact GLM batched verification,
including CPU routes; they exclude prefill, multi-GPU expert parallelism and
pure-GPU mode. Reading them synchronizes the device. A temporary GPU expert
counts as GPU execution, but not as a resident cache hit.

### Global frequency admission for single-token decode

The ordinary Qwen4/GLM GGUF and GLM 5.3 compact NVFP4 hybrid paths observe all layers before
selecting admissions at the end of a token or GLM verification batch. Candidates compete for a global
budget by their heat gain over an eligible resident in the same physical slot
partition. There is no rotating layer eligibility or one-admission-per-layer
limit. Copies run on an admission stream after current cache readers finish;
subsequent cache users wait for the payload and residency publication together.
Candidate and resident ordering is maintained incrementally in indexed heaps.
Decode does not sort all candidates or rebuild resident indices every token.
Scores retain the same float decay and cutoff; in-place heap repairs preserve
expert/slot tie ordering when decay rounds previously different scores together.
Prefill uses its bulk admission path. Compact GLM MTP verification uses the same
global frequency policy; ordinary GGUF verification, thread-TP expert parallelism
and other non-GGUF adapters keep their existing admission policies.

GLM 5.3 NVFP4 uses the same per-layer measured CPU/GPU miss split and per-expert
DMA pipeline as GGUF decode. Resident and temporary records both keep NUMA's
compact gate/up interleave, so DMA copies the original row shards without a
restoration kernel, expanded weights or another host weight snapshot. Its
existing BF16 rounding, routed scores, clamping and block-128 activation
quantization remain in the GLM adapter. Global admissions use this native DMA
path after current readers finish. Layer-partitioned dual-GPU inference advances
and closes the policy once per device per ordinary decode token.

GLM's NUMA backend also prepares streaming when `--moe_cuda_cache 0`, keeping
temporary uploads available without resident slots. Per-device zero-budget
overrides have the same effect. Compact GLM verification can use this path too.

Compact GLM verification supports two to nine rows through the common batched
hybrid dispatcher. A repeated expert has one CPU/GPU owner across rows and is
uploaded once; each temporary expert begins computation after its own DMA.
Resident experts stay on GPU. Per-layer, per-row-count CPU, resident GPU,
temporary GPU and PCIe timings select the missed experts to offload. The CPU
retains grouped expert GEMMs; GPU submission overlaps its gate/up worker queue.
GLM's BF16 boundaries, score placement, block-128 activation quantization and
expert-ordered reduction are retained. Larger batches keep the prefill path.

When MTP is enabled, a compatible compact draft expert table joins the existing
cache budget. Target verification and compatible draft calls advance the global
frequency policy, with admissions after readers complete. BF16 draft expert
weights keep their existing execution path; the GLM NVFP4 checkpoint used in
testing has BF16 draft experts. This covers one GPU per layer, including
layer-partitioned dual-GPU models; GLM thread-TP expert parallelism is unchanged.
Compressed MLA supports learned DSA with MTP and rank-local rollback. Expanded attention uses the dense backend; see the [GLM guide](glm5.md).

With frequency caching active, compact GLM verification can also use the other
GPUs whose caches have already been initialized for the same model. This applies
to 2–9 rows in a layer-partitioned model; attention placement and single-token
decode retain their existing paths. Resident routes prefer the origin GPU and
then another GPU holding that expert. Each unique miss has one CPU/GPU owner,
chosen from measured NUMA, GPU, PCIe and host-staged activation/result costs.
Uploads and kernels run on each GPU while the shared NUMA worker pool runs its
subset. P2P is not required. Per-expert results are gathered before the existing
ordered BF16 reduction, rather than reducing GPU-local partial sums.

The helper reuses the model's original pinned NUMA records and existing cache;
it does not admit foreign-layer experts or duplicate host weights. One reusable
temporary workspace is kept per GPU across origin-device changes. Cache
frequency observation/admission remains on the layer's origin GPU. Cooperative
route counters charge GPU work to its executing device and CPU work/call count
to the origin, so summing devices counts every route and layer exactly once.
Single-GPU and unsupported layouts keep their previous dispatchers.

For greedy GLM MTP, `--mtp_min_p` (0–1, default 0) optionally truncates a draft
chain before admitting a candidate whose full-vocabulary probability is below
the threshold. The existing acceptance-based depth limit still applies; the
threshold itself stays fixed. A rejected first candidate leaves the next step
on ordinary target decode, while later truncation restores the draft KV cache
to its committed prefix. Zero disables the confidence reduction, and sampled
generation keeps its existing behavior. Verbose request completion reports
verified/accepted draft counts and confidence checks/stops; accepted counts
include the final verified batch even if the output limit truncates its delivery.

GLM NVFP4 NUMA-assisted prefill reuses the resident compact records with the
existing multi-row projection and activation kernels. Selected admissions are
uploaded directly into cache slots; other misses retain temporary uploads and
next-expert prefetch. Active resident experts are protected from eviction until
their readers finish. For a layer-partitioned model, only the GPU owning that
layer's output admits new experts, so assist GPUs do not spend their decode
cache budget on another GPU's layers. Prefill fills use the same global heat
policy as GGUF and remain resident when ordinary decode begins.

This prefill path also estimates CPU/GPU placement from normal layer execution,
including transfer and compute overlap. It starts with a small CPU subset and
GPU work from the same layer, without rebuilding synthetic CPU/GPU calibration
curves as successive layers increase the busiest expert's row count. This
placement policy also applies with no expert cache; explicit `FT_EXPERT_LIMIT`
continues to override automatic placement. No new tuning variable is required.

Configure before loading a model. These options have both underscore and hyphen
spellings, and are applied to newly created cache policies:

| CLI option | Default | Meaning |
| --- | ---: | --- |
| `--moe_cache_half_life` | `128` | Heat half-life in decode steps; `0` disables decay |
| `--moe_cache_update_interval` | `1` | Update every N decode steps; decay accounts for N steps |
| `--moe_cache_max_replacements` | `96` | Maximum admissions per update across all layers, including empty-slot fills; `0` disables decode admission |
| `--moe_cache_max_bytes` | `0` | Additional byte limit per update, such as `30m`; `0` means unlimited bytes within the expert-count limit |
| `--moe_cache_min_heat` | `1` | Minimum candidate heat |
| `--moe_cache_margin` | `1` | Require candidate heat > resident heat × factor + margin |
| `--moe_cache_factor` | `1` | Resident heat multiplier, at least 1 |
| `--moe_cache_min_residence` | `0` | Minimum decode steps before an admitted expert may be evicted |
| `--moe_cache_prefill_prior` | `0` | Fraction of rescaled prefill heat retained at decode start, in [0,1]; resident weights remain cached |
| `--moe_cache_rank_by_bytes` | off | Rank by heat gain divided by the candidate's actual record bytes |

The byte limit applies to each update, not each token when the update interval
exceeds one. It charges the compact record copied, including when a tiny cache
uses oversized shared slots. Empty slots do not require a positive replacement
gain, but still consume the count and byte budgets. A slot is replaced at most
once per update. Limits do not apply to prefill fills or temporary GPU execution
of uncached experts, which are separate from admission.

For a hard 30 MiB update limit:

```sh
ftllm server /path/to/model.gguf --device cuda --moe_device numa \
  --moe_cuda_cache 10g --moe_cache_max_bytes 30m --moe_cache_rank_by_bytes
```

The Python API exposes the same settings without environment variables:

```python
from ftllm import llm
llm.set_moe_cuda_cache(10 << 30)
llm.set_moe_cache_policy(
    half_life=128, update_interval=1, max_replacements=96,
    max_bytes=0, min_heat=1, margin=1, factor=1,
    min_residence=0, prefill_prior=0, rank_by_bytes=False,
)
# Load the model after configuration.
```

C++ callers can include `moe_cache_config.h` and use
`SetMoeCacheConfig(MoeCacheConfig{...})`. Existing caches retain their configuration.
The effective policy is logged when a cache is initialized. Higher admission
budgets can improve hit rate while increasing PCIe traffic; benchmark decode
throughput as well as resident-route hit rate when tuning these settings.

## GPU-resident GGUF experts

SwiGLU decode and small batches use these fused kernels when every routed
expert in a layer is already on the same GPU. This path is enabled automatically
for ordinary GGUF tensors with matching layouts within each layer and FP32,
FP16 or BF16 activations. It does not need an expert-cache budget. Larger batches
use grouped MMQ where supported. Shared experts inside the same operator,
repacked layouts and incompatible placements retain the existing operator paths.

A layer uploads only its gate/up and down pointers: 8 KiB for 512 experts.
The kernels read route IDs and scores directly on CUDA, without copying route
IDs back to the CPU or duplicating the weights. Q8-supported formats use the
same five-kernel DP4A sequence as the cache; other supported formats use the
floating-point fused kernels. Scratch storage belongs to the invocation, and
pointer tables are invalidated when any participating tensor moves, frees its
storage or is destroyed. Warm the layer before CUDA Graph capture so its
pointer table already exists.

Cached, resident and streamed experts select their supported kernels
automatically. Different reduction orders can change generated tokens;
the optimized and fallback paths are not guaranteed to be bitwise identical.

For a Qwen4-Exp target GGUF without embedded MTP weights, attach a
`qwen4exp-mtp` GGUF using `--draft /path/to/mtp.gguf --mtp 3`.
The auxiliary file uses HF `mtp.*` tensor names, raw HF norm weights, and
packed `gate_up_proj` / `down_proj` expert tensors. Loading splits the expert
axis while retaining ordinary quantized blocks and applies the norm offset
once. Target and draft host expert tables share the same GGUF LRU pool and
expert-record budget; draft dense weights and verifier state use additional
memory. The existing Qwen4 MTP generation constraints still apply.

Qwen4 hybrid PP can pipeline chunked prefill with greedy MTP and CPU
embedding (`--low_gpu_mem`). The final stage updates draft KV for each chunk,
retains the target hidden row needed at the next chunk boundary, and joins
target request state before the final proposal and prefix snapshot. This also
supports exact QSA KV streaming. CUDA embedding, sampling, restored prefixes
and other unsupported pipeline configurations retain serial prefill.

After warmup, read or reset counters outside timed inference:

```python
from ftllm import llm
before = llm.get_moe_cuda_cache_stats(device=0, reset=True)
# Run the measured requests here.
after = llm.get_moe_cuda_cache_stats(device=0)
print(after["hit_rate"], after["payload_bytes"], after["slots"])
```

The query synchronizes the selected CUDA device and aggregates prepared
cache groups. Reset clears hits/misses while preserving cached weights and
LRU ages. Report actual `payload_bytes`, since free-memory reservation can
reduce the requested capacity. Route counters follow the shared LRU's
convention: each unique missing expert in a row counts one refill miss;
additional duplicate routes count as hits. Invalid expert IDs are excluded.
The API raises an error on builds without GPU cache statistics.

Records remain quantized; FP8 is not expanded to BF16 in the cache. FP8 slot
pointer tables share one allocation before capture: two weight tables for
packed block128, plus two scale tables for native E4M3. The existing indexed
gate/up/down kernels read them. Cache lookup and refill produce slot IDs on the GPU, so
changing routes do not require a per-token host pointer upload. All dependent
computation runs on `cudaStreamPerThread`; asynchronous admission streams publish
an event dependency before subsequent cache reads.

Model integration is available in DeepSeek V4.1 (described below) and the
Qwen4-Exp backbone used by Qwen3.8-Flash-Next. Qwen4-Exp prepares cache sources before deferred NUMA registration, borrowing supported pinned layouts and taking a snapshot for other layouts.
Prefill and unsupported layouts
retain the configured MoE backend. CPU-only and ROCm builds do not enable this
NVIDIA CUDA adapter. A model must prepare its expert tables and call the cache
dispatch/release interfaces to use the adapter; the CLI flag alone does not
add integration to other model implementations.

Qwen4-Exp / Qwen3.8-Flash-Next also supports the cache with thread-level TP:

```sh
ftllm server /path/to/Qwen3.8-Flash-Next --tp 2 \
  --moe_device numa --moe_cuda_cache 4g
```

For supported FP32 NUMA decode and verifier batches of up to nine rows,
individual experts are assigned across the TP ranks by expert ID modulo rank
count. Rank 0 supplies one
authoritative routing decision. Each GPU computes its resident expert subset
of the same layer; rank 0 computes the remaining routes once on NUMA. The
existing TP reduction combines these partial results and the shared-expert
slices. Shared experts launch after routing copies so their GPU work can
overlap the NUMA subset. Recurring cold routes are admitted at most one per
GPU per layer call, subject to measured copy cost; cold routes still execute
on NUMA for the current call. A device with no usable cache leaves its routes
on NUMA without disabling the other devices. A verifier snapshots cache
residency for all rows before admitting cold experts, so every occurrence of
an expert has the same owner. Supported NVFP4 NUMA subsets group rows by expert
to reuse weight reads; other CPU configurations retain row-wise computation.
GPU rows use the existing expert kernels, with admissions deferred until all
resident rows have completed. Rank contributions use fused FP32 multiply-adds
for weighted accumulation, including when all routes execute on NUMA.

The byte budget applies to each device. Ordinary TP decode retains its dense
CUDA Graph segments while expert dispatch runs outside capture. Cache warmup
precedes startup KV budgeting. Larger prefill batches and unsupported EP shapes retain
the rank-zero host-expert path and its existing cache eligibility.
`--moe_device_layers` can keep some expert layers on CUDA; those resident
layers are excluded from host-cache preparation. Disabling the cache preserves
the original rank-zero host-expert execution.

Measure throughput with the cache both enabled and disabled: refill and host
routing overhead can outweigh the saved CPU work. Qwen's existing NVFP4 hybrid
path uses FP32 CUDA activations and BF16 NUMA activations. Changing the measured
CPU/GPU split can therefore change logits and greedy tokens, including between
repeated requests; cache enablement does not promise bitwise CPU equivalence.
NUMA prefill also selects CPU/GPU experts by timing when the cache is disabled.
For numerical comparisons, set the existing `FT_EXPERT_LIMIT=0` in both runs
to assign prefill experts to GPU whenever GPU assistance is supported. Use
`FT_GPU_PREFILL=0` to hold these experts on CPU. These are diagnostic settings,
not recommended throughput settings.

To distinguish backend precision from implementation errors, compare expert
operators with identical inputs and routes before comparing whole-model logits.
Check the fused gate/SiLU and down projections against independently dequantized
weights, then check weighted rank contributions and the TP sum separately.
Small activation differences can cross a top-k routing boundary and replace an
expert, amplifying the difference in later layers.

Qwen4-Exp MTP verifier batches of up to nine tokens can use the cache and become
eligible for CUDA Graph when the remaining graph requirements are satisfied.
Larger batches retain the configured MoE backend. Eligible target and host draft expert tables share the cache budget. GPU-resident draft experts use the GPU backend. Decode/verification graph eligibility still depends on shape, layout and TP capabilities; enabling the cache alone does not guarantee graph execution.

For supported compact NVFP4 host tables assigned entirely to NUMA, preparation
copies only original E4M3/global scales, invokes the model's NUMA registration
callback, then borrows its pinned block-16 shards. Refill restores compact GPU
records without rounding. With the cache enabled, complete 32-row tiles place
packed weights before their FP32 scale plane (`NVFP4_BLOCK_16_PLANAR`), using
exactly the same storage size. CPU kernels and CUDA hybrid-prefill Linear read
this layout directly with the existing arithmetic and scratch buffers. Shards
without complete tiles keep the inline layout; gate/up and down may differ.
The cache is published only after all shards validate. Failure or an exception
releases the temporary snapshot; unsupported layouts retain a full snapshot.
Repeated preparation reuses the group, preserving graph source addresses.
Borrowed weights must remain alive until the model releases the cache.

For Qwen3.8-Flash-Next-NVFP4 the borrowed layout retains about 7.03 GiB of original scales outside the NUMA weight storage. No full snapshot is allocated during loading,
though individual tensor conversions still need temporary storage. Planar
weight ranges let refill use contiguous 16-byte loads without transferring
inline FP32 scales; narrow or inline shards use 4-byte loads. Cache capacity,
LRU replacement and configuration are unchanged. NUMA warmup also returns
freed allocator pages on the final pass when all weights are already registered;
this cleanup runs during warmup, outside request execution.

For a native FP8 checkpoint, keep `--dtype auto` and use CUDA compute with
`--moe_device numa --moe_cuda_cache 5g`. The same byte budget holds fewer FP8
experts than NVFP4 experts of the same shape. Capacity and performance should
be measured per format. The mapped host snapshot also occupies system RAM in
addition to storage used by the fallback backend.

## DeepSeek V4.1

Use CUDA for the main model and shared experts, and NUMA for the routed experts.
Both original NVFP4 and the Q2_K/Q4_K mixed export support this cache. A dual-GPU
configuration with 8 GiB of cache on each device is:

```sh
FT_NUMAS=1 numactl -C 0-31 -m 0 \
  ftllm server /path/to/DeepSeek-V4.1-Flash \
  --device "{'cuda:0':1,'cuda:1':1}" --moe_device numa --threads 30 \
  --moe_cuda_cache 8g
```

The cache borrows registered, pinned NUMA block-32 or Q2_K_R4/Q4_K_R4 weights,
without a separate host weight snapshot. The shared scheduler measures CPU/GPU compute and refill costs
to choose disjoint expert subsets, preferring resident experts. Resident experts
can remain on CPU when that gives a better split. A cache is not a guaranteed
speedup: route reuse, capacity and PCIe transfers matter.

Single-token decode and single-request DSpark verification with 2–8 BF16 rows
automatically use the hybrid cache. Verify groups all rows for each expert on
one backend, preserving CPU weight reuse, and offloads only resident groups.
It admits at most one recurring cold expert after GPU computation when estimated
CPU work can cover the copy. Verify has independent per-device buffers and cost
estimates while sharing the decode cache and LRU state. Ordinary prefill, CUDA
Graph outside TP and unsupported shapes retain the configured backend.
The draft model does not use this routed-expert cache.

TP single-token decode and single-request DSpark verification with 2–8 rows
also support the hybrid cache, including segmented CUDA Graph execution:

```sh
FT_NUMAS=1 FASTLLM_CUDA_GRAPH=1 numactl -C 0-31 -m 0 \
  ftllm server /path/to/DeepSeek-V4.1-Flash \
  --tp 2 --moe_device numa --threads 30 \
  --moe_cuda_cache 8g --dspark 5
```

Layers distribute their cache ownership across TP devices. Routing copies finish
before shared-expert work launches, allowing it to overlap the CPU expert subset.
Results for every verify row use CPU staging and replica upload without requiring
peer access. Cache work stays outside graph capture and preserves captured tensor
addresses. Unsupported inputs or unprofitable splits fall back before launching
shared work, so the shared expert runs exactly once on either path.

Eager TP DSpark verification with 2–8 rows reuses one workspace across candidate
counts and requests. Per-weight activation quantization buffers stay alive
through event-ordered device dispatch, and both devices finish before the
workspace lock is released. Long prefill keeps synchronous dispatch. Set
`FASTLLM_DSV41_DISABLE_TP_VERIFY_ASYNC=1` to compare with the synchronous eager
verification path without changing CUDA Graph or expert-cache settings.

Expert math preserves V4.1 block-32 FP8 activation quantization, SwiGLU clipping,
route weighting before down-input quantization and per-expert BF16 rounding.
Results accumulate in ascending expert-ID order in FP32 before final BF16
rounding. CPU/GPU reduction can still introduce floating-point differences.
For Q2_K/Q4_K, the GPU reads the existing R4 packing directly and encodes FP8-rounded
activations as GGML Q8_K using the signed maximum. Gate/up rows retain their NUMA
interleaving, and route scores are applied before the intermediate FP8/Q8 boundary.
The single-row CPU subset reuses the grouped GGML V4.1 preparation used by verification.
The `cuda_v41_q2_moe_cache` test covers top-6 routes, eviction, CPU/GPU splits,
zero inputs and scores, and SwiGLU clipping against NUMA and a scalar reference.
The block-32 GPU prefill GEMM also accumulates into FP32 before its BF16 cast,
preventing reduced-precision cuBLAS partial reductions. Source NVFP4 experts
are registered before their first GPU prefill use, including when startup
warmup is skipped. Keeping the default warmup moves this one-time packing
cost out of the first request; `--moe_cuda_cache 0` disables the GPU cache.

Optional diagnostics and tuning (the defaults require no environment flags):

| Variable | Meaning |
| --- | --- |
| `FASTLLM_DSV41_MOE_CACHE_MODE=gpu` | Pure GPU cache for single-token decode; misses refill from NUMA. Default `hybrid`. Verify retains its NUMA fallback in pure mode. |
| `FASTLLM_DSV41_MOE_CACHE_PREFETCH=N` | Single-token admission interval, default 31; adjusted to be coprime with the layer count. Zero disables admission for both decode and verify. Positive values enable cost-controlled verify admission each call. |
| `FASTLLM_DSV41_MOE_CACHE_GPU_EXPERTS=N` | Override the measured split for diagnostics: 0–top-k decode routes, or up to 15 resident verify groups. Unset uses the scheduler. |
| `FASTLLM_DSV41_MOE_CACHE_TRACE=1` | Per-device route, residency and admission counters every 1024 calls, for decode and verify. |

Hybrid residency is resident routes divided by all routes; GPU-selected hits
alone do not measure the whole workload. Pure mode reports actual cache hits
and misses, synchronizing at entry and at reporting intervals. Pure mode can
be slower when demand refill dominates.

## Disk-backed multilevel cache

`MergeMOE` models can retain a budgeted subset of disk-backed experts in RAM
and VRAM:

```sh
FT_NUMAS=1 numactl -C 0-31 -m 0 \
  ftllm server /path/to/DeepSeek-V4.1-Flash \
  --device "{'cuda:0':1,'cuda:1':1}" --moe_device disk --threads 30 \
  --cuda_shared_expert true --moe_cuda_cache 4g --moe_cpu_cache 32g
```

`--moe_cpu_cache` sets the process-wide RAM budget; `--moe_cuda_cache` sets
VRAM **per GPU**. Both default to zero. A GPU hit executes on that GPU, a RAM
hit executes on CPU, and a miss loads the expert from disk. Cold prefill can
stream missing experts through CUDA; `FT_GPU_PREFILL=0` disables this path.
`FASTLLM_DISK_MOE_GPU_PREFILL_MIN_TOKENS` sets its minimum batch size (default 32).
NUMA-cache modes and per-device environment overrides do not apply
to this backend.

Gate/up and down weights are cached together. Replacement prefers the lowest
batch-normalized frequency score, then the least recently used entry. Scores
halve every 4096 expert lookups. RAM replacement requires a second visit and
a strictly hotter candidate; CUDA promotion also requires a second visit,
admits at most one expert per layer invocation, and requires a score advantage
greater than 16 for replacement. Promotion releases the RAM copy, increasing
combined coverage; another GPU can load it again if needed.

Budgets cover retained expert payload, excluding other model weights, KV/Engram
tables, metadata and temporary buffers. Computation stages at most 256 rows
per expert. V4.1 single-token decode uses two disk readers and queues at most
eight missing experts within 128 MiB of weight payloads; one oversized expert
may still run. Other paths retain their one- or two-expert read-ahead windows.
The reusable CPU weight workspace holds at most four buffers of 16 MiB each.
Cache reads default to `O_DIRECT` where available; `FASTLLM_DISK_DIRECT_IO=0` selects buffered reads. Shrinking or
disabling a cache releases excess entries, and model unload releases its
entries. On glibc, retired buffers are periodically returned to the OS.
The old `FASTLLM_DISK_MOE_GPU_PREFILL` and `FASTLLM_DISK_NO_CACHE` aliases
are removed; use `FT_GPU_PREFILL` and `FASTLLM_DISK_DIRECT_IO`, respectively.

For V4.1 batches of up to eight tokens, resident NVFP4 GPU experts are submitted
before the independent CPU batch and gathered once. Results retain each route's
BF16 rounding boundary and are accumulated in expert-ID order. GPU cache
replacement waits for the resident work to finish, and rejected promotions
skip weight packing. Shared GPU experts can overlap disk MoE work on a single GPU.

CUDA residency supports compact NVFP4 with block-32 UE8M0 scales, block-128
FP8 (native or packed), FP32, FP16 and BF16. Other formats use CPU execution.
GLM GGUF also supports canonical IQ2_XXS/IQ2_S gate/up with IQ3_XXS/IQ4_XS
down projections and BF16 activations. Disk and RAM retain the original IQ
blocks; CPU execution does not copy or R4-repack them. IQ2/IQ3 use native
Q8_K dots, while IQ4_XS preserves the model's BF16 down-input arithmetic.
This path prefetches up to two missing experts. For batches whose per-route
FP32 output fits in 64 MiB, resident GPU experts are submitted together,
overlap CPU computation, and return their results in one transfer. Reduction
keeps expert-ID order and duplicate routes. Larger batches retain bounded
streaming execution. GPU replacement waits until borrowed records finish;
the frequency policy and per-layer admission limit remain unchanged.
`KimiK3RoutedExperts` does not use this cache. V4.1 retains its quantization,
route-weight placement and ordered reduction; CPU/CUDA matrix products can
have floating-point rounding differences.

Python callers can set budgets with `llm.set_moe_cpu_cache(bytes)` and
`llm.set_moe_cuda_cache(bytes)` before loading. `llm.get_disk_moe_cache_stats()`
returns cumulative `cpu_hits`, `cuda_hits`, `misses`, `disk_bytes`, `uploads`,
`cpu_evictions` and `cuda_evictions`, plus current `cpu_bytes` and `cuda_bytes`
(summed across GPUs). Hits count token/expert routes, including disk-backed
shared experts; GPU hits are excluded from CPU hits. Use snapshot differences
for request statistics. `disk_bytes` counts checkpoint payload read on misses,
`uploads` counts retained GPU promotions, and evictions include explicit
capacity changes and model unload.

## Metadata: `fastllm-cuda-expert-cache.cuh`

`ExpertCacheView` owns no memory. The caller supplies a global key-to-slot map,
slot keys, 64-bit last-use timestamps and a step counter. Initialize both maps
to -1 and the other arrays to zero. Hit/miss counter pointers are optional.

`EnsureExpertCache<MaxQueries>` accepts an expert-ID list and a `keyBase` for its
table. It returns slots in the original route order, plus the unique missing
expert IDs and destination slots. Missing IDs are relative to `keyBase` and
sorted by ID. Repeated IDs copy once; invalid IDs return slot -1. A request
protects all its resident experts before selecting victims. LRU ties select
the lowest slot number, so results are deterministic. Counters count valid
routes minus unique misses as hits, including duplicate routes served by one
fill.

Choose the block size once using `ExpertCacheThreads`. Small caches keep ages
in registers. Larger caches scan metadata in tiles; they do not require shared
memory proportional to cache capacity. The default query bound is 64 and can
be changed by template instantiation. The NVFP4 adapter currently uses 16.
Keys and slots are signed 32-bit IDs; timestamps are unsigned 64-bit values.

The caller provides valid map/output allocations and enough slots for the
request. All updates and dependent copies/compute for one cache must be ordered
on a stream or through graph dependencies. This is not a concurrently writable
multi-stream cache. Resetting the timestamp requires resetting its metadata.

## Record transport: `fastllm-cuda-record-copy.cuh`

`RecordCopyView` describes source/destination pointers, independent record
pitches, and bytes to copy per record. `CopyRecords` reads its count on the GPU
and flattens work over records and byte units. It makes no allocations or host
readbacks, so changing miss counts work inside a captured graph.

Aligned records use 16-byte loads/stores. Other layouts use byte copies, with
no padding or tail overread. The ordinary path uses 32-bit division; large
batches use 64-bit indexing. Records are opaque: FP32, FP16, BF16, packed
quantized values, scale arrays and auxiliary data have identical copy semantics.
Sources may be device memory or mapped pinned host memory. Source storage must
stay alive until GPU work completes, destination IDs must be distinct, and
source/destination ranges must not overlap.

Call `RecordCopyConfiguration` once per layout/device. The concurrency target
is capped by device resource capacity, without naming an architecture. Layout
alignment remains an adapter choice: the current adapters align complete records
to 128 bytes to avoid repeated misalignment at record boundaries.

## Routing: `fastllm-cuda-ordered-reduce.cuh`

`OrderedBlockReduce<N>` preserves the descending-stride FP32 reduction tree,
using warp shuffles for its last five levels. All physical block threads must
participate; at least N threads are required. N is a power of two of at least
32, and scratch storage has N+1 floats. The distinct result cell permits
consecutive reductions to reuse scratch safely.

`WarpArgMax<Items>` separates value reduction from index reduction for finite
keys, preferring the lowest index on ties. All 32 lanes participate and own
contiguous slices of the row. Callers retain their NaN handling and any legacy
tie-resolution rules. Input types can convert to FP32 before these helpers;
changing the source precision does not silently change the accumulation type.

The router includes a 512-expert/top-10
shape specialization. Its dispatch checks shape and dtype, not model name.
It keeps the original softmax arithmetic, normalization, and legacy tie path.
Other router shapes retain their existing implementations. Reusing these
helpers in a new specialization requires checking that model's reduction and
tie semantics; the helpers do not automatically optimize every shape.

## Adding a model or quantization format

1. Define a record layout and host packing, including weights, scales and any
   format-specific metadata. Register separate groups for incompatible layouts.
2. Allocate cache metadata/records and use `keyBase` to distinguish expert tables.
3. Run `EnsureExpertCache`, then `CopyRecords`, on the compute stream. No dtype
   branch is needed in either building block.
4. Supply gate/up/down compute for the new record layout and activation type.
   This is the format-specific part. `fastllm-moe-cache.cu` manages the shared
   cache/record lifecycle and compact NVFP4 compute; FP8 slot compute reuses
   the indexed kernels in `fastllm-moe-fp8.cu`. Keep a new format's scales and
   layout checks in its adapter rather than in LRU or record transport.
5. Validate route IDs, weights and full generated sequences against that model's
   reference, then benchmark its shapes and target GPUs.

## Standalone checks

The three standalone kernel tests and an adapter integration test are available
through the existing `UNIT_TEST` CMake option:

```sh
cmake -S . -B build -DUSE_CUDA=ON -DUNIT_TEST=ON
cmake --build build --target cuda_expert_cache_test cuda_ordered_reduce_test cuda_record_copy_test cuda_moe_cache_test
ctest --test-dir build -L cuda --output-on-failure
```

From the repository root, build each test with the architecture of the test GPU:

```sh
nvcc -O3 -std=c++17 -arch=sm_120 -Iinclude/devices/cuda test/basic/test_cuda_expert_cache.cu -o /tmp/cache_test
nvcc -O3 -std=c++17 -arch=sm_120 -Iinclude/devices/cuda test/basic/test_cuda_ordered_reduce.cu -o /tmp/reduce_test
nvcc -O3 -std=c++17 -arch=sm_120 -Iinclude/devices/cuda test/basic/test_cuda_record_copy.cu -o /tmp/copy_test
/tmp/cache_test
/tmp/reduce_test
/tmp/copy_test
```

The cache test compares requests and complete state with an independent CPU
LRU reference, including duplicates, invalid IDs, multiple table bases, graph
replays, large capacities, and crossing the 32-bit timestamp boundary. It also
accepts a 16-int-per-row route trace and a slot count. `--quick` limits generated
request counts for sanitizer runs.

The reduction test compares FP32/FP16/BF16 softmax results bitwise with the
original shared-memory tree across row widths and logical block sizes. It also
checks argmax ties and partial rows. The copy test checks mapped-host and device
sources, different pitches, alignment, tails, guards, graph replay, changing
counts, and both index widths.

The adapter integration test links the real runtime and checks FP32/FP16/BF16,
known expert outputs, cache eviction, graph replay, disabled/insufficient
budgets, scale validation, hidden-width validation and release. NVFP4 covers
aligned and odd widths; FP8 covers its supported layouts. Random FP8 tests
compare gate and output bytes against the original all-resident GPU backend
for FP16/BF16, including two layer tables, repeated expert IDs, changing inputs
and scores, eviction and both eager execution and graph replay.
Layered model checks exclude CUDA-resident tables, initialize per-device caches,
and verify release/reprepare when the first registered host table is not layer 0.
On two GPUs, expert-parallel tests compare summed rank outputs against independent
CPU/GPU expert references, including duplicate routes, zero/negative scores,
asymmetric cache budgets, reversed device order and collective fallback.
An all-CPU routing case checks fused accumulation against a scalar reference.
Verifier checks include transitions between one, four and nine rows and
compare grouped NUMA values with independent single-row expert outputs.

`test/benchmark/qwen4_tp_short_requests.py` exercises a real Qwen checkpoint
with 512/2040-token inputs, 1/2/8-token outputs and repeated cache/MTP switches.
It checks the TP/NUMA prefill handoff without per-operator synchronization,
which would mask premature cross-thread reuse of CUDA temporary buffers.

Shared-NUMA NVFP4 cases compare gate/output bytes with a full snapshot across
NUMA shard counts, verifier sizes, eviction and eager/graph replay, including a
2560-by-640 expert. They cover distinct gate/up scales, invalid shards,
registration exceptions, retries and reuse of an already-active cache. Planar
and mixed-layout shards use the same full-snapshot oracle. CUDA Linear tests
compare FP32/FP16/BF16 outputs bitwise with the inline layout, including bias,
odd widths and GEMV/GEMM batch sizes. `nvfp4_planar_test` checks equal storage,
packing and CPU outputs with and without AVX512-BF16 across row-tile boundaries.

Architecture compilation and execution are distinct checks: compiling these
tests for another SM does not establish its runtime correctness or performance.

GPU-resident GGUF experts also accept 1–32 input rows with CUDA routing tensors.
The batched gate/up, down and reduction kernels retain each row's single-token
expert order and rounding. Q8 activations and partial results use caller-owned
scratch sized for the actual row and route counts; weights remain packed.
`cuda_gguf_moe_cache_test` checks all 24 admitted GGUF formats against a CPU
oracle and compares batched results bitwise with individual rows, including
four-row MTP verification, invalid/duplicate routes and graph replay.
With `USE_NUMAS`, its `--hybrid` mode checks CPU subsets against serial
GEMM/quantization and bounds the combined CPU/GPU result using independent
per-expert outputs. It covers mixed formats and widths across layers,
single-device and two-device execution, and duplicate/zero/negative routes.
For larger resident prefill batches, NVIDIA SM75+ can group routes on the GPU
and reuse packed expert tiles with MMQ. Gate/up supports Q2_0 and IQ2_XXS/XS/S;
IQ1_M gate/up retains its Q8 dot fallback. Down supports Q2_0 and IQ2_XXS/XS/S.
The path accepts up to 4096 rows subject to route and workspace limits, without
a persistent dequantized weight copy or host route synchronization. Q2_0 K
tails, including 320-column TP shards, are masked before loading weights.
Other types/shapes retain the ordinary prefill fallback. Large-batch MMQ is
checked against independent CPU gate/up and down oracles: changed accumulation
order can cross a later Q8 rounding boundary. It does not promise bitwise equality
to single-row inference; the 1–32-row decode/verifier path remains unchanged.
Set `--chunked_prefill_size` above 32 to use this path (for example, 512 or
1024, subject to available activation/workspace memory).
`cuda_gguf_moe_grouped` covers uneven groups, all routes selecting one expert,
empty groups, graph replay, three activation types and actual TP shards.
`cuda_data_device_test` requires two GPUs and checks source-device CPU reads
and CUDA clones while the caller is on the other GPU. These checks protect
CPU embedding handoffs and MTP checkpoints without per-operator synchronization.

Qwen4-Exp GGUF checkpoints support resident tensor parallel experts with
`--tp 2 --moe_device cuda:0,1 --moe_cuda_cache 0`. Gate/up rows and down columns
use the same intermediate slice, aligned to the down weight's GGUF block and
the 32-value Q8 activation block. A 640-wide Q2_0 expert therefore splits into
320 columns on each rank without dequantizing or requantizing its weights.
Packed GGUF output heads split by vocabulary row. CPU token embeddings share
one immutable host table across ranks; `--low_gpu_mem` keeps that policy explicit
when CUDA Graph is enabled. CUDA embedding operators honor `lockInCPU`.

Resident eager TP2 reductions use peer access for aligned FP32/FP16/BF16
tensors up to 288 KiB. Each thread reads its packed inputs before either rank
overwrites them, so the reduction supports in-place storage and changing
temporary addresses. Captured execution, larger messages and unsupported peer
topologies retain NCCL. Topology, tensor size, alignment and capture state
determine eligibility automatically.
`cuda_gguf_tp_shards_test` checks packed byte conservation and independent CPU
expert results on both ranks, including 320/320 splitting and four-row MTP.
`cuda_tp2_reduce_test` queues changing shapes, alternating buffers, in-place
and separate outputs, and delayed rank streams without intermediate host
synchronization. It also checks collective rejection and recovery for an
unaligned destination, and dtype-rounded residual additions with changing
inputs and delayed peer streams.

The V4.1 `cuda_dsv41_moe_cache_test --dual` integration test uses an independent
FP8/BF16 dense oracle for CPU subsets, hybrid/pure decode and 2–8-row verify.
It covers all 4096 FP4 code/scale combinations, repeated and zero-weight routes,
cold admission, eviction, CPU/GPU prefill, unregistered NVFP4 cold startup,
asymmetric budgets and GPU 0/1/0 output
movement. `--dual --verify-only` limits it to multirow verification.

## GGUF NUMA GPU-assisted prefill

NUMA prefill retains its CPU/GPU expert scheduler. For supported 33–4096-row
batches, each GPU worker uploads its selected expert subset into reusable
scratch and calls the grouped packed-weight MMQ kernels. CPU-selected experts
continue to run on NUMA. The GPU restores IQ2_XXS/XS/S R4 and cross-SwiGLU row
layouts without modifying host weights or retaining a second model snapshot.
Q2_0 gate/up weights are also supported; down weights use Q2_0 or
IQ2_XXS/XS/S. IQ1_M retains per-expert GEMM for host prefill: the current
grouped IQ1_M gate kernel uses per-route DP4A and loses batch weight reuse.
FP32, FP16 and BF16 activations are accepted on NVIDIA SM75 or newer.

DeepSeek-V4.1's BF16 Q2_K gate/up + Q4_K down pair also uses this streamed
path, including Q2_K_R4/Q4_K_R4 NUMA weights. Admission checks the model's
block-32 activation contract, formats, shapes and workspace capacity instead
of excluding every V4 model. The adapter retains BF16 projection rounding,
SwiGLU clipping, route scores before the down-input BF16/FP8 boundary, and
ascending expert-ID reduction. Both projections use signed-max GGML Q8_K
quants with FP32 scales. The shared MMQ tile loop uses V4.1 loaders and MMA
fragments that form integer activation sums instead of rounding scale/sum
metadata to half precision. Matrix accumulation can still change rounding
relative to the NUMA scalar oracle; bitwise CPU/GPU equality is not promised.
V4 block-128, shared experts inside this operator,
and other format/activation combinations retain the per-expert fallback.
The V4.1 GGUF fallback also applies the required down-input FP8 boundary.

V4.1 CPU input preparation fuses source conversion, FP8 quantization and
GGML encoding across the existing worker pool with reusable per-row scratch.
It skips the redundant pre-FP8 Q8 encoding when no unquantized shared expert
needs it. This preserves CPU/GPU expert scheduling and adds no environment
switch. `cuda_v41_q2_moe_prefill` checks both GPUs at 33, 65, 128, 408, 1023,
1024, 1041 and 4096 rows against NUMA and independent scalar references, including zeros,
negative/zero route scores, clipping, selected subsets, canonical/R4 weights,
NUMA shards, immutable inputs and rejection of unsupported math/shapes.
The `_small` and `_model_shape` tests cover cancellation-sensitive 256-wide
inputs and the model's 5120 × 2304 expert dimensions, respectively.

Scratch includes the current worker's restored packed weights, projection upload
staging, route metadata, grouped MMQ products and activations. V4.1 uses two staging
buffers so a high-priority stream can upload/restore down weights during gate/up
MMQ; an event gates the down projection. Other formats use one staging buffer.
V4.1 batches of at least 1024 rows use 64-row compute tiles while retaining
16-row route padding, with guarded reads for each expert's last tile. Scratch is reused across
layers on the same device, separately from the expert-cache payload budget.
Admission checks the entire selected subset, workspace size and available
memory before uploading weights. Shared experts inside the operator,
incompatible shapes/types, small decode batches, CUDA Graph capture and
insufficient memory retain the existing per-expert implementation.
Q8 activation quantization can change logits and generated tokens.

The grouped path quantizes each gate/up input token once and gathers packed Q8
blocks into the expert route layout. Its compact input reuses the product
buffer before matrix multiplication, so the persistent workspace size does
not grow. Gather and down-input quantization skip rows beyond the device-side
active route count while explicitly zeroing padding within active expert
tiles. Q2_0 tiles use aligned 16-bit loads and register byte permutations;
their MMA accumulation order and Q8 scale rounding remain unchanged.

The ordinary Q2_0 Dense path also accepts 1–8-row Q8/DP4A MMVQ for
FP32/FP16/BF16. It requires 64-column alignment and at most 32 KiB of staged
Q8 activation per block; other shapes retain the existing fallback. This
Dense change adds activation quantization and can change generated tokens.
Final deployment measurements are listed in the [Qwen4 benchmark](benchmarks/qwen4_exp_en.md#gpu-resident-gguf-experts).

`cuda_gguf_moe_host` checks the CPU R4 repacker against an independent decoded
weight oracle, cross/non-cross gate/up layouts, selected subset changes,
NUMA row shards, both GPUs, three activation types and immutable host storage.

### GLM compact NVFP4 disk hierarchy

With `--moe_device disk`, a GLM compact E4M3 NVFP4 checkpoint retains only
file offsets, raw scale locations and tensor multipliers during loading.
Routed experts are read with the disk backend's bounded direct-I/O buffers;
only requested experts are packed into the native compact NUMA/CUDA layout.
No full-model NUMA registration or expert warmup snapshot is created.
This path requires the NUMA backend and BF16 activations. Existing disk
fallbacks remain available for other weight formats.

`--moe_cpu_cache` bounds resident compact RAM payload **including the small
staging cache**, while `--moe_cuda_cache` remains a per-GPU payload budget.
Active CPU/GPU work, two read-ahead experts, non-MoE weights, activations and
runtime allocations are additional. These are not process RSS limits; leave
headroom and measure loading, prefill and decode high-water marks together.
For a 64 GiB process target, start with a 40 GiB RAM payload budget and validate
the complete workload before increasing it. CUDA allocations are independently
releasable on eviction. The input checkpoint is never modified.

Both tiers use `MoeFrequencyPolicy` and the existing `--moe_cache_*` controls
(half-life, update interval, replacement count/bytes, heat threshold, margin,
factor, minimum residence, prefill prior and byte ranking). Both tiers observe
all routes, preserving an expert's heat while it moves between RAM and CUDA.
Persistent host and CUDA payloads are exclusive: promotion removes the host
copy, and RAM candidates exclude CUDA residents. Decode admissions occur after
all registered MoE layers have been observed. CUDA plans first; evicted CUDA
records are copied back only if selected by the RAM frequency policy. Prefill
uses the same ownership rule with histogram admission and bounded lookahead.
Payloads are leased until arithmetic and transfers finish; changing a budget
invalidates the old tier state before it is rebuilt.

The bounded host staging area retains hot already-read candidates across
layers. When the RAM budget holds at least one expert, decode updates admit
only available RAM/staging records or selected CUDA victims, without initiating
disk reads merely to populate a cache. Candidate availability does not discard
frequency history, and updating it does not rebuild the policy's heaps. A RAM
budget smaller than one expert retains bounded disk-read fallback for CUDA
admission so that disabling RAM caching still allows the CUDA hot set to adapt.

A CUDA hit needs no disk or RAM payload. On a CUDA miss, RAM hits and demand
reads supply compact weights to NUMA or temporary CUDA execution selected by
the measured overlap scheduler. Resident CUDA work is submitted before disk
reads. The disk path currently uses the calling layer's GPU; it does not use
the separate multi-GPU verifier dispatcher. `get_disk_moe_cache_stats()` reports
RAM/CUDA hits, two-tier misses, source bytes read, uploads, evictions and retained
payload bytes. `disk_bytes` includes admission reads as well as demand reads;
use process I/O counters to measure physical reads and alignment overhead.
For the compact hierarchy, `cpu_cuda_overlap_bytes` measures retained duplicate
host/CUDA payloads; `cuda_demotions` and `cuda_demotion_bytes` count selected
CUDA-to-RAM transfers. Active call leases are not persistent cache ownership.
The original nine-value C statistics ABI is retained; the capacity-bounded v2
API adds the hierarchy counters and is used by the Python wrapper when present.

### GLM-5.3 GGUF hybrid tensor parallelism

GLM-5.3 GGUF supports `--device cuda:0 --tp 0,1 --moe_device disk`.
The TP degree must divide the KDA and MLA head counts, and shard widths must
respect the quantization-block alignment. Each rank owns its
attention heads, recurrent state and KV pages. Attention output, dense-FFN
and shared-expert down projections reduce their rank-local partial sums.
Routing executes on the owner rank. Routed host
experts are evaluated once, using a single process-wide RAM cache; TP does not
load a full host expert copy per rank. `--cuda_shared_expert true` is required. MTP placement and unsupported draft formats follow the [GLM deployment guide](glm5.md); prefix/history reuse is bypassed for hybrid TP. As with ordinary BF16 tensor
parallelism, partitioning changes floating-point accumulation and can change
generated tokens; it does not promise bitwise agreement with serial execution.

With `--moe_device numa`, single-token TP decode overlaps the owner rank's
shared expert with CPU routed experts and streamed GPU misses. Other ranks
submit their own shared projections and meet the owner at the FFN reduction.
CUDA submission threads use available cores outside the pinned NUMA expert
workers when the process CPU affinity allows it. Disk experts and multi-row
GPU assistance retain their pre-MoE coordination because they can use peer
GPUs; the cache policy and dynamic miss-allocation policy are unchanged.

For single-row TP reductions, successfully placed submission threads spin
for at most 1 ms before sleeping at the NCCL host rendezvous. This avoids
scheduler wakeups after short CPU MoE work without taking cores from the
expert workers. Both pre/post submission boundaries, error propagation and
timeouts remain active. Multi-row work and ranks without spare cores use
the default short spin followed by a condition-variable wait.

For the native GLM GGUF disk adapter, the TP owner searches resident experts
across all participating GPUs and launches their work before CPU misses.
Promotions select available capacity or the coldest eligible victim across
those GPUs, keeping the existing frequency admission policy. `--moe_cuda_cache`
still specifies the budget **per GPU**, while `--moe_cpu_cache` specifies one
shared RAM budget. For example, `--moe_cuda_cache 8g --moe_cpu_cache 48g` uses
16 GiB of CUDA expert payload across two ranks and 48 GiB of host payload.
Attention weights, KV state and workspaces are outside those budgets.
GGUF expert records retain their original IQ layout on disk, in RAM and in
the GPU cache; this path does not introduce R4 repacking.

The ordinary GGUF disk cache maintains frequency/recency min-heaps for RAM
and each GPU. Touches update resident keys incrementally; aging rebuilds a
heap once per epoch. This removes a full-model victim scan from repeated
promotions without changing admission or eviction order. GPU buffers are
released only after all resident expert work has been collected.
An admission can reuse an evicted GGUF record of exactly the same byte size
on the same device, avoiding a synchronous CUDA free/allocation pair. The
record is handed directly to its replacement; trimming and unloading still
release storage, and there is no additional GPU buffer pool outside the budget.

Native GLM GGUF CUDA expert projection accepts batches of input rows without
changing Q8_K quantization, BF16 rounding or expert-ID accumulation order.
Disk prefill submits all rows assigned to one temporary expert together;
resident-cache calls use bounded 32-row tiles (a tiling choice, not a model
batch-size limit). Both serial and TP execution use this path. The disk-cache
test compares batched per-route output bitwise against individual-row calls
for all four supported IQ gate/down pairs, on both GPUs, including absent
experts and zero/negative routing scores.

For GLM with `--moe_device numa`, `--moe_cuda_cache 0` disables resident
expert payloads while retaining the measured decode split between NUMA and
temporary GPU uploads. The same scheduler runs for serial layer placement
and on the host-expert owner of TP. CPU compute, PCIe transfer and staged GPU
compute timings determine the split; it is not a fixed number of experts.
Temporary upload/workspace buffers still use VRAM, but have zero persistent
expert slots and perform no cache admission. Supported GGUF layouts borrow
the registered NUMA weight shards; any required GPU layout restoration uses
bounded staging storage rather than a full host snapshot.

GLM GGUF NUMA hybrid inference with multiple TP devices distributes complete
cached experts across those devices by default for IQ2_XXS/IQ2_S gate/up and IQ3_XXS/IQ4_XS
down. Expert `e` belongs to `devices[e % devices.size()]`, using the configured
TP device order. Each GPU has its own frequency policy and cache budget, and
admits only its assigned experts. Replacement-count and upload-byte limits
apply per GPU. Different per-device capacities are allowed. No additional mode
flag or environment variable is needed when `--moe_cuda_cache` is enabled.

Both projections keep the native NUMA/R4 layout. A cache hit runs entirely on
its owner GPU, without exchanging intermediate gate/up activations between
cards. Each route's FP32 output is gathered for the existing ordered BF16
reduction; GPU-local partial reductions are not substituted. Cache misses
retain the CPU/multi-GPU dynamic dispatcher and may execute on any participating
GPU regardless of permanent cache ownership.

Decode and small-batch verify (up to 9 rows) observe routes on their owner and
update caches after the step. Large prefill uses the same ownership when
assigning GPU experts, fills native records during upload, and reuses them in
later chunks and decode. The GLM TP device list is also passed to the NUMA
prefill workers, so every owner GPU participates. Prefill cache publication
waits for the payload to be ready. Ordinary GGUF profiling calls on these same
weights fall back from the canonical-record streaming adapter; they must not
read native records as canonical data or overwrite native slots with restored
payloads. Zero-cache execution keeps dynamic miss offload without persistent slots.

Logical route counters count each expert invocation once.
`fastllm_moe_cuda_cache_ep_stats(device, values)` reports device count, local
slots, occupied local slots, local payload bytes, locally computed cache-hit
routes and cumulative whole-expert upload bytes. Hit routes can be summed across
GPUs; the per-device totals need not match.

### Qwen4 hybrid MTP graph capture

By default, single-GPU Qwen3.8-Flash-Next hybrid MTP owns graph segments
per request and verifier length. Workspace reservation is always enabled: the
eager warmup records pooled allocation sizes, then capture reserves sufficient
blocks outside capture before returning them to the graph pool. This avoids
relying on warmup scratch that another graph has since pinned. Capacity failure
falls back to eager execution. Reservation can increase temporary VRAM use; it
does not change the configured expert-cache budget.

The default limit remains one new segment per stage invocation. Short verifier
lengths may not finish capturing during a 1024-token response. Set the generic
capture budget to front-load more shapes during warmup:

```bash
export FASTLLM_CUDA_GRAPH_CAPTURE_BUDGET=8
```

The capture budget is clamped to 1–128. TP honors it for the first compatible
graph-reuse request; ordinary short-context or internal warmup requests retain
the one-segment limit. A reusable TP request defaults to eight if the variable
is unset. TP records allocation demand per rank and reserves each device's
workspace before all ranks enter capture. Larger values front-load capture work
into warmup and are not necessarily faster. Expert routing, weights, MTP
acceptance rules and the per-layer CPU/GPU synchronization are unchanged.

Validation should include complete generation and repeated timings without a
GPU profiler. CUDA Graph node tracing materially changes launch overhead and
should be used to inspect submission patterns, not to report production TPS.

### Reusing Qwen4 graphs between serving requests

For a single CUDA device, CUDA layer PP, or CUDA TP with MTP, graph-slot
reuse is enabled automatically for compatible requests. Use `--max_batch 1`,
fixed startup KV reservation, greedy text generation, and disabled
history/prefix caching. Set `FASTLLM_CUDA_GRAPH_CAPTURE_BUDGET` when the warmup
should capture more verifier lengths up front.

The first compatible request is the capture warmup. Run a representative full
request before timing or accepting production traffic so ordinary decode and
all required MTP verifier lengths have time to warm and capture. For the
16K-input/1K-output benchmark, use that complete shape as the warmup. This does
not capture every possible shape during model construction.

One model-owned serving slot (one cache per rank for TP) keeps the target/draft graphs, KV page tables,
linear-attention state allocations, QSA buffers and MTP checkpoint storage at
stable addresses. Finishing a successful request retains that slot and releases its logical KV
page references before the scheduler admits another request. Reusing it
clears token history, logical KV/QSA lengths, recurrent state and pending draft
outputs without moving captured storage. The existing pointer, dtype, shape
and capacity checks still guard replay.

After the first compatible request, target and draft capture are frozen for
that slot. Captured variants replay; unseen or invalidated variants run eager
without capturing during later requests. A too-short warmup can therefore
leave variants eager until the slot is retired and warmed again.

This implementation only reuses prompts at least as long as the QSA
indexer budget. Multimodal input, non-greedy generation, prefix/history
restore and short-context requests keep their existing paths. Target-only TP
keeps its existing cache-reuse behavior. An incompatible
request retires the idle slot before using the bounded KV pool. Cancellation,
request errors or forward exceptions discard the slot. The next compatible
request then starts a new warmup. At most one completed slot is retained;
its graph/workspace memory stays allocated between requests and must fit
alongside the configured expert cache and KV reservation.

`qwen4_gguf_test --graph-slot-reset` validates history reset, freeze
propagation and stable QSA/MTP checkpoint storage on the same or different CUDA
devices. PP synchronizes every participating device before resetting shared
request state; TP resets each rank’s target and draft state and returns its
logical KV pages before the next admission.

For PP with MTP, explicitly select CPU embedding before creating the model
(`ftllm.llm.set_cuda_embedding(False)`). Otherwise Graph mode defaults to CUDA
embedding, and the target’s first stage and the draft’s last stage can move
the same full embedding weight between GPUs. This is separate from expert
cache residency and from graph-slot reuse.

### Qwen4 TP MTP draft placement and sampling

TP MTP expert shards are kept resident on each rank’s GPU, outside the
target’s bounded expert cache. Target experts retain their CPU/NUMA policy.
The draft’s GPU weights and workspaces still count against the total device
memory budget.

For a canonical Q8_0 or Q6_K head, `FASTLLM_MTP_DRAFT_VOCAB` selects draft-only
vocabulary rows before the target head is split. The selected rows are then
partitioned across TP ranks with 256-row-aligned boundaries. Token mapping and
TopK tie ordering use the original subset-row positions. A subset too small
to give every rank an aligned shard retains the full-vocabulary TP path.
The target head and greedy verification always keep the full vocabulary;
sampled or device-chained proposals retain their existing full-head paths.

The draft Graph includes local TopK and confidence reduction. Each rank reads
back one compact tuple containing its row ID, local top probability and maximum
logit. The host merges candidates and reconstructs the global subset softmax
denominator without gathering full logits. Reuse and freeze rules above also
apply to these draft graphs.

---

### Cuda Native Prefill (`docs/cuda-native-prefill.md`)

# 可选 CUDA 低比特 prefill 路径

原有 decode 权重格式与 W8A16/W4A16 路径保留。本功能增加 W8A8/W4A4 prefill；激活量化会引入数值误差，仅在 SM120 的已验证单卡形状上默认开启。FastLLM 中间张量为 FP16。

## 默认策略与显式开关

以下三个开关未设置时使用自动白名单，显式 `0` 禁用，显式 `1`（或 `true`）允许其他受支持 SM/形状尝试，但仍须通过所有 CanRun 检查。其他值按禁用处理。

| 开关 | 自动开启范围（仅 SM120，即 compute capability 12.0） |
|---|---|
| `FASTLLM_CUDA_NATIVE_FP8_PREFILL` | M=32..4096；(N,K)=(16384,5120)、(14336,5120)、(34816,5120)、(5120,6144)、(5120,17408) |
| `FASTLLM_CUDA_NATIVE_NVFP4_PREFILL` | M=9..4096；(N,K)=(34816,5120)、(5120,17408)，后续布局、对齐及分块限制仍适用 |
| `FASTLLM_CUDA_GDN_PREPARE_WY` | [batch,heads,chunks,chunkSize,headDim]=[1,48,1..64,64,128] |

这些是 Qwen3.8-27B 单卡已验证形状；不读取模型名字。其他模型只有匹配形状且通过数据类型、布局等检查才会进入。TP 切分形状、其他 SM 默认关闭，保留手动开启。`FASTLLM_CUDA_NVFP4_NATIVE_LAYOUT` 独立保留 opt-in，不随这三个开关自动打开。

## 手动启用

```bash
export FASTLLM_CUDA_NATIVE_FP8_PREFILL=1
export FASTLLM_CUDA_NATIVE_NVFP4_PREFILL=1
export FASTLLM_CUDA_GDN_PREPARE_WY=1
ftllm server /path/to/Qwen3.8-27B-NVFP4 --max_batch 1 --mtp 0 \
  --kv_cache_dtype fp8_e4m3 --tokens 16384 --prefix_cache false \
  --chunked_prefill_size 2048
```

不保留第二份权重布局：每次调用在复用的固定 scratch 内重排当前层权重，调用结束后不缓存。额外 5.6 GB 布局缓存及其环境开关已移除。

## 条件及回退

| 路径 | 条件 | 回退 |
|---|---|---|
| FP8 cuBLASLt | CUDA >=12.8，SM89+，M=32..4096，N/K 为16倍数；row FP8 权重、FP16输入输出 | 原 W8A16 路径 |
| NVFP4 cuBLASLt | CUDA >=12.8，SM10x/12x，M=128..4096且为128倍数，N为128倍数、K为64倍数 | 原 Marlin 路径 |
| NVFP4 TMA融合 | SM120；M=256..4096，支持尾块；gate/up+SwiGLU N=34816,K=5120；down+residual N=5120,K=17408；无 bias | 上述通用 cuBLASLt，再回退原权重布局对应的 Linear 路径 |
| GDN prepare | SM80+，FP16 dense，chunk64、K/V head128；非 ragged、未启用已有 Triton GDN 路径 | 原 KKT、mask、逆三角、WY/WU 完整路径 |
| gated RMSNorm | 原 FP16 head128 路径，每 CTA 4 行；行内归约顺序不变 | 小行数使用每 CTA 1 行 |

所有路径还检查指针、维度、设备、布局、对齐、别名和可用编译镜像。cuBLASLt 路径需要可用算法和足够的固定 scratch。CUDA graph capture 中的新低比特/GDN prepare 路径返回 false，保留原计算。普通 decode 不进入新低比特 GEMM。

Block 的 CanRun 检查结构条件，Run 内仍允许在写出结果前因库算法或描述符不可用而回退；kernel 启动错误抛出，不能在写过 residual 后静默重复计算。

`FASTLLM_CUDA_NATIVE_NVFP4_TMA=0` 禁用 TMA 特化；只有父开关允许（自动白名单或显式 `1`）时 TMA 才可能运行。
`FASTLLM_CUDA_GDN_NORM_MULTIROW=0` 恢复原单行 gated norm。

每 GPU 的 cuBLASLt 状态使用互斥锁和 CUDA event 协调跨 stream scratch；预热时预留 32 MiB workspace 和 384 MiB scratch，进入服务后不增长。形状算法在首次使用时选择，测速必须预热。

## 实现与来源

FP8 逐 token E4M3 激活量化；NVFP4 使用 block16 E2M1 数据、E4M3 block scale 和逐 token global scale。通用 GEMM 使用 FP32 输出中间缓冲，避免在恢复缩放之前 FP16 溢出，随后融合缩放、SwiGLU 或 residual。

TMA 特化使用自包含的手写 CUDA 内核，将缩放及 SwiGLU/residual 融合到矩阵乘尾部，支持 FP16 输出；不依赖外部推理引擎的算子源码。不同 FP32 累加顺序可能在 FP16 舍入边界产生少量差异。

GDN prepare 融合 KKT、cumsum/decay/mask、分块三角逆和 WY/WU。三角逆使用 TF32×3 乘积以控制误差；WMMA accumulator 经共享内存转换，不依赖不同 fragment 类型的内部元素排列。

## 验证

```bash
CUDA_HOME=/path/to/cuda-13.1 CUDA_VISIBLE_DEVICES=0 \
  python3 test/cuda/native_prefill/run.py --arch 120f --tma
```

测试包含 FP8/NVFP4 量化参考、SwiGLU/residual、TMA/cuBLASLt 对照、decode/graph/不支持尺寸回退、GDN prefix/decay 和 compact scratch 别名。GPU 测试需串行在空闲设备运行。

使用 CUDA 13.1.80，在 SM120 RTX 5090 上完成运行验证。通用源文件另通过 SM75/80/86/89/90/100/120/121 编译检查；不代表已在这些硬件上实测。TMA 单独使用 compute_120f/sm_120f 编译。

整模型 2K/8K、每组33个 teacher-forced token 的 logits 抽样用于发现明显数值问题，不替代完整困惑度/下游质量评估。因此自动开启仅限上述 SM120 形状，其他 SM 仍保持 opt-in。

## 仅关闭 Marlin 的回退对照

进程启动前设置 `FASTLLM_CUDA_NVFP4_MARLIN=0` 禁止 NVFP4 权重转换成 Marlin 布局；默认值为1。已经转换的权重仍必须由理解其布局的路径处理，因此需要重启服务切换，不能在线改变布局。

这是原始格式 GEMV / 解量化后 FP16 GEMM 的回退对照，当前不等价于原生 NVFP4 TMA prefill。设置 `FASTLLM_CUDA_FP8_MARLIN=0` 可同时禁用 FP8 Marlin（SM120上默认已关闭）。不引入任何权重副本缓存。

## SM120 共用 NVFP4 权重布局

在上述 prefill 开关外，设置以下选项并重启服务：

```bash
export FASTLLM_CUDA_NVFP4_NATIVE_LAYOUT=1
export FASTLLM_CUDA_NVFP4_MARLIN=0
export FASTLLM_CUDA_FP8_MARLIN=0
```

这条路径使 NVFP4 prefill 和 decode 共用一份权重。预热时在原权重 allocation 内转换为 E2M1 codes、cuBLASLt tiled E4M3 scales 和 global scale。prefill 直接读取，不再执行 Marlin→NVFP4 重排；decode 使用读取同一布局的 W4A16 GEMV，并融合 SwiGLU/residual。

不保存第二份常驻权重。转换期间仅有当前层临时缓冲（最大约 100 MB），校验和安装后立即释放。低比特 prefill 仍使用前文的 416 MiB 固定共享工作区；大 M 回退最多请求 32 MiB 解量化 scratch。原权重 allocation 不缩小。

初始转换仅启用于 SM12x、可用 SM120+ 编译镜像、单设备完整权重，且限于 N=34816/K=5120 和 N=5120/K=17408 两种尺寸。需要 FP16 输入输出、NVFP4 block16、预热同步阶段；不能在 graph capture 中转换。E4M3 scales 必须通过还原检查，失败则不改写原权重。开关默认关闭。其他 SM、TP shard 尺寸或不满足条件的权重保留原有调度；当前新布局仅在 SM120 单卡实测。

转换之后：

- M=1..8：原生 W4A16 GEMV，FP32 累加、FP16 输出，支持 CUDA graph；M=1 对上述 K 特化。
- M=9..4096：SM120 上符合手写融合条件的 M=256..4096 直接使用 TMA（支持尾块）；其他情况将 M 补齐到 128 倍数后使用 cuBLASLt。输入只读真实行、输出只写真实行。
- 库算法、scratch、形状或 capture 条件不适合低比特 prefill 时，使用理解新布局的 GEMV 或分块解量化 GEMM。融合路径返回 false 后，Linear 仍能正确读取新布局，再执行原后处理。

`Data` 只增加布局标记，不增加权重缓存指针。复制保留布局信息；迁回 CPU、reshape/resize 和 expansion 前恢复原始格式；释放清除标记。不能直接对 tiled 权重调用 `FakeFrom` 创建原始字节切片，必须先恢复源权重，接口会显式拒绝错误用法。

回归覆盖 pack/restore、M=1/2/3/8 GEMV、graph replay、两个实际 K 特化、M=9/17/63/127/129/255 的补齐和输出边界，以及原生布局 prefill 与 cuBLASLt 对照。`native-layout-lifecycle.cpp` 另链接构建好的 `libfastllm_tools.so`，检查复制、CPU 迁移和 reshape。测试在空闲 GPU 上串行执行。


## SM120 手写融合 prefill

开启原 NVFP4 native prefill 后，符合条件时默认使用自包含的手写 kernel。`FASTLLM_CUDA_NATIVE_NVFP4_FRESH=0` 或 `FASTLLM_CUDA_NATIVE_NVFP4_TMA=0` 可禁用此路径并回退 cuBLASLt。

支持 SM120、M=256..4096（包括非整倍数尾块）、gate/up N34816 K5120 的 SwiGLU 融合，以及 down N5120 K17408 的 residual 融合。保留无 bias、指针对齐、非 CUDA Graph capture 约束。普通 Linear mode0、其他尺寸、SM 或资源不满足时回退 cuBLASLt/原 Linear 路径；M<256 沿用旧通用路径。

复用原有权重与固定 scratch，没有新增常驻权重副本。activation scale 按 256 行 tile 预留容量，量化只写实际行；尾块 kernel 不读取无效行。

`test/cuda/native_prefill/run.py --arch 120f --tma` 使用 cuBLASLt 作为独立数值参考，并包含 `native-fresh-test`，覆盖量化、native weight layout、原地 residual、379/487/775 等尾块及小 M/非融合模式回退。测试不要求逐位相等。

真实模型首层 down 回放中，379/775 行分别有5/2个 FP16 输出与 cuBLASLt 不同，最大绝对差3.05e-5/6.10e-5，相对RMS误差约2e-7；双精度参考表明差异来自FP32累加顺序及FP16舍入。多层低比特量化可能放大差异，因此不承诺任意长度的最终logits或生成文本逐位一致。

---

### Cuda Tp Fusions (`docs/cuda-tp-fusions.md`)

# CUDA decode fusions and tensor parallel shards

These kernels operate on local tensor dimensions, not on the TP rank count. They use SIMT CUDA operations and retain complete unfused Block paths. Changing the rank count does not require another weight format or another model implementation.

## Supported local tensors

| Path | Activations / batch | Local shape and weight layout |
| --- | --- | --- |
| GDN input projection + convolution + SiLU + cache update | FP16 or BF16; batch 1–8 | Row-scaled FP8; K is a multiple of 256, 256–32768; N <= 65536; convolution width 4; positive QKV and Z widths |
| Row-scaled FP8 GEMV | FP16 or BF16; batch 1 | 512 <= K <= 32768, N >= 4096; one scale per output row, including blockM >= local K |
| FP8 projection + residual | FP16 or BF16; batch 1 | Same row-scale semantics; 512 <= N <= 65536; float bias or no bias |
| NVFP4 gate/up + SwiGLU, or down + residual | FP16; batch 1 (down shape below also supports 2–8) | Existing NVFP4 block-16 Marlin layout; N multiple of 128, 128–65536; K multiple of 128, 256–32768; no bias |

The FP8 row path selects vector loads for K multiples of 512 or 256. Other widths use masked byte loads, aligned activation pairs, and an explicit odd-element tail. Legacy FP16/BF16 fallback GEMV paths also handle uneven row widths safely.

GDN dimensions are runtime parameters, including the separation between QKV and Z. Cache slot IDs must be valid and distinct, as required by the existing Block. The original full-size specialization is retained. NVFP4 has a K=5120 specialization with runtime N as well as a fully dynamic specialization; the existing full-size kernels are retained.

## Admission and fallback

`CanRun` checks the operation's dtype, layout, alignment, batch, scale metadata, buffer separation where required, and available CUDA image. GDN, FP8 residual and NVFP4 retain an SM75 minimum; the row GEMV also checks its loadable CUDA image. Missing capability returns to the existing complete operation path. `CanRun` does not update residuals or convolution state. A cold FP8 scale cache is not admitted during graph capture.

The uneven gate/up shapes N=11520 or 11776, K=5120 deliberately retain Marlin + SwiGLU: end-to-end measurements on RTX 5090 found this faster than the fused SIMT kernel. This is a conservative performance exception, not a correctness restriction. The other supported fusions remain active for these TP configurations.

Residual fusion preserves the existing TP reduction protocol: only the designated rank adds the replicated residual before all-reduce. Adding it on every rank would multiply it by the TP size.

`FASTLLM_CUDA_TP_FUSIONS=0` disables the shape extensions while retaining the base fusions. Existing per-operation flags still apply:

- `FASTLLM_CUDA_GDN_INPUT_CONV`
- `FASTLLM_CUDA_FP8_ROW_GEMV`
- `FASTLLM_CUDA_FP8_LINEAR_ADD`
- `FASTLLM_CUDA_NVFP4_SWIGLU`
- `FASTLLM_CUDA_NVFP4_ADD`

## Validation scope

The CUDA tests cover the TP=2/3/4 shard dimensions, other local dimensions, FP16/BF16 where supported, CPU reference values, forced and shape-based fallbacks, changing inputs across CUDA Graph replays, and GDN cache slots. CUDA 13.1 compilation was checked for SM75, 80, 86, 89, 90, 100, 120 and 121. Runtime correctness, memory checking and end-to-end performance were measured on SM120 only; compilation success is not a performance claim for other GPUs.

## Local down projection tuning and small batches

For FP16 NVFP4 down projections with local N=5120, K=8704, batch-one
selects a fixed-dimension instance of the existing SIMT GEMV core. The residual
Block uses its additive epilogue; ordinary Linear uses an overwrite epilogue,
so non-root TP ranks also use GEMV without adding a replicated residual.
The warp layout and FP32 reduction order match the generic fused down kernel.
The dispatch requires SM75 or newer
and checks that a compatible CUDA kernel image is available. FP4 decoding
uses the existing native conversion or its half-based fallback on older SMs.
Gate/up retains its previous dispatch.

For this down shape, batches 2–8 use Marlin Tensor Cores with a fused residual
epilogue. Intermediate split-K results stay in the existing FP32 scratch;
only the final reduction owner adds the residual while writing output. The
Linear result is rounded to half before addition, matching Linear + AddTo.
Ordinary Linear on non-root TP ranks keeps the original Marlin path for these
batches.

The small-batch CanRun requires the complete reduction scratch to fit in the
already-prepared weight allocation and checks the selected residual kernel's
image and shared-memory capability. Kernel attributes are prepared and cached
outside CUDA Graph capture; a cold attribute cache during capture falls back.
No scratch allocation or weight-layout change is performed by this fusion.
Batch >8, unsupported dimensions, dtype, bias, layout, overlapping buffers,
missing scratch, or unsupported kernels retain the complete existing Block path.

`FASTLLM_CUDA_NVFP4_SHAPE_TUNING=0` disables this shape's tuning, including
the small-batch residual fusion, while preserving preceding TP fusions.
The existing NVFP4 and TP-fusion flags still apply. Set flags before server
startup / CUDA Graph capture.

Runtime validation is on RTX5090 only. Compilation coverage on other SMs and
the absence of SM120-only instructions do not establish performance on GPUs
that have not been measured.

---

### Custom (`docs/custom.md`)

### 自定义模型

对于Fastllm框架中没有支持的模型，可以通过自定义模型结构来支持

Pyhton 自定义模型只需要一个python文件来描述模型结构，可参考 [QWEN](../example/python/qwen2.py) 中的实现

### Python自定义模型的使用

使用ftllm.chat, ftllm.webui, ftllm.server时，可以加入参数--custom来指定自定义模型文件

假设我们的模型位于 `~/Qwen2-7B-Instruct/` 目录，自定义模型位于 `~/qwen2.py`

那么可以使用命令

``` sh
python3 -m ftllm.chat -t 16 -p ~/Qwen2-7B-Instruct/ --custom ~/qwen2.py 
```

来通过自定义模型文件加在Qwen2模型，server和webui用法类似

### Python自定义模型的写法

自定义模型时，需要实现一个模型的描述类，继承自ftllm.llm.ComputeGraph

对应 [QWEN](../example/python/qwen2.py) 中的代码

``` python
from ftllm.llm import ComputeGraph
class Qwen2Model(ComputeGraph):
```

文件最后需要定义 `__model__` 变量来指定自定义模型结构对应的class, 对应代码

``` python
__model__ = Qwen2Model
```

模型描述类中需要实现build方法，来获取模型参数、描述计算流程

这里以示例代码为例介绍

``` python
class Qwen2Model(ComputeGraph):
    def build(self):
        # 1. 获取weight, data, config
        weight, data, config = self.weight, self.data, self.config

        # 2. 设置一些config
        config["max_positions"] = 128000

        # 3. 描述计算流程
        head_dim = config["hidden_size"] // config["num_attention_heads"]
        self.Embedding(data["inputIds"], weight["model.embed_tokens.weight"], data["hiddenStates"]);
        # 以下是计算流程，具体参见示例代码
```

#### `self.config`

模型配置，默认会从模型文件夹下的 `config.json` 文件中读取

build方法中可以修改config中的参数，例如改动 `max_positions` 可以修改上下文长度

有一些模型的 `config.json` 中使用的变量名不一致，需要在build过程中手动为config赋值。

例如在TeleChat7B模型的配置中没有 `max_positions` 变量，而是用 `seq_length` 变量代表长度，那么在build方法中需要用如下代码赋值：

``` python 
self.config["max_positions"] = self.config["seq_length"]
```

config中，有以下变量必须要赋值（如果config.json中变量名一致，可以不处理）：

``` python
self.config["max_positions"] #代表最长上下文长度
```

#### `self.weight`

代表权重数据

`self.weight[weightName]` 代表模型文件中名为weightName的参数（对应HF模型文件夹中.safetensors文件中的参数名）

#### ```self.data```

代表计算流程的中间变量和输入变量

`self.data[dataName]` 代表名为dataName的中间变量，`dataName` 可以使用除以下输入变量名之外的任意字符串

输入变量：

``` python
data["inputIds"] # 输入token
data["positionIds"] # 位置信息
data["attentionMask"] # mask信息
data["sin"] # 用于旋转编码的sin
data["cos"] # 用于旋转编码的cos
data["atype"] # 推理中的数据类型
data["pastKey."][i] # 第i个block的key cache
data["pastValue."][i] # 第i个block的value cache
```

#### 计算流程及算子

使用基类ComputeGraph添加算子的函数来描述计算流程

目前支持的算子见文档 [自定义模型算子](./custom_op.md)

### cpp版本的自定义模型

（cpp版本的自定义模型接口还在修改中...）

---

### Custom Op (`docs/custom_op.md`)

## 自定义模型算子文档

### `AddTo`
```python
def AddTo(self, input0, input1, alpha = 1.0):
    """
    将两个输入节点相加，并乘以一个可选的缩放因子 alpha。

    参数:
    input0 (GraphNode): 第一个输入节点。
    input1 (GraphNode): 第二个输入节点。
    alpha (float, optional): 缩放因子，默认为 1.0。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "AddTo", 
                       "nodes": {"input0": input0, "input1": input1, "alpha": FloatGraphNode(alpha)}})
```

### `DataTypeAs`
```python
def DataTypeAs(self, input, input1):
    """
    将输入节点的数据类型转换为另一个输入节点的数据类型。

    参数:
    input (GraphNode): 需要转换数据类型的输入节点。
    input1 (GraphNode): 目标数据类型的输入节点。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "DataTypeAs", 
                       "nodes": {"input": input, "input1": input1}})
```

### `Embedding`
```python
def Embedding(self, input, weight, output):
    """
    执行嵌入操作，将输入索引映射到嵌入权重。

    参数:
    input (GraphNode): 输入索引节点。
    weight (GraphNode): 嵌入权重节点。
    output (GraphNode): 输出节点。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "Embedding", 
                       "nodes": {"input": input, "weight": weight, "output": output}})
```

### `ExpandHead`
```python
def ExpandHead(self, input, headDim):
    """
    把input最后一维展开成[-1, headDim]。

    参数:
    input (GraphNode): 输入节点。
    headDim (int): 头部维度大小。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "ExpandHeads", 
                       "nodes": {"input": input, "headDim": IntGraphNode(headDim)}})
```

### `FusedAttention`
```python
def FusedAttention(self, q, k, v, curk, curv, original, mask, output, seqLens, 
                   scale, maskType=0, unitLen=128):
    """
    执行Attention操作。

    参数:
    q (GraphNode): 查询节点。
    k (GraphNode): key cache 
    v (GraphNode): value cache 
    curk (GraphNode): 当前key
    curv (GraphNode): 当前value
    original (GraphNode): 原始节点，用于恢复计算后的shape
    mask (GraphNode): 掩码
    output (GraphNode): 输出
    seqLens (GraphNode): 序列长度
    scale (float): 缩放因子
    maskType (int, optional): 掩码类型，默认为 0。
    unitLen (int, optional): 单元长度，默认为 128。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "FusedAttention", 
                       "nodes": {"q": q, "k": k, "v": v, "curk": curk, "curv": curv, 
                                "original": original, "mask": mask, "output": output, "seqLens": seqLens, 
                                 "scale": FloatGraphNode(scale), 
                                 "maskType": IntGraphNode(maskType), "unitLen": IntGraphNode(unitLen)}})
```

### `Linear`
```python
def Linear(self, input, weight, bias, output):
    """
    执行线性变换操作。

    参数:
    input (GraphNode): 输入节点。
    weight (GraphNode): 权重节点。
    bias (GraphNode): 偏置节点。
    output (GraphNode): 输出节点。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "Linear", 
                       "nodes": {"input": input, "weight": weight, "bias": bias, "output": output}})
```

### `LlamaRotatePosition2D`
```python
def LlamaRotatePosition2D(self, input, positionIds, sin, cos, rotaryDim):
    """
    执行 Llama 模型的二维位置旋转操作。

    参数:
    input (GraphNode): 输入节点。
    positionIds (GraphNode): 位置 ID 节点。
    sin (GraphNode): 正弦节点。
    cos (GraphNode): 余弦节点。
    rotaryDim (int): 旋转维度大小。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "LlamaRotatePosition2D", 
                       "nodes": {"input": input, "positionIds": positionIds, "sin": sin, "cos": cos, "rotaryDim": IntGraphNode(rotaryDim)}})
```

### `MulTo`
```python
def MulTo(self, input0, input1):
    """
    将两个输入节点相乘。

    参数:
    input0 (GraphNode): 第一个输入节点。
    input1 (GraphNode): 第二个输入节点。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "MulTo", 
                       "nodes": {"input0": input0, "input1": input1}})
```

### `RMSNorm`
```python
def RMSNorm(self, input, weight, eps, output):
    """
    执行 RMS 归一化操作。

    参数:
    input (GraphNode): 输入节点。
    weight (GraphNode): 权重节点。
    eps (float): 小常数，用于防止除零错误。
    output (GraphNode): 输出节点。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "RMSNorm", 
                       "nodes": {"input": input, "weight": weight, "eps": FloatGraphNode(eps), "output": output}})
```

### `Silu`
```python
def Silu(self, input, output):
    """
    执行 SiLU（Sigmoid Linear Unit）激活函数操作。

    参数:
    input (GraphNode): 输入节点。
    output (GraphNode): 输出节点。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "Silu", 
                       "nodes": {"input": input, "output": output}})
```

### `SplitLastTokenStates`
```python
def SplitLastTokenStates(self, input, seqLens, output):
    """
    分割batch输入中每个batch的最后一个 token 状态。

    参数:
    input (GraphNode): 输入节点。
    seqLens (GraphNode): 序列长度节点。
    output (GraphNode): 输出节点。

    返回:
    无返回值，结果存储在内部图结构中。
    """
    self.graph.append({"type": "SplitLastTokenStates", 
                       "nodes": {"input": input, "output": output, "seqLens": seqLens}})
```

---

### Deepseek (`docs/deepseek.md`)

# DeepSeek-V4 / DeepSeek-V4-Flash 部署指南

[English](deepseek_en.md) · [返回 README](../README.md) · [混合推理](mixforward.md) · [Benchmark](benchmarks/deepseek_v4.md)

本文面向 DeepSeek-V4 与 DeepSeek-V4-Flash。早期 DeepSeek 模型仍可兼容运行，但不再作为本指南的主要示例。

## API Server 快速启动

~~~bash
ftllm server fastllm/DeepSeek-V4-Flash \
  --model_name DeepSeek-V4-Flash \
  --host 0.0.0.0 --port 8080
~~~

对于大型 MoE checkpoint，FastLLM 默认会优先使用 CUDA 运行非专家部分，并根据构建能力将专家层放到 CPU 或 NUMA。正式部署时建议显式指定设备布局。

## 按设备选择启动命令

### 单 GPU + NUMA

~~~bash
ftllm server fastllm/DeepSeek-V4-Flash \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192 \
  --gpu_mem_ratio 0.9
~~~

单路 CPU 机器可以使用 `--moe_device cpu`。

### 多 GPU 张量并行

当 GPU 总显存足以容纳目标 checkpoint 时：

~~~bash
ftllm server /data/models/deepseek-v4 \
  --tp 0,1 \
  --max_batch 8 \
  --gpu_mem_ratio 0.9
~~~

DeepSeek-V4 的 `--tp` 会选择 MultiCUDA 张量并行路径。更多 GPU 可以显式写成 `--tp 0,1,2,3` 或使用对应的裸数字数量。

### GPU、NUMA 与磁盘混合专家

~~~bash
ftllm server fastllm/DeepSeek-V4-Flash \
  --device cuda \
  --moe_device "{'cuda':1,'numa':8,'disk':1}" \
  --chunked_prefill_size 8192
~~~

比例表示 MoE 层在不同设备上的相对分配。磁盘只适合容量补充，并强依赖 SSD 随机读取性能。更多布局见[混合推理指南](mixforward.md)。

### 多 GPU + NUMA 混合 MoE

~~~bash
ftllm server fastllm/DeepSeek-V4-Flash \
  --device cuda \
  --moe_device "{'multicuda:0,1':15,'numa':85}"
~~~

该方式让部分专家层使用两卡张量并行，其余专家层运行在 NUMA 内存中。比例应根据显存、内存带宽和实际吞吐调整。

## 内置 DSpark

包含内置 DSpark 权重和配置的 DeepSeek-V4 checkpoint 可以使用：

~~~bash
ftllm server /data/models/deepseek-v4 \
  --dspark 7
~~~

`--dspark` 不能小于 checkpoint 训练时的 block 大小。服务启动时会校验模型架构和 DSpark 配置。

## 长上下文

~~~bash
ftllm server /data/models/deepseek-v4 \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --gpu_mem_ratio 0.9
~~~

实际上下文上限取模型原生上限、命令行上限和 KV Cache 容量的较小值。

## SM120 TP8 稀疏注意力路径

在支持的 SM120/SM121 CUDA 设备上，TP8 稀疏注意力测试使用：

~~~bash
FASTLLM_CUDA_GRAPH=1 \
FASTLLM_CUDA_CUSTOM_ALLREDUCE=1 \
ftllm server /data/models/deepseek-v4 \
  --tp 8 --triton \
  --max_batch 1
~~~

该命令是已记录 TP8 Benchmark 的推荐起点，不代表其他 GPU 架构也应使用相同设置。实测速度与限制见 [DeepSeek-V4 Benchmark](benchmarks/deepseek_v4.md) 和[稀疏注意力分析](deepseek_v4_sparse_attention.md)。

## 思考与工具调用

~~~bash
ftllm server /data/models/deepseek-v4 \
  --enable_thinking true \
  --tool_call_parser auto
~~~

DeepSeek-V4 使用模型对应的编码和工具协议。API 端建议保留模型原始模板与 parser 自动选择。

## Benchmark

- [DeepSeek-V4 TP8 SM120 性能摘要](benchmarks/deepseek_v4.md)
- [DeepSeek-V4 稀疏注意力详细分析](deepseek_v4_sparse_attention.md)
- [通用 Benchmark 入口](benchmark.md)

尚未收录单 GPU + NUMA 和磁盘专家布局的正式吞吐；这些命令是部署起点，不能用 TP8 数据外推速度。

---

### Deepseek En (`docs/deepseek_en.md`)

# DeepSeek-V4 / DeepSeek-V4-Flash Deployment

[中文](deepseek.md) · [Back to README](../README_EN.md) · [Hybrid inference](mixforward.md) · [Benchmark](benchmarks/deepseek_v4_en.md)

This guide focuses on DeepSeek-V4 and DeepSeek-V4-Flash. Earlier DeepSeek models remain compatible but are no longer the primary examples.

## API server quick start

~~~bash
ftllm server fastllm/DeepSeek-V4-Flash \
  --model_name DeepSeek-V4-Flash \
  --host 0.0.0.0 --port 8080
~~~

Large MoE checkpoints normally use CUDA for non-expert layers and CPU or NUMA for experts. Set the layout explicitly for production.

## Commands by device

### Single GPU + NUMA

~~~bash
ftllm server fastllm/DeepSeek-V4-Flash \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192 \
  --gpu_mem_ratio 0.9
~~~

Use `--moe_device cpu` on a single-socket host.

### Multi-GPU tensor parallelism

When aggregate GPU memory can hold the checkpoint:

~~~bash
ftllm server /data/models/deepseek-v4 \
  --tp 0,1 \
  --max_batch 8 \
  --gpu_mem_ratio 0.9
~~~

DeepSeek-V4 selects the MultiCUDA path when `--tp` uses multiple GPUs.

### GPU, NUMA, and disk experts

~~~bash
ftllm server fastllm/DeepSeek-V4-Flash \
  --device cuda \
  --moe_device "{'cuda':1,'numa':8,'disk':1}" \
  --chunked_prefill_size 8192
~~~

Disk experts are a capacity fallback and depend heavily on SSD random-read performance.

### Multi-GPU + NUMA hybrid MoE

~~~bash
ftllm server fastllm/DeepSeek-V4-Flash \
  --device cuda \
  --moe_device "{'multicuda:0,1':15,'numa':85}"
~~~

Tune the ratio against GPU memory and host-memory bandwidth. See the [hybrid inference guide](mixforward.md).

## Embedded DSpark

For a DeepSeek-V4 checkpoint containing embedded DSpark weights:

~~~bash
ftllm server /data/models/deepseek-v4 --dspark 7
~~~

The requested value cannot be smaller than the block size used to train the checkpoint.

## Long context

~~~bash
ftllm server /data/models/deepseek-v4 \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --gpu_mem_ratio 0.9
~~~

## SM120 TP8 sparse-attention path

~~~bash
FASTLLM_CUDA_GRAPH=1 \
FASTLLM_CUDA_CUSTOM_ALLREDUCE=1 \
ftllm server /data/models/deepseek-v4 \
  --tp 8 --triton \
  --max_batch 1
~~~

This is the starting point for the recorded TP8 result, not a universal recommendation for other GPU architectures. See the [DeepSeek-V4 benchmark](benchmarks/deepseek_v4.md) and [sparse-attention analysis](deepseek_v4_sparse_attention.md).

## Thinking and tool calling

~~~bash
ftllm server /data/models/deepseek-v4 \
  --enable_thinking true \
  --tool_call_parser auto
~~~

## Benchmark status

The repository has a published TP8 SM120 result. Single-GPU + NUMA and disk-expert throughput have not been measured under a publishable common methodology.

---

### Deepseek V41 (`docs/deepseek_v41.md`)

# DeepSeek-V4.1-Flash 支持说明

[返回 README](../README.md) · [DeepSeek-V4 部署指南](deepseek.md) · [混合推理](mixforward.md)

本文记录 FastLLM 对 DeepSeek-V4.1 系列（`model_type = deepseek_v41`，目前为 DeepSeek-V4.1-Flash）的支持范围、
架构差异、启动方式与验证方法。

面向自动化 agent 的 `--auto` 启动、参数覆盖和验收流程见 [模型部署速查](model-deployment-agent.md)。

实现与回归入口：

- `include/models/deepseekv41.h` / `src/models/deepseekv41.cpp`：模型（继承 `DeepSeekV4Model`，复用其 MoE / HC-post / WoA / 采样等基础设施）
- `src/models/deepseekv41_vision.cpp`：视觉编码器（ViT + aligner）与图文前向 `ForwardMultimodal`
- `src/devices/cpu/deepseekv41ops.cpp`：V4.1 专用算子的 CPU 参考实现
- `src/devices/cuda/models/deepseekv41-kernels.cu`：对应的 CUDA kernel（面向 SM86 等无 FP8 tensor core 的设备；
  稀疏注意力与 indexer 打分在 SM80+ 上走 BF16 mma，其余算子为 FP32）
- `src/models/deepseekv41_dspark.cpp`：DSpark 投机解码（草稿层 `mtp.*`、校验与回滚）
- `src/devices/cuda/models/deepseekv41-dspark-kernels.cu`：DSpark 草稿侧 markov head 整条链的融合 kernel
- `tools/fastllm_pytools/deepseek_v41_engram.py`：Engram 哈希元数据生成
- `tools/fastllm_pytools/encoding_dsv41.py`：官方 V4.1 prompt 编码（vendored）
- `tools/fastllm_pytools/deepseek_v41_multimodal.py`：图像动态分辨率预处理（移植自官方 `image_processor.py`）与图像占位符展开
- `test/basic/deepseek_v41_reference.py`：与官方 `inference/model.py` 的端到端数值对齐测试
- `test/basic/deepseek_v41_vision_reference.py`：图文输入的端到端对齐测试（含真实 ViT 权重验证）
- `test/basic/deepseek_v41_dspark.py`：DSpark 的精确性（开 / 关输出一致）与回滚测试
- `test/basic/test_deepseek_v41_cpu_fixture.py`：CPU-only 的固定 fixture 对齐回归（只依赖 numpy，可进 CI）
- `test/ops/deepseekV41OpsRegression.cpp`：11 个 V4.1 专用算子的 CPU / CUDA 一致性测试

## 相对 DeepSeek-V4 的架构变化

| 项目 | DeepSeek-V4 | DeepSeek-V4.1 |
| --- | --- | --- |
| Hyper-Connections | 每个子层自己算 pre/post/comb 并立即使用 | pre 由上一个子层计算、下一个子层使用；最后一层 FFN 的 pre 用于 lm_head 前的折叠 |
| 压缩注意力 | 每层独立 compressor（ratio 4 / 128，带 ape、overlap） | `compress_ratios` 取 0 / 1 / 2；只有 `kv_source_layer_ids` 中的层拥有 compressor 与压缩 KV cache，其后各层直接读取（跨层共享） |
| Indexer | 每个 CSA 层有独立的 128 维 compressor + Hadamard 旋转 | key 由 compressor latent 经 `wk + k_norm` 派生；只有 `index_source_layer_ids` 层运行 indexer，其它层复用最近的 top-k |
| 两级 top-k | 无 | `candidate_source_layer_id` 层先按 `candidate_block_size` 分块选出 `candidate_topk_blocks` 个块，之后的 index source 只在候选块内选 top-k |
| Engram | 无 | `engram_layer_ids` 层前对 residual stream 做 n-gram 哈希查表（每层约 100 GB 的 FP8 表）并门控写回 |
| 路由 | 前若干层 hash 路由 | 全部 noaux_tc，`gate.bias`；图像 token 使用 `gate.bias_vl` |
| q 处理 | 额外 RMS 归一 | 无 |
| 权重格式 | FP8 128x128 块 scale | 稠密与共享专家 FP8 32x32 块 UE8M0 scale；路由专家 FP4（沿 K 每 32 个一组 UE8M0 scale） |
| 附加模块 | MTP / DSpark | DSpark 草稿层（mtp.*，markov head 为 embed + head、草稿层 128 专家 top-3、目标层取自主干）、视觉编码器（vision.* / aligner.* / image_start / image_end / image_newline） |

## 当前支持范围

已实现并通过数值对齐测试：

- 文本推理（prefill + decode，含分块 prefill），CUDA 与 CPU 两套算子路径；
- 多请求批量 decode：多个请求的 token 拼成一个序列共享一次前向（Linear / MoE / Engram 查表按整批执行，
  注意力与压缩 KV 按请求分别执行），见下文"多请求与前缀缓存"；
- 前缀缓存 / 多轮对话复用（`--cache_history true`）；
- 跨层共享压缩 KV（ratio 1 / 2）、两级 indexer top-k、Engram、Hyper-Connections、sqrt-softplus 路由；
- 与官方实现一致的 FP8 / FP4 伪量化（窗口 KV、压缩 KV、indexer q/k）；
- 真实 checkpoint 的权重格式（FP8 32x32、FP4 路由专家、FP8 + UE8M0 的 Engram 表）；
- 图像输入（OpenAI 接口的 `image_url`）：ViT + aligner、图像 token 的 `gate.bias_vl` 路由与 Engram 掩码，详见下文；
- DSpark 投机解码（`mtp.*` 草稿层），详见下文"DSpark 投机解码"。
- 张量并行（`--tp 2`、`--tp 4`、`--tp 8`），详见下文"张量并行"。

尚未实现：

- 视觉编码器与 DSpark 草稿层的张量并行及 CUDA Graph；主干 decode / DSpark 校验已支持分段图；
- DSpark 与批量 decode 的组合（批内不产生候选，只保持草稿缓存同步）、DSpark 与图文请求的组合。

## Engram 元数据

Engram 的哈希基于 tokenizer 归一化（NFKC / NFD / 去重音 / 小写 / 空白折叠）后的"压缩 token id"。
该映射依赖 HuggingFace `tokenizers` 的 normalizer，因此由 Python 侧生成、C++ 侧加载：

```bash
python -m ftllm.deepseek_v41_engram /path/to/DeepSeek-V4.1-Flash
# 生成 /path/to/DeepSeek-V4.1-Flash/engram_meta.json（压缩词表大小应为 99092）
```

通过 `ftllm` 启动时自动读取模型目录的 `engram_meta.json`，缺失时自动生成；模型目录只读时写到
`~/.cache/fastllm/engram/`，C++ 侧自动从该缓存目录读取。素数桶布局由 C++ 侧按官方算法推导，
并与 `engram_num_embeddings` 做一致性校验，无需指定元数据环境变量。

Engram 表（两层，各约 100 GB）以 FP8 + UE8M0 scale 原样保存；查表在 CPU 完成，
`wkv` 投影与门控在 GPU 完成。默认 `--ngram_device cpu` 将表常驻内存；
`--ngram_device disk` 与 Qwen4 PLE 共用通用磁盘权重加载和 `EmbeddingDirect` 算子，
按行读取权重及 scale，不加载整张表。磁盘读取可能增加延迟，访问过的数据仍可能占用操作系统页缓存。

### Engram 的分段计时与几个开关

查表发生在模型代码里而不是算子里，`FASTLLM_PRINT_PROFILE` 看不到它，需要单独计时：

```bash
FASTLLM_DSV41_ENGRAM_PROFILE=1 FASTLLM_CUDA_SYNC=1 ftllm server ...
```

会按 decode / prefill 分桶打印四段的平均耗时：`hash`（算行号）、`gather`（读表 +
FP8→BF16）、`wkv`（投影）、`apply`（门控写回）。后两段在 GPU 上异步下发，
不加 `FASTLLM_CUDA_SYNC=1` 只能量到 kernel launch 的时间。`=2` 额外逐次打印，
`FASTLLM_DSV41_ENGRAM_PROFILE_EVERY=N` 控制中途汇总的频率（默认 64 次，0 表示只在退出时打印）。

Engram 配置如下；默认值见表。权重格式与数学边界应分别核对：

| 开关 | 作用 |
|---|---|
| `FASTLLM_DSV41_ENGRAM_POOL`（默认开，=0 关） | 查表改用 fastllm 的常驻线程池，替掉每次调用现场 create/join 最多 32 个 `std::thread` 的写法。输出逐位相同，prefill 收益最明显。 |
| `FASTLLM_DSV41_ENGRAM_PREFETCH=1` | 跨层预取。后台线程提前计算下一层的 n-gram 行号；常驻表还会在缓存预算内预读表行，磁盘模式只预计算行号。后台线程只看历史窗口的快照，不引用请求状态。 |
| `FASTLLM_DSV41_ENGRAM_MADVISE=random / hugepage / both` | 仅作用于常驻表：`random` 设置 `MADV_RANDOM`；`hugepage` 使用匿名 mmap + `MADV_HUGEPAGE` 分配，减少页表与 TLB 开销，并省掉 `std::vector` 的清零。 |
| `FASTLLM_DSV41_ENGRAM_WKV_FP8`（默认开，=0 关） | `layers.{1,14}.engram.wkv.weight` 在真实权重里本来就是 F8_E4M3 + UE8M0 块 scale（`[25600, 6144]`，block 32x32），默认会被解量化成启动 dtype（float16），每层 157 MB 变 314 MB。打开后按原样保留 FP8，**不做任何重量化**——权重数值就是 checkpoint 里的那份，比解成 float16 还少一次舍入；省下每层 157 MB 显存与同样多的每步带宽。只有伴随的 `.scale` 张量存在时才切换，权重是 BF16 的迷你模型不受影响。 |

## 自动配置（推荐）

自动化部署建议先把本页和 [Agent 部署速查](model-deployment-agent.md) 交给 agent。对 V4.1 Flash，自动规划器会根据资源尝试 `fast_prefill`、DSpark、Engram 放置和长上下文预算；主机内存不足时会把 Engram/专家回退到磁盘，并在日志中给出实际配置。

```bash
ftllm server /path/to/DeepSeek-V4.1-Flash-1017 \
  --auto --model_name deepseek-v4.1-flash-1017 \
  --host 0.0.0.0 --port 8080
```

如果需要模拟较小机器，使用 `--auto_gpu_memory_limit 20g` 和
`--auto_host_memory_limit 128g`；它们限制自动规划预算，省略时自动读取实际硬件。

## 手动配置参考

下面以单张 CUDA GPU + 大内存主机为例（专家与 Engram 表放在 CPU 内存；显存需求取决于上下文和 chunk）：

```bash
ftllm server /path/to/DeepSeek-V4.1-Flash \
  --device cuda --moe_device numa \
  --dtype float16 \
  --chunked_prefill_size 4096
```

- 没有 FP8 tensor core 的 GPU（如 SM86）请使用 `--dtype float16`，稠密 FP8 权重会在加载时按 32x32 块 scale 反量化；
- `--kv_cache_dtype` 控制长期 KV 缓存（压缩 KV + indexer key）的存储精度，见下面的“KV 缓存存储精度”一节。
  默认 BF16，可选 `fp8_e4m3`（1650 B/token）与 `fp4_e2m1`（890 B/token，与官方一致，且数值无损）；
- `ftllm` launcher 的自动配置按 config 计算 V4.1 的常驻内存下界（FP4 专家约 289 GB + Engram 表约 203 GB +
  稠密部分），权重文件不全时也不会低估；主机内存不足以放下专家与 Engram 表时会退到 `moe_device=disk`；
- 单路 CPU 机器可用 `--moe_device cpu`；
- 内存需求：Engram 表约 200 GB + 路由专家（FP4）约 270 GB + 加载临时空间；
- 首次启动会生成 `engram_meta.json`（约 1 分钟）并读入两张 Engram 表。

CPU / NUMA 专家使用 FastLLM 自有线程池，由 `--threads` 控制；CLI 会自动设置 `FT_THREADS`，无需重复指定。

### 导出 Q2_K/Q4_K 混合量化

`tools/deepseek_v41_export_q2.py` 按分片导出可独立加载的 FastLLM 模型目录，配置见
`example/quant/deepseekv41/Q2_K_MIXED.json`：主干路由专家的 `w1/w3` 使用 Q2_K，`w2` 使用 Q4_K；
其它线性权重使用 FP16，模型映射保护的权重保留指定精度。Engram 表和 DSpark 草稿权重逐字节复制。
混合分片会先分离这些原样保留的张量，导出不依赖 `FASTLLM_DSPARK_TOKENS` 设置。
原始 Flash checkpoint 的路由专家已经是 FP4，因此这里是从 FP4 再量化，未使用校准集或 importance matrix。

```bash
FT_NUMAS=1 numactl -C 0-31 -m 0 \
  python tools/deepseek_v41_export_q2.py \
  --model /path/to/DeepSeek-V4.1-Flash \
  --output /path/to/DeepSeek-V4.1-Flash-Q2_K-Mixed \
  --threads 28
```

中断后使用相同命令并增加 `--resume`；脚本检查源配置、量化配置和分片信息，跳过已经完成的分片。
输出是 FastLLM 的扩展 safetensors 格式，需使用支持导出激活元数据和 V4.1 GGML NUMA 激活转换的 FastLLM：
独立 scale 会内嵌到转换后的权重，`config.json` 中的 `fastllm_activation_quantized_linears` 保留原模型的激活量化边界。

单 NUMA 混推启动示例（CPU 核号应按机器的 NUMA 拓扑调整）：

```bash
FT_NUMAS=1 numactl -C 0-31 -m 0 \
  ftllm server /path/to/DeepSeek-V4.1-Flash-Q2_K-Mixed \
  --device cuda:0 --moe_device numa --ngram_device cpu \
  --threads 28 --dtype float16 --kv_cache_dtype bfloat16 \
  --chunked_prefill_size 4096 --max_batch 1 --dspark 5 \
  --fast_prefill --cache_history true --host 0.0.0.0 --port 8080
```

### Q2 混合量化的缓存与计算

支持 `--moe_cuda_cache` 的 NUMA Q2_K_R4/Q4_K_R4 专家直接借用已注册的主机权重，避免第二份完整快照。
普通 decode 与 2–8 行 DSpark 验证保留 block-32 FP8 激活量化、GGML Q8 编码、SwiGLU 限幅、路由权重位置和逐专家 BF16 舍入。
命中率应区分 cache lookup 与全部 route：CPU 分流未必查询 CUDA 缓存，lookup 命中率不是全部专家驻留率。

长 prefill 在 BF16、block-32 激活、Q2_K gate/up + Q4_K down 以及兼容形状和设备上使用 grouped MMQ，支持 NUMA R4 布局。
输入按 token 量化后按 route 分发；scale 和 min 修正保留 FP32。至少 1024 行时使用 64-row 计算块，其余使用小块，尾部访问限于当前专家。
down 权重在独立 stream 上传，与 gate/up 计算重叠；不支持的布局或显存不足时保留回退。
不同矩阵累加路径可能改变 BF16/FP8 舍入及后续路由，功能检查不替代模型质量评估。

Q2 混合量化也可以使用磁盘专家和有容量上限的 CPU 专家缓存。Engram 同时放磁盘、关闭 DSpark 的示例：

```bash
FT_NUMAS=1 OPENBLAS_NUM_THREADS=1 numactl -C 0-31 -m 0 \
  ftllm server /path/to/DeepSeek-V4.1-Flash-Q2_K-Mixed \
  --device cuda:0 --moe_device disk --moe_cpu_cache 64g --ngram_device disk \
  --threads 28 --dtype float16 --kv_cache_dtype bfloat16 \
  --chunked_prefill_size 4096 --max_batch 1 --dspark 0 --mtp 0 \
  --fast_prefill --cache_history true --host 0.0.0.0 --port 8080
```

`64g` 表示 64 GiB 专家缓存容量，不是进程总内存上限。磁盘缓存路径为 V4.1 的 GGML 路由专家和
FP16 共享专家保留 block-32 激活量化、BF16 中间舍入及路由权重位置；这两种专家在 CPU 上计算。
专家缓存未命中时默认使用 direct I/O，避免另在系统页缓存中保留整份专家；Engram 行读取仍使用系统页缓存。
磁盘 MoE 已用独立标量计算验证，不能直接沿用 NUMA 配置的整模型精度评测结论。


磁盘路径直接读入对齐专家缓冲，保留不对齐文件偏移、分散 gate/up 和文件尾部的普通读取回退。
CPU 缓存按热度和最后访问时间管理，4096 次访问后衰减；预算限制专家 payload，不限制整个进程 RSS。
另有最多 32 MiB 的读取缓冲复用池。2–8 行 MoE 验证使用双专家预取，保持计算、缓存准入及归约顺序，异常退出也等待全部预取完成。
未消费专家的暂存空间不计入活跃缓存预算；该模型单个 Q2 路由专家约 13.7 MiB。

部署速度、内存和适用范围见[Benchmark](benchmarks/deepseek_v41.md)。

## 张量并行

支持 GPU 共享专家、单 token 异步调度，以及分段和单请求整图 CUDA Graph。吞吐收益取决于 GPU、卡间通信和
CPU 专家占比，应在相同配置下分别测量 decode 与首 token 延迟。

```bash
FASTLLM_CUDA_GRAPH=1 ftllm server /path/to/DeepSeek-V4.1-Flash \
  --tp 2 --moe_device numa --cuda_shared_expert true --dtype float16
```

`--tp 2` 会把主 device 归一化成 `multicuda:0,1` 并设置 `FASTLLM_TP`（触发加载期的权重切分）。
用户显式给出的 `--moe_device`（如 `numa`）不受影响：路由专家仍在 CPU / NUMA 上算一份，
结果广播回两张卡。直接写 `--device multicuda:0,1` 也能启用，只是稠密权重改为在首次使用时切分，
显存峰值更高。

### 切什么、为什么

沿用 DeepSeek-V4 的切法，只切 **query head** 这一条线：

| 权重 | 切法 | 说明 |
| --- | --- | --- |
| `attn.wq_b` | 按行切（`linearRow`） | 输出 q 的 head 维分片，`Reshape` 会把 tpAxis 从最后一维换算到 head 维 |
| `attn.attn_sink` | 按 head 切 | 与 q 的分片区间一致 |
| `attn.wo_a` | 按 head 组切（`linearRow`） | 区间必须对齐到 `o_group`（每组 `num_attention_heads / o_groups` 个 head） |
| `attn.wo_b` | 按列切（`linearColumn`） | 输入分片，输出 all-reduce 回复制布局 |
| `ffn.shared_experts.gateup` / `w2` | 行切 / 列切 | 与其它 MoE 模型一致 |
| `head.weight` | 按行切 | 两卡各算一半词表，`LLMSamplingBlock` 已有分片 logits 的贪心 / 汇聚路径 |

其余全部**复制**（每卡一份完整副本）：`wq_a`、`wkv`、`compressor.*`、`indexer.*`、`ffn.gate`、
各 RMSNorm 权重，以及跨层共享的压缩 KV、indexer key、滑窗 KV 缓存。

为什么跨层共享的压缩 KV 与两级 indexer 选复制而不是切分 + all-gather：

- **压缩 KV 与 head 无关**。V4.1 的注意力是 MLA 式的：每个位置只有一份 `head_dim` 的 latent，
  所有 query head 共享它。沿 head 切它没有意义，沿序列切则每一层注意力都要 all-gather 整段 KV，
  通信量正比于上下文长度 × 层数。复制的代价只是一份显存（压缩后本身就比滑窗 KV 小 1~2 倍）。
- **压缩 KV 是跨层共享的**。`kv_source_layer_ids` 的层算出的 cache 会被后面若干层直接读，
  切分后每个消费层都要重新 all-gather，而不是只在生产层付一次代价。
- **两级 indexer 的候选块也是跨层共享的**。`candidate_source_layer_id` 选出的候选块被之后所有
  index source 层复用。indexer 的分数矩阵是 `[token, m]`（1M 上下文时每 token 有 50 万个候选），
  切 index head 意味着要对这个矩阵做 all-reduce 才能取 top-k——通信量比整个注意力还大。
  而且 top-k 必须在两张卡上**逐位一致**：卡 0 和卡 1 选到不同的 KV 块，
  两边算出的注意力就不是同一个函数的两个分片了。复制 indexer 既省通信又天然保证一致。
- **路由分数变换与专家选择同理**逐卡各算一份，保证两卡选到同一组专家。
- **Engram 表在 CPU**，查表结果按 token 复制到每张卡。

### 约束

TP 要求 query heads 均匀分片，且每个分片包含完整的 `o_group`。
真实模型 64 头、`o_groups=8`，TP=2/4/8 分别对应每卡 32/16/8 个 head；
稀疏 Attention 的 MMA 和标量回退路径均支持尾部不足一个 tile 的 head，
不会读取或写入填充 head 对应的全局内存。不满足分组对齐条件时模型会打印说明并**整体退回单卡**（撤销注意力与 head 的 TP 权重注册，
把 device map 改回 `cuda:<第一张卡>`），而不是做"只切 FFN"的半张量并行。

八卡 GPU 专家配置示例（Engram 表仍常驻 CPU 内存）：

```bash
FASTLLM_CUDA_GRAPH=1 ftllm server /path/to/DeepSeek-V4.1-Flash \
  --tp 8 --dtype auto --ngram_device cpu --chunked_prefill_size 512
```

默认 MoE 设备跟随主设备，使用八卡张量并行。此命令不启用近似 `--fast_prefill`。

视觉编码器（ViT + aligner）与 DSpark 草稿层仍在单卡上算。`--tp 2 --dspark 5` 可以组合使用：
目标模型按上述方式张量并行，草稿层在第一张卡运行，共享的 `head.weight` 沿用两卡词表分片，
完整 logits 汇总到第一张卡进行候选生成与校验。校验回滚同时截断两张卡上的缓存副本。

TP 主模型特征传回草稿 GPU 时显式指定设备；回归覆盖两种首卡顺序、预填充、不同接受长度及缓存副本一致性。

### 混合推理的 decode 调度

TP 的共享专家在各卡复制计算，保留完整点积后的 BF16 舍入边界；混合执行的路由专家仍使用配置的 CPU / NUMA 后端。
输入先暂存，再发射 GPU 共享专家，使两条分支重叠。单 token TP 默认使用常驻 worker 与流事件，
保持 Graph 和段外算子使用的 GPU 地址稳定，并在采样前等待所有 logits 分片的生产完成。

CPU 专家结果上传、共享结果相加与 HC post 在一次 worker 调用中执行，保留 AddTo 的中间舍入。
普通单 token 和 DSpark verify 共用该路径。输入直接 D2H，CPU MoE 输出保持二维以复用 GPU 副本；每次上传覆盖新结果。
不支持的布局、类型或设备使用原路径。同步 H2D 保证 CPU 源在回调返回后即可复用。

DSpark 提交 KV 时复用同一套异步 TP 派发，在整轮提交结束后同步两张卡。
部分接受时，截取的 KV 行保留到同步结束，避免逐层同步以及临时缓冲提前释放。
NUMA 的多行 BF16 输入在至少 8192 个元素时按完整 block-32 分配激活量化任务，保持原有量化结果；
单行和较小输入保持串行，避免线程派发开销。
这些优化不改变 prefill 的动态专家分配。

比较 TP 与按层分卡时，应保持共享专家位置、CPU 线程、NUMA、KV 类型和 prefill 分块相同。
NUMA 按多个 LLC 分散绑核时，TP 执行线程保留启动时的 CPU 亲和性，避免把专家正在使用的核
误判为空闲核。不需要额外设置绑核环境变量。

## 快速 prefill（`--fast_prefill`）

```bash
ftllm server /path/to/DeepSeek-V4.1-Flash --device cuda --moe_device numa \
  --fast_prefill
```

默认关闭，也支持 `--fast-prefill` 写法，启动时以参数为准。
开启后使用 decoder SWA bounded replay：每个文本 prefill chunk 完整计算到最后一个
KV source 层，后续层只计算各请求末尾 `sliding_window` 个 token。V4.1-Flash 对应
第 0–20 层处理完整 chunk，第 21–39 层处理末尾至多 128 token。
长度不超过窗口的片段、包含图像嵌入或图像掩码的前向保持完整计算。

压缩 KV 和 indexer key 仍完整保留，RoPE、缓存槽位和请求长度使用原始绝对位置。
支持分块 prefill、混合批次、前缀缓存恢复及 DSpark；DSpark 的 main hidden 同步取尾部，
verify 始终计算全部候选位置。单 token decode 和 CUDA Graph 的执行范围不变。

这是**近似计算**：后段层的局部 attention 在保留窗口的起点截断，可能改变 logits，
差异不局限于浮点舍入。开启后应按实际任务验证质量，并固定输入与 chunk 配置对比速度。


## decode 与 DSpark 校验的 CUDA Graph

`FASTLLM_CUDA_GRAPH=1` 在启动预热时捕获普通 decode 图。按照 `max_batch` 预热 1、2、4 等二次幂，以及配置的最大 batch；未预热的 batch 或失效图使用 eager，不在普通请求内重新捕获。DSpark 目标验证仍保留独立的分段图。

GPU 执行模式下，单请求文本 decode 满足以下条件时，会进一步组成每卡一张整图：

- `--ngram_device cuda`，全部 Engram 表均已成功放到 GPU；
- 普通 BF16 KV，未开启 DSpark，兼容的 TP head 分片；
- 当前支持的 512 维 Full Attention、128 维 Indexer、四路 HC 布局。

整图包含各层投影、归一化、动态 KV 更新、Indexer、Attention、路由专家、共享专家、Engram GPU 计算及 LM head。每个 decode step 每张卡只调用一次 `cudaGraphLaunch`，上下文长度和 KV 地址通过固定元数据缓冲更新，不随上下文增长重新捕获。请求准备、embedding 输入准备、Engram 地址计算和采样仍在图外；不能把它理解为整个 HTTP 请求只有一个 GPU 操作。

启动日志出现以下信息，才表示整图已就绪：

```text
DeepSeek-V4.1 full decode graph ready: batch=1, ranks=8, one graph per rank/step
```

例如八卡单并发服务：

```bash
FASTLLM_CUDA_GRAPH=1 ftllm server /path/to/DeepSeek-V4.1-Flash \
  --tp 8 --ngram_device cuda --dtype auto --atype float32 --max_batch 1
```

这里 `atype=float32` 是模型前端接受的设置；主干内部仍按原模型使用 BF16 激活及相应量化边界。`dtype=auto` 保留带缩放的 FP8/FP4 源权重。

其他已预热的 batch、CPU Engram 或混合专家模式使用可捕获的分段图，其动态操作保留在图外。普通多 token prefill、按层分卡及带同步 profiling/dump 的路径使用 eager。工作区由模型持有，不能并发取得锁时走 eager；设备、精度或权重布局变化后重新校验，捕获失败时各 TP rank 一致回退。按层部署应保持 Graph 关闭，避免 CUDA embedding 放置影响跨卡访问。

排查参数包括 `FASTLLM_DSV41_CUDA_GRAPH_WARMUP`（默认 2）和 `FASTLLM_DSV41_CUDA_GRAPH_DEBUG`。故障注入和回放掩码仅供回归测试使用，不应作为部署调优配置。

## 按层切分

```bash
FASTLLM_CUDA_GRAPH=0 ftllm server /path/to/DeepSeek-V4.1-Flash \
  --device "{'cuda:0':1,'cuda:1':1}" --moe_device numa
```

用普通的 device map 就能把层平均分到两张卡上（`SelectDeviceFromMap` 按权重划分层区间），
V4.1 的 `ForwardSegments` 每层都会 `ApplyDeviceMap`，隐藏状态在 stage 边界由执行器自动搬运。
每卡只保存自己那一半的层权重，**每卡显存约减半**；单请求延迟基本不变（两张卡是串行执行的，
不是真正的流水线——调度器没有 micro-batch，所以并发请求也不会形成 stage 级重叠）。

### 切分点必须落在 kv source 层的边界上

V4.1 的压缩 KV 是跨层共享的：`kv_source_layer_ids` 的层算出的 cache 会被之后若干层直接读。
如果 source 层和消费层落在不同卡上，执行器会在每次访问时把整段压缩 KV 搬到另一张卡，
下一步 source 层追加时再搬回来——每个 token 都要来回搬整段缓存。

真实模型（40 层）的 source 布局正好让**均分**成为合法切分点：

| source 层 | 服务的层 |
| --- | --- |
| 2 | 2–7 |
| 8 | 8–13 |
| 14 | 14–19 |
| 20 | 20–39（同时是 `candidate_source_layer_id`，候选块被 24/28/32/36 复用） |

`{'cuda:0':1,'cuda:1':1}` 得到的切分点正好是 20，stage 0 = 0–19、stage 1 = 20–39，
每个 (source, 消费者) 对都在同一张卡上，Engram 层 [1, 14] 也都在 stage 0。
**不要为了平衡显存把切分点挪到 21**（`head.weight` 1.3 GB 在 stage 1，会让 stage 1 略重）：
挪一层就会把 source 20 和它的 19 个消费层拆到两张卡上。

### 与张量并行的取舍

| | 张量并行 | 按层切分 |
| --- | --- | --- |
| 权重分布 | 注意力与 head 分片，共享专家复制；路由专家使用配置的 EP/混合后端 | 整层归属一张卡 |
| 计算方式 | 同一层由多卡同时计算，需要集合通信 | 单请求依次经过各卡上的层 |
| Decode 调度 | 支持常驻 worker 和符合条件的整图 | stage 边界传递隐藏状态 |
| 约束 | head 均分，每个分片包含完整的 o_group | 切分点必须落在 kv source 边界 |

两种部署方式不能直接以旧的逐算子调度耗时比较。实际速度取决于卡间互联、
GPU/CPU 专家布局、Engram 放置和 CUDA Graph 是否生效，应在相同负载下测量。

## prefill 的多卡专家流（`FT_MOE_ASSIST_DEVICES`）

`--moe_device numa` 下，一个 prefill 分块要把**全部**路由专家的权重过一遍，专家权重是从主机内存
流式送到 GPU 的，不需要张量并行分片。所以瓶颈是「主机内存 -> GPU」这条链路的带宽，而不是算力：
多一张卡就多一条链路。但 `GetNumasMoeCudaAssistDevices()` 原本只返回承载稠密层的那张卡，
模型放得下单卡的机器上第二张卡在 prefill 期间完全闲置。

```bash
FT_MOE_ASSIST_DEVICES=0,1 ftllm server ... --device cuda --moe_device numa
```

把额外的 CUDA 设备加进专家流。默认为空，行为不变。每张卡拿到一份输入激活的副本、一组不相交的
专家，各自算出一份 partial，最后在 root 卡（产出这一层激活的那张）上相加。

### 输入搬运与归约重叠

输入副本由对应 GPU worker 在自己的 stream 搬运，优先使用 peer copy；不可用时等待 D2H 事件后经 pinned host staging 上传。
各 GPU 的 partial 同样在 worker 内搬到 root 侧独立缓冲，与 CPU 和 root GPU 的专家计算重叠。root stream 等待事件后按顺序相加，完成同步后才释放 partial。

输入搬运与 partial 归约始终使用重叠路径，无需设置环境变量。
没有 peer 通路时自动使用 pinned host 中转。下面两项调度策略仍默认关闭。

| 变量 | 作用 |
| --- | --- |
| `FT_MOE_ASSIST_BALANCE=1` | 按各卡实测的「每专家毫秒」分配 GPU 专家，而不是按 route 数均分 |
| `FT_EXPERT_LIMIT_AUTO=1` | 用真实层反馈出的 CPU / GPU 速度算 expertLimit，取代单专家合成 benchmark |

事件、归约缓冲、pinned 中转缓冲都按设备缓存在每层的 MoE manager 上，跨层复用。
设置 `FASTLLM_PROFILE_NUMAS_MOE=1 FASTLLM_PROFILE_DETAIL=1` 可统一查看每层的
CPU/GPU 专家划分、各卡 route 数，以及 stage / limit / prep / cpu / join / reduce 耗时。

### expertLimit 的选择

`expertLimit` 是「一个专家至少要有多少 route 才值得送上 GPU」的阈值：低于它的专家留在 CPU 上算，
和 GPU 并行，理想情况下两边同时结束。默认由 `MoeExpertSpeedEstimator` 在第一次 prefill 时跑一个
合成 benchmark 推出来——**每轮只跑一个专家、每轮 sync**。这样量到的 GPU 每专家耗时里含着
无法摊掉的固定开销（workspace 准备、stream 创建、启动延迟），也拿不到真实层里跨专家的
「copy(i+1) 与 compute(i) 重叠」，会系统性高估 GPU，把过多 route 留在 CPU 上。

`FT_EXPERT_LIMIT_AUTO=1` 改成用真实层反馈的两个系数直接算 makespan 最优点：

- 每张卡的「每专家毫秒」= worker 线程墙钟 ÷ 分到的专家数（EMA）。因为是两张卡并发跑时量的，
  跨专家流水重叠、两卡争抢主存带宽的影响都已经算进去。
- CPU 的「每 route 毫秒」= CPU 专家段墙钟 ÷ 落在 CPU 上的 route 数（EMA）。

然后枚举阈值 t，用与实际分配一致的贪心把 GPU 专家摊到各卡上，取 `max(cpuMs, gpuMs)` 最小的 t。
样本不足（前几层）时先将 route 最少的两个专家分给 CPU，其余交给 GPU，以收集两侧耗时。
`FT_EXPERT_LIMIT=<n>` 的显式覆盖优先级最高，
两种自动估计都不会执行。

动态 prefill 分配可能改变专家归约和舍入路径。数值比较应使用相同输入及生成历史，结合 logits
误差与任务结果判断；不要仅为逐字节复现而关闭动态分配。生成历史分歧之后的 logits 不能直接比较。

## KV 缓存存储精度（`--kv_cache_dtype`）

长期 KV 缓存只有两部分：**压缩 KV**（`kv_source_layer_ids` 各层，每 `compress_ratio` 个 token 一行，
`head_dim` = 512）与 **indexer key**（同样的层，`index_head_dim` = 128）。滑窗缓存长度固定为
`sliding_window`，不随上下文增长，不计入每 token 开销。

写进缓存之前，这两者都已经过与官方一致的伪量化（`DeepSeekV41RotaryQuant` 的 `quantMode`）：

| 张量 | 伪量化网格 | 分组 | scale 编码 |
| --- | --- | --- | --- |
| 压缩 KV | FP4 E2M1 | 每 16 个通道 | E4M3 |
| indexer key | FP4 E2M1 | 每 32 个通道 | UE8M0（2 的幂） |
| 滑窗 KV | FP8 E4M3 | 每 32 个通道 | UE8M0 |

也就是说值本来就落在 FP4 / FP8 网格上，**按 FP4 存储是无损的**。三档存储的行布局与开销：

| `--kv_cache_dtype` | 压缩 KV 行 | indexer key 行 | 每 token | 相对 BF16 | 精度 |
| --- | --- | --- | --- | --- | --- |
| 默认（BF16） | 1024 B | 256 B | **3200 B** | 1.00x | 基准 |
| `fp8_e4m3` | 528 B（512 E4M3 + 16 UE8M0） | 132 B | **1650 B** | 0.52x | 压缩 KV 有不超过 2^-4 的相对舍入 |
| `fp4_e2m1` | 288 B（256 打包 E2M1 + 32 E4M3） | 68 B（64 打包 E2M1 + 4 UE8M0） | **890 B** | 0.28x | **逐 bit 无损** |

890 B/token 与官方实现一致。每 token 2.5 行压缩 KV（层 2/8/14 的 `compress_ratio` 是 2，层 20 是 1）：
`2.5 x 288 + 2.5 x 68 = 890`。

- **FP4 无损的原因**：`DeepSeekV41QuantizeKV` 的块 scale 推导（`DeepSeekV41BlockScale`）与伪量化
  (`DeepSeekV41FakeQuantRow`) 是同一份代码，分组大小与 scale 编码也完全对齐，所以对已经伪量化过的行
  是幂等的；而 `q * scale`（q 至多 3 个有效位、scale 是 E4M3 或 2 的幂）在 BF16 上也是精确的。
  实测：开启伪量化时 FP4 存储与 BF16 存储的 logits **逐 bit 相同**（`test/basic/deepseek_v41_kv_cache_dtype.py`）。
- **FP8 为什么有损**：压缩 KV 的网格是「FP4 值 x E4M3 scale」，而 FP8 存储用的是 2 的幂块 scale，
  两者的网格对不上，会引入一次真实的舍入。它的用途是在不支持 FP4 打包读取的场合省一半显存。
- 滑窗 KV 在 `fp8_e4m3` 与 `fp4_e2m1` 两档下**都按 FP8 存储**（它本来就在 FP8 网格上，改 FP4 会真的损失精度），
  三种行布局可以在同一次前向里混用，读路径按行宽自动识别。
- 缓存行统一放在 `INT8` 的 `Data` 里，形状 `[b, rows, rowBytes]`；FP4 打包为一个字节两个 code，
  低 4 位是偶数下标。CUDA 侧在把候选装进共享内存时就地解成 BF16 片段，再进 `mma.sync`，
  `FASTLLM_DSV41_LEGACY_ATTN` / `FASTLLM_DSV41_LEGACY_INDEXER` 的标量回退路径同样支持。

### 怎么选

- **长上下文 / 高并发**：用 `fp4_e2m1`。同样显存能装的上下文约为 BF16 的 3.6 倍，且数值与 BF16 完全一致，
  没有任何精度代价；长上下文下稀疏注意力每步读取的字节数同比下降，decode 也有正收益。
- **默认（BF16）**：短上下文、显存不紧张时省掉打包/解包的一点开销。
- `fp8_e4m3`：介于两者之间，但既比 FP4 大又比 FP4 差，现在没有明显适用场景，保留是为了兼容既有部署。

## 多请求与前缀缓存

### 批量 decode

`DeepSeekV41Model::ForwardSegments` 把若干"片段"（请求状态 + 起始位置 + token 数）拼成一个 token 流：
embedding、Hyper-Connections、各 Linear、路由、MoE 与 Engram 查表按整批执行，RoPE、压缩 KV 追加、
indexer top-k、稀疏注意力与滑窗写入按片段分别执行。通用调度器的多请求 `ForwardBatch` 直接走这条路径，
单请求 `Forward` 是它的单片段特例；含长 prefill 片段的混合批次退回为逐个前向。

每个请求的缓存（`DeepSeekV41RequestState`）与 `ResponseContext` 绑定，请求结束或 abort 时释放。
在迷你模型上，8 并发 decode 与逐个请求相比，token 序列的差异仅来自 GEMM 按 batch 选核带来的
BF16 舍入（同一请求换不同的批次伙伴 / 批内位置，logits 逐 bit 一致）；单卡 3090Ti 上迷你模型的
decode 吞吐从 209 tok/s（1 并发）提高到 573 tok/s（8 并发）。

### 前缀缓存

OpenAI 接口在模型实例内保存工具轮次的原始输出（含思考和 DSML，支持流式）。内存使用 LRU（默认 128 条、8 MiB）；
被内存淘汰的条目会写入模型生命周期内的私有临时 SQLite，因此仍可恢复原文，不会因工具历史过长而重新编码旧 DSML、破坏 token 前缀。
正常释放模型时临时存储会清理；进程被强制终止时不保证执行清理。续轮的调用 ID、顺序、函数名、参数值和正文匹配时恢复原文，避免 DSML 拼写、JSON 格式等变化破坏 token 前缀。
匹配忽略 JSON 空格、对象键顺序和纯空白正文；客户端可省略思考内容，但修改思考、切换思考模式或
模板要求丢弃思考时不复用。不接受客户端原始模板覆盖；未命中仍按普通模板编码和 prefill。

启动时加 `--cache_history true`。请求结束时把每层 `windowKV`、`compressedKV`、`indexK`、`rawTail`
与 Engram 历史快照到 CPU 内存（LRU，默认保留 8 条），新请求按最长公共前缀查找并恢复，只对新增 token
做 prefill。多轮对话中只要客户端原样回传上一轮的回复，通常就是精确命中。
图文快照还会绑定图像 patch 的哈希、尺寸和位置：同一张图可以复用对应前缀，换图会在图像 span 前停止匹配，追加图像只复用此前已经确认的部分；不会把相同的占位 token 误当成相同图片。
图像 prefill 保持原始 embedding 和 mask，完成后续文本 decode 仍可使用 DSpark；图像位置本身不生成草稿候选。

DSML 块前的两个模板分隔换行不作为 assistant 正文返回，避免续轮重复添加。
流式解析暂存这两个换行；普通正文末尾的换行在流结束时照常输出。

恢复长度受模型结构约束：滑窗缓存是只保留最后 `window_size` 个位置的环形缓冲，因此只能恢复到记录
长度 T 或 T-1（记录不超过 `window_size` 时可以任意截断）；ratio-2 压缩层凑不满一组的原始尾块只在
精确命中时可用，其它情况要求恢复长度为偶数。不满足约束的候选会被跳过（退回完整 prefill）。

| 变量 | 作用 |
| --- | --- |
| `FASTLLM_DSV41_DISABLE_PREFIX_CACHE` | 关闭前缀缓存 |
| `FASTLLM_DSV41_PREFIX_CACHE_DEBUG` | 打印命中 / 记录日志 |
| `FASTLLM_DSV41_PREFIX_CACHE_MIN_TOKENS` | 最短命中长度（默认 16） |
| `FASTLLM_DSV41_PREFIX_CACHE_MAX_RECORDS` | 最多保留的记录数（默认 8，也可用 `FASTLLM_PREFIX_CACHE_SNAPSHOT_MAX_RECORDS`） |

迷你模型上精确命中与 T-1 截断命中后的贪心输出与不中断的原请求逐 bit 一致；800 token 提示词的
首 token 延迟从 39–54 ms 降到 6 ms。

## DSpark 投机解码

`config.json` 的 `text_config` 里 `dspark_block_size > 0` 且 checkpoint 带 `mtp.*` 权重时可以开启。
启动加 `--speculative_algorithm dspark --dspark 5`。`--dspark N` 指每轮最多校验 N 个候选，
运行时草稿长度取 `max(dspark_block_size, N)`：小于训练块时只校验前缀，
大于训练块时扩展整个草稿前向，包含双向注意力和 Markov 链。
本 checkpoint 训练块为 5，已验证 `--dspark 7`；全部接受时一轮最多输出 8 个 token。
更长草稿的速度取决于接受率与校验成本，训练配置本身不需要修改。

```bash
ftllm server /path/to/DeepSeek-V4.1-Flash \
  --device cuda --moe_device numa --dtype float16 \
  --speculative_algorithm dspark --dspark 5
```

`ftllm` 的自动配置（launcher）在 `enable_speculative_decoding` 时会识别 V4.1 的内置 DSpark，
按 checkpoint 的训练 block size 填 `--draft_tokens`。不加 `--dspark` / `--draft_tokens` 时
不加载 `mtp.*`。原始量化 checkpoint 的草稿原始张量（含 scale）约 7.39 GiB；
实际占用还受加载 dtype、权重转换和运行缓冲影响。

### 结构

`mtp.0/1/2` 是三个与主干同构的草稿层：`compress_ratios` 在这三层是 0（纯滑窗注意力），
MoE 是 `dspark_n_routed_experts` = 128 专家 top-3，embedding 与 lm_head 与主干共享。
另外 `mtp.0` 有 `main_proj` / `main_norm`，`mtp.2` 有 `norm`、`markov_head`（`embed` + `head`，
秩 256）与 `confidence_head`（输入 `dim + 256`）。

草稿层的滑窗 KV 不来自草稿 token 自身，而来自目标模型：`dspark_target_layer_ids`（[37, 38, 39]）
各层 attention 的输入（对 4 份 Hyper-Connections 取均值）拼成 `3 * dim` 后经 `main_proj` / `main_norm`
得到 `main_x`，每个草稿层再用自己的 `wkv` / `kv_norm` 把它写进一行滑窗 KV。也就是说每个已提交
位置在草稿侧只有一行 KV，草稿模型不需要重跑主干。

一次 proposal：把 `block_size` 个位置的输入 token 置为 `dspark_noise_token_id`（第 0 个位置放锚点
token，即目标模型刚产出、还没进 KV 缓存的那个 token），一次前向产出 `block_size` 组 logits；
再用 markov head 逐位置做 bigram 修正，得到 `block_size` 个候选 token；
`confidence_head` 对每个位置给出一个 sigmoid 后的置信度。

贪心请求取 argmax；采样请求在 GPU 保存完整草稿分布 q，通过共用的 MTP 采样与拒绝采样内核校验，
不再限制草稿支持集为 64 项。Markov 修正使用上一步实际抽到的 token；草稿与目标均使用请求的
`temperature`、`top_k`、`top_p`，无工具掩码时与普通 CUDA 采样一致。
工具名、参数名约束按位置更新：在允许集合内取 top-k 并重新归一化后取 top-p，草稿、校验和 bonus
使用各自前缀；拒绝和待发队列不会错误推进约束状态。强制候选测试使用 one-hot q，无需额外开关。

### 校验与回滚

候选与锚点拼成一个 `1 + N` 长度的片段一次喂给目标模型（`ForwardSegments` 天然支持一次多 token），
贪心请求逐位置比较 argmax，第一个不匹配处截断。采样请求复用 `qwen3_5.cpp` 的链式拒绝采样：
实际草稿概率为 q，目标条件概率为 p，以 `min(1, p(token)/q(token))` 接受候选；首次拒绝时从
归一化的 `max(p-q, 0)` 抽取替代 token，全部接受时从额外目标行抽取 bonus token。
接受 n 个候选时这一轮提交 n + 1 个 token、产出 n + 1 个输出
token：第一个立刻返回，其余进入请求的待发队列，调度器之后每轮直接出队，不再前向。

校验前向按完整 block 更新缓存，接受长度确定后要把多算的部分退回：

- 滑窗环形缓冲：写入**延后**到接受长度确定之后。片段内的位置在稀疏注意力里一律从 `chunkKV` 读取，
  推迟写入不改变本次前向的任何结果，因此也不需要为回滚保存被覆盖的旧行；
- 压缩 KV 与 indexer key：按接受后的长度重新截断行数，并用保存下来的 compressor 原始输入流
  （旧 `rawTail` + 本次新行）重建 `rawTail`；
- Engram 历史与各层 `totalLen`：截断到接受后的长度。

拒绝采样在目标条件概率相同的前提下保持目标输出分布，不要求草稿分布等于目标分布。
这不代表开启与关闭 DSpark 的原始 logits 逐 bit 一样，也不保证相同随机种子给出相同序列。
一次多 token 与逐 token 前向会选择不同的 GEMM / MoE 路径，浮点舍入可能改变 logits，
也可能翻转接近并列的 argmax；分布测试与浮点前向误差需要分别验证。

### 限制

- 支持简单贪心及 CUDA 上的 temperature / top-k / top-p 采样，也支持默认工具名、参数名约束。
  重复惩罚、工具内容采样、没有前缀状态的独立 token 白名单、`output_logits`、正的
  `output_token_least`、非有限采样参数会退回普通解码；CPU 采样也走普通路径。
  与普通解码一样，`do_sample=true`、正温度且 `top_k<=1` 时将 top-k 规范化为 5；
- 只在**单请求**前向里产生候选。批量 decode 的那一轮不投机，但仍然采集 main hidden，
  让草稿滑窗跟上目标缓存；已经校验通过的 token 在批量路径里也能正常出队；
- 图文请求不投机；
- 前缀缓存命中恢复出来的前缀没有草稿侧的滑窗（`main_x` 无法从目标缓存反推），
  草稿注意力只看得到恢复之后新增的位置，接受率会在最初的 `window_size` 个 token 内偏低；
- 校验提交在首个 EOS / stop token 处截断，避免结束时缓存超出真实输出。
  请求若因长度上限在待发队列还没取完时结束，这一轮多算的 token 仍可能让缓存长度超过
  `allTokens`，该请求的前缀缓存记录会被跳过；
- 支持与主干张量并行组合（`--tp 2`），草稿层仍在第一张卡运行；目标校验支持分段 CUDA Graph，草稿层仍逐算子执行。

### 调试环境变量

| 变量 | 作用 |
| --- | --- |
| `FASTLLM_DSPARK_TOKENS` | 每轮校验的候选数（由 `--dspark` / `--draft_tokens` 设置，不要直接设） |
| `FASTLLM_DSPARK_CONFIDENCE_THRESHOLD` | 置信度低于该值的候选之后不再校验；0 表示总是用满 block |
| `FASTLLM_DSPARK_STATS` | 默认 `1`，打印总体及逐位置接受率；`0` 关闭，`2` 打印详细分段耗时与逐轮记录 |
| `FASTLLM_DSPARK_STATS_EVERY` | 每累计 N 轮校验打印一次（默认 32，0 表示只在退出时打印） |
| `FASTLLM_DSPARK_STATS_WARMUP` | 统计前跳过的轮数（默认 3）。第一次 decode 含 CUDA context / 显存池 / 权重量化缓存的一次性开销，会把均值拉偏 |
| `FASTLLM_DSPARK_DISABLE_FUSED_MARKOV` | 关掉 markov head 的融合 kernel，退回通用算子（对拍 / 排查用；两条路径输出逐 bit 一致） |
| `FASTLLM_DSPARK_PROBE_EVERY` | 每 N 轮故意不带候选走一次普通单 token 前向，给"校验 N 个候选"提供同等缓存状态下的对照基线。默认 0（关闭），只在诊断时打开 |
| `FASTLLM_DSPARK_STATS_FILE` | 每轮校验追加一行 `轮次 候选数 接受数`（测试用） |
| `FASTLLM_DSPARK_FORCE_DRAFTS` | 用文件里的候选替换模型的候选，构造指定的接受长度（测试用） |
| `FASTLLM_DSV41_DEBUG_STATE` | 每次前向后导出各层缓存长度（对比回滚是否正确） |

## 图像输入

`config.json` 顶层的 `vision_n_layers > 0` 时加载视觉编码器：32 层 ViT（1024 维、16 头、patch 14、2D RoPE、SwiGLU MLP）
+ aligner（3x3 下采样后 `w1 -> GELU -> w2` 投影到 5120 维）+ 三个学习到的分隔符嵌入。权重保持 float16（不参与低比特量化），
以 float32 激活在执行器所在设备上计算，长序列注意力按 1024 个 query 分块。

处理流程（与官方 `image_processor.py` / `model.py` 一致）：

1. Python 侧（`deepseek_v41_multimodal.py`）：`encode_messages(..., return_multi_modal_data=True)` 渲染带
   `<｜deepseek_image｜>` 占位符的 prompt；每张图按动态分辨率规则缩放 / 灰色补边（`vision_min_pixels` 295936、
   `vision_max_n_token` 1024），切成 `n_vit_h x n_vit_w` 个 patch，占位符展开为
   `[image_start] + ([IMAGE] * n_llm_w + [image_newline]) * n_llm_h + [image_end]`（每个位置都是 `image_token_id`），
   patch 与每张图的 `(起始位置, n_vit_h, n_vit_w)` 作为 payload 传给 C++；
2. C++ 侧：请求创建时把多模态输入记到请求状态（`OnResponseContextCreated` / `ForwardMultimodal`），
   第一个 prefill 块对每张图做 ViT + aligner，把结果与分隔符嵌入组成 span 嵌入缓存起来；之后每个 prefill 块
   （无论由调度器还是模型自己按 `chunked_prefill_size` 切分，span 可以跨块）把与 span 重叠的位置换成图像嵌入，
   图像 span 内的 token 路由使用 `gate.bias_vl`、Engram 对其不做查表也不参与 n-gram；decode 步骤与纯文本相同。

用法：与其它多模态模型相同，OpenAI 接口的 `image_url` 支持 http(s)、`data:image/...;base64` 与 `file://`：

```bash
curl http://127.0.0.1:8080/v1/chat/completions -H "Content-Type: application/json" -d '{
  "model": "v41", "messages": [{"role": "user", "content": [
    {"type": "text", "text": "描述这张图片"},
    {"type": "image_url", "image_url": {"url": "data:image/png;base64,...."}}]}]}'
```

限制：

- 只支持图像，不支持视频；一张图最多 1024 个 token（约 9216 个 ViT patch），多张图按出现顺序对应；
- 带图请求不与其它请求合并 prefill；图像 span 必须落在 prompt 内（Python 侧展开时保证）；
- 前缀缓存 / 多轮复用尚未实现，每个请求都会重新编码图像；
- 21 环境下服务模式若加载不到 HF tokenizer，会退回 fastllm 原生 tokenizer 编码 prompt（两者对占位符的 id 相同）。


## 数值验证

参考精度模式及验收边界见[精度说明](deepseek_v41_precision.md)。同一数学模式的回归、官方参考对齐和生成质量属于不同检查，不能相互替代。

### CPU-only 固定 fixture 回归（无需 torch）

`test/basic/deepseek_v41_reference.py` 需要 torch、transformers 与官方 `inference/` 代码，进不了 CI。
`test/basic/test_deepseek_v41_cpu_fixture.py` 把「一个微型 V4.1 checkpoint + 官方实现算出的参考 logits」
固化在 `test/basic/deepseek_v41_fixture.npz`（约 2.2 MB）里，只依赖 numpy 与 fastllm 的 Python 包：

```bash
PYTHONPATH=build/tools python test/basic/test_deepseek_v41_cpu_fixture.py
```

追加 `--ngram-device disk` 可用同一份 fixture 校验磁盘 Engram 加载与按行读取。

退出码 0 表示通过，1 表示失败；fixture 缺失时打印重新生成的命令并以 0 退出（跳过）。
fixture 里的模型是 5 层、dim 128、词表 128、2 专家，覆盖 V4.1 的全部结构特性：
`compress_ratios = (0, 2, 2, 1, 1)`（三种压缩层）、`kv_source_layer_ids = (1, 3)`（层 2 / 4 跨层复用压缩 KV）、
`index_source_layer_ids = (1, 3, 4)` 且 `candidate_source_layer_id = 3`（两级 top-k）、
`engram_layer_ids = (1, 4)`、`hc_mult = 2`、`window_size = 8`（48 token 的 prefill 让滑窗环形缓冲绕多圈）。

比较方式：逐步（prefill + 4 步 decode）比较 logits 的余弦相似度（>= 0.9975）与
`max|diff|` 占 logits 值域的比例（<= 4%），并要求贪心 token 与参考一致；
参考侧是官方实现的 bf16 前向，噪声底约为值域的 2–3%（fastllm 自己的 CPU 与 CUDA 路径之间也有约 1.5%），
所以容差按值域折算而不是给绝对值。fixture 生成时会在多个提示词种子里挑一个每步 top1/top2 间距都
远大于该噪声的，避免 argmax 因舍入翻转；万一仍然翻转，测试会按"参考侧两者间距落在容差内"判为并列而不算失败。

重新生成 fixture（需要 torch + 官方 inference 代码 + 一张 GPU）：

```bash
PYTHONPATH=build/tools python test/basic/deepseek_v41_fixture_gen.py \
  --reference-dir /path/to/DeepSeek-V4.1-Flash/inference \
  --work-dir /tmp/v41-micro --out test/basic/deepseek_v41_fixture.npz
```

生成脚本自带一个 128 词的迷你 tokenizer，不依赖真实 checkpoint 的 tokenizer.json。

### 算子级 CPU / CUDA 一致性

`test/ops/deepseekV41OpsRegression.cpp`（`cmake -DUNIT_TEST=ON`）用同一份输入分别在 CPU 与 CUDA 上跑
11 个 V4.1 专用算子并比较输出，覆盖随机输入与边界情况：

```bash
./build/deepseekV41OpsRegression            # 全部算子
./build/deepseekV41OpsRegression --list     # 列出用例
./build/deepseekV41OpsRegression IndexerTopK CandidateBlocks   # 只跑指定算子
ctest -R deepseekV41Ops                     # 无 CUDA 设备时以 77 跳过
```

## Decode 算子与兼容性

单 token indexer 使用 head 作为 MMA 矩阵行。注意力将输出维度分成四份；候选较多且临时空间
充足时，先并行计算 QK，再保持原有的 64 槽在线 softmax、BF16 概率舍入和 PV 顺序。
BF16 5120 维、1–8 行 RMSNorm 与四路 HC Finish 使用保持归约顺序的专用内核。

MMA 路径检查设备及实际加载内核的架构、线程数和共享内存限制；不满足时使用原 FP32 标量路径。
非 decode 形状使用通用 MMA，注意力临时内存不足时退回无需该空间的融合 MMA；执行错误正常上报。
RMSNorm 其他类型、维度和批量保留原路径，不满足向量读取对齐时使用标量读取。
CUDA 构建仍需包含目标显卡支持的代码镜像，并使用支持该架构的工具链。

其他加速路径也保留回退：

- BF16×FP8 block-32 Marlin 要求 SM80+、匹配的尺寸和可无损表示的 E8M0 scale；否则保留原始行优先权重，使用通用 GEMV/cuBLAS 路径。
- FP8 GEMV 的单行循环展开仅针对 SM90，其余架构使用原循环。
- MoE 的 V4.1 量化、路由权重和 FP32 求和边界独立于加速后端；不支持 Marlin 时使用普通 CUDA 路径。不能捕获的路径保持 eager。
- GPU Engram 要求各 rank 可互访且显存充足，否则保留 CPU 查表；不改变原始表的数值格式。
- 整图不支持的形状、KV 类型、执行模式或设备退回分段图/eager。启动后失效的普通 decode 图不在请求内重新捕获。

压缩 Attention 保留原有 PV 累加顺序；未保留改变 FP32 结合顺序的候选分块优化。整图回归对普通和压力输入均要求与 eager 结果逐位一致。

```bash
./build/deepseekV41OpsRegression
ctest --test-dir build -R 'deepseekV41(Precision|Tp|DecodeOptimization|FullDecode)' --output-on-failure
python test/basic/test_deepseek_v41_tp_graph.py --binary ./build/deepseekV41TpGraphRegression
```

Graph fixture 需要 PyTorch、safetensors、numpy 和两张 CUDA GPU；覆盖不同长度的连续请求、
启动预热后的 eager / Graph 切换和共享专家重叠，以及 DSpark 的 1～8 token 图重放、目标层特征与部分接受后的 KV 回滚。
`--hc-mult 2` 可补测两路 HC，默认四路。
算子回归检查通用路径与 decode 对齐、独立 CPU RMSNorm 参考、量化 KV、远端 logits 同步、
缓冲复用、并发 Graph 及捕获期间临时空间不足时的回退。

## 调试环境变量

| 变量 | 作用 |
| --- | --- |
| `FASTLLM_DSV41_LEGACY_ATTN` | 稀疏注意力退回 FP32 标量 kernel（对比 / 排查用） |
| `FASTLLM_DSV41_LEGACY_INDEXER` | indexer 打分退回 FP32 标量 kernel |
| `FASTLLM_DSV41_LEGACY_HCMIX` | HC 混合系数退回旧 kernel |
| `FASTLLM_DSV41_LEGACY_FP4_UNPACK` | 量化 KV 缓存行退回逐元素解包（不再按 4 字节一组批量展开） |
| `FASTLLM_DSV41_LEGACY_TOPK` | indexer top-k 退回旧的「全 visible 扫描 + 8 轮 4-bit radix」kernel |
| `FASTLLM_DSV41_LEGACY_ROTARY` | 旋转 / 伪量化退回旧的「一行一个 block + 共享内存」kernel |
| `FASTLLM_DSV41_DISABLE_HCPRENORM` | 不融合 HcApplyPre 与 RMSNorm，退回两个算子分开做 |
| `FASTLLM_DSV41_INDEX_SCORE_MB` | indexer 分数矩阵的显存预算（MB，默认 128），决定 token 维分块大小 |
| `FASTLLM_DSV41_INDEX_CHUNK` | 直接指定 indexer 的 token 分块大小（覆盖上面的预算推算） |
| `FASTLLM_DSV41_ENGRAM_PROFILE` | Engram 查表 / 转换 / 投影分段计时（见 "Engram 元数据"） |
| `FASTLLM_DSV41_ENGRAM_POOL` | Engram 查表改用常驻线程池 |
| `FASTLLM_DSV41_ENGRAM_PREFETCH` | 跨层预取下一个 Engram 层的行号与表行 |
| `FASTLLM_DSV41_ENGRAM_MADVISE` | Engram 表的内存访问提示（random / hugepage / both） |
| `FASTLLM_DSV41_ENGRAM_WKV_FP8` | `engram.wkv` 保留 checkpoint 里的 FP8 精度，不解量化 |
| `FASTLLM_DSV41_DISABLE_FAKE_QUANT` | 关闭 FP8 / FP4 伪量化（仅用于对齐调试） |
| `FASTLLM_DSV41_DISABLE_CUDA_ROUTE` | 路由退回 CPU 参考实现 |
| `FASTLLM_DSV41_DUMP_DIR` | 把每层中间张量写到该目录（对齐调试） |
| `FASTLLM_DSV41_DISABLE_TP_ATTENTION` | 张量并行时不切分注意力 head（排查用，注意力改为每卡各算一份） |
| `FASTLLM_CUDA_GRAPH` 等 | decode 与 DSpark 校验的 CUDA Graph，见对应章节 |
| `FASTLLM_TRACE_OPS` | 逐算子打印"算子名 / 落在哪个设备 / 权重名"（排查 TP 落点用） |
| `FASTLLM_DSV41_DISABLE_PREFIX_CACHE` 等 | 前缀缓存相关，见"多请求与前缀缓存" |
| `FASTLLM_DSPARK_*` | DSpark 投机解码相关，见"DSpark 投机解码" |
| `FT_MOE_ASSIST_DEVICES` / `FT_MOE_ASSIST_BALANCE` / `FT_EXPERT_LIMIT_AUTO` | NUMA MoE 的多卡专家流，见"prefill 的多卡专家流" |
| `FASTLLM_PROFILE_NUMAS_MOE=1 FASTLLM_PROFILE_DETAIL=1` | NUMA MoE 分阶段耗时、CPU/GPU 专家划分及各卡 route 数 |

## DSpark 与专家缓存验证

CUDA + NUMA 混合专家缓存会自动处理 2–8 行 verify，包含 TP + CUDA Graph 路径，配置和命中率口径见
[CUDA 专家缓存](cuda-expert-cache.md#deepseek-v41)。HC mix 支持 1–8 行小批量；
WoA 对小于 16 行的批次复用权重，保持各输出的累加顺序，其他形状走已有路径。
草稿的稠密层、路由专家、共享专家与 HC post 复用主干的量化和 BF16 舍入语义。

`deepseekV41SamplingRegression` 检查实际 CUDA 拒绝采样器的输出分布、部分拒绝、
bonus token 和缓存回滚；`deepseekV41OpsRegression` 检查小批量算子与原归约路径；
`cuda_dsv41_moe_cache_test --dual` 检查独立数值参考和跨卡缓存切换。
`test/basic/test_deepseek_v41_tp_graph.py --binary build-fastllm/deepseekV41TpGraphRegression --expert-cache`
使用 NVFP4 专家检查 TP verify 缓存、1–6 行 Graph 重放、主模型特征与 KV 回滚。
编程模板在 top-p 截断后可能只剩一个候选，因此高接受率本身不能证明使用了贪心验证；
评估时应记录采样参数、拒绝轮数和代码功能结果，分别检查采样校正与前向浮点误差。

### Decode 投影与准备操作

以下优化自动按输入条件选择，无需新增环境变量；不符合条件时继续使用原路径：

- **mHC finish 与 Norm 合并下发**：BF16、HC=4、少于 16 个 token、偶数隐藏维度 4096–8192。
  当前子层的 Sinkhorn 与使用上一子层 pre 系数的 Norm 在独立 CTA 中并行。
  保持原 FP32 归约树、折叠后的 BF16 舍入及 Sinkhorn 顺序。
- **Hopper mHC 点积预取**：SM90、BF16、HC=4、单 token、隐藏维度 4096–8192 时，
  每条原有累加链分组预取 16 轮输入与 FP32 权重，按原顺序累加；尾部保留标量循环，
  不改变 BF16 舍入、FMA 顺序和 warp/block 归约树。其它架构、dtype、尺寸和 batch 使用原点积实现。
- **Full Decode 的旋转准备**：独立的 Q/KV 描述符、输出旋转和窗口写入分别共用一次 kernel 下发。
  位置、量化和窗口更新规则保持不变。
- **Q/KV 投影与准备融合**：单 token、BF16 输入和原始块缩放 FP8 权重时，两个独立投影共用一次下发，
  保留原逐行完整 K 归约。Q/KV 维度为 512–4095 内的 32 倍数时，再合并两个 RMSNorm 与 Q 激活量化。
  RMSNorm 保留模型的 epsilon（包括 0）。Q 先按原规则舍入 BF16，再进行 block-32 FP8 round-trip。
  同时保留未量化 Q；Indexer 仅在原有量化条件满足时复用量化 Q。
  多 token、不支持的权重布局、参考精度模式及中间张量导出使用原路径。
- **Engram WKV 单 token 计算分片**：BF16 输入和原始 FP8 权重、对齐的输出通道、可用 NCCL TP 组。
  每个 rank 只计算自己的输出通道，随后 AllGather BF16 结果。每个点积仍计算完整 K，
  没有跨卡部分和归约。权重继续保留复制布局；这项优化减少计算和读取，不减少权重显存。
  Prefill、多 token、其它 dtype、非对齐尺寸及不支持的权重布局使用原投影路径。
- **Router 准备融合**：不超过 8 个 token、BF16 输入、FP32 权重、至多 1024 个专家时，
  直接在寄存器中转换输入，并融合温度缩放与 sqrt(softplus)。保留原 FP32 点积归约顺序，
  专家 Top-K、偏置、归一化和路由规则不变。
- **O_A 单 token 归约**：各输出行使用独立 warp，保留原 256 线程二叉树求和顺序，
  减少 block 同步。其余 token 数和不适用的分组布局继续走已有实现。

这些路径使用通用 CUDA 实现，不依赖 Hopper 专用指令。CUDA 回归
`deepseekV41DecodeProjections` 检查 mHC、Router 和 O_A 的精确结果及拒绝路径；
`deepseekV41EngramProjection` 检查多卡输出顺序、较大 batch 回退及非法分片边界；
`deepseekV41QKVPreparation` 检查投影、Norm 与量化的逐位结果、Graph 重放和拒绝路径；
`deepseekV41FullDecode` / `deepseekV41FullDecodeStress` 覆盖旋转、KV 生命周期和图重放。
MoE 专家计算与通信调度未在这些优化中调整。

---

### Deepseek V41 Execution Modes (`docs/deepseek_v41_execution_modes.md`)

# DeepSeek V4.1 执行后端

`FASTLLM_EXECUTION_MODE` 是统一的执行模式选择变量，现由 DeepSeek V4.1、Qwen4 与 GLM 5.3 Flash 接入。
完整的共用组件、forward 拆分和模块边界说明见 [执行路径设计](execution_modes.md)。
其他模型保持原有行为。变量不改变设备配置，也不会自动搬迁权重。

| 值 | 选择规则 |
| --- | --- |
| `auto`（默认，也包括未设置或空值） | 按各层最终生效的主计算和路由专家设备自动选择 |
| `gpu` | 主计算和路由专家均配置为 CUDA / multicuda |
| `hybrid` | 主计算和路由专家的设备组合同时包含 GPU 与 CPU / NUMA / 磁盘专家后端 |
| `cpu` | 主计算和路由专家均使用主机后端（CPU / NUMA / disk），保留 CPU 参考路径 |

显式模式与设备配置不符时，初始化报错，避免静默进入另一条路径。支持分层
`--moe_device_layers`、按层设备映射以及 TP 不满足 head 对齐要求时的设备回退。
Engram/embedding 表的存储位置、小路由结果的 CPU 分桶和算子的 dtype fallback
不决定执行模式；`gpu` 表示 GPU 专家后端，并不保证每一个辅助算子都在 GPU 上。

## 启动

保留自动选择，通常无需调整原命令：

```bash
export FASTLLM_EXECUTION_MODE=auto
ftllm server MODEL --device cuda --moe_device numa
# 日志：FASTLLM_EXECUTION_MODE=auto -> hybrid (DeepSeek-V4.1)
```

显式选择纯 GPU 或混合模式：

```bash
FASTLLM_EXECUTION_MODE=gpu ftllm server MODEL --device cuda --moe_device cuda
FASTLLM_EXECUTION_MODE=hybrid ftllm server MODEL --device cuda --moe_device numa
```

多卡纯 GPU 使用现有的 `multicuda:0,1,...` 设备配置，选择规则相同。
`--device cuda --moe_device cuda --ngram_device disk` 仍选择 `gpu`。
`--device cuda --moe_device disk` 则选择 `hybrid`，由磁盘专家后端处理权重读取及
CPU/GPU 执行；这与仅将 Engram 表放在磁盘上的配置不同。
磁盘专家会临时装载和释放权重，目前自动使用 eager 执行，避免 CUDA Graph 的
持久工作区与临时权重生命周期不匹配；即使请求开启 Graph，也会明确记录回退。
仅 Engram 放 disk 不触发该回退，纯 GPU 和 NUMA 专家仍保留原有 Graph 支持。
混合模式的 GPU 专家缓存仍通过 `--moe_cuda_cache` 等参数配置；执行模式与缓存容量、
动态分流策略相互独立。设置 `hybrid` 本身不会开启专家缓存。

Python 启动器为该模型默认设置 `FASTLLM_EXECUTION_MODE=auto`。C++ 初始化在最终设备
回退之后解析并保存模式。不会把进程环境中的 `auto` 改写为推断值，因此
同一进程后续创建不同配置的模型仍能分别选择。修改环境变量不会切换已经加载的模型。

## 代码组织

- `include/utils/execution_mode.h`：与模型无关的模式解析、设备分类和一致性检查。
- `src/models/deepseekv41_gpu.cpp`：GPU 路由专家执行、TP 路由暂存、共享专家合并。
- `src/models/deepseekv41_hybrid.cpp`：CPU/NUMA/磁盘专家、共享专家重叠执行、GPU 专家缓存、
  动态分流、跨 CPU/GPU 数据搬运及输出回传。CPU 参考路径复用此后端的无缓存分支。
- `src/models/deepseekv41_execution.h/.cpp`：共享工作区、MoE 调用上下文和
  TP 感知的张量搬运工具。
- `src/models/deepseekv41.cpp`：公共权重、注意力、KV、路由数学、Graph 分段及采样。
  独立的 GPU / Hybrid forward 通过公共块接口调用这些实现。
- `include/utils/hybrid_moe.h` 与 `src/utils/hybrid_moe.cpp`：接入模型共用的混合 MoE
  步骤生命周期、后端调度与 prefill 预取入口。

两条后端共享底层 CUDA 算子与模型数学，不复制注意力、请求状态和 KV 实现。
DSpark 的主模型验证经过同一后端分派；其草稿模型代码仍在原有独立文件。
新增后端优化时，先在对应后端修改；公共数值或存储修改则放在公共实现。

## 回归检查

```bash
cmake --build build-fastllm --target execution_mode_test deepseekV41TpGraphRegression -j16
build-fastllm/execution_mode_test
python test/basic/test_deepseek_v41_tp_graph.py \
  --binary build-fastllm/deepseekV41TpGraphRegression --gpu-experts
python test/basic/test_deepseek_v41_tp_graph.py \
  --binary build-fastllm/deepseekV41TpGraphRegression
python test/basic/test_deepseek_v41_tp_graph.py \
  --binary build-fastllm/deepseekV41TpGraphRegression --expert-cache --fp8-dense
```

还可运行以下磁盘存储组合，分别验证模式选择、Graph 和 KV 回滚：

```bash
python test/basic/test_deepseek_v41_tp_graph.py \
  --binary build-fastllm/deepseekV41TpGraphRegression --gpu-experts --ngram-device disk
python test/basic/test_deepseek_v41_tp_graph.py \
  --binary build-fastllm/deepseekV41TpGraphRegression --disk-experts
```

TP 测试需要两张 CUDA 卡，覆盖 prefill/decode、Graph 捕获/回放、1–8 个 DSpark
候选的验证、特征与 KV 回滚。`--gpu-experts`、`--disk-experts` 与 `--expert-cache` 三者互斥。

---

### Deepseek V41 Precision (`docs/deepseek_v41_precision.md`)

# DeepSeek V4.1 参考精度模式

`FASTLLM_DSV41_REFERENCE_MATH=1` 用于对齐官方模型的 CPU PyTorch 参考计算。必须在加载模型前设置；默认关闭。

```bash
export FT_NUMAS=1
export FASTLLM_DSV41_REFERENCE_MATH=1
export FASTLLM_DEEPSEEK_V41_BLAS_LIBRARY=/path/to/torch/lib/libtorch_cpu.so

numactl -C 0-31 -m 0 ftllm server /path/to/DeepSeek-V4.1-Flash \
    --device cuda --moe_device cpu --cuda_shared_expert false \
    --threads 30 --dtype float16 --kv_cache_dtype bfloat16 --max_batch 1
```

数学库应与生成参考张量的 CPU PyTorch 环境一致；此模式通过该库的 SLEEF/VML 入口计算 CPU 路由和 SwiGLU。不设置数学库时使用标准库函数，不能据此预期逐值一致。

该模式保留 FP8 共享专家的原始分块 scale，对 FP8/NVFP4 专家逐 block-32 累加。CUDA 侧保留 FP32/BF16 存储边界，并匹配参考的归一化归约、mHC、RoPE、分组投影、64 槽在线 softmax 和 FP32 输出头。CUDA graph 在此模式下关闭。CPU 使用 AVX-512 并行计算独立输出，无 AVX-512 时保留标量回退；CUDA 并行计算注意力槽位、输出通道和归约中的独立分量，保持参考累加顺序。单请求 2–512 token 的 CPU prefill，在专家权重为受支持的 block-32 FP8/NVFP4 时按专家合并成最多 4-token 的计算任务，并按原专家顺序还原每个 token 的输出；其他形状继续走既有路径。HC 的 4×4 Sinkhorn 迭代保存在 warp 寄存器中，单 token 投影合并读取连续 K 值，保持原有 FP32 舍入。

2026-09-12 在 EPYC 9374F、RTX 4090 D、单卡注意力和 CPU MoE 上，对完整 40 层模型执行了 5-token Prefill 和两步固定输入 Decode：120 个层末输出、3 组 logits 均逐值一致。另有 40 层独立 MoE 对比全部一致。这是上述输入和参考环境的验证结果；尚未据此验收长上下文、多请求、多卡、量化 KV cache 或官方 GPU 内核的逐值精度。

`deepseekV41PrecisionRegression` 覆盖激活量化、RoPE 存储边界、64 槽注意力及扩容缓存、mHC FP32 舍入、混合专家和 block-32 累加。普通模式与参考模式应分别运行：

```bash
FT_NUMAS=1 numactl -C 0-31 -m 0 ./deepseekV41PrecisionRegression --cuda
FT_NUMAS=1 FASTLLM_DSV41_REFERENCE_MATH=1 \
    numactl -C 0-31 -m 0 ./deepseekV41PrecisionRegression --cuda
```

2026-09-12 测量，在上述 EPYC 9374F + RTX 4090 D 配置下，使用 30 线程、`FT_NUMAS=1 numactl -C 0-31 -m 0`、单请求和 BF16 KV，实测如下。每组运行 3 轮、每轮 32 步 decode，取中位数；排除模型加载、tokenization 和 HTTP 开销，关闭 dump、前缀缓存及 CUDA graph。

| 输入 token | Prefill token/s | Prefill 秒 | Decode token/s |
| ---: | ---: | ---: | ---: |
| 128 | 22.42 | 5.71 | 8.10 |
| 512 | 20.58 | 24.88 | 7.50 |

## 允许舍入差异时的速度配置

在单 GPU 注意力、CPU 路由专家上，可使用普通数学模式并将共享专家放到 GPU：

```bash
export FT_NUMAS=1
export FASTLLM_DSV41_REFERENCE_MATH=0
export FASTLLM_CUDA_GRAPH=0
export FASTLLM_DSV41_DISABLE_PREFIX_CACHE=1

numactl -C 0-31 -m 0 ftllm server /path/to/DeepSeek-V4.1-Flash \
    --device cuda --moe_device cpu --cuda_shared_expert true \
    --threads 30 --dtype float16 --kv_cache_dtype bfloat16 --max_batch 1
```

GPU 共享专家保留 gate/up 的 BF16 舍入与截断、激活的 BF16 舍入和后续 FP8 量化。在 graph 关闭且路由专家位于 CPU 时，提前复制 CPU 分支的输入，使共享专家的 GPU 计算可与 CPU 计算重叠。参考模式或多卡情况下，共享专家由 MoE 路径统一计算。

普通模式的单 token mHC 将独立点积和平方和分配到多个 CUDA block，保留各输出原有的累加和归约顺序。CPU MoE 在满足上文权重和形状约束时，也可在普通模式按专家合并最多 8-token 的任务；参考模式继续采用最多 4-token 的任务并保留其数学计算方式。可分别设置 `FASTLLM_DSV41_LEGACY_HCMIX_DECODE=1`、`FASTLLM_DSV41_DISABLE_SHARED_OVERLAP=1`、`FASTLLM_DSV41_DISABLE_BATCHED_MOE=1`，选择未融合的诊断路径。

普通模式的 CPU NVFP4 block-32 矩阵计算，在多个 token 间复用权重解码与 scale，并展开固定大小循环以减少寄存器溢出。各 token 保留独立累加和原有归约；仅在不会溢出的输入和 scale 范围内，将精确的 2 的幂缩放移到 scale 上，极端输入及 E8M0 最小 scale 保留原缩放顺序（含 DAZ 非规格化数处理）。单 token 仍走既有内核。对于至少 256-token 的合并 MoE，输入量化与输出归并按 token 分给线程池，每个 token 内部的专家累加顺序不变。设置 `FASTLLM_DSV41_DISABLE_NVFP4_TOKEN_REUSE=1` 或 `FASTLLM_DSV41_DISABLE_PARALLEL_MOE_ROWS=1` 可分别禁用这两项优化进行对照；参考模式不启用这两项优化。

该配置接受与参考模式的数值差异。基础问答、计算和较长文本检查不能证明广泛能力等价；固定输入 logits 相对参考模式仍有明显差异。要求逐值对齐时，应继续使用参考模式。上述速度路径的验收范围是单卡、单请求、BF16 KV、关闭 graph 和缓存，并非长上下文、多请求或多卡的完整精度评测。

## 8K 普通模式配置

2026-09-13，基于 `fd873f3a` 的被测构建：单 RTX 4090、CPU 路由专家、GPU 共享专家、FP16 加载、BF16 KV、30 线程、NUMA 0、chunk 512，关闭 Graph 和前缀缓存。
该显存配置使用 512 分块完成 8K 请求验证；更大分块需重新核算工作区。
HTTP 固定生成 64 token，各测三轮取中位数，输入吞吐为输入数除以 TTFT，包含接口开销。

| 输入 token | TTFT 秒 | 输入 token/s | Decode token/s |
| ---: | ---: | ---: | ---: |
| 113 | 1.66 | 67.99 | 19.93 |
| 498 | 7.27 | 68.50 | 19.22 |
| 8178 | 109.28 | 74.84 | 19.06 |

24 项基础质量、中文数据分析和 8207-token 三位置 JSON 检索通过，检索的 9 个数字正确。普通模式允许与参考模式的数值差异；这些结果不能证明 8K 逐值对齐或多卡能力等价。
参考模式的完整模型验收仍限于 5-token prefill 与两步固定输入 decode，共 120 个层末输出和 3 组 logits。

---

### Deepseek V4 Sparse Attention (`docs/deepseek_v4_sparse_attention.md`)

# DeepSeek V4 Sparse Attention Decode

[Benchmark](benchmarks/deepseek_v4.md) · [部署指南](deepseek.md)

## 接口与调度

Triton 路径保留模型的缓存布局：BF16 query `[1, 1, local_heads, 512]`、FP32 window KV `[1, 128, 512]`、BF16 compressed KV `[1, capacity, 512]`，以及 FP32 attention sink。
通用 split-K 按 batch/head/split 并行，默认每段处理 8 个候选，输出 FP32 numerator、max 和 denominator，再合并 sink。位置和有效长度来自 graph-safe `decode_meta`。
工作区按设备复用；Graph capture 期间不分配或释放地址。Triton 编译、元数据校验、载入或 launch 失败时回退原生 CUDA。

SM120/SM121、D=512 且 local heads ≤16 时优先使用 tensor-core 路径：每块处理对齐到 16 的 heads 和 16 个候选，QK/PV 使用 BF16 dot，softmax 和 split/merge 保留 FP32，默认 8 warps。失败时依次尝试通用 Triton、原生 CUDA。
原生 CUDA 承担更宽的兼容范围，包括 FP32/FP16/BF16 query、不同 compressed KV dtype、batch 和最大 1024 head dim。

需要启用总 Triton 开关：

```bash
FASTLLM_CUDA_TRITON=1
```

相关环境变量：

| 环境变量 | 默认值 | 作用 |
|---|---:|---|
| `FASTLLM_CUDA_TRITON_DEEPSEEK_V4_SPARSE_DECODE` | `1` | sparse decode Triton 总开关 |
| `FASTLLM_CUDA_TRITON_DSV4_SPARSE_SM120` | `1` | SM12x 专用路径 |
| `FASTLLM_CUDA_TRITON_DSV4_SPARSE_SM120_SPLIT_SIZE` | `16` | SM12x candidate tile，可选 16/32/64 |
| `FASTLLM_CUDA_TRITON_DSV4_SPARSE_SM120_SPLIT_WARPS` | `8` | SM12x split kernel warps |
| `FASTLLM_CUDA_TRITON_DSV4_SPARSE_SPLIT_SIZE` | `8` | 通用路径 split size，可选 8/16/32/64 |
| `FASTLLM_CUDA_TRITON_DSV4_SPARSE_SPLIT_WARPS` | `4` | 通用 split kernel warps |
| `FASTLLM_CUDA_TRITON_DSV4_SPARSE_MERGE_BLOCK_D` | `32` | merge kernel 的输出维 tile |
| `FASTLLM_CUDA_TRITON_DSV4_SPARSE_MERGE_WARPS` | `4` | merge kernel warps |

推荐 TP8 decode 同时开启：

```bash
FASTLLM_CUDA_GRAPH=1
FASTLLM_CUDA_CUSTOM_ALLREDUCE=1
FASTLLM_CUDA_TRITON=1
```

## 数值与验证范围

通用 split/merge 的独立 FP32 参考测试最大误差不超过 1.49e-8。SM120 使用 BF16 tensor-core 归约，不保证与 FP32 路径逐位一致；需要 FP32 split-K 时设置 `FASTLLM_CUDA_TRITON_DSV4_SPARSE_SM120=0`。
已记录的全模型短上下文验证覆盖 16 步，top-1 相同，decode logits 余弦相似度为 0.997749–0.999921；这不是长上下文或广泛质量评估。
发布的性能只覆盖 TP8、128 输入 / 256 输出，不应外推到更长候选列表、其他 batch 或混合专家放置。

---

### Demo Arguments (`docs/demo_arguments.md`)

# Fastllm Python Demo 参数说明

## 通用参数

模型相关配置，OpenAI API Server， WebUI, 对话Demo 均可使用

- **模型路径 (`-p, --path`)**: 指定模型的路径，可以是fastllm模型文件或Hugging Face模型文件夹。例如:
```bash
--path ~/Qwen2-7B-Instruct/ # 从~/Qwen2-7B-Instruct/中读取模型，这里的模型需要是从HuggingFace或ModelScope或其他网站下载的Hugging face格式的标准模型，暂不支持AWQ，GPTQ等格式
--path ~/model.flm # 从~/model.flm中读取模型，这里的模型是Fastllm格式的模型文件
```
- **GGUF 视觉模块 (`--mmproj`)**: 指定与主模型匹配的视觉 GGUF 文件，目前仅用于 Qwen3.5 架构族的 GGUF（包括相应 Qwen3.6/3.8 模型）。还需通过 `--ori` 或主模型同目录的 `config.json` 提供完整视觉配置；当前不能与外部 MTP 同时使用。命令示例见 [GGUF 多模态](qwen3.md#gguf-multimodal)。
- **推理类型 (`--atype`)**: 设置中间计算类型，可以指定为`float16`或`float32`
- **图片 embedding 缓存 (`--image-embedding-cache`)**: Qwen3.5 native 图片路径的 CPU 缓存上限，默认 `512m`，例如 `--image-embedding-cache 1g`；设为 `0` 关闭。首次处理图片请求时才创建，按实际内容分配内存，不额外常驻 GPU 显存。命中后跳过 native 图片预处理和视觉编码；图片解码、传输及语言模型预填充仍可能执行。配置、作用范围与验证方式见[多模态缓存](multimodal-cache.md)。
- **启动多模态预分配 (`--multimodal`)**: Qwen3.5 架构族（包括相应 Qwen3.8）的视觉权重与 CUDA 临时工作区在 KV cache 定容前分配，后续视觉编码复用固定工作区。默认关闭；开启后启动时间增加，可用 KV 容量相应减少。工作区按 processor 的单张图片/单个视频像素上限确定，多张图片逐张复用；修改 processor 上限后需重启，超出预算的 native 输入会报错。
  多模态特征和完整文本 embedding 保留在 CPU，文本 prefill 只上传当前分块。图片总量仍受上下文长度和系统内存限制，工作区不限制文本侧其他缓存。
  CUDA 视觉跟随普通 `--tp` 的设备与比例，支持奇数卡；多卡时不再由 `--vision_device cuda:N` 单独覆盖视觉放置。各卡在 KV 定容前完成视觉预热和工作区预留。CPU 视觉只预先加载权重，不分配 CUDA 工作区。
- **权重类型 (`--dtype`)**: 指定模型的权重类型，适用于读取Hugging Face模型时。可以指定为`float16`, `int8`, `int4`, `int4g`(int4分组量化)，例如：
```bash
--dtype float16  # 使用float16权重（不量化）
--dtype int8     # 在线量化成int8权重
--dtype int4g128 # 在线量化成int4分组权重（128个权重一组）
--dtype int4g256 # 在线量化成int4分组权重（256个权重一组）
--dtype int4     # 在线量化成int4权重
```
- **使用的设备 (`--device`)**: 指定服务器使用的设备。可以指定为`cpu`或`cuda`或额外编译的其余device类型
- **CUDA Embedding (`--cuda_embedding`)**: 若带上此配置且device设置为`cuda`，那么会在cuda设备上进行embedding操作，这样速度会略微提升，显存占用也会提升，建议在显存非常充足的情况下使用

- **低显存模式 (`--low_gpu_mem`)**: 关闭 CUDA embedding 配置和 GPU token handoff；此开关优先于 `--cuda_embedding` 和 `FASTLLM_GPU_TOKEN_HANDOFF` 环境变量。CUDA Graph 仍按原有自动策略或 `FASTLLM_CUDA_GRAPH` 设置运行，Qwen3.5/3.6 普通 CUDA 推理会将 embedding 保留在 CPU，并可继续使用 CUDA Graph；其他模型的后端可能仍要求在 Graph 模式下使用 GPU embedding。该开关可与双卡 TP、MTP 或 DFlash2 配合使用（MTP 与 DFlash2 二选一）；当前 Qwen3.5 系列的 MTP 校验 Graph 不支持 CPU embedding，因此该校验阶段会回退到普通执行。该开关默认关闭，与 `--low` 低内存模式独立，不改变 KV 类型或上下文配额，也不保证避免 OOM。例如：`ftllm server /path/to/model --device cuda --low_gpu_mem`。
- **CUDA权重slab (`--cuda_slab`)**: 设置 CUDA 模型权重 slab 分配块大小，单位 MB，默认 `0` 表示关闭。对于将大量 MOE 专家权重放在 CUDA 上的场景，可以使用如 `--cuda_slab 1024` 减少小权重分别分配造成的显存碎片和页对齐开销
- **KV缓存类型 (`--kv_cache_dtype`)**: 默认 `auto`，可使用 `float16`、`bfloat16`、`fp8_e4m3`；Qwen3.5 架构的 CUDA 分页注意力还支持 `fp4`（别名 `nvfp4`、`fp4_e2m1`）。仅量化普通注意力 KV，不改变模型权重或线性注意力状态精度。硬件要求、内存格式与示例见[FP4 KV cache](fp4-kv-cache.md)。
- **KV缓存最大使用量 (`--kv_cache_limit`)**: 设置KV缓存的最大使用量。若不使用此参数或设置为`auto`，框架会自动处理。手动设定示例如下：
```bash
--kv_cache_limit 5G   # 设置为5G
--kv_cache_limit 100M # 设置为100M
--kv_cache_limit 168K # 设置为168K
```
- **单会话上下文 (`--max_context_length`, `--max-context-length`)**: 输入与输出合计上限；扩大模型声明窗口需要有效的 RoPE 配置，warmup 后容量不足会启动失败。
- **RoPE 扩展 (`--rope_scaling`, `--rope-scaling`)**: HF Qwen2/3/3.5 布局支持静态 YaRN；填 `yarn` 或 JSON。Qwen3 等模型须明确 `original_max_position_embeddings`。用法、支持范围和验证记录见[上下文扩展](context-length-extension.md)。
- **最大Batch数量 (`--max_batch`)**: 设置每次同时处理的请求数量。若不使用此参数，框架会自动处理
- **线程数量 (`-t, --threads`)**: 设置CPU线程数量，device设置为`cpu`时对速度有较大影响，设置为`cuda`时影响较小，主要影响读取模型的速度
- **自定义模型描述文件 (`--custom`)**: 指定描述自定义模型的Python文件。具体见 [自定义模型](custom.md)

## OpenAI API Server配置参数
- **模型名称 (`--model_name`)**: 指定部署的模型名称，API调用时会进行名称核验
- **API服务器主机地址 (`--host`)**: 设置API服务器的主机地址
- **API服务器端口号 (`--port`)**: 设置API服务器的端口号


## Web UI 配置参数
- **模型提示 (`model` / `-p, --path`)**: 可选；用于推导默认 API 模型名，省略时会从 `/v1/models` 自动发现
- **监听地址 (`--host`)**: 设置 WebUI 监听地址，默认为 `127.0.0.1`
- **页面端口 (`--port`)**: 设置 WebUI 监听端口，默认为 `1616`
- **页面标题 (`--title`)**: 设置 WebUI 的页面标题
- **模型 API (`--api_base`)**: 设置 OpenAI 兼容 API 的地址，默认为 `http://127.0.0.1:8080/v1`
- **API 模型名 (`--api_model`)**: 设置 API 请求使用的模型名；默认使用模型目录名，也可从 `/v1/models` 自动发现
- **API Key (`--api_key`)**: 设置 WebUI 后端访问模型 API 时使用的密钥；密钥不会下发到浏览器
- **API 请求超时 (`--api_timeout`)**: 设置单次模型 API 请求的超时时间（秒）
- **API 就绪超时 (`--api_ready_timeout`)**: 设置 WebUI 启动前等待模型 API 就绪的最长时间（秒）
- **最大输出 (`--max_token`)**: 设置最大输出 token 数；默认不设人工上限，小于等于 `0` 也表示不限制
- **会话目录 (`--history_dir`)**: 设置 SQLite 会话记录和上传附件的保存目录，默认为 `~/.fastllm/webui`
- **上传上限 (`--max_upload_mb`)**: 设置单个文档、图片或视频的最大体积（MiB）
- **数据行数上限 (`--data_max_rows`)**: 设置数据分析时每个数据表最多读取的行数，默认为 `200000`
- **代码上下文上限 (`--code_max_context_chars`)**: 设置代码项目智能体单次注入模型的最大源码字符数，默认为 `60000`
- **联网超时 (`--web_search_timeout`)**: 设置 Web Agent 单次搜索或网页读取的超时时间（秒）
- **智能体运行时 (`--agent_runtime`)**: 设置代码与联网智能体运行时，默认为 `pi`；`builtin` 使用原有单轮链路，`auto` 在 Pi 可用时优先使用 Pi
- **Pi 任务超时 (`--pi_agent_timeout`)**: 设置单次 Pi 智能体任务的总超时，默认为 `300` 秒
- **Pi 最大轮数 (`--pi_agent_max_turns`)**: 设置单次 Pi 智能体任务最多使用的模型轮数，默认为 `8`
- **Pi 上下文窗口 (`--pi_agent_context_window`)**: 设置传给 Pi 的模型上下文窗口，默认为 `40000` token

新版 WebUI 由 FastAPI 和原生前端提供，但不再在 WebUI 进程内加载模型。请先用 `ftllm server` 启动 OpenAI 兼容 API，再启动 WebUI：

```bash
ftllm server /path/to/model --host 127.0.0.1 --port 8080 --model_name my-model --device cuda
ftllm webui /path/to/model --host 127.0.0.1 --port 1616 \
    --api_base http://127.0.0.1:8080/v1 --api_model my-model
```

WebUI 启动前会等待 `/v1/models` 就绪，所有模型调用都通过 `/v1/chat/completions` 完成；代码分析和联网搜索默认由 Pi 进行多轮工具调用，其余模式保持直接调用。浏览器只访问 WebUI 后端，因此 API Key 不会暴露给页面；模型只由 API Server 加载一份。Pi 配套 wheel 的构建和安装方式见 [`tools/ftllm_agent_runtime/`](../tools/ftllm_agent_runtime/)，未安装时可传入 `--agent-runtime builtin`。WebUI 支持多会话持久化、关闭/低/中/高思考档位、图片和视频上传，以及无需搜索服务 API Key 的快速搜索和深度浏览。文件知识库可读取 PDF、DOCX、XLSX、PPTX、CSV、Markdown、文本和常见代码文件，支持会话内跨请求检索、跨文件问答及原文位置引用；扫描版 PDF 暂不包含 OCR。数据分析智能体可读取 CSV、TSV、JSON、JSONL 和 XLSX，通过受限的分组汇总、趋势、相关性及排序操作生成结论、PNG 图表和可编辑的 Excel 报告，不执行模型生成的任意代码。代码项目智能体会通过只读工具按需查看会话内的源码快照和项目配置文件，并按行号进行跨文件审查和问题定位；用户要求修改时可生成经过路径校验、可下载的 unified diff，但服务器不会执行源码、应用补丁或运行模型生成的命令。“生成 PPT”可以从主题或会话文件直接生成 4–20 页、可继续编辑的 16:9 PPTX，并支持科技蓝、商务红、自然绿和高级黑金风格。文件和联网资料都会视为不可信数据，并在回答下方保留来源。

---

### Development Validation (`docs/development-validation.md`)

# 开发与验证记录

这页整理可长期维护的开发结论和验证入口，方便维护者、部署人员和自动化 agent 快速判断一项改动是否已经有回归覆盖。它不是逐轮实验日志：临时命令、原始日志、profiler 波形和未采用的方案放在仓库外的实验归档中，最终性能数字写入对应的 Benchmark 页面。

## 文档分工

| 需要查什么 | 入口 | 内容边界 |
| --- | --- | --- |
| 自动部署和模型选择 | [Agent 部署速查](model-deployment-agent.md) | `--auto` 默认流程、资源限制、手动覆盖和验收步骤；建议先交给 agent 阅读 |
| 自动规划器的规则 | [混合推理自动规划](hybrid-auto.md) | 设备选择、MoE/PLE/Engram 放置、缓存预算和回退条件 |
| 单个模型的参数与限制 | 各模型部署指南 | 当前支持范围、手动命令、格式限制和已知风险 |
| 最终性能结论 | [Benchmark 索引](benchmark.md) 及 `docs/benchmarks/` | 硬件、权重格式、源码版本、输入输出长度、缓存设置、计时口径和限制 |
| 可执行正确性回归 | `test/`、CTest | 数值对齐、启动器、下载器、算子和 API 回归 |
| 版本变更 | [版本日志](version.md) | 面向发布的功能和兼容性变化 |

## 当前模型验证入口

| 模型簇 | 部署说明 | Benchmark / 回归 |
| --- | --- | --- |
| Qwen3.8 27B | [Qwen3 部署指南](qwen3.md) | [Qwen3 Benchmark](benchmarks/qwen3.md)、[DFlash2](benchmarks/qwen38_27b_dflash2.md) |
| Qwen3.8-Flash-Next GSQ / NVFP4 | [Flash-Next 用户手册](qwen3.8-flash-next/README.md) | [Qwen4-Exp Benchmark](benchmarks/qwen4_exp.md) |
| DeepSeek V4.1 Flash | [DeepSeek V4.1 说明](deepseek_v41.md) | [V4.1 Benchmark](benchmarks/deepseek_v41.md)、CPU fixture 和 DSpark 回归 |
| GLM-5.3-Flash | [GLM-5 部署指南](glm5.md) | [GLM Benchmark](benchmarks/glm5.md)、CUDA/NUMA/缓存回归 |

自动部署相关的可执行覆盖主要位于 `test/api/test_auto_config.py`、`test/api/test_launcher.py`、`test/api/test_launcher_webui_browser.py` 和 `test/api/test_modelscope_download.py`。模型算子和数值边界测试继续按模型指南列出的 CTest 或 Python fixture 执行。

## Benchmark 记录要求

新增或更新一组数字时，正文至少记录：

1. GPU 型号、数量、可见设备和主机内存；
2. 权重仓库、量化格式、分片路径和 FastLLM 源码版本；
3. 输入长度、输出长度、批大小、线程数和 warmup 方法；
4. `--auto` 的实际规划结果，或完整的手动启动命令；
5. `moe_cuda_cache`、`moe_cpu_cache`、prefix/history cache、MTP/DSpark、Graph 和 chunked prefill 设置；
6. TTFT、prefill、decode 的计时口径，以及未覆盖的场景。

同一配置重新测量时，更新原结论并保留源码版本和测量范围；不要在部署文档中追加逐轮优化日记。没有源码版本的旧结果要明确标记为历史参考，不能写成当前 HEAD 的性能。

## 提交前检查

部署器改动应在安装了 FastAPI、Uvicorn、requests 和 httpx 的测试环境中执行以下回归。浏览器测试还需要 Playwright 与 Chromium（`python3 -m playwright install chromium`）；未安装时该组会跳过，不能把跳过计为通过。

```bash
git diff --check
PYTHONPATH=test/api python3 -m unittest \
  test.api.test_auto_config \
  test.api.test_hybrid_auto \
  test.api.test_moe_cuda_cache_cli \
  test.api.test_launcher \
  test.api.test_launcher_webui_browser \
  test.api.test_modelscope_download
```

如果修改了 CUDA、模型算子或权重格式，还要运行对应的 `ctest --test-dir <build> ...` 回归，并在 Benchmark 或模型指南中记录实际构建配置。只修改文档时，`git diff --check` 和链接/命令检查即可；不要为了文档变更伪造性能数字。

---

### Dots3 Note (`docs/dots3_note.md`)

# Dots3-Note 部署指南

[English](dots3_note_en.md) · [返回 README](../README.md) · [Benchmark](benchmarks/dots3_note.md)

FastLLM 的 Dots3-Note 路径支持 DSA 索引、稀疏注意力、长上下文缓存、分块 Prefill、思考内容和工具调用。

## API Server 快速启动

~~~bash
ftllm server /data/models/dots3-note \
  --model_name dots3-note \
  --host 0.0.0.0 --port 8080
~~~

## GPU + NUMA 混合 MoE

~~~bash
ftllm server /data/models/dots3-note \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192 \
  --gpu_mem_ratio 0.9
~~~

## GPU + CPU

单路 CPU 或不使用 NUMA 后端时：

~~~bash
ftllm server /data/models/dots3-note \
  --device cuda --moe_device cpu \
  --chunked_prefill_size 8192
~~~

对于 FP8 checkpoint，FastLLM 会按模型和设备选择适合的 MoE 激活类型与 CPU 快速路径。建议先使用 `--dtype auto`。

## 长上下文与缓存

~~~bash
ftllm server /data/models/dots3-note \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --gpu_mem_ratio 0.9
~~~

## 思考与工具调用

~~~bash
ftllm server /data/models/dots3-note \
  --enable_thinking true \
  --tool_call_parser auto
~~~

API 支持流式和非流式 `reasoning_content`，工具调用 parser 默认自动选择。

## Benchmark 状态

仓库目前记录了算子、长缓存和 API 回归，但没有完整的 Dots3-Note 设备吞吐表。建议设备命令和数据状态见 [Dots3-Note Benchmark](benchmarks/dots3_note.md)。

---

### Dots3 Note En (`docs/dots3_note_en.md`)

# Dots3-Note Deployment

[中文](dots3_note.md) · [Back to README](../README_EN.md) · [Benchmark](benchmarks/dots3_note_en.md)

FastLLM supports Dots3-Note DSA indexing, sparse attention, long-context caching, chunked prefill, reasoning content, and tool calling.

## API server quick start

~~~bash
ftllm server /data/models/dots3-note \
  --model_name dots3-note \
  --host 0.0.0.0 --port 8080
~~~

## GPU + NUMA hybrid MoE

~~~bash
ftllm server /data/models/dots3-note \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192 \
  --gpu_mem_ratio 0.9
~~~

## GPU + CPU

~~~bash
ftllm server /data/models/dots3-note \
  --device cuda --moe_device cpu \
  --chunked_prefill_size 8192
~~~

For FP8 checkpoints, FastLLM selects model- and device-specific MoE activation and CPU fast paths. Start with `--dtype auto`.

## Long context and caching

~~~bash
ftllm server /data/models/dots3-note \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --gpu_mem_ratio 0.9
~~~

## Thinking and tool calling

~~~bash
ftllm server /data/models/dots3-note \
  --enable_thinking true \
  --tool_call_parser auto
~~~

The API supports streaming and non-streaming `reasoning_content`. Tool parsing defaults to automatic selection.

## Benchmark status

Operator, long-cache, and API regressions exist, but the repository has no complete Dots3-Note device-throughput table. See the [Dots3-Note benchmark page](benchmarks/dots3_note_en.md) for suggested device commands and the current data status.

---

### Dtype Config (`docs/dtype_config.md`)

## 基本用法

通过 原始模型 + 量化配置文件，可以使用、导出不同格式的量化模型

例如我们使用模型

`Qwen3-Next-80B-A3B-Instruct` （https://hf-mirror.com/Qwen/Qwen3-Next-80B-A3B-Instruct）

和量化配置文件：

`UD-Q2_K_S.json` : （[这个文件](../example/quant/qwen3_next/UD-Q2_K_S.json)）

下面这条命令会读会读取`UD-Q2_K_S.json`中的的规则来进行动态量化，并部署`api server`
```
ftllm server Qwen3-Next-80B-A3B-Instruct --dtype_config UD-Q2_K_S.json
```

下面这条命令会读会读取`/root/dtype_config.json`中的的规则来进行动态量化，并导出模型
```
ftllm export Qwen3-Next-80B-A3B-Instruct --dtype_config UD-Q2_K_S.json -o Qwen3-Next-80B-A3B-Instruct-UD-Q2_K_S
```

# 如何编写配置文件

这个文档以`DeepSeek-V3-0324`模型为例，介绍如何使用动态量化功能

（注意，目前的量化功能为初版，后续会支持动态设定量化方法，以及设置重要性矩阵来提高量化精度）

编写动态量化配置时，最好先了解如下知识：
- 需要对`正则表达式`有一定了解
- 需要对`json`格式有一定了解
- 需要对模型的结构有一定了解。可以通过查看模型文件夹下的`model.safetensors.index.json`来查看模型的权重名称。

我们以如下示例来说明如何编写配置文件：

```
[
    {
        "key" : "(.)*",
        "dtype" : "int4g",
        "comment": "default use dtype int4g"
    },
    {
        "key" : "model\\.layers\\.([0-9]|[1][0-9]|2[0-5])\\.(.)*",
        "dtype" : "fp8",
        "comment": "layer 0~25 use dtype fp8"
    },
    {
        "key" : "(.)*mlp\\.gate\\.weight",
        "dtype" : "float16",
        "comment": "gate use float16"
    },
    {
        "key" : "(.)*experts(.)*[gate|up|down]_proj(.)*",
        "dtype" : "int4",
        "comment": "moe use int4"
    }
]
```
将以上json代码保存为`/root/dtype_config.json`（这里的路径只是举例，你可以保存到任意位置）
然后就可以使用`--dtype_config /root/dtype_config.json`来读取其中的配置（如果你的文件不是保存在`/root/dtype_config.json`，那么请使用你保存文件的路径）

**说明**：
- 配置文件中是一个json的数组，数组中每个元素代表一条量化规则。
- 每条规则中，目前有两个关键参数
    - `key`: 指定匹配权重的正则表达式
    - `value`: 当权重和`key`指定的正则表达式匹配时，使用`value`指定的类型
- 越靠后的规则优先级越高，一个权重如果匹配上了多个规则，那么会使用最后一条规则
- 规则中的其余参数不会有影响。例如上例中的`comment`参数仅为注释
- 上例中:
    - 0~25层的MLA部分使用`fp8`类型
    - 剩余层的MLA部分使用`int4g`类型
    - gate权重均使用`float16`类型
    - moe部分均使用`int4`类型

---

### English Custom (`docs/english_custom.md`)

### Custom Models

For models that are not supported by the Fastllm framework, you can support them by customizing the model structure.

A custom Python model requires only a Python file to describe the model structure. You can refer to the implementation in [QWEN](../example/python/qwen2.py).

### Using Python Custom Models

When using `ftllm.chat`, `ftllm.webui`, or `ftllm.server`, you can add the `--custom` parameter to specify the custom model file.

Assuming our model is located in the `~/Qwen2-7B-Instruct/` directory and the custom model is located in `~/qwen2.py`, you can use the command:

```sh
python3 -m ftllm.chat -t 16 -p ~/Qwen2-7B-Instruct/ --custom ~/qwen2.py
```

to load the Qwen2 model using the custom model file. The usage for `server` and `webui` is similar.

### Writing Python Custom Models

When creating a custom model, you need to implement a model description class that inherits from `ftllm.llm.ComputeGraph`.

Refer to the code in [QWEN](../example/python/qwen2.py):

```python
from ftllm.llm import ComputeGraph
class Qwen2Model(ComputeGraph):
```

At the end of the file, you need to define the `__model__` variable to specify the class corresponding to the custom model structure, with the corresponding code:

```python
__model__ = Qwen2Model
```

The model description class needs to implement the `build` method to obtain model parameters and describe the computation flow.

Here is an example based on the sample code:

```python
class Qwen2Model(ComputeGraph):
    def build(self):
        # 1. Get weight, data, config
        weight, data, config = self.weight, self.data, self.config

        # 2. Set some config
        config["max_positions"] = 128000

        # 3. Describe the computation flow
        head_dim = config["hidden_size"] // config["num_attention_heads"]
        self.Embedding(data["inputIds"], weight["model.embed_tokens.weight"], data["hiddenStates"]);
        # The following is the computation flow, see the example code for details
```

#### `self.config`

The model configuration, which by default is read from the `config.json` file in the model folder.

You can modify parameters in the `config` within the `build` method, such as changing `max_positions` to modify the context length.

For some models, the variable names used in `config.json` may differ and need to be manually assigned during the `build` process.

For example, in the TeleChat7B model configuration, there is no `max_positions` variable but instead uses `seq_length` to represent the length. In the `build` method, you need to assign it with the following code:

```python
self.config["max_positions"] = self.config["seq_length"]
```

In the `config`, the following variables must be assigned (if the variable names in `config.json` are consistent, no action is needed):

```python
self.config["max_positions"] # Represents the maximum context length
```

#### `self.weight`

Represents the weight data.

`self.weight[weightName]` represents the parameter named `weightName` in the model file (corresponding to the parameter names in the `.safetensors` file in the HF model folder).

#### `self.data`

Represents the intermediate variables and input variables of the computation flow.

`self.data[dataName]` represents the intermediate variable named `dataName`. `dataName` can be any string except for the following input variable names:

Input variables:

```python
data["inputIds"] # Input tokens
data["positionIds"] # Position information
data["attentionMask"] # Mask information
data["sin"] # Sin for rotary encoding
data["cos"] # Cos for rotary encoding
data["atype"] # Data type in inference
data["pastKey."][i] # Key cache for the i-th block
data["pastValue."][i] # Value cache for the i-th block
```

#### Computation Flow and Operators

Use the functions of the base class `ComputeGraph` to describe the computation flow.

The currently supported operators are documented in [Custom Model Operators](./custom_op.md).

### Custom Models in C++

(The interface for custom models in C++ is still under modification...)

---

### English Demo Arguments (`docs/english_demo_arguments.md`)

# Fastllm Python Demo Parameter Explanation

## General Parameters

Configuration related to the model, OpenAI API Server, WebUI, and conversation demo can all use these parameters.

- **Model Path (`-p, --path`)**: Specifies the path to the model, which can be a fastllm model file or a Hugging Face model directory. For example:
  ```bash
  --path ~/Qwen2-7B-Instruct/ # Reads the model from ~/Qwen2-7B-Instruct/, where the model needs to be a standard Hugging Face format model downloaded from HuggingFace, ModelScope, or other websites. Formats like AWQ, GPTQ, etc., are currently not supported.
  --path ~/model.flm # Reads the model from ~/model.flm, where the model is a Fastllm format model file
  ```
- **GGUF vision module (`--mmproj`)**: Specifies a matching vision GGUF for Qwen3.5-family GGUF models, including corresponding Qwen3.6/3.8 checkpoints. Supply complete vision configuration through `--ori` or a `config.json` alongside the main GGUF. It cannot currently be combined with external MTP. See the [GGUF multimodal example](qwen3_en.md#gguf-multimodal).
- **Inference Type (`--atype`)**: Sets the intermediate computation type, which can be specified as `float16` or `float32`.
- **Weight Type (`--dtype`)**: Specifies the weight type of the model, applicable when reading Hugging Face models. It can be specified as `float16`, `int8`, `int4`, `int4g` (int4 grouped quantization), for example:
  ```bash
  --dtype float16  # Uses float16 weights (no quantization)
  --dtype int8     # Quantizes to int8 weights online
  --dtype int4g128 # Quantizes to int4 grouped weights online (128 weights per group)
  --dtype int4g256 # Quantizes to int4 grouped weights online (256 weights per group)
  --dtype int4     # Quantizes to int4 weights online
  ```
- **Device to Use (`--device`)**: Specifies the device used by the server. It can be specified as `cpu`, `cuda`, or other device types compiled additionally.
- **CUDA Embedding (`--cuda_embedding`)**: If this configuration is included and the device is set to `cuda`, embedding operations will be performed on the cuda device, slightly increasing speed and GPU memory usage. It is recommended to use this when there is ample GPU memory.

- **Low GPU Memory (`--low_gpu_mem`)**: Disables the CUDA embedding setting and GPU token handoff, taking precedence over `--cuda_embedding` and `FASTLLM_GPU_TOKEN_HANDOFF`. CUDA Graph still follows its existing automatic policy or `FASTLLM_CUDA_GRAPH`; ordinary Qwen3.5/3.6 CUDA inference keeps embedding on the CPU and can continue using CUDA Graph. Other model backends may still require GPU embedding in Graph mode. The flag can be combined with two-GPU TP, MTP, or DFlash2 (MTP and DFlash2 are mutually exclusive). The current Qwen3.5-family MTP verification Graph does not support CPU embedding, so that verification stage falls back to eager execution. This opt-in flag is independent of `--low`. It does not change the KV dtype or context budget, and does not guarantee that the model will avoid OOM. Example: `ftllm server /path/to/model --device cuda --low_gpu_mem`.
- **CUDA Weight Slab (`--cuda_slab`)**: Sets the CUDA model-weight slab size in MB. The default value `0` disables it. For MoE runs that place many expert weights on CUDA, a value such as `--cuda_slab 1024` can reduce fragmentation and page-alignment overhead from many small weight allocations.
- **KV Cache Maximum Usage (`--kv_cache_limit`)**: Sets the maximum usage for the KV cache. If this parameter is not used or set to `auto`, the framework will handle it automatically. Manual settings examples are as follows:
  ```bash
  --kv_cache_limit 5G   # Sets to 5G
  --kv_cache_limit 100M # Sets to 100M
  --kv_cache_limit 168K # Sets to 168K
  ```
- **Context window (`--max_context_length`, `--max-context-length`)**: Limits combined input and output tokens. Extending the declared window requires a supported RoPE configuration; insufficient calibrated KV capacity fails startup.
- **RoPE extension (`--rope_scaling`, `--rope-scaling`)**: Accepts `yarn` or JSON for supported HF Qwen2/3/3.5 layouts. Supply the original RoPE length explicitly when it is not known. See [context extension](context-length-extension.md) for configuration and validation limits.
- **Maximum Batch Size (`--max_batch`)**: Sets the number of requests processed simultaneously each time. If this parameter is not used, the framework will handle it automatically.
- **Number of Threads (`-t, --threads`)**: Sets the number of CPU threads, which significantly affects speed when the device is set to `cpu`, and has a smaller impact when set to `cuda`, mainly affecting the speed of model loading.
- **Custom Model Description File (`--custom`)**: Specifies the Python file describing the custom model. See [Custom Model](custom.md) for details.

## OpenAI API Server Configuration Parameters
- **Model Name (`--model_name`)**: Specifies the name of the deployed model, which will be verified during API calls.
- **API Server Host Address (`--host`)**: Sets the host address of the API server.
- **API Server Port Number (`--port`)**: Sets the port number of the API server.

## Web UI Configuration Parameters
- **Model hint (`model` / `-p, --path`)**: Optional; derives the default API model name. When omitted, the model is discovered from `/v1/models`.
- **Listening address (`--host`)**: Sets the WebUI listening address; the default is `127.0.0.1`.
- **Page port (`--port`)**: Sets the WebUI listening port; the default is `1616`.
- **Page title (`--title`)**: Sets the WebUI page title.
- **Model API (`--api_base`)**: Sets the OpenAI-compatible API URL; the default is `http://127.0.0.1:8080/v1`.
- **API model (`--api_model`)**: Sets the model name used in API requests; it defaults to the model-directory name and can also be discovered from `/v1/models`.
- **API key (`--api_key`)**: Sets the credential used by the WebUI backend; it is never sent to the browser.
- **API request timeout (`--api_timeout`)**: Sets the timeout in seconds for one model API request.
- **API readiness timeout (`--api_ready_timeout`)**: Sets how long the WebUI waits for the model API before it starts listening.
- **Maximum output (`--max_token`)**: Sets the output-token limit. By default there is no artificial limit; values less than or equal to `0` also mean unlimited.
- **History directory (`--history_dir`)**: Stores the SQLite conversation database and uploaded attachments; the default is `~/.fastllm/webui`.
- **Upload limit (`--max_upload_mb`)**: Sets the maximum size of each document, image, or video in MiB.
- **Data row limit (`--data_max_rows`)**: Sets the maximum number of rows read from each table during data analysis; the default is `200000`.
- **Code context limit (`--code_max_context_chars`)**: Sets the maximum source characters injected into one code-agent model request; the default is `60000`.
- **Web timeout (`--web_search_timeout`)**: Sets the timeout in seconds for each Web Agent search or page read.
- **Agent runtime (`--agent_runtime`)**: Selects the code and web agent runtime. The default is `pi`; `builtin` uses the original single-call paths, while `auto` prefers Pi when available.
- **Pi task timeout (`--pi_agent_timeout`)**: Sets the total timeout for one Pi agent task; the default is `300` seconds.
- **Pi maximum turns (`--pi_agent_max_turns`)**: Limits one Pi task to this many model turns; the default is `8`.
- **Pi context window (`--pi_agent_context_window`)**: Sets the model context window reported to Pi; the default is `40000` tokens.

The WebUI uses FastAPI and a native frontend, but no longer loads the model in its own process. Start an OpenAI-compatible API with `ftllm server`, then point the WebUI at it:

```bash
ftllm server /path/to/model --host 127.0.0.1 --port 8080 --model_name my-model --device cuda
ftllm webui /path/to/model --host 127.0.0.1 --port 1616 \
    --api_base http://127.0.0.1:8080/v1 --api_model my-model
```

Before listening, the WebUI waits for `/v1/models`, and every model call uses `/v1/chat/completions`. Code analysis and web search use Pi for multi-turn tool calls by default; the other modes continue to call the model directly. The browser talks only to the WebUI backend, so API credentials are not exposed and the model is loaded exactly once by the API Server. Build and install the companion wheel from [`tools/ftllm_agent_runtime/`](../tools/ftllm_agent_runtime/), or pass `--agent-runtime builtin` when it is absent. The WebUI supports persistent conversations, off/low/medium/high reasoning levels, image and video uploads, and quick search or deep browsing without a search-provider API key. Its file knowledge base reads PDF, DOCX, XLSX, PPTX, CSV, Markdown, text, and common source-code files, with cross-request retrieval, cross-file Q&A, and source locations inside each conversation; scanned PDFs do not yet include OCR. The data-analysis agent reads CSV, TSV, JSON, JSONL, and XLSX, executes only validated grouping, trend, correlation, and sorting operations, and produces findings, PNG charts, and editable Excel reports without running arbitrary model-generated code. The code-project agent uses read-only tools to inspect source snapshots and project-configuration files from the conversation for line-referenced cross-file review and issue localization. When changes are requested, it can produce a path-validated downloadable unified diff, but the server never executes source, applies patches, or runs model-generated commands. "Create PPT" generates an editable 4–20 slide, 16:9 PPTX from a topic or conversation files in tech, business, nature, or premium styling. File and web results are treated as untrusted data and retained as sources under the answer.

---

### Execution Modes (`docs/execution_modes.md`)

# GPU 与混合推理执行路径

`FASTLLM_EXECUTION_MODE=auto|gpu|hybrid|cpu` 是统一选择器，支持
Qwen4（包括 `qwen4_exp` / Qwen3.8-Flash-Next）、DeepSeek V4.1 与
GLM 5.3 Flash（`glm5_next`）。
默认 `auto` 根据每层最终生效的主干和路由专家设备选择；显式模式与设备不符时报错。
环境变量不搬迁权重，也不改变已经创建的模型。

| 配置 | 执行路径 |
| --- | --- |
| 主干与路由专家均在 CUDA / multicuda | GPU |
| 主干和专家的有效配置同时包含 CUDA 与 CPU / NUMA / disk | Hybrid |
| 主干与专家均在 CPU / NUMA / disk | CPU，使用保留的主机专家实现 |

Engram、PLE、embedding 的存储位置与主干执行模式独立。仅将这些表放在 CPU
或磁盘上仍可选择 GPU forward。路由结果的 CPU 分桶也不改变模式。

## 分层方式

两条 forward 独立维护执行顺序：

- `src/models/qwen4_exp_gpu.cpp` 与 `qwen4_exp_hybrid.cpp`；
- `src/models/deepseekv41_gpu.cpp` 与 `deepseekv41_hybrid.cpp`；
- `src/models/glm5_next_gpu.cpp` 与 `glm5_next_hybrid.cpp`。

公共的 `ForwardTarget` / `ForwardSegments` / `ForwardLayers` 只派发到所选 forward，不再持有共同的
主循环。GPU forward 调用 GPU 专家计算；Hybrid forward 自己管理混合 MoE 步骤、
预取和专家执行。CPU 路径使用 Hybrid 文件中的主机后端分支，不启动 CUDA 缓存策略。

公共模型代码保留权重加载、数学语义、Attention、KV 和采样；Graph 策略与共享机制的文件边界见下文。
Qwen / DeepSeek 的 `ForwardWorkspace` 持有一次前向的状态，`ForwardState` 提供块接口。
GLM 保留栈上的逐层临时张量，通过 Attention、FFN 前后处理块共享数学实现。
Attention/GDN/QSA、Hyper-Connection、共享专家与现有 CUDA 融合算子继续共用，
不复制数值实现，不使用 `Forward<isHybrid>` 模板包装同一条主循环。

张量生命周期也是块接口的一部分：跨块张量归 workspace 所有，CUDA Graph 的
持久地址仍由原图工作区持有。Qwen 每层临时量保持原来的释放顺序，避免改变临时
显存池的复用行为。DeepSeek 的 TP 异步作用域覆盖整次前向，并在释放图锁前结束。
Qwen / DeepSeek 混合策略的启动仍位于 embedding 之前；准备输入是显式的公共块，避免拆文件时
改变缓存准入等待、设备选择与输入计算的先后顺序。

MTP / DSpark 的请求管理、接受/拒绝与回滚由公共逻辑维护；目标模型验证进入所选 forward。

## 复用混合 MoE

通用入口在 `include/utils/hybrid_moe.h` 与 `src/utils/hybrid_moe.cpp`：

1. 使用现有格式描述 `FastllmCudaMoeCacheLayer` 注册专家表，保留原权重表的身份与生命周期。
2. 在支持动态分流的完整 decode 步骤外建立 `HybridMoeDecodeStep`，调用 `Begin`。
   `Finish` / 析构负责结束策略与缓存准入，异常路径也会收尾；不能在每个专家或每层重新开始。
3. 路由完成后调用 `RunHybridMoe`，传入归一化输入、专家索引、分数、权重表和层号。
   可传入独立共享专家的回调，让后端安排 CPU 专家与 GPU 计算重叠。
4. 分块流水 prefill 可使用 `HybridMoePrefetch`。它检查层范围、行数和 NUMA 放置，
   支持的格式沿用原后端预取；其他情况保持原路径。
5. 多设备分层使用 `HybridMoeDecodeGroup`：在每张卡第一次到达适用 MoE 层时启动，
   每个设备每步只启动一次；不适用的层不占据条目。退出时按设备顺序收尾。
   TP 主机专家使用 `RunHybridMoeOnDevices`，保留设备组、共享专家回调和原格式适配。

动态分流的成本模型、热点统计、缓存准入/驱逐、异步专家上传和格式专用计算继续由
`src/devices/cuda/moe/fastllm-moe-cache.cu` 及其格式适配层统一维护。
Qwen4、DeepSeek V4.1 与 GLM 5.3 Flash 通过上述入口使用同一后端，无需把这些策略复制到每个模型。

格式相关数学不能混用：DeepSeek V4.1 的 block-32 激活量化、路由分数乘法位置、
BF16 舍入与 SwiGLU clamp 继续由其适配层负责。新模型必须先验证该格式契约。
不支持的格式或批量返回原专家后端；选择 Hybrid 不等于启用 GPU 专家缓存。

TP 副本暂存、跨设备输出归并以及 Qwen 的双卡流水/EP 协作仍保留模型适配代码。
它们涉及各自的张量布局，不能仅凭相同专家数复用。通用缓存和分流入口不承诺任意
布局、任意批量都能命中优化路径。

## GLM 5.3 Flash

`ForwardGpuLayers` 与 `ForwardHybridLayers` 分别拥有完整的逐层循环。KDA / DSA、
Hyper-Connection、路由计算及共享专家块共用；各自的专家调度在独立源文件中维护。
混合部署中某层专家全部位于 CUDA 时，该层复用 GPU 专家块，其他层仍保留主机调度。

`glm5_next_tp_state.h` 只声明共享的 TP 运行时类型；TP 权重切分、线程、事件交接与
KDA Graph 的实现仍由原运行时维护。每个 TP rank 在最终设备配置确定后解析执行模式。
稠密 FFN 层不使用路由专家，自动模式忽略这些层上未使用的专家设备配置。

GLM 的缓存策略在各设备首个适用 MoE 层启动，维持原有时序；draft MoE 也使用通用
步骤生命周期。共享专家与主机专家的重叠、TP 结果归并、MTP/KDA 回滚和前缀快照保留。

当前 GLM 专用 GPU 专家缓存注册支持原有 NVFP4 block-16 和符合条件的 GGUF 格式。
FP8 源权重仍使用其既有主机/GPU 后端；选择 Hybrid 或设置缓存预算不会自动将 FP8
转换为 NVFP4/GGUF，也不代表该缓存已启用。

## Qwen4 模块边界

| 职责 | 实现 |
| --- | --- |
| GPU / Hybrid 普通前向与专家执行 | `qwen4_exp_gpu.cpp` / `qwen4_exp_hybrid.cpp` |
| 每层及草稿的专家执行计划、路由与公共共享专家数学 | `qwen4_exp_moe.cpp` |
| GPU 图编排 | `qwen4_exp_gpu_graph.cpp` |
| Hybrid 图段与段外专家编排 | `qwen4_exp_hybrid_graph.cpp` |
| 图存储、捕获/回放/销毁、共享数学与状态发布 | `qwen4_exp_graph.cpp` / `qwen4_exp_graph.h` |
| 混合专家协作、绑核与执行生命周期 | `qwen4_exp_hybrid_parallel.cpp` / `qwen4_exp_hybrid.h` |
| 分层 prefill 流水 | `qwen4_exp_pp.cpp` |
| TP 线程、通信与共享请求状态 | `qwen4_exp_tp.cpp` / `qwen4_exp_tp.h` |

全模型模式负责配置检查和 forward 选择；`MoEExecutionPlan` 另按实际设备为目标各层和草稿选择专家后端。
主模型专家在 NUMA、草稿专家在 GPU 时，两者分别调用对应后端。该计划记录后端、层号、前缀和原权重/偏置表；格式、形状和 Graph 能力仍由运行时检查，不是“权重在 GPU 即可进图”的承诺。

`HybridExecutionScope` 与 `HybridPrefillScope` 管理混合策略、CPU 亲和性及步骤收尾；公共 workspace 管理共享张量。
MTP 的接受、拒绝、前缀提交和 KV 回滚只有一份语义实现。共享专家、远端专家合并及 buffer 有效期通过专家接口和图边界协调。

维护时必须保持图地址有效、张量释放顺序、TP 集合通信顺序和 MTP 回滚语义。模型回归需覆盖固定输入 logits、CPU fallback、Graph 捕获/重放、TP、前缀恢复和投机回滚；精度测试与稳态测速分开。

---

### Faq (`docs/faq.md`)

# 常见问题

## CMAKE

### CMAKE_CUDA_ARCHITECTURES must be non-empty if set.

**现象：**

> CMake Error at cmake/Modules/CMakeDetermineCUDACompiler.cmake:277 (message):  
>   CMAKE_CUDA_ARCHITECTURES must be non-empty if set.  
> Call Stack (most recent call first):  
>   CMakeLists.txt:39 (enable_language)

**解决办法：**

部分版本cmake存在该问题，需手动指定`CMAKE_CUDA_ARCHITECTURES`。执行：

```shell
cmake .. -DUSE_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=native
```

### Unsupported gpu architecture 'compute_native'

**现象：**

> nvcc fatal : Unsupported gpu architecture 'compute_native'

**解决办法：**

根据GPU型号手动指定GPU的[Compute Capability](https://developer.nvidia.com/cuda-gpus)。如：

```shell
cmake .. -DUSE_CUDA=ON -DCUDA_ARCH="61;75;86;89"
```

若需要支持多种GPU架构，请使用“;”分隔（如上面例子）。

### identifier "\__hdiv" is undefined

**现象：**

> src/devices/cuda/fastllm-cuda.cu(247): error: identifier "hexp" is undefined  
> src/devices/cuda/fastllm-cuda.cu(247): error: identifier "\__hdiv" is undefined  
> ...

**原因：** [计算能力（Compute Capability）](https://developer.nvidia.com/cuda-gpus) <= 5.3 的GPU不支持半精度计算。

**解决办法：** 如需要支持这些GPU，执行cmake时使用编译选项`CUDA_NO_TENSOR_CORE`：

```shell
cmake .. -DUSE_CUDA=ON -DCUDA_ARCH="52;61" -DCUDA_NO_TENSOR_CORE=ON
```

### undefined reference to `std::filesystem'

**现象：** 

> CMakeFiles/fastllm.dir/src/model.cpp.o: In function `fastllm::ExportLLMModelFromHF(std::string const&, fastllm::DataType, int, std::string const&, std::string const&, std::string const&, bool, fastllm::DataType, int)':
> model.cpp:(.text+0x10748): undefined reference to `std::experimental::filesystem::v1::status(std::experimental::filesystem::v1::path const&)'
> ...

**原因：** 8.0 版本之前的GCC （包括某些 GCC 8.X版本）并不完全支持C++ 17的 `<filesystem>` 库，某些环境需要手动链接。

**解决办法：** 设置`FASTLLM_LINKED_LIBS`，手动指定链接`stdc++fs`

```shell
cmake .. -DFASTLLM_LINKED_LIBS=stdc++fs
```

## Windows

### fastllm.h error

**现象：**

> include\fastllm.h(50): error : identifier "top_k" is undefined  
> include\fastllm.h(172): error : expected a "}"  
> include\fastllm.h(234): error : identifier "DataDevice" is undefined  
> ....

**解决办法：** 参考 [example\README.md](/example/README.md)。签出代码后，**修改 include/fastllm.h**，Visual Studio中点击”文件“ -> "高级保存选项"，在编码中选择”Unicode (UTF-8 **带签名**) -代码页 65001“，或在其他文本编辑器中转为”UTF-8 BOM“编码。（由于linux下gcc不识别BOM头，MSVC依赖BOM判断文件编码，该修改只能手动处理。）

### main.exe 无法识别中文输入

**原因：** Windows下cmd不支持UTF-8编码，

**解决办法：** 编译[Win32Demo](/example/README.md#win32demo-windows平台) 或使用 [WebUI](/example/README.md#web-ui)

### Windows（MSVC）编译下，int4出现乱码

**原因：** MSVC编译器优化选项 "`/Ob2`"、"`/Ob3`"与的现有代码冲突，

**解决办法：** 编译时，在”属性“中找到"C/C++" -> "优化" -> "内联函数扩展" 中选择“只适用于 \__inline (/Ob1)”。

### 导入提示 FileNotFoundError

**现象：**

> File "...Python\lib\ctypes\_\_init\_\_.py", line 374, in \_\_init\_\_  
>     self._handle = _dlopen(self._name, mode)  
> FileNotFoundError: Could not find module 'tools\fastllm_pytools\fastllm_tools.dll' (or one of its dependencies). Try using the full path with constructor syntax.

**解决办法：** 非CPU编译时，部分版本的python存在这一问题。

GPU编译时，根据使用的CUDA版本，将cudart cublas的相关dll文件复制到fastllm_tools同一目录下，例如：

* CUDA 9.2
  * %CUDA_PATH%\bin\cublas64_92.dll
  * %CUDA_PATH%\bin\cudart64_92.dll
* CUDA 11.x 
  * %CUDA_PATH%\bin\cudart64_110.dll
  * %CUDA_PATH%\bin\cublas64_11.dll
  * %CUDA_PATH%\bin\cublasLt64_11.dll
* CUDA 12.x 
  * %CUDA_PATH%\bin\cudart64_12.dll
  * %CUDA_PATH%\bin\cublas64_12.dll
  * %CUDA_PATH%\bin\cublasLt64_12.dll

## ftllm

### 释放内存报错： CUDA error when release memory

**现象：**
退出时报错：
> Error: CUDA error when release memory!  
> CUDA error = 4, cudaErrorCudartUnloading at fastllm/src/devices/cuda/fastllm-cuda.cu:1493  
> 'driver shutting down'

**原因：** python解释器在终止时常常会优先终止自己的进程，而没有现先析构调用的第三方库，因此在退出python时CUDA Runtime已关闭，释放显存操作失败。由于大多数时候显存已释放，并不会引起问题。

**解决办法：** python程序退出时，先显式调用 `llm.release_memory()`方法。

### ftllm加载报错

**现象：**
调用ftllm时报错，显示Load fastllm failed. (或者version `GLIBCXX_3.4.32' not found)

**原因：** 
可能是GLIBC版本低于运行要求，可能发生于Ubuntu 20.04以下版本

**解决办法：** 

##### 如果使用conda环境

尝试执行
```
conda install conda-forge::libstdcxx-ng
```

安装完成后，使用下面命令检查
```
strings $CONDA_PREFIX/lib/libstdc++.so.6 | grep GLIBCXX | sort | uniq
```

##### 非conda环境

在/etc/apt/sources.list文件末尾增加：
```
deb http://mirrors.aliyun.com/ubuntu/ jammy main
```

然后更新glibc
```
sudo apt update
sudo apt install libc6
```

更新完成后检查
```
ldd --version
```
若版本 >= 2.35，说明更新成功，此时应该可以成功载入ftllm了

若仍无法载入，可尝试[源码安装](../README.md#快速开始)

---

### Fp4 Kv Cache (`docs/fp4-kv-cache.md`)

# FP4 KV cache

Qwen3.5 架构的 CUDA 分页注意力支持 NVFP4 KV 存储，包括 Qwen3.8-27B-FP8。使用 `--kv_cache_dtype fp4` 开启；也接受 `nvfp4` 和 `fp4_e2m1`。默认仍为 `auto`。

双卡示例：

```bash
numactl -C 0-31 -m 0 ftllm server /path/to/Qwen3.8-27B-FP8 \
    --tp 2 --dtype auto --kv_cache_dtype fp4 --tokens 32768
```

Launcher 和终端向导的 KV Cache 类型选项也可选择 `fp4`。独立工作室连接已有 API server，缓存类型需要在模型服务的启动参数中设置。

## 范围与格式

- 当前支持 Qwen3.5 架构的 CUDA KV、128/256 维注意力头，CPU KV 不支持此格式。FlashInfer FP4 路径需要 CUDA 12.8 及以上、SM80 及以上 GPU；不满足条件、未编译 FlashInfer 或设置 `FASTLLM_FORCE_NATIVE_ATTN=1` 时自动使用原生注意力。
- 模型权重保持 `--dtype` 指定的类型；`--dtype auto` 保留源 FP8 权重。Q 和注意力计算仍使用 FP16/BF16，线性注意力的卷积缓存与 recurrent state 保留原有精度。
- 每 16 个 KV 值保存 8 字节 E2M1 数据和 1 字节 E4M3 缩放因子。包含缩放因子后，存储为 FP16 KV 的 **28.125%**、FP8 KV 的 **56.25%**。这些比例仅针对普通注意力 KV，不代表整个模型的显存占用比例。
- 每个物理页保存 `[packed KV][block scales]`，整页复制、回收和 MTP 验证页克隆会同时处理数据及缩放因子。缓存预算和实际分配都包含缩放因子。
- prefill、普通 append、融合 decode、批量追加及图捕获中的追加均写入相同格式。FlashInfer 读取打包数据和对应的 scale strides，不在显存中保留完整 FP16 KV 副本。
- 原生 fallback 使用软件解包，直接读取相同的 E2M1 数据和 E4M3 缩放因子，不依赖 FP4 硬件指令。prefill 复用分块 cuBLAS 工作区，decode 复用原生 split-KV 与在线 softmax；批量图重放从设备读取分页元数据。该路径用于兼容回退，性能可能低于 FlashInfer；原生分页注意力仍不支持滑动窗口。

FP4 是有损量化，可能改变 logits 和生成结果。内存节省不保证提高吞吐；在 4090 上解包与缩放也有计算成本。

## 验证

参数及 Launcher 透传测试：

```bash
python3 test/api/test_fp4_kv_cache.py
```

开启 CMake 的 `UNIT_TEST` 和 CUDA 后：

```bash
cmake --build build --target cuda_fp4_kv_test -j8
ctest --test-dir build -R '^cuda_fp4_kv(_native.*)?$' --output-on-failure
```

CUDA 测试检查空缓存不分配存储、物理字节数与预算一致、FP4 打包结果与独立 CPU 量化参考一致，以及注意力输出与同一量化数据反量化后的结果一致。覆盖非连续页、部分末页、128/256 维头、FP16/BF16 查询、短查询和最长 4097 token 的 KV；另检查融合 decode 与独立量化一致，以及融合写入的 CUDA Graph replay。

原生回退测试覆盖单序列入口、批量 prefill、split-KV decode 和注意力图重放。不同长度的双请求另与 CPU softmax 参考对照，图重放期间修改设备上的页数、物理页索引、末页长度和查询边界，检查分页元数据不会被固定在捕获时的值。覆盖 GQA 1–8 组、1/7/16/128 token 页、带 padding 的 Q head stride，以及最长 32769 token 的 KV。向量解包还覆盖全部有限非负 E4M3 scale 和 E2M1 打包值对。

`cuda_fp4_kv_native` 使用已有的 `FASTLLM_FORCE_NATIVE_ATTN=1` 检查公共接口的自动回退；另有关闭 GQA 共享、关闭 split 的测试，检查通用回退分支。FlashInfer 不可用时这些测试仍会运行。

## 原生 FP4 注意力优化

FP4 复用了原生 FP8 的以下优化：

- GQA 2–8 组共用 KV tile：每个 warp 负责一个 Q head，KV 读取和解包后通过共享内存供整组复用。共享内存按维度重排，避免连续读取落入少量 bank。
- 每个线程一次读取 4/8 个 FP4 值，复用对应的 block16 scale；共享 tile 还复用 E4M3 查找表。普通 per-head 回退与 prefill gather 也使用向量读取。
- 页游标同时跟踪打包数据和 scale 的物理地址，仅在跨页时重新查表，支持非连续页及短页。
- 在线 softmax 使用 log2 域，在最大值未改变时跳过重新缩放；split 结果使用同域的并行归约合并。工作区地址保持稳定，支持 CUDA Graph 重放。

这些优化不改变 FP4 量化格式，也不增加运行时环境变量。已有的 `FASTLLM_PAGED_GQA_DECODE=0` 和 `FASTLLM_PAGED_NO_SPLIT=1` 可用于对照。

四种分页写入入口共用类型分发和写入逻辑；测试还覆盖 FP32/FP16/BF16 输入到 FP32/FP16/BF16/FP8 存储的 48 种组合，检查已有格式的结果和页内偏移保持一致。

模型级验证应分别运行 FP16 KV 和 FP4 KV，固定模型、TP 配置、上下文容量与生成参数，记录显存、首字延迟、吞吐和 logits 差异，并检查长文检索与多轮输出。MTP、CUDA Graph 和多模态需要分别验证，不应仅以模型成功加载作为通过标准。

### 双卡验证范围

Qwen3.8-27B-FP8，RTX 4090D + RTX 4090，TP=2、32K KV 容量、CPU/内存绑定 NUMA 0。使用正常 CLI 的自动优化配置，开启 CUDA Graph、GPU token handoff 和前缀缓存，MTP 关闭：

| 指标 | FP16 KV | FP4 KV |
| --- | ---: | ---: |
| 测试结束时两卡显存合计 | 40042 MiB | 38634 MiB |
| 代码生成解码速度 | 57.38 token/s | 57.11 token/s |
| 4133 token 长文首次请求首字延迟 | 0.985 s | 1.065 s |
| 重复长文首字延迟（命中 4096 token 前缀） | 0.069 s | 0.070 s |

两卡合计节省 1408 MiB（约 1.38 GiB）。算术、代码生成和长文密码检索均通过；这是少量请求的实测，不能代替完整精度评测。关闭图执行和前缀缓存的 FP16/FP4 对照也通过。

FP4 和 FP16 API 对照测试还覆盖了图片首轮、携带原图的后续追问、图片 embedding 缓存命中及两条并发文本请求。双卡多模态入口使用 GPU 上的完整 embedding 副本。

对已启动的服务可运行图片追问回归检查（建议设置 `--max_batch 2`）：

```bash
python3 test/multimodal/fastllm_openai_image_followup_check.py \
    --base-url http://127.0.0.1:8080
```

使用正常 CLI 配置时，FP4 的 MTP 加 CUDA Graph 短请求检查通过，日志确认捕获了双卡 `verify=2` 的验证图。尚未进行 MTP 的完整吞吐或精度评测。

同一双卡模型设置 `FASTLLM_FORCE_NATIVE_ATTN=1` 后，8K KV 容量下通过了图片首轮与追问、双请求并发、4133 token 密码检索、4096 token 前缀复用和 MTP 短请求；日志确认 batch 1/2 解码图及双卡 `verify=2` 验证图均捕获成功。原生内核另通过 CUDA 12.9 的 SM70 编译检查，尚未在 SM70 实卡上验证 FP4。

格式及接口参考：[FlashInfer 注意力](https://docs.flashinfer.ai/api/attention.html)、[NVFP4 KV 分页量化](https://docs.flashinfer.ai/api/page.html)。本实现使用仓库已有的 FlashInfer 头文件，无需额外安装 Python FlashInfer。

---

### Glm5 (`docs/glm5.md`)

# GLM-5 / GLM-5.3-Flash 部署指南

[English](glm5_en.md) · [返回 README](../README.md) · [Agent 部署速查](model-deployment-agent.md) · [Benchmark](benchmarks/glm5.md)

当前路径覆盖 GLM-5 DSA、GLM-5.3-Flash KDA 与分页缓存，以及部分 GLM-5.2 量化 KV-B checkpoint 的纯 CPU 推理。

自动化部署建议先把本页和 [Agent 部署速查](model-deployment-agent.md) 交给 agent，并优先使用 `--auto`。下面的手动命令用于固定设备、缓存和量化路径。

## 自动配置（推荐）

~~~bash
ftllm server /data/models/GLM-5.3-Flash-UD-IQ2_XXS-00001-of-00004.gguf \
  --auto --model_name glm-5.3-flash \
  --host 0.0.0.0 --port 8080
~~~

GGUF 只需指定第一个分片，其他分片保留在同一目录。需要限制规划预算时，增加
`--auto_gpu_memory_limit 20g` 或 `--auto_host_memory_limit 128g`。

## 手动配置参考

~~~bash
ftllm server /data/models/glm5 \
  --model_name glm5 \
  --host 0.0.0.0 --port 8080
~~~

## GLM-5.3-Flash NVFP4 紧凑存储

GLM-5.3-Flash 的 ModelOpt NVFP4 路由专家在单设备 CUDA 后端（包括 `cudapp` 按层串行）及 NUMA 后端默认使用紧凑存储。`--moe_device numa` 的 CPU 专家计算和 CUDA 混合 prefill 共用该紧凑布局。

每 16 个权重保留 8 字节 FP4 数据和 1 字节 E4M3 块缩放，每行另存 4 字节全局缩放。合并 gate/up 权重时保留各自的全局缩放；这是存储布局转换，不重新量化权重。

## GLM-5.3-Flash GGUF

支持 `general.architecture=glm5next` 的 GLM-5.3-Flash GGUF，包括 Unsloth 的四分片 `UD-IQ2_XXS`。指定第一个分片即可加载其余分片，无需 `--ori`。当前路径支持文本推理及内置 NextN 草稿层的 MTP；`--mtp 0` 时不加载草稿权重。

路由专家保留 GGUF 混合量化格式；加载时还原 KDA 衰减参数和拆分、转置的 MLA KV-B 权重。支持 AVX512 BF16 的 CPU 在 IQ4_XS 单行计算时直接读取压缩权重，在寄存器中解码并完成 BF16 点积，省去临时 FP32/BF16 权重缓冲，保留原有舍入和累加顺序；多行计算和其他 CPU 继续使用分块 BF16 回退，权重不整体展开。KDA 的 128 维 Q8 投影使用 CUDA 反量化 GEMM，避免进入要求 K 维度按 256 对齐的 MMQ 内核。

CPU 词嵌入保留 GGUF 量化表，按 token 解码所需行，避免将完整词表展开为 FP32；显式 CUDA 词嵌入仍使用浮点导入。混合 TP 模式按层加载、恢复并上传非专家权重，随后释放 CPU 源数据，初始化 rank 时直接接管已上传的分片。NUMA 专家仍由各 rank 共享。这些优化自动启用，无需额外环境变量；分组上传目前针对 GGUF TP 路径。

双卡按层串行、单 NUMA 的启动示例（按机器调整线程数）：

~~~bash
FT_NUMAS=1 numactl --cpunodebind=0 --membind=0 \
  ftllm chat /data/models/GLM-5.3-Flash-UD-IQ2_XXS-00001-of-00004.gguf \
  --device cudapp=2 --moe_device numa --threads 20 \
  --dtype bfloat16 --atype bfloat16 --moe_atype bfloat16 \
  --kv_cache_dtype bfloat16 --chunked_prefill_size 8192 \
  --moe_cuda_cache 0 --moe_cpu_cache 0 --mtp 0
~~~

GGUF 的 BF16 混合推理可通过 `--moe_cuda_cache 12G` 为每张显卡设置 12 GiB 专家缓存，支持 IQ2_XXS/IQ2_S gate/up 与 IQ3_XXS/IQ4_XS down。GPU 保留压缩权重，并遵循 GGUF 的 Q8_K/BF16 激活、限幅和路由权重顺序。decode 与最多 9 行的小批验证使用缓存和动态分流，更大批次保留 NUMA 路径。支持的固定页 NUMA 布局直接复用专家权重，避免保存第二份完整主机快照；无法共享的布局保留快照回退。

上述 GGUF 类型配合 `--tp 0,1 --moe_device numa --moe_cuda_cache 16G` 时，默认按完整专家分卡缓存：专家 `e` 存在 `devices[e % devices.size()]`，遵循 TP 参数中的设备顺序。每卡独立维护频率策略和容量，只换入分配给本卡的专家，实际容量为运行时工作区预留显存后确定，允许各卡不同。缓存直接使用原生 NUMA/R4 权重布局，gate/up 和 down 在所属卡上完成；每条路由的 FP32 结果汇集后按原有 BF16 顺序归约。未命中专家继续使用 NUMA／多卡动态分流。prefill 在两卡上传时填充缓存，后续 chunk 和 decode 复用；decode/verify 继续更新频率和驻留专家。该模式自动启用，无需额外参数；单卡、零缓存及不支持的格式保留既有路径。

需要降低主存占用时，可将部分 MoE 层固定放到 CUDA，其余层使用 NUMA 动态分流。例如 45 层、前三层为 dense 的 GLM-5.3-Flash：

~~~bash
FT_NUMAS=1 numactl -C 0-31 -m 0 \
  ftllm chat /data/models/GLM-5.3-Flash-UD-IQ2_XXS-00001-of-00004.gguf \
  --device cuda:0 --tp 0,1 \
  --moe_device '{"cuda:0":7,"cuda:1":4,"numa":34}' --threads 20 \
  --dtype bfloat16 --atype bfloat16 --moe_atype bfloat16 \
  --moe_cuda_cache 0 --moe_cpu_cache 0 --mtp 0
~~~

该映射将第 3–6 层的全部路由专家放到 GPU 0，第 7–10 层放到 GPU 1（层号从 0 开始），其余 34 个 MoE 层保留在 NUMA。GPU 层在加载时上传并释放 CPU 权重，不占用动态专家缓存，不再参与 CPU/GPU 分流。TP 的注意力与共享专家仍由两卡协同计算；每个驻留 MoE 层由映射指定的一张卡计算路由专家。设备映射的数值按全部模型层分配，因此 GPU 0 的 7 层包含前三个 dense 层。

进一步降低主存可使用 `--moe_device '{"cuda:0":9,"cuda:1":6,"numa":30}'`，将第 3–8 层放到 GPU 0、第 9–14 层放到 GPU 1，每卡驻留 6 个 MoE 层。上述 UD-IQ2_XXS 权重比每卡 4 层配置多移出约 9.04 GiB 主机权重。双 24 GiB 显卡、512-token 输入和 640-token 输出的短测可运行，但显存峰值已达约 23.48 / 23.22 GiB；更长上下文需重新预留 KV 与工作区空间。这些驻留层包含 IQ4_XS down，prefill 会使用下面说明的 GEMV 路径，增加驻留层数不保证 prefill 提速。

要让纯 GPU MoE 层也参与张量并行，使用 `--tp 0,1 --moe_device numa --moe_device_layers 30`。`moe_device_layers` 指最后 30 个模型层使用 NUMA；前 15 层中包含 3 个 dense 层和 12 个 MoE 层。每个 GPU MoE 层按中间维度等分 gate/up 的行与 down 的列，各卡独立计算路由和专家分片，随后复用 FFN AllReduce 合并路由专家与共享专家的部分结果。其余 NUMA 层保持原有多卡动态分流。GGUF 分片按层加载并直接上传，保留压缩格式，不保存完整 CPU 副本；GLM GGUF 的 GPU 专家 TP 默认启用 64 MiB 权重 slab，减少大量小分配的显存浪费，可用已有的 `--cuda_slab` 参数覆盖。

在上述 TP 配置中使用 `--mtp 3 --speculative_algorithm mtp` 可开启最多 3 个草稿 token 的自适应深度 MTP，`--mtp_min_p` 沿用既有置信度阈值，0 表示关闭。目标模型多行验证仍使用 TP 和 NUMA 动态分流；草稿层在协调 rank 上运行 attention/共享专家，路由专家沿用最后一个目标层的 MoE 设备设置，词表输出头复用 rank 0 的存储。GGUF 草稿权重保留自身量化格式；不支持 GLM GPU 分流的格式（如此 IQ2_XXS 文件内草稿层的 Q2_K/Q3_K）走通用 NUMA 路径，不会关闭目标层的分流。MTP 不复用 history/prefix 快照。

多行验证保留单行推理的归约与舍入顺序。支持的 KDA 使用寄存器扫描处理任意正序列长度；MLA 的两个吸收投影直接读写原张量中的各行，复用单行 cuBLAS 调用，省去逐行拆分与拼接。无法使用直接行访问的布局保留原路径。TP 提交线程已绑定独立核心时，验证沿用 decode 的有限自旋等待，仍保留 NCCL 提交前后的跨 rank 同步。可用 `cuda_kda_prefill` 和 `cuda_matmul_single_rows` 回归检查递归状态及投影的逐位一致性。

GGUF 混推的小批量验证复用专家注册时的 NUMA 适用性检查结果。2–32 行验证中，持久协调线程推进 CPU 的 gate/up、激活和 down 阶段，GPU 上传与计算提交保留在原调用线程，避免 CPU 后续行等待 GPU 提交完成；CPU 数学计算仍使用配置的 NUMA 工作线程。无需额外开关，单行、其他权重格式和更大批次保留各自路径。`cuda_glm5_gguf_cache` 回归覆盖多行的逐位一致性、CPU/GPU 路由归属以及 GPU 提交异常后的 CPU 任务收尾。

驻留 GGUF 路径支持 BF16 激活、IQ2_XXS/IQ2_S gate/up 与 IQ3_XXS/IQ4_XS down，复用 GLM 缓存路径的限幅、score-before-down、量化边界和有序归约。支持 INT8 MMA 的 NVIDIA GPU（SM75+）上，超过 32 行且 down 为 IQ3_XXS 时自动按专家聚合 token，使用 grouped MMQ；保留每 256 个值一组的正 FP32 Q8_K scale 和最近偶数舍入，不引入 V4.1 的 FP8 量化边界。长输入按最多 1024 行分块，限制临时显存，无需额外开关。decode/小批量、IQ4_XS down 和不支持 MMQ 的设备保留原来的驻留 GEMV 路径；IQ4_XS 的 BF16 down 不会被改为 Q8。可运行 `cuda_glm5_gguf_resident` 验证两卡上的量化组合、32/33 行分派边界、1025 行分块尾部及 CPU 权重释放。

真实命中率应使用 `get_moe_cuda_cache_route_stats()` 在请求前后的差值计算 `resident_routes / routes`，覆盖分给 CPU 的专家，并排除预取查询；`get_moe_cuda_cache_stats()` 的 hits/misses 是查询计数，口径不同。

完整专家分卡缓存沿用 `--moe_cache_*` 参数；`--moe_cache_max_replacements` 和 `--moe_cache_max_bytes` 分别限制每卡每次更新的替换数量和上传字节数。命中路由归属实际计算卡，两卡的逻辑路由计数可以相加。C 接口 `fastllm_moe_cuda_cache_ep_stats()` 额外返回参与卡数、本卡槽位数、已填槽位数、权重字节数、计算的命中路由数及累计上传字节数。`cuda_glm5_gguf_ep_cache` 回归覆盖设备倒序、按专家编号分卡、单／双 NUMA 源布局、冷热替换和多种验证宽度，全命中结果与未分片 GPU 路径逐位对照。`cuda_glm5_gguf_ep_prefill` 和 `cuda_glm5_gguf_ep_numa_prefill` 检查缓存填充与复用、原生权重字节及 NUMA／双卡 prefill 数值。

测速时先用同长度输入完成预热，并关闭 prefix/history cache。启用 `UNIT_TEST` 后可运行 `glm5_next_gguf`、`numas_gguf_fallback`、`cuda_gguf_mmq_alignment` 和 `cuda_glm5_gguf_cache` 回归，分别覆盖分片映射与布局恢复、混合量化专家回退、窄投影的 CUDA 数值与边界、缓存的 CPU/GPU 分配与数值。

## GLM-5.3-Flash NVFP4 grouped Marlin

兼容的 CUDA 紧凑 NVFP4 路由专家默认使用 grouped Marlin，支持 BF16 激活、独立 gate/up 全局缩放和 GPU 路由。prefill 与 decode 共用一次重排后的权重布局；成功准备后释放原 GPU 布局。首次执行包含重排开销，测速需先预热。

GLM 的 `swiglu_limit` 会传入 Marlin 激活核和普通 CUDA 回退路径：gate 只限制上界，up 限制正负两侧，再计算 `SiLU(gate) * up`。非零限幅不会进入只支持普通 SwiGLU 的快捷路径。形状或缩放不满足 Marlin 条件时保留源权重并回退。

存储格式与计算路径自动选择。Marlin 的 Tensor Core 累加和 BF16 舍入与普通路径不同，整模 logits 和 greedy 文本不保证逐位一致。

启用 `UNIT_TEST` 构建后，可运行：
~~~bash
ctest --test-dir build-fastllm -R 'cuda_nvfp4_(marlin|compact)' --output-on-failure
~~~
回归覆盖零限幅行为、BF16/FP16/FP32 激活边界、带限幅的 CUDA 调度、1024-token 路由、4096/2048 top-8、CUDA Graph 重放，以及不支持形状和内存分配方式的回退。

## GLM-5.3-Flash KDA prefill

BF16 KDA prefill 在 head dimension=128、序列长度至少 64、FP32 QK 归一化、beta 舍入为 BF16、每 head 一个 `a_log` 时自动使用 CUDA 寄存器递推核。预处理并行计算 QK 归一化和 gate；每个 warp 负责 16 个 value 列，将 FP32 状态列保存在寄存器中，减少逐 token 的全局显存读写和同步。

实现保持原递推的累加顺序、独立的 decay 舍入及 gate 倒数后乘法，不使用三角分块求解重排。正常编译即可启用，无额外环境开关或 `LD_PRELOAD`。decode、辅助输出、state-only 续算、不匹配的形状或参数，以及 CUDA Graph capture 保留原 CUDA 路径。

临时空间通过现有 CUDA workspace 管理器复用，需求为 `batch × sequence × heads × (3 × 128 + 1) × 4` 字节；batch=1、sequence=1024、heads=64 时为 96.25 MiB。该空间在设备上复用，不按层重复常驻分配。

启用 `UNIT_TEST` 后运行：
~~~bash
ctest --test-dir build-fastllm -R '^cuda_kda_prefill$' --output-on-failure
~~~
回归逐位比较逐 token 递推与寄存器路径的 BF16 输出和 FP32 最终状态，覆盖边界长度、多 batch、长记忆 gate、零值与极小值 QK、初始状态、混合长度续算和 CUDA Graph 重放。

## GLM-5.3-Flash KDA causal convolution

CUDA causal convolution 沿 token 维度增加 block 并行，在 1024 token、8192 channels 时使用 4096 个 block。每个线程仍按原顺序累加卷积 tap，历史缓存由后续同一 stream 的更新核处理。该改动适用于共用此算子的模型，无需额外环境开关、临时显存或预加载库；单 token decode 仍使用原来的 block 数。

## GLM-5.3-Flash MLA prefill

CUDA BF16 prefill 在 query 长度至少 64、QK/V 维度为 256、压缩 KV rank 为 512 时，默认按 attention head 分组临时展开完整历史 K/V，复用现有 `MatMulTransB` 和 FlashInfer `AttentionPaged`。长期缓存仍保存压缩 KV，decode 使用原来的 absorbed MLA。不需要额外运行开关或预加载库；不改变原有 dense attention 语义，也未引入 DSA indexer。

每组展开 K/V 的活动空间上限为 256 MiB，另需 latent、输出和 attention workspace。该上限不包含分配器缓存，不能视为整个设备的额外显存上限。短 query、已有精确小 batch 模式、CUDA Graph capture、不支持的类型或布局，以及展开工作区容量不足时保留原路径。

展开 K/V 改变 BF16 舍入顺序，不保证与 absorbed MLA 的 logits 或 greedy 文本逐位一致。7 组 512/2048/16384-token 输入各生成 8 token，其中 3 个短输入样本分叉，37/56 个 token 位置相同；16K 样本的 8 个 token 相同。仅比较相同前缀的 40 个位置时，logits 最大绝对差为 4.125、最大 RMSE 为 0.5238，概率分布最大 TV 为 0.2246。该抽样不替代完整模型质量评测。

启用 `UNIT_TEST` 后运行 `ctest --test-dir build-fastllm -R '^glm5_next_mla_prefill$' --output-on-failure`。测试以独立 CPU 双精度计算为参考，覆盖不同 head 分组、碎片化页、尾页、带历史的 causal mask、压缩缓存内容不变及回退；CUDA memcheck 为 0 errors。当前实测硬件为 RTX 5090。

## GLM-5.3-Flash learned DSA

CUDA BF16 的 11 个 DSA 层加载 checkpoint 中的 Indexer 权重，执行 32-head、128-dim 的学习式索引。KPool-4 使用逐维 gate + APE 的 softmax 汇聚，经过 BF16、归一化 Hadamard 和 UE8M0 scale 的 E4M3 舍入后，对可见完整组评分，选择最多 512 组，展开成 2048 个 token，再附加当前未完整组的 0–3 个 token。2048 token 以内仍全选，但同时维护索引缓存。

接入复用 DeepSeek-V4 的压缩汇聚、DeepSeek-V4.1 的评分/Top-K/512 维稀疏注意力、Qwen4 的组索引展开和 Naive 的 FP8 量化。复用现有 LayerNorm、量化器及 host 接口。为复用 BF16 Tensor Core 评分，FP8 舍入后的值按 2 的幂反量化后保存在 BF16 中。分页 latent KV 保持原格式，稀疏注意力临时收集连续历史；KPool 缓存和尾组随 chunk、decode 和历史快照保持同步。

当前支持 compressed MLA，包括 MTP 多行验证。目标层按 rank 保存 KDA 状态和 DSA KPool 尾部；拒绝草稿后按接受前缀恢复 KDA、截断 KV 页并重新生成受影响的 KPool 分组，草稿层也独立回滚其 KV 和索引缓存。expanded attention 尚未接入 learned DSA。`FASTLLM_GLM5_NEXT_DSA_BACKEND=dense` 可恢复原 dense 路径用于对照。稀疏选择会改变长输入的注意力语义，不保证与旧 dense 路径生成相同 token，不同 GEMM 后端也不保证逐位相同。

启用 `UNIT_TEST` 后可运行 `ctest --test-dir build-fastllm -R '^glm5_next_dsa$' --output-on-failure`。回归包含独立 LayerNorm、Hadamard/FP8、KPool 参考计算，非 4 倍数分块与尾组，2048 和 32K 附近的因果 Top-K，以及连续/碎片化分页 latent attention。

## GLM-5.3-Flash FlashInfer sparse prefill

显式 `CUDA_ARCH` 列表包含 SM120（例如 `-DCUDA_ARCH=120` 或 `'-DCUDA_ARCH=80;120'`），且 CUDA 编译器版本至少为 12.9、未设置 `CUDA_NO_TENSOR_CORE` 时，默认编译 FlashInfer `GLM53_NOPE` sparse prefill。运行时只在 SM120、单 batch、BF16、64 attention heads、latent rank=512、query 至少 64 token、连续输入且不在 CUDA Graph capture 内时使用。其他情况使用现有 BF16 DSA attention。DSA 的前 2048 token 仍使用 dense 全选路径。

该路径直接复用固定版本的 FlashInfer `swapAB` kernel，并用现有 Dots3 `QuantizeKKernel` 将临时收集的 BF16 latent KV 转成每 128 维一组的 E4M3 + FP32 scale。每个 token 为 528 字节；2051 宽的选择结果用 `-1` 补齐到 2112，保留完整因果尾组。softmax scale 继续使用模型的 `1/16`。Q/输出接口为 BF16，kernel 内部 Q、KV 和概率使用 FP8 运算。分页缓存和历史快照仍保存 BF16，每个 chunk 重新量化临时历史，不持久化 FP8 状态。

只需现有 CUDA/C++ 构建环境。`third_party/flashinfer_glm53` 固定上游 `7eb86aa0fdc1248fab43c89801de4ed450e35e77` 的最小头文件集合，保留逐文件 BSD-3-Clause 和项目 Apache-2.0 许可。两个 fast-div 兼容头复用现有 vendored 实现，以兼容 CUDA 12.9。新头文件只对专用 object target 可见，kernel 单独生成 `sm_120a` cubin。无需安装 FlashInfer Python 包、PyTorch或 Triton。

需要保留 BF16 DSA attention 时，在进程启动前设置：

~~~bash
FASTLLM_GLM5_NEXT_DSA_BACKEND=bf16 ftllm server /data/models/glm5.3-flash --mtp 0
~~~

后端统一由 `FASTLLM_GLM5_NEXT_DSA_BACKEND` 控制，在模型初始化时读取：

| 值 | 行为 |
| --- | --- |
| `auto`（默认） | learned DSA；满足条件时使用 FlashInfer，否则回退 BF16 |
| `bf16` | learned DSA，始终使用 BF16 sparse attention |
| `dense` | Dense attention；expanded 模式使用此路径，compressed MLA 的 MTP 可使用 DSA |

未设置或空值等同 `auto`，其他值会报错。生成 token 不保证与 BF16 后端一致。

FP8 sparse attention 与 BF16 后端的误差可能超过一般 BF16 舍入差异。已记录的 16K/32K 首步 logits 相对 L2 差异为 18.9%/24.6%，余弦相似度为 0.9822/0.9707；有限样本的 greedy token 相同不能替代完整质量评估。32K 输入为 16K 序列重复，不用于评判长文质量。

测量配置和最终吞吐见[Benchmark](benchmarks/glm5.md)。启用 `UNIT_TEST` 后运行：

~~~bash
ctest --test-dir build-fastllm -R '^glm5_next_(flashinfer.*|dsa|mla_prefill)$' --output-on-failure
~~~

回归包含独立 E4M3 打包、double softmax、全屏蔽行、单 key、选择索引补齐、非整块 query、碎片化分页历史以及 dtype/layout/Graph capture 回退。运行验证覆盖 RTX 5090；其他架构的编译检查不代表运行性能或精度已验证。

## GPU + NUMA 混合 MoE

~~~bash
ftllm server /data/models/glm5 \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192 \
  --gpu_mem_ratio 0.9
~~~

该布局适合模型主体和热点路径放在 GPU、专家权重放在多路 NUMA 内存的机器。

## GPU + CPU

~~~bash
ftllm server /data/models/glm5 \
  --device cuda --moe_device cpu \
  --chunked_prefill_size 8192
~~~

## GLM-5.2 量化 KV-B 纯 CPU

仅适用于已验证的量化 KV-B checkpoint：

~~~bash
ftllm server /data/models/glm5.2-quantized-kvb \
  --device numa --moe_device numa \
  -t 64
~~~

`-t 64` 只是多路服务器示例，应根据物理核心数和内存带宽重新测试。

## 长上下文、思考与工具调用

~~~bash
ftllm server /data/models/glm5 \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --enable_thinking true \
  --tool_call_parser auto
~~~

GLM-5.3-Flash 的 KDA、分页历史缓存和 NUMA 解码流水会根据模型结构自动选择。

## Benchmark

限定配置的最终 prefill 测量及限制见[GLM Benchmark](benchmarks/glm5.md)。

---

### Glm5 En (`docs/glm5_en.md`)

# GLM-5 / GLM-5.3-Flash Deployment

[中文](glm5.md) · [Back to README](../README_EN.md) · [Benchmark](benchmarks/glm5_en.md)

Current support covers GLM-5 DSA, GLM-5.3-Flash KDA and paged caching, and pure-CPU inference for selected GLM-5.2 checkpoints with quantized KV-B weights.

## API server quick start

~~~bash
ftllm server /data/models/glm5 \
  --model_name glm5 \
  --host 0.0.0.0 --port 8080
~~~

## GPU + NUMA hybrid MoE

~~~bash
ftllm server /data/models/glm5 \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192 \
  --gpu_mem_ratio 0.9
~~~

## GPU + CPU

~~~bash
ftllm server /data/models/glm5 \
  --device cuda --moe_device cpu \
  --chunked_prefill_size 8192
~~~

## Quantized KV-B GLM-5.2 on CPU

This path requires a validated quantized KV-B checkpoint:

~~~bash
ftllm server /data/models/glm5.2-quantized-kvb \
  --device numa --moe_device numa \
  -t 64
~~~

The thread count is an example for a multi-socket server and must be tuned for physical cores and memory bandwidth.

## Long context, thinking, and tools

~~~bash
ftllm server /data/models/glm5 \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --enable_thinking true \
  --tool_call_parser auto
~~~

GLM-5.3-Flash KDA, paged history caches, and NUMA decode pipelining are selected from the model architecture.

## Benchmark

Scoped GLM-5.3-Flash prefill measurements and their limits are available on the [benchmark page](benchmarks/glm5_en.md).

---

### Hybrid Auto (`docs/hybrid-auto.md`)

# 混合推理自动配置

使用 `--auto` 可以一次选择完整的 CUDA 运行配置：

```sh
ftllm server /path/to/model.gguf --auto
```

支持 Linux NVIDIA CUDA 上的 dense Qwen3、Qwen3.5 架构的稠密 HF 模型，以及 Qwen4、GLM-5.3、DeepSeek-V4.1 混推模型。未启用 `--auto` 时，原有数值和默认行为保持不变。

Qwen3.8-27B-FP8 已验证 `--auto`：优先选择纯 GPU TP，默认并发 4、模型原生上下文和内置 MTP5，显存不足时降低上下文。并发 MTP 验证默认采用 eager CUDA 路径，以避开批量验证 CUDA Graph 的非法内存访问；可用 `FASTLLM_QWEN35_MTP_VERIFY_CUDA_GRAPH=1` 显式覆盖。其他 Qwen3.5 架构 checkpoint 的量化格式和 MTP 权重仍需分别验证。

直接运行 `ftllm` 打开的 Launcher 中，新增启动项默认选择“自动配置”，只需选择本地模型并填写模型名、监听地址、端口等服务信息。启动命令使用 `--auto`，每次启动重新读取硬件余量；表单中原有的手动推理参数和环境变量不会带入这个模式。原有长上下文、高并发和自定义模式仍保留，已保存的配置不会自动切换。

自动配置设置中可以填写可选的“显存上限（每卡）”和“内存上限”。它们分别对应
`--auto_gpu_memory_limit` 与 `--auto_host_memory_limit`，接受 `20g`、`128g` 等二进制容量；留空时继续读取启动时的实际可用显存和主机内存。显存上限作用于每张可见 GPU，主机内存上限作用于整个自动规划预算。这是规划上限，不会限制操作系统或其它进程的实际内存分配；规划结果不足以容纳模型时会在加载前报错。

Dense Qwen3 按实际 GQA 头数、KV、dense 权重及 tied output head 副本预算，不分配专家缓存；上下文取 128K 与模型原生窗口的较小值。目前 Qwen3 的 CUDA Graph 预热会触发 deferred pool 的 stream-capture-unsupported，因此此模型的 `--auto` 默认使用普通 CUDA 执行。显式 `FASTLLM_CUDA_GRAPH` 仍优先；其他模型的 Graph 默认策略不变。

Qwen3.5 稠密 HF 模型按全注意力层的 GQA KV、线性注意力层的常驻状态、原生 FP8 权重和独立 MTP 层预算。先尝试原生上下文与纯 GPU TP2，必要时尝试 TP4 和较短上下文；显式 `--tp`、`--max_batch`、`--max_context_length`、`--tokens`、`--mtp 0` 或 `--speculative_algorithm off` 优先。内置 `mtp.safetensors` 且配置声明一个草稿层时默认启用 MTP5。默认 `--cuda_embedding`，不自动启用视觉输入。

## 单参数自动选择

DeepSeek-V4.1 默认采用不超过模型原生窗口的 **1M（1,048,576 token）上下文、单请求**，开启 **DSpark 7**（置信度阈值默认 0.5）和 **fast_prefill**。Qwen3.5 稠密 HF 模型默认并发 4、尽量使用原生上下文；其他模型保持 **128K 上下文、单请求**。按代表性的 4K 输入、512 输出估计交互延迟。读取可见 CUDA 设备的可用显存、SM 数、内存带宽参数、PCIe 条件和 peer access 能力，比较单卡、PP、双卡等比例 TP，并联合选择专家驻留、显存缓存、主存缓存和 chunk。权重可全驻留且预算允许时，也可选择全部专家放 GPU。CPU 线程依据进程可用的物理核数选择，并预留启动和 IO 线程。

DeepSeek-V4.1 的 `--auto` 默认使用 **`kv_cache_dtype=fp4`**：长期压缩 KV 和 indexer key 在原模型计算中已经经过 FP4 伪量化，按匹配的网格、分组和 scale 打包是无损存储；滑窗使用其原有 FP8 网格。Flash-1017 的长期 KV 为 **890 B/token**，1M 有效载荷约 **0.869 GiB**，加上当前分配器的扩容容量和新旧缓冲重叠，规划预留约 **1.59 GiB**。此前统一选择 16 位 KV 会将这两项分别放大到 3.125 / 5.69 GiB。显式 `--kv_cache_dtype`（包括 `float16`、`bfloat16`、`fp4_e2m1`、`auto`）优先；启用 `FASTLLM_DSV41_DISABLE_FAKE_QUANT` 或不满足打包维度时不自动启用 FP4。其他模型的存储默认不变。

DeepSeek-V4.1 的 `fast_prefill` 沿用已有近似注意力语义，可能改变 logits。可用 `--no_fast_prefill` 关闭；`--dspark 0` 或 `--speculative_algorithm off` 关闭推测解码。显式上下文、草稿数、阈值和 JSON 配置中的 `fast_prefill: false` 均优先；`--threads 64` 可固定线程数。预算不足时先调整自动 chunk、缓存和专家放置，仍放不下则报错，不自动缩短 1M 上下文。

DeepSeek-V4.1 的显存估算按 KV source 层统计共享的压缩 KV 和 index key，并计入原生缓存扩容的容量余量及新旧缓冲重叠。Indexer 打分按原生分块预算估算（默认 128 MiB，遵循 `FASTLLM_DSV41_INDEX_SCORE_MB` 和 `FASTLLM_DSV41_INDEX_CHUNK`），不直接预留完整的 `chunk × context` 分数矩阵。原模型的 FP8 投影按实际保留的精度及 scale 预算，输出头、路由和其他强制展开的投影仍按原生精度计入。DeepSeek-V4.1 磁盘专家的自动 chunk 候选为 **8192、4096、2048、1024、512、256**；其他路径的候选上限保持 4096。候选不超过上下文长度；显式指定数值时不会自动降低。

规划日志分别输出 `kv_logical_bytes_per_device`（有效载荷）、`kv_capacity_bytes_per_device`（含增长余量的容量）和 `kv_reallocation_bytes_per_device`（扩容时的旧缓冲重叠），避免将有效 KV 大小和规划峰值混为一谈。

DeepSeek-V4.1 原生 FP8/MXFP4 模型在单请求、非 TP、BF16、磁盘专家和 `fast_prefill` 路径下，按 attention、WoA、MoE 分阶段的临时缓冲峰值估算工作区。MoE 使用复用的 256 行专家缓冲，WoA 保留 FP32 fallback 的权重展开和分块空间；调试 dump、其他精度、GPU 驻留专家等路径继续采用通用保守估算。CUDA graph 隐式启用的 GPU embedding 也计入预算。

磁盘混推的 `--auto` 在上下文预算不变的前提下优先选择最大的可行 chunk，并比较正常 embedding 与 `low_gpu_mem`。仍需混推时优先将 embedding 留在 CPU，为 chunk 和专家缓存腾出显存；全部专家可放 GPU 时保留正常模式。显式 `--cuda_embedding`、`--low_gpu_mem`（包括 JSON 中的 false）和 chunk 优先。此策略保留默认 `gpu_mem_ratio`、512 MiB guard、完整上下文 KV 及扩容重叠预算，不把短请求的实测显存当作 1M 上下文上限。

主存优先使用 NUMA（构建支持时），否则使用 CPU；超大 PLE/Engram 表优先按需读取，减少启动搬运和主存占用，主存仍放不下时再考虑磁盘专家缓存。CUDA Graph 默认开启，原有 `FASTLLM_CUDA_GRAPH` 显式设置仍优先。原有运行时检查继续决定 Graph 能否实际捕获。

Qwen4 混推在上下文容量达到 64K 时，优先使用每层 32K 显存池的精确 QSA KV streaming，将腾出的显存用于专家缓存或驻留。计入完整 KV、工作区和安全余量后，若各卡仍能容纳自己负责的全部专家（驻留或缓存），则完整显存 KV 也参与延迟比较。32K 池不可行时再尝试 16K、8K 池；较短上下文仅在完整 KV 放不下时考虑 streaming。所有方案保留逻辑上下文容量；完整 FP16 KV 放在 pinned 主存，索引仍覆盖全部上下文。预算包括主存 KV、GPU 页表和 prefill 临时使用的一整层 KV，保留原有显存安全余量。主存专家加 KV streaming 的优先级高于磁盘专家；主存不足或 streaming 不可行时仍可退回完整显存 KV。若仍放不下，加载前报错，不自动缩短上下文。

自动 KV streaming 仅用于现有支持的 Qwen4 FP16 QSA、单请求混推路径，可使用单卡、PP 或 TP；不启用其他模型的近似 KVMem。它关闭 prefix/history 复用，但保留原生注意力选页；模型检测自动填入的历史缓存默认值不会阻止规划。用户通过命令行、JSON 或现有 prefix-cache 环境变量显式开启复用，或跳过 warmup、指定其他精度、多请求、指定不等于单请求上下文的总 token 容量时，不自动启用。显式 `--kvmem` 仍需配合原有手动参数。最终 `[auto]` 日志中的 `automatic.kv_streaming` 和 `kvmem` 给出实际选择。

这些是快速估计规则，不是实测吞吐或完整调优：不会为了挑选配置反复加载模型。缓存局部性、PCIe 和算子效率的估计有误差；先应用上述 KV 容量策略，再比较预计延迟。估计延迟相差不超过 10% 时，优先选择较少设备和集合通信的方案；同一拓扑在显存能容纳全部专家时优先保留完整 KV。实际请求也可能更适合另一种模式。Qwen3.5 稠密 HF 模型自动搜索双卡和四卡等比例 TP；其他模型的自动 TP 搜索仍限双卡、相近规格。多卡 PP 和显式等比例 TP 继续沿用现有预算校验。

Qwen4 的纯自动选型优先采用可行的单卡或 PP 方案，减少混推解码中的逐层 TP 归并；同一主存后端没有可行 PP 配置时，仍可退回 TP。避免磁盘专家的优先级高于 PP 偏好：先搜索主存专家后端的驻留、缓存和 chunk 组合，包括可行的 TP，全部放不下才考虑磁盘专家；PLE/Engram 按需读取独立于专家后端。显式 `--tp` 优先，其他模型继续按延迟估计选择。

Qwen4 自动混推在主存充足时优先将目标专家全部放在主存，把可用显存用于动态专家缓存；主存不足时仍可选择部分整层驻留，避免过早使用磁盘专家。能全驻留 GPU 的模型仍保留全 GPU 方案。PP 的自动缓存按每张卡的权重、KV、MTP 和工作区分别核算，不再由最紧张的卡统一限制；`moe_cuda_cache_per_device` 给出各卡字节预算，`moe_cuda_cache` 为其中的最大值。显式缓存和显式驻留仍优先；TP 缓存仍使用统一的每卡预算，其他模型保持原有选择规则。

显存预算保留 `gpu_mem_ratio` 对应的预留量（默认总显存的 10%），并另外计入每卡 512 MiB 的 Graph、库句柄和分配器保护空间；不会将实测剩余显存全部分配给专家缓存。Qwen 的预算还包括 Graph 默认启用的 CUDA embedding，GGUF 词嵌入按实际展开后的 FP32 大小计算；多卡 PP MTP 自动采用 CPU embedding 时免去对应显存开销。这是容量估计，不是运行时显存硬上限；CUDA 分配器可能保留可复用的空闲缓冲，因此驱动看到的显存峰值可能高于规划估算。实际余量仍取决于请求形状和运行时分配。

显式参数优先，包括关闭缓存的 `0`、全部主存专家的 `--moe_device_layers -1`、`--tp off`、线程数、精度和阈值。除上述带内置草稿权重的 Qwen3.5 稠密 HF 模型外，仅添加 `--auto` 不会启用 MTP，需指定 MTP 或提供草稿模型。若指定并发而未指定总 token 容量，会按上下文乘以并发预留。不会为了通过预算缩小用户指定的上下文或并发。JSON 配置中的设置也作为显式值处理；`--auto` 下同时给出的命令行设置优先于 JSON。

使用 `--auto --draft /path/to/mtp.gguf` 指定 Qwen4 外部 MTP GGUF 时，默认最多生成 3 个草稿 token，加上 anchor 为 4 行目标验证，匹配现有验证 CUDA Graph 的宽度。显式正数 `--mtp`、`--draft_tokens` 优先；阈值默认关闭，可用 `--mtp_min_p` 指定。保留草稿路径但关闭推测解码时，使用 `--speculative_algorithm off`。

Qwen4 混推开启 MTP 且自动选到多卡 PP 时，若未显式指定 embedding 策略，会启用现有 `low_gpu_mem` 配置，使用 CPU embedding，避免目标首级与草稿末级之间反复搬运整份词嵌入权重。显式 `--cuda_embedding`、`--low_gpu_mem` 或 JSON 中对应的布尔值保持优先；单卡、TP、纯 GPU 和其他模型不应用这条规则。

Qwen4 的跨卡 prefill 流水线要求驻留专家与对应的普通层位于同一张 GPU。跨阶段驻留时，保留原有双卡布局并采用串行分块 prefill；解码配置不变。全部主存专家或同阶段驻留仍可使用流水线。

已有线程环境变量仍遵循原生 NUMA 运行时的优先级：`FT_THREADS` 优先于 `FASTLLM_NUMA_THREADS`，后者按有效 NUMA 节点数换算总线程数。未显式指定线程时，自动规划采用同样的顺序；这些环境变量也可能覆盖命令行线程数。

```sh
# 自动选择其余参数，固定 16K 上下文和双请求容量
ftllm server /path/to/model.gguf --auto --max_context_length 16384 --max_batch 2

# 固定 PP 模式，其余自动选择
ftllm server /path/to/model.gguf --auto --device cudapp=2
```

启动打印 `[auto]`，给出最终模式、设备、线程、chunk、驻留、缓存、上下文及内存预算；`model.hybrid_auto_plan` 保存同一结果。权重精度、路由数学和采样算法保持原有实现。

## 混合推理共用的 prefill 专家缓存

CUDA/NUMA 混推在有专家显存缓存时，默认让 prefill 和 decode 复用同一份缓存与热度策略。这个运行时能力也适用于手动配置，不依赖 `--auto`。Qwen 标准 MoE、DeepSeek-V4.1、GLM-5.3 共用权重传输层：原生布局一致时直接借用显存记录；布局不一致时在 GPU 上还原原有 NUMA 字节布局，包括 gate/up 行序和 FP8/NVFP4 scale。未命中的专家继续临时上传，符合热度策略的记录写入有容量上限的缓存。

原生格式的缓存已有 decode 历史时，prefill 保留已有记录、decode 热度和更新周期，只按当前路由热度填充空槽。已满缓存的替换交给后续 decode 观察决定，避免长提示词覆盖 decode 工作集、导致 prefill 提升却生成变慢。冷缓存仍可按原有 prefill 热度策略装入专家；GGUF 保留原有阶段策略。

支持现有共享 NUMA 缓存注册的标准 FP8（block128/per-channel）、NVFP4（float-scale/planar/compact）、DeepSeek-V4.1 FP8 block128/NVFP4 block32，以及 GLM-5.3 原生 FP8 block128/compact NVFP4。GGUF 保留原有 grouped prefill 路径。没有可用缓存或布局不受支持时，继续执行原有主存路径。

调度器根据整层 CPU/GPU 并行执行的实测耗时调整分流；原生 DeepSeek-V4.1 和 GLM-5.3 也使用这一反馈路径。原生逐专家路径中，低路由量的已驻留专家仍可分配到 CPU，避免缓存命中引入过多小算子开销；分配到 GPU 的已驻留专家使用拥有其记录的设备。显式 `FT_EXPERT_LIMIT` 固定 CPU/GPU 分流阈值，但不固定多个 GPU 之间的专家分配；后者仍可依据实测耗时调整，BF16 分组归约不保证跨次逐位一致。辅助 GPU 可以读取已有缓存，写入仍遵循原有 owner/admission 规则。

该优化保留现有投影、激活、量化和归并算子，不启用 `fast_prefill` 的近似注意力。缓存命中仍需要计算；需要布局还原时还会有 GPU 内存读写，因此 prefill 加速幅度取决于缓存覆盖率、PCIe 搬运与算子耗时。

排查或对照测试可设置 `FASTLLM_MOE_PREFILL_CACHE=0`，同时旁路 prefill 的驻留调度、缓存读取和写入，不关闭 decode 缓存；正常运行无需设置。在两次请求之间读取 `ftllm.llm.get_moe_cuda_cache_prefill_stats(device=0)` 并相减，可得到共享原生路径的调用数、GPU 专家/路由数、命中专家/路由数、避免上传的字节数、实际上传字节数和新写入专家数。统计读取会同步 GPU，不能放入吞吐计时区间；不包含独立 grouped GGUF 后端，GPU 内部布局还原也不计入上传字节。

## DeepSeek 混推的分块预填充与校验

启用 `--fast_prefill` 后，DeepSeek-V4.1 对满足 bounded replay 条件的单请求分块预填充，只在最后一块执行后段 SWA 层与输出头；前面的块更新长期 KV。末块不超过 SWA 窗口、增量续写、图像输入或 DSpark 等情况沿用原路径。该优化沿用 `fast_prefill` 的近似注意力语义；该模型的 `--auto` 默认开启，手动启动仍需显式开启。

`FASTLLM_DSV41_HYBRID_PP=1` 显式启用混推跨块流水线，默认关闭。它要求 CUDA PP 层放置、NUMA 专家、`fast_prefill`、单请求 greedy，以及各级独立拥有其使用的共享 KV/index 来源；例如已验证的双卡 `--device cudapp=14:26 --tp off`。专家显存缓存、DSpark、历史复用或不兼容的层切分等情况会沿用普通路径。该入口不用于纯 GPU 或 TP 执行。

DeepSeek-V4.1 原生 FP8/NVFP4 专家缓存随 `--moe_cuda_cache` 启用，不需要额外的频率策略开关。单卡 DSpark 的 2～8 token 校验在 Graph 开启时也使用现有缓存和动态分流，保持图段的缓冲地址；无可用 GPU 专家时保留 NUMA 回退。`get_moe_cuda_cache_route_stats` 同时统计普通解码与校验的路由，需在请求结束后读取。DeepSeek-V4.1 的 `--auto` 支持并默认开启内置 DSpark：所有启用草稿层的权重、BF16 滑窗状态、目标层 hidden 特征和验证临时空间均计入预算。草稿稠密部分按最后一张 PP 卡核算（TP 使用保守复制上界），草稿专家沿用最后 N 个主干层的专家放置；不会为纯滑窗草稿分配整段 1M KV。外置 DSpark 仍需手动配置。

## DeepSeek FP8 WoA 的通用 prefill GEMM

DeepSeek-V4/V4.1 的 CUDA WoA 投影在 token 数不小于 16 时，默认尝试分组 GEMM，并按设备能力使用 Tensor Core；混合推理和全 GPU 推理共用入口，不依赖 `--auto`、专家缓存或 `fast_prefill`。支持可变 token 数、batch、分组数和输出 rank，以及 FP8 block32、block128 和矩形量化块；非对齐的 K/N、token 尾块和不足 128 个元素的输入量化尾块均有边界处理，不要求固定 SM 数。原有 SM120 的 7/8-token 特化仍保留；其他小批量走原有归约路径，奇数 rank/量化行块走逐行归约。

WoA 根据实际设备能力和数值条件选择计算路径，所有路径都保留已有的 FP8 权重解码到 FP16 的舍入：

- SM80 及以上，BF16 输入且所有权重 scale 均为 float32 正规数表示的 2 的整数幂或正零时，解码结果可以精确表示为 BF16。直接使用 BF16 Tensor Core、FP32 累加并输出 BF16，省去输入展开和独立输出转换。每次调用检查实际 scale，不使用可能失效的指针缓存。
- SM80 及以上的其余 BF16/FP16 输入，将输入和解码权重精确展开到 FP32，使用 TF32 Tensor Core 和 FP32 累加，再舍入到 BF16。FP16 权重与 BF16/FP16 输入都能精确表示为 TF32，不额外损失乘法输入精度。
- SM75 使用 pedantic FP32 cuBLAS GEMM；未量化的 FLOAT32 输入也使用该路径，避免 TF32 截断。更早设备或 cuBLAS 拒绝时继续使用通用归约实现。

SM120 上仍保留原有 WoA 输入 E4M3/UE8M0 量化。行宽能被 128 整除时，每个 warp 处理一组 128 个元素，用 shuffle 完成最大值归约、一次计算并广播 scale，再进行四元素 FP8 转换；非对齐行继续走有边界检查的实现。GEMM 的累加顺序与原有 SIMT 归约不同，结果允许 BF16 精度内的数值差异，不承诺投影逐位一致。SM120 以下仍按原有行为不执行这一步输入量化。

展开后的权重只在本次调用中使用，不建立跨请求的常驻权重缓存。真实 G=8、N=1024、K=4096 形状的 BF16 路径只需 64 MiB 权重工作区，直接读取输入、写入输出，默认每次 GEMM 处理最多 4096 行。TF32/FP32 路径另需 128 MiB 权重工作区，token 工作区按最多 1024 行、通常不超过 160 MiB 分块（单行超过该预算时仍保留一行）。它们由现有 CUDA 内存池管理和复用，不能等同于请求结束立即归还驱动的显存。输入量化和最终输出的现有存储另计。

无需新增启动参数。A/B 排查可设置以下环境变量，恢复时取消变量：

- `FASTLLM_DSV4_DISABLE_CUDA_WOA_GEMM=1`：关闭全部 WoA GEMM，回退到 SIMT；不关闭专家缓存或输入量化。
- `FASTLLM_DSV4_DEBUG_CUDA_WOA_GEMM=1`：输出命中形状及 cuBLAS 拒绝状态。

构建 `deepseekV4WoAGemmRegression` 后运行可检查 FP64 独立参考、小批量回退、奇数维度、矩形量化块、输入范围、输入量化和真实投影形状；附加 `--bench` 输出SIMT 回退与自动 GEMM 路径的含同步交替测速中位数。旧架构验证需使用对应的 `compute_75/80/89` 编译 WoA 并测试对应能力分支；在新 GPU 上 JIT 执行旧 PTX 只能验证兼容路径，不能代表旧 GPU 的实际吞吐，也不能替代完整模型在旧 GPU 上的部署验证。

## 逐项自动预算

Qwen4、GLM-5.3 和 DeepSeek-V4.1 的混合推理命令支持以下可选值：

| 参数 | `auto` 的含义 |
| --- | --- |
| `--moe_cuda_cache auto` | 扣除权重、KV、固定状态、工作区和预留空间，计算每卡专家缓存上限 |
| `--moe_cpu_cache auto` | `moe_device=disk` 时按有效主存余量计算专家缓存上限；CPU/NUMA 常驻专家不另分配此缓存 |
| `--moe_device_layers auto` | 加载前规划 GPU 驻留前缀，将后面的专家层留在指定的主机设备 |
| `--chunked_prefill_size auto` | 在 4096、2048、1024、512、256 中选择满足内存预算的切片；DeepSeek-V4.1 磁盘专家额外考虑 8192，且不超过上下文长度 |
| `--auto_gpu_memory_limit 20g` | 将自动规划的每张 GPU 显存预算限制为不超过 20 GiB |
| `--auto_host_memory_limit 128g` | 将自动规划的主机内存预算限制为不超过 128 GiB |

只有写成 `auto` 的参数才参与自动选择。原有数值及默认值保持不变。
`moe_device_layers` 的数值仍表示**末尾使用 `moe_device` 的层数**，不是 GPU 驻留层数；`-1` 表示全部专家层使用该设备。

```sh
ftllm server /path/to/model.gguf \
  --device cuda --tp 2 --moe_device numa --ngram_device disk \
  --tokens 16384 --max_context_length 16384 --max_batch 1 \
  --moe_cuda_cache auto --moe_device_layers auto \
  --chunked_prefill_size auto
```

PP 使用 `--device cudapp=2` 并省略 `--tp`。也可以只自动设置缓存，保留明确的 chunk 和驻留配置：

```sh
ftllm server /path/to/model.gguf \
  --device cudapp=2 --moe_device numa --ngram_device disk \
  --tokens 16384 --max_batch 1 --chunked_prefill_size 512 \
  --moe_cuda_cache auto
```

主存不足时，可显式选择磁盘专家及自动主存缓存。当前 Qwen4 GGUF 磁盘专家的 GPU 缓存尚不支持，自动 CUDA 缓存会设为 0 并说明原因；主存缓存仍按预算分配：

```sh
ftllm server /path/to/model.gguf \
  --device cuda --moe_device disk --ngram_device disk \
  --tokens 16384 --max_batch 1 --chunked_prefill_size 512 \
  --moe_cuda_cache auto --moe_cpu_cache auto
```

HF packed FP4 在 `auto`/`nvfp4` 下按配对的权重与 scale 元数据核算，包含运行时 scale、行对齐和布局转换开销，已合入的 scale 不重复计费。E4M3 取带行全局 scale/对齐的紧凑布局与 FP32 block scale 回退中较大的预算；E8M0 保留 block16 兼容布局的容量上界，以覆盖 CPU 能力或开关导致的回退，不再统一将 packed payload 翻倍。未识别的布局和显式精度转换仍采用保守上界。

规划器读取 GGUF 全部分片的张量目录，或 HF safetensors 的索引和头部，不读取权重正文。GPU 预算采用 CUDA 的逻辑设备编号，因此遵循 `CUDA_VISIBLE_DEVICES`；主存预算同时检查 Linux `MemAvailable`、当前及各级可见父 cgroup 的内存限制与占用，不将 swap 计为可用内存。

上下文、KV 精度、MTP、阈值和用户指定的并发数不参与自动降级。`--tokens` 是整体 KV token 容量；未指定时，按 `--max_context_length` 或模型原生窗口预留。自动规划下未指定 `--max_batch` 时取 1，并打印结果。大窗口放不下会在加载前报错，需要用户明确调整参数。

联合规划优先选择可容纳的 chunk，再比较 GPU 上能存放的专家总量；容量接近时优先减少主存专家。PP 按普通层和驻留专家各自的分配规则核算，TP 对专家分片核算、对公共权重和状态保守预留。Qwen 的常驻 GPU 草稿专家及 GLM 的主机草稿专家分别计入。逐项自动预算仍使用统一的每卡缓存上限；统一 `--auto` 的 Qwen4 主存专家 PP 使用上述分卡预算。两种方式都不自动设置缓存环境变量。

启动时的 `[hybrid auto]` 输出包括具体参数、每卡预算及估计占用、主存预算及估计占用。Python 返回的模型还可通过 `model.hybrid_auto_plan` 查看结果。估计包含加载中间缓冲和运行工作区，不能视作实测峰值；其他进程在规划后占用内存仍可能导致分配失败。原有运行时容量检查继续生效。

这是一套内存容量规划规则，不是吞吐最优配置搜索。它复用现有缓存、动态 CPU/GPU 分流、Graph 和 TP/PP 执行路径，不改变算子、路由数学或采样策略。改变缓存或驻留配置可能选用现有的不同计算后端，其数值特性与手动使用相同参数一致。

当前支持 Linux、等比例 TP、单卡及加权 CUDA PP 设备映射。CUDA 自动缓存按模型支持现有的 GGUF、FP8、packed FP4 专家格式；GLM/DeepSeek 的主存专家 CUDA 缓存要求 NUMA 和 GGUF/native FP8 E4M3/native packed FP4，普通 CPU 专家仍使用显式缓存参数。HF 精度转换按保守上界核算。除统一 `--auto` 的 Qwen4 精确 KV streaming 外，显式 KVMem、自定义 KV 字节上限、异比例 TP、视觉、自定义模型/权重转换、LoRA、RoPE 扩展及 MTP/内置 DeepSeek-V4.1 DSpark 以外的推测算法暂不参与自动规划，仍可使用原有显式参数。自动规划也不能与每卡缓存环境变量覆盖同时使用。逐项自动预算要求混合设备配置；统一 `--auto` 可以在预算允许时选择专家全驻留 GPU，但不选择纯 CPU 执行。

---

### Kimi K3 (`docs/kimi_k3.md`)

# Kimi-K3 部署指南

[English](kimi_k3_en.md) · [返回 README](../README.md) · [Benchmark](benchmarks/kimi_k3.md)

FastLLM 的 Kimi-K3 路径覆盖 KDA/MLA、分块 Prefill、CUDA/NUMA、CPU/GPU 专家、磁盘专家、外部 DSpark、XTML 服务协议和工具调用。

## API Server 快速启动

~~~bash
ftllm server /data/models/kimi-k3 \
  --model_name kimi-k3 \
  --host 0.0.0.0 --port 8080
~~~

## GPU + NUMA 混合 MoE

~~~bash
ftllm server /data/models/kimi-k3 \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192 \
  --gpu_mem_ratio 0.9
~~~

单路 CPU 机器可将 `--moe_device` 改为 `cpu`。

## 磁盘专家

主机内存不足时，可把少量 MoE 层放到高速 SSD：

~~~bash
ftllm server /data/models/kimi-k3 \
  --device cuda \
  --moe_device "{'cuda':1,'numa':8,'disk':1}" \
  --chunked_prefill_size 8192
~~~

磁盘比例越高，解码越容易受随机 I/O 限制。建议先从很小比例开始。

## 外部 DSpark

外部 draft 目录必须是与目标模型匹配的 `DSparkDraftModel` checkpoint：

~~~bash
ftllm server /data/models/kimi-k3 \
  --speculative_algorithm dspark \
  --speculative_draft_model_path /data/models/kimi-k3-dspark \
  --speculative_dspark_confidence_threshold 0.5
~~~

block size 默认读取 draft 配置。FastLLM 会在启动时校验 draft 架构和 block 大小。

## 长上下文、思考与工具调用

~~~bash
ftllm server /data/models/kimi-k3 \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --enable_thinking true \
  --tool_call_parser auto
~~~

Kimi-K3 使用 XTML 对话协议；通常应保留模型自带 tokenizer 和模板。

## Benchmark 状态

仓库目前没有可发布的 Kimi-K3 完整吞吐记录。建议设备命令和数据状态见 [Kimi-K3 Benchmark](benchmarks/kimi_k3.md)。

---

### Kimi K3 En (`docs/kimi_k3_en.md`)

# Kimi-K3 Deployment

[中文](kimi_k3.md) · [Back to README](../README_EN.md) · [Benchmark](benchmarks/kimi_k3_en.md)

FastLLM supports Kimi-K3 KDA/MLA, chunked prefill, CUDA/NUMA execution, CPU/GPU and disk experts, external DSpark, XTML serving, and tool calling.

## API server quick start

~~~bash
ftllm server /data/models/kimi-k3 \
  --model_name kimi-k3 \
  --host 0.0.0.0 --port 8080
~~~

## GPU + NUMA hybrid MoE

~~~bash
ftllm server /data/models/kimi-k3 \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192 \
  --gpu_mem_ratio 0.9
~~~

Use `--moe_device cpu` on a single-socket host.

## Disk experts

~~~bash
ftllm server /data/models/kimi-k3 \
  --device cuda \
  --moe_device "{'cuda':1,'numa':8,'disk':1}" \
  --chunked_prefill_size 8192
~~~

Start with a small disk fraction because decode can become random-I/O bound.

## External DSpark

The draft directory must contain a matching `DSparkDraftModel` checkpoint:

~~~bash
ftllm server /data/models/kimi-k3 \
  --speculative_algorithm dspark \
  --speculative_draft_model_path /data/models/kimi-k3-dspark \
  --speculative_dspark_confidence_threshold 0.5
~~~

The block size defaults to the draft configuration and is validated at startup.

## Long context, thinking, and tools

~~~bash
ftllm server /data/models/kimi-k3 \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --enable_thinking true \
  --tool_call_parser auto
~~~

Kimi-K3 uses its XTML conversation protocol; keep the checkpoint's tokenizer and template.

## Benchmark status

No publishable Kimi-K3 throughput report is currently stored in the repository. See the [Kimi-K3 benchmark page](benchmarks/kimi_k3_en.md) for suggested device commands and the current data status.

---

### Kvmem (`docs/kvmem.md`)

# KVMem（实验性，默认关闭）

KVMem 用固定大小的 GPU KV 池和按需分配的主机备份保存长上下文。超过 GPU 常驻预算后，注意力只访问 sink、最近上下文和检索到的旧页。这会改变模型输出；KV 字节的无损换页不代表稀疏注意力与完整注意力等价。

当前接入 Qwen3 和 Qwen3.5 的单卡 CUDA 普通解码，以及 Qwen3.5 的 DFlash/MTP 推测解码。支持 head_dim 为 128/256 的 FP16/BF16 MHA/GQA，计算与 KV 类型须一致。启用后目标模型使用单请求 eager 路径并关闭历史/prefix 缓存；尚不支持 TP、多模态、滑窗注意力或带位置偏置的目标注意力。未传入配置时，原有缓存、CUDA Graph、融合算子和推测解码路径保持原样。

## 参考来源

本功能的设计参考了 GitHub 项目 [kvmem/kvmem-llama.cpp](https://github.com/kvmem/kvmem-llama.cpp)，主要借鉴其固定 GPU KV 工作集、主机分层存储，以及基于 query 和 pre-RoPE mean-K 检索历史 KV 的思路。FastLLM 结合自身的分页注意力、请求生命周期和 DFlash/MTP 提交与回滚机制进行了适配。感谢该项目的作者与贡献者。

## 使用

在 `warmup()` 或第一次推理之前配置，配置后不要修改设备、计算/KV 类型或并发数：

```python
model.set_kvmem(
    max_tokens=32768,        # 逻辑上下文上限，不会扩展模型 RoPE 上限
    resident_tokens=8192,   # 每个完整注意力层的 GPU KV 槽位数
    sink_tokens=128,
    recent_tokens=2048,
    retrieval_tokens=4096,
    prefill_tokens=512,
    host_mib=8192,          # 整个请求所有完整注意力层的主机 KV 预算
    retrieval_interval=64,  # 推测验证的检索刷新间隔；1 表示逐轮刷新
)
model.warmup()
```

CLI 对应一个 JSON 配置参数，不需要环境变量：

```bash
ftllm server MODEL --device cuda --max_batch 1 \
  --atype float16 --kv_cache_dtype float16 --speculative_algorithm off \
  --kvmem '{"max_tokens":32768,"resident_tokens":8192,"host_mib":8192}'
```

Qwen3.5 推测解码沿用现有参数，将上述 `--speculative_algorithm off` 替换为：

```bash
# DFlash2
--speculative_algorithm dflash --draft /path/to/DFlash2 --draft_tokens 3
# 内置 MTP 权重
--speculative_algorithm mtp --draft_tokens 3
```

`prefill_tokens` 必须至少容纳 `draft_tokens + 1` 行验证输入。目标 full-attention KV 使用 KVMem；DFlash 草稿保持原有滑窗缓存，MTP 草稿保持原有完整分页缓存。因此 `resident_tokens` 不限制草稿模型的显存；MTP 还需额外分配逻辑上下文上限对应的草稿 KV 和 lookahead 页。

`retrieval_interval` 默认为 64，控制 DFlash/MTP 目标验证的检索评分复用：已提交 token 位置跨过对应间隔时刷新。它是正整数，不要求按物理 KV 页对齐，也不改变常驻或检索页数。需要更短的复用间隔时可设为 32，需要逐轮刷新时设为 1；Python 和 CLI 共用此参数。例如在已有 `--kvmem` JSON 中加入 `"retrieval_interval":32`，或向 `model.set_kvmem` 传入 `retrieval_interval=32`。更长间隔减少检索和换页开销，但查询变化时可能继续使用旧评分并影响输出质量。普通解码仍按 KV 页边界刷新。

除 `max_tokens` 外，token 预算必须按 KV 页长对齐（默认 128）。常驻预算至少覆盖 `sink + recent + retrieval + prefill + 一页`，最后一页用于未对齐边界。主机预算须能容纳逻辑上限下所有完整注意力层的 KV（向上取整到完整页）；内存只在冷页首次换出时实际分配。预算不足、模型不支持或配置组合不支持时会报错。

GPU KV 大小约为 `resident_tokens × 全注意力层数 × KV heads × head_dim × 4` 字节；此外还有 FP32 mean-K 索引、当前分块的 Q/K 临时张量、GDN 状态和模型工作区。`host_mib` 限制 KV 页备份，不包含模型其他 CPU 内存。`--kv_cache_limit` 与此模式不兼容；显式 `--chunked_prefill_size` 可设置默认 `prefill_tokens`，JSON 中的值优先。

冷页备份使用普通主机内存。CUDA 换页批量提交 K/V 拷贝，每批完成后统一同步，避免逐页显式同步；不增加额外的 pinned 中转副本。所有旧页备份完成后才复用槽位，恢复完成后才发布新页表。

物理驻留与注意力选页分开管理：本轮未选中的完整旧页可以留在空闲槽位中，只有新增页或检索缺页需要空间时，才从未选中的驻留页中按最近使用时间淘汰。缓存命中不会增加注意力可见页，也不改变选页顺序或检索评分；常驻上限和 GPU KV 池大小保持不变。`residentPages` 统计全部物理驻留页，可能大于当前注意力视图的页数。

## 超出原生上下文

KVMem 只改变 KV 存储和注意力选页，扩大 RoPE 范围仍须在模型构造时显式配置。HF 和 GGUF 可使用公共 `--max_context_length`、`--rope_scaling` 参数；GGUF 的默认加载路径不变。Qwen3.5 架构、原生 262144 且符合已知 M-RoPE 配方的模型可用 `yarn` 简写，目标 524288 自动采用 factor=2，目标 1000000 采用 factor=4：

```bash
ftllm server /path/to/Qwen3.8-27B-GSQ-RCO-IQ3_S-mtp.gguf \
  --device cuda --cuda_embedding --max_batch 1 \
  --atype float16 --kv_cache_dtype float16 --tokens 1000000 \
  --max_context_length 1000000 --rope_scaling yarn \
  --prefix_cache false --cache_history false --speculative_algorithm off \
  --kvmem '{"max_tokens":1000000,"resident_tokens":8192,"sink_tokens":128,"recent_tokens":2048,"retrieval_tokens":4096,"prefill_tokens":512,"host_mib":65536}'
```

该模型的目标 KV 约为每 token 64 KiB，因此 512K / 100 万 token 需要约 32 / 61 GiB 主机 KV 预算，另加权重等 CPU 内存。8192 常驻槽位约占 512 MiB GPU KV，但索引、工作区和草稿缓存另计。支持内置 MTP 共用目标 YaRN 配置（`--speculative_algorithm mtp --draft_tokens 3`），其额外 GPU KV 在这两个长度下约为 2 / 3.8 GiB。

Qwen3.5 目标也可与全部采用滑窗注意力、原生 default RoPE 的 DFlash2 同用：将上述 `off` 替换为 `dflash --draft /path/to/DFlash2 --draft_tokens 7`。草稿保留自身的 RoPE 参数和滑窗，主模型独立使用 YaRN 验证；草稿 Q/K 不与主模型 KV 直接计算注意力，因此两者不要求相同的旋转维度或缩放系数。加载时检查每层布局、窗口及 RoPE 类型，拒绝完整注意力、缩放 RoPE、partial rotary 或 M-RoPE 的草稿。草稿缓存有界，旋转按需计算绝对位置；候选的接受率仍可能随目标 YaRN 和稀疏选页而变化，不能由短上下文加速比推算长上下文速度。

扩展后的容量、位置编码和任务质量需要分别验证。YaRN 和稀疏选页均可能改变输出，成功回答单个长文检索问题不代表通用长文理解质量得到保证。

## 模型接入接口

- `KvMemPages`（`include/kvmem.h`、`src/kvmem.cpp`）只负责逻辑页、槽位、选页、预算和主机备份。批量传输回调与 CUDA/模型解耦，回调须在返回前完成整批传输，包括异常路径。每个请求的每层单独拥有实例。
- `KvMemAppend`（`src/devices/cuda/fastllm-kvmem.cu`）接收归一化的 RoPE 前 Q/K `[1,T,H,D]` 及 RoPE 后 K/V `[H,T,D]`，增量更新 FP32 key-sum 索引，再写入固定 GPU 池。输出 `Data.pageIndex` 是按逻辑顺序排列的借用视图，`Data.dims[1]` 保留完整逻辑长度。
- 模型覆盖 `SupportsKvMem()` 和返回实际 KV 字节数的 `KvMemLayerBytesPerToken(layer)`，如有混合层则覆盖 `KvMemLayerEligible()`，模型特有约束可通过 `ValidateKvMemModel(config)` 校验本次候选配置，在单卡前向入口调用 `PrepareKvMemCaches()`。完整注意力层在 RoPE 之前保留 Q/K，RoPE 之后调用 `KvMemAppend()`，再使用现有 causal paged attention。位置编码仍使用绝对 token 位置。
- 推测验证在每层 `KvMemCache` 上调用 `BeginTransaction(tokens)`，执行一次指定行数的前向，再调用 `FinishTransaction(acceptedTokens, keyCache, valueCache)`。参数是保留的输入前缀行数，包含验证首 token；传 0 表示完全回滚，也可在尚未执行 Append 时取消。禁止嵌套事务、多次 Append 和越界提交。模型还须恢复其他循环状态：Qwen3.5 验证使用独立 GDN 状态，提交相应前缀快照；无可用快照时回滚 KV，再重放已接受输入。
- 调度器释放缓存时调用 `ReleaseKvMemCache()`，不能将借用的 `pageIndex` 返还给全局分页池。禁止隐式 `Data::CopyFrom` 克隆 KVMem 缓存。事务不提供任意历史快照或 prefix 共享；其他模型必须显式接入状态提交与异常清理。

## 检索与边界

索引保存每个逻辑页、每个 KV head 的 FP32 key sum，查询时除以实际 token 数得到 mean-K。对每个 Q head 计算缩放点积，对所有已提交页做 softmax 后平均各 head 的概率。当前按层独立选页，与 NInfer 的跨层聚合策略不同。

分块 prefill 用首行 query 查询此前已提交的页，避免未来 token 影响之前行的选页；当前 query 所在的全部尾页始终保留，因此压缩页表后的 causal mask 与绝对时间顺序一致。每个 prefill 分块、分块后的首个单 token，以及 decode 的页边界会刷新检索。最后一个 prompt token 独立执行，使用最后的提示 query 刷新检索再采样。

完整冷页首次换出后保持不可变，重复换出复用主机副本。正在写入的半页和整个验证尾部不会被换出；传输完成后才复用槽位和发布新页表。验证只查询此前已提交的索引，新 raw K 暂存至提交时，仅将接受前缀加入 key sum。拒绝尾部的物理页与逻辑长度同步裁剪，后续追加覆盖半页里的无效尾部；拒绝 token 不进入主机备份或索引。

普通解码在已提交的 query 位置跨 KV 页时刷新检索，页内复用评分；推测验证默认每跨 64 个已提交 token 的区间刷新，区间内复用评分，一次验证共享首行 query 的选择。`retrieval_interval=1` 恢复逐轮刷新。刷新间隔以验证前的已提交位置计算，未接受的草稿不推进区间；从普通解码切换到不同的验证间隔或切回时也重新评分。物理驻留缓存与检索刷新间隔分别管理。拒绝尾部不进入索引，整轮取消会使缓存评分失效，避免被取消的首行 query 影响重试。prefill 按分块刷新，分块后的单 token query 也重新评分。这仍是稀疏目标模型的近似策略，不承诺与逐 token 重新检索或完整 KV 的输出逐 token 一致。尚未实现异步预取或目标 graph 捕获。

## 验证入口

`kvmem_pages_test` 覆盖换页、预算、刷新间隔配置校验、因果边界、事务与失败状态；`cuda_kvmem_test` 覆盖 FP16/BF16、MHA/GQA、不同 head_dim、检索恢复、大批量换页、稀疏注意力的 CPU 数值参考，以及拒绝/部分接受/全部接受后的 KV 和检索索引一致性、共享缓存释放及模型配置失败的状态保留。检索测试包含默认 64、显式 32 和逐轮刷新，验证区间内复用、跨界刷新、取消重试及普通/推测模式切换。两者都接入 CTest。CUDA 测试需要可用 GPU。

---

### Laguna (`docs/laguna.md`)

# Laguna 部署指南

[English](laguna_en.md) · [返回 README](../README.md) · [Benchmark](benchmarks/laguna.md)

FastLLM 的 Laguna 路径支持长上下文缓存、CUDA Graph、多卡张量并行、混合 MoE、NVFP4 和 INT4_GROUP32。

## API Server 快速启动

~~~bash
ftllm server /data/models/laguna \
  --model_name laguna \
  --host 0.0.0.0 --port 8080
~~~

## 多 GPU 张量并行

当多卡总显存能够容纳目标 checkpoint 时：

~~~bash
ftllm server /data/models/laguna \
  --tp 4 \
  --max_batch 8 \
  --gpu_mem_ratio 0.9
~~~

Laguna 会根据 `--tp` 选择主设备和专家张量并行布局。四卡场景下 CUDA slab 默认值也会按模型布局自动调整。

八卡示例：

~~~bash
ftllm server /data/models/laguna \
  --tp 0,1,2,3,4,5,6,7 \
  --max_batch 16 \
  --gpu_mem_ratio 0.9
~~~

### GPU + NUMA 混合 MoE

~~~bash
ftllm server /data/models/laguna \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192
~~~

## 长上下文

~~~bash
ftllm server /data/models/laguna \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --gpu_mem_ratio 0.9
~~~

## 精度

首次加载建议使用 `--dtype auto`。NVFP4 和 INT4_GROUP32 是否可用取决于 checkpoint 格式、GPU 架构和当前 kernel 支持，不应仅根据文件名强制覆盖精度。

## Benchmark 状态

仓库目前只记录了 Laguna 多卡、CUDA Graph、混合 MoE 和量化路径的功能验证，没有统一硬件上的正式速度表。建议设备命令和数据状态见 [Laguna Benchmark](benchmarks/laguna.md)。

---

### Laguna En (`docs/laguna_en.md`)

# Laguna Deployment

[中文](laguna.md) · [Back to README](../README_EN.md) · [Benchmark](benchmarks/laguna_en.md)

FastLLM supports Laguna long-context caching, CUDA Graph, multi-GPU tensor parallelism, hybrid MoE, NVFP4, and INT4_GROUP32.

## API server quick start

~~~bash
ftllm server /data/models/laguna \
  --model_name laguna \
  --host 0.0.0.0 --port 8080
~~~

## Multi-GPU tensor parallelism

When aggregate GPU memory can hold the checkpoint:

~~~bash
ftllm server /data/models/laguna \
  --tp 4 \
  --max_batch 8 \
  --gpu_mem_ratio 0.9
~~~

Laguna derives the main-device and expert tensor-parallel layout from `--tp`. The CUDA slab default is also adjusted for the four-GPU model layout.

Eight-GPU example:

~~~bash
ftllm server /data/models/laguna \
  --tp 0,1,2,3,4,5,6,7 \
  --max_batch 16 \
  --gpu_mem_ratio 0.9
~~~

## GPU + NUMA hybrid MoE

~~~bash
ftllm server /data/models/laguna \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192
~~~

## Long context

~~~bash
ftllm server /data/models/laguna \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --gpu_mem_ratio 0.9
~~~

## Precision

Use `--dtype auto` for an initial load. NVFP4 and INT4_GROUP32 support depends on checkpoint format, GPU architecture, and available kernels; do not force a precision solely from the filename.

## Benchmark status

The repository contains functional validation for Laguna multi-GPU, CUDA Graph, hybrid MoE, and quantization paths, but no standardized speed table. See the [Laguna benchmark page](benchmarks/laguna_en.md) for suggested device commands and the current data status.

---

### Launcher Agents (`docs/launcher-agents.md`)

# Launcher 中使用 OpenCode、Codex 和 Claude Code

`ftllm launch` 的导航分为“模型管理”和“agent”两组。前一组包含启动服务、下载模型、运行日志、硬件信息；后一组包含工作室、DeepSeek Harness、OpenCode、Codex、Claude Code。四个原生 agent 使用 Launcher 当前启动的 FastLLM 模型，分别保存会话。

**pip 包只包含适配代码和页面。进入页面、刷新、启用插件都不会安装外部工具。** 点击 agent 分组旁或对应页面中的“管理”，可在模型服务未启动时手动安装。也可以启动模型后点击 **安装并打开 OpenCode / Codex**。安装进度、取消和失败重试与 Harness 一致。已安装时，打开栏目可直接启动工具。

## 使用

1. 运行 `ftllm launch`，启动一个支持工具调用的模型 API。
2. 点击 OpenCode、Codex 或 Claude Code；首次使用时手动点击安装按钮。
3. OpenCode 使用原生网页：点击 **Add project**，选择运行 Launcher 的机器上的目录，再创建会话。
4. Codex 左侧按工作目录分组显示会话，右侧显示当前会话和它实际绑定的目录。点击“新建会话”，在“会话工作目录”旁点击文件夹按钮，浏览运行 Launcher 的机器并选择目录，也可直接输入路径；发送第一条消息时创建会话并绑定该目录。点击目录旁的 `+` 可直接在该目录下准备新会话。已有会话使用各自保存的目录，切换会话不会改变目录。
5. Claude Code 使用接近 Harness 的简洁会话侧栏和聊天区，跟随 Launcher 的浅色 / 黑夜模式。目录选择、按项目分组、独立草稿、Markdown、回车发送、重命名和归档复用同一套会话界面。底部可选择当前模型声明的思考档位。

Claude Code 基于官方 Agent SDK 运行，通过 FastLLM 的 `/v1/messages` 使用当前模型，无需登录 Anthropic。工具和命令由真正的 Claude Code 执行；保留默认权限检查，需要批准的操作会显示“允许一次 / 拒绝 / 取消本轮”，工具提出的问题也可在页面回答。这里的权限确认不等于操作系统沙箱。停止、刷新和重新打开后可以恢复已保存的会话；新建会话会绑定所选目录，不会修改另一会话的目录。

Claude Code 的安装、升级 / 重装、删除、启用 / 停用与其他 agent 使用相同管理面板。它需要独立 SDK 运行环境，即使 PATH 中已有 `claude` 命令也不会复用该命令或用户的 `~/.claude`。删除只移除 `~/.fastllm/claude/runtime/`，保留 `home/` 中的原生会话、界面历史以及工作目录。

Codex 页面支持会话搜索、切换、重命名、归档、逐项消息和工具输出、文件修改详情、审批、回答工具问题和取消生成。用户消息靠右，Codex 回复靠左；`Enter` 发送消息，`Shift+Enter` 换行，也支持 `Ctrl+Enter` / `Cmd+Enter`。中文输入法选词时的回车不会发送。每个会话的输入草稿及各目录下的新会话草稿保存在当前浏览器，完整会话由 Codex 保存；切换栏目保留页面，刷新后可以恢复会话和待审批请求。

消息、计划和思考内容复用工作室的 Markdown 渲染器，支持流式更新、标题、表格、列表、链接、代码块和代码复制；刷新后历史消息也会重新渲染。命令输出和文件差异保留纯文本格式，消息中的 HTML 不会作为页面代码执行。

Codex 输入框下方提供“思考档位”，按当前模型和会话记住选择；新会话从模型默认档位开始。OpenCode 使用输入框中的 **Choose model variant** 原生菜单，Harness 使用原生模型设置中的思考档位。启动 agent 时会读取当前服务 `/v1/models` 的能力声明，模型使用别名也可识别。例如 Qwen3.5 支持 `low / medium / xhigh`，Kimi K3 支持 `low / high / max`；不会向这些模型发送不支持的通用档位。没有声明可调档位的模型继续使用服务默认设置，Codex 会显示禁用的“模型默认”及原因。更新适配代码后需重启 Launcher 并重新打开 agent；思考参数的服务端处理更新需同时重启模型服务。

Qwen3.5 架构族和 Qwen4-Exp 的默认思考档位为 `xhigh`。外部客户端传入 `high`、`max`、`minimal` 或数值预算等 Qwen 不支持的通用值时，服务端回退到 `xhigh`；`low / medium / xhigh` 保持原值，`none` 仍表示关闭思考。该兼容行为覆盖 Chat Completions 的 `reasoning_effort`、Responses 的 `reasoning.effort` 和 Anthropic Messages 的 `output_config.effort`；Chat 模板参数中的 `reasoning_effort` / `thinking_effort` 也使用同一回退规则。未指定档位时仍保留服务的思考开关默认值，显式关闭思考的模板参数或 Anthropic `thinking` 设置仍优先。

外部 ZCode 等客户端使用 `/v1/messages` 时，服务端会在生成结束后完成工具解析并发送最后缓冲的调用。例如 Qwen 已完整生成 `</function>`、仅缺少外层 `</tool_call>` 时，仍会发送 `tool_use`，避免客户端只收到“现在开始处理”的正文便结束。未耗尽输出预算却留下不完整工具参数，或全部调用都被判为无效时，会返回错误，不能伪装成正常 `end_turn` / `stop`；尚无有效工具调用、仅因输出预算耗尽而留下未完成调用时，仍保留 `max_tokens` / `length`，供客户端识别。Anthropic 流式响应若已输出一个完整调用、后续调用再因预算截断，目前仍返回 `tool_use`，这一混合场景尚未修复。普通文本回复的正常结束语义不变，这不能保证模型完成所有业务要求。

两者都能运行命令和修改文件。Codex 使用 `workspace-write` 和 `on-request`，需要额外权限时在页面请求批准；OpenCode 使用原生权限交互。工作目录用于组织任务，不代表操作系统级隔离。它们与原来只允许前端扩展的“自定义界面”权限不同；用户自建界面仍不能声明原生执行能力。

agent 管理面板复用插件注册表和管理接口，也可从 **自定义界面 → 已有自定义项与恢复 → 管理** 打开。工作室不提供外部运行环境管理。

- **安装**：下载安装独立运行环境，无需启动模型；已有系统命令时可选择安装独立版本。
- **升级 / 重装**：安装面板标明的当前支持版本；已是该版本时重新安装。先停止对应 agent，在临时目录完成安装和验证后替换运行环境，失败时保留旧版本。
- **删除**：停止该 agent 并仅移除其 `runtime/`，保留会话、配置和工作目录。不会删除内置页面或系统安装，之后可重新安装；若 PATH 中有可用命令，会继续显示为使用系统安装。
- **启用 / 停用**：复用插件开关。停用会停止对应进程或取消安装，保留运行环境、会话、文件和模型服务。

停止模型、切换模型或退出 Launcher 时，会同时停止对应的 agent 或取消安装、升级。

## 安装与数据

| 工具 | 点击安装时使用的版本 | 默认数据目录 | 模型接口 |
| --- | --- | --- | --- |
| OpenCode | `opencode-ai@1.18.26` | `~/.fastllm/opencode/` | `/v1/chat/completions` |
| Codex | `@openai/codex@0.153.4` | `~/.fastllm/codex/` | `/v1/responses` |
| Claude Code | `@anthropic-ai/claude-agent-sdk@0.3.266`，内含 Claude Code `2.1.266` | `~/.fastllm/claude/` | `/v1/messages` |

每个目录中的 `runtime/` 保存独立 Node/npm 和工具；`workspace/` 是默认工作目录。OpenCode 的配置、缓存、会话分别位于 `config/`、`cache/`、`data/`、`state/`；Codex 的配置和会话位于 `home/`。不覆盖用户原有的全局 OpenCode / Codex 配置和登录信息。

Claude Code 的 `home/launcher-sessions/` 保存页面使用的会话目录、标题、归档状态和消息历史；原生 SDK 同时在独立 `home/` 内保存用于续聊的会话。密钥只通过子进程环境传递。模型、工作目录和权限策略由适配器设置，不加载全局或项目 settings 来替换这些选项。

思考参数通过 Anthropic `output_config.effort` 传给 FastLLM，并使用与 Chat Completions 一致的模型专用规则；未声明档位时保留服务默认行为，避免 Claude Code 自动补上模型不支持的 `high`。升级这部分适配后，需要同时重启 Launcher 和模型 API 服务。

安装器复用 Harness 的 Node 24.20.0 下载与 SHA-256 校验，支持 Linux、macOS、Windows 的 x64/ARM64 安装包选择；当前完整验证的平台是 Linux x64。npm 沿用用户的包源、代理和证书配置。安装在临时目录完成并验证后才切换到正式目录，失败保留原有文件；同一工具的安装使用文件锁互斥。

Linux x64 实测独立运行环境的磁盘占用约为 OpenCode **491 MiB**、Codex **459 MiB**（按 `du` 统计，硬链接不重复计算），不含 npm 缓存和会话数据。Launcher 的网页/终端代理使用轻量 Python 依赖 `httpx` 和 `websockets`，由 ftllm 的服务依赖提供，不属于外部 Agent 运行时。

PATH 中已有的 `opencode` 或 `codex` 可以直接复用；上表版本经过验证，其他版本的协议或网页可能不同。适配器禁止 OpenCode 自动升级。FastLLM 地址、模型名称、上下文容量自动配置；模型密钥通过子进程环境传入，不写入生成的配置正文。Codex 不需要登录 OpenAI 账号来使用此本地提供方。

适配器不压缩、裁剪或重新拼接会话历史。OpenCode 的自动压缩与裁剪已关闭；Codex 配置了高于模型容量的自动压缩阈值。模型或运行时报告的上下文超限会显示在页面。原生工具自身对单次命令输出的长度限制仍遵循工具的实现。

## 嵌入和部署

Claude Code 与 Codex 的会话界面通过 Launcher 已有的认证接口访问 stdio 桥接，不需要开放额外网页端口。Claude Code 在 Linux x64 验证了真实 SDK 的流式回复、批准后写入文件、原生会话续聊与思考档位传递；其他平台尚未做端到端验证。参考官方 [Agent SDK](https://code.claude.com/docs/en/agent-sdk/typescript)、[网关配置](https://code.claude.com/docs/en/llm-gateway)与[环境变量](https://code.claude.com/docs/zh-CN/env-vars)。

OpenCode 使用原生静态资源和 API。内部服务只监听回环地址，外部入口通过独立端口代理 HTTP、SSE 和终端 WebSocket。入口使用独立 token/HttpOnly cookie，服务密码只由后端注入。页面与 Launcher DOM 位于不同 origin。

内嵌 OpenCode 的浅色/深色模式跟随 Launcher，切换主题不会刷新会话或丢失输入。内置的外观适配缩减原生窗口的重复边框和留白，并调整项目栏在嵌入区域内的布局；聊天、项目选择、文件差异、审批和终端继续由 OpenCode 原生界面处理。适配样式以表中的已验证版本为依据，不改写安装包。

通过 localhost、局域网 IP 或主机名访问同一个 Launcher 时，OpenCode 会为当前浏览器提供对应地址的嵌入入口，复用已有进程，并保留其他已打开页面的连接。

与当前 Harness 集成相同，OpenCode 嵌入要求 HTTP、IPv4 或主机名，并且浏览器能访问分配的额外端口；HTTPS、IPv6 和仅开放 Launcher 单端口的反向代理尚未适配。网页是否可离线使用取决于所复用的 OpenCode 安装包是否带有原生网页资源，不能将任意 PATH 版本都视为离线包。

Codex 通过 app-server 的 stdio JSON-RPC 连接到 Launcher 后端。网页沿用 Launcher 的鉴权和端口，不另外公开 app-server。后端只提供此页面需要的会话、回合和审批操作，不提供任意 JSON-RPC 转发或用户插件后端注册。尚未适配的原生请求会在页面提示，可取消对应回合。

接口依据：[OpenCode Web](https://opencode.ai/docs/web/)、[OpenCode 自定义提供方](https://opencode.ai/docs/providers/)、[Codex app-server](https://learn.chatgpt.com/docs/app-server)、[Codex 配置](https://learn.chatgpt.com/docs/config-file/config-reference)。

---

### Launcher Harness (`docs/launcher-harness.md`)

# Launcher 中使用 DeepSeek Harness

Launcher 主导航的“工作室”下面提供 **DeepSeek Harness** 入口。启动模型 API 后点击入口，已安装的 Harness 会启动独立进程，并在内容区嵌入原生页面；尚未安装时显示安装按钮。切换到其他栏目再返回时保留页面状态。

Harness 作为内置页面插件，复用已有的插件注册表、启停状态和自定义界面管理页。验证版本为 `@deepseek-ai/dsh@0.1.5-alpha.1`。

## 插件管理

在左侧 **agent → 管理**、Harness 页面上的“管理”，或 **自定义界面 → 已有自定义项与恢复 → DeepSeek Harness → 管理** 中打开统一管理面板：

- 查看未安装、已安装、安装中或运行中等状态。
- 点击“安装”即可安装独立运行环境，无需先启动模型。
- 点击“升级 / 重装”安装面板标明的当前支持版本，已是该版本时重新安装。升级前停止 Harness，失败保留旧运行环境。
- 点击“删除”仅移除独立运行环境，保留会话和工作目录，不删除系统安装；删除后可重新安装。
- 点击“停用”关闭 Harness 或取消当前安装，页面显示停用提示；模型服务、已安装环境和会话数据保留。
- 点击“启用”恢复页面，开关会持久保存；启用本身不下载依赖，也不启动 Harness。

页面、脚本、样式和 `plugin.json` 位于发布包的 `ui_plugins/harness/`，遵循内置插件只读规则。安装和进程生命周期由应用注册的后端处理；用户创建的自定义界面仍使用原有沙箱权限，不能声明原生插件或执行安装命令。

## 安装与启动

```bash
ftllm launch
```

1. 在 Launcher 中启动模型 API。
2. 点击左侧 **DeepSeek Harness**。尚未安装时，手动点击 **安装并打开 Harness** 下载独立运行环境，或提前在管理面板中安装；仅进入、刷新或切换页面不会安装。页面显示下载量、依赖安装和验证进度，“安装并打开”完成后自动打开。
3. 首次进入按 Harness 提示选择工作区，即可聊天和使用工具。

不需要预装 Node/npm，也不会修改系统 Node 或全局 npm 包。运行环境保存在 `~/.fastllm/deepseek-harness/runtime`，后续直接复用。pip 安装 ftllm 时只安装集成代码，不下载 Harness 本体。

一键安装为 Linux、macOS、Windows 的 x64/ARM64 提供对应 Node 24.20.0 运行环境，从 Node 官方站点下载并检查 SHA-256，再通过 npm 安装固定版本的 Harness。当前实际验证了 Linux x64，运行环境约占 **440 MiB**（不含 npm 缓存和会话数据）；其他平台大小可能不同。首次安装需要能访问 Node 下载站点及 npm 包源，npm 沿用用户的源、代理和证书配置。

安装期间可以切换栏目，返回后继续显示进度；也可以点击“取消安装”。失败时显示错误，点击“重试”即可重新安装。下载和安装先在临时目录完成，验证成功后才启用；多个 Launcher 不会同时向同一目录安装。

已经安装了可用 `dsh` 的用户可直接复用 PATH 中的命令。此前手动安装到 `runtime/node_modules/node` 的运行环境也继续支持。

Launcher 自动配置 FastLLM 提供方的 API 地址、模型名称、密钥和已识别的上下文容量。密钥只通过子进程环境传递。工作区选择器直接显示在嵌入页中。

Launcher 根据 `/v1/models` 的 `input_modalities`（兼容 `inputModalities`）向 Harness 声明图片输入能力，让 `read_image` 可以读取原生视觉模型支持的图片。缺少元数据时只声明文本；不会根据模型别名猜测视觉能力。更新后需重启模型服务和 Harness，刷新能力声明与客户端配置。

原生图片能力覆盖带视觉配置的 Qwen3.5 dense/MoE、Qwen3.8-Flash-Next、DeepSeek-V4.1、CogVLM、Gemma4 和 Step3.7；GGUF + mmproj 沿用投影器能力声明。缺少视觉配置或设置 `language_model_only` 的原生检查点保持仅文本。

Launcher 设置 `compat.supportsDeveloperRole: false`，让 Harness 使用 `system` 发送系统提示词，避免 Qwen 模板拒绝 `developer` 角色。直接连接 API 时，Qwen3.5/3.8 服务也会将 `developer` 指令与显式 `system` 指令按原顺序合并为开头的系统消息，保留用户消息和工具调用记录。

模型输出预算通过 `compat.maxTokensField: max_tokens` 发送，兼容只支持旧字段的 FastLLM 服务。新版服务也接受 `max_completion_tokens`；两者同时提供时，以非空的 `max_completion_tokens` 为准。

默认单次输出预算为 `min(32768, contextWindowTokens / 2)`（至少 1 token），思考与正文、工具参数共用该预算。例如 64K 上下文允许最多 32K 输出，32K 上下文允许最多 16K 输出；旧版最多 8K 的预算可能在长思考期间耗尽。Pi 还会根据实际对话长度预估并缩小请求预算，因此提高输出预算不能消除上下文不足造成的截断。修改后需要重启 Harness 以重新生成提供方配置。

## 流式连接与诊断

Harness 的 `llm-pi-ai.providers.fastllm.streamIdleTimeoutMs` 控制等待模型有效输出的空闲时限，默认 300000 毫秒。SSE 注释心跳只维持传输连接，不会重置这个时限。若报错包含 `pi-ai stream idle timeout`，应结合排队和 prefill 耗时调整该配置。模型以 `finish_reason: length` 结束则表示本次输出预算耗尽，与网络断开不同。

Harness 退出时，Launcher 将最近的日志尾部脱敏后保存到 `~/.fastllm/deepseek-harness/logs/latest.log`，上一份保存在 `previous.log`。每份读取最多 64 KiB，去掉边界处的不完整行；已知模型密钥、URL 中的令牌和 Authorization 值会隐藏。日志文件在 POSIX 上以 0600 权限创建。原始临时日志仍随启动目录清理。

## 实验性恢复扩展（默认关闭）

Launcher 默认不加载恢复扩展。需要试用时，在启动 Launcher 前显式设置环境变量（仅值 `1` 开启）：

```bash
FTLLM_HARNESS_EXPERIMENTAL_RECOVERY=1 ftllm launch
```

取消该环境变量并重启 Launcher 和 Harness 后恢复默认行为。角色兼容、输出预算、SSE 心跳和诊断日志独立于此开关。

**已知限制：** 文本续写可能重复截断处的内容；headless 标准输出只包含最后一段回答，前面的片段需从会话记录读取；恢复额度耗尽时，最后生成的一段文本可能只保存在失败尝试中，没有进入正式回答。退出码 0 不保证续写后的完整文本无重复。这些问题仍待修复。

启用后，扩展使用同一会话中的实际结果继续任务：

- 文本达到输出上限后，保留前文并排队一个续写回合。
- 只有思考、没有答案或工具调用时，要求模型减少思考并给出可用输出。
- 工具调用被服务端拒绝或截断时，将恢复提示写入会话，再要求模型重建合法调用；被拒绝的调用不会执行，之前成功的工具结果继续保留。

恢复提示带有插件来源并记录在会话中。每次用户输入默认最多自动恢复 2 次，续写和上述重试共用这个额度；达到上限后返回明确错误。用户取消后不发起恢复。摘要压缩、上下文超限、网络重试仍遵循 Harness 本身的策略。

上述错误恢复期间若有待处理输入，扩展会保留队列、结束当前失败轮次，并交由 Harness 在新轮次正常读取输入。即时指令保持原有顺序，排队的后续任务仍各自进入独立轮次；即使恢复额度已耗尽，新输入也会优先处理。会话记录会将交接前的轮次标为扩展主动中止，随后记录新输入的执行结果。

启用时，Launcher 生成的 patch 会插入 `fastllm-harness-recovery`，其 `config.maxRecoveries` 为 2。独立使用 Harness 时，可在 patch 的 `insert` 中加载已安装 ftllm 包目录内的 `harness_recovery.mjs`，配置 `provider: fastllm`、`maxRecoveries: 2`。`maxRecoveries` 可设为 0–10；0 只禁止自动重试和续写，仍会进行错误分类及待处理输入交接，不等于卸载扩展。扩展使用 Harness 的公开插件钩子，不修改 npm 依赖。

## 数据和生命周期

- 默认配置和会话保存在 `~/.fastllm/deepseek-harness/home`，默认工作目录为 `~/.fastllm/deepseek-harness/workspace`，也可以在 Harness 中选择其他工作区。
- “停止 Harness”仅关闭 Harness；停止模型、切换模型或退出 Launcher 时，也会关闭对应的 Harness 进程或取消尚未完成的安装。已安装环境、会话和文件保留。
- 启动失败时显示错误，可以点击“重试”。刷新 Launcher 后可以重新连接仍在运行的 Harness，或继续查看安装进度。
- 原有工作室与 Harness 分别保存会话。

当前嵌入使用同一主机的独立 HTTP 代理端口，页面与 Launcher DOM 保持隔离。Harness 内部服务只监听回环地址，代理在后端完成原生 token/cookie 认证；浏览器使用代理独立的 token/HttpOnly cookie，原生凭据不发送给浏览器。

通过 localhost、局域网 IP 或主机名访问同一个 Launcher 时，每个浏览器获得对应地址的入口，复用已有 Harness 进程。Harness 重启导致入口地址变化时，已打开页面会自动重连；普通栏目切换和状态轮询仍保留当前页面。

支持 IPv4 地址或主机名；HTTPS、IPv6 和仅开放 Launcher 一个端口的反向代理部署尚未适配。远程访问需要同时能访问代理分配的端口。

---

### Launcher Plugins (`docs/launcher-plugins.md`)

# Launcher 与工作室自定义界面

`ftllm launch` 的模型管理、下载、日志、硬件信息和工作室页面分别位于
`tools/fastllm_pytools/ui_plugins/` 下的文件夹中。原有页面布局、模型进程和会话数据保持兼容。
启动器主导航提供“自定义界面”入口，底层使用独立文件夹中的界面插件；工作室工具栏不再重复显示入口。

## 使用自然语言定制

1. 在 Launcher 中启动模型 API，打开“自定义界面”子页面。
2. 在左侧对话框输入修改要求并发送。“修改选项”中可选择参考界面、定制位置和保存名称，默认自动选择与命名。
3. 描述需求，例如“在主界面右上角增加硬件状态栏，每三秒刷新内存和显存用量”，
   “换成蓝色皮肤，把侧栏移到右边”，或“在工作室增加一个按钮，把当前对话的摘要插入输入框”。
4. 左侧按轮显示你的需求、助手的修改说明，以及成功、取消或失败的结果。修改过程中只显示简短状态与耗时，步骤摘要可以展开查看，不展示原始生成代码。
   右侧显示可以交互的整页预览，直接点击其中的导航切换页面、使用原有主题控件切换浅色/深色；上方只保留桌面/手机尺寸与刷新。
5. 生成完成后自动更新预览；展开“查看与编辑文件”也可以直接编辑，停顿后自动校验并刷新。
   确认效果后点击“应用修改”。继续描述要求时，模型会收到本次对话的历史需求、修改摘要与最新草稿（包括手动编辑）；生成失败或取消时保留上一份草稿。
6. “已有自定义项与恢复”中可以继续定制、停用、恢复上一版或恢复默认皮肤。自己创建的自定义项还可删除：确认后删除其文件与上一版本，界面立即卸载该项；内置项不可删除，编辑会话和草稿会保留。

“返回”及浏览器后退会回到原页面，工作室草稿和模型服务保持运行。生成中可以取消，离开子页面也会取消生成。
左上角“对话列表”可搜索、切换、重命名和删除编辑会话；页面只显示选中会话的消息。
“新建对话”创建空白会话，各自保留对话历史、未发送的输入、修改选项和待应用草稿，模型只接收当前会话的历史。
会话保存在当前浏览器的 IndexedDB 中，刷新或重新打开同一地址后可恢复；不同浏览器或不同服务地址不共享这些记录。
切换会重新校验草稿并恢复预览；手动编辑出错时仍保留原文和上一份有效画面。生成或应用过程中暂不能切换，可先取消生成。
删除会话前会确认，仅删除其记录和未应用草稿，已应用的界面不受影响。保存失败或其他页面更新了记录时会显示错误，避免静默覆盖。
历史不会被自动裁剪；超过对话长度限制或模型上下文不足时会明确报错。
在页面地址后加上 `#customize` 可以直接打开编辑子页面，独立 WebUI 也支持这种方式。独立 WebUI 预览整个工作室；Launcher 中预览整个应用。

整页预览复制当前界面及发布版模板，使用独立样式范围；不复制原生脚本和事件处理器。
预览中可点击导航、打开设置、展开侧栏和编辑表单，这些操作只影响预览副本。刷新或编辑文件后保留预览的页面与主题，表单恢复为当前应用的快照。
状态组件可通过 `hardware.read`、`runtime.read` 读取实时数据；
启动/停止模型、发送对话、插入草稿等操作在预览中不可用。需要更新当前应用状态的画面时点击“刷新预览”。
未打开过的工作室显示发布版的空白对话模板，不会为了预览创建会话。

生成预览不会写入文件。模型只返回文件内容，不获得 shell、Python 执行器或文件系统工具。
若模型 API 报告上下文不足、返回不完整 JSON，或生成了非法路径，界面显示错误，现有插件不变。
应用时检查预览对应的版本；插件若已被另一操作修改，则拒绝覆盖，需重新生成预览。
预览文件只存于进程内存，最多保留 8 份、每份有效期 10 分钟，不写入插件目录；“刷新预览”可续建预览。
无效编辑不能应用，并保留上一个有效画面。预览中的自定义脚本仍使用与正式组件相同的 iframe 隔离。

## 插件目录

默认用户目录是 `~/.fastllm/plugins`，启动时也可指定：

```bash
ftllm launch --plugins-dir /path/to/my-plugins
ftllm webui --api_base http://127.0.0.1:8000/v1 --plugins-dir /path/to/my-plugins
```

每个插件一个文件夹，文件夹名称必须与清单中的 `id` 相同：

```text
~/.fastllm/plugins/
  hardware-monitor/
    plugin.json
    index.html
    app.js
    styles.css
```

`plugin.json` 示例：

```json
{
  "apiVersion": 1,
  "id": "hardware-monitor",
  "name": "硬件监控",
  "entry": "index.html",
  "slot": "page",
  "capabilities": ["hardware.read"]
}
```

- `slot: "page"` 在 Launcher 中增加页面；`slot: "studio"` 在工作室中增加扩展区域。
- `slot: "topbar"`、`"sidebar"`、`"statusbar"` 分别在主界面右上角、侧栏、底栏增加常驻组件。
  切换页面不重载这些组件；未安装时不占用空间。独立 WebUI 只挂载工作室扩展和皮肤。
- `slot: "theme"` 修改主界面和工作室的配色、字体与圆角，以及启动器的侧栏和内容布局，详见下文。
- 页面插件可用 `replaces` 替换 `launch`、`download`、`logs`、`hardware` 或 `webui`。
  停用定制插件后恢复内置页面。同一页面有多个替换插件时，按插件 ID 排序使用第一个已启用的插件。
- ID 使用小写字母、数字、连字符，以字母开头，最多 48 字符。内置插件 ID 保留，不能覆盖。
- 一个插件最多 64 个 UTF-8 文本文件，总大小不超过 1 MiB。支持 HTML、JS、CSS、JSON、SVG、Markdown。
  禁止符号链接、绝对路径、隐藏文件和 `..` 路径。
- 原生内置插件随软件发布，使用 `native: true`；用户插件不能声明此字段。

用户插件可以直接在磁盘上编辑。界面每两秒检测一次目录变化，添加、修改、停用和移除只影响对应插件。
重新加载会销毁旧 iframe、关闭消息通道并取消旧前端请求，模型服务继续运行。会话仍由核心保存。
插件内未保存的临时界面状态会在该插件重载时消失，因此工作室扩展应通过核心接口操作对话。

清单暂时写坏时，在当前进程中保留最后一个有效版本并显示错误；第一次发现的无效插件不加载。
JS 运行错误显示在组件区域，自定义界面入口仍属于主界面。通过管理界面发布的版本保留一份历史，支持回退；
直接在磁盘上编辑不会自动生成历史版本。`.state.json`、`.history/` 和 `.lock` 由核心管理。

## 主界面组件和皮肤

右上角硬件状态栏使用普通 HTML/CSS/JS 插件，将清单改为：

```json
{
  "apiVersion": 1,
  "id": "hardware-monitor",
  "name": "硬件状态栏",
  "entry": "index.html",
  "slot": "topbar",
  "size": {"width": 280, "height": 44},
  "capabilities": ["hardware.read"]
}
```

三个主界面位置都支持 `size`：宽度 80–640、高度 24–160，单位为 CSS 像素。
默认宽 240；顶部默认高 44、侧栏高 96、底栏高 32。组件适应容器宽度，多个组件超出时在区域内滚动。
侧栏组件填满侧栏宽度，窄屏时改为横向排列。组件不能覆盖核心按钮，也不能直接修改主页面 DOM。

皮肤插件只需一个 `plugin.json`，不执行脚本，无需 HTML 入口或服务能力：

```json
{
  "apiVersion": 1,
  "id": "blue-skin",
  "name": "蓝色皮肤",
  "slot": "theme",
  "theme": {
    "light": {
      "background": "#f4f7ff", "surface": "#ffffff", "sidebar": "#eaf0ff",
      "primary": "#2563eb", "primaryHover": "#1d4ed8", "primarySoft": "#dbeafe"
    },
    "dark": {
      "background": "#101827", "surface": "#18233b", "sidebar": "#18233b",
      "primary": "#60a5fa", "primaryHover": "#3b82f6", "primarySoft": "#1e3a5f"
    },
    "layout": {"sidebarSide": "right", "sidebarWidth": 220, "radius": 12, "font": "system"}
  }
}
```

`light` 和 `dark` 分别对应原来的日间/黑夜模式。配色字段均可省略，未指定时沿用该模式默认值。
支持 `background`、`surface`、`surfaceMuted`、`text`、`mutedText`、`border`、`primary`、
`primaryHover`、`primaryText`、`primarySoft`、`sidebar`、`topbar`；颜色只接受 `#RGB` 或 `#RRGGBB`。

| 布局字段 | 可用值 | 作用范围 |
| --- | --- | --- |
| `sidebarSide` | `left` / `right` | 启动器桌面布局，窄屏保留顶部导航 |
| `sidebarWidth` | 180–320 | 启动器桌面侧栏宽度 |
| `contentWidth` | 720–1800 | 启动器页面最大宽度 |
| `spacing` | `compact` / `comfortable` | 启动器桌面内容间距 |
| `font` | `system` / `serif` / `mono` | 主界面与工作室字体 |
| `radius` | 0–24 | 主界面与工作室主要控件圆角 |

应用或启用皮肤时自动停用其他皮肤；编辑已停用的皮肤不会自动启用。手工放入多个已启用皮肤时，
按 ID 排序使用第一个。皮肤热更新不会重建工作室或清空草稿。自定义界面的“恢复默认皮肤”会停用所有皮肤，
保留页面、组件和模型服务；管理入口与编辑子页面有独立样式，不受自定义配色影响。

皮肤由核心将上述配置映射到指定样式属性，不接受任意 CSS、选择器或 JavaScript。
要同时增加功能和换肤，可以使用一个组件插件和一个皮肤插件，各自热更新、回退。

## 浏览器 SDK

核心在插件脚本之前注入 `window.ftllm`。HTML 可以引用插件内的相对 JS/CSS 路径，不需要引入 SDK。
接口采用异步调用，清单必须声明对应能力：

| 能力 | 参数 | 返回值 |
| --- | --- | --- |
| `hardware.read` | 无 | CPU、内存、GPU、NUMA、磁盘和构建信息 |
| `runtime.read` | 无 | 当前模型服务状态；独立 WebUI 返回模型名称 |
| `model.chat` | `messages`、可选 `max_tokens` | `{content, reasoning}`，调用当前模型 API |
| `studio.context` | 无 | `{conversation, draft}`，当前对话与输入框文本的副本 |
| `studio.insert` | `{text}` | 在工作室当前输入位置插入文本，返回 `{ok: true}` |

`model.chat` 只接受 system/user/assistant 文本消息，不执行工具，不接受任意 API 地址。
`studio.*` 需要已打开的工作室。插件无法直接访问主页面的会话对象或修改其内部状态。

硬件插件的 `app.js` 示例：

```javascript
async function refresh() {
  const output = document.querySelector("#status");
  try {
    const report = await ftllm.call("hardware.read");
    const gib = bytes => (bytes / 1024 ** 3).toFixed(1);
    output.textContent = `内存：${gib(report.memory.total - report.memory.available)} / ${gib(report.memory.total)} GiB`;
  } catch (error) {
    output.textContent = error.message;
  }
}
refresh();
setInterval(refresh, 3000);
```

对应 HTML 提供 `<p id="status">正在读取…</p>` 和 `<script src="app.js"></script>` 即可。
GPU 字段包括 `name`、`memoryTotalMiB`、`memoryFreeMiB`、`utilization` 和 `temperature`。

主题和语言通过事件传入，同时设置根元素的 `data-theme`：

```javascript
window.addEventListener("ftllm-context", event => {
  const {theme, locale, palette} = event.detail;
  // 使用插件自己的 CSS 和文案适配主题、语言。
  // palette 是当前皮肤对应模式的配色；恢复默认皮肤时为空对象。
});
```

## 核心边界

核心负责鉴权、模型进程生命周期、会话存储、插件校验/发布/回退、能力代理和恢复入口。
内置页面的业务 JS 和模板位于各自目录，公共导航、主题、对话 API 及受控硬件读取服务保持稳定接口。
内置模块属于经过发布验证的代码；停用内置页面隐藏入口内容，不会停止模型、下载任务或删除会话。

可编辑插件运行在不带 `allow-same-origin` 的 sandbox iframe 中；它们不能访问父页面 DOM、控制令牌、
cookie 或浏览器存储，不能直接发起网络 API 请求。消息通道绑定具体 iframe 的具体版本，核心再次校验能力。
插件静态资源可被浏览器读取，所以不要把 API 密钥或其他秘密写进插件源码。

目前支持前端页面与交互扩展，不运行用户提供的 Python 后端。需要新增系统权限或超出上述接口的能力时，
应在核心中增加一个经过校验的服务接口，再供插件调用，不能通过生成代码绕过边界。
这一边界保护应用内的修改流程，不替代操作系统文件权限，也不保证隔离浏览器死循环或资源耗尽。

## 验证

```bash
PYTHONPATH=tools:test/api:test python3 -m unittest test_ui_plugins test_launcher test_launcher_webui test_webui_helpers
PYTHONPATH=tools:test/api python3 -m unittest test_launcher_webui_browser
```

浏览器测试需要 Playwright 和 Chromium，覆盖既有工作室行为、热更新、父 DOM 隔离、工作室扩展与模型预览发布。

---

### Llama Cookbook (`docs/llama_cookbook.md`)

# LLaMA 类模型转换参考

这个文档提供了了转换LLaMA同结构模型的方法。

LLaMA类模型有着基本相同的结构，但权重和prompt构造有差异。在fastllm中，通过转转模型时修改部分配置，实现对这些变体模型的支持、

## 声明

以下配置方案根据模型的源代码整理，不保证模型推理结果与原版完全一致。

## 修改方式

目前，转换脚本和两行加速方式均可用于llama类模型。但无论采用哪一种方式，都需要预留足够的内存（可以用swap空间）。

在float16模式下，转换时约需要4×参数量+1GB的空闲内存。

### 转换脚本

这里以支持推理各类Llama结构的基座模型为例，介绍如何应用本文档。

* 方案一：修改转换脚本

以alpaca2flm.py为模板修改。在创建model之后添加：

```python
    model = LlamaForCausalLM.from_pretrained(model_name).float()
    # config.json中定义了自己的model_type的需要添加
    conf = model.config.__dict__
    conf["model_type"] = "llama"
    # 接下来的部分各个Chat模型有差别，Base模型有的需要添加pre_prompt。
    torch2flm.tofile(exportPath, model, tokenizer, pre_prompt = "", 
                     user_role = "", bot_role = "", history_sep = "", 
                     dtype = dtype)
```
其中，`pre_prompt` 、`user_role` 、`bot_role` 、`history_sep`分别为“开始的系统提示词（第一轮对话之前）”，“用户角色标志”，“用户话语结束标志及模型回复开始标志”，“两轮对话之间的分隔符”。

* 方案二：修改config.json
在下载的模型目录下，修改配置文件`config.json`中，修改"model_type"为`llama`，并增加下面的键-值对：

```json
    "pre_prompt": "",
    "user_role": "",
    "bot_role": "",
    "history_sep":  "",
```

如需添加Token ID而非字符串（类似baichuan-chat模型），可以使用“<FLM_FIX_TOKEN_{ID}>”的格式添加。

* 执行脚本

```shell
python3 tools/alpaca2flm.py [输出文件名] [精度] [原始模型名称或路径]
```

### 两行加速

```python
    conf = model.config.__dict__
    conf["model_type"] = "llama"
    llm.from_hf(model, tokenizer, pre_prompt = "", 
                user_role = "", bot_role = "", history_sep = "", 
                dtype = dtype)
```

## 对齐

如果想使fastllm模型和原版transformers模型基本一致，最主要的操作是对齐tokenizer。
如果模型使用了huggingface 加速版本的Tokenizers（即模型目录中包含`tokenizer.json`并优先使用），目前的转换脚本**仅在从本地文件转换时，能够对齐tokenizer**。

注意检查原始tokenizer的`encode()`方法返回的结果前面是否会加空格。如果原始tokenizer没有加空格，则需要设置：

```python
    conf["tokenizer_add_dummy_prefix"] = False
```

## Base Model

见上方“[修改方案](#修改方式)”。

一部分模型需要制定bos_token_id，假设bos_token_id为1则可以配置如下：

```python
    torch2flm.tofile(exportPath, model, tokenizer, pre_prompt = "<FLM_FIX_TOKEN_1>", 
                     user_role = "", bot_role = "", history_sep = "", 
                     dtype = dtype)
```

## Chat Model

对Chat Model，同样是修改转换脚本，或修改模型的config.json，以下是目前常见的chat model的配置：

### InternLM（书生）

* internlm/[internlm-chat-7b](https://huggingface.co/internlm/internlm-chat-7b)
* internlm/[internlm-chat-7b v1.1](https://huggingface.co/internlm/internlm-chat-7b-v1_1)
* internlm/[internlm-chat-20b](https://huggingface.co/internlm/internlm-chat-20b)

```python
    conf = model.config.__dict__
    conf["model_type"] = "llama"
    torch2flm.tofile(exportPath, model, tokenizer, pre_prompt = "<s><s>", 
                     user_role = "<|User|>:", bot_role = "<eoh>\n<|Bot|>:", 
                     history_sep = "<eoa>\n<s>", dtype = dtype)
```

可以直接使用`llamalike2flm.py`脚本转换：

``` sh
cd build
python3 tools/llamalike2flm.py internlm-7b-fp16.flm float16 internlm/internlm-chat-20b #导出float16模型
python3 tools/llamalike2flm.py internlm-7b-int8.flm int8 internlm/internlm-chat-20b #导出int8模型
python3 tools/llamalike2flm.py internlm-7b-int4.flm int4 internlm/internlm-chat-20b #导出int4模型
python3 tools/llamalike2flm.py internlm-7b-int4.flm float16 internlm/internlm-chat-7b #导出internlm-chat-7b float16模型
```

* internlm/[internlm2-chat-1_8b](https://huggingface.co/internlm/internlm2-chat-1_8b)
* internlm/[internlm2-chat-7b](https://huggingface.co/internlm/internlm2-chat-7b)
* internlm/[internlm2-chat-20b](https://huggingface.co/internlm/internlm2-chat-20b)

使用`llamalike2flm.py`脚本转换：

``` sh
cd build
python3 tools/llamalike2flm.py internlm2-1.8b-fp16.flm float16 iinternlm/internlm2-chat-1_8b #导出1.8B float16模型
python3 tools/llamalike2flm.py internlm2-7b-fp16.flm float16 internlm/internlm2-chat-7b #导出chat-7b float16模型
python3 tools/llamalike2flm.py internlm2-7b-int8.flm int8 internlm/internlm2-chat-7b #导出chat-7b int8模型
```

### XVERSE

* xverse/[XVERSE-13B-Chat](https://huggingface.co/xverse/XVERSE-13B-Chat)
* xverse/[XVERSE-7B-Chat](https://huggingface.co/xverse/XVERSE-7B-Chat)

```python
    conf = model.config.__dict__
    conf["model_type"] = "llama"
    conf["tokenizer_add_dummy_prefix"] = False
    torch2flm.tofile(exportPath, model, tokenizer, pre_prompt = "", 
                     user_role = "Human: ", bot_role = "\n\nAssistant: ", 
                     history_sep = "<FLM_FIX_TOKEN_3>", dtype = dtype)
```
XVERSE-13B-Chat V1 版本需要对输入做NFKC规范化，fastllm暂不支持，因此需要使用原始tokenizer. 

* xverse/[XVERSE-13B-256K](https://huggingface.co/xverse/XVERSE-13B-256K)

该模型没有将RoPE外推参数放到config中，因此需要手工指定：
```python
    conf = model.config.__dict__
    conf["model_type"] = "llama"
    conf["rope_theta"] = 500000
    conf["rope_scaling.type"] = "dynamic"
    conf["rope_scaling.factor"] = 2.0
    conf["tokenizer_add_dummy_prefix"] = False
    torch2flm.tofile(exportPath, model, tokenizer, pre_prompt = "", 
                     user_role = "Human: ", bot_role = "\n\nAssistant: ", 
                     history_sep = "<FLM_FIX_TOKEN_3>", dtype = dtype)
```

### 其他 llama1 系列

* Vicuna v1.1 v1.3
```python
    torch2flm.tofile(exportPath, model, tokenizer, 
                     pre_prompt="A chat between a curious user and an artificial intelligence assistant. "
                                "The assistant gives helpful, detailed, and polite answers to the user's questions. "
                     user_role="USER: ", bot_role=" ASSISTANT:",  history_sep="<s>", dtype=dtype)
```

* BiLLa 
```python
    torch2flm.tofile(exportPath, model, tokenizer, pre_prompt = "\n", 
                     user_role = "Human: ", bot_role = "\nAssistant: ", 
                     history_sep = "\n", dtype = dtype)
```

### llama2-chat

* meta-llama/Llama-2-chat

|Model|Llama2-chat|Llama2-chat-hf|
|-----|-----|-----|
|  7B | [meta-llama/Llama-2-7b-chat](https://huggingface.co/meta-llama/Llama-2-7b-chat) | [meta-llama/Llama-2-7b-chat-hf](https://huggingface.co/meta-llama/Llama-2-7b-chat-hf) |
| 13B | [meta-llama/Llama-2-13b-chat](https://huggingface.co/meta-llama/Llama-2-13b-chat) | [meta-llama/Llama-2-13b-chat-hf](https://huggingface.co/meta-llama/Llama-2-13b-chat-hf) |

|Model|  CodeLlama-Instruct                                                                               |
|-----| ------------------------------------------------------------------------------------------------- |
|  7B |  [codellama/CodeLlama-7b-Instruct-hf](https://huggingface.co/codellama/CodeLlama-7b-Instruct-hf)  |
| 13B | [codellama/CodeLlama-13b-Instruct-hf](https://huggingface.co/codellama/CodeLlama-13b-Instruct-hf) |

官方示例代码中，可以不用系统提示语：

```python
    torch2flm.tofile(exportPath, model, tokenizer, pre_prompt = "<FLM_FIX_TOKEN_1>", 
                     user_role = "[INST] ", bot_role = " [/INST]", 
                     history_sep = " <FLM_FIX_TOKEN_2><FLM_FIX_TOKEN_1>", dtype = dtype)
```

**Llama-2系列支持系统提示语需要修改代码**，单轮可以使用以下带有系统提示语的版本：

```python
    torch2flm.tofile(exportPath, model, tokenizer, 
                     pre_prompt = "<FLM_FIX_TOKEN_1>[INST] <<SYS>>\nYou are a helpful, respectful and honest assistant. Always answer as helpfully as possible, " \
        "while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. " \
        "Please ensure that your responses are socially unbiased and positive in nature.\n\nIf a question does not make any sense, " \
        "or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, " \
        "please don't share false information.\n<</SYS>>\n\n", 
                     user_role = " ", bot_role = " [/INST]", 
                     history_sep = " <FLM_FIX_TOKEN_2><FLM_FIX_TOKEN_1>", dtype = dtype)
```

* ymcui/Chinese-Alpaca-2

|Model|Chinese-Alpaca-2|Chinese-Alpaca-2-16K|
|-----|-----|-----|
|  7B | [ziqingyang/chinese-alpaca-2-7b](https://huggingface.co/ziqingyang/chinese-alpaca-2-7b) | [ziqingyang/chinese-alpaca-2-7b-16k](https://huggingface.co/ziqingyang/chinese-alpaca-2-7b-16k) |
| 13B | [ziqingyang/chinese-alpaca-2-13b](https://huggingface.co/ziqingyang/chinese-alpaca-2-13b) | [ziqingyang/chinese-alpaca-2-13b-16k](https://huggingface.co/ziqingyang/chinese-alpaca-2-13b-16k) |

```python
    torch2flm.tofile(exportPath, model, tokenizer, 
                     pre_prompt = "<FLM_FIX_TOKEN_1>[INST] <<SYS>>\nYou are a helpful assistant. 你是一个乐于助人的助手。\n<</SYS>>\n\n"
                     user_role = " ", bot_role = " [/INST]", 
                     history_sep = " <FLM_FIX_TOKEN_2><FLM_FIX_TOKEN_1>", dtype = dtype)
```

### RUC-GSAI/YuLan-Chat

  * Full
    * [YuLan-Chat-2-13B](https://huggingface.co/yulan-team/YuLan-Chat-2-13b-fp16)
  * Delta (需要原始LLaMA)
    * [YuLan-Chat-1-65B-v2](https://huggingface.co/yulan-team/YuLan-Chat-1-65B-v2-delta) 
    * [YuLan-Chat-1-65B-v1](https://huggingface.co/RUCAIBox/YuLan-Chat-65b-delta) 
    * [YuLan-Chat-1-13B-v1](https://huggingface.co/RUCAIBox/YuLan-Chat-13b-delta) 

```python
    torch2flm.tofile(exportPath, model, tokenizer, 
                     pre_prompt="The following is a conversation between a human and an AI assistant namely YuLan, developed by GSAI, Renmin University of China. " \
                                "The AI assistant gives helpful, detailed, and polite answers to the user's questions.\n",
                     user_role="[|Human|]:", bot_role="\n[|AI|]:", history_sep="\n", dtype=dtype)
```

### Yi

* 01-ai/[Yi-6B-Chat](https://huggingface.co/01-ai/Yi-6B-Chat)

* 01-ai/[Yi-34B-Chat](https://huggingface.co/01-ai/Yi-34B-Chat)

```python
    torch2flm.tofile(exportPath, model, tokenizer, pre_prompt="",
                     user_role="<|im_start|>user\n", bot_role="<|im_end|><|im_start|>assistant\n", history_sep="<|im_end|>\n", dtype=dtype)
```

* [SUSTech/SUS-Chat-34B](https://huggingface.co/SUSTech/SUS-Chat-34B)

### WizardCoder

  * [WizardCoder-Python-7B-V1.0](https://huggingface.co/WizardLM/WizardCoder-Python-7B-V1.0)
  * [WizardCoder-Python-13B-V1.0](https://huggingface.co/WizardLM/WizardCoder-Python-13B-V1.0)

```python
    torch2flm.tofile(exportPath, model, tokenizer, 
                     pre_prompt="Below is an instruction that describes a task. " \
                                "Write a response that appropriately completes the request.\n\n",
                     user_role="### Instruction:\n", bot_role="\n\n### Response:", history_sep="\n", dtype=dtype)
```

### Deepseek Coder

  * [Deepseek-Coder-1.3B-Instruct](https://huggingface.co/deepseek-ai/deepseek-coder-1.3b-instruct)
  * [Deepseek-Coder-6.7B-Instruct](https://huggingface.co/deepseek-ai/deepseek-coder-6.7b-instruct)
  * [Deepseek-Coder-7B-Instruct v1.5](https://huggingface.co/deepseek-ai/deepseek-coder-7b-instruct-v1.5)

```python
    torch2flm.tofile(exportPath, model, tokenizer, 
                     pre_prompt="<FLM_FIX_TOKEN_32013>	You are an AI programming assistant, utilizing the Deepseek Coder model, developed by Deepseek Company, " \
                                "and you only answer questions related to computer science. For politically sensitive questions, security and privacy issues, " \
                                "and other non-computer science questions, you will refuse to answer.\n",
                     user_role="### Instruction:\n", bot_role="\n### Response:\n", history_sep="\n<|EOT|>\n", dtype=dtype)
```

---

### Mixforward (`docs/mixforward.md`)

# 混合推理使用说明

这个文档以`DeepSeek-V4-Flash`模型为例，介绍如何使用混合推理来榨干硬件

## 基本用法

假设我们在一台有两张48G的显卡上部署`DeepSeek-V4-Flash`模型，一般用法是这样的


```
ftllm server fastllm/DeepSeek-V4-Flash
```

这时候会默认将模型的moe部分运行在numa上，非moe部分运行在cuda上，等价于如下命令：

```
ftllm server fastllm/DeepSeek-V4-Flash --device cuda --moe_device numa
```

## 将部分moe层运行在单张显卡上

用上述命令运行时，显存会有大量剩余，我们可以通过设置`moe_device`，将一部分moe层
指定在cuda上运行

```
ftllm server fastllm/DeepSeek-V4-Flash --device cuda --moe_device "{'cuda':1,'numa':19}"
```

上述命令中将`moe_device`设置为`"{'cuda':1,'numa':19}"`，代表`1/20`的moe层运行在cuda上，`19/20`的moe层运行在numa上

这样能轻微提升decode速度，但是可能会降低上下文长度

## moe部分使用显卡、numa、硬盘一起推理

当机器的内存不足以放下全部moe权重，或者希望把显卡、numa和SSD都利用起来时，可以把`moe_device`同时设置为`cuda`、`numa`和`disk`

```
ftllm server fastllm/DeepSeek-V4-Flash --device cuda --moe_device "{'cuda':1,'numa':8,'disk':1}"
```

上述命令中将`moe_device`设置为`"{'cuda':1,'numa':8,'disk':1}"`，代表`1/10`的moe层运行在cuda上，`8/10`的moe层运行在numa上，`1/10`的moe层从硬盘读取并推理

硬盘推理速度主要受SSD读取速度影响，一般会比numa慢很多，建议只把少量moe层放到`disk`上作为容量补充。如果上下文较长，可以配合`--chunked_prefill_size`降低prefill阶段的显存峰值

## 将部分moe层运行在多张显卡上

使用下面的命令可以使用多张显卡来加速部分moe层

```
ftllm server fastllm/DeepSeek-V4-Flash --device cuda --moe_device "{'multicuda:0,1':15,'numa':85}"
```

上述命令中将`moe_device`设置为`"{'multicuda:0,1':15,'numa':85}"`，代表`15/100`的moe层使用0,1两张gpu张量并行推理，`85/100`的moe层运行在numa上

这样能进一步提升decode速度

## multicuda调优环境变量

multicuda会为每张CUDA卡维护专用worker线程来执行多卡任务，减少通用线程池调度和线程唤醒开销。下面这些环境变量可以用来排查或调优专用worker行为：

| 环境变量 | 默认值 | 说明 |
| --- | --- | --- |
| `FASTLLM_DISABLE_MULTICUDA_DEDICATED_WORKERS` | 未设置 | 设为`1`、`true`等有效值时关闭multicuda专用worker，回退到通用线程池执行多卡任务。设为`0`、`false`或`off`等价于未设置。 |
| `FASTLLM_MULTICUDA_WORKER_CPU_BASE` | `0` | 专用worker绑核的起始CPU编号。第`i`个worker会绑定到`(CPU_BASE + i) % CPU数量`。 |
| `FASTLLM_MULTICUDA_WORKER_SPIN_ITERS` | `262144` | worker没有新任务时的忙等轮数，超过后进入条件变量等待。数值越大，任务延迟可能更低，但空闲CPU占用更高；设为`0`可以尽量减少忙等。 |

例如希望worker从CPU 16开始绑核，并减少空闲忙等：

```
export FASTLLM_MULTICUDA_WORKER_CPU_BASE=16
export FASTLLM_MULTICUDA_WORKER_SPIN_ITERS=4096
ftllm server fastllm/DeepSeek-V4-Flash --device cuda --moe_device "{'multicuda:0,1':15,'numa':85}"
```

如果怀疑专用worker导致调度或绑核问题，可以临时关闭：

```
export FASTLLM_DISABLE_MULTICUDA_DEDICATED_WORKERS=1
ftllm server fastllm/DeepSeek-V4-Flash --device cuda --moe_device "{'multicuda:0,1':15,'numa':85}"
```

---

### Model Deployment Agent (`docs/model-deployment-agent.md`)

# Agent 部署速查：Qwen3.8、Flash-Next、DeepSeek V4.1 与 GLM-5.3

这份说明给自动化 agent 使用。建议用户先把本文和目标模型的部署指南交给 agent，再让 agent 根据机器的 GPU、显存、主机内存、模型路径和端口执行。下载页中的模型簇和下面的命令使用相同的 ModelScope ID；路径、端口和模型名由 agent 按机器环境替换。

## 选择与默认策略

混合推理优先使用 `--auto`。它会在启动时读取可见 GPU、显存、主机内存和模型配置，联合选择主干设备、MoE 专家设备、专家缓存、上下文容量和分块 prefill。显式参数覆盖自动规划器，适合在第一次启动失败或需要固定实验条件时使用。

当前 `--auto` 支持 Linux NVIDIA CUDA 上的 Qwen3 dense、Qwen3.5 架构的稠密 HF 模型、Qwen4/Flash-Next、GLM-5.3 和 DeepSeek-V4.1。Qwen3.8-27B-FP8 已验证自动 TP2、内置 MTP5、并发 4 和原生 262144 上下文；其他 Qwen3.5 checkpoint 需分别验证。

| 模型簇 | 推荐入口 | 适合的下载项 |
| --- | --- | --- |
| Qwen3.8 27B FP8 | `--auto`；纯 GPU TP、MTP、并发 4、尽量使用原生上下文 | `Qwen/Qwen3.8-27B-FP8` |
| Qwen3.8 27B 其他格式 | 手动 CUDA / TP，按量化格式单独验证 | `Qwen/Qwen3.8-27B`、`QUASAR-QAT/Qwen3.8-27B-QUASAR-NVFP4` |
| Qwen3.8-Flash-Next GSQ-RCO | `--auto`；GGUF 需要把首个分片作为模型路径 | `Q2_0`、`IQ2_XS`、`IQ3_XXS`、`IQ3_S`，下载时保留对应子目录和 `mmproj` |
| DeepSeek V4.1 Flash | `--auto`；主机内存不足时按日志改用磁盘专家 | `deepseek-ai/DeepSeek-V4.1-Flash-1017` |
| GLM-5.3-Flash | `--auto`；GGUF 需要把首个分片作为模型路径 | 本地完整 HF/NVFP4 目录或 `glm5next` GGUF 分片，见 [GLM 指南](glm5.md) |

自动化流程应等待日志出现就绪事件，再请求 `/v1/models` 检查 `id` 和上下文窗口，最后发送一个短请求验证输出。不要根据进程已经启动就判定模型可用。

## 推荐部署顺序

1. 确认模型架构属于上面的支持范围，再用下方的 `--auto` 命令启动；尚未验证的 Qwen3.8 格式使用手动配置。
2. 等待就绪日志，读取规划器实际选择的 `device`、`moe_device`、缓存、chunk 和上下文上限。
3. 调用 `/v1/models` 和一次短请求；如果失败，把完整启动日志交给 agent，再只增加必要的显式覆盖。
4. 需要比较性能时固定 GPU 可见性、线程、输入输出长度和 warmup，只改变一个配置项。

`--auto_gpu_memory_limit` 和 `--auto_host_memory_limit` 是规划上限，不是运行时强制的进程 cgroup。省略时自动读取当前机器配置；填写后，agent 应把限制作为可用预算并在日志中确认回退路径。

## 启动命令

```bash
# Qwen3.8-27B FP8：自动纯 GPU TP，默认并发 4、内置 MTP5、最长可用原生上下文
ftllm server /data/models/Qwen3.8-27B-FP8 \
  --auto --model_name qwen3.8-27b \
  --host 0.0.0.0 --port 8080

# Qwen3.8-Flash-Next GSQ-RCO；路径指向下载目录中的第一个 GGUF 分片
ftllm server /data/models/Qwen3.8-Flash-Next-GSQ-RCO/Q2_0/Qwen3.8-Flash-Next-GSQ-RCO-Q2_0-00001-of-00002.gguf \
  --auto --model_name qwen3.8-flash-next \
  --host 0.0.0.0 --port 8080

# DeepSeek V4.1 Flash 1017
ftllm server /data/models/DeepSeek-V4.1-Flash-1017 \
  --auto --model_name deepseek-v4.1-flash-1017 \
  --host 0.0.0.0 --port 8080

# GLM-5.3-Flash GGUF；路径指向第一个分片
ftllm server /data/models/GLM-5.3-Flash-UD-IQ2_XXS-00001-of-00004.gguf \
  --auto --model_name glm-5.3-flash \
  --host 0.0.0.0 --port 8080
```

API 检查示例：

```bash
curl http://127.0.0.1:8080/v1/models
curl http://127.0.0.1:8080/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"qwen3.8-27b","messages":[{"role":"user","content":"用一句话介绍自己。"}],"max_tokens":64}'
```

## 手动参数与覆盖规则

自动模式失败、需要复现实验或需要固定设备布局时，再使用下面的手动参考。参数组合要以目标模型指南和启动日志为准；同一行命令不要同时指定互相冲突的 `--tp` 与 `cudapp`。

### 设备和专家放置

| 参数 | 用途 | 典型值 |
| --- | --- | --- |
| `--device` | 主干计算设备 | `cuda`、`cudapp=2`、`numa` |
| `--tp` | CUDA 张量并行 | `2`、`0,1`、`auto` |
| `--moe_device` | MoE 专家设备 | `cuda`、`numa`、`cpu`、`disk` |
| `--moe_device_layers` | 让最后 N 层使用 MoE 设备，剩余专家留在主设备 | `36` |
| `--threads` | CPU/NUMA 线程数 | `28`、`64` |
| `--gpu_mem_ratio` | 给模型、KV 和缓存的显存比例 | `0.9` |
| `--auto_gpu_memory_limit` | 自动规划时每张 GPU 的显存上限 | `20g`；省略则读取实际可用显存 |
| `--auto_host_memory_limit` | 自动规划时主机内存总上限 | `128g`；省略则读取实际可用内存 |

例如固定双卡 PP、NUMA 专家：

```bash
ftllm server /data/models/Qwen3.8-Flash-Next \
  --device cudapp=2 --moe_device numa --threads 28 \
  --chunked_prefill_size 4096 --gpu_mem_ratio 0.9
```

### 模型手动起步配置

这些命令用于定位问题和做可重复对照，数值不是所有机器的最佳值：

```bash
# Qwen3.8 27B FP8：双卡 TP，显存足够时优先
ftllm server /data/models/Qwen3.8-27B-FP8 \
  --device cuda --tp 2 --chunked_prefill_size 4096 \
  --gpu_mem_ratio 0.9 --max_batch 1

# Qwen3.8-Flash-Next GSQ：双卡按层，专家放 NUMA
ftllm server /data/models/Qwen3.8-Flash-Next-GSQ-RCO/Q2_0/Qwen3.8-Flash-Next-GSQ-RCO-Q2_0-00001-of-00002.gguf \
  --device cudapp=2 --moe_device numa --ngram_device disk \
  --chunked_prefill_size 4096 --moe_cuda_cache 0 --mtp 0

# DeepSeek V4.1 Flash：大内存单卡混合推理
ftllm server /data/models/DeepSeek-V4.1-Flash-1017 \
  --device cuda --moe_device numa --ngram_device cpu \
  --fast_prefill --dspark 7 --max_context_length 1048576 \
  --chunked_prefill_size 4096

# GLM-5.3-Flash：双卡 PP，NUMA 专家，先关闭 MTP 做基线
ftllm server /data/models/GLM-5.3-Flash-UD-IQ2_XXS-00001-of-00004.gguf \
  --device cudapp=2 --moe_device numa --chunked_prefill_size 8192 \
  --dtype bfloat16 --atype bfloat16 --moe_atype bfloat16 \
  --moe_cuda_cache 8g --mtp 0
```

Qwen3.8-Flash-Next 和 GLM 的 GGUF 分片必须保留同目录的其余分片；DeepSeek 的 `--ngram_device cpu` 需要约 200 GB 级别的 Engram 常驻内存，内存不足时改为 `disk`。模型支持的参数和实际默认值会随 checkpoint 变化，agent 应以 `ftllm server --help`、模型指南和启动日志为最终依据。

### 缓存、上下文和 prefill

| 参数 | 用途 | 说明 |
| --- | --- | --- |
| `--moe_cuda_cache` | GPU 专家缓存 | `4g` 表示每张参与设备的预算；只对 CPU/NUMA/disk 专家有效 |
| `--moe_cpu_cache` | 磁盘专家的主存缓存 | `32g`、`64g`；不是进程总内存上限 |
| `--ngram_device` | Qwen PLE 或 V4.1 Engram | `cpu` 常驻内存，`disk` 按需读取 |
| `--chunked_prefill_size` | 每块 prefill 的 token 数 | 长输入可从 `4096` 开始，显存允许时增大 |
| `--max_context_length` | 单会话输入和输出合计上限 | DeepSeek 自动模式默认最多 1M，其它模型通常按 128K 规划 |
| `--tokens` | 所有请求共享的 KV token 池 | 并发或长上下文时显式设置 |
| `--kv_cache_limit` | KV Cache 总预算 | 例如 `8g`、`32g` |
| `--prefix_cache true` | 复用重复前缀 | 会占用 KV 预算；自动模式可能按模型策略选择 |

### 投机解码与快速路径

| 参数 | 用途 |
| --- | --- |
| `--dspark 7` | DeepSeek V4.1 内置 DSpark，每轮最多 7 个草稿 token |
| `--mtp 3` | 支持 MTP 的 Qwen/其它模型启用 MTP |
| `--draft` / `--speculative_draft_model_path` | 指定外部 DFlash/MTP 草稿目录 |
| `--fast_prefill` | 启用模型支持的快速 prefill；DeepSeek V4.1 使用近似注意力，可能改变 logits 和输出 |
| `--no_fast_prefill` | 关闭快速 prefill，做精度或回归对照 |
| `--speculative_dspark_confidence_threshold 0.5` | DSpark 草稿置信度门槛；提高时通常更早停止草稿生成，接受率和速度需实测 |

DeepSeek V4.1 的自动模式默认规划不超过原生窗口的 1M 上下文、DSpark 7 和 `fast_prefill`；预算不足且调整专家放置、缓存和 chunk 后仍不可行时会报错，不自动缩短窗口。显式 `--dspark 0`、`--no_fast_prefill` 或上下文参数会覆盖默认值。GSQ GGUF 的量化布局由文件决定，不要再额外指定在线量化类型。

## 低资源回退

当日志显示显存或主机内存不足时，按以下顺序修改并重试：先减少 `--moe_cuda_cache`，再把 `--moe_device numa` 改成 `--moe_device disk --moe_cpu_cache 32g`，最后降低 `--max_context_length` 或 `--chunked_prefill_size`。磁盘专家对随机读取敏感，应该把模型放在本地 NVMe；`--ngram_device disk` 只控制 PLE/Engram 表，不等于专家已经走磁盘。

## Agent 验收清单

1. 启动命令返回后，等待 `ready` 或等价的服务就绪日志。
2. `GET /v1/models` 必须返回预期 `model_name`。
3. 发送短文本请求，确认 HTTP 200、输出非空且没有 NaN/设备错误。
4. 从启动日志记录实际 `device`、`moe_device`、缓存容量、`chunked_prefill_size` 和上下文窗口；这些值可能与请求的自动配置不同。
5. 需要比较速度时固定输入长度、输出长度、线程数和 GPU 可见性，只改变一个规划参数。

更多模型专用限制见 [Qwen3.8-Flash-Next 用户手册](qwen3.8-flash-next/README.md)、[Qwen3 部署指南](qwen3.md)、[DeepSeek V4.1 说明](deepseek_v41.md)、[GLM 指南](glm5.md) 和 [自动规划器说明](hybrid-auto.md)。

---

### Models (`docs/models.md`)

# 支持的模型 Supported Models

## 说明

目前Fastllm加载模型有以下几种方式。

* **直接读取** (load from Huggingface .safetensors)  
    直接读取HuggingFace上发布的.safetensors格式的模型（其他格式的模型可以使用transformer库导出成safetensors格式，参见[导出safetensors模型](#导出safetensors模型)。

* **离线转换** (convert offline)  
    将原始模型转换为.flm格式的模型，一些[模型](#flm模型库)已经转换好。

* **加载后转换（不推荐）**  (convert on-the-fly (NOT RECOMMENDED)) 
    将原始模型加载为HuggingFace模型，再通过`from_hf()`方法，转换并加速，这种方法内存占用大且速度慢，目前不再推荐。

## 支持模型一览 Model List


* ✔ 表示支持该方式，并测试通过；  
    ✔ means supports this mode and passes the test.

* ❌ 表示本应该支持该方式，但实际测试后发现本功能并不受支持，可能在后续版本修复；  
    ❌ means this method is supposed to be supported, but failed after actual testing.

* √ 表示支持，但是还没有测试过。  
    √ means supported, but not tested.

### GLM系列

|              模型  | 加载后转换 |  离线转换  |  直接读取  |
|-----------------: |------------|------------|------------|
| THUDM/ChatGLM-6b | [✔](#chatglm系列) | [✔](#chatglm模型导出-默认脚本导出chatglm2-6b模型) |  |
| THUDM/ChatGLM-6b-int8 | [✔](#chatglm系列) | ❌ |  |
| THUDM/ChatGLM-6b-int4 | [✔](#chatglm系列) | ❌ |  |
| THUDM/ChatGLM2-6b | [✔](#chatglm系列) | [✔](#chatglm模型导出-默认脚本导出chatglm2-6b模型) |  |
| THUDM/glm-large-chinese |  | [✔](../tools/scripts/glm_export.py) | |
| THUDM/ChatGLM2-6b-int8 | [✔](#chatglm系列) | ❌ |  |
| THUDM/ChatGLM2-6b-int4 | [✔](#chatglm系列) | ❌ |  |
| THUDM/ChatGLM2-6b-32k | [✔](#chatglm系列) | [✔](#chatglm模型导出-默认脚本导出chatglm2-6b模型) |  |
| THUDM/ChatGLM3-6b | [✔](#chatglm系列) | [✔](#chatglm模型导出-默认脚本导出chatglm2-6b模型) |  |
| THUDM/ChatGLM3-6b-32k | [✔](#chatglm系列) | [✔](#chatglm模型导出-默认脚本导出chatglm2-6b模型) |  |
| THUDM/ChatGLM3-6b-128k | ❌ | ❌ |  |
| THUDM/glm-4-9b-chat | [✔](#chatglm系列) | [✔](#chatglm模型导出-默认脚本导出chatglm2-6b模型) | ✔ |
| THUDM/codegeex4-all-9b | [✔](#chatglm系列)<sup>2</sup> | [✔](#chatglm模型导出-默认脚本导出chatglm2-6b模型)<sup>2</sup> | ✔ |

> 注2：需要手动设置 pre_prompt

### Qwen系列

|              模型  | 加载后转换 |  离线转换  |  直接读取  |
|-------------------: |------------|------------|------------|
| Qwen/Qwen-7B-Chat   | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen-14B-Chat  | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen-72B-Chat  | [✔](#其它模型) | [✔](#qwen模型导出) | √ |
| Qwen/Qwen-1_8B-Chat | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen1.5-0.5B-Chat | [✔](#其它模型) | [✔](#qwen模型导出) | ✔<sup>3</sup> |
| Qwen/Qwen1.5-1.8B-Chat | [✔](#其它模型) | [✔](#qwen模型导出) | ✔<sup>3</sup> |
| Qwen/Qwen1.5-4B-Chat   | [✔](#其它模型) | [✔](#qwen模型导出) | ✔<sup>3</sup> |
| Qwen/Qwen1.5-7B-Chat   | [✔](#其它模型) | [✔](#qwen模型导出) | ✔<sup>3</sup> |
| Qwen/Qwen1.5-14B-Chat  | [✔](#其它模型) | [✔](#qwen模型导出) | ✔<sup>3</sup> |
| Qwen/Qwen1.5-72B-Chat  | [✔](#其它模型) | [✔](#qwen模型导出) | ✔<sup>3</sup> |
| Qwen/Qwen1.5-32B-Chat  | [✔](#其它模型) | [✔](#qwen模型导出) | ✔<sup>3</sup> |
| Qwen/Qwen1.5-110B-Chat | [√](#其它模型) | [√](#qwen模型导出) | √<sup>3</sup> |
| Qwen/CodeQwen1.5-7B-Chat | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen2-0.5B-Instruct | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen2-1.5B-Instruct | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen2-7B-Instruct   | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen2-72B-Instruct  |  | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen2.5-0.5B-Instruct | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen2.5-1.5B-Instruct | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen2.5-3B-Instruct   | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen2.5-7B-Instruct   | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen2.5-14B-Instruct  | [✔](#其它模型) | [✔](#qwen模型导出) | ✔ |
| Qwen/Qwen2.5-32B-Instruct  | √ | √ | ✔ |
| Qwen/Qwen2.5-72B-Instruct  |  | √ | ✔ |
| [Qwen4-Exp / Qwen3.8-Flash-Next FP8 text](qwen4_exp.md) |  |  | ✔ |

> 注3： ~~需要更新，检查 `tokenizer_config.json` 是否为最新版本~~

### DeepSeek系列

|                                       模型  | 加载后转换 |  离线转换  |  直接读取  |
|-------------------------------------------: |------------|------------|------------|
| deepseek-ai/Deepseek-Coder-1.3B-Instruct    | [✔](llama_cookbook.md#deepseek-coder) | [✔](llama_cookbook.md#deepseek-coder) | ✔ |
| deepseek-ai/Deepseek-Coder-6.7B-Instruct    | [✔](llama_cookbook.md#deepseek-coder) | [✔](llama_cookbook.md#deepseek-coder) | ✔ |
| deepseek-ai/Deepseek-Coder-7B-Instruct v1.5 | [✔](llama_cookbook.md#deepseek-coder) | [✔](llama_cookbook.md#deepseek-coder) | ✔ |
| deepseek-ai/deepseek-coder-33b-instruct     | [√](llama_cookbook.md#deepseek-coder) | [√](llama_cookbook.md#deepseek-coder) | ✔ |
| deepseek-ai/DeepSeek-V4.1-Flash（[说明](deepseek_v41.md)） | ✔ | ✔ | ✔ |
| deepseek-ai/DeepSeek-V2-Chat                | √ | ✔ | √ |
| deepseek-ai/DeepSeek-V2-Lite-Chat           | √ | ✔ | ✔ |
| deepseek-ai/DeepSeek-Coder-V2-Instruct      | √ | ✔ | √ |
| deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct | √ | ✔ | ✔ |

### LLaMA类模型

|              模型  | 加载后转换 |  离线转换  |  直接读取  |
|-----------------: |------------|------------|------------|
| meta-llama/Llama-2-7b-chat-hf | [✔](llama_cookbook.md#llama2-chat) | [✔](llama_cookbook.md#llama2-chat) |  |
| meta-llama/Llama-2-13b-chat-hf | [✔](llama_cookbook.md#llama2-chat) | [✔](llama_cookbook.md#llama2-chat) |  |
| codellama/CodeLlama-7b-Instruct-hf | [✔](llama_cookbook.md#llama2-chat) | [✔](llama_cookbook.md#llama2-chat) | ✔ |
| codellama/CodeLlama-13b-Instruct-hf | [✔](llama_cookbook.md#llama2-chat) | [✔](llama_cookbook.md#llama2-chat) | ✔ |
| xverse/XVERSE-13B-Chat | [✔](llama_cookbook.md#xverse) | [✔](llama_cookbook.md#xverse) |  |
| xverse/XVERSE-7B-Chat | [✔](llama_cookbook.md#xverse) | [✔](llama_cookbook.md#xverse) |  |
|  |  |  |  |
| internlm/internlm-chat-7b | [✔](llama_cookbook.md#internlm书生) | [✔](llama_cookbook.md#internlm书生) |  |
| internlm/internlm-chat-20b | [✔](llama_cookbook.md#internlm书生) | [✔](llama_cookbook.md#internlm书生) |  |
| internlm/internlm2-chat-1_8b | [✔](llama_cookbook.md#internlm书生) | [✔](llama_cookbook.md#internlm书生) | ✔<sup>4</sup> |
| internlm/internlm2-chat-7b | [✔](llama_cookbook.md#internlm书生) | [✔](llama_cookbook.md#internlm书生) | ✔<sup>4</sup> |
| internlm/internlm2-chat-20b | [✔](llama_cookbook.md#internlm书生) | [✔](llama_cookbook.md#internlm书生) | ✔<sup>4</sup> |
| internlm/internlm3-8b-instruct | [✔](llama_cookbook.md#internlm书生) | [✔](llama_cookbook.md#internlm书生) | ✔<sup>4</sup> |
|  |  |  |  |
| 01-ai/Yi-6B-Chat | [✔](llama_cookbook.md#yi) | [✔](llama_cookbook.md#yi) | ✔<sup>4</sup> |
| 01-ai/Yi-34B-Chat | [✔](llama_cookbook.md#yi) | [✔](llama_cookbook.md#yi) | ✔<sup>4</sup> |
| SUSTech/SUS-Chat-34B | [✔](llama_cookbook.md#llama2-chat) | [✔](llama_cookbook.md#llama2-chat) |  |
| 01-ai/Yi-Coder-1.5B-Chat | [✔](llama_cookbook.md#yi) | [✔](llama_cookbook.md#yi) | ✔<sup>4</sup> |
| 01-ai/Yi-Coder-9B-Chat | [✔](llama_cookbook.md#yi) | [✔](llama_cookbook.md#yi) | ✔<sup>4</sup> |
|  |  |  |  |
| meta-llama/Meta-Llama-3-8B-Instruct |  | [✔](../tools/scripts/llama3_to_flm.py) | ✔ |
| meta-llama/Meta-Llama-3-70B-Instruct |  | [✔](../tools/scripts/llama3_to_flm.py) | ✔ |

> 注4： Python ftllm用AutoTokenizer而不使用Fastllm Tokenizer可以实现加载，C++程序需要安装`libsentencepiece.so`，并在编译时打开`USE_SENTENCEPIECE`选项才能加载Tokenizer。


### 其它模型

|              模型  | 加载后转换 |  离线转换  |  直接读取  |
|-----------------: |------------|------------|------------|
| microsoft/Phi-3-mini-4k-instruct |  |  | ✔ |
| google/gemma-2-9b |  |  | ✔ |
| google/gemma-2-27b |  |  | ✔ |
| TeleAI/TeleChat2-3B |  |  | ✔ |
| TeleAI/TeleChat2-7B |  |  | ✔ |
| fnlp/moss-moon-003-sft | [✔]() | [✔](#moss模型导出) |  |
| fnlp/moss-moon-003-sft-plugin | [✔]() | [✔](#moss模型导出) |  |
|  |  |  |  |
| baichuan-inc/baichuan-13b-chat | [✔](#其它模型) | [✔](#baichuan模型导出-默认脚本导出baichuan-13b-chat模型) |  |
| baichuan-inc/Baichuan2-7B-Chat | [✔](#其它模型) | [✔](#baichuan2模型导出-默认脚本导出baichuan2-7b-chat模型) |  |
| baichuan-inc/baichuan2-13b-chat | [✔](#其它模型) | [✔](#baichuan2模型导出-默认脚本导出baichuan2-7b-chat模型) |  |
|  |  |  |  |
| openbmb/MiniCPM-2B-sft-fp16 | [✔](#其它模型) | [✔](#minicpm模型导出) |  |
| openbmb/MiniCPM-2B-dpo-fp16 | [✔](#其它模型) | [✔](#minicpm模型导出) |  |
| openbmb/MiniCPM3-4B | [✔](#其它模型) | [✔](#minicpm模型导出) |  |
|  |  |  |  |


### 导出safetensors模型

通过transformers库可以将模型导出成.safetensors格式，代码如下：

``` python
# 保存这段代码为trans.py, 然后执行
# python trans.py --input 原模型地址 --output 导出.safetensors模型的地址（可以和input相同）
from transformers import AutoModelForCausalLM, AutoTokenizer

import argparse
def parse_args():
    parser = argparse.ArgumentParser(description = "trans")
    parser.add_argument("--input", type = str, required = True)
    parser.add_argument("--output", type = str, required = True)
    return parser.parse_args()

if __name__ == "__main__":
    args = parse_args()
    tokenizer = AutoTokenizer.from_pretrained(args.input, trust_remote_code = True)
    model = AutoModelForCausalLM.from_pretrained(args.input, device_map = "cpu",torch_dtype = "auto", trust_remote_code = True).eval()

    model.save_pretrained(args.output, max_shard_size = "2048MB", safe_serialization = True)
    tokenizer.save_pretrained(args.output, max_shard_size = "2048MB", safe_serialization = True)
```

### 加载后转换（两行加速模式）(convert on-the-fly)

#### ChatGLM系列

``` python
# 这是原来的程序，通过huggingface接口创建模型
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm2-6b", trust_remote_code = True)
model = AutoModel.from_pretrained("THUDM/chatglm2-6b", trust_remote_code = True)

# 加入下面这两行，将huggingface模型转换成fastllm模型
# 目前from_hf接口只能接受原始模型，或者ChatGLM的int4, int8量化模型，暂时不能转换其它量化模型
from ftllm import llm
model = llm.from_hf(model, tokenizer, dtype = "float16") # dtype支持 "float16", "int8", "int4"
model = model.eval()
```

model支持了ChatGLM的API函数`chat()`, `stream_chat()`，因此ChatGLM的demo程序无需改动其他代码即可运行

#### 其它模型

``` python
# 通过huggingface接口创建模型，参考每个模型readme.md中的加载方式
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code = True)
model = AutoModelForCausalLM.from_pretrained(model_name_or_path, trust_remote_code = True)

# 加入下面这两行，将huggingface模型转换成fastllm模型
# 目前from_hf接口只能接受原始模型，或者ChatGLM的int4, int8量化模型，暂时不能转换其它量化模型
from ftllm import llm
model = llm.from_hf(model, tokenizer, dtype = "float16") # dtype支持 "float16", "int8", "int4"
```
ftllm实现了兼容Transformers的`generate()`方法。


转好的模型也可以导出到本地文件，之后可以直接读取，也可以使用fastllm cpp接口读取

``` python
model.save("model.flm"); # 导出fastllm模型
new_model = llm.model("model.flm"); # 导入flm模型
```

### flm模型库

可以在以下链接中找到一部分已经转换好的模型

[huggingface](https://huggingface.co/fastllm) [modelscope](https://modelscope.cn/profile/huangyuyang)

### 模型导出(convert offline)

#### ChatGLM模型导出 (默认脚本导出ChatGLM2-6b模型)

``` sh
# 需要先安装ChatGLM-6B环境
# 如果使用自己finetune的模型需要修改chatglm_export.py文件中创建tokenizer, model的代码
cd build
python3 tools/chatglm_export.py chatglm2-6b-fp16.flm float16 #导出float16模型
python3 tools/chatglm_export.py chatglm2-6b-int8.flm int8 #导出int8模型
python3 tools/chatglm_export.py chatglm2-6b-int4.flm int4 #导出int4模型
```

#### baichuan模型导出 (默认脚本导出baichuan-13b-chat模型)

``` sh
# 需要先安装baichuan环境
# 如果使用自己finetune的模型需要修改baichuan2flm.py文件中创建tokenizer, model的代码
# 根据所需的精度，导出相应的模型
cd build
python3 tools/baichuan2flm.py baichuan-13b-fp16.flm float16 #导出float16模型
python3 tools/baichuan2flm.py baichuan-13b-int8.flm int8 #导出int8模型
python3 tools/baichuan2flm.py baichuan-13b-int4.flm int4 #导出int4模型
```

#### baichuan2模型导出 (默认脚本导出baichuan2-7b-chat模型)

``` sh
# 需要先安装baichuan2环境
# 如果使用自己finetune的模型需要修改baichuan2_2flm.py文件中创建tokenizer, model的代码
# 根据所需的精度，导出相应的模型
cd build
python3 tools/baichuan2_2flm.py baichuan2-7b-fp16.flm float16 #导出float16模型
python3 tools/baichuan2_2flm.py baichuan2-7b-int8.flm int8 #导出int8模型
python3 tools/baichuan2_2flm.py baichuan2-7b-int4.flm int4 #导出int4模型
```

#### MOSS模型导出

``` sh
# 需要先安装MOSS环境
# 如果使用自己finetune的模型需要修改moss_export.py文件中创建tokenizer, model的代码
# 根据所需的精度，导出相应的模型
cd build
python3 tools/moss_export.py moss-fp16.flm float16 #导出float16模型
python3 tools/moss_export.py moss-int8.flm int8 #导出int8模型
python3 tools/moss_export.py moss-int4.flm int4 #导出int4模型
```

#### LLAMA系列模型导出
``` sh
# 修改build/tools/alpaca2flm.py程序进行导出
# 不同llama模型使用的指令相差很大，需要参照torch2flm.py中的参数进行配置
```
一些模型的转换可以[参考这里的例子](llama_cookbook.md)

#### QWEN模型导出
* **Qwen**
```sh
# 需要先安装QWen环境
# 如果使用自己finetune的模型需要修改qwen2flm.py文件中创建tokenizer, model的代码
# 根据所需的精度，导出相应的模型
cd build
python3 tools/qwen2flm.py qwen-7b-fp16.flm float16 #导出float16模型
python3 tools/qwen2flm.py qwen-7b-int8.flm int8 #导出int8模型
python3 tools/qwen2flm.py qwen-7b-int4.flm int4 #导出int4模型
```

* **Qwen1.5 / Qwen2 / Qwen2.5**

```sh
# 需要先安装QWen2环境（transformers >= 4.37.0）
# 根据所需的精度，导出相应的模型
cd build
python3 tools/llamalike2flm.py qwen1.5-7b-fp16.flm float16 "qwen/Qwen1.5-4B-Chat" #导出wen1.5-4B-Chat float16模型
python3 tools/llamalike2flm.py qwen1.5-7b-int8.flm int8 "qwen/Qwen1.5-7B-Chat" #导出Qwen1.5-7B-Chat int8模型
python3 tools/llamalike2flm.py qwen1.5-7b-int4.flm int4 "qwen/Qwen1.5-14B-Chat" #导出Qwen1.5-14B-Chat int4模型
# 最后一个参数可替换为模型路径
```

#### MINICPM模型导出
```sh
# 需要先安装MiniCPM环境（transformers >= 4.36.0） 
# 默认脚本导出iniCPM-2B-dpo-fp16模型
cd build 
python tools/minicpm2flm.py minicpm-2b-fp16.flm #导出dpo-float16模型
python tools/minicpm2flm.py minicpm3-4b-fp16.flm openbmb/MiniCPM3-4B #导出minicpm3-float16模型
./main -p minicpm-2b-float16.flm # 执行模型
```

---

### Multimodal Cache (`docs/multimodal-cache.md`)

# 多模态缓存

## 当前实现

Qwen3.5 native 图片路径支持图片 SHA-256 → 最终视觉 embedding 缓存。同一模型实例再次收到相同图片时，可跳过 native resize、patch 构造、视觉编码器和 merger 计算。特征保留原有 FP32 精度，以 CPU LRU 管理。

缓存默认上限为 **512 MiB**，最多保留 **128 个条目**。首次处理带有效缓存键的图片请求时才创建缓存池；模型加载、纯文本、纯视频和关闭缓存的请求均不触发创建。内存随条目写入逐步分配，不预分配整个容量。

## 配置

在 `ftllm server` 模型启动命令中添加参数：

```bash
--image-embedding-cache 1g  # 上限为 1 GiB
--image-embedding-cache 0   # 关闭图片 embedding 缓存
```

也支持别名 `--image_embedding_cache`。显式命令行参数优先于环境变量；未指定参数时保留 `FASTLLM_IMAGE_EMBEDDING_CACHE_BYTES` 的值，环境变量未设置时使用 512 MiB。

直接使用 Python 模型入口时，可在加载模型前设置环境变量，值必须是非负十进制字节数，例如：

```bash
export FASTLLM_IMAGE_EMBEDDING_CACHE_BYTES=536870912
```

环境变量为空、格式错误或超出 native `size_t` 范围时回退到默认容量。容量在首次创建缓存池时读取，修改启动配置后需重新加载模型。

## 缓存键与生命周期

- Python 对实际发送的连续 FP32 图片数据、shape、grid、处理配置及身份版本计算 SHA-256。摘要以现有 payload 的 `[图片数量, 8]` int32 tensor 传入 native，保留完整 256 位，不改模型 token 或 C API。
- native 在键中加入模型计算类型，并以模型实例隔离缓存。每张图片单独缓存；`[A, B, A]` 中两次 A 可复用同一个结果，合并后的特征仍按请求顺序排列。
- 命中时复制到本次请求自己的输出，淘汰不会使在途请求持有失效的指针。缓存只保留 CPU 特征，不额外常驻 GPU 显存。
- 容量不足时按最近最少使用顺序淘汰。单张图片特征超过容量时正常计算，但不写入缓存，也不为它清空已有条目。
- 重新初始化模型参数、加载权重时清空缓存，卸载模型时释放。缓存容量限制保留的特征数组字节数；条目数上限另行限制索引元数据，本次请求的工作内存不计入缓存容量。

## 作用范围

当前仅缓存 Qwen3.5 native 图片路径的最终视觉特征；其他模型、视频和未携带缓存键的旧 payload 沿用原有计算路径。

图片读取、base64 传输、解码、Python payload 准备、哈希和语言模型预填充仍可能执行。图片缓存命中不等于 KV 前缀命中，不会增加 `cached_input_tokens`。

带有多模态输入的请求不写入或恢复仅按 token ID 索引的通用历史缓存和分页前缀缓存。图片占位 token 不能标识像素内容或多模态位置状态；Qwen3.5/3.8 恢复纯文本前缀后还可能误入解码分支，跳过视觉编码与分块预填充。该限制保留单次请求内的 KV 复用、图片 embedding 缓存和纯文本请求的前缀缓存；长多模态历史仍需重新完成语言模型预填充。

Qwen3.8-Flash-Next（Qwen4Exp）另有模型内部的前缀快照记录路径。其请求状态保留多模态标记，首次视觉 prefill 及后续 decode 均不记录这种快照；直接调用 C++ `ForwardMultimodal` 也会设置该标记。纯文本独立快照仍可记录和恢复。

开启模型 verbose 日志后，可用 `Image embedding cache hit` / `Image embedding cache miss` 确认命中情况。工作室中的等待提示不代表缓存状态。

## 验证

无需模型权重的回归测试：

```bash
python3 test/api/test_image_embedding_cache.py
g++ -std=c++17 -pthread -O1 -g -fsanitize=address,undefined \
    -fno-omit-frame-pointer -Iinclude \
    test/basic/test_image_embedding_cache.cpp -o /tmp/test_image_embedding_cache
/tmp/test_image_embedding_cache
```

Python 测试覆盖图片内容、尺寸、grid 和处理参数失效、输入布局等价、重复图片顺序、关闭缓存及文本/视频路径。C++ 测试覆盖容量解析、LRU、字节与条目上限、超大条目、请求输出独立性、实例隔离和并发访问。

启用 CMake 测试后，`multimodal_history_cache` 检查多模态状态不会污染 token 历史缓存、图片请求不会恢复文本 KV，并验证纯文本缓存仍可记录与恢复。模型集成回归还应在开启前缀缓存时先预热相同文本前缀，再分别提交不同图片，确认图片内容正确且多模态请求没有 token 前缀命中。

`qwen4_multimodal_cache` 使用小型 CPU KV/QSA 状态执行真实快照存储和恢复，检查图片、视频请求在后续 decode 阶段仍不记录快照，同时覆盖直接 C++ 多模态入口与请求状态复用。它不运行视觉编码器或完整模型推理。

模型集成验证应关闭 KV 前缀缓存以隔离视觉缓存效果，比较冷请求、命中请求和关闭图片缓存时的 logits，并检查命中日志。还应验证先运行纯文本，再提交图片时才创建缓存，以及多图片 `[A, B, A]` 的顺序。已有验证覆盖 Qwen3.5-2B 单卡、Qwen3.5 架构的 27B FP8 模型 TP2 和 CPU 延迟初始化路径；单卡/TP2 logits 使用 `rtol=atol=1e-5`，CPU 冷/热请求 logits 完全一致。

当前未提供跨请求的多模态 KV 前缀恢复；图片 embedding 缓存与 KV 缓存是独立功能。

双 2080 Ti 的 [32 张真实收据完整 Pi 任务](benchmarks/qwen38_27b_multimodal_agent_2080ti.md)记录了逐图覆盖、三次上下文压缩、显存和自然结束状态。流程完成，独立验收 350/351；仍有一项日期识别差异，不能将缓存隔离验证等同于全部内容正确。

---

### Naive N05 Flash (`docs/naive_n05_flash.md`)

# Naive-N0.5-Flash-FP8 混合推理

## MiMo-V2 DSA checkpoint

`model_type=mimo_v2`、`enable_dsa=true` 的 split-QKV checkpoint 复用
`NaiveN05FlashModel`。原始 `config.json` 和 safetensors 无需改名或转换。
模型工厂同时识别 `MiMoV2ForCausalLM` / `MiMoV2DSAForCausalLM` 架构名；
运行时仍使用 `naive_n05_flash` 分派 TP、KV 缓存、思考与工具解析。
非 DSA、融合 QKV、全局 attention sink 或带共享专家的 MiMo 配置会明确报错。

已对照的 MiMo checkpoint 为 48 层、hidden size 4096，含 9 个 DSA 全局层和
39 个滑窗层；Q/K 为 192 维、V 为 128 维，前 64 维使用 RoPE。全局/SWA
分别有 4/8 个 KV heads，滑窗为 128。首层为 dense MLP，其余 47 层每层有
256 个专家、每 token 选 8 个；索引器为 16 heads × 128 维、top-k 2048。
这些结构和权重名称与 Naive 的现有实现一致，FP8 专家权重可直接复用加载路径。
`attention_value_scale=0.707`、不同的全局/SWA RoPE theta 和滑窗 sink
均从原始配置或权重读取。配置未给出的索引器尺寸使用上述默认值，并已核对权重形状。

8 张 H20（SM90）部署示例：

```bash
export CUDA_HOME=/usr/local/cuda
export PATH="$CUDA_HOME/bin:$PATH"
bash install.sh -DUSE_CUDA=ON -DCUDA_ARCH=90
FASTLLM_CUDA_GRAPH=1 ftllm server /path/to/MiMo-V2-DSA \
  --model_name mimo-fastllm --tp 8 --dtype auto --atype bfloat16 \
  --cuda_embedding --max_batch 32 --tokens 1048576 --threads 16 \
  --cache_history false --enable_thinking false --port 8000
```

### 启动时预分配 KV

普通 BF16 KV 的多卡 TP 路径在预热结束时为每张卡建立一个固定容量的 KV 池，
不依赖 Hopper、EP8 或 CUDA Graph，也不需要额外环境变量。`--tokens` 控制全局层
的总 token 预算；`--max_batch` 与 `--chunked_prefill_size` 控制滑窗层及分块
prefill 的容量预算。未指定 `--tokens` 时取各 TP 卡中最小空闲显存的 75% 为预算，
扣除滑窗及对齐空间后计算 token 容量；剩余显存留给工作区。未指定 `--max_batch`
时按 512 路预留，不会因显存不足自动下调。配置超出预算会在启动时报错。

请求的 K/V 仍是互不重叠的连续视图，直接复用现有 DSA/SWA 算子。首次请求、
长上下文扩容及 CPU 历史恢复都从池内领取空间，结束后归还并合并空闲区间；
运行期间不为这些 KV 调用 `cudaMalloc`。各 TP rank 在进入计算和通信前完成申请；
池耗尽时终止本轮选中的请求并返回容量错误，后续请求仍可处理，不向池外分配。
池内采用连续区间，未改为分页 attention；极端混合长度仍可能造成池内碎片。
CUDA Graph、采样和临时算子工作区有各自的分配策略，不属于这项 KV 保证。
CPU、按层串行、非 BF16 KV、草稿模型及跳过预热的直接调用保留原路径。

### EP8 与内置 MegaMoE

MiMo FP8 在 8 张 H20 上可直接使用 `--tp 8 --ep 8`：Attention 保持 TP8，
MoE 每卡放置 32 个完整专家，默认调用内置 MegaMoE W8A8，无需额外的
MegaMoE 开关或 `.so` 路径。`--ep 1` 使用原来的 MoE TP 路径；省略 `--ep`
时默认也是该路径（显式环境配置除外）。启动 JSON 支持 `"ep": 8`。

```bash
FASTLLM_CUDA_GRAPH=1 ftllm server /path/to/MiMo-V2-DSA \
  --tp 8 --ep 8 --dtype fp8_e4m3 --atype bfloat16 \
  --cuda_embedding --max_batch 4 --threads 16 --cache_history false
```

SM90 构建默认将 kernel 编入 FastLLM 主库。源码改编自 DeepGEMM，固定版本的
头文件、MIT 许可证和原始文件哈希位于 `third_party/deep_gemm_ep8`；构建和
推理均不需要安装 DeepGEMM Python 包或加载独立适配器。明确请求 `--ep 8`
但卡数、硬件或构建不支持时会报错。EP 必须在加载模型前选定。

内核沿用已有的权重布局，支持 1～4 路解码 CUDA Graph。大于 64 行的 prefill
在 TP/EP 边界按 token 均衡分成 8 份，每张卡每轮最多提交 64 个不同 token。
各分片长度至多相差 1；空分片以 0 行参与同一轮专家计算和同步。各卡完成自己的
输出分片后，通过 NVLink peer copy 直接汇集到原输出张量。主机同步保证拷贝前
分片已完成、所有读取完成前源张量不会复用；无需填充输出或增加 GPU 缓冲区。
此优化只接入上述 H20 EP8 后端，不会自动用于其他模型、GPU 架构或 EP 卡数。
32 个本地专家共享按路由总数和对齐余量计算的 6080 行中间池，所有层复用
43.92 MiB/卡持久工作区，比原 8 行版本增加
29.82 MiB/卡，不增加专家权重副本。工作区硬上限为 48 MiB。
合批 Graph 沿用每个请求独立的 KV 缓存；请求数量、顺序、缓存地址/容量或
Attention 区间改变时重新校验并捕获，不为每个 batch size 保留一套 Graph。
至多 64 行的输入仍由每张卡提交一份 hidden state，避免给短 decode/verify 添加
额外汇集通信；router 投影也保持复制执行。超过 4 路及 verify 保留 eager。
W8A8 与原 W8A16 并非数值等价，可能改变 logits。详细限制和独立校验命令见
[`USAGE.txt`](../tools/mega_moe_sm90/USAGE.txt)。

激活和 KV cache 使用 BF16，专家保留 checkpoint 的 FP8。CUDA 13.0 的
低比特 prefill 编译单元单独使用 C++17，规避 NVCC 与 libstdc++ 13 在
`std::destroy_at` 上的 C++20 内部编译错误，其余 CUDA 单元保持原有标准。
加载器还修复了并行合并专家权重时对 `unordered_map` 的无锁插入/删除：
读取数据保持并行，权重发布、合并、注册与删除共用互斥锁，避免偶发丢失
`gateup_proj.weight`。`hf_parallel_merge` 回归会连续加载并核对所有合并内容。

回归覆盖 MiMo 模型识别、配置拒绝、CLI 的 BF16/TP 默认值、注意力和 8 卡
计算；完整模型验证覆盖算术、排序、代码生成及 4377/9957-token 的长文检索。
生成的质数函数另通过 2023 个输入用例；OpenAI 兼容 API 的 8 路并发对话、
工具调用及工具结果续写、流式输出等 12 项检查通过。
这些测试未覆盖 checkpoint 配置中的完整 1M 上下文。

### MiMo 在 8×H20-3e 上的实测

在上述 TP8 / FP8 专家 / BF16 激活与 KV 配置下，完整 checkpoint 加载约
145 秒。原生接口使用 2048 个随机输入 token、256 个固定输出 token，关闭
历史缓存，结果如下。总吞吐包含 prefill；解码速度为请求级中位数。

| 同时提交请求数 | 总输出吞吐（token/s） | 解码速度（token/s/请求） | 首 token 中位数（秒） |
| --- | --- | --- | --- |
| 1（两次） | 70.5–81.0 | 103.0–109.3 | 0.68–1.30 |
| 8 | 107.1 | 18.19 | 4.27 |
| 32 | 110.6 | 4.79 | 16.76 |

`max_batch=200` 的同类测试在生成完成前发生 `cudaErrorMemoryAllocation`，
当时 device 0 仍报告约 90 GiB 空闲；尚未确认根因，不能将它解释为显存容量
耗尽，也不能报告为完成的 200 并发结果。部署示例因此使用 `max_batch=32`。
原生测速为保证长度使用 `min_length=256`，会走全词表采样路径。

另一次 HTTP 排队测试使用 `max_batch=32`，同时提交 200 个不同的数字串
聊天请求，每请求实际输入 3097 token、固定输出 32 token，200/200 成功。
整轮耗时 368.17 秒，总输出 6400 token（17.38 token/s，包含输入处理与排队），
请求延迟中位数 242.42 秒。8 卡整轮平均 GPU 忙碌率 69.49%、SM 活跃度
37.42%、Tensor Core 活跃度 1.43%、显存带宽活跃度 1.72%；每卡平均
42.87 GiB 显存、201.79 W。它验证的是限流后的 200 路 HTTP 服务，不代表
200 个请求同时在 GPU 解码；这些硬件活动指标也不等于模型 FLOPS 利用率。

### MiMo FP8 BS1 CUDA Graph

`FASTLLM_CUDA_GRAPH=1` 现在允许 GPU 常驻的 FP8 E4M3 专家进入单行 TP
解码图路径，保留 FP16 专家激活、BF16 attention/KV 的现有算术。专家指针表
在预热阶段初始化；若 MoE 退回需要 CPU 读取路由的实现，则禁用该缓存的图捕获。
FP8 多行合批和 speculative verification 仍使用原有 eager 路径。

8×H20 的完整 MiMo 对照覆盖 127、254、2047、2048、2049-token 输入，
共 128 个输出位置，每个位置比较全部 152576 维 FP32 logits。Graph 与 eager
逐字节一致，最大绝对误差及 RMSE 均为 0；另有 6 次 256-token 测速请求的
输出 token 序列一致。

Linux CTest `naive_n05_tp_graph`、`naive_n05_tp_fp8_graph` 覆盖长度边界、
请求缓存复用、采样模式切换、capture begin / instantiate 失败回退。
两个测试固定 `FASTLLM_CUDA_CUSTOM_ALLREDUCE=1`，使跨进程比较采用相同的
通信归约顺序；未固定时自动调优可能选择不同后端，连 prefill logits 也会出现
小幅差异。正常服务继续保留原有自动调优设置。

Nsight 在每张卡上确认每 token 一次 `cudaGraphLaunch`。GPU 0 的每步
kernel 数从此前的 947 减至 849（946 是普通 kernel 发射 API 次数），专家 gate/up+SwiGLU 与 down+reduce 仍为
约 1.258 / 1.453 ms/token，与原 eager 的 1.263 / 1.458 ms 接近。
原始波形、logits、测速脚本及结果保存在 `/root/fastllm/run/cuda_graph/`；
本地结果目录为 `mimo_results/cuda_graph/`。

同一进程内切换 Graph 开关，保持输入、权重、精度和通信后端一致，预热后
按 eager / graph / graph / eager / eager / graph 顺序各测 3 次。
完全不加载 Nsight，2048 输入、强制 256 输出，排除首 token 的结果如下：

| 路径 | 三次解码 token/s | 中位数 token/s | 中位数 ms/token |
| --- | --- | ---: | ---: |
| eager | 108.07 / 112.96 / 111.90 | 111.90 | 8.937 |
| CUDA Graph | 133.38 / 140.61 / 135.74 | 135.74 | 7.367 |

本组完整解码段吞吐提升 21.3%；排除前 16 个输出 token 后，Graph 稳定段
中位数为 140.61 token/s。

另一次已注入 Nsight、尚未开启采集的进程测得 eager / graph 中位数为
97.38 / 74.48 token/s，且通信自动调优结果也发生变化；不能把这组数据当作
无 profiler 的吞吐。正式性能结论采用 `cuda_graph/plain/results.json`；
逐 kernel 采集数据用于确认图回放和归因，不用其墙钟时间推算生产速度。

## 编译与启动

```bash
bash install.sh
FT_NUMAS=1 numactl -C 0-31 -m 0 \
  ftllm server ~/hfmodels/Naive-N0.5-Flash-FP8 \
  --device cuda --moe_device numa
```

`-C` 指定 CPU 编号；小写 `-c` 指定 NUMA 节点，不能用 `-c 0-31`。
默认线程数会考虑 `FT_NUMAS` 和进程 CPU affinity，并给推理控制线程预留 CPU。
在 32 个可用逻辑 CPU 的配置中自动选择 28 个 NUMA 工作线程，也可以显式指定 `--threads 28`。
不要让忙等工作线程占满 `-C` 指定的全部 CPU；工作线程占满可用 CPU 时可能造成调度争抢。
保持 `--dtype auto`，保留 checkpoint 的 FP8 专家权重。激活和 KV cache 默认使用 BF16。
完整的 `model-00001-of-00049.safetensors` 等分片可以直接加载，无需手动补索引文件。
长 prefill 默认自动分配 CPU/GPU 专家；如需关闭 GPU 专家 prefill，在启动前设置
`FT_GPU_PREFILL=0`。此开关在进程初始化时读取。


长请求可按显存容量设置 `--chunked_prefill_size 8192`；默认分块为 2048。
`--device cudapp=2` 将主干按层分到两卡，长 prefill 的专家可同时由 CPU 和两张 GPU 执行。
实际线程数、分块和并行度需按硬件调整，测量结果见[Benchmark](benchmarks/naive_n05.md)。

## 实现范围

- CUDA 执行稠密层、路由、注意力及输出投影；专家权重保存在 NUMA。decode 专家由 CPU 执行，长 prefill 按路由负载分给 CPU 与各辅助 GPU 并行执行。
- 支持 Q/K 192 维、V 128 维、前 64 维的部分 GPT-NeoX RoPE，以及不同全局/滑窗 RoPE 参数。
- 滑窗注意力保留 127 个历史 token，计入 attention sink 和 value scaling。
- 全局层支持 FP8 indexer、因果约束及稳定的 top-2048 选择；索引键保存在对应请求的 KV cache 中。
- NUMA 专家使用动态 W8A8：每 128 个激活使用独立 FP32 缩放系数，在 GEMM 中应用激活和权重的缩放，并对齐原始实现的 BF16 舍入顺序。参考 FP8 内核使用的 FP32 → FP16 向零截断 → FP8 舍入也在 NUMA 路径中保留。
- 默认以 2048 token 分块 prefill，可用 `--chunked_prefill_size` 覆盖；每次 Forward 处理一条序列。跨请求前缀复用使用模型专用的 CPU KV 归档，HTTP 请求由现有调度器处理。

稀疏索引和稳定 Top-K 按形状选择 CUDA 或 CPU 路径。已记录的完整模型验证最长输入为 32727 token；模型配置中的 1M 上下文尚无完整验收结果。

## 工具调用与跨请求前缀缓存

默认启动自动根据 Naive 的 XML 模板选择 `qwen3_coder` 工具解析器，支持 `tools`、
工具结果消息和流式增量；`tool_choice=required` 或指定函数时复用 XML 工具选择引导。
也可以通过已有的 `--tool_call_parser` 显式覆盖解析器。
Python 接口也会把工具历史中的 JSON 参数字符串转换为模板所需的对象。
请求通过 `chat_template_kwargs.enable_thinking=true` 开启思考时，思考内容放入
`reasoning_content`，工具调用仍放入 `tool_calls`；默认启动不开启思考。

MoE 启动默认启用 `--cache_history true`；可用 `--cache_history false` 关闭。
完成或中止请求时，仅记录已经执行 Forward 的 token，排除尚未计算 KV 的最后一个输出。
后续请求按 token ID 匹配最长公共前缀，支持相同请求、追加工具结果、较短请求和分支请求。
相同请求仍保留一个输入 token 重新计算 logits。

归档在 CPU 分块保存滑窗裁剪前的 K/V，以及全局层打包的索引键；命中时全局层恢复完整
前缀，滑窗层只恢复边界前所需的 127 个 token，并恢复绝对位置和调度状态。
不可变归档块可在分支请求之间共享，当前请求的 KV 独立分配。
恢复过程只操作 CPU 内存，不等待其他请求的 GPU Forward。

归档最多保留 5 条已完成记录，按最近使用顺序淘汰。已完成、活动和待恢复请求共享
主机内存预算，共享块只计一次，最后一个引用释放时归还额度。
预算按 serving token pool（未设置时使用模型最大上下文）和每 token 归档大小估算，
目标至少为 8 GiB，再受可用主机/容器内存四分之一的限制；无法读取内存信息时以
40 GiB 为上限。预算在首次记录时确定，仅按实际保存的 token 分配，无新增环境变量。
长会话可以使用其他记录释放的额度；预算不足时淘汰已完成记录，仍不足则保留已缓存
前缀，其余输入正常计算。活动请求持有的块不会因淘汰而失效。
该模型的归档始终使用 CPU 内存，不受 `--cache_fast` 影响，不额外常驻 GPU KV 副本。
API 的 `usage.prompt_tokens_details.cached_tokens` 表示本次命中的输入 token 数。

### 本地 Codex CLI

`/v1/models` 的 Naive 模型目录提供 `none`（关闭思考）和 `low`（开启思考），默认 `none`。
Naive 的 `low` 是思考开关，没有较短的思考 token 预算；Codex 自动生成会话标题时也会
继承此设置。交互速度优先时保留 `none`。首次长提示仍需完整 prefill，后续请求通过
历史缓存复用前缀。

Codex 的自动标题请求有 30 秒超时，长 prefill 时排队生成可能超时并在下一轮重试。
以下命令通过 `X-FastLLM-Codex-Title-Mode: local` 显式启用本地标题：服务端仅匹配
Codex 的标题提示和对应 JSON schema，从用户请求提取不超过 36 字的标题，直接返回，
不进入模型队列。标题是请求文本的摘要标签；普通对话、工具调用和其他结构化输出仍由
模型生成。不带此请求头时保留模型生成标题的行为。

获取当前服务的模型目录并启动客户端：

```bash
curl --noproxy '*' -fsS http://127.0.0.1:8080/v1/models -o /tmp/naive-n05-models.json &&
NO_PROXY=127.0.0.1,localhost no_proxy=127.0.0.1,localhost \
codex --no-daemon -m naive-n05 \
  -c model_provider=fastllm \
  -c model_catalog_json=/tmp/naive-n05-models.json \
  -c model_reasoning_effort=none \
  -c cli_auth_credentials_store=ephemeral \
  -c features.plugins=false -c features.remote_plugin=false -c features.apps=false \
  -c web_search=disabled -c check_for_update_on_startup=false \
  -c analytics.enabled=false \
  -c model_providers.fastllm.name=FastLLM \
  -c model_providers.fastllm.base_url=http://127.0.0.1:8080/v1 \
  -c model_providers.fastllm.wire_api=responses \
  -c model_providers.fastllm.requires_openai_auth=false \
  -c 'model_providers.fastllm.http_headers={"X-FastLLM-Codex-Title-Mode"="local"}' \
  -c model_providers.fastllm.supports_websockets=false \
  -c model_providers.fastllm.request_max_retries=0 \
  -c model_providers.fastllm.stream_max_retries=0
```

仅关闭 `remote_plugin` 不会关闭插件目录的其他远程查询；此命令关闭整个插件功能。
`cli_auth_credentials_store=ephemeral` 让这个本地客户端不读取持久化 ChatGPT 登录凭据，
避免发送前等待远程用户设置查询超时；不会删除已有登录、技能或会话记录。
`NO_PROXY` 与 `no_proxy` 保证本地 API 直连。代码或配置更新后应重启服务和 Codex，
并重新获取模型目录；旧进程与旧目录文件不会自动加载修改。


`tools/naive_n05_flash_bench.py` 默认关闭历史缓存；加 `--cache_history true` 可测前缀复用，并通过 `cached_input_tokens` 查看命中。
缓存只减少命中前缀的 prefill。恢复边界可能改变计算批量和浮点累加顺序，不保证任意边界的 logits 逐位相同。

## DSpark 推测解码

支持配套的 `Naive-N0.5-Flash-FP8-Draft` checkpoint，沿用现有命令行参数：

```bash
FT_NUMAS=1 numactl -C 0-31 -m 0 \
  ftllm server ~/hfmodels/Naive-N0.5-Flash-FP8 \
  --device cuda --moe_device numa --threads 28 \
  --draft ~/hfmodels/Naive-N0.5-Flash-FP8-Draft --draft_tokens 7
```

Draft 使用 5 层 BF16 Qwen3 骨干、1024-token 滑窗、学习的 mask embedding 和
vanilla Markov head。目标模型的 8 个中间层输出经投影成为 draft 上下文；目标模型的
embedding 和输出头共享。DSpark 从 anchor 所在的第 0 个位置开始预测，7 个位置均可
作为候选，与仅使用后续 mask 位置的 DFlash 不同。
默认置信度阈值为 0.5，可用已有的 `--speculative_dspark_confidence_threshold` 调整。

随机采样采用[标准拒绝采样](https://arxiv.org/abs/2211.17192)：候选来自完整归一化的
draft 分布 `q`，以 `min(1, p(token)/q(token))` 接受；拒绝时从归一化的
`max(p-q, 0)` 采样，全部接受则额外从目标模型分布采样一个 token。
目标和 draft 均应用温度、top-k、top-p、重复惩罚及最低生成长度限制。
贪心分布是此算法的退化情形。拒绝后的 KV 和中间特征按实际接受长度回退。

验证阶段的线性层使用与逐 token decode 相同的归约顺序，避免 BF16 舍入改变路由并
逐层放大。历史缓存同时归档 draft 的投影特征，命中后只恢复最后 1023 个位置；
请求提前结束时不把未输出的候选发布到前缀缓存。
需要工具名称/参数约束或工具正文采样的请求，以及要求返回 logits 的请求，使用逐 token
目标推理；这些请求仍可使用 draft 上下文归档和前缀缓存。

NUMA FP8 W8A8 内核按行数选择小块及打包路径，小块共享最多 8 行的权重解码，
同时复用多列的激活读取；较大块复用 8 行 × 32 列的 BF16 点积。
不均匀专家组使用现有分组任务队列并优先处理较大的组。优化覆盖不同 token 行数和尾块，
不依赖固定的候选数量。

### 复现与验证

开启 `UNIT_TEST` 后编译以下目标。配对基准在同一个进程内加载同一组目标和 draft 权重，
每题分别完整生成普通和推测回答预热，再交替测量两条路径，关闭历史缓存，记录各 token
时间、候选接受率和完整输出。生成后的代码在独立子进程执行测试。
生成结果写到仓库外的报告目录，再交给代码检查工具复验。

```bash
cmake -S . -B build-fastllm -DUNIT_TEST=ON
cmake --build build-fastllm -j16 --target naive_n05_speculative_bench \
  numas_fp8_moe_bench speculative_sampling_test naive_n05_draft_test
python3 tools/naive_n05_speculative_check.py \
  --model ~/hfmodels/Naive-N0.5-Flash-FP8 --write-cases /tmp/naive-cases.json
FT_NUMAS=1 FT_THREADS=28 numactl -C 0-31 -m 0 \
  build-fastllm/naive_n05_speculative_bench \
  ~/hfmodels/Naive-N0.5-Flash-FP8 ~/hfmodels/Naive-N0.5-Flash-FP8-Draft \
  /tmp/naive-cases.json /tmp/naive-results.json
python3 tools/naive_n05_speculative_check.py \
  --model ~/hfmodels/Naive-N0.5-Flash-FP8 \
  --report /tmp/naive-results.json --output /tmp/naive-code-checks

# 不均匀路由：7 行、64 个专家、每行选 8 个，其中 4 个为公共专家。
FT_NUMAS=1 FT_THREADS=28 FT_GPU_PREFILL=0 numactl -C 0-31 -m 0 \
  build-fastllm/numas_fp8_moe_bench 7 40 64 4

# 使用原 checkpoint 自带的 Torch 实现导出 draft 骨干参考。
python3 tools/naive_n05_draft_fixture.py \
  --draft ~/hfmodels/Naive-N0.5-Flash-FP8-Draft --output /tmp/naive-draft-fixture
build-fastllm/naive_n05_draft_test /tmp/naive-draft-fixture
ctest --test-dir build-fastllm --output-on-failure \
  -R '^(speculative_sampling|naive_n05_history|naive_n05_attention|numas_fp8_eager_moe(_avx2|_hybrid)?)$'
```

## Logits 对照

工具 `tools/naive_n05_flash_logits.py` 导出未经采样处理的 logits，按相同 token 前缀比较。
`reference` 使用 checkpoint 自带的 Transformers 模型代码和原始 FP8 eager experts；专家逐层搬到 GPU，适合显存不足以容纳整个模型的机器。
参考环境验证使用 Transformers 5.17、PyTorch 2.10.0+cu128、kernels 0.16.0 和 accelerate。

先用模型 tokenizer 生成一份输入 token ID JSON，例如 `[151644, ...]`，两个后端使用同一文件：

```bash
FT_NUMAS=1 numactl -C 0-31 -m 0 \
  python3 tools/naive_n05_flash_logits.py fastllm \
  ~/hfmodels/Naive-N0.5-Flash-FP8 --device cuda --moe_device numa \
  --input-ids input.json --output fastllm.npz --steps 3

python3 tools/naive_n05_flash_logits.py reference \
  --model ~/hfmodels/Naive-N0.5-Flash-FP8 --device cuda:0 \
  --input-ids input.json --output reference.npz --steps 3 \
  --teacher-forcing fastllm.npz

python3 tools/naive_n05_flash_logits.py compare reference.npz fastllm.npz
```

两个采集命令均可增加 `--repeat-to 2057`，验证跨过 2048 个索引候选后的稀疏注意力。
比较输出包括 MAE、RMSE、最大绝对误差、余弦相似度、概率分布 KL、top-1 和 top-10 重合数。
如果生成 token 已分叉，必须使用 `--teacher-forcing` 对齐后续输入，工具会拒绝比较不同前缀的 logits。

## 回归测试

开启 CMake `UNIT_TEST` 后：

```bash
cmake -S . -B build-fastllm -DUNIT_TEST=ON
cmake --build build-fastllm --target naive_n05_attention_test naive_n05_history_test numas_fp8_eager_moe_test -j16
ctest --test-dir build-fastllm -R 'naive_n05_(attention|history)|numas_fp8_eager_moe' --output-on-failure
```

CUDA 测试覆盖部分 RoPE、GQA、滑窗边界、attention sink、稀疏因果掩码、FP8/BF16 indexer 与分数相同时的稳定选择。
NUMA 测试以独立标量参考覆盖 W8A8 量化、FP8 次正规数、BF16 舍入、专家分组和请求规模变化，同时运行 AVX512 与 AVX2 路径。

新增的 `naive_n05_history_test` 验证真实请求初始化和缓存钩子，覆盖精确重复、短请求、
任意公共前缀、127/128 滑窗边界、跨块恢复、分支互不影响、多模态隔离、关闭清空、
命中与重复记录时的 LRU 更新，以及 CPU 恢复不等待 Forward 锁。
工具解析回归可运行 `python3 -m unittest discover -s test/toolcall -p 'test_qwen*.py'`。
Naive 思考与工具混合输出回归：
`python3 -m unittest discover -s test/api -p 'test_naive_n05_tools.py'`。


## 仅量化 MoE 专家的 NVFP4 导出

`tools/naive_n05_export_nvfp4.py` 将原始 FP8 专家反量化后，导出为 E2M1 权重、
每 16 列一个 E4M3 缩放值和每矩阵一个 FP32 缩放值。采用就近舍入，平局取偶数；
该导出未做激活校准或量化感知训练。推理使用 BF16 激活。
注意力、路由、首层稠密 MLP、词嵌入及输出头原样复制。

导出需要有 CUDA 的 PyTorch 和 safetensors，输出目录必须不存在：

```bash
python tools/naive_n05_export_nvfp4.py \
  --model ~/hfmodels/Naive-N0.5-Flash-FP8 \
  --output ~/hfmodels/Naive-N0.5-Flash-MoE-NVFP4

FT_NUMAS=2 numactl -C 0-63 -m 0,1 \
  ftllm server ~/hfmodels/Naive-N0.5-Flash-MoE-NVFP4 \
  --device cuda --moe_device numa --threads 40
```

运行时自动识别 safetensors 中的 NVFP4 格式，使用与 Qwen4 相同的紧凑 E4M3 NUMA
布局；无需设置新的环境变量。`nvfp4_export.json` 记录量化方法、采样权重误差和全部
非专家张量的 SHA256。已验证的导出含 36,096 个专家矩阵；472 个非专家张量的字节校验
全部通过。权重分片从 293.465 GiB 缩至 170.025 GiB，减少 42.1%。

导出器的小规模回归无需加载完整模型，覆盖舍入、全零专家、分块缩放和非专家
字节一致性。使用已安装 PyTorch 和 safetensors 的 Python 运行：

```bash
python -m unittest discover -s test -p test_naive_n05_export_nvfp4.py -v
```

### 八卡 CUDA 专家推理

CUDA 专家使用现有的紧凑 `NVFP4_BLOCK_16_E4M3_PACKED` 行布局：每行保存
4 字节原始 FP32 全局缩放，每 16 个权重保存 8 字节 FP4 数据和 1 字节原始 E4M3
分块缩放，行末对齐到 4 字节。保留一份 GPU 专家权重，模型文件无需重新导出。
CPU/NUMA 专家继续使用原始紧凑布局。

Gate 和 Up 各行保留自己的全局缩放，避免归一化到同一最小值造成溢出。
CUDA MoE 支持默认 BF16 和 FP16 激活，沿用原生 FP32 分块缩放路径的点积顺序、
归约、SwiGLU 和路由舍入。内核按需解码 FP8 并乘行全局缩放，不缓存展开的 FP32
分块缩放。测试用两种布局的同一权重检查输出逐 bit 一致。
8 张 32 GiB RTX 5090 可以使用普通设备映射把 48 层分配到八卡：

```bash
ftllm server ~/hfmodels/Naive-N0.5-Flash-MoE-NVFP4 \
  --device '["cuda:0","cuda:1","cuda:2","cuda:3","cuda:4","cuda:5","cuda:6","cuda:7"]' \
  --moe_device '["cuda:0","cuda:1","cuda:2","cuda:3","cuda:4","cuda:5","cuda:6","cuda:7"]' \
  --max_batch 1 --mtp 0 --tokens 8192 --chunked_prefill_size 512 \
  --cuda_slab 288 --cache_history false
```

这是按层分配，不是张量并行。对该 checkpoint 的 3028 亿个专家参数，紧凑专家数据
连同行全局缩放约占 158.992 GiB；相比每块保存 FP32 缩放的 12 字节布局，
减少约 52.508 GiB。
实际进程显存还包含稠密权重、KV cache、工作区和分配器开销。

`cuda_nvfp4_compact_moe_test` 用独立 FP64 参考检查原始 FP4/FP8 解码、不同 Gate/Up
全局缩放、零与最大分块缩放，以及 BF16/FP16 的多种路由批次，并与 FP32 分块
缩放布局逐 bit 对比。开启 `UNIT_TEST`
后可编译并运行：

```bash
cmake --build build-fastllm --target cuda_nvfp4_compact_moe_test -j16
ctest --test-dir build-fastllm --output-on-failure -R '^cuda_nvfp4_compact_moe$'
```


## 多卡专家辅助

长 prefill 复用 `GetNumasMoeCudaAssistDevices()`，识别 TP、`cudapp` 和 `FT_MOE_ASSIST_DEVICES` 的设备列表。
调度器根据 CPU 和各 GPU 的预计完成时间分配专家；各 GPU 使用独立 worker、stream、cuBLAS handle 和工作区锁。
保留 FP8 激活量化、BF16 舍入、独立 route 写回及按专家 ID 的归约顺序；GPU worker 失败时先等待全部任务结束，再执行完整 CPU 回退。
此路径的 decode 专家仍在 CPU，辅助卡主要用于长 prefill。

## KV 容量与数值边界

全局层按 `input_token_length + output_token_limit - 1` 预留 KV，受模型和请求上限约束，并按 128 token 对齐。
SWA 只预留窗口和当前 chunk 所需空间。预留是物理容量提示，不改变逻辑长度；无输出上限或缺少输入长度元数据时保留增量扩容。
RoPE 与 AttentionValues 按实际维度计算；Indexer 要求量化块宽为 128，不支持的布局、类型和设备明确报错。
Grouped Marlin 的工作区同时覆盖按专家分组和按 route 填充的布局，不依赖固定专家数。

## TP 候选输出与小批 MoE

普通 TP 解码和 DSpark verify 会自动在每卡选择局部候选，在 CPU 合并全局候选，
减少完整词表的回传。Greedy 保留原 CUDA Top1 的并列分数顺序；有限 Top-k 采样按
分数降序、token ID 升序。普通采样在选择前缩放温度，DSpark 使用原始分数选择，
随后沿用原概率构造、接受/拒绝采样及 KV 提交规则。

紧凑采样支持 K=2..64、每卡词表不超过 262144、全局词表不超过 2^24；这些是算子
工作区和 FP32 token ID 格式的边界，不依赖 RTX 5090、TP8 或固定模型词表。
Greedy 不受上述每卡词表大小限制。要求完整 logits、重复惩罚、最短输出长度、
词表约束或超出紧凑算子边界时，保留原完整 logits 路径。普通串行推理沿用原实现。
Graph 复用同时校验候选模式、K、温度缩放以及已有的缓存地址/容量和通信 generation。

BF16/NVFP4 小批 MoE 对 2..8 行、top-k<=16 按专家稳定分组，保留原 route ID 和
每个 token 的加权累加顺序。每个输出 tile 在一个 CTA 内完成 K 归约，避免后续行
的路由选择改变已有前缀结果。与逐行计算比较使用既有数值容差；相同路径的 eager、
Graph replay 和后缀路由变化下的前缀比较仍要求逐 bit 一致。其他数据类型和尺寸
继续走已有调度，跨设备 scratch 迁移也保留。

这些路径没有额外环境开关。`FASTLLM_TP`、`FASTLLM_DSPARK_TOKENS` 和
`FASTLLM_DSPARK_CONFIDENCE_THRESHOLD` 是现有 CLI 的有效配置接口，继续保留。

启用 `UNIT_TEST` 和 `USE_CUDA` 后，相关 CTest 为 `naive_n05_logits`、
`naive_n05_verify_selection_{2,4,8}`、`naive_n05_verify_selection_graph`（Linux）、
`naive_n05_tp_graph`（Linux）、`cuda_nvfp4_marlin_grouped_rows` 和
`cuda_nvfp4_marlin_cross_device_rows`。`speculative_sampling` 还覆盖 CPU 概率与残差采样。


## 草稿投影融合与独立 TP

草稿 Q/K/V 与 Gate/Up 在模型加载阶段分别合并，沿用 WeightMergeRule 的所有权规则，
合并成功后删除原条目。上下文 K/V 从合并 QKV 建立只读行视图，不另存合并权重缓存。
BF16 草稿支持 Q/K RMSNorm、RoPE 和 KV 写入融合；SwiGLU 保留先将 SiLU 舍入为
BF16 再相乘的数值语义。布局不满足融合条件时保留原算子路径，Graph 和 eager 均可运行。

草稿并行只使用一个配置项 `FASTLLM_DSPARK_TP`：

| 值 | 执行方式 |
|---|---|
| 不设置或 `1` | 单卡草稿，兼容原默认行为 |
| `2`、`4` 等整数 N | QKV/Attention/O 与 MLP 均按 N 卡切分 |
| `mlp:2`、`mlp:4` 等 `mlp:N` | 每卡计算完整 Attention 并持有完整 KV，只有 MLP 切分 |

配置在草稿第一次使用多卡时固定，不支持加载后切换。Target 已配置 TP 时，草稿采用其
设备列表的前 N 张卡；否则从草稿当前设备开始，按可见 CUDA 设备编号补齐。不会把
target/head 的并行度改成 N。上下文 FC 和 Markov proposal 继续在草稿首卡运行。
仅 MLP TP 会产生计算所需的 Attention 副本；分片与副本都释放父投影的原始存储，
不额外保留未分片大矩阵。

并行实现按实际设备数量处理，不限定为双卡或四卡。N 不能超过可用设备数或 target TP
设备数，MLP 中间维度必须能被 N 整除；完整 TP 还要求 Q/KV 头数能被 N 整除。
仅 MLP TP 支持单 KV 头。当前路径要求 CUDA embedding、BF16 稠密投影、peer access，
不支持 low-memory 模式；head_dim 为 4 的倍数且不超过 256，草稿 block 在 2..31 范围。
不符合要求时明确报错，不在已分片权重上静默退回单卡。

多卡上下文投影使用逐层 K/V 行视图和 batched GEMM；工作区只保留激活与小指针表，
不缓存权重副本。草稿拥有独立通信组、持久工作线程和 Executor，不替换 target 的通信组。
所有从卡提交完成后首卡才允许复用输入存储。Graph 捕获失败时统一释放各卡 graph 并回退
到相同 TP 策略的 eager 路径。未为上下文更新新增 CUDA Graph。

Linux CUDA CTest 中，`naive_n05_draft_fusion` 与 `naive_n05_draft_kv` 覆盖融合数值、
布局拒绝、请求复用及跨窗口更新；`naive_n05_draft_tp_*`、`naive_n05_draft_mlp_tp_*`
覆盖完整/仅 MLP TP、MQA、2/4/8 卡、Graph/eager、捕获失败回退和非零首卡。
TP 改变 GEMM 与跨卡归约顺序，跨策略使用既有数值容差；同一策略 Graph/eager 要求逐位一致。

草稿并行度需按实际硬件和请求测量；默认不自动提高，不承诺线性加速。


## 普通 TP 解码合批

未配置 DSpark 草稿模型时，Naive TP 支持服务端按 `--max_batch` 合并活跃请求。
例如保留现有 TP8 启动参数，设置 `--max_batch 2 --mtp 0`，并移除
`--speculative_algorithm`、`--draft` 等草稿参数。`--mtp 0` 本身不会关闭显式启用的 DSpark。

多个请求共享一次 TP worker 派发，QKV、输出投影、路由、稠密 MLP 与词表投影按多行执行；
Attention/Indexer 使用各请求独立的 KV、位置及长度。为保持普通解码的专家归约顺序，
MoE 专家投影暂按请求执行，其余投影和层间通信仍按多行执行。权重沿用原有分片，不复制 KV
或增加合并权重缓存。采样配置、最短输出限制与历史 token 仍按请求分别处理。
当前服务调度器仍逐请求 prefill，再对活跃请求合批 decode；指针式 ForwardBatch
也接受按请求顺序打包、无 padding 的变长输入。

合批 Graph 状态与单请求 decode、DSpark verify 分开。满足已有 Graph 后端约束时，
最多八个单 token 请求复用每卡一个 Graph；请求顺序、数量、KV 地址/容量、
Attention 区间或候选选择配置改变时重新检查。更大 batch、prefill 和 Graph 捕获失败
使用同一合批前向的 eager 路径。没有新增环境变量，融合和 Graph 仍遵循现有设置。

本路径仅为普通 CUDA TP 开启合批能力。草稿解码和非 TP 模式仍使用现有单请求路径；
它不是两个独立 GPU stream 同时跑两份模型。多行投影共享权重读取，具体吞吐收益
取决于各请求的专家重合度、上下文长度与硬件，需要端到端测量。

### TP 跨请求历史前缀复用

Naive CUDA TP 支持 `--cache_history true`，普通解码、普通合批和 DSpark 共用已有的
CPU 历史归档。按完整 token 前缀匹配，可复用重复、追加、缩短和中途分叉的请求。
SWA 归档保留已移出活动窗口的历史行，DSA 包含 index key，DSpark 同时保存已提交
hidden features；未接受的草稿以及结束/取消后未发出的后缀不会作为命中前缀发布。

归档只保存一份逻辑 KV；TP 恢复时重新分片，并向共享 KV 头的 rank 复制对应头。
请求创建仅恢复私有 CPU 状态，首次前向由各 rank 并行上传 CUDA，避免与其他请求的前向工作区竞争。
单个存活请求可复用上一请求的空闲 GPU 分配，恢复内容后再执行；有其他存活请求时不转移 KV 所有权。
Graph replay 完成后记录新行，每卡批量暂存后统一下载，不将 CPU 下载放入 Graph 捕获，保留已有融合路径。
需要单 token target 前向并收集草稿 hidden states 时，也可使用单行 Graph；
恢复完草稿上下文后直接开始推测的请求继续走原有 Verify Graph。
缓存使用上述共享主机预算，最多保留 5 条已完成记录，不新增配置环境变量。
`--prefix_cache` 的通用 paged-cache 开关不代替这个模型的 `--cache_history`。

历史归档及恢复包含 CPU/GPU 传输，有固定开销；命中 token 数不等于端到端收益。
短前缀可能不比重新 prefill 更快，应按实际上下文和命中率测量首 token 延迟及生成速度。

---

### Nccl Startup (`docs/nccl-startup.md`)

# NCCL 启动自检

新建多卡 NCCL 通信组后，FastLLM 会执行一次 FP32 AllReduce 并检查每张卡的结果。
复用已初始化的通信组时不会重复自检；正常推理算子保持不变。

初始化、通信调用、异步错误或数值检查失败时，程序会输出具体阶段和库返回的错误，
中止通信组并以非零状态退出，不等待键盘输入，也不继续使用未归约的数据。
NaN 和 Inf 均视为自检失败。

`FASTLLM_NCCL_INIT_TIMEOUT_MS` 设置通信组重建和自检的总超时，默认 `60000` 毫秒，
允许 `1` 到 `3600000`。无效值会打印提示并采用默认值。耗时较长的环境可以调大此值。
超时监控也覆盖阻塞的 NCCL 初始化、分组提交和失败清理；超时后直接终止进程，
避免等待正在阻塞的 CUDA/NCCL 调用。此超时不限制模型推理或自定义归约自动测速。

排查时应确认实际加载的 NCCL 库、CUDA 运行时、驱动和 GPU 连接，不能仅凭错误
认定是 CUDA 大版本不匹配。实测 Ubuntu 提供的旧 NCCL 包和 NVIDIA 官方包即使
报告相同的 NCCL/CUDA 版本，通信结果也可能不同。

Linux 双卡回归：构建时启用 `USE_CUDA=ON`、`UNIT_TEST=ON`，运行
`ctest --test-dir <build> -R '^nccl_init_' --output-on-failure`。
覆盖正常初始化、通信组复用、NaN/错误数值、同步/异步错误和阻塞超时。
故障注入仅存在于测试可执行文件中，不影响正常程序。

---

### Nightly (`docs/nightly.md`)

# fastllm-nightly 使用文档

`fastllm-nightly` 是 fastllm 的开发预览版本，包含最新的功能和改进，适合希望提前体验新特性的用户。

> **注意：** 目前 nightly 版本仅支持 **Linux + Nvidia GPU** 环境，暂不支持 AMD GPU、Windows 等其他平台。

查看 [Nightly 更新日志](nightly_changelog.md) 了解每次更新的详细变更记录。

安装 nightly 版本：

```
pip install ftllm-nightly -U
```

## 与稳定版的区别

`ftllm-nightly` 和 `ftllm` 的使用方式完全一致，安装后同样通过 `ftllm` 命令使用。两者不能同时安装，安装 nightly 版本前请先卸载稳定版：

```
pip uninstall ftllm
pip install ftllm-nightly -U
```

如需切换回稳定版：

```
pip uninstall ftllm-nightly
pip install ftllm -U
```

## 参数说明

以下是 `ftllm` 命令支持的全部参数，按功能分类介绍。

如果您是第一次使用 ftllm，建议直接使用基本的启动命令，所有参数都会自动设置：

```
ftllm run Qwen/Qwen3-0.6B
ftllm server Qwen/Qwen3-0.6B
```

如果您希望进一步调优，请参照以下参数说明。

### 模型加载参数

- `model`:
  - **描述**: 位置参数，指定要加载的模型。可以是 Huggingface 模型名、本地 HF 模型文件夹、fastllm 模型文件或 JSON 配置文件。
  - **示例**: `ftllm run Qwen/Qwen3-0.6B`、`ftllm run /mnt/Qwen3-0.6B`

- `-p` 或 `--path`:
  - **描述**: 与 `model` 位置参数等价的可选写法，指定模型路径。
  - **示例**: `ftllm run -p /mnt/Qwen3-0.6B`

- `--custom`:
  - **描述**: 指定描述自定义模型结构的 python 文件。文件中需定义 `__model__` 变量作为模型图。
  - **示例**: `ftllm run Qwen/Qwen3-0.6B --custom my_model.py`

- `--lora`:
  - **描述**: 指定 LoRA 权重路径，加载模型时会同时加载 LoRA 适配器。
  - **示例**: `ftllm run Qwen/Qwen3-0.6B --lora /mnt/my_lora`

- `--cache_dir`:
  - **描述**: 指定在线 Huggingface 模型的本地缓存目录。不设定时默认缓存到系统缓存路径（Linux 下为 `~/.cache/fastllm`）。
  - **示例**: `ftllm run Qwen/Qwen3-0.6B --cache_dir /mnt/model_cache`

- `--ori`:
  - **描述**: 指定原始模型权重路径，读取 GGUF 格式文件时需要配合使用。ftllm暂不支持从GGUF文件中读取模型配置
  - **示例**:

``` sh
ftllm server DeepSeek-V3-0324-Q4_K_M-00001-of-00009.gguf --ori DeepSeek-V3
```

### 数据类型参数

当启动的模型为浮点精度（`BF16`、`FP16`、`FP8`）时，可以通过以下参数来设置推理精度。若不设定，ftllm 会使用模型中设定的精度进行推理。若使用的模型已经是量化好的模型（如 AWQ、Fastllm 导出的量化模型等），建议不指定这些参数。

- `--dtype`:
  - **描述**: 指定模型的权重数据类型（读取 HF 模型时有效）。
  - **可选值**: `int4g` `int4` `int8` `fp8` `float16` `bfloat16` 或其他支持的数据类型。
  - **默认值**: `auto`（自动根据模型配置选择）
  - **示例**: `--dtype int4`

- `--atype`:
  - **描述**: 指定推理时的激活类型。
  - **可选值**: `float32`、`float16`、`bfloat16`
  - **默认值**: `auto`（当 device 为 `cpu`/`numa`/`tfacc` 时自动设为 `float32`）
  - **示例**: `--atype bfloat16`

- `--dtype_config`:
  - **描述**: 指定权重类型配置文件，用于对不同层设置不同的量化精度（动态量化）。
  - **示例**: `--dtype_config my_dtype_config.json`

- `--moe_dtype`:
  - **描述**: 指定 MOE（Mixture of Experts）层的权重数据类型（读取 HF 模型时有效）。如果模型不是 MOE 结构，此参数不会生效。
  - **可选值**: `int4g` `int4` `int8` `fp8` `float16` `bfloat16` 或其他支持的数据类型。
  - **示例**: `--moe_dtype int4`

- `--moe_atype`:
  - **描述**: 指定 MOE 层的激活类型。
  - **可选值**: `float32`、`float16`、`bfloat16`
  - **示例**: `--moe_atype bfloat16`

### 设备与线程参数

可以通过以下参数来设定执行推理的设备和线程配置。

- `--device`:
  - **描述**: 指定模型运行的计算设备。
  - **示例**: `--device cpu`、`--device cuda`
  - **常用值说明**:
    - `cpu` 使用 CPU 推理
    - `cuda` 使用 GPU 推理
    - `numa` 使用多路 NUMA 节点加速推理，在多 CPU 的机器才会有提升。使用 numa 加速时，强烈建议关闭系统 numa 平衡（ubuntu 中可使用命令 `sudo sysctl -w kernel.numa_balancing=0`）
    - `multicuda` 使用多设备张量并行推理
      - **使用多显卡**: `--device multicuda:0,1`
      - **使用多显卡+CPU**: `--device multicuda:0,cpu`
      - **按比例使用多显卡+CPU**: `--device multicuda:0:4,1:5,cpu:1`（`cuda:0` 计算 4/10，`cuda:1` 计算 5/10，`cpu` 计算 1/10）
  - **串行计算**: 一些场景下可以指定不同的 device 串行执行，例如：
    - `--device "{'cuda:0':3,'cuda:1':2}"`: `3/5` 的层运行在 `cuda:0`，`2/5` 的层运行在 `cuda:1`
    - **简写**: `--device cudapp=N` 表示 N 卡均匀串行，例如 `--device cudapp=4` 等价于 `--device "{'cuda:0':1,'cuda:1':1,'cuda:2':1,'cuda:3':1}"`
    - **简写**: `--device cudapp=1:2:3` 表示三卡按 1:2:3 比例串行

- `--vision_device`:
  - **描述**: 指定 Qwen3.5 / Qwen3.6 / Qwen3.8 视觉编码器的运行设备，默认 `auto`。设为 `cpu` 时视觉塔权重常驻内存并在 CPU 上完成编码，可减少视觉显存占用，代价是图像编码速度明显下降。
  - **CUDA 放置**: 单卡 CUDA 下 `auto` 使用首个前向 GPU；多卡 CUDA 视觉跟随普通 TP 的设备列表与分片比例，`cuda:N` 不单独覆盖视觉放置。详见[视觉张量并行](qwen35-vision-tp.md)。
  - **取值**: `auto`、`cpu`、`cuda`、`cuda:N`
  - **设备编号**: `cuda:N` 中的 `N` 是当前进程可见的 GPU 编号，受 `CUDA_VISIBLE_DEVICES` 影响；编号必须存在。无 CUDA 构建不能使用 `cuda` / `cuda:N`。
  - **环境变量**: 未指定命令行参数时读取 `FASTLLM_QWEN35_VISION_DEVICE`，未设置时使用 `auto`；显式命令行参数优先。
  - **示例**: `--vision_device cpu`、`--vision_device cuda:1`

- `--moe_device`:
  - **描述**: 指定 MOE 层的计算设备。一般和 `--device` 指定为不同的设备来实现混合推理。如果模型不是 MOE 结构，此参数不会生效。
  - **示例**: `--moe_device cpu`、`--moe_device numa`
  - **常用值说明**:
    - `cpu` 使用 CPU 推理
    - `numa` 使用多路 NUMA 节点加速推理
    - `cuda` 使用 GPU 推理（MOE 层需要大量显存，一般不建议指定为 `cuda`）
  - **说明**: 例如 `--device cuda --moe_device cpu` 实现 MOE 模型的单卡+CPU 混合推理；`--device cuda --moe_device numa` 实现单卡+多 NUMA 节点加速推理。

若不设定 `--device` 和 `--moe_device`，会使用默认配置：

| 模型类型 | device | moe_device |
|-------:|--------|------------:|
| 稠密模型 | cuda   |   不生效    |
| MOE模型  | cuda   |   numa    |

如果只设置了device没设置moe_device，则moe_device会跟随device

- `-t` 或 `--threads`:
  - **描述**: 设置使用的 CPU 线程数。不设定时自动检测
  - **示例**: `-t 30`

- `--cuda_embedding`:
  - **描述**: 开关参数，在 CUDA 上进行 embedding 计算。启用后可以减少 CPU-GPU 之间的数据传输。
  - **示例**: `ftllm server Qwen/Qwen3-0.6B --cuda_embedding`

- `--cuda_shared_expert` 或 `--cuda_se`:
  - **描述**: 指定 MOE 中的共享专家是否在 CUDA 上执行，默认为 `true`。设为 `false` 可以节省显存。
  - **示例**: `--cuda_se false`

- `--enable_amx` 或 `--amx`:
  - **描述**: 是否开启 Intel AMX 加速，默认为 `false`。需要 CPU 支持 AMX 指令集。
  - **示例**: `--amx true`


### 推理与批处理参数

- `--max_batch`:
  - **描述**: 设置每次最多同时推理的请求数量。不设定时由系统自动决定。
  - **示例**: `--max_batch 32`

- `--chunked_prefill_size`:
  - **描述**: 设置分块 prefill 的切片大小（首块与后续块相同）。启用分块 prefill 可以在长上下文场景下降低首 token 延迟，同时提高并发时的吞吐量。
  - **示例**: `--chunked_prefill_size 8192`

- `--moe_experts`:
  - **描述**: 指定 MOE 层使用的专家数。不设定则根据模型配置自动设定。减少专家数可以提高推理速度，但可能降低推理准确度。
  - **示例**: `--moe_experts 6`

### 缓存与显存参数

- `--tokens`:
  - **描述**: 设置总的 token 数量，用于计算 paged cache 的最大页数。不设定时由系统根据可用显存自动计算。
  - **示例**: `--tokens 65536`

- `--page_size`:
  - **描述**: 设置 paged cache 每页的大小（以 token 数计）。
  - **默认值**: `128`
  - **示例**: `--page_size 256`

- `--gpu_mem_ratio`:
  - **描述**: 设置 GPU 显存使用比例。例如 `0.9` 表示最多使用 90% 的显存，剩余部分留给系统和其他程序。
  - **默认值**: `0.9`
  - **示例**: `--gpu_mem_ratio 0.8`

- `--cuda_slab`:
  - **描述**: 设置 CUDA 模型权重 slab 分配块大小，单位 MB。默认 `0` 表示关闭。对于将大量 MOE 专家权重放在 CUDA 上的场景，启用后可以减少大量小权重分别 `cudaMalloc` 带来的页对齐和碎片开销。
  - **默认值**: `0`
  - **示例**: `--cuda_slab 1024`

### 对话与模板参数

- `--enable_thinking`:
  - **描述**: 是否开启硬思考（thinking）开关。启用后模型会在回答前先进行思考推理过程（需要模型支持，如 Qwen3、Glm4Moe 等）。对于支持的模型会自动启用。
  - **示例**: `--enable_thinking true`、`--enable_thinking false`

- `--tool_call_parser`:
  - **描述**: 指定工具调用（function calling）的解析器类型。
  - **默认值**: `auto`（根据模型自动选择）
  - **示例**: `--tool_call_parser auto`

### Qwen3.5 MTP 随机草稿

MTP 对非贪心采样请求默认使用随机草稿，并保存每一步的实际草稿分布 q。目标分布 p 遵守请求的 temperature/top-k/top-p，按 `min(1, p/q)` 接受草稿；首次拒绝后从归一化的 `max(p-q, 0)` 补采，并丢弃后续草稿。单请求和多请求批处理使用同一套提案状态与拒绝采样路径。

```sh
ftllm server /path/to/model --tp 2 --mtp 5 --speculative_algorithm mtp
```

greedy 请求仍使用贪心草稿和精确匹配验证。DFlash 使用自己的提案状态和验证路径。

默认通过 Gumbel-max 融合生成随机草稿，并保存温度缩放后的 logits 和归一化常数；此时 q 不做 top-k/top-p 截断，目标 p 仍保留请求的全部过滤。设置 `FASTLLM_MTP_GUMBEL=0` 可使用保存完整概率数组的实现，q 此时也经过 top-k/top-p 过滤；两者均按实际 q 做拒绝采样，关闭 Gumbel 不会切回贪心草稿。长预填充首次播种也保存对应的 q，供随后验证使用。

CUDA embedding 可用时，`FASTLLM_MTP_GPU_CHAIN` 默认开启，减少草稿 token 回传。`FASTLLM_MTP_PREFIX_GRAPH` 仅在全局 CUDA Graph 开启且 GPU 草稿链生效时使用，可设为 `0` 单独关闭。它们不改变采样公式。随机草稿的收益取决于实际负载、接受率和采样开销，应比较完整解码速度。

NVIDIA SM75 及以下（包括 RTX 2080 Ti）默认关闭 CUDA Graph；普通 Qwen3.5 系列推理仅在所有参与 GPU 的算力均大于 7.5 且满足自动启用条件时开启，MTP 模式不自动开启。可通过 `FASTLLM_CUDA_GRAPH=1` 显式开启或 `FASTLLM_CUDA_GRAPH=0` 显式关闭。全局 Graph 关闭时，草稿前缀 Graph 也不会运行，随机草稿和完整分布拒绝采样仍然生效。

### 服务部署参数

以下参数在使用 `ftllm server` 部署 API 服务时可用。

- `--model_name`:
  - **描述**: 设置部署的模型名称。调用 API 时会进行名称核验。不设定时，如果使用在线模型会自动使用模型的 repo id。
  - **示例**: `--model_name my-model`

- `--host`:
  - **描述**: 指定 API server 的监听地址。
  - **默认值**: `0.0.0.0`
  - **示例**: `--host 127.0.0.1`

- `--port`:
  - **描述**: 指定 API server 的监听端口。
  - **默认值**: `8080`
  - **示例**: `--port 8000`

- `--api_key`:
  - **描述**: 设定 API Key。设置后，客户端调用接口时需要在请求头中携带此密钥进行认证。
  - **示例**: `--api_key sk-xxxxxxxx`

- `--think`:
  - **描述**: 强制在输出中添加 `<think>` 标签。当模型输出中丢失了思考标签时可以使用此参数修复。
  - **默认值**: `false`
  - **示例**: `--think true`

- `--hide_input`:
  - **描述**: 开关参数，隐藏日志中的请求信息。在生产环境中使用可以保护用户隐私。
  - **示例**: `ftllm server Qwen/Qwen3-0.6B --hide_input`

- `--dev_mode`:
  - **描述**: 开关参数，启用开发模式。开启后可以通过 API 获取当前对话列表并主动停止推理任务，方便调试。
  - **示例**: `ftllm server Qwen/Qwen3-0.6B --dev_mode`

## 注意事项

- nightly 版本更新频率较高，可能包含未充分测试的功能
- 如遇到问题，可以尝试更新到最新 nightly 版本或切换回稳定版
- 欢迎在 [GitHub Issues](https://github.com/ztxz16/fastllm/issues) 中反馈 nightly 版本的问题

## 版本查看

```
ftllm -v
```

---

### Nightly Changelog (`docs/nightly_changelog.md`)

# Nightly 更新日志

本文档记录 `fastllm-nightly` 每次更新的详细变更内容。

---

## 0.0.0.3 (2026.3.7)

### 新模型支持

- 支持 Minimax-M2 的 GGUF 格式
- 初步支持 Qwen3.5 

### 新功能

- 初步支持 Anthropic API 兼容接口，包含工具调用（tool call）支持

### CUDA / 计算优化

- 预编译架构增加 SM80，支持A系列显卡，RTX 30系列显卡
- 修复一些显存泄漏问题

### Bug 修复

- 修复纯CPU计算的一些错误

---

## 0.0.0.2

- 初始的nightly版本

---

### Qwen3 (`docs/qwen3.md`)

# Qwen3.5 / Qwen3.6 / Qwen3.8 部署指南

面向自动化 agent 的模型簇选择、`--auto` 启动和参数表见 [模型部署速查](model-deployment-agent.md)。建议把该页交给 agent 先读，再按本页的手动参数做固定实验。

[English](qwen3_en.md) · [返回 README](../README.md) · [Qwen4-Exp](qwen4.md) · [Benchmark](benchmarks/qwen3.md)

本文面向当前 Qwen3.5、Qwen3.6 和 Qwen3.8 模型，包含 GGUF 多模态部署说明。Qwen4-Exp / Qwen3.8-Flash-Next 使用独立架构，请阅读 [Qwen4-Exp 部署说明](qwen4.md)。

不同 checkpoint 可能是稠密、MoE、FP8、NVFP4 或其他量化格式。首次启动建议保留 `--dtype auto`，再根据显存和实测结果调整。

## 选择部署模式

Qwen3.8 27B 属于 Qwen3.5 架构。Qwen3.8-27B-FP8 的稠密 HF checkpoint 已验证 `--auto`，默认使用纯 GPU TP、并发 4 和内置 MTP5，按预算选择最长的原生上下文。其他格式仍可使用下方 CUDA/TP 手动配置。Flash-Next 混合推理见[独立指南](qwen3.8-flash-next/README.md)。

~~~bash
ftllm server /data/models/Qwen3.8-27B-FP8 \
  --auto --model_name qwen3.8-27b \
  --host 0.0.0.0 --port 8080
~~~

`--auto` 优先尝试双卡 TP，显存不足时尝试四卡 TP 和较短上下文。显式指定 `--tp`、`--max_batch`、`--max_context_length`、`--tokens` 或 `--mtp 0` 可覆盖默认值。`--auto_gpu_memory_limit` 和 `--auto_host_memory_limit` 仅约束自动规划预算。

## 手动配置参考

~~~bash
ftllm server /data/models/qwen \
  --model_name qwen \
  --host 0.0.0.0 --port 8080
~~~

FastLLM 会根据模型配置选择默认精度和设备。生产部署建议显式设置 `--model_name`、`--max_batch`、上下文上限和显存比例。

## 按设备选择启动命令

### 单张 NVIDIA GPU

适合能完整放入显存的稠密或量化 checkpoint：

~~~bash
ftllm server /data/models/qwen \
  --device cuda \
  --max_batch 8 \
  --gpu_mem_ratio 0.9
~~~

### 多张 NVIDIA GPU 张量并行

~~~bash
ftllm server /data/models/qwen \
  --device cuda --tp 0,1 \
  --cuda_embedding \
  --max_batch 16 \
  --gpu_mem_ratio 0.9
~~~

`--tp 0,1` 显式选择两张卡；也可以用 `--tp 2` 表示前两张可见 GPU。具体模型支持的并行路径和显存容量需要在目标机器上验证。

### GPU + NUMA 混合 MoE

适合专家权重无法全部放入显存的 MoE checkpoint：

~~~bash
ftllm server /data/models/qwen-moe \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192 \
  --gpu_mem_ratio 0.9
~~~

如果机器只有单路 CPU，可将 `--moe_device` 改为 `cpu`。NUMA 模式下线程数默认自动选择，也可以通过 `-t` 调整。

### CPU / NUMA

~~~bash
# 单路 CPU
ftllm server /data/models/qwen \
  --device cpu -t 32

# 多路 NUMA
ftllm server /data/models/qwen \
  --device numa -t 64
~~~

线程数应结合物理核心数和内存带宽实测，不建议直接照搬示例值。

<a id="gguf-multimodal"></a>

## GGUF 多模态（`--mmproj`）

GGUF 发布包通常将语言模型和视觉模块分开存放。使用不含视觉权重的主模型 GGUF 处理图片时，需要通过 `--mmproj` 显式加载配套的视觉 GGUF 文件；仅指定 `--ori` 不会加载视觉权重。

~~~bash
ftllm server /data/models/qwen3.8-27b-Q4_K_M.gguf \
  --mmproj /data/models/mmproj-qwen3.8-27b-f16.gguf \
  --ori /data/models/Qwen3.8-27B \
  --model_name qwen3.8
~~~

示例中的路径和文件名需替换为实际文件。默认使用单张 GPU；双卡可追加 `--tp 2`。

- **文件匹配**：主模型、mmproj 和原始配置必须来自相匹配的模型；不要混用不同尺寸或版本的视觉模块。
- **配置来源**：推荐通过 `--ori` 指向原始 HF 配置和 tokenizer 目录，其中 `config.json` 必须包含完整的 `vision_config`。保留配套的 `preprocessor_config.json`，处理视频时也保留 `video_preprocessor_config.json`；无需为此下载原始 HF 大权重。
- **省略 `--ori`**：当主模型 GGUF 的元数据和 tokenizer 可直接读取时，可将匹配的 `config.json` 和预处理配置放在**主模型 GGUF 所在目录**，再省略 `--ori`。当前不会仅凭 mmproj 文件自动补齐 `vision_config`，缺失时视觉推理会报配置不完整。
- **支持范围**：`--mmproj` 当前仅接入 Qwen3.5 架构族的 GGUF，包括使用该架构的 Qwen3.6/3.8；HF 模型目录不使用此参数。当前不能与通过 `--draft` 挂载的**外部 MTP** 同时使用，此限制不等同于禁用 GGUF 内置 MTP。

服务启动后可通过工作室上传图片，或使用 `/v1/chat/completions` 的 `image_url` 消息内容发送图片。

## 长上下文与缓存

~~~bash
ftllm server /data/models/qwen \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --gpu_mem_ratio 0.9
~~~

`--max_context_length` 是输入与输出合计上限。超出模型声明值时须指定有效的 `--rope_scaling`，容量不足会在启动时失败。Qwen3 的 YaRN 需要明确原始长度（常见为 32768）；具体配置和验证范围见[上下文扩展](context-length-extension.md)。实际窗口可从 `/v1/models` 查询。

## MTP

对于包含兼容 MTP 权重的模型：

~~~bash
ftllm server /data/models/qwen-with-mtp \
  --device cuda --mtp 4
~~~

`--mtp` 设置每轮 draft token 数，`0` 表示关闭，当前最大为 8。MTP 并不只属于某一个 Qwen 版本，是否可用取决于模型结构和 checkpoint。

Qwen3.5 GGUF 会直接使用文件中内置的 NextN/MTP 层。如果 GGUF 不含该层，可以挂载同结构模型中抽出的独立 `mtp.safetensors`：

~~~bash
ftllm server /data/models/qwen3.5-target.gguf \
  --device cuda --cuda_embedding \
  --draft /data/models/qwen3.5-fp8/mtp.safetensors \
  --draft_tokens 5
~~~

`mtp.safetensors` 的同目录必须有匹配的 `config.json`。也可以把 `--draft` 指向包含这两个文件的目录。加载时会校验 Qwen3.5 架构、层数、hidden size、head 数、词表和全部 MTP 张量形状；独立 MTP 当前只支持挂载到 GGUF target。MTP 会频繁访问词表投影，显存允许时应保留 `--cuda_embedding`，否则速度会明显下降。

## Qwen3.8 DFlash2

DFlash2 需要独立的 draft checkpoint：

~~~bash
ftllm server /data/models/qwen3.8-27b-fp8 \
  --model_name qwen3.8 \
  --tp 2 --cuda_embedding \
  --max_batch 1 \
  --speculative_algorithm dflash \
  --speculative_draft_model_path /data/models/qwen3.8-27b-dflash2 \
  --speculative_num_draft_tokens 8
~~~

当前推荐使用 draft checkpoint 的原生 block 大小。完整复现方式与实测速度见 [Qwen3.8 DFlash2 Benchmark](benchmarks/qwen38_27b_dflash2.md)。

## 思考与工具调用

~~~bash
# 关闭模型的 thinking 模板
ftllm server /data/models/qwen --enable_thinking false

# 自动选择工具调用解析器
ftllm server /data/models/qwen --tool_call_parser auto
~~~

API 请求也可以通过 `chat_template_kwargs.enable_thinking` 按请求控制思考模式。工具调用能力取决于具体 Instruct checkpoint 和 chat template。

## Benchmark

- [Qwen3.6-27B-FP8 双 RTX 5090 TP2](benchmarks/qwen36_27b_fp8.md)
- [Qwen3.8-27B DFlash2 / MTP 双 RTX 5090](benchmarks/qwen38_27b_dflash2.md)
- [通用 Benchmark 入口](benchmark.md)

速度只对文档中记录的模型、精度、硬件、batch 和上下文口径有效。

---

### Qwen35 Vision Tp (`docs/qwen35-vision-tp.md`)

# Qwen3.5 视觉张量并行

Qwen3.5 架构族（包括对应的 Qwen3.6/3.8 模型）的 CUDA 视觉编码器跟随普通 `--tp` 的设备列表和分片比例。注意力按完整 head 分配，MLP 和 merger 支持不均匀分片，可使用 3、5、7 等奇数卡配置。

```bash
CUDA_VISIBLE_DEVICES=0,1,2 ftllm server /path/to/model \
    --device cuda --tp 3 --atype float16 --multimodal
```

## 放置与精度

- 多卡 CUDA 视觉使用文本 TP 的所有设备；`--vision_device cuda:N` 在此配置下也跟随文本 TP。CPU 视觉和普通单卡执行继续沿用原有路径。
- 多卡 CUDA 模式下，分片前检查视觉权重的存储格式，接受未打包的 FLOAT32、FLOAT16、BFLOAT16 权重；量化或 GGUF 视觉权重会明确报错并指出权重名。
- 默认 FP16 路径的输出投影保留 FP32 部分和，归约后按原算子的舍入规则加入偏置与残差。跨 TP 的浮点结果可能不同，应结合 FP32 参考和原精度误差判断。
- 精度验证区分激活类型和权重存储类型；FP32/FP32、FP16/FP16、FP16/BFLOAT16 分别对照，其他混合类型组合需单独验证。这里的 BFLOAT16 指权重存储；当前 CUDA LayerNorm 不支持 BFLOAT16 激活。

## 多模态显存与缓存

`--multimodal` 在 KV cache 定容前加载视觉权重、预热并预留各卡固定工作区。工作区根据 processor 的单图/单视频上限和该卡实际分片确定，多张图片逐张复用。调整 processor 上限后需要重启；超出预留 patch 预算的 native 输入会报错。

每卡仍保留完整隐藏状态、残差和归约缓冲，应按各卡实际容量评估视觉显存。自动 KV 定容会使用释放出的空间，因此整卡显存还取决于 KV 预算、文本权重及运行时分配。

已有 verbose 日志在编码完成后输出每卡 `capacity`、`peak_cumulative`、`live`，并给出媒体数量和特征 token 数。`peak_cumulative` 是包含启动预热的累计高水位；整卡峰值需另外采样。

视觉最终特征保持 FP32 CPU 格式，图片 embedding 缓存继续驻留 CPU，文本 prefill 按分块上传。图片特征命中与 KV 命中分别统计；现有多模态 KV 安全 miss 行为保留。MTP、跨卡 KV/线性状态恢复和工具调用使用原有接口。

当 TP 卡数多于 KV heads 时，没有 KV 分片的卡不阻止前缀复用；保存 MTP 快照和恢复前缀前，会检查所有有效 K/V 与 MTP 分片是否齐全。

## 回归入口

- [原生分片、舍入与缓存测试](../test/basic/test_qwen35_vision_tp.cpp)
- [真实模型 API 回归](../test/api/qwen35_vision_tp_probe.py)：图片冷/热及换序、媒体页边界、跨卡文本前缀、MTP 原始 token 对照、流式与非流式工具调用。

---

### Qwen35 Streaming Load (`docs/qwen35_streaming_load.md`)

# Qwen3.5 系列 CUDA 按层加载

Qwen3.5/3.8 dense 模型加载 safetensors 或 GGUF 时，每个 decoder 层完成读取、
转换和合并后，立即上传 CUDA 并释放 CPU 源权重；独立 lm_head 同样处理。
单 GPU 使用原有 `ftllm server <模型路径>`，多 GPU 使用 `--tp`，无需额外开关。
DFlash/MTP 的主模型也适用，draft、embedding 和视觉权重保持原有准备流程。

适用条件是完整目标权重布局和纯 CUDA 设备映射；单 GPU 的 low-memory、CPU KV
cache 模式不启用此路径。此改动不扩展其他架构、MoE 或 CPU offload。
GGUF 的分组机制不依赖具体量化类型，但仍要求当前后端支持该格式及切分布局。

## 实现要点

- GGUF 加载器遵循已有的模型加载分组回调：组内并行读取，组间串行上传。
- GGUF GDN 的 tiled-head 排列和衰减转换在上传前完成，按层记录，避免重复处理。
  安全 FP32 反量化标记在合并、切分前设置；混合量化继续使用分开投影的布局。
- 单 GPU、TP 和预热复用 GDN 权重准备函数。合并 qkvz/ba 后，流式路径删除源
  权重；普通前向及隐藏状态前向支持仅保留合并权重的预填充。
- 单 GPU 提前上传矩阵及卷积权重。一维 norm 等权重仍按原流程准备，保留 CPU
  RMSNorm 偏移处理；共享 embedding 的输出头仍在预热时生成。
- GGUF 外部 MTP 的 KV 头数校验读取已加载配置，避免 `--ori` 时误用基类默认值。

## 内存与验证范围

2026-09-08，RTX 4090 D + RTX 4090、每卡 24564 MiB、约 1.5 TiB 主机 RAM；单卡使用 RTX 4090。以下为进程 `VmHWM`，不含未映射文件页缓存。被测构建未记录完整源码版本，结果不代表当前 HEAD 的测量：

| 配置 | RSS 峰值 |
| --- | ---: |
| 单卡 Qwen3.5-9B BF16 checkpoint | 4.32 GiB |
| 单卡 Qwen3.5-4B FP8 测试副本 | 4.40 GiB |
| 单卡 Qwen3.8-27B GGUF Q4_K_M | 8.85 GiB |
| 单卡同 GGUF + DFlash2 | 12.55 GiB |
| TP2 Qwen3.8-27B-FP8 + DFlash2 | 8.92 GiB |

普通模型使用默认 server 参数；DFlash2 另指定 draft 和 `--speculative_algorithm dflash --max_batch 1 --tokens 2048`。4B FP8 副本将 decoder 线性矩阵保存为 F8_E4M3、128×128 block scale，其余保留 BF16；单纯给 BF16 checkpoint 指定 dtype 不等于验证原生 FP8。
上述数据是特定权重和构建的加载结果，不代表任意上下文或并发下的进程内存上限。

禁用 swap 的 systemd scope 冷启动检查中，单卡 9B 使用 8 GiB 限额，单卡 Q4_K_M 和 TP2 FP8+DFlash2 使用 12 GiB，单卡/TP2 GGUF+DFlash2 使用 16 GiB，均完成三次 256-token 请求。scope 限额包含文件缓存，与进程 RSS 口径不同；未在相同物理内存容量的整机上验证。

```bash
ctest --test-dir build-fastllm \
  -R '^qwen35_streaming_(tp|single_gpu)_weights$' --output-on-failure
```

回归覆盖 CPU 源释放、CUDA 字节一致、共享输出头、draft 隔离、CPU norm、GGUF 合并和混合量化布局，以及 GDN 转换幂等性与安全反量化标记。设备不足时跳过。

---

### Qwen3 En (`docs/qwen3_en.md`)

# Qwen3.5 / Qwen3.6 / Qwen3.8 Deployment

[中文](qwen3.md) · [Back to README](../README_EN.md) · [Qwen4-Exp](qwen4_exp.md) · [Benchmarks](benchmarks/qwen3_en.md)

This guide covers current Qwen3.5, Qwen3.6, and Qwen3.8 models, including GGUF multimodal deployment. Qwen4-Exp / Qwen3.8-Flash-Next uses a separate architecture and is documented in the [Qwen4-Exp guide](qwen4_exp.md).

Checkpoint layouts may be dense, MoE, FP8, NVFP4, or another quantized format. Keep `--dtype auto` for an initial launch, then tune from measured memory use and throughput.

The dense HF Qwen3.8-27B-FP8 checkpoint supports `--auto`. It prefers all-GPU TP, sets batch 4, enables its embedded MTP5 draft, and chooses the longest native context that fits the memory budget. Other checkpoint formats need separate validation.

~~~bash
ftllm server /data/models/Qwen3.8-27B-FP8 --auto \
  --model_name qwen3.8-27b --host 0.0.0.0 --port 8080
~~~

## API server quick start

~~~bash
ftllm server /data/models/qwen \
  --model_name qwen \
  --host 0.0.0.0 --port 8080
~~~

## Commands by device

### Single NVIDIA GPU

~~~bash
ftllm server /data/models/qwen \
  --device cuda \
  --max_batch 8 \
  --gpu_mem_ratio 0.9
~~~

### Multi-GPU tensor parallelism

~~~bash
ftllm server /data/models/qwen \
  --device cuda --tp 0,1 \
  --cuda_embedding \
  --max_batch 16 \
  --gpu_mem_ratio 0.9
~~~

`--tp 0,1` selects two explicit GPUs; `--tp 2` selects the first two visible GPUs.

### GPU + NUMA hybrid MoE

~~~bash
ftllm server /data/models/qwen-moe \
  --device cuda --moe_device numa \
  --chunked_prefill_size 8192 \
  --gpu_mem_ratio 0.9
~~~

Use `--moe_device cpu` on a single-socket host.

### CPU / NUMA

~~~bash
ftllm server /data/models/qwen --device cpu -t 32
ftllm server /data/models/qwen --device numa -t 64
~~~

Treat the thread counts as examples and tune them for physical cores and memory bandwidth.

<a id="gguf-multimodal"></a>

## GGUF multimodal models (`--mmproj`)

GGUF releases commonly distribute the language model and vision module separately. To process images with a main GGUF that lacks vision weights, explicitly load the matching vision GGUF with `--mmproj`; `--ori` alone does not load vision weights.

~~~bash
ftllm server /data/models/qwen3.8-27b-Q4_K_M.gguf \
  --mmproj /data/models/mmproj-qwen3.8-27b-f16.gguf \
  --ori /data/models/Qwen3.8-27B \
  --model_name qwen3.8
~~~

Replace the example paths and filenames with your actual files. The default uses one GPU; append `--tp 2` for two GPUs.

- **Matching files:** Use a main model, mmproj, and original configuration from matching checkpoints. Do not mix vision modules from different model sizes or versions.
- **Configuration:** Prefer `--ori` pointing to the original HF configuration and tokenizer directory. Its `config.json` must include the complete `vision_config`. Keep the matching `preprocessor_config.json`, and `video_preprocessor_config.json` for video input. The original HF weight shards are not needed for this.
- **Without `--ori`:** If the main GGUF provides supported model metadata and a readable tokenizer, place the matching `config.json` and preprocessing configuration alongside the **main model GGUF**, then omit `--ori`. The loader does not currently reconstruct `vision_config` from the mmproj file alone; missing vision configuration causes visual inference to fail.
- **Scope:** `--mmproj` currently supports Qwen3.5-family GGUF models, including Qwen3.6/3.8 checkpoints using that architecture. It is not used for HF model directories. It cannot currently be combined with an **external MTP** module attached through `--draft`; this restriction does not disable MTP embedded in the main GGUF.

Once the server is running, upload images in Studio or send `image_url` content through `/v1/chat/completions`.

## Long context and caching

~~~bash
ftllm server /data/models/qwen \
  --max_context_length 131072 \
  --chunked_prefill_size 8192 \
  --prefix_cache true \
  --gpu_mem_ratio 0.9
~~~

Extending the model-declared window requires a valid `--rope_scaling` configuration; insufficient calibrated KV capacity fails startup. For Qwen3, specify the original RoPE length explicitly (commonly 32768). See [context extension](context-length-extension.md) for examples and validation limits.

## MTP

For a model with compatible MTP weights:

~~~bash
ftllm server /data/models/qwen-with-mtp \
  --device cuda --mtp 4
~~~

`--mtp` configures draft tokens per step; `0` disables it and the current maximum is 8. MTP is not exclusive to one Qwen version.

Qwen3.5 GGUF files use an embedded NextN/MTP layer directly when present. A GGUF without that layer can attach a standalone `mtp.safetensors` extracted from a structurally matching model:

~~~bash
ftllm server /data/models/qwen3.5-target.gguf \
  --device cuda --cuda_embedding \
  --draft /data/models/qwen3.5-fp8/mtp.safetensors \
  --draft_tokens 5
~~~

A matching `config.json` must be adjacent to `mtp.safetensors`; `--draft` may also point to the directory containing both files. Loading validates the Qwen3.5 architecture, layer and hidden dimensions, head counts, vocabulary, and every required MTP tensor shape. Standalone MTP currently attaches only to GGUF targets. MTP accesses the vocabulary projection frequently, so keep `--cuda_embedding` when memory permits or throughput drops substantially.

## Qwen3.8 DFlash2

~~~bash
ftllm server /data/models/qwen3.8-27b-fp8 \
  --model_name qwen3.8 \
  --tp 2 --cuda_embedding \
  --max_batch 1 \
  --speculative_algorithm dflash \
  --speculative_draft_model_path /data/models/qwen3.8-27b-dflash2 \
  --speculative_num_draft_tokens 8
~~~

DFlash2 requires a matching standalone draft checkpoint. See the [Qwen3.8 DFlash2 benchmark](benchmarks/qwen38_27b_dflash2.md).

## Thinking and tool calling

~~~bash
ftllm server /data/models/qwen \
  --enable_thinking false \
  --tool_call_parser auto
~~~

Thinking can also be controlled per API request through `chat_template_kwargs.enable_thinking`.

## Benchmarks

- [Qwen3.6-27B-FP8 on dual RTX 5090 TP2](benchmarks/qwen36_27b_fp8.md)
- [Qwen3.8-27B DFlash2 / MTP on dual RTX 5090](benchmarks/qwen38_27b_dflash2.md)
- [Benchmark index](benchmark_en.md)

Each result applies only to the recorded checkpoint, precision, hardware, batch, and context.

---

### Qwen4 (`docs/qwen4.md`)

# Qwen4-Exp / Qwen3.8-Flash-Next

部署和使用说明已统一到 [Qwen3.8-Flash-Next 用户手册](qwen3.8-flash-next/README.md)。

手册包含双卡串行与并行混推、专家缓存、固定 GPU 专家层、MTP、API 调用和本机实测性能。

[English user guide](qwen3.8-flash-next/README_EN.md)

---

### Qwen4 Exp (`docs/qwen4_exp.md`)

# Qwen4-Exp / Qwen3.8-Flash-Next

Deployment and usage instructions are now maintained in the [Qwen3.8-Flash-Next User Guide](qwen3.8-flash-next/README_EN.md).

The guide covers serial and parallel hybrid deployment on two GPUs, expert caching, fixed GPU expert layers, MTP, API requests, and measured performance.

[中文用户手册](qwen3.8-flash-next/README.md)

---

### Rocm (`docs/rocm.md`)

# ROCm 编译与 wheel 打包

本文维护在 `master` 分支，以下构建命令在 **`rocm` 分支**执行。该分支提供独立的 HIP 构建、采样实现和 `ftllm-rocm` 打包入口；本文中的 Python SDK 版本为 **10.0.0**。

```sh
git clone --branch rocm https://github.com/ztxz16/fastllm.git
cd fastllm
```

已有仓库也可以在保存当前工作后切换到 `rocm`。新版脚本需要包含本文末尾列出的打包提交；旧的系统 ROCm 6.x 安装说明不适用于这组 Python SDK wheel。

## 构建与运行条件

| 用途 | 条件 |
|---|---|
| Docker 编译 | Linux x86_64、可用的 Docker 及访问权限、下载基础镜像和依赖的网络；无需 GPU、AMD 驱动或宿主 ROCm SDK |
| 宿主直接编译 | Linux x86_64、Python >= 3.10、ROCm Python 开发 SDK、GCC/G++、CMake、Ninja、libnuma 开发包及 Python 打包工具 |
| 安装 wheel 后运行 | Linux x86_64、支持 AVX2/F16C/FMA 的 CPU、Python >= 3.10、兼容的 AMD 内核驱动和固件、GPU 设备访问权限及匹配的运行库设备包 |

当前分发基线是 Ubuntu 22.04 / Python 3.10，实机为 RX 7900 GRE（gfx1100）。Ubuntu 24.04 / Python 3.12 也能构建，但该环境生成的原生库引用了 `GLIBC_2.38`，不能作为 Ubuntu 22.04 兼容包分发。`linux_x86_64` 标签不代表支持所有 Linux 发行版，也不承诺 manylinux2014/glibc 2.17。

当前脚本不生成 ARM64 或 Windows wheel。ARM 主机上通过 Docker 模拟 x86_64 的构建方式尚未验证。

## 默认覆盖的 GPU 架构

`--architectures` 默认值为 **`all`**。脚本读取当前 `rocm` 包的依赖元数据，保留适用于当前平台的设备包，再与 SDK 编译器支持的目标取交集。无需维护固定的短架构列表；升级 SDK 后，新增且满足条件的目标会自动进入默认编译范围。

当前 ROCm Python SDK 10.0.0 提供以下 **25 个目标**：

| 目标组 | 编译目标 / pip extra |
|---|---|
| gfx9 | `gfx908`、`gfx90a`、`gfx942`、`gfx950` |
| gfx10.1 | `gfx1010`、`gfx1011`、`gfx1012` |
| gfx10.3 | `gfx1030`、`gfx1031`、`gfx1032`、`gfx1033`、`gfx1034`、`gfx1035`、`gfx1036` |
| gfx11.0 | `gfx1100`、`gfx1101`、`gfx1102`、`gfx1103` |
| gfx11.5 | `gfx1150`、`gfx1151`、`gfx1152`、`gfx1153` |
| gfx12.0 | `gfx1200`、`gfx1201` |
| gfx12.5 | `gfx1250` |

编译目标覆盖不等于对应硬件已完成运行验收。请根据目标显卡选择架构，并在对应硬件上验证模型、精度和性能。MI50/Radeon VII 的 `gfx906` 虽然能被编译器识别，但不在这组 SDK 的运行库设备包中，因此本 wheel 不包含它。

脚本会检查最终共享库中的每个 HIP bundle，确认其包含全部请求的目标。缺少运行库设备包、编译不支持或代码对象不完整都会报错，不会静默移除显卡目标后继续生成包。

## 使用 Docker 编译 wheel

推荐从仓库根目录运行：

```sh
# 查看当前镜像 SDK 能覆盖的目标；不编译 FastLLM
bash make_whl_rocm_docker.sh --list-architectures

# 默认编译全部可用架构
bash make_whl_rocm_docker.sh --jobs 12
```

首次执行会构建 Ubuntu 22.04 + ROCm SDK 10.0.0 开发镜像。镜像只安装用户态构建依赖，不安装 DKMS 或内核驱动，不挂载 GPU，也不需要 `--privileged`。默认的 25 架构构建会比单架构明显耗时；`--jobs` 控制构建任务并行数，单个 HIP 源文件内部仍可能逐架构编译。

默认产物位于：

```text
build-rocm-docker-22.04/dist/
  ftllm_rocm-0.1.8.1.post1-py3-none-linux_x86_64.whl
  ftllm_rocm-0.1.8.1.post1-py3-none-linux_x86_64.whl.sha256
  README.txt
```

文件归调用者的 UID/GID 所有。宿主用户没有 Docker socket 权限时，可以使用 `sudo bash make_whl_rocm_docker.sh ...`，脚本仍会按原调用者的身份写入产物。

常用选项：

```sh
# 只编译指定子集，支持逗号或分号分隔
bash make_whl_rocm_docker.sh --architectures 'gfx1100;gfx942' --jobs 12

# 复用已构建镜像
bash make_whl_rocm_docker.sh --skip-image-build --jobs 12

# 只重新打包已有构建；架构列表必须与该构建一致
bash make_whl_rocm_docker.sh --skip-image-build --skip-build \
  --architectures 'gfx1100;gfx942'

# 指定相对于挂载仓库的输出目录
bash make_whl_rocm_docker.sh --dist-dir dist/rocm --jobs 12

# Ubuntu 24.04，输出到 build-rocm-docker-24.04/dist/
bash make_whl_rocm_docker.sh --ubuntu 24.04 --jobs 12
```

`--build-dir` 和 `--dist-dir` 为容器内路径，相对路径从 `/workspace/fastllm`（挂载的源码仓库）解析。容器构建目录与宿主构建目录分开，以免混用 CMake 缓存。更换 SDK 版本或自定义基础镜像时，应选择新的构建目录；SDK 升级导致 `all` 的架构列表变化时，旧构建需要重新编译。

Docker Hub 无法访问时，可以使用 Ubuntu 官方 ECR 镜像：

```sh
bash make_whl_rocm_docker.sh \
  --base-image public.ecr.aws/ubuntu/ubuntu:22.04 --jobs 12

# 24.04 需选择对应的基础镜像
bash make_whl_rocm_docker.sh --ubuntu 24.04 \
  --base-image public.ecr.aws/ubuntu/ubuntu:24.04 --jobs 12
```

Ubuntu 镜像来源见 [Canonical 文档](https://ubuntu.com/docs/oci-registries/oci-how-to/getting-started/)。`--base-image` 也可以指定镜像 digest；它应与 `--ubuntu` 选择的发行版一致。`--image` 可指定构建镜像名称，`--rocm-version` 可指定 Python SDK 版本。当前验证版本为 10.0.0，其他 SDK 版本需要重新验证。

如需只构建镜像，两个 Dockerfile 的 context 都使用 `whl_docker_rocm/`：

```sh
docker build -t ftllm-rocm-builder:ubuntu22.04-rocm10.0.0 whl_docker_rocm
docker build -f whl_docker_rocm/24.04/Dockerfile \
  -t ftllm-rocm-builder:ubuntu24.04-rocm10.0.0 whl_docker_rocm
```

镜像通过共用的 `install-sdk.sh` 安装固定版本的开发工具。构建 context 不包含源码、模型或已有构建产物；运行镜像时再挂载源码仓库。Docker 与宿主打包入口最终调用相同的 `tools/scripts/build_rocm_wheel.py`。

## 使用宿主 SDK 编译 wheel

以下命令适用于 Ubuntu 22.04；SDK 的 10.0.0 是 Python 发行包版本：

```sh
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
  build-essential ca-certificates git libnuma-dev python3-venv

python3 -m venv "$HOME/venvs/fastllm-rocm-build"
. "$HOME/venvs/fastllm-rocm-build/bin/activate"
python -m pip install 'pip==26.2.1'
python -m pip install \
  'rocm[libraries,devel]==10.0.0' \
  'cmake==4.4.3' 'ninja==1.13.2' 'setuptools==84.0.0' 'wheel==0.48.0' \
  --extra-index-url https://stable.repo.amd.com/rocm/whl-next/

bash make_whl_rocm.sh --list-architectures
bash make_whl_rocm.sh --jobs 12
```

脚本自动定位当前 Python SDK，不需要固定的虚拟环境路径，也不需要提前设置 `ROCM_PATH`、`HIP_PATH`、`CMAKE_PREFIX_PATH` 或 `LD_LIBRARY_PATH`。默认构建目录为 `build-rocm-wheel`，默认输出目录为 `<build-dir>/dist`。

```sh
# 选择子集和独立构建目录
bash make_whl_rocm.sh --architectures 'gfx1100;gfx942' \
  --build-dir build-rocm-subset --jobs 12

# 复用该构建，只重新生成 wheel
bash make_whl_rocm.sh --architectures 'gfx1100;gfx942' \
  --build-dir build-rocm-subset --skip-build
```

脚本不删除原有构建目录；`--skip-build` 会检查 ROCm 后端、CPU 构建基线和架构配置，不接受不匹配的 CUDA 或其他架构构建。

### 只编译原生共享库

开发和调试时也可以直接调用 CMake：

```sh
# 在上面的 SDK 虚拟环境中执行
export ROCM_PATH="$(python -m rocm_sdk path --root)"
cmake -S . -B build-rocm-dev -G Ninja \
  -DUSE_ROCM=ON -DUSE_CUDA=OFF -DUSE_NUMAS=ON \
  -DROCM_ARCH='gfx1100;gfx942' \
  -DCMAKE_CXX_COMPILER=/usr/bin/g++
cmake --build build-rocm-dev --target fastllm_tools --parallel 12
```

这里生成的是开发用共享库。分发 Python 包时使用前面的 wheel 脚本，它会设置统一 CPU 基线、生成运行库加载代码、附带 libnuma 和许可证，并声明对应 GPU 设备包的 pip extras。不要直接把开发目录中的 `.so` 当作完整 pip 包分发。

## 用户安装 wheel

在已有 Python/pip 和兼容 AMD 驱动的机器上，用户不需要安装 ROCm 开发 SDK、hipcc、GCC、CMake、PyTorch 或 Triton。ROCm 核心运行库、数学库、指定 GPU 的设备数据和应用 Python 依赖会由 pip 安装；模型文件单独准备。

以 RX 7900 GRE 的 `gfx1100` 为例，在 wheel 所在目录运行：

```sh
python3 -m venv .venv
. .venv/bin/activate
python -m pip install \
  './ftllm_rocm-0.1.8.1.post1-py3-none-linux_x86_64.whl[gfx1100]' \
  --extra-index-url https://stable.repo.amd.com/rocm/whl-next/
python -m pip check

ftllm chat /path/to/Qwen3.5-4B \
  --device cuda:0 --dtype float16 --atype float16 \
  --max_batch 1 --kv_cache_limit 1G --chunked_prefill_size 256 --enable_thinking false

ftllm server /path/to/Qwen3.5-4B \
  --device cuda:0 --dtype float16 --atype float16 \
  --max_batch 1 --kv_cache_limit 1G --chunked_prefill_size 256 --enable_thinking false
```

AMD 后端沿用 `cuda:0` 设备参数。需要访问 `/dev/kfd` 和 `/dev/dri/renderD*`；驱动、固件及设备访问权限由宿主提供。已安装 wheel 的程序不需要手动设置 ROCm 库路径环境变量。

至少选择一个与显卡对应、且包含在该 wheel 中的架构 extra。多种 GPU 可以组合为 `[gfx1100,gfx942]`；`[all-gpus]` 安装该 wheel 所覆盖全部架构的设备数据，体积会显著增加。普通 `[all]` 保持应用功能依赖的含义，不代表全部 GPU。

`ftllm-rocm` 与 NVIDIA 版使用相同的 `ftllm` Python 模块和 CLI，请使用独立虚拟环境。本文描述的是本地生成 wheel 的安装方式，不能假定 PyPI 上已有相同版本或相同架构覆盖的发布包。

## 构建标识、验证与限制

wheel 内的 `ftllm/rocm_build_info.json` 记录 SDK 版本、实际架构、HIP bundle 检查数、源码提交、工作区是否有未提交修改、原生库 SHA256、构建系统、Python 和 glibc 版本。构建时的 glibc 版本不是自动计算出的最低运行版本。

当前已验证的能力包括 Qwen3.5-4B FP16 推理、OpenAI API、ROCm 原生采样和图重放。gfx942 的 FP8 兼容层使用软件转换保留 OCP E4M3 格式，转换回归覆盖所有双字节组合、舍入、次正规数、正负零、NaN 和饱和边界。DFlash 专用 top-k 和拒绝采样尚未完成移植；其他模型、量化路径及视觉功能需要分别验证。

构建端的架构选择测试无需 GPU：

```sh
python test/test_rocm_wheel_architectures.py -v
```

2026-09-05 验证记录：

| 构建环境 | 验证结果 |
|---|---|
| Ubuntu 22.04 / Python 3.10 宿主 SDK | 25 目标完整编译和打包通过；18 个 HIP bundle 均含全部目标，wheel 为 85.37 MiB |
| Ubuntu 22.04 Docker | gfx1100 + gfx942 编译和打包、镜像缓存复用、仅重新打包通过；产物安装和模型 API 验证通过 |
| Ubuntu 24.04 Docker / Python 3.12 | gfx1100 编译和打包通过；未进行该产物的 GPU 运行验收 |

25 目标 wheel 已在独立运行环境中安装并通过 `pip check`、CLI 导入、Qwen3.5-4B FP16 API、原生采样及图重放回归。运行环境不含开发 SDK、NVIDIA 依赖、PyTorch 或 Triton；FastLLM/ROCm 库从该环境加载，未借用源码构建目录。实机验证仅使用 RX 7900 GRE，其他目标目前完成了编译检查。gfx942 的 FP8 软件转换逻辑还在 gfx1100 上通过强制软件分支验证，这不能替代 MI300 实机验收。

85.37 MiB 仅为 FastLLM wheel 的体积，不包括 pip 安装的 ROCm 运行库、设备数据、其他 Python 依赖和模型文件。

对应 `rocm` 分支提交：`e9e5e4a0`（多架构 FP8 兼容）、`dfe41a6b`（动态全架构 wheel 打包）、`be211b1b`（Docker 构建入口）。本次 25 目标 wheel 记录的源码提交为 `be211b1b`，构建时工作区干净。

## 鸣谢

[leavelet](https://github.com/leavelet) 提供了早期 ROCm 支持。

---

### Structured Output (`docs/structured-output.md`)

# JSON 结构化输出

Chat Completions 的 `response_format` 与 Responses 的 `text.format` 使用同一套服务层实现，适用于不同模型，不需要额外环境变量。

支持 `text`（普通文本，默认）、`json_object`（JSON 对象）和 `json_schema`（指定 JSON Schema）。例如：

```json
{
  "model": "your-model",
  "input": "为检查目录内容的任务起一个标题",
  "text": {
    "format": {
      "type": "json_schema",
      "name": "task_title",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {"title": {"type": "string", "maxLength": 36}},
        "required": ["title"],
        "additionalProperties": false
      }
    }
  }
}
```

Chat Completions 对应写法为 `"response_format": {"type": "json_schema", "json_schema": {"name": "task_title", "strict": true, "schema": {...}}}`。
JSON 对象模式为 `"response_format": {"type": "json_object"}`，或 `"text": {"format": {"type": "json_object"}}`。

实现采用通用系统提示引导生成，并在完成时校验 JSON 和 Schema；**尚未使用逐 token 的语法约束解码**，成功率仍取决于模型的指令遵循能力。无论 `strict` 是否设置，完成的答案都会接受格式校验。嵌套对象、数组、枚举和文档内 `$ref` 由 `jsonschema` 校验；不会联网获取外部 `$ref`。

流式输出仍逐段发送，收到成功的结束事件之前，内容只是未完成的候选结果。若最终校验失败，Chat Completions 返回 `invalid_response_format` 错误，Responses 流以 `response.failed` 结束，不会伪报成功。非流式校验失败返回 HTTP 500；非法格式或 Schema 在推理前返回 HTTP 400。

格式要求作用于最终文本答案，不会禁止正常工具调用。达到输出 token 上限时仍按截断处理（`length` / `incomplete`），不误判为 Schema 错误。普通文本请求不增加格式提示或输出校验。

---

### Tfacc (`docs/tfacc.md`)

## TFACC介绍

TFACC是ThinkForce公司7000系列处理器的AI算力平台，可用于TF 7000系列处理器的大模型推理加速。

## 快速开始

### 加载驱动

``` sh
cd fastllm/third_party/tfacc/driver/tfacc2
./build_driver.sh
modprobe tfacc2
```

### 打开TFACC计算服务

``` sh
cd fastllm/third_party/tfacc
python3 ./launch.py 4 & #这里的参数是numa节点数量，需要根据7000服务器具体的型号设定
```

### 编译

建议使用cmake编译，需要提前安装c++编译器，make, cmake

gcc版本建议9.4以上，cmake版本建议3.23以上

使用如下命令编译

``` sh
bash install.sh -DUSE_TFACC=ON
```

### 运行demo程序

我们假设已经获取了名为`model.flm`的模型（参照 [模型获取](models.md)，初次使用可以先下载转换好的模型)

编译完成之后在build目录下可以使用下列demo:

``` sh
# 这时在fastllm/build目录下

# 命令行聊天程序, 支持打字机效果 (只支持Linux）
./main -p model.flm 

# 简易webui, 使用流式输出 + 动态batch，可多路并发访问
./webui -p model.flm --port 1234 

# python版本的命令行聊天程序，使用了模型创建以及流式对话效果
python tools/cli_demo.py -p model.flm 

# python版本的简易webui，需要先安装streamlit-chat
streamlit run tools/web_demo.py model.flm 

```

更多功能及接口请参照[详细文档](../README.md)

---

### Tp2 Prefill (`docs/tp2-prefill.md`)

# 双卡 Ampere prefill 可选加速

以下两个开关默认关闭，只有值为 `1` 才启用，在通信组初始化时读取。

| 开关 | 行为 |
|---|---|
| `FASTLLM_TP2_WHT6_ALLREDUCE=1` | 将 rank-local FP16 contribution 做 32 点 WHT 和 6-bit 量化，通过主机固定内存中转，解码求和后再加未压缩的 residual。属于有损通信。 |
| `FASTLLM_TP2_MLP_OVERLAP=1` | 将符合条件的 MLP 按 512 token 分块，使 down projection 后的归约与下一块计算重叠。WHT6 开启时使用 WHT6，否则使用原始 NCCL FP16 归约。 |

两者均仅适用于 TP=2、两张卡都为 SM80/SM86 的 eager 执行。CUDA Graph capture、其他架构及不满足条件的调用继续使用已有路径。关闭开关时，不分配相应 GPU 工作区或创建额外 CUDA stream，也不改变原 NCCL 归约函数。

WHT6 仅接管 1–32 MiB 的 FP16 contribution，保留原 Marlin 权重布局。它改变归约数值结果，应针对实际任务验证质量；默认行为不包含此量化。

MLP overlap 当前限定无 bias、FP16 activation/residual、FP8 E4M3 Marlin 权重、128×128 block scales，以及每卡 `tokens=2048, hidden=5120, intermediate=8704` 的 dense MLP。工作区约 300.5 MiB/卡，只在首次符合条件的调用中分配，跟随通信组释放。它不预取或反量化下一层权重。

原始 NCCL overlap 保留 rank 0 先以 FP16 加 residual、再进行 FP16 AllReduce 的顺序。分块 GEMM 仍可能因计算顺序产生舍入差异。两卡须以相同顺序调用集合通信；开始提交后发生错误会终止当前路径，不切换后端继续执行。

## 默认 FP8 linear 路径

SM75/80/86 的大 prefill 可使用已有 Marlin 布局反量化 + cuBLAS 路径，仍受既有 `FASTLLM_CUDA_FP8_PREFILL_CUBLAS` 开关与工作区条件控制。该扩展只作用于 FP8，共用状态入口不会将 NVFP4 扩展到 SM80/86。

SM80/86 的 2–3 token 宽扩展矩阵采用 Marlin 布局多行 GEMV：`N>=16384, K>=4096, K<=N/2`。单 token GEMV 保持原实现，其他小 batch 和中间尺寸继续使用 Marlin GEMM。没有新增 GEMV 行数环境开关。

## 回归测试

配置 `-DUSE_CUDA=ON -DUNIT_TEST=ON` 后构建相关 target，再运行：

```bash
ctest --test-dir build-fastllm --output-on-failure \
  -R 'cuda_fp8_(sm75_prefill|mlp_prefill|marlin_multirow)|tp2(WHT6AllReduce|_mlp_overlap)'
```

测试覆盖独立 CPU 反量化参考、cuBLAS 与 Marlin 路由、CUDA Graph、prefill 前后 decode、WHT6 编码及通信缓冲区复用，以及 NCCL/WHT6 两种 MLP overlap 后端。历史名称 `cuda_fp8_sm75_prefill_*` 的测试同时适用于 SM80/86。

---

### Version (`docs/version.md`)

# 版本日志

[English](version_en.md)

## 未发布

- 新增 DeepSeek-V4.1-Flash，支持文本与图像输入、Engram、DSpark 推测解码、张量并行和 CPU/CUDA 混合推理，完善精度、前缀缓存和工具调用
- 完善 Qwen4-Exp / Qwen3.8-Flash-Next 张量并行解码与 MTP 验证、回滚，优化 QSA 长上下文选择、PLE 搬运、词表分片和 CUDA Graph 复用
- 优化 Qwen3.5/3.8 MTP，支持草稿层张量并行、NVFP4 草稿量化和候选词表筛选，改善批量草稿与验证性能，修复采样分布、因果边界和缓存回滚问题
- 优化 DFlash2 草稿注意力、候选评分和缓存写入，完善单卡与多卡计算图、工作区复用和显存预算，修复验证图和容量边界重复预填充问题
- 优化不同 GPU 架构下的 FP8/NVFP4 预填充与小批量解码，扩展 GDN、Attention、RMSNorm 和投影融合，增加符合条件的原生低比特预填充路径及回退
- 优化 CPU/NUMA 低比特计算、专家分组调度和搬运重叠，完善 GGUF IQ2/IQ3 解码与小批量计算，修复 Qwen3.5 GGUF 单步解码崩溃及 x86 指令集兼容问题
- 新增磁盘专家多级缓存，扩展 Qwen4 和 DeepSeek-V4.1 的张量并行专家缓存与批量验证，支持分层专家放置，降低模型加载峰值并完善临时工作区复用和回收
- 完善 Qwen3.5 架构多模态，支持视觉编码器卸载和不均匀张量并行，修复多图特征合并、工具结果图片及调用历史，隔离图文请求的文本前缀缓存并改善预填充显存使用
- 完善 OpenAI 与 Anthropic 接口的思考参数兼容、工具调用流式解析和结束状态，修复 Responses 历史拆分、无限生成结束原因及流式断连清理，改进上下文超限错误提示
- 新增通用 JSON 结构化输出，支持 Chat Completions 的 `response_format` 和 Responses 的 `text.format`，通过提示引导与完成时校验支持 JSON 对象和 JSON Schema，并明确报告格式错误
- 启动器接入 OpenCode、Codex 和 Claude Code，支持独立安装、会话管理和工具审批；完善 DeepSeek Harness 与 Pi 的运行时适配、输出预算和结束判定
- 重构启动器为插件化界面，支持自然语言定制、主题切换和黑夜模式，完善 Markdown 与隔离 HTML 预览；改进 Agent 运行时打包、绿色包双击入口和离线参数手册
- 新增 NCCL 启动通信自检与超时处理，修复多卡归约同步、跨卡死锁、Marlin 越界、CUDA Graph 缓冲生命周期与分片地址复用，以及小批量 RMSNorm 归约舍入差异

## V0.1.8.2

- 新增网页部署启动器和 Electron 桌面启动器，支持模型下载、启动项管理、长上下文与高并发自动配置，以及推理速度和可用上下文显示
- 重构工作室，支持多会话并发、停止生成、附件和历史记录，接入 Pi 目录智能体，增加联网搜索、文档问答、数据分析、代码处理和 PPT 生成能力
- 完善 Qwen3.8-Flash-Next 多模态、MTP 和前缀缓存，新增 FP8/NVFP4 CUDA 专家缓存、动态混合调度和 NUMA 权重共享，改善混合推理速度与内存使用
- 新增 GLM-5.3 MTP 推测解码，支持 DeepSeek-V4 混合推理 MTP
- 完善 GGUF 自描述加载，支持 Qwen3.5 架构 GGUF 的内外置 MTP 和多模态投影；修复低比特量化兼容问题，通过分块反量化降低临时显存占用
- 新增 `--low_gpu_mem` 低显存模式，为 Qwen3.5 架构增加 FP4 KV Cache，并优化张量并行权重加载内存和高分辨率视觉推理，新增图片特征缓存
- 新增 `--rope_scaling`，为 HF Qwen2、Qwen3、Qwen3.5 系列支持静态 YaRN 上下文扩展，并增加启动时的上下文容量检查
- 优化不同 GPU 架构下的 FP8 Linear、NVFP4、GDN 和分页注意力，改善预填充、解码及小批量推理性能
- 修复奇数卡张量并行卡死、多卡量化权重重排死锁、MTP 验证图显存泄漏和双卡多模态越界等问题，完善工具调用流式解析与参数校验
- Wheel 增加 SM86 原生代码，Linux 默认安装 Triton；完善绿色包依赖、命令行入口和使用说明，支持无桌面环境部署

## V0.1.8.1

- 完善 Qwen3.8-Flash-Next，支持 MTP 推测解码和跨请求前缀缓存，优化 CUDA/NUMA 混合推理性能
- 完善 Qwen3.5 GGUF、DFlash2 和双卡推理，修复 CUDA Graph 相关问题
- 完善 CUDA 12.9、多架构 Wheel、Triton 服务脚本及 Linux 绿色包的构建与打包

## V0.1.8.0

### 新模型与推理能力

- 支持 Qwen4-Exp / Qwen3.8-Flash-Next FP8 文本模型，覆盖四路超连接、Gated DeltaNet、QSA 稀疏注意力、PLE ngram 和 FP8 MoE；当前支持文本解码，不加载视觉和 MTP 权重
- 完善 Qwen4 CPU、CUDA 和 NUMA 混合推理、长上下文与前缀缓存、预填充和解码融合算子及 CUDA Graph；新增 `--ngram_device disk`，可从磁盘按需读取超大 PLE 表以降低主机内存占用
- 支持 Kimi-K3，覆盖 CUDA、NUMA、CPU/GPU 专家并行、磁盘专家、KDA/MLA 分块预填充、DSpark、XTML 服务协议及工具调用
- 支持 Dots3-Note，加入 DSA 索引、稀疏注意力、长上下文缓存、分块预填充、思考模式及工具调用
- 支持 GLM-5.3-Flash，加入 KDA、分页历史缓存、压缩分页注意力、NUMA 解码流水及思考和工具调用协议
- 新增 GLM-5 DSA 模型支持，并支持 GLM-5.2 量化 KV-B 的纯 CPU 推理
- 支持 Laguna 模型及长上下文缓存，完善 CUDA Graph、八卡张量并行、混合 MoE、NVFP4 和 INT4_GROUP32 推理
- 新增 HY-V3 模型及工具调用解析，并支持 Poolside V1 工具调用协议
- 为 Qwen3.8/Qwen3.5 架构接入 DFlash2 投机解码，支持独立 draft checkpoint、rejection sampling、批量解码、双卡张量并行、长上下文、前缀快照和紧凑 INT4 draft 权重；双卡等权 CUDA 部署会自动启用 DFlash2 backbone TP
- 为 DeepSeek-V4 接入模型内置 DSpark，并为 Kimi-K3 支持外部 DSpark draft 模型；统一 DSpark/DFlash2 草稿参数，新增 `--dspark`、`--draft` / `--draft_model_path` 和 `--draft_tokens` 简写，同时保留 `--speculative_*` 完整参数
- 继续优化 Qwen3.5 MTP，支持 MoE MTP，改善批量状态备份、八 token 验证快照、并发调度和长 KV 验证性能

### 性能与后端

- 系统优化 Qwen3.5 的路由、FP8 MoE、RMSNorm、GDN、预填充和解码融合；符合条件的 CUDA 张量并行部署会自动启用 CUDA Graph、GPU Token Handoff 和 CUDA Embedding 快速路径
- 将 Qwen3.5 默认分块 Prefill 大小调整为 2048
- 深化 DeepSeek-V4 CUDA、CPU 和 NUMA 推理，加入多卡完整图执行、SM120 稀疏 MLA、路由、MoE 与 WoA 加速，并优化压缩缓存和混合 NUMA MoE 流水
- 优化 Dots3 稀疏预填充、FP8 索引器和长 KV 搬运，优化 Kimi-K3 KDA/MLA 缓存与分块预填充，以及 GLM-5.3-Flash NUMA 解码流水
- 新增 CUDA Q2_K GGUF、AWQ MoE Marlin、NVFP4 Marlin 张量并行、SM75+ FP8 dense Marlin、SM89 FP8 CUTLASS 自动择优及紧凑 NVFP4 CUDA/NUMA 回退
- 支持 compressed-tensors `pack-quantized` 非对称 INT4 checkpoint，统一转换为可移植的 `INT4_GROUP` 权重，覆盖 CPU、CUDA 与多卡 TP，并补齐 TFACC 元数据兼容
- 优化 INT4_GROUP、FP8、NVFP4、MXFP4、Q8_0/Q8_K 等量化格式在 CUDA、CPU、NUMA 和多卡场景下的加载、计算及显存使用
- 优化自定义 AllReduce 自动选择、多卡图内通信、无 P2P 跨卡回退、CUDA Graph 显存复用及分页缓存调度
- 优化分页注意力预填充和 SM70/SM75 D256 GQA 解码，支持磁盘流式执行 Linear 和 Embedding，并将 FlashInfer 更新至 `v0.6.16.post1`

### 服务与接口

- 新增 `--max_context_length` / `--max-context-length`，限制单会话输入与输出总长度；`/v1/models` 同时返回模型上下文、实际上下文、KV Cache 容量和配置上限
- 新增 `--startup-progress ndjson`，通过标准错误输出模型加载、权重读取、预热和服务就绪进度
- OpenAI Chat Completions、Responses API 和 Anthropic API 使用运行时原生 token 统计，返回缓存命中、未命中输入 token 和实际输出 token
- 为 Qwen3.5、Qwen4-Exp、Kimi-K3、Dots3 和 GLM-5.3-Flash 完善 reasoning effort，并正确拆分流式和非流式 `reasoning_content`
- 新增或完善 GLM、HY-V3、Kimi-K3、Dots3、Qwen4-Exp 和 Poolside V1 工具调用解析，改进 `tool_choice=required`、参数约束和跨分片流式解析
- 改进 Fastllm Studio 的模型部署、启动参数、运行状态、模型选择和本地聊天界面

### 稳定性与兼容性

- 修复流式请求句柄复用竞态、提前终止后的资源释放、停止词约束、最小输出长度和缓存用量统计问题
- 修复前缀缓存恢复、低 KV 预算调度、CUDA Graph KV 定容、图捕获期间通信、显存池复用和多卡量化权重拆分问题
- 修复 DeepSeek-V4 多卡缓存恢复、纯 CPU/非 CUDA 构建、NUMA 退出卡死和双卡串行解码，以及 Qwen3.5 GGUF 合并、多卡采样和服务预热显存估算问题
- 完善 SM60/Pascal 的纯旧架构 CUDA 构建、原生分页注意力预填充和 AWQ Marlin 兼容回退，并适配不同 CCCL 版本的采样算子接口
- 修复 AWQ、GGUF MoE、FP8 Marlin/CUTLASS、NUMA FP8 MoE、Q8_K 和 YaRN 等量化精度或跨平台兼容性问题
- 补充新模型、量化算子、CUDA Graph、MoE、多卡、工具调用和 API 回归测试，以及 logits 对齐、NCCL、HLE 和性能分析工具

## V0.1.7.1

- 完善Qwen3.5 MTP解码，支持多卡、批量和非贪婪采样，支持FP8草稿头
- 优化Qwen3.5 MTP验证转置和单请求草稿调度，修复状态提交、批量全接受、长输入、前缀缓存命中及显存溢出问题
- 优化Qwen3.5 MoE和TP2归约及CUDA Graph资源使用，修复TP多模态前向和非CUDA编译问题
- 新增FP8 Linear加速路径并内置所需头文件，优化FP8算子变体选择、输入量化缩放、权重缓存及小批量回退
- 优化Qwen3 FP8 MLP融合路径
- 完善auto模式下的FP8权重识别、NVFP4权重加载及多卡切分，优化NVFP4单Token GEMV，修复FP8多卡缩放因子切分
- 支持OpenAI Responses API
- 完善工具调用解析和流式、非流式校验，支持tool_choice、strict参数校验以及工具名和参数名生成约束
- 修复多种工具流解析状态及聊天结束原因，补充DeepSeek V4等工具调用回归和手动测试
- 修复think模式下KV Cache复用失败，以及CPU Qwen3数据类型、重复输出和乱码问题
- 修复分页缓存越界写入，调整分页缓存调度容量和预热采样页预算
- 修复CUDA Graph错误熔断、MultiCUDA大权重偏移溢出和CUDA多卡内存释放死锁，完善CUDA执行同步及多Token状态快照
- 补充CUDA多卡、状态快照和算子数值回归测试，以及FP8算子基准和算子开发支持

## V0.1.7.0

- 新增终端部署向导和模型向导（ftllm即可执行）
- 优化张量并行
- 初步支持Qwen3.5 MTP解码
- 支持Qwen3.5 MoE路径，修复Qwen3MoE异构TP及混合MoE设备路径
- 修复Qwen3.5前缀缓存、空注意力分片和非CUDA编译问题
- 支持Step3.7文本模型、视觉前向和多模态预处理
- 初步支持CUDA Graph
- 支持MoE混合推理的层数
- 优化多卡按层切分平衡，修复多卡分页缓存分配和分页调度饿死问题
- 支持SM60, SM70
- 修复Qwen工具调用解析、cuBLAS句柄表并发竞争、CUDA分页注意力崩溃、分页缓存元数据及页数统计等问题
- 修复logits结果生命周期和潜在内存泄漏问题
- 更新混合推理文档

## V0.1.6.4

- 优化DeepSeek V4解码速度
- 优化DeepSeek V4预填重速度
- 优化DeepSeek V4并发速度
- 优化DeepSeek V4 NVFP4算子的速度
- DeepSeek V4模型支持多卡串行
- 减少NVFP4模型的内存占用
- 优化CUDA MOE速度
- 支持多CUDA, NUMA, Disk一起混合推理
- 支持Step3.5模型，补充Step3.5运行时和服务适配，优化Step3.5 MoE拆分与并发解码
- 优化Qwen3.5批量解码吞吐

## V0.1.6.3

- 修复transformers 5.8.0升级后不兼容的bug
- 增加`--cuda_slab`参数，可减少大量CUDA模型权重小块分配造成的显存碎片

## V0.1.6.2

- 支持磁盘MoE推理，补充磁盘设备后端和SSD读取速度测试工具
- 优化GGUF磁盘MoE推理，修复磁盘MoE及FP8磁盘MoE加载问题
- 支持DeepSeek V4思考输出, 可以使用参数--enable_thinking控制
- 优化DeepSeek V4显存缓存拼接，修复解码缓存内存增长问题
- 限制DeepSeek V4稀疏预填充临时显存，降低长上下文预填充显存峰值
- 调整DeepSeek V4默认采样参数，默认使用top_k = 5

## V0.1.6.1

- 更新DeepSeek V4量化配置，补充Q2、Q4、UD-Q2_K_L、UD-Q2_K_M量化示例
- 支持CUDA NVFP4线性计算
- 支持CUDA Q2_K_R4反量化
- 优化DeepSeek V4 compressedKV CUDA缓存，提升稀疏解码性能

## V0.1.6.0

- 支持DeepSeek V4模型，补充DeepSeek V4的分词和chat_template处理
- 修复DeepSeek V4缺少chat_template时加载出错的问题
- 修复DeepSeek V4工具调用解析的问题
- 支持Qwen3.5多模态推理，补充Qwen3.5多模态Python接口
- 支持Qwen3.5 MoE模型识别和默认参数设置
- 初步支持Gemma4多模态推理
- 支持Minimax-M2模型，补充Minimax-M2工具调用解析
- 初步支持Anthropic API兼容接口，新增`/v1/messages`接口
- 修复Anthropic API工具调用的问题
- OpenAI兼容API支持图文输入，支持http、data url和file url图片
- 修复多模态推理资源管理的问题
- 支持参数`--kv_cache_dtype`设置KV缓存类型
- 增加`--chunked_prefill_size`参数设置分块prefill大小
- 增加`--tokens`、`--page_size`、`--gpu_mem_ratio`等参数，方便控制长上下文和缓存
- 增加`--moe_atype`参数，可指定MOE层激活类型
- 增加`--cuda_se`参数，可控制共享专家是否使用CUDA执行
- 支持`cudapp=N`、`cudapp=1:2:3`形式简写多卡串行执行参数
- API server支持读取模型默认采样参数
- API server支持通过命令行覆盖temperature、top_p、top_k、repeat_penalty等采样参数
- 修复API server中enable_thinking和think参数透传的问题
- API server启动时会尝试提高ulimit，减少请求较多时文件描述符不足的问题
- 新增`ftllm.env`模块，可读取当前wheel的编译信息
- 优化Python包版本读取，支持从`ftllm`、`ftllm-nightly`、`ftllm-rocm`读取版本号
- 优化CUDA wheel依赖库加载，支持从pip安装的nvidia依赖中查找CUDA、cuBLAS、NCCL库
- CUDA wheel补充NCCL依赖和SM80预编译架构
- 修复Python历史缓存的问题
- 修复读取GGUF模型时未加载generation_config的问题
- 修复CPU embedding、tie weight等模型加载问题
- 修复tool parser在tokenizer缺失时可能出错的问题
- 修复nightly打包元数据和Python包编译脚本相关问题

## V0.1.5.1

- 初步支持AMX加速，在支持AMX的机器上通过参数--amx true来开启
- 修复纯CPU执行Next模型出错的bug
- 修复部分没有AVX512的机器执行出错的bug
- Numa分配出错时输出提示信息和解决命令
- 修复Cuda计算长Attention的错误 (pr from loveheart)
- 修复Arm上的编译

## V0.1.5.0

- 提高moe_device为numa时的prefill速度
- moe_device为numa时，可以不设置环境变量，通过`-t`参数设置总的线程数
- 支持参数`--moe_dtype bfloat16`指定moe类型为bf16, 在一些情况下可以提高速度
- 支持GLM系列的FP8模型，修复GLM模型长上下文的bug
- 支持DeepSeek V3.2模型
- 新增项目引用文件说明，详见[参考代码和文章](../README.md#参考代码和文章)

## V0.1.4.9

- 修复Qwen3-Next长上下文中出现的bug

## V0.1.4.8

- 增加 通用动态量化（Universal Dynamic Quantization）功能，参考[动态量化指南](dtype_config.md)
- 修复Qwen3-Next缓存错误的Bug
- 提升Qwen3-Next混合推理速度

## V0.1.4.7

- 初步适配Qwen3-Next模型

## V0.1.4.6

- 修复GLM4.5出错的一些bug
- 支持一些GGUF类型

## V0.1.4.5

- 部分模型支持工具调用 [查看详情](../README.md#工具调用)
- 支持GLM4.5, GLM4.5-AIR
- 修复1.4.3版本部分模型缓存失效的bug
- DSV3.1支持使用enable_thinking打开思考模式
- Prefill时会发空包维持客户端连接
- 修复部分GGUF-q8模型计算出错的bug
- 修复部分fp16模型numa计算出错的bug

## V0.1.4.3

- 修复一些情况下前端退出后后端继续推理的问题
- 修复一些情况下crtl + c无法退出的问题
- 修复numa模式下推理GGUF出现的一些计算错误
- 增加GGUF中q8_0类型支持

## V0.1.4.2

- 修复AVX512模式下出错的问题

## V0.1.4.1

- 修复一些可能的glibc出错问题

## V0.1.4.0

- 适配部分GGUF模型
- 混合推理提升Prefill速度
- c++中增加了一些分词器
- 修复位置编码的一些bug
- 修改重复惩罚因子，当last_n为0时，重复惩罚与Transfromers一致
- 修复download可能出错的一些bug

## V0.1.3.4

- 修复Kimi-K2模型分词出现的一些错误

## V0.1.3.3

- 支持Kimi-K2模型
- 修复8numa时的一些bug
- 加速CPU上的AWQ计算

## V0.1.3.2

- 修复上个版本一些模型无法读取的bug

## V0.1.3.1

- 支持Hunyuan模型 （混元）
- 支持Ernie_4.5模型 （文心）
- 支持PanguPro模型 （盘古）
- 支持Minimax 01, Minimax M1模型
- 优化旧GPU上的一些计算性能
- 在dev_mode下支持主动终止请求 [查看详情](https://github.com/ztxz16/fastllm/pull/535)
- 修复numa模式下无法计算float16的问题
- 修复numa个数过多时可能出现的一些错误

## V0.1.3.0

- DeepSeek模型中，默认改用cuda执行共享专家，可通过参数`--cuda_se false`来关闭
- 支持使用类似`--device "{'cuda:0':1,'cuda:1':1}"`的命令来串行执行，参考[如何设定运行设备](../README.md#3-如何设定运行设备)
- 增加动态量化功能，参考[动态量化指南](dtype_config.md)
- 修复一些超长prompt可能引起的出错
- 多卡张量并行时将平分显存，目前测试阶段，仅在llama系模型（如Qwen2, Qwen2.5, QwQ等）生效
- 所有moe模型支持`--moe_expert`参数 （之前的版本中，qwen3-moe此参数不会生效）


## V0.1.2.0

- 规范版本号 a.b.c.d
- a为保留位，目前为0
- b为大版本号
- c为小版本号
- d为bug修复版本的编号

## V0.0.1.2

- 优化了numa加速
- 略微提升了prefill和decode速度
- 支持了moe的混合张量并行，参考[混合推理指南](mixforward.md)
- 修复了multicuda的一些bug，支持了所有精度的混合张量并行
- 修复了C++下Jinja模板的一些bug，支持Qwen3, DS等一系列模型的内置分词器

## V0.0.1.1

- 支持了 `FP8_E4M3` 精度（新老硬件均可）
- MOE模型支持用`--moe_dtype`来设置混合精度
- 可以在`ROCM`环境下使用`pip`安装了
- 修复了C++下Jinja模板的一些bug
- api server的默认输出token数由8K提升到32K

## V0.0.1.0

- 支持了千问3模型 [部署指南](qwen3.md)
- 优化了DeepSeek模型的显存使用
- 增加参数`--cache_fast`来指定是否使用显存缓存

## V0.0.0.9

- 优化了使用DeepSeek模型时的多轮对话缓存
- 略微提升了DeepSeek模型的多并发速度
- 减少了DeepSeek模型Prefill时的显存消耗，可以支持更长的上下文
- 支持了DeepSeek模型的INT8量化 （使用原始模型时`--dtype int8`，或者导出时`--dtype int8`）
- 隐藏了 "None of PyTorch, TensorFlow >= 2.0 ..." 的警告信息
- 增加了`--cache_dir`参数来指定缓存目录
- server增加了`--hide_input`参数来隐藏日志中的请求信息
- webui增加了`--max_token`参数来指定最大输出，--think参数来强制思考

## V0.0.0.8

- api server增加api_key参数，来设定api_key
- api server支持了一些复合输入
- 提升了moe模型prefill的速度
- 增加了--version参数查看版本号

## V0.0.0.7

- 增加config选项，可通过config.json文件来启动模型
- 提升moe模型的速度

## V0.0.0.6

- 降低GLIBC版本，PIP安装包兼容更多系统
- PIP安装包支持更多架构（目前最低支持到SM_52）

## V0.0.0.5

- 修改文档，增加了一些pip安装后无法使用的情况说明
- 聊天模式下自动读取模型的生成配置文件
- 修复一些情况下kv_cache_limit计算错误的问题

## V0.0.0.4

- 增加ftllm run, chat, webui, server接口

---

### Version En (`docs/version_en.md`)

# Changelog

[中文](version.md)

## Unreleased

- Added DeepSeek-V4.1-Flash support with text and image inputs, Engram, DSpark speculative decoding, tensor parallelism, and CPU/CUDA hybrid inference. Improved numerical accuracy, prefix caching, and tool calling.
- Improved Qwen4-Exp / Qwen3.8-Flash-Next tensor-parallel decoding, MTP verification, and rollback, with optimized long-context QSA selection, PLE transfers, vocabulary sharding, and CUDA Graph reuse.
- Optimized Qwen3.5/3.8 MTP with tensor-parallel draft layers, NVFP4 draft quantization, and vocabulary shortlists. Improved batched drafting and verification, and fixed sampling distributions, causal boundaries, and cache rollback.
- Optimized DFlash2 draft attention, candidate scoring, and cache writes. Improved single- and multi-GPU graphs, workspace reuse, and memory budgeting, and fixed verification graphs and repeated prefill at capacity boundaries.
- Optimized FP8/NVFP4 prefill and small-batch decoding across GPU architectures. Expanded GDN, Attention, RMSNorm, and projection fusion, and added native low-bit prefill paths with eligibility checks and fallbacks.
- Optimized CPU/NUMA low-bit compute, grouped expert scheduling, and compute/transfer overlap. Improved GGUF IQ2/IQ3 decoding and small-batch compute, and fixed Qwen3.5 GGUF single-step decode crashes and x86 instruction-set compatibility issues.
- Added multilevel caching for disk-backed experts. Expanded tensor-parallel expert caching and batched verification for Qwen4 and DeepSeek-V4.1, added per-layer expert placement, reduced peak loading memory, and improved temporary workspace reuse and reclamation.
- Improved multimodal inference for the Qwen3.5 architecture with vision-encoder offloading and uneven tensor parallelism. Fixed multi-image feature merging, tool-result images, and tool-call history; isolated text prefix caching for image requests and improved prefill memory usage.
- Improved reasoning-parameter compatibility, streaming tool-call parsing, and finish states in the OpenAI and Anthropic APIs. Fixed Responses history splitting, finish reasons for unlimited generation, and streaming disconnect cleanup, and improved context-limit errors.
- Added general JSON structured output through Chat Completions `response_format` and Responses `text.format`, with prompt guidance and completion-time validation for JSON objects and JSON Schema, plus explicit format errors.
- Integrated OpenCode, Codex, and Claude Code into Launcher with independent installation, session management, and tool approvals. Improved DeepSeek Harness and Pi runtime integration, output budgets, and completion detection.
- Reworked Launcher into a plugin-based interface with natural-language customization, theme switching, and dark mode. Improved Markdown and isolated HTML previews, Agent runtime packaging, portable-package double-click entry points, and the offline parameter manual.
- Added NCCL startup communication checks and timeout handling. Fixed multi-GPU reduction synchronization, cross-GPU deadlocks, Marlin out-of-bounds accesses, CUDA Graph buffer lifetimes and shard-address reuse, and small-batch RMSNorm reduction rounding differences.

## V0.1.8.2

- Added a web deployment launcher and an Electron desktop launcher with model downloads, launch profile management, automatic configuration for long contexts and high concurrency, and inference speed and available context displays.
- Reworked Studio with concurrent chat sessions, generation cancellation, attachments, and history. Integrated the Pi directory agent with web search, document Q&A, data analysis, code processing, and PowerPoint generation.
- Improved Qwen3.8-Flash-Next multimodal inference, MTP, and prefix caching. Added FP8/NVFP4 CUDA expert caching, dynamic hybrid scheduling, and NUMA weight sharing to improve hybrid inference speed and memory usage.
- Added GLM-5.3 MTP speculative decoding and MTP support for DeepSeek-V4 hybrid inference.
- Improved self-describing GGUF loading, with embedded and external MTP weights and multimodal projectors for Qwen3.5-architecture GGUF models. Fixed low-bit quantization compatibility issues and reduced temporary GPU memory usage through chunked dequantization.
- Added the `--low_gpu_mem` mode and FP4 KV Cache for the Qwen3.5 architecture. Reduced memory usage during tensor-parallel weight loading, optimized high-resolution vision inference, and added image feature caching.
- Added `--rope_scaling` with static YaRN context extension for HF Qwen2, Qwen3, and Qwen3.5 families, along with context capacity checks at startup.
- Optimized FP8 Linear, NVFP4, GDN, and paged attention across GPU architectures to improve prefill, decode, and small-batch inference performance.
- Fixed hangs with odd GPU counts in tensor parallelism, deadlocks during multi-GPU quantized-weight repacking, GPU memory leaks in MTP verification graphs, and out-of-bounds accesses in two-GPU multimodal inference. Improved streaming tool-call parsing and argument validation.
- Added native SM86 code to wheels and Triton as a default Linux dependency. Improved portable package dependencies, command-line entry points, and usage instructions for deployment without a desktop environment.

## V0.1.8.1

- Improved Qwen3.8-Flash-Next with MTP speculative decoding, cross-request prefix caching, and faster CUDA/NUMA hybrid inference.
- Improved Qwen3.5 GGUF, DFlash2, and two-GPU inference, and fixed related CUDA Graph issues.
- Improved build and packaging for CUDA 12.9, multi-architecture wheels, the Triton server script, and Linux portable packages.

## V0.1.8.0

### New models and inference capabilities

- Added text-generation support for Qwen4-Exp / Qwen3.8-Flash-Next FP8, including four-stream hyper-connections, Gated DeltaNet, QSA sparse attention, PLE ngrams, and FP8 MoE. Vision and MTP weights are not loaded by the current text decoder.
- Improved Qwen4 CPU, CUDA, and NUMA hybrid inference, long-context and prefix caching, fused prefill/decode operators, and CUDA Graph execution. Added `--ngram_device disk` to read the large PLE table from disk on demand and reduce host-memory usage.
- Added Kimi-K3 support with CUDA, NUMA, CPU/GPU expert parallelism, disk-backed experts, chunked KDA/MLA prefill, DSpark, XTML serving, and tool calling.
- Added Dots3-Note support with DSA indexing, sparse attention, long-context caching, chunked prefill, reasoning output, and tool calling.
- Added GLM-5.3-Flash support with KDA, paged history caches, compressed paged attention, NUMA decode pipelining, reasoning output, and tool calling.
- Added GLM-5 DSA support and pure-CPU inference for GLM-5.2 checkpoints with quantized KV-B weights.
- Added Laguna support and long-context caching, with improved CUDA Graph execution, eight-GPU tensor parallelism, hybrid MoE, NVFP4, and INT4_GROUP32 inference.
- Added HY-V3 model and tool-call parsing support, together with the Poolside V1 tool-call protocol.
- Added DFlash2 speculative decoding for Qwen3.8/Qwen3.5 architectures, including separate draft checkpoints, rejection sampling, batched decoding, two-GPU tensor parallelism, long-context execution, prefix snapshots, and compact INT4 draft weights. Equal-weight two-GPU CUDA deployments now automatically enable DFlash2 backbone tensor parallelism.
- Added embedded DSpark for DeepSeek-V4 and external DSpark draft models for Kimi-K3. Unified DSpark/DFlash2 draft options with the new `--dspark`, `--draft` / `--draft_model_path`, and `--draft_tokens` shorthand while retaining the full `--speculative_*` options.
- Further optimized Qwen3.5 MTP, adding MoE MTP support and improving batched state backup, eight-token verification snapshots, concurrent scheduling, and long-KV verification.

### Performance and backends

- Optimized Qwen3.5 routing, FP8 MoE, RMSNorm, GDN, prefill, and decode fusion. Eligible CUDA tensor-parallel deployments now automatically enable CUDA Graph, GPU Token Handoff, and CUDA Embedding fast paths.
- Changed the default Qwen3.5 chunked-prefill size to 2048.
- Expanded DeepSeek-V4 CUDA, CPU, and NUMA inference with full-step multi-GPU graphs, SM120 sparse MLA, routing, MoE and WoA acceleration, compressed-cache improvements, and mixed NUMA MoE pipelining.
- Optimized Dots3 sparse prefill, FP8 indexing, and long-KV movement; Kimi-K3 KDA/MLA caching and chunked prefill; and the GLM-5.3-Flash NUMA decode pipeline.
- Added CUDA Q2_K GGUF, AWQ MoE Marlin, tensor-parallel NVFP4 Marlin, SM75+ FP8 dense Marlin, automatic SM89 FP8 CUTLASS selection, and compact NVFP4 CUDA/NUMA fallbacks.
- Added compressed-tensors asymmetric INT4 `pack-quantized` checkpoint support, converting weights into portable `INT4_GROUP` tensors for CPU, CUDA, and multi-GPU tensor parallelism, with compatible TFACC metadata handling.
- Improved INT4_GROUP, FP8, NVFP4, MXFP4, Q8_0, and Q8_K loading, compute performance, and memory usage across CUDA, CPU, NUMA, and multi-GPU execution.
- Improved automatic custom AllReduce selection, in-graph multi-GPU communication, no-P2P copy fallbacks, CUDA Graph memory reuse, and paged-cache scheduling.
- Optimized paged-attention prefill and SM70/SM75 D256 GQA decode, added disk-streamed Linear and Embedding execution, and updated FlashInfer to `v0.6.16.post1`.

### Serving and APIs

- Added `--max_context_length` / `--max-context-length` to limit combined per-session input and output length. `/v1/models` now reports the model limit, effective context window, KV-cache capacity, and configured limit.
- Added `--startup-progress ndjson` to emit model initialization, weight loading, warmup, and server-ready progress events on standard error.
- OpenAI Chat Completions, Responses API, and Anthropic API now use native runtime token accounting and report cached input, uncached input, and actual output token counts.
- Added or improved reasoning-effort handling for Qwen3.5, Qwen4-Exp, Kimi-K3, Dots3, and GLM-5.3-Flash, including correct streaming and non-streaming `reasoning_content` separation.
- Added or improved tool-call parsing for GLM, HY-V3, Kimi-K3, Dots3, Qwen4-Exp, and Poolside V1, including better `tool_choice=required`, argument constraints, and fragmented streaming output handling.
- Improved Fastllm Studio model deployment, launch configuration, runtime status, model selection, and local chat workflows.

### Stability and compatibility

- Fixed streaming request-handle reuse races, cleanup after early termination, stop constraints, minimum output length, and cached-token usage accounting.
- Fixed prefix-cache restoration, low-KV-budget scheduling, CUDA Graph KV sizing, communication during graph capture, memory-pool reuse, and multi-GPU quantized-weight partitioning.
- Fixed DeepSeek-V4 multi-GPU cache restoration, pure-CPU and non-CUDA builds, NUMA shutdown hangs, and two-GPU serial decode, as well as Qwen3.5 GGUF merging, multi-GPU sampling, and warmup memory estimation.
- Improved SM60/Pascal compatibility for legacy-only CUDA builds, native paged-attention prefill, and AWQ Marlin fallbacks, and adapted sampling operators to multiple CCCL interfaces.
- Fixed quantization accuracy and cross-platform compatibility issues affecting AWQ, GGUF MoE, FP8 Marlin/CUTLASS, NUMA FP8 MoE, Q8_K, and YaRN.
- Added regression coverage for new models, quantized operators, CUDA Graph, MoE, multi-GPU execution, tool calling, and APIs, together with logits-alignment, NCCL, HLE, and performance-analysis tools.

For release notes before V0.1.8.0, see the [Chinese changelog](version.md).

---

### Wheel Build (`docs/wheel_build.md`)

# Linux x86_64 wheel 构建

发布环境采用 Ubuntu 20.04 / glibc 2.31、GCC 11、CUDA 12.9 和 Binutils 2.38，
目标是兼容 Ubuntu 20.04/22.04。运行 wheel 需要 Python >= 3.10；构建 Python 可用 3.8。

在仓库根目录执行：

```bash
docker build -f whl_docker/Dockerfile --build-arg BUILD_JOBS=32 -t fastllm-wheel:cu129-u20 .
docker run --rm -v "$PWD:/src" -w /src fastllm-wheel:cu129-u20 \
  bash make_whl.sh --cpu-jobs 32 --cuda-jobs 12 --nvcc-threads 2
```

编译不需要 GPU。默认产物仍在 `build-fastllm/tools/dist`，兼容现有桌面版和 portable
打包脚本。CPU/CUDA 中间文件分别在 `build-wheel/cpu` 和 `build-wheel/cuda`，
日志为 `build-wheel/build.log`。可用 `--output-dir` 指定产物目录，
用新的 `--build-dir` 做干净构建；默认复用已有构建目录。

大内存、多核机器可使用 `--cpu-jobs 96 --cuda-jobs 24 --nvcc-threads 4 --split-compile 2`。
NVCC 内部线程会叠加在 CUDA 并行任务上，普通机器应降低线程数。
`--nightly` 生成 nightly 包；完整选项见 `bash make_whl.sh --help`。

没有 Docker 权限时，可在具备 `CAP_SYS_CHROOT` 的机器上准备 Ubuntu 20.04 rootfs，
安装相同工具链并在其中执行脚本。编译、链接和 libnuma/NCCL 都必须使用兼容的
旧系统依赖；仅更换 GCC 不会降低宿主机 glibc 要求。

- 默认 `--max-glibc 2.31`，提前拒绝更新的构建环境，打包时再次审计 ELF 导入符号。
- 使用 `linux_x86_64` 标签；glibc 检查不等同于 manylinux 认证。
- 默认保留全架构和 PTX，以 CUDA `size` 压缩并 strip 暂存的动态库。
  该压缩格式要求 CUDA 12.4 发布时期或更新的驱动，特定 GPU/PTX 可能要求更高版本。
  `--compression legacy` 可使用原压缩方式；普通 CMake 构建仍默认 legacy。
- CPU/CUDA 库保留 `$ORIGIN` 搜索路径并附带 libnuma，CUDA 库由 pip 依赖和驱动提供。
- 每次打包读取当前 Python 源码，避免增量构建遗漏 Python 修改。
  wheel 同目录的 JSON 记录 ELF 依赖、glibc 要求、大小和 SHA256，阶段耗时显示在脚本输出中。

在 Python 3.10/3.11 虚拟环境中安装：

```bash
python -m pip install -c tools/wheel/runtime-cu129.txt build-fastllm/tools/dist/ftllm-0.1.8.2-py3-none-linux_x86_64.whl
python -c 'from ftllm import llm; print(llm.get_cpu_threads())'
```

发布前验证目标系统的依赖安装、CPU/CUDA 加载、GPU 运算和 CUDA Graph。
包含某个架构的二进制不代表已经在对应物理 GPU 上验证。

---

### Benchmarks Deepseek V4 (`docs/benchmarks/deepseek_v4.md`)

# DeepSeek-V4 Sparse Attention Benchmark

[English](deepseek_v4_en.md) · [Benchmark 索引](../benchmark.md) · [完整稀疏注意力分析](../deepseek_v4_sparse_attention.md) · [部署指南](../deepseek.md)

## 测试配置

- 模型：DeepSeek-V4 / DeepSeek-V4-Flash。
- 并行：TP8。
- 测试 CUDA 架构：SM120；计算路径支持 SM120/SM121。
- 输入/输出：128 / 256 token。
- 固定开启 CUDA Graph 和自定义 AllReduce。
- 原始报告没有记录 GPU 具体 SKU、显存和互联，因此不能将结果标注为某一款消费卡或数据中心卡。
- 未记录测量日期与源码版本；这些数据不能视为当前 HEAD 的性能。

## 推荐 TP8 启动命令

~~~bash
FASTLLM_CUDA_GRAPH=1 \
FASTLLM_CUDA_CUSTOM_ALLREDUCE=1 \
ftllm server /data/models/deepseek-v4 \
  --model_name deepseek-v4 \
  --tp 8 --triton \
  --max_batch 1
~~~

`--triton` 会检测当前 Python 环境中的 Triton；不可用时回退到内置 CUDA。SM120 稀疏路径还可以通过 `FASTLLM_CUDA_TRITON_DSV4_SPARSE_SM120` 单独控制。

## 实测结果

| 指标 | 实测值 |
| --- | ---: |
| TTFT | 405.47 ms |
| TPOP | 10.01 ms/token |
| Decode throughput | 99.93 token/s |
| Total throughput | 86.57 token/s |
| Total time | 2.9572 s |

CUDA Graph trace 中，SM120 split + merge 稀疏注意力每层平均为 5.796 µs。这是短上下文测量，不能外推到长上下文、其他硬件或混合专家放置。

算子接口、数值限制和回退行为见[稀疏注意力说明](../deepseek_v4_sparse_attention.md)。

---

### Benchmarks Deepseek V41 (`docs/benchmarks/deepseek_v41.md`)

# DeepSeek-V4.1-Flash Benchmark

[Benchmark 索引](../benchmark.md) · [部署与验证](../deepseek_v41.md)

以下为 2026-10-02 记录的最终配置结果；量化格式、设备放置和采样口径分别注明，不能混合解释成同一配置的性能。

## Q2_K/Q4_K NUMA 专家 prefill

RTX 4090 + RTX 4090 D 按层 1:1 串行，单 NUMA、28 线程、每卡 8 GiB 专家缓存、chunk 4096、fastprefill 开启、FP16 加载、BF16 KV、专家和 Engram 常驻主存。输入 8600 token、输出 1 token，关闭推测解码和前缀复用，预热后三次正式请求。

| 指标 | 结果 |
| --- | ---: |
| TTFT 中位数 | 5.893 秒 |
| 输入 token / TTFT | 1459.5 token/s |
| CPU RSS 峰值 | 425.06 GiB |
| GPU0 / GPU1 显存峰值 | 23.69 / 23.01 GiB |

被测原生库 SHA256：`438a2a404ac7459eb9557edb873f15be74f2775b7cd0547f4b3f842645fa8b6c`。该配置完成 grouped MMQ，无回退。长文本三编码检索正确；数值回归与内存检查通过，不能代替完整困惑度评测。

## Q2_K/Q4_K 磁盘专家

单 RTX 4090、单 NUMA、28 线程、64 GiB CPU 专家缓存、Engram 磁盘、chunk 4096、fastprefill 开启、FP16 加载、BF16 KV。41-token 代码输入、192-token 输出，history/prefix cache 关闭。两种模式均加载草稿权重，分别预热后各测三次，取中位数。

| 模式 | Decode token/s | 说明 |
| --- | ---: | --- |
| 普通解码 | 5.72 | 该任务推荐配置 |
| DSpark 5，阈值 0.5 | 5.44 | 接受率 61.8%，TTFT 10.81 秒 |

DSpark 正式请求峰值 RSS 为 70.93 GiB，活跃专家缓存不超过 64 GiB。每请求物理 SSD 读取约 117.98 GiB，普通解码约 86.02 GiB；高接受率仍可能因专家读取成本而无法提高吞吐。
192 个输出 token 的一致性、缓存容量及独立算子参考检查通过；该测量未覆盖长上下文或完整模型质量。按此任务部署可使用 `--dspark 0 --mtp 0`。

磁盘配置被测原生库 SHA256：`b141c7fdc951aa1c02d80071b80bbf6b60437992f59e5a963c17a40f960c2a98`。

---

### Benchmarks Deepseek V4 En (`docs/benchmarks/deepseek_v4_en.md`)

# DeepSeek-V4 Sparse Attention Benchmark

[中文](deepseek_v4.md) · [Benchmark index](../benchmark_en.md) · [Deployment guide](../deepseek_en.md)

## Test configuration

- Model: DeepSeek-V4 / DeepSeek-V4-Flash.
- Parallelism: TP8.
- Tested CUDA architecture: SM120; the kernel path supports SM120/SM121.
- Input/output: 128 / 256 tokens.
- CUDA Graph and custom AllReduce were enabled.
- The source report did not record the exact GPU SKU, memory, or interconnect, so the results must not be attributed to a specific GPU model.
- The measurement date and source revision were not recorded; these results do not establish current HEAD performance.

## Recommended TP8 command

~~~bash
FASTLLM_CUDA_GRAPH=1 \
FASTLLM_CUDA_CUSTOM_ALLREDUCE=1 \
ftllm server /data/models/deepseek-v4 \
  --model_name deepseek-v4 \
  --tp 8 --triton \
  --max_batch 1
~~~

`--triton` uses Triton when it is available in the active Python environment and otherwise falls back to the built-in CUDA path.

## Results

| Metric | Measured value |
| --- | ---: |
| TTFT | 405.47 ms |
| TPOP | 10.01 ms/token |
| Decode throughput | 99.93 token/s |
| Total throughput | 86.57 token/s |
| Total time | 2.9572 s |

SM120 split + merge sparse attention averaged 5.796 µs per layer in the CUDA Graph trace. These measurements cover short context only; they do not establish performance for long context, other hardware or hybrid expert placement.

Kernel contracts, numerical limits and fallback behavior are documented in the [sparse attention guide](../deepseek_v4_sparse_attention.md).

---

### Benchmarks Dots3 Note (`docs/benchmarks/dots3_note.md`)

# Dots3-Note Benchmark

[English](dots3_note_en.md) · [Benchmark 索引](../benchmark.md) · [部署指南](../dots3_note.md)

仓库目前记录了算子、长缓存和 API 回归，但没有完整的 Dots3-Note 设备吞吐数据。下列配置是建议的复测起点。

| 设备布局 | 建议启动命令 | 速度 |
| --- | --- | --- |
| GPU + NUMA MoE | `ftllm server /data/models/dots3-note --device cuda --moe_device numa --chunked_prefill_size 8192` | 待实测 |
| GPU + CPU MoE | `ftllm server /data/models/dots3-note --device cuda --moe_device cpu --chunked_prefill_size 8192` | 待实测 |
| 长上下文 | `ftllm server /data/models/dots3-note --device cuda --moe_device numa --max_context_length 131072 --chunked_prefill_size 8192` | 待实测 |

长上下文测试应分别记录不同输入长度的 TTFT、Prefill token/s、显存占用和持续 Decode，避免只报告短输入结果。测试脚本说明见 [Benchmark 工具](../../test/benchmark/README.md)。

---

### Benchmarks Dots3 Note En (`docs/benchmarks/dots3_note_en.md`)

# Dots3-Note Benchmark

[中文](dots3_note.md) · [Benchmark index](../benchmark_en.md) · [Deployment guide](../dots3_note_en.md)

Operator, long-cache, and API regressions exist, but the repository does not yet contain a complete Dots3-Note device-throughput report. The configurations below are suggested measurement starting points.

| Device layout | Suggested launch command | Speed |
| --- | --- | --- |
| GPU with NUMA MoE | `ftllm server /data/models/dots3-note --device cuda --moe_device numa --chunked_prefill_size 8192` | Not measured |
| GPU with CPU MoE | `ftllm server /data/models/dots3-note --device cuda --moe_device cpu --chunked_prefill_size 8192` | Not measured |
| Long context | `ftllm server /data/models/dots3-note --device cuda --moe_device numa --max_context_length 131072 --chunked_prefill_size 8192` | Not measured |

Long-context tests should report TTFT, prefill token/s, memory use, and sustained decode at multiple input lengths instead of publishing only a short-input result. See the [benchmark tools](../../test/benchmark/README.md).

---

### Benchmarks Glm5 (`docs/benchmarks/glm5.md`)

# GLM-5 / GLM-5.3-Flash Benchmark

[部署指南](../glm5.md) · [Benchmark 索引](../benchmark.md)

## GLM-5.3-Flash prefill

2026-10-03，8 × RTX 5090，`cudapp=8`、NVFP4 routed experts、BF16 激活、chunk 1024、单请求、MTP=0、上下文预算 65536。默认 `auto` DSA 在满足条件时使用 FlashInfer sparse prefill；关闭 prefix/history cache，排除加载和首次权重重排。各长度预热后测三次取中位数。

| 输入 token | TTFT 秒 | 输入 token / TTFT |
| ---: | ---: | ---: |
| 16384 | 6.106 | 2683.4 token/s |
| 32768 | 12.402 | 2642.1 token/s |

TTFT 包含调度和首 token。32K 输入由 16K 序列重复构成，只用于性能与回归；该页没有相同最终配置的完整 decode 数据。
FP8 计算不保证与 BF16 后端逐位一致，已记录的 16K/32K 首步 logits 相对 L2 差异为 18.9%/24.6%。详细能力、精度限制及回退见[部署指南](../glm5.md#glm-53-flash-flashinfer-sparse-prefill)。

被测原生库 SHA256：`9f1988e01cae1892083d21f0f9f92c99e564ef9e9d135eea2125fa8372477df7`。

---

### Benchmarks Glm5 En (`docs/benchmarks/glm5_en.md`)

# GLM-5 / GLM-5.3-Flash Benchmark

[Deployment guide](../glm5_en.md) · [Benchmark index](../benchmark_en.md)

## GLM-5.3-Flash prefill

Measured 2026-10-03 on eight RTX 5090 GPUs: `cudapp=8`, NVFP4 routed experts, BF16 activations, chunk 1024, batch 1, MTP off, context budget 65536. The default `auto` DSA backend uses FlashInfer sparse prefill when eligible. Prefix/history caching is disabled; loading and initial weight rearrangement are excluded. Median of three runs per length after warmup.

| Input tokens | TTFT seconds | Input tokens / TTFT |
| ---: | ---: | ---: |
| 16384 | 6.106 | 2683.4 token/s |
| 32768 | 12.402 | 2642.1 token/s |

TTFT includes scheduling and the first token. The 32K input repeats a 16K sequence and is a performance/regression sample, not a long-context quality evaluation. No complete decode measurement for this final configuration is recorded here.
FP8 computation need not match BF16 bit for bit; measured first-step logits relative L2 differences were 18.9%/24.6% at 16K/32K. See the [backend and numerical contract](../glm5.md#glm-53-flash-flashinfer-sparse-prefill).

Measured native library SHA256: `9f1988e01cae1892083d21f0f9f92c99e564ef9e9d135eea2125fa8372477df7`.

---

### Benchmarks Kimi K3 (`docs/benchmarks/kimi_k3.md`)

# Kimi-K3 Benchmark

[English](kimi_k3_en.md) · [Benchmark 索引](../benchmark.md) · [部署指南](../kimi_k3.md)

仓库目前只有功能与协议验证，没有可发布的 Kimi-K3 标准吞吐数据。下列配置是建议的复测起点，不代表实测性能。

| 设备布局 | 建议启动命令 | 速度 |
| --- | --- | --- |
| GPU + NUMA MoE | `ftllm server /data/models/kimi-k3 --device cuda --moe_device numa --chunked_prefill_size 8192` | 待实测 |
| GPU + CPU MoE | `ftllm server /data/models/kimi-k3 --device cuda --moe_device cpu --chunked_prefill_size 8192` | 待实测 |
| GPU + NUMA + 磁盘专家 | `ftllm server /data/models/kimi-k3 --device cuda --moe_device "{'cuda':1,'numa':8,'disk':1}"` | 待实测 |

Kimi-K3 还支持外部 DSpark。对比时应分别记录普通 Decode 与 DSpark 的接受长度、TTFT 和持续吞吐，不能把功能回归结果作为速度数据。测试脚本说明见 [Benchmark 工具](../../test/benchmark/README.md)。

---

### Benchmarks Kimi K3 En (`docs/benchmarks/kimi_k3_en.md`)

# Kimi-K3 Benchmark

[中文](kimi_k3.md) · [Benchmark index](../benchmark_en.md) · [Deployment guide](../kimi_k3_en.md)

The repository currently has functional and protocol validation for Kimi-K3, but no standardized throughput result suitable for publication. The configurations below are starting points for measurement, not performance claims.

| Device layout | Suggested launch command | Speed |
| --- | --- | --- |
| GPU with NUMA MoE | `ftllm server /data/models/kimi-k3 --device cuda --moe_device numa --chunked_prefill_size 8192` | Not measured |
| GPU with CPU MoE | `ftllm server /data/models/kimi-k3 --device cuda --moe_device cpu --chunked_prefill_size 8192` | Not measured |
| GPU, NUMA, and disk experts | `ftllm server /data/models/kimi-k3 --device cuda --moe_device "{'cuda':1,'numa':8,'disk':1}"` | Not measured |

Kimi-K3 also supports an external DSpark draft model. A comparison should report standard decode and DSpark separately, including accepted length, TTFT, and sustained throughput. See the [benchmark tools](../../test/benchmark/README.md).

---

### Benchmarks Laguna (`docs/benchmarks/laguna.md`)

# Laguna Benchmark

[English](laguna_en.md) · [Benchmark 索引](../benchmark.md) · [部署指南](../laguna.md)

仓库目前只有 Laguna 多卡、CUDA Graph、混合 MoE 和量化路径的功能验证，没有统一硬件上的正式速度表。下列配置是建议的复测起点。

| 设备布局 | 建议启动命令 | 速度 |
| --- | --- | --- |
| 四卡 TP4 | `ftllm server /data/models/laguna --tp 4 --max_batch 8 --gpu_mem_ratio 0.9` | 待实测 |
| 八卡 TP8 | `ftllm server /data/models/laguna --tp 0,1,2,3,4,5,6,7 --max_batch 16 --gpu_mem_ratio 0.9` | 待实测 |
| GPU + NUMA MoE | `ftllm server /data/models/laguna --device cuda --moe_device numa --chunked_prefill_size 8192` | 待实测 |

发布结果时应注明 GPU 互联、checkpoint 精度、CUDA Graph 状态、上下文长度和 batch。NVFP4 与 INT4_GROUP32 必须分开记录。测试脚本说明见 [Benchmark 工具](../../test/benchmark/README.md)。

---

### Benchmarks Laguna En (`docs/benchmarks/laguna_en.md`)

# Laguna Benchmark

[中文](laguna.md) · [Benchmark index](../benchmark_en.md) · [Deployment guide](../laguna_en.md)

The repository currently contains functional validation for Laguna multi-GPU, CUDA Graph, hybrid MoE, and quantized paths, but no formal speed table on a consistent hardware platform. The configurations below are suggested measurement starting points.

| Device layout | Suggested launch command | Speed |
| --- | --- | --- |
| Four-GPU TP4 | `ftllm server /data/models/laguna --tp 4 --max_batch 8 --gpu_mem_ratio 0.9` | Not measured |
| Eight-GPU TP8 | `ftllm server /data/models/laguna --tp 0,1,2,3,4,5,6,7 --max_batch 16 --gpu_mem_ratio 0.9` | Not measured |
| GPU with NUMA MoE | `ftllm server /data/models/laguna --device cuda --moe_device numa --chunked_prefill_size 8192` | Not measured |

Published results should include the GPU interconnect, checkpoint precision, CUDA Graph state, context length, and batch size. NVFP4 and INT4_GROUP32 results must be reported separately. See the [benchmark tools](../../test/benchmark/README.md).

---

### Benchmarks Naive N05 (`docs/benchmarks/naive_n05.md`)

# Naive-N0.5-Flash Benchmark

[Benchmark 索引](../benchmark.md) · [部署与验证](../naive_n05_flash.md)

以下记录各部署配置的最终测量，日期和测试范围分别注明；未记录统一源码版本的结果不能视为当前 HEAD 的性能承诺。

## NVFP4 八卡按层部署

2026-10-02，48 层 MoE-NVFP4，8 × RTX 5090、CUDA 13.1、BF16 激活/KV，按层分卡、chunk 512、上下文预算 65536、单请求、MTP=0、16 线程，关闭 history/prefix cache。测量排除加载、预热及 profiler；表中为正式请求中位数。

| 输入 token | TTFT 秒 | Decode token/s |
| ---: | ---: | ---: |
| 56 | 0.0779 | 67.093 |
| 80 | 0.0939 | 65.366 |
| 7565 | 5.0159 | 54.045 |
| 32727 | 24.8515 | 51.384 |

生成代码通过每次 980 项独立检查。完整模型最长输入为 32727 token，64K 仅覆盖 KV 单元回归；未覆盖该配置的 MTP 端到端性能，也没有可发布的峰值显存测量。

## FP8 双卡辅助 prefill

2026-10-01，RTX 4090 + RTX 4090 D、EPYC 9374F，单 NUMA、CPU 0–31、28 线程。主干 `cudapp=2` 按层串行，专家在 NUMA，长 prefill 由 CPU 和双 GPU 并行处理；带 7-token Draft，输入 7557 token，关闭历史复用和专家权重缓存。

| Chunk | 预热后 TTFT 中位数 | 正式样本数 |
| ---: | ---: | ---: |
| 2048 | 22.085 秒 | 2 |
| 8192 | 16.999 秒 | 3 |

8192 分块的有效输入吞吐约 444.5 token/s。样本数有限，decode 专家仍在 CPU；不能用此结果推断双 GPU 的 decode 收益。

## FP8 单卡混推 DSpark

2026-09-29，EPYC 9374F、CPU 0–31、单 NUMA、28 线程、RTX 4090，候选上限 7、置信度阈值 0.5、单并发、贪心采样。模型完整预热，以下速率排除加载和 TTFT。

| 编程任务 | 输出 token | DSpark token/s | 候选接受率 |
| --- | ---: | ---: | ---: |
| 合并闭区间 | 100 | 24.44 | 79.8% |
| 二分查找首个匹配 | 105 | 29.76 | 95.8% |
| 最小字典序拓扑排序 | 163 | 23.53 | 75.1% |

三题贪心输出与目标逐 token 解码一致，生成代码通过独立测试。这是有限任务的稳态结果；首次访问专家仍需准备权重，短任务首次执行不保证提速。随机采样正确性与浮点前向误差需分别验证，不能用接受率代替精度评估。

---

### Benchmarks Qwen3 (`docs/benchmarks/qwen3.md`)

# Qwen3.5 / Qwen3.6 / Qwen3.8 Benchmark

[English](qwen3_en.md) · [Benchmark 索引](../benchmark.md) · [部署指南](../qwen3.md)

Qwen3 系列按具体 checkpoint、精度和推理路径分别记录，不能把不同版本的数据合并比较。

## 已有实测

| 模型与路径 | 测试设备 | 代表结果 | 完整记录 |
| --- | --- | --- | --- |
| Qwen3.6-27B-FP8 TP2 | 2 × RTX 5090 32GB，PCIe，无 NVLink | Decode：B1 94.81、B16 1163.94、B64 2930.45 token/s | [启动命令与完整结果](qwen36_27b_fp8.md) |
| Qwen3.8-27B-FP8 DFlash2 TP2 | 2 × RTX 5090 32GB，PCIe，无 NVLink | 固定流：MTP5 273.65、DFlash B8 381.00 token/s | [启动命令与完整结果](qwen38_27b_dflash2.md) |

## Qwen3.5

仓库目前没有可发布的 Qwen3.5 标准吞吐数据。以下命令仅作为复测起点，速度均待实测：

| 设备布局 | 建议启动命令 | 速度 |
| --- | --- | --- |
| 单张 CUDA | `ftllm server /data/models/qwen3.5 --device cuda --max_batch 1` | 待实测 |
| 双卡 TP2 | `ftllm server /data/models/qwen3.5 --tp 2 --max_batch 8` | 待实测 |
| CPU / NUMA | `ftllm server /data/models/qwen3.5 --device numa -t 64` | 待实测 |

发布新结果时，请记录 checkpoint、精度、FastLLM commit、设备与互联、输入输出长度、batch、完整命令和统计口径。测试脚本说明见 [Benchmark 工具](../../test/benchmark/README.md)。

---

### Benchmarks Qwen36 27b Fp8 (`docs/benchmarks/qwen36_27b_fp8.md`)

# Qwen3.6-27B-FP8 Benchmark

[Benchmark 索引](../benchmark.md) · [部署指南](../qwen3.md)

## 测试配置

- 日期：2026-08-09。
- 模型：Qwen3.6-27B-FP8。
- 硬件：2 × RTX 5090，每卡 32607 MiB，PCIe PIX，无 NVLink。
- 并行：Tensor Parallel 2。
- FastLLM：0.1.7.1，commit `6e01b27663c0`。
- 上下文上限：1024。
- 每个请求固定输出 256 token，关闭 thinking、Prefix Cache 和 MTP。
- 结果是客户端可见的聚合 Decode 吞吐，正式测试 3 次取中位数。

## 推荐 TP2 启动命令

~~~bash
FASTLLM_CUDA_GRAPH=1 \
ftllm server /data/models/Qwen3.6-27B-FP8 \
  --model_name qwen3.6-27b \
  --dtype auto \
  --tp 2 --cuda_embedding \
  --max_batch 64 \
  --max_context_length 1024 \
  --gpu_mem_ratio 0.98 \
  --prefix_cache false \
  --enable_thinking false
~~~

这是复现实测环境的起点。生产环境可以重新开启 Prefix Cache，并根据目标上下文降低 `--max_batch`。

## 实测 Decode 吞吐

| Batch / 并发 | FastLLM token/s |
| ---: | ---: |
| 1 | 94.8087 |
| 2 | 183.0459 |
| 4 | 338.1496 |
| 8 | 630.5965 |
| 16 | 1163.9353 |
| 32 | 1854.3000 |
| 64 | 2930.4490 |
| 128 | 不可用：预热 OOM |
| 256 | 不可用：容量/OOM |

## 其他设备布局

| 布局 | 建议命令 | 当前速度 |
| --- | --- | --- |
| 单张 CUDA | `ftllm server /data/models/Qwen3.6-27B-FP8 --device cuda --max_batch 1` | 待实测 |
| 双卡 TP2 | 使用上方推荐命令 | B1 94.81；B64 2930.45 token/s |
| CPU / NUMA | `ftllm server /data/models/Qwen3.6-27B-FP8 --device numa -t 64` | 待实测 |

单卡是否能容纳模型和所需 KV Cache 取决于 checkpoint 的实际权重布局与上下文设置。

## 如何复测

~~~bash
python3 test/benchmark/decode.py /data/models/Qwen3.6-27B-FP8 \
  --tp 2 \
  --batch-size 16 \
  --max_batch 16 \
  --prefill-length 512 \
  --max-tokens 256
~~~

复测时记录请求构造、采样参数、计时窗口及完整输出；只有口径一致的结果才可与本页数值比较。

---

### Benchmarks Qwen38 27b Dflash2 (`docs/benchmarks/qwen38_27b_dflash2.md`)

# Qwen3.8-27B-FP8 DFlash2 / MTP Benchmark

[Benchmark 索引](../benchmark.md) · [部署指南](../qwen3.md)

## 测试配置

- 日期：2026-08-19，FastLLM commit `161962de`。
- 目标模型：Qwen3.8-27B-FP8。
- Draft：独立 Qwen3.8-27B-DFlash2 checkpoint。
- 硬件：2 × RTX 5090，每卡 32607 MiB，PCIe，无 NVLink。
- DFlash2 推荐 block：8（包含 anchor，即 7 个实际 draft token）。

## 推荐 DFlash2 启动命令

~~~bash
ftllm server /data/models/Qwen3.8-27B-FP8 \
  --model_name qwen3.8-dflash2 \
  --dtype auto \
  --tp 2 --cuda_embedding \
  --max_batch 1 \
  --tokens 8192 \
  --gpu_mem_ratio 0.98 \
  --speculative_algorithm dflash \
  --speculative_draft_model_path /data/models/Qwen3.8-27B-DFlash2 \
  --speculative_num_draft_tokens 8
~~~

Draft checkpoint 的 `architectures` 必须包含 `DFlash2DraftModel`。省略 block 参数时使用 checkpoint 中的原生值。

## 固定 token 流 Decode

固定 64-token 输入，在 TTFT 后强制生成固定长度，用于排除 EOS 和输出长度差异。

| 路径 | 输出长度 | 运行次数 | 中位吞吐 |
| --- | ---: | ---: | ---: |
| MTP5 | 1024 | 2 | 273.65 token/s |
| DFlash B8 | 1024 | 3 | 381.00 token/s |
| DFlash B8 | 4096 | 2 | 377.52 token/s |

固定流结果表明该 checkpoint 的推荐配置为原生 B8。

## GSM8K 端到端

128 题、单并发，temperature=1、top_p=0.95、top_k=20、最多输出 4096 token：

| 配置 | 输出 token | 总耗时 | 吞吐 | 正确 |
| --- | ---: | ---: | ---: | ---: |
| DFlash B8 | 70878 | 342.308 s | 207.06 token/s | 120/128 |

这是包含采样、不同 EOS 和不同输出长度的服务端到端吞吐，不能与固定流的 381.00 token/s 混为同一指标。

MTP5 配置保留相同目标模型、TP2、embedding 和容量参数，移除上述三个 `--speculative_*` 参数，改用 `--mtp 5`。DFlash 和 `--mtp` 不能同时启用。本页没有单卡或 CPU / NUMA 的实测速率。

## 接受率与采样语义

上述 GSM8K 测量中，七个候选位置的累计接受率分别为 86.68%、71.18%、58.33%、48.29%、39.90%、33.37%、27.69%；每轮平均提交 4.654 个 token。
接受率取决于输入和采样参数，不能视为固定配置的常数。

随机采样使用标准拒绝采样：候选分布为 q，目标分布为 p，以 `min(1, p/q)` 接受；首次拒绝后从归一化的 `max(p-q, 0)` 采样，全部接受时额外采样目标 token。
这保证的是给定目标条件分布下的采样语义；不同批量前向仍可能有浮点舍入差异，不保证相同随机种子产生逐 token 相同的输出。

---

### Benchmarks Qwen38 27b Multimodal Agent 2080ti (`docs/benchmarks/qwen38_27b_multimodal_agent_2080ti.md`)

# Qwen3.8-27B-FP8：双 2080 Ti 的 32 图完整任务

2026-09-25 使用独立 Pi 0.84.4 测试构建 `6c67951b`，累计处理 32 张真实收据，完成对账、网页生成、部署和浏览器检查。任务在 1823.60 秒后自然结束，无 OOM、API 错误或人工续写；独立验收 **350/351，严格未全通过**，保留了一项日期识别差异。

| 配置 | 值 |
|---|---|
| GPU | 2 × RTX 2080 Ti，每卡 22 GiB |
| 模型 | Qwen3.8-27B-FP8 |
| 推理 | TP2、MTP3、Graph OFF、max_batch=1 |
| 上下文 / 内存 | 65536 token、gpu_mem_ratio=0.80、kv_cache_limit=3g、chunked_prefill_size=1024 |
| 采样 | temperature=1.0、top_k=20、top_p=0.95 |
| Pi | thinking high，read/bash/edit/write，单次用户提示，无额外扩展或 skills |
| 输出预算 | 初始 32768，随后由 Pi 按上下文夹紧；3600 秒实验时限未触发 |
| 被测源码 | `6c67951b459475c03523d6f6021effbd7f17ee20` |
| 隔离 native SHA256 | `e988ab0b3570a502c68f269b7f6c847b7e538185b5354e8a351b2cb6124feff2` |

图片来自 [SROIE 参赛团队公开仓库](https://github.com/zzzDavid/ICDAR-2019-SROIE/tree/27be4271b251c256f695acbade9a801bffe85994/data)，固定该 revision。从源编号 000～035 中，在推理前选取前 32 个不同商户/日期/金额组合且金额标注有效的样本，以种子 20260925 打乱，保留原始扫描字节。公开数据不能排除训练集重叠。另构造含缺失、重复、金额不符各 4 项的账本；未向模型提供隐藏标注。

任务要求四批各读 8 张原图，每批保存提取结果后再读下一批；读完全部 32 张后，回读 R001/R017、R005/R021、R010/R026、R016/R032 四对，核对金额并生成完整明细、汇总及商户分组。随后实现带搜索、异常过滤、原图链接和 JSON 下载的本地网页，部署至 loopback，执行浏览器检查、回读桌面及移动截图，写 README 后结束。未使用 OCR 或外部网络，也未给模型人工纠错提示。

| 检查项 | 实测 |
|---|---|
| 逐图覆盖 / 指定回读 | 32/32 原图，8/8 回读，分批保存顺序正确 |
| 图片工具结果 | 42 次：32 次初读、8 次回读、2 张网页截图 |
| 单请求图片峰值 | 26 个图片片段，包含重复；不同原始收据峰值为 24 张 |
| API 输入峰值 | 48721 token |
| 自动压缩 | 3 次，均完成并自动继续 |
| API / 工具完整性 | 53/53 HTTP 200 和完整 SSE；75/75 工具调用有对应执行和返回 |
| 自然结束 | 最终 `stop`、`agent_settled`、idle；无 length、超时或外部续写 |
| 两卡显存峰值 | 18220 / 18002 MiB，即 17.79 / 17.58 GiB |
| 首输出等待 | 中位 23.91 秒，最长 53.66 秒，包含思考输出的首个流式文本 |
| 独立验收 | 商户 32/32、金额 32/32、日期 31/32；全部检查 350/351 |

## 结论与范围

这是累计 32 张收据的分轮任务；Pi 会压缩上下文，不能当作 32 张图片同时驻留的验证。53 个请求包括 50 次任务推理和 3 次摘要；多模态文本前缀命中为 0。512 MiB 视觉 embedding 缓存记录 357 次命中、142 次未命中，不能仅由该命中率推导端到端收益。

独立验收为 **350/351，未全通过**。唯一错误为 R013 日期：模型输出 `06/03/2016`，标注为 `06/03/2018`。任务正常结束和网页功能通过不等于识别结果完全正确。

独立网页检查覆盖明细、搜索、过滤、JSON 下载、移动布局及图片链接。测试仅覆盖一个单并发 Pi 收据任务，不代表其他图片类型、多并发或更大图片数量的表现。

---

### Benchmarks Qwen3 En (`docs/benchmarks/qwen3_en.md`)

# Qwen3.5 / Qwen3.6 / Qwen3.8 Benchmarks

[中文](qwen3.md) · [Benchmark index](../benchmark_en.md) · [Deployment guide](../qwen3_en.md)

Results are separated by exact checkpoint, precision, and inference path. Measurements from different Qwen3 releases must not be combined as if they were one model.

## Qwen3.6-27B-FP8 TP2

- Hardware: 2 × RTX 5090 with 32,607 MiB each, connected over PCIe without NVLink.
- FastLLM: 0.1.7.1, commit `6e01b27663c0`.
- Workload: context limit 1,024, 256 generated tokens per request, three measured runs after warmup.
- Thinking, prefix cache, and MTP were disabled.

~~~bash
FASTLLM_CUDA_GRAPH=1 \
ftllm server /data/models/Qwen3.6-27B-FP8 \
  --model_name qwen3.6-27b \
  --dtype auto \
  --tp 2 --cuda_embedding \
  --max_batch 64 \
  --max_context_length 1024 \
  --gpu_mem_ratio 0.98 \
  --prefix_cache false \
  --enable_thinking false
~~~

| Batch / concurrency | Decode throughput |
| ---: | ---: |
| 1 | 94.8087 token/s |
| 2 | 183.0459 token/s |
| 4 | 338.1496 token/s |
| 8 | 630.5965 token/s |
| 16 | 1163.9353 token/s |
| 32 | 1854.3000 token/s |
| 64 | 2930.4490 token/s |
| 128 / 256 | Unavailable due to capacity or warmup OOM |

The [Qwen3.6 benchmark](qwen36_27b_fp8.md) records the final configuration and results.

## Qwen3.8-27B-FP8 with DFlash2 TP2

- Hardware: 2 × RTX 5090 with 32,607 MiB each, connected over PCIe without NVLink.
- Measured 2026-08-19, FastLLM commit `161962de`.
- Draft model: a separate Qwen3.8-27B-DFlash2 checkpoint.
- Recommended DFlash block: 8, including the anchor token.

~~~bash
ftllm server /data/models/Qwen3.8-27B-FP8 \
  --model_name qwen3.8-dflash2 \
  --dtype auto \
  --tp 2 --cuda_embedding \
  --max_batch 1 \
  --tokens 8192 \
  --gpu_mem_ratio 0.98 \
  --speculative_algorithm dflash \
  --speculative_draft_model_path /data/models/Qwen3.8-27B-DFlash2 \
  --speculative_num_draft_tokens 8
~~~

| Path | Output length | Median fixed-stream throughput |
| --- | ---: | ---: |
| MTP5 | 1024 | 273.65 token/s |
| DFlash B8 | 1024 | 381.00 token/s |
| DFlash B8 | 4096 | 377.52 token/s |

The final 128-question GSM8K run with DFlash B8 reached 207.06 token/s and 120/128 correct. This end-to-end result includes sampling and variable output lengths, so it is not directly comparable to the forced fixed-token stream. See the [DFlash2 benchmark](../benchmarks/qwen38_27b_dflash2.md).

## Qwen3.5

No standardized Qwen3.5 throughput result is currently publishable from this repository.

| Device layout | Suggested launch command | Speed |
| --- | --- | --- |
| Single CUDA GPU | `ftllm server /data/models/qwen3.5 --device cuda --max_batch 1` | Not measured |
| Two-GPU TP2 | `ftllm server /data/models/qwen3.5 --tp 2 --max_batch 8` | Not measured |
| CPU / NUMA | `ftllm server /data/models/qwen3.5 --device numa -t 64` | Not measured |

---

### Benchmarks Qwen4 Exp (`docs/benchmarks/qwen4_exp.md`)

# Qwen4-Exp / Qwen3.8-Flash-Next Benchmark

[English](qwen4_exp_en.md) · [Benchmark 索引](../benchmark.md) · [部署指南](../qwen4.md) · [架构详情](../qwen4_exp.md)

## 2026-09-29：双 RTX 2080 Ti，Flash-Next NVFP4

测试版本为提交 `d6ef5c784`，覆盖六种混合部署方式。完整启动命令见[部署指南](../qwen4.md)。

- 硬件：两张 **22 GiB 显存版** RTX 2080 Ti（每卡 22528 MiB），AMD EPYC 7452，32 核 / 64 线程，单 NUMA 节点，125.7 GiB RAM；Ubuntu 24.04.4，驱动 595.84。
- 模型：`Qwen3.8-Flash-Next-NVFP4` 文本推理，48 个主干 MoE 层。FP16 激活，PLE disk，28 计算线程，batch=1，KV 预算 1 GiB，`gpu_mem_ratio=0.95`。
- 统一关闭 CUDA Graph、Triton、前缀缓存、历史缓存和思考。普通与 MTP 模式共用已加载的 MTP 权重。
- 每项预热一次、正式测一次；decode 预热生成 128 token。输入为统一的代码生成提示词，填充到指定长度，所有配置使用相同输入 token。

### Prefill 与 decode

Prefill 使用 **4096 输入 token、`chunked_prefill_size=4096`、MTP=0**，完整请求只生成 1 个 token。吞吐取原生 `[Prompt]` 前向计时，包含得到首 token 所需的 logits/采样，不是单一 GPU kernel 时间；TTFT 则从原生请求提交计到首 token。

Decode 另用 512 输入、固定 512 输出，分别测普通解码和 MTP=3；吞吐为 `511 / (末 token 到达时间 − 首 token 到达时间)`，不含模型加载、预热或 TTFT。两个阶段都没有 HTTP/SSE 网络开销。

| 部署方式 | Prefill token/s | Prefill TTFT 秒 | 普通 decode token/s | MTP3 token/s |
| --- | ---: | ---: | ---: | ---: |
| 串行，缓存关闭 | 919.43 | 4.456 | 22.36 | 30.08 |
| 串行，每卡缓存 8 GiB | 902.92 | 4.538 | 26.13 | 39.84 |
| 串行，固定 12 层专家 | 960.99 | 4.273 | 23.99 | 32.92 |
| TP2，缓存关闭 | 1240.71 | 3.303 | 20.60 | 32.63 |
| TP2，每卡缓存 8 GiB | 1254.39 | 3.267 | 27.71 | 46.29 |
| TP2，固定 12 层专家 | 1321.08 | 3.102 | 22.57 | 37.82 |

不同部署的生成内容和 MTP 接受情况可能不同，以上是单轮部署效果比较，不是强制相同输出的算子微基准。固定 512 输出也不代表完整编程任务或 Agent 自然结束验收。

### 内存与显存

| 部署方式 | GPU0 峰值 GiB | GPU1 峰值 GiB | 解码 RSS GiB | 主机 HWM GiB |
| --- | ---: | ---: | ---: | ---: |
| 串行，缓存关闭 | 7.81 | 10.25 | 72.60 | 72.86 |
| 串行，每卡缓存 8 GiB | 15.83 | 18.26 | 79.64 | 80.42 |
| 串行，固定 12 层专家 | 12.46 | 20.30 | 56.56 | 65.05 |
| TP2，缓存关闭 | 10.76 | 10.84 | 77.05 | 80.79 |
| TP2，每卡缓存 8 GiB | 18.78 | 18.86 | 84.09 | 88.37 |
| TP2，固定 12 层专家 | 18.33 | 18.41 | 61.05 | 80.62 |

显存为每 2 秒采样的全程峰值，包含大块 prefill；解码 RSS 为正式解码阶段采样中位数。HWM 包含加载峰值，不能拿较低的稳态 RSS 作为启动所需内存。所有组进程 swap 为零，正常完成后退出，未出现 OOM 或 CUDA 错误。

固定 12 层时，TP2 使用 `--moe_device_layers 36`，前 12 层专家跨两卡分片。串行则使用 `cudapp=1:7` 主干和 `{'cuda:0':6,'cuda:1':6,'numa':36}` 专家映射；它与其他串行组的 24/24 主干分界不同。固定层会占用冷门专家的显存，动态缓存可跨层保留活跃专家，不能只按相同容量推断速度。

### 复测

使用仓库中的[同口径测量脚本](../../test/benchmark/qwen38_hybrid.py)，在已安装或构建的 `ftllm` Python 环境运行。脚本固定测试一轮，输出目录必须不存在：

~~~bash
CUDA_VISIBLE_DEVICES=0,1 FT_NUMAS=1 FASTLLM_CUDA_GRAPH=0 \
python test/benchmark/qwen38_hybrid.py /data/models/Qwen3.8-Flash-Next-NVFP4 \
  --output-dir /tmp/qwen38-tp-cache8 \
  --tp 2 --moe_device numa --moe_cuda_cache 8g \
  --atype float16 --ngram_device disk --threads 28 --mtp 3 \
  --max_batch 1 --kv_cache_limit 1g --gpu_mem_ratio 0.95 \
  --chunked_prefill_size 4096 --prefix_cache false --cache_history false \
  --enable_thinking false
~~~

把布局参数替换为部署指南中的其他组合，串行时清除 `FASTLLM_TP`。脚本自动做 4096-token prefill 和两种解码模式；`results.json` 保存 TTFT、完整输出 token 和到达时间，终端 `[Prompt]` 行保存原生 prefill 吞吐。原生计时与内置 `ftllm benchmark` 使用 `输入 token / TTFT` 得到的 Prefill 指标口径略有不同。


## GGUF 专家常驻 GPU

硬件为双 22 GiB RTX 2080 Ti、EPYC 7452、单 NUMA；TP2、FP16 激活、CUDA Graph 开启、MTP 关闭、专家缓存预算为 0。全部专家保留原生 GGUF 格式驻留 GPU，CPU embedding，磁盘 PLE，28 线程、1 GiB KV 预算、GPU memory ratio 0.95。
模型路径指向同目录中第一份 GGUF 分片，启动参数见[部署指南](../qwen3.8-flash-next/README.md#gguf-resident-performance)。

原生生成接口计时，batch=1、greedy，无 HTTP 或 profiler；各组预热后测三轮，取中位数。解码为 512 输入、512 输出，按 `511 / (末 token 时间 - 首 token 时间)` 计；prefill 为 4096 输入、1 输出，按 `4096 / TTFT` 计。

| 权重 | 测量日期 | 解码 chunk 32 | 解码 chunk 512 | Prefill chunk 1024 |
| --- | --- | ---: | ---: | ---: |
| GSQ-RCO-IQ2_XS | 2026-10-01 | 70.87 | 69.77 | 1205.13 |
| GSQ-RCO-Q2_0（含 Q3_K Dense） | 2026-10-02 | 71.59 | 70.67 | 1359.00 |

单位为 token/s。两种权重分别测量，不是量化格式的受控对比。不同 chunk 也可能改变生成轨迹。

| 权重 | 每卡逻辑权重 GiB | GPU0/1 加载及预热后 GiB | GPU0/1 采样峰值 GiB |
| --- | ---: | --- | --- |
| IQ2_XS | 19.3554 | 20.2088 / 20.2049 | 21.0586 / 20.8379 |
| Q2_0 | 18.6634 | 19.8436 / 19.8416 | 20.6855 / 20.4648 |

显存峰值每 2 秒采样，可能漏掉短暂峰值。IQ2_XS 主机 HWM 为 41.905 GiB、swap 为 0，CPU embedding 为 2.368 GiB。算术、JSON 和 4096-token prefill 检查通过，不能替代完整质量评测。Q8 激活点积存在量化误差，不保证与浮点激活计算逐位相同。

被测隔离原生库 SHA256：IQ2_XS 为 `0e9aa1b66b2d502e150395a40bfca36f2b43c0ea937329f43f89d60861add82b`；Q2_0 为 `2c60b9f0661321f32447f8d3bd1de75998e6fd9029eda3a95a79aad210241f91`。结果仅代表上述构建和 SM75 硬件。

---

### Benchmarks Qwen4 Exp En (`docs/benchmarks/qwen4_exp_en.md`)

# Qwen4-Exp / Qwen3.8-Flash-Next Benchmark

[中文](qwen4_exp.md) · [Benchmark index](../benchmark_en.md) · [Deployment guide](../qwen4_exp.md)

## 2026-09-29: Flash-Next NVFP4 on two RTX 2080 Ti GPUs

All six configurations use inference code from commit `d6ef5c784`. See the [deployment commands](../qwen4.md).

Hardware: two **22 GiB** RTX 2080 Ti GPUs (22528 MiB each), an AMD EPYC 7452 with 32 cores / 64 threads, one NUMA node and 125.7 GiB RAM; Ubuntu 24.04.4, driver 595.84. This is not the standard 11 GiB 2080 Ti configuration.

Model: `Qwen3.8-Flash-Next-NVFP4`, text inference, FP16 activations, disk-backed PLE, 28 compute threads, batch 1, 1 GiB KV budget and GPU memory ratio 0.95. CUDA Graph, Triton, prefix/history caches and thinking are disabled. MTP weights remain loaded in both decode modes.

### Throughput

Prefill uses **4096 input tokens, chunk size 4096 and MTP=0**, followed by one output token. Throughput comes from the native `[Prompt]` Forward timer, including computation needed for the first token; it is not isolated kernel timing. TTFT measures request submission to the first token.

Decode uses a separate 512-input / fixed-512-output request for each of MTP=0 and MTP=3. Rate is `511 / (last token arrival − first token arrival)`, excluding loading, warmup and TTFT. Each measurement has one warmup and one measured request (128 output tokens for decode warmup). All configurations use identical tokenized coding prompts padded to the target input length. There is no HTTP/SSE transport in these timings.

| Deployment | Prefill token/s | Prefill TTFT s | Decode token/s | MTP3 token/s |
| --- | ---: | ---: | ---: | ---: |
| Sequential, cache off | 919.43 | 4.456 | 22.36 | 30.08 |
| Sequential, 8 GiB cache/GPU | 902.92 | 4.538 | 26.13 | 39.84 |
| Sequential, 12 resident expert layers | 960.99 | 4.273 | 23.99 | 32.92 |
| TP2, cache off | 1240.71 | 3.303 | 20.60 | 32.63 |
| TP2, 8 GiB cache/GPU | 1254.39 | 3.267 | 27.71 | 46.29 |
| TP2, 12 resident expert layers | 1321.08 | 3.102 | 22.57 | 37.82 |

Generated text and speculative acceptance can differ between deployments. These are single-round deployment comparisons, not forced identical-token microbenchmarks or agent-completion tests.

### Memory

| Deployment | GPU0 peak GiB | GPU1 peak GiB | Decode RSS GiB | Host HWM GiB |
| --- | ---: | ---: | ---: | ---: |
| Sequential, cache off | 7.81 | 10.25 | 72.60 | 72.86 |
| Sequential, 8 GiB cache/GPU | 15.83 | 18.26 | 79.64 | 80.42 |
| Sequential, 12 resident expert layers | 12.46 | 20.30 | 56.56 | 65.05 |
| TP2, cache off | 10.76 | 10.84 | 77.05 | 80.79 |
| TP2, 8 GiB cache/GPU | 18.78 | 18.86 | 84.09 | 88.37 |
| TP2, 12 resident expert layers | 18.33 | 18.41 | 61.05 | 80.62 |

GPU peaks are sampled every two seconds across loading, prefill and decode. Decode RSS is the median during measured decoding; host HWM includes loading. All cases exited normally with zero process swap and no OOM/CUDA errors. Lower steady-state RSS does not prove that loading fits within that amount of RAM.

TP2 resident placement uses `--moe_device_layers 36`, keeping the first 12 expert layers sharded across the GPUs. Sequential resident placement uses `cudapp=1:7` with `{'cuda:0':6,'cuda:1':6,'numa':36}`; its decoder split differs from the other sequential cases (24/24). Resident layers reserve cold experts too, while dynamic caching can retain active experts across layers.

### Reproduce

Run the [measurement script](../../test/benchmark/qwen38_hybrid.py) in an installed/built `ftllm` environment. Use a new output directory:

~~~bash
CUDA_VISIBLE_DEVICES=0,1 FT_NUMAS=1 FASTLLM_CUDA_GRAPH=0 \
python test/benchmark/qwen38_hybrid.py /data/models/Qwen3.8-Flash-Next-NVFP4 \
  --output-dir /tmp/qwen38-tp-cache8 \
  --tp 2 --moe_device numa --moe_cuda_cache 8g \
  --atype float16 --ngram_device disk --threads 28 --mtp 3 \
  --max_batch 1 --kv_cache_limit 1g --gpu_mem_ratio 0.95 \
  --chunked_prefill_size 4096 --prefix_cache false --cache_history false \
  --enable_thinking false
~~~

Replace the placement flags for other configurations; clear `FASTLLM_TP` for sequential placement. The script records raw tokens and arrival times in `results.json`, and native prefill throughput in `[Prompt]` log lines. The built-in `ftllm benchmark` Prefill value uses input tokens divided by TTFT and therefore has a slightly different timing boundary.


## GPU-resident GGUF experts

Hardware: two 22 GiB RTX 2080 Ti GPUs and an EPYC 7452, one NUMA node. TP2, FP16 activations, CUDA Graph enabled, MTP off, expert cache budget 0; native GGUF experts on GPU, embedding on CPU, PLE on disk, 28 threads, 1 GiB KV budget, GPU memory ratio 0.95. See the [launch configuration](../qwen3.8-flash-next/README_EN.md#gguf-resident-performance).

Native generation timings, batch 1, greedy, no HTTP or profiler; median of three measured runs after warmup. Decode: 512 input and 512 output tokens, `511 / (last arrival - first arrival)`. Prefill: 4096 input tokens and one output, `4096 / TTFT`.

| Weights | Date | Decode, chunk 32 | Decode, chunk 512 | Prefill, chunk 1024 |
| --- | --- | ---: | ---: | ---: |
| GSQ-RCO-IQ2_XS | 2026-10-01 | 70.87 | 69.77 | 1205.13 |
| GSQ-RCO-Q2_0 (Q3_K Dense included) | 2026-10-02 | 71.59 | 70.67 | 1359.00 |

Rates are token/s. These separate measurements are not a controlled comparison of quantization formats. Chunk size may change generated trajectories.

| Weights | Logical weights GiB/GPU | GPU0/1 after load and warmup GiB | GPU0/1 sampled peak GiB |
| --- | ---: | --- | --- |
| IQ2_XS | 19.3554 | 20.2088 / 20.2049 | 21.0586 / 20.8379 |
| Q2_0 | 18.6634 | 19.8436 / 19.8416 | 20.6855 / 20.4648 |

Two-second GPU sampling may miss brief peaks. IQ2_XS host HWM was 41.905 GiB with zero swap; CPU embedding used 2.368 GiB. Arithmetic, JSON and 4096-token prefill checks passed; these are not a full quality evaluation. Q8 activation dot products have quantization error and need not match floating-point activation computation bit for bit.

Measured native-library SHA256: IQ2_XS `0e9aa1b66b2d502e150395a40bfca36f2b43c0ea937329f43f89d60861add82b`; Q2_0 `2c60b9f0661321f32447f8d3bd1de75998e6fd9029eda3a95a79aad210241f91`. Results apply to these builds on SM75.

---

--- METRICS ---
- Files Extracted: 104
- Estimated Token Budget: ~126164 tokens
- Canonical Reference: https://codewiki.google/github.com/ztxz16/fastllm
