ref(upstream): FULL TREE — Deep-Spark xllm (1470) + ds_vllm csrc/models (703)

Replaces cherry-picked upstream_ref with complete source trees.

xllm/ — Iluvatar official C++ inference engine (15MB, 1470 files)
  Complete: kernels → layers → models → runtime → scheduler → api
  Excluded: .git, binary images, third_party submodule checkouts

ds_vllm/ — Iluvatar official vllm fork (8MB, 703 files)
  Included: csrc/ (ALL CUDA kernels), fused_moe/, qwen3_5 model, _custom_ops
  Excluded: tests, benchmarks, docs, examples (not needed for reference)

Critical call chains now fully traceable:
  MoE: moe_topk_softmax_kernels.cuh → ixformer.h → fused_moe.cpp → layer
  GDN: qwen3_gated_delta_net_base.cpp → qwen3_5_gated_delta_net.cpp
  Attention: ixformer.h → xllm_paged_attention → attention.cpp
This commit is contained in:
EX Engine
2026-08-10 02:53:54 +00:00
parent 9e4fb3712f
commit 002f9879b2
2179 changed files with 494021 additions and 79 deletions

View File

@@ -0,0 +1,28 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version, and other tools you might need
build:
os: ubuntu-24.04
tools:
python: "3.13"
jobs:
pre_build:
# - cp -r docs/zh/* docs/
- mv docs/zh/* docs/
- rm -rf docs/en/
- find docs/ -name "*.md" -exec sed -i 's#../assets/#assets/#g' {} \;
# Build documentation with Mkdocs
mkdocs:
configuration: mkdocs_zh.yml
# Optionally, but recommended,
# declare the Python requirements required to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt

View File

@@ -0,0 +1,58 @@
# 1. LLM精度测试
## 1.1 设置ais_bench
```bash
# 使用conda或uv为ais_bench创建虚拟环境
conda create --name ais_bench python=3.10 -y
conda activate ais_bench
# 下载ais_bench并安装依赖
git clone https://gitee.com/aisbench/benchmark.git
cd benchmark/
pip3 install -e ./ --use-pep517
# 下载数据集并复制到ais_bench目录下
cp -r /path/to/dataset /path/to/benchmark/ais_bench/datasets
```
## 1.2 修改配置
根据实际情况修改精度测试配置文件:`/path/to/benchmark/ais_bench/benchmark/configs/models/vllm_api/vllm_api_general_chat.py`,采样参数建议按如下代码设置:
```python
models = [
dict(
attr="service",
type=VLLMCustomAPIChat,
abbr='vllm-api-general-chat',
path="/path/to/model/Qwen3-8B", # 模型路径
model="Qwen3-8B", # 模型名称
request_rate = 0,
retry = 2,
host_ip = "127.0.0.1",
host_port = 19000, # xllm服务端端口
max_out_len = 32768, # 限制模型最大长度
batch_size=32,
trust_remote_code=False,
generation_kwargs = dict(
temperature = 0.6,
# top_k = -1,
top_p = 0.95,
# seed = None,
# repetition_penalty = 1,
),
pred_postprocessor=dict(type=extract_non_reasoning_content)
)
]
```
## 1.3 启动ais_bench
在使用ais_bench前需要先启动xllm服务。使用`ais_bench -h`能够获取参数含义对于gsm8k和ceval数据集的启动命令如下
```bash
# 使用gsm8k数据集
ais_bench --models vllm_api_general_chat --datasets gsm8k_gen_0_shot_cot_chat_prompt --dump-eval-details
# 使用ceval数据集
ais_bench --models vllm_api_general_chat --datasets ceval_gen_0_shot_cot_chat_prompt --merge-ds --dump-eval-details
```
我们会在未来将ais_bench和数据集ceval和gsm8k集成进开发镜像ais_bench文档和数据集如下
* [ais_bench文档](https://ais-bench-benchmark.readthedocs.io/zh-cn/latest/index.html)
* [数据集](https://ais-bench-benchmark.readthedocs.io/zh-cn/latest/base_tutorials/all_params/datasets.html)

View File

@@ -0,0 +1,85 @@
---
hide:
- navigation
---
# 服务启动参数
xLLM使用gflags来管理服务启动参数具体的参数含义如下
## 常用参数
| 参数名称 | 类型 | 默认值 | 其他值 | 参数含义 | 其他 |
|:---------:|:---------:|:---------:|:---------:|:---------:|:---------:|
| `master_node_addr` | `string` | "127.0.0.1:19888" | ip:port | master节点rpc server的监听地址 | [详情](./features/basics.md) |
| `host` | `string` | "" | 当前device所在的机器ip | 当前device用于通信的host ip每个device上会启动一个rpc server用于多卡之间通信 | |
| `port` | `int32` | 8010 | 任意可用的端口 | 与host参数配套使用组合后用于device间的rpc通信 | |
| `model` | `string` | "" | | 模型所在的路径 | |
| `devices` | `string` | "npu:0" | | 指定当前进程使用的NPU设备 | |
| `nnodes` | `int32` | 1 | | 当前服务所使用的device总数 | |
| `node_rank` | `int32` | 0 | 0 ~ device总数减1 | 每个device的rank id | |
| `max_memory_utilization` | `double` | 0.8 | 0-1之间 | 模型权重和KV Cache一起可用的最大device memory占比 | |
| `max_tokens_per_batch` | `int32` | 10240 | | 每个step可计算的最大token数量 | |
| `max_seqs_per_batch` | `int32` | 1024 | | 每个step可计算的最大sequence数量 | |
| `enable_chunked_prefill` | `bool` | true | false | 是否开启chunked prefill | |
| `enable_prefill_sp` | `bool` | false | true | 是否开启 prefill 阶段的 sequence parallel | 支持 `enable_chunked_prefill=true`,但仅限纯 prefill batch`PREFILL` / `CHUNKED_PREFILL``MIXED``DECODE` batch 不会进入 sequence parallel。 |
| `enable_schedule_overlap` | `bool` | false | true | 是否开启异步调度 | [详情](./features/async_schedule.md) |
| `enable_prefix_cache` | `bool` | true | false | 是否开启prefix cacheDeepSeek暂不支持 | |
| `communication_backend` | `string` | "hccl" | "lccl" | 通信操作采用的后端 | |
| `block_size` | `int32` | 128 | | KV Cache存储的block size大小 | |
| `task` | `string` | "generate" | "embed", "mm_embed" | 服务类型生成式、embedding或多模态embedding | |
| `max_cache_size` | `int64` | 0 | | 可使用的KV Cache大小单位byte | |
| `kv_cache_dtype` | `string` | "auto" | "int8" | KV Cache数据类型。"auto"表示与模型dtype对齐不量化"int8"启用INT8量化以节省约50%显存。仅MLU后端支持 | |
## MOE模型相关参数
| 参数名称 | 类型 | 默认值 | 其他值 | 参数含义 | 其他 |
|:---------:|:---------:|:---------:|:---------:|:---------:|:---------:|
| `dp_size` | `int32` | 1 | 2的指数 | Attention部分的dp规模大小 | |
| `ep_size` | `int32` | 1 | 2的指数 | MoE部分的ep规模大小 | |
| `expert_parallel_degree` | `int32` | 0 | 1,2 | ep并行相关参数gflag默认值为0`ep_size > 1`且未显式配置时部分NPU MoE实现会按EP Level 1处理`ep_size=devices`总数时可设置为2使用all2all通信 | |
支持 MLA 的模型会自动开启 MLA不再需要单独配置 CLI 参数。
## PD分离相关参数
| 参数名称 | 类型 | 默认值 | 其他值 | 参数含义 | 其他 |
|:---------:|:---------:|:---------:|:---------:|:---------:|:---------:|
| `enable_disagg_pd` | `bool` | false | true | 是否启动PD分离 | [详情](./features/disagg_pd.md) |
| `disagg_pd_port` | `int32` | 7777 | 任意可用的端口 | 启用PD分离后配置对应每张卡上启动的pd分离rpc server的监听端口号 | |
| `instance_role` | `string` | DEFAULT | PREFILL DECODE MIX | 默认情况下为DEFAULT开启PD分离后需要配置为PREFILL、DECODE或者MIX | |
| `kv_cache_transfer_mode` | `string` | "PUSH" | "PULL" | PD分离传输KV Cache的模式。PUSH模式Prefill逐层向Decode传输PULL模式Decode一次性拉取Prefill的KV Cache | |
| `transfer_listen_port` | `int32` | 26000 | 任意可用的端口 | 启用PD分离后配置对应每张卡上KV Cache Transfer的监听端口 | |
## MTP相关参数
| 参数名称 | 类型 | 默认值 | 其他值 | 参数含义 | 其他 |
|:---------:|:---------:|:---------:|:---------:|:---------:|:---------:|
| `draft_model` | `string` | "" | | MTP模型所在的路径 | [详情](./features/mtp.md) |
| `draft_devices` | `string` | "npu:0" | 与`devices`格式保持一致,如`npu:0``npu:0,npu:1` | 与devices设置保持一致 | |
| `num_speculative_tokens` | `int32` | 0 | 任意整数建议1或者2 | MTP模型每次step输出token的个数 | |
## 图执行相关参数
| 参数名称 | 类型 | 默认值 | 其他值 | 参数含义 | 其他 |
|:---------:|:---------:|:---------:|:---------:|:---------:|:---------:|
| `enable_graph` | `bool` | false | true | 是否启用图执行模式优化decode阶段性能。仅用于decode阶段对prefill阶段不生效。支持ACL GraphNPU、MLU Graph。 | [详情](./features/graph_mode.md) |
| `enable_graph_mode_decode_no_padding` | `bool` | false | true | decode阶段按实际`num_tokens`建图而不是按padding后的shape建图 | |
| `enable_prefill_piecewise_graph` | `bool` | false | true | 是否启用prefill阶段的分段Graph。attention以eager执行其他算子进入图捕获。 | |
| `max_tokens_for_graph_mode` | `int32` | 2048 | 任意大于等于0的整数 | 图执行模式最大token数。为0表示不限制。 | |
## 配套xLLM-service使用的参数
| 参数名称 | 类型 | 默认值 | 其他值 | 参数含义 | 其他 |
|:---------:|:---------:|:---------:|:---------:|:---------:|:---------:|
| `etcd_addr` | `string` | "" | ip:port | etcd的rpc server监听地址 | |
| `enable_service_routing` | `bool` | false | true | 请求是否来自xllm service当使用xllm service管理xllm实例时使用 | |
## 其他参数
| 参数名称 | 类型 | 默认值 | 其他值 | 参数含义 | 其他 |
|:---------:|:---------:|:---------:|:---------:|:---------:|:---------:|
| `max_concurrent_requests` | `int32` | 200 | 任意大于等于0的整数 | 限流用限制实例中正在处理的总请求数设置为0表示不限流 | |
| `model_id` | `string` | "" | | 模型名称,非路径 | |
| `num_request_handling_threads` | `int32` | 4 | 任意大于0的整数 | 处理输入请求的线程池大小 | |
| `num_response_handling_threads` | `int32` | 4 | 任意大于0的整数 | 处理输出的线程池大小 | |
| `prefill_scheduling_memory_usage_threshold` | `double` | 0.95 | 0-1之间的值 | 当kv cache使用量达到该阈值时暂停prefill请求的调度 | |
| `rank_tablefile` | `string` | "" | | 创建通信域的配置文件,多机场景需要 | |

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,475 @@
# Graph Mode 设计文档
## 概述
xLLM 的 Graph Mode 覆盖多种图执行后端。其目标是在推理服务场景中,将原本由 Host 频繁发起的小粒度 kernel 启动,转化为先捕获、后重放的图执行流程,从而降低 Host 调度开销、减少设备气泡,并提升吞吐和时延稳定性。
本文档面向需要理解实现原理和关键设计的开发者,重点介绍以下内容:
- Graph Mode 的原理与在 xLLM 中的落地方式
- 动态维度参数化
- Piecewise Graph
- 多 Shape 复用的显存池(含输入 Tensor 复用)
本文聚焦 Graph Mode 的统一设计,不展开不同后端之间的差异。
本文档的设计目标包括:
- 统一 xLLM 在不同图执行后端上的 Graph Mode 抽象
- 解释动态维度参数化、Piecewise Graph 与多 Shape 显存复用三项关键设计
- 说明这些设计分别解决什么问题、依赖什么前提、边界在哪里
本文档的非目标包括:
- 不覆盖所有算子或所有模型的适配细节
- 不替代功能文档中的参数说明与使用示例
相关设计文档:
- 若希望看一个更偏业务推理场景、并且聚焦固定调度、多步执行和定制算子的案例,可参考:[生成式推荐设计文档](generative_recommendation_design.md)
## 1. Graph Mode 原理和在 xLLM 中的落地
### 1.1 Graph Capture / Replay 的基本原理
传统 eager 执行模式下,模型一次 forward 会由 Host 连续发起大量 kernel、memcpy 和同步操作。对于 decode 这类单 step 计算较小但请求频繁的场景Host 调度开销会比较显著,设备端也更容易出现执行气泡。
Graph Mode 的基本思路是:
1. **Capture 阶段**:第一次遇到某个 shape bucket 时,在专用 stream 上执行一次 forward并把这条执行路径上的 kernel 启动、内存操作和依赖关系记录成图。
2. **Replay 阶段**:后续相同 bucket 的请求,不再由 Host 逐个下发 kernel而是直接重放已捕获的图。
![alt text](../../assets/graph_mode.png)
这类机制通常要求:
- **执行路径稳定**
- graph capture 记录的是一次具体执行路径capture 完成后这条路径上的控制流、launch 形态和依赖关系就被固化下来
- 因此capture 路径中无法在 replay 时再切换到另一套动态条件分支
- **关键 Tensor 地址稳定**
- capture 期间写入图中的关键 Tensor 地址,在 replay 时必须仍然可用
- **算子与结果语义稳定**
- replay 的正确性依赖路径上的算子与 Graph Mode 兼容,且不会因为运行时条件变化而改变语义
### 1.2 xLLM GraphMode 基础工作
从 1.1 的三条基本要求出发,前两项主要由 xLLM 运行时负责:一是把动态请求整理成可稳定 replay 的执行单元,二是保证 replay 时仍然访问 capture 阶段记录下来的固定地址。为此xLLM 在 Graph Mode 下引入了统一的 Graph Executor负责收敛分桶、持久化 buffer、graph cache 以及 capture / replay 生命周期管理。
xLLM 在运行时侧需要完成的基础工作,主要包括:
- **围绕执行路径稳定的图选择与执行调度**
- 请求需要先按 `num_tokens` 或相近 shape 归入 bucket
- bucket 维度维护 graph cache已命中则直接 replay未命中则先 capture 再缓存
- 对可整图捕获的路径执行完整图;存在 break graph 的路径则切换到 Piecewise Graph
- **围绕地址稳定的持久化 buffer 与 graph instance 管理**
- tokens、positions、seq_lens、block_tables 等动态输入不能在 replay 前重新分配到任意新地址, xLLM 需要先把这些输入写入持久化 buffer再在固定地址上更新内容
- 模型计算图过程临时分配的Tensor都需要持久化保存对对应的graph instance中不能随着作用域结束触发Tensor回收
- 启用共享内存池后,不同 shape 的 capture 可以复用同一组底层物理内存,但 graph 看到的虚拟地址仍需保持稳定
- **统一的 Graph Executor 封装**
- capture、cache、replay、图实例管理以及后端差异屏蔽都需要由运行时统一处理
- 这层封装的目的,是把 Graph Mode 的运行时组织逻辑从模型实现中剥离出来,而不是把它们散落到各个 layer 或算子里
在具体执行上Graph Executor 的运行时流程可以概括为:
1. **请求分桶**:按 `num_tokens` 或相近 shape 归类到 bucket
2. **输入准备**:将 tokens、positions、seq_lens、block_tables 等写入持久化 buffer并更新 `attn_metadata``plan_info` 等动态元数据
3. **capture 或 replay 决策**bucket 已命中则直接 replay未命中则先 capture、缓存图再进入 replay 路径
4. **执行图**:按场景执行完整图或 Piecewise Graph
而 1.1 中第三项“算子与结果语义稳定”,更多落在模型和算子本身的适配上,见 1.3。
### 1.3 模型适配 Graph Mode 需要的改造
在 xLLM 运行时负责分桶、buffer 和 graph instance 管理之后,模型侧仍然需要解决 1.1 中第三项要求:保证 capture / replay 期间的算子行为与结果语义稳定,不会因为 host 参与、dispatch 变化或 launch 形态变化而失效。
模型适配 Graph Mode 需要的改造主要包括:
- **去掉不支持 capture 的操作**
- 泛化要求capture 路径中不能包含会触发 host 参与决策、host-device 隐式同步或额外控制流切换的操作,例如 stream 同步、读取 host tensor 决定逻辑分支、隐式 host-device 同步交互等
- 典型示例 1算子内部依赖 host tensor 内容决定任务划分或执行模式。例如商发 8.3 的 ATB [PA](https://www.hiascend.com/document/detail/zh/canncommercial/83RC1/API/ascendtbapi/ascendtb_01_0197.html) 和 [MLA](https://www.hiascend.com/document/detail/zh/canncommercial/83RC1/API/ascendtbapi/ascendtb_01_0314.html),会根据 host 侧kv_seq_lens和q_seq_lens来做不同的任务排列。
- 典型示例 2host 标量参与 Tensor 计算,或 host 侧直接读取 Tensor 数据,例如:
```cpp
Tensor a;
Tensor b = a * 0.5; // 标量会被隐式传到 tensor 所在设备,产生同步 h2d
torch::Tensor max_of_seq = torch::max(input_params.kv_seq_lens);
max_seq_len_ = std::max(max_of_seq.item<int>(), max_seq_len_); // host 读取 tensor
```
- **算子适配**
- 泛化要求:需要保证计算图的 kernel 执行路径稳定。对于同一个输入 shape每次执行计算图选择的 kernel 应保持一致,不发生 prefill、chunked prefill、decode 混杂;同时 `grid_dim``block_dim`、task 数量、workspace 形态和 tiling 内容也不能变化
- 典型示例Ascned Cann商发8.3版本 的 ATB PA 会根据序列长度和 batch size 实时决定是否启用 flash-decoding 长序列模式;长序列模式和短序列模式的 tiling key 不同dispatch 后实际 launch 的 `kernel_name` 也不同。这种“同一业务路径、不同运行时条件触发不同 kernel”的行为在进入 Graph Mode 前需要先收敛
## 2. 动态维度参数化
### 2.1 问题
Graph Capture 记录的是固定的 kernel 执行序列和 launch 形态。Replay 时,运行时只能重复这套已经记录下来的 `grid_dim``block_dim`、task 数量和 tiling 路径,不能在同一张图里重新决定它们。
因此,请求里的动态维度进入 Graph Mode 后,并不都能在 replay 时继续变化。对 attention 来说,真实动态因素通常不止 `num_tokens`,还包括 `batch_size``q_seq_lens``kv_seq_lens``block_tables_size` 等。如果这些维度进一步影响 task 划分、workspace 布局、`tiling_params` 或 kernel 路径,那么 replay 就会沿用 capture 时的旧配置,从而出错。
本章要解决的问题就是:哪些动态信息可以在图选定后继续更新,哪些必须通过分桶或重新建图处理。
### 2.2 解法
动态维度参数化的解法,不是“让一张图接受任意 `num_tokens`”,而是在**图已经按 `bucket_num_tokens` 选定之后**,让同一个 bucket 尽量覆盖更多真实动态请求。
#### 2.2.1 参数化边界
一个维度能不能在 replay 时变化,关键不在于它是不是“请求里的动态值”,而在于它有没有在 capture 阶段被折叠进图的执行形态。
可以把动态因素分成两类:
1. **决定图形态的维度**
- 一旦进入 `grid_dim``block_dim`、task 数量、workspace 布局、tiling key 或 `tiling_params`,它就成为 capture 结果的一部分
- 这类维度变化后,通常不能在同一张 graph 内直接修改
2. **图选定后仍可更新的维度**
- 例如 `batch_size``q_seq_lens``kv_seq_lens``block_tables``new_cache_slots``plan_info`
- 它们只有在不改写执行形态时,才适合作为参数化对象
一个简化后的 norm kernel 例子是:
```cpp
int grid_dim = num_tokens;
NormKernel<<<grid_dim, block_dim>>>(x, y, ...);
```
这里的 `grid_dim``block_dim` 都属于 launch params。只要 `num_tokens` 已经参与了 launch 配置它就属于图形态的一部分capture 完成后replay 就不能在同一张图里把 `grid_dim = 128` 改成 `grid_dim = 256`
因此,动态维度参数化的判断标准很直接:凡是会改变 launch、workspace 或 tiling 路径的动态因素,都不应只靠 replay 前改参数解决。
#### 2.2.2 xLLM 中的处理方式
xLLM 的处理方式分两层。
第一层先处理 `num_tokens`:通过 bucket 化和 padding把原始请求归一化到固定的 `bucket_num_tokens`,再据此选定图。
第二层再处理 bucket 内剩余的动态信息:对依赖 host tensor、host planning 或 host 侧 tiling 的算子做改造,把真正随请求变化的信息放到设备侧持久化 buffer 中,或者显式外置为 replay 前可更新的数据,而不是让 replay 继续绑定 capture 时那次 host 侧 planning 结果。
以 NPU attention 为例,改造前常见的问题是:`q_seq_lens``kv_seq_lens` 先在 Host 侧参与 planning生成 task、workspace 或 tiling 相关数据,再发射 kernel
```cpp
// Host side
auto plan_info = PlanAttention(q_seq_lens_host, kv_seq_lens_host, ...);
WritePlanToWorkspace(attention_workspace, plan_info);
AttentionKernel<<<grid_dim, block_dim>>>(
q, k, v, out,
attention_workspace,
tiling_params);
```
这类实现里graph replay 不会自动重做 planning因此只能重复 capture 时那次 launch 和那次 workspace 布局。改造后的方向是,在固定 launch 形态下,把 `q_seq_lens``kv_seq_lens``block_tables_size``plan_info` 等动态信息写入设备侧持久化 buffer再由 kernel 在执行时读取,例如`kv_seq_lens`的参数化示例:
```cpp
struct AttentionLaunchArgs {
void* q;
void* k;
void* v;
void* out;
int32_t batch_size;
int32_t* kv_seq_lens; // device buffer
void* attention_workspace;
void* tiling_params;
};
LaunchAttentionKernel(args);
```
如果某个动态因素一旦变化就会改写任务数量、数组长度、workspace 形态或 tiling 路径,那么 xLLM 不会强行把它塞进“参数化更新”里,而是采用两种处理方式之一:
1. 扩大分桶维度,把这些因素纳入选图键,例如 `num_tokens``num_comm_tokens` 的组合
2. 把这段对动态 shape 敏感的执行留在 full graph 之外,交给 Piecewise Graph 处理
### 2.3 结果
动态维度参数化的直接结果是:同一个 `num_tokens` bucket 可以安全覆盖更多真实请求在执行形态稳定的前提下attention 等算子也能在 replay 时看到正确的 `batch_size``seq_lens``block_tables_size``plan_info` 等动态信息。
需要注意的是,参数化并不等于取消分桶。`num_tokens` 的动态性仍主要通过选图吸收;如果同一个 bucket 下还有其他会改写 tiling key、任务数量、通信规模或 kernel 路径的因素,那么这些因素仍然必须进入分桶键,或者转为 Piecewise Graph。通信相关场景就是典型例子例如 Attention DP + MoE EP 下,通信规模可能取决于最大 DP Data Size而不完全等于单卡本地的 `num_tokens`
## 3. Piecewise Graph
### 3.1 问题
在 prefill、chunked prefill 等场景中,整条 forward 路径往往比 decode 更复杂。某些算子可能:
- 无法被稳定 capture
- 对动态元数据高度敏感
- 在 capture 模式下依赖额外的运行时准备逻辑
如果要求“整图都必须可捕获”Graph Mode 的适用范围会非常受限。只要有一个关键算子 break graph整条路径都无法使用图执行。
### 3.2 解法
#### 3.2.1 底层逻辑
在实现上,一个经常被问到的问题是:为什么 decode 可以使用 full graph而 chunked prefill 往往只能使用 Piecewise Graph
关键差别在 attention。
在当前 decode 语义下,单步通常是“每个序列生成一个 token”因此 `num_tokens``batch_size` 基本是一一对应的。对 attention 来说,这意味着:
- `num_tokens` 增长时,`batch_size` 也同步增长
- `q_seq_lens` 的模式相对稳定
- 任务划分、workspace 需求和 launch args 变化主要可以跟着 `num_tokens` 一起分桶
因此,按 `num_tokens` 建 bucket 时decode 路径满足 full graph 的复用条件。
但在 chunked prefill 中,`num_tokens``batch_size` 不再绑定。相同的 `num_tokens`,可能对应完全不同的请求组合,例如:
- `num_tokens = 128, batch_size = 2, q_seq_lens = [64, 64]`
- `num_tokens = 128, batch_size = 4, q_seq_lens = [32, 32, 32, 32]`
- `num_tokens = 128, batch_size = 8, q_seq_lens = [16, 16, 16, 16, 16, 16, 16, 16]`
对 attention 算子来说,这些请求虽然 `num_tokens` 相同,但并不是同一种 shape。attention 的任务数量划分、本地索引、`q_seq_lens` / `kv_seq_lens` 的长度、以及对应的 `plan_info` 都和 `batch_size` 有直接关系。如果系统只按 `num_tokens` 去复用之前记录的 graph那么 attention 在 replay 时就可能沿用 capture 阶段那套“旧的 batch 划分”和“旧的任务分块”,从而导致结果错误。
这里的 `batch_size` 之所以难以简单参数化,是因为它影响的不只是某个标量值,而是整组 launch args 的结构:
- `q_seq_lens` / `kv_seq_lens` 数组长度会变
- attention 的任务数量和任务边界会变
- `plan_info` 和 workspace 布局会变
- kernel 内部使用的索引和分块逻辑会变
换句话说,`batch_size` 在 chunked prefill 中不是“改几个参数就行”的维度,而是会改变 attention 这一段图的执行形态。除非把 `num_tokens × batch_size` 甚至更细粒度的 `seq_lens` 组合都纳入分桶,也就是做高维笛卡尔积建图,否则仅靠 `num_tokens` 选图并不安全。
这也是为什么当前实现更适合:
- **decode**attention 可以随 full graph 一起捕获
- **chunked prefill**attention 留在图外,其他稳定部分进入 Piecewise Graph
#### 3.2.2 核心思路
Piecewise Graph 的目标不是强行整图捕获,而是把一条完整执行路径拆成多个 piece
- 可图化的部分:捕获为多个子图
- 不适合图化的部分:保留为 eager 形式
在 replay 时,再按 capture 期间记录的顺序,把这些 piece 串起来执行。
这样可以在“无法整图”的前提下,尽量保留 Graph Mode 的收益。
下图以三层 Qwen3 decoder 为例展开了 Piecewise Graph 的执行顺序attention 保留为独立 runner而两次 attention 之间的非 attention 算子会被尽量压紧并组织成连续的 graph piece。
![Piecewise Graph 示意图](../../assets/piecewise_graph_diagram.svg)
#### 3.2.3 xLLM 中的实现方式
在 xLLM 中Piecewise Graph 主要用于 prefill 类场景,由 `enable_prefill_piecewise_graph` 控制。
它的核心实现方式是:
1. 使用 `PiecewiseGraphs` 维护 replay 指令序列
2. 可捕获片段以图对象形式保存
3. attention 这类不直接进入图的部分,通过 `AttentionRunner` 记录为 runner
4. replay 时按原始顺序执行 `graph -> runner -> graph -> ...`
这套机制的关键点在于 attention 的处理:
- 在 piecewise capture 期间attention 会暂时结束当前图捕获
- attention 本身不在 capture 时执行,而是把运行 attention 所需的张量、workspace 和参数保存下来
- replay 时,再基于最新的 `plan_info``q_cu_seq_lens``kv_cu_seq_lens` 执行 attention
这样做的直接原因就是chunked prefill 下 attention 的动态维度不只由 `num_tokens` 决定,而当前图分桶主要按 `num_tokens` 组织。把 attention 留在图外,才能避免“同样的 `num_tokens` 命中了 graph`batch_size``seq_lens` 已经变了”的 replay 错误。
因此Piecewise Graph 的本质不是“图内支持 attention 的所有动态逻辑”,而是:
- 让可稳定 capture 的部分进入图
- 让对动态 shape 更敏感的 attention 保持单独执行
- 通过统一指令序列保证整条执行路径顺序不变
### 3.3 结果
这套做法带来的结果和边界主要体现在以下几个方面。
#### 3.3.1 适用场景
- prefill
- chunked prefill
- 局部 break graph但整条路径仍希望获得图执行收益的场景
#### 3.3.2 优点
- 比“整图要么全成功、要么全放弃”更灵活
- 能显著扩大 Graph Mode 的覆盖范围
- 适合 attention 动态性强、而 MLP 等部分较稳定的模型结构
#### 3.3.3 限制
- 当前主要覆盖 prefill 类路径
- replay 依赖正确更新的 `attn_metadata.plan_info`
## 4. 多 Shape 复用的显存池
### 4.1 问题
Graph Mode 在多 shape 场景下面临两个同时出现的问题:
1. **捕获期显存随 shape 数量线性增长**
- 每次新的 shape 进入 capture运行时通常会为它分配独立的 memory buffer pool
- 由于图执行要求 capture 和 replay 之间地址稳定,这些缓冲区往往不能及时释放
- 结果是显存占用从期望的 `max(shape)` 变成了 `sum(shape)`
2. **输入侧也会重复占用显存**
- 如果每个 shape 都维护一套独立的 tokens、positions、seq_lens、block_tables 等输入 tensor
- 那么即使图本身可以复用,输入侧显存也会继续按 shape 累积
因此,多 Shape 复用显存池要解决的问题,不只是节省显存,而是要在支持动态新 shape 的同时,把显存行为从 `sum(shape)` 收敛到 `max(shape)`
具体目标包括:
1. **捕获期显存复用**:不同 shape 的 graph 共享同一组底层物理内存
2. **输入侧显存复用**:不同 shape 的 replay 共享同一组持久化输入 buffer
3. **地址不冲突**:每个已捕获 graph 仍然保有自己稳定的虚拟地址视图
4. **支持按需新建 shape**:新 shape 出现时可以继续 capture而不必推倒已缓存的 graph
5. **不增加 replay 开销**:内存复用主要发生在 capture 和输入准备阶段replay 路径尽量保持不变
#### 4.1.1 根本原因
- 以 CUDA 路径为例allocator 往往会为每次 graph capture 分配独立的 memory buffer pool
- Graph Mode 又要求 capture 和 replay 之间地址稳定,因此旧 shape 对应的 buffer 不能像普通 eager 临时内存那样及时回收
- 当不同 shape 持续进入 capture 时,显存占用就会从期望的 `max(shape)` 累积到 `sum(shape)`
#### 4.1.2 约束条件
- 不同 shape 的 graph 在当前前提下不会同时 replay
- 需要支持动态出现的新 shape按需进行 capture
- 不希望为了新 shape 推倒已缓存 graph或重新捕获已经可用的旧 shape
- replay 路径不希望因为显存复用而引入额外的图执行复杂度
#### 4.1.3 失败方案与技术难点
一个更直接的想法是:每次 capture 前重置分配指针,让不同 shape 复用同一段 virtual address space。
但这条路在工程上并不安全。allocator 通常按地址跟踪已经分配的 block如果新 capture 复用了旧地址,就可能覆盖原来的地址记录。这样一来,旧 graph 后续在 tensor 析构或释放时,就可能遇到地址记录缺失,甚至触发 `invalid device pointer` 一类错误。
这也暴露了问题的关键Graph Mode 真正需要稳定的是“每张 graph 看到的地址视图”,而真正需要复用的是“底层 physical memory”。因此技术难点不只是节省显存而是要同时满足
- 已捕获 graph 的 virtual address space 彼此不冲突
- 底层 physical memory 又能在多 shape 之间共享
- 新 shape 进入 capture 时,不破坏已有 graph 的可 replay 性
### 4.2 解法
多 Shape 复用的底层思路,不是让不同 shape 共用同一段虚拟地址,而是让它们共享同一组物理内存,同时为每个已捕获 graph 保留独立的虚拟地址视图。
这样设计的原因是:如果强行让不同 shape 复用同一段虚拟地址空间allocator 的地址跟踪会发生冲突,旧 graph 释放或析构时可能找不到原来的地址记录,最终导致错误。换句话说,真正需要复用的是底层物理显存,而不是 graph 看到的虚拟地址。
为了说明为什么 xLLM 选择“共享 physical memory + 独立 virtual address space”可以把几种方案对比如下
| 方案 | 内存复用机制 | 物理内存结果 | 地址冲突风险 / 规避方式 | 主要局限 |
|------|--------------|--------------|--------------------------|----------|
| vLLM | 共享 graph memory pool + 大 shape 优先 capture | 可能继续接近 `sum(shape)` | 依赖共享 memory pool 和空闲块管理,通常无显式地址冲突 | 复用效果受捕获顺序和 allocator 行为影响 |
| SGLang | 全局 graph memory pool + 大 shape 优先 capture | 可能继续接近 `sum(shape)` | 依赖全局 pool / graph-private pools 管理规避冲突 | 不同 shape 较多时显存仍会继续增长 |
| xLLM | 多 virtual address space 映射同一组 physical memory | 收敛到 `max(shape)` | 每张 graph 保留独立地址视图,从根源避免地址记录冲突 | 依赖底层运行时提供 VMM 和 allocator 接入能力 |
xLLM 的选择不是复用同一段 virtual address space而是让每个 graph 保留自己的地址视图,同时把真正可共享的部分下沉到底层 physical memory。
#### 4.2.1 输入 Tensor 多 Shape 复用
xLLM 先解决输入侧显存重复占用的问题。做法是:不再为每个 shape 分配独立输入 tensor而是预分配一组“最大 shape”对应的持久化 buffer然后让不同 shape 共享这组 buffer。
这组 buffer 通常包括:
- `tokens`
- `positions`
- `q_seq_lens`
- `kv_seq_lens`
- `block_tables`
- `new_cache_slots`
- `hidden_states`
replay 前的处理方式也比较直接:
1. 把当前请求的实际输入写入 buffer 前缀
2. 必要时对 padding 区域补零
3. 用 slice 视图构造当前 actual shape 的输入
这样做的结果是:输入侧显存不再按 shape 累加,而是稳定在一份 `max(shape)`
#### 4.2.2 捕获期显存复用
解决输入侧之后,还需要解决 graph capture 本身的显存累积问题。
xLLM 的核心做法是:
- 每次 capture 使用**新的虚拟地址空间**
- 不同虚拟地址空间映射到**同一组物理内存**
这样可以同时满足两件事:
1. **对 graph 来说地址稳定**:每个已捕获 graph 都看到自己的固定虚拟地址
2. **对物理显存来说只保留一份**:底层物理内存按最大需求扩张,不再按 shape 叠加
相比“复用同一虚拟地址”的思路,这种做法规避了地址跟踪冲突问题,因此更适合和现有 allocator 机制协作。
这套方案依赖统一的虚拟内存管理能力,核心操作包括:
- 保留一段新的虚拟地址空间
- 创建或扩张底层物理内存
- 将同一组物理内存映射到多个虚拟地址空间
- 在需要时执行 unmap、释放地址空间和访问权限设置
其中最关键的特性是:**同一组物理内存可以同时映射到多个虚拟地址空间**。这正是“不同 shape 的 graph 地址互不冲突,但底层物理显存仍然共享”的基础。
#### 4.2.2.1 地址映射关系
Graph 捕获期显存复用的核心映射关系如下:
![多虚拟地址空间映射同一物理内存](../../assets/shared_vmm_mapping_diagram.svg)
这张图表达的是:随着 shape 逐步增大,每次 capture 都切换到新的虚拟地址空间,但三个 virtual space 复用的是同一个 physical memory pool。不同 shape 只会使用其中不同比例的页,而底层物理显存总占用收敛到 `max(shape)`
### 4.3 结果
#### 4.3.1 内存效率指标
| 指标 | 显存不复用方案 | 显存复用方案 |
|------|----------------|--------------|
| 物理内存大小 | `sum(shape)` | `max(shape)` ✅ |
| 虚拟内存 | 多份 | 多份 |
| 稳定性 | 地址冲突风险 | 稳定 ✅ |
这里的关键变化是:物理显存收敛到 `max(shape)`,而 virtual address space 仍会随着已捕获 shape 数量增长。
#### 4.3.2 性能影响
- **Capture 阶段**:切换虚拟地址空间和扩张映射会带来额外开销
- **Replay 阶段**:无额外图执行开销,与常规 Graph Mode replay 基本一致
- **输入准备阶段**:增加一次写入持久化 buffer 和 slice 视图构造,但这部分通常远小于重复分配带来的成本
#### 4.3.3 虚拟地址空间开销
这套方案的主要代价之一,是 virtual address space 会随着已捕获 shape 数量持续增长。但这部分增长对应的是地址空间占用,而不是物理显存占用。
在 64 位系统下virtual address space 通常远大于单卡显存规模,因此对当前这类 graph capture 场景来说,它通常不是首要瓶颈。换句话说,这套方案是用更充足的地址空间,换取物理显存从 `sum(shape)` 收敛到 `max(shape)`
#### 4.3.4 结果验证
- 不同 shape 的 capture 可以共享同一组底层 physical memory
- 每次 capture 仍保有独立的 virtual address space而不是复用同一组地址视图
- 地址冲突类问题不再通过“复用同一虚拟地址”这条路径暴露出来
- 显存占用从按 shape 累积的 `sum(shape)` 收敛到 `max(shape)`
#### 4.3.5 实际效果
- 多 shape 场景下,显存行为从“随 capture 次数线性累积”转为“物理显存按最大 shape 收敛”
- Graph 复用的稳定性提升,规避了直接复用同一虚拟地址空间带来的地址冲突问题
- 设计上支持动态新 shape 按需 capture而不必推倒已有 graph 缓存
- replay 路径保持不变或基本不变,主要复杂度集中在 capture 和输入准备阶段
#### 4.3.6 适用前提与边界
- 虚拟地址空间会随着已捕获 shape 数量增长而增长,但物理显存只按最大需求扩张
- 方案依赖底层运行时提供虚拟地址空间管理、物理内存映射以及 allocator 接入能力
- 当前假设不同 shape 的 graph 不会同时 replay正是这个前提让底层物理内存能够安全共享
- 当前实现仍以生命周期内统一持有物理内存和虚拟空间为主,不做细粒度动态释放
## 5. 参考文档
- [ACLGraph Capture / Replay 机制说明](https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/83RC1alpha002/appdevg/acldevg/aclcppdevg_000519.html)
- [CudaGraph Capture 机制参考](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cuda-graphs)
- [vLLM 图执行实现参考](https://github.com/vllm-project/vllm/blob/main/vllm/v1/worker/gpu/cudagraph_utils.py)
- [SGLang 图执行实现参考](https://github.com/sgl-project/sglang/blob/main/python/sglang/srt/model_executor/cuda_graph_runner.py)

View File

@@ -0,0 +1,27 @@
# 代码结构
```
├── xllm/
| : 主代码目录
│ ├── api_service/ # api服务化实现
│ ├── core/
│ │ : xllm核心功能代码目录
│ │ ├── common/
│ │ ├── distributed_runtime/ # 分布式PD服务实现
│ │ ├── framework/ # 引擎执行模块实现
│ │ ├── kernels/ # 国产芯片kernels适配实现
│ │ ├── layers/ # 模型层实现
│ │ ├── platform/ # 多平台兼容层
│ │ ├── runtime/ # worker/executor角色实现
│ │ ├── scheduler/ # 批调度与PD调度实现
│ │ └── util/
│ ├── function_call # function call实现
│ ├── models/ # 模型实现
│ ├── processors/ # 多模态模型预处理实现
│ ├── proto/ # 通信协议
│ ├── pybind/ # python接口
| └── server/ # xLLM服务实例
├── examples/ # 服务调用示例
├── tools/ # NPU Timeline生成工具
└── xllm.cpp # xLLM启动入口
```

View File

@@ -0,0 +1,396 @@
# xLLM Ascend TileLang Kernel 开发指南
本文说明在 xLLM 中新增或修改 Ascend TileLang kernel 的开发方式。示例全程使用当前的 `rope` kernel。
相关目录:
- Python kernel 定义:`xllm/xllm/compiler/tilelang/targets/ascend/kernels`
- NPU runtime wrapper`xllm/xllm/core/kernels/npu/tilelang`
构建和测试应在 NPU 容器中执行。
## 1. 先判断修改类型
- 新增 `specialization`
- 给现有 kernel 增加一组新的编译参数组合
- 仍复用同一个 wrapper、同一套 runtime dispatch 字段和同一套 C ABI
- 典型动作是修改 `DISPATCH_SCHEMA``SPECIALIZATIONS`
- 新增 `kernel`
- 新增一个新的逻辑算子
- 典型动作是新增 Python kernel 文件、wrapper C++ 文件和一条 CMake 接线
`rope` 来说:
-`SPECIALIZATIONS` 增加一项 `{"variant_key": "...", "head_dim": ..., "rope_dim": ..., "dtype": ...}`,这是新增 `specialization`
- 新增一个新的 `xxx_wrapper.cpp` 对外接口,这是新增 `kernel`
## 2. 开发顺序
推荐按下面顺序开发:
1.`rope.py` 这类 Python 文件里先写 TileLang kernel 实现
2. 实现 `generate_source(...)`,把 kernel lower 成 Ascend-C 源码
3. 声明 `DISPATCH_SCHEMA``SPECIALIZATIONS`
4. 先生成一次 `registry.inc` 并查看内容
5. 再写或修改 wrapper 里的 runtime specialization 构造逻辑
6. 接入 CMake 并运行测试
这个顺序的重点是:
- 先把 kernel 本身实现出来
- 再把 runtime dispatch schema 固定下来
- 最后根据生成出来的 `registry.inc` 写 wrapper
## 3. 编写 Python Kernel
`rope.py` 为例Python 侧可以按三层理解:
- `build_rope_kernel(...)`kernel 实现
- `generate_source(...)`AOT 导出
- `RopeKernel`:注册 kernel并声明 dispatch schema 与编译实例
### 3.1 实现 `build_rope_kernel(...)`
`build_rope_kernel(...)` 才是 TileLang kernel 的实现主体。这里负责写:
- `@T.prim_func`
- 输入输出张量 shape
- `with T.Kernel(...)` 下的并行任务组织
- UB 分配和实际计算逻辑
`rope.py` 的精简骨架如下:
```python
def build_rope_kernel(
head_dim: int,
rope_dim: int,
vec_core_num: int,
ub_buffer_bytes: int,
):
task_num = vec_core_num
m_num = vec_core_num // 2
@T.prim_func
def rope_in_place_kernel(...):
with T.Kernel(m_num, is_npu=True) as (cid, vid):
task_id = cid * 2 + vid
...
return rope_in_place_kernel
```
这里的 `head_dim``rope_dim` 是这一组实现真正依赖的编译参数。
`rope` 这类 vector kernel 还要遵守当前 AOT 使用方式下的固定任务约定。当前路径是 AOT 编译kernel launch 的 `block_num` 会在编译时固定下来,因此:
- 运行时输入 shape 不影响 kernel launch 的 `block_num`
- 运行时输入 shape 只影响固定任务之间的 workload 切分
当前 `rope.py` 的约定是:
```python
task_num = vec_core_num
m_num = vec_core_num // 2
with T.Kernel(m_num, is_npu=True) as (cid, vid):
task_id = cid * 2 + vid
```
这表示:
- `cid` 范围是 `[0, vec_core_num // 2)`
- `vid` 范围是 `[0, 2)`
- 总任务数固定为 `task_num = vec_core_num`
因此,`rope.py` 在推导单个 specialization 的编译 token 数时,也按固定任务数计算:
```python
max_rows_num_in_ub = _derive_max_rows_num_in_ub(...)
compile_num_tokens = task_num * max_rows_num_in_ub
```
### 3.2 实现 `generate_source(...)`
`generate_source(...)` 负责把上面的 TileLang kernel lower 成最终源码。导出层的职责,是把一组 specialization 参数转换成可编译的 Ascend-C 源码。
`rope` 来说,核心逻辑如下:
```python
@staticmethod
def generate_source(head_dim: int, rope_dim: int, dtype: str) -> str:
vec_core_num = detect_vec_core_num()
tilelang_kernel = build_rope_kernel(
head_dim=head_dim,
rope_dim=rope_dim,
vec_core_num=vec_core_num,
ub_buffer_bytes=FIXED_UB_BUFFER_BYTES,
)
with tilelang.tvm.transform.PassContext(...):
kernel = tilelang.engine.lower(tilelang_kernel)
return kernel.kernel_source
```
这里的规则是:
- `generate_source(...)` 的输入来自当前这组 `SPECIALIZATIONS`
- `generate_source(...)` 内部调用 `build_rope_kernel(...)`
- 返回值是 lower 后的源码字符串
### 3.3 声明 `DISPATCH_SCHEMA` 与 `SPECIALIZATIONS`
当 kernel 实现和导出层写完后,再通过 `@register_kernel` 类把它接入框架。
`rope.py` 当前的最小模板如下:
```python
from ....common.spec import DispatchField, TilelangKernel, register_kernel
@register_kernel
class RopeKernel(TilelangKernel):
DISPATCH_SCHEMA = [
DispatchField("head_dim", "int32"),
DispatchField("rope_dim", "int32"),
DispatchField("dtype", "dtype"),
]
SPECIALIZATIONS = [
{
"variant_key": "hd128_rd128_bf16",
"head_dim": 128,
"rope_dim": 128,
"dtype": "bf16",
},
{
"variant_key": "hd576_rd64_bf16",
"head_dim": 576,
"rope_dim": 64,
"dtype": "bf16",
},
]
@staticmethod
def generate_source(head_dim: int, rope_dim: int, dtype: str) -> str:
...
```
这里要分清楚两个概念:
- `DISPATCH_SCHEMA`
- 定义 runtime specialization 的字段名、顺序和类型
- 是 C++ 侧 specialization struct、builder 和查表接口的单一真相源
- `SPECIALIZATIONS`
- 表示要实际编译出的实例集合
- 每一项都对应一个 variant
规则如下:
- `DISPATCH_SCHEMA` 中每个字段都必须出现在每一项 `SPECIALIZATIONS`
- `SPECIALIZATIONS` 中可以有额外字段,这些字段会传给 `generate_source(...)`,但不会进入 runtime dispatch schema
- `variant_key` 是这一组 specialization 的唯一标识
- `DISPATCH_SCHEMA``SPECIALIZATIONS` 必须与 runtime specialization 一一对应
`rope` 来说runtime dispatch 维度是:
- `head_dim`
- `rope_dim`
- `dtype`
所以这三个字段必须同时出现在:
- `DISPATCH_SCHEMA`
- 每一项 `SPECIALIZATIONS`
构建时Ascend build 会根据主构建路径传入的 `--device a2|a3` 解析实际使用的 `bisheng_arch`
### 3.4 查看生成的 Ascend-C 源码
在调试 `build_rope_kernel(...)` 的实现细节,或者比较不同 kernel 写法对最终代码生成的影响时,建议通过公共入口 `compile-kernels` 重新生成产物,再查看对应 specialization 的 Ascend-C 源码。
`rope` 来说,可以先固定:
- `head_dim=576`
- `rope_dim=64`
- `dtype=bf16`
然后重新生成 `rope` 的编译产物:
```bash
python xllm/compiler/tilelang_launcher.py compile-kernels \
--target ascend \
--device a3 \
--output-root /tmp/tilelang_debug \
--kernels rope \
--force
```
这里建议带上 `--force`,保证源码和 object 会按当前修改重新生成,不直接命中旧 cache。
这里把 `--output-root` 指到独立的调试目录 `/tmp/tilelang_debug`,这样当前命令只会在这个目录下生成 `rope` 的调试产物,不会和主构建目录里的其他 kernel 产物混在一起。
执行后,可以直接查看对应 specialization 的源码中的入口函数、UB 分配和向量计算逻辑:
```bash
sed -n '1,200p' \
/tmp/tilelang_debug/targets/ascend/rope/hd576_rd64_bf16/rope_hd576_rd64_bf16_kernel.cpp
rg -n 'extern "C"|__global__|alloc_ub|alloc_shared|g_tilingKey' \
/tmp/tilelang_debug/targets/ascend/rope/hd576_rd64_bf16/rope_hd576_rd64_bf16_kernel.cpp
```
如果要比较两种 kernel 写法的差异,做法是保持 specialization 不变,在修改前后各执行一次 `compile-kernels --force`,再对生成的 `.cpp` 做 diff
```bash
cp /tmp/tilelang_debug/targets/ascend/rope/hd576_rd64_bf16/rope_hd576_rd64_bf16_kernel.cpp \
/tmp/rope_before.cpp
diff -u /tmp/rope_before.cpp \
/tmp/tilelang_debug/targets/ascend/rope/hd576_rd64_bf16/rope_hd576_rd64_bf16_kernel.cpp
```
这样可以把“specialization 变化”和“kernel 实现变化”分开看。
执行后,可以重点查看这些文件:
- `/tmp/tilelang_debug/targets/ascend/rope/hd576_rd64_bf16/rope_hd576_rd64_bf16_kernel.cpp`
- `/tmp/tilelang_debug/targets/ascend/rope/registry.inc`
- `/tmp/tilelang_debug/targets/ascend/rope/manifest.json`
这三类文件分别对应:
- 某个 specialization 的最终 Ascend-C 源码
- wrapper 会直接包含的 runtime dispatch 接口
- 当前 kernel 的全部编译产物记录
调试顺序建议是:
1. 先执行 `compile-kernels --force` 重新生成当前 kernel 的产物
2. 查看对应 specialization 的 `.cpp`,分析代码生成结果
3. 再看 `registry.inc``manifest.json` 是否符合预期
4. 最后通过 `rope_wrapper_test` 看完整接入后的结果和性能
## 4. 修改 Wrapper
新增 `kernel` 时,需要新增 wrapper。新增 `specialization` 时,只有 runtime specialization 语义变化,才需要同步修改 wrapper。
`rope_wrapper.cpp` 来说,人工需要保留的内容是:
- tensor shape、dtype、layout 校验
- 把输入整理成 `x_rows / sin_rows / cos_rows`
- 从 tensor 构造 runtime specialization
- 组装 launch 参数并调用 `entry->fn(...)`
### 4.1 `registry.inc` 会自动生成什么
`registry.inc` 由 Python 侧的 `DISPATCH_SCHEMA``SPECIALIZATIONS` 和导出出来的 Ascend-C ABI 自动生成。
`rope` 来说,生成内容包括:
- `RopeSpecialization`
- `RopeHeadDim`
- `RopeRopeDim`
- `RopeDType`
- `RopeKernelFn`
- `make_rope_specialization(...)`
- `find_rope_kernel_entry(...)`
- `available_rope_variant_keys()`
`rope_wrapper.cpp` 来说,`registry.inc` 会直接提供 `RopeSpecialization``operator==(...)``RopeKernelFn` 等 dispatch 相关定义。`dtype` 转换统一使用公共函数 `to_tilelang_dtype(...)`
### 4.2 wrapper 里真正要写的东西
`rope_wrapper.cpp` 中最关键的人工逻辑,是从 tensor 构造 runtime specialization。当前写法如下
```cpp
RopeSpecialization build_runtime_specialization(const torch::Tensor& x_rows) {
return make_rope_specialization(
RopeHeadDim{static_cast<int32_t>(x_rows.stride(0))},
RopeRopeDim{static_cast<int32_t>(x_rows.size(1))},
RopeDType{to_tilelang_dtype(x_rows.scalar_type())});
}
```
`rope` 而言:
- `head_dim` 对应 `x_rows.stride(0)`,也就是 kernel 使用的 `x_stride`
- `rope_dim` 对应 `x_rows.size(1)`
- `dtype` 对应 `x_rows.scalar_type()`
运行时路径是:
1. wrapper 把输入整理成 `x_rows / sin_rows / cos_rows`
2. `build_runtime_specialization(...)``x_rows` 构造 specialization
3. `find_rope_kernel_entry(...)` 在静态 registry 中精确匹配
4. 命中后通过 `entry->fn(...)` 调用实际编译出来的符号
当前查找策略是线性扫描加精确匹配。只要 `head_dim``rope_dim``dtype` 有一个不一致,就不会命中。
所以新增 `specialization` 时,要重点核对的是:
- Python 侧 `DISPATCH_SCHEMA` 的字段语义
- Python 侧 `SPECIALIZATIONS` 的字段值
- wrapper 里 `build_runtime_specialization(...)` 构造出的字段值
这三者必须完全对齐。
### 4.3 先生成并查看 `registry.inc`
在写或修改 wrapper 之前,先生成一次 `registry.inc` 并查看内容。重点看:
- 生成出来的 `RopeSpecialization` 字段顺序是否符合预期
- 生成出来的字段包装类型名是否符合预期
- `make_rope_specialization(...)` 的参数顺序是什么
- 生成出来的 entry symbol 名称是什么
`registry.inc` 是 wrapper 的直接契约,先看它,再写 wrapper。
## 5. 修改 CMake
新增 `kernel` 时,在 `xllm/xllm/core/kernels/npu/tilelang/CMakeLists.txt` 中接入这个 kernel。
CMake 接入统一通过高层 helper 完成:
- `tilelang_register_runtime_kernel(NAME <kernel> WRAPPER_SRCS <srcs...>)`
`rope` 为例,最小模板如下:
```cmake
tilelang_register_runtime_kernel(
NAME rope
WRAPPER_SRCS rope_wrapper.cpp
)
```
这条 helper 会完成:
-`TILELANG_GENERATED_ROOT/targets/ascend/<kernel>/manifest.json` 推导 manifest 路径
- 导入 manifest
- 把该 kernel 的 wrapper source 和 compiled objects 加入 `tilelang_kernels`
- 自动追加 `XLLM_TL_<KERNEL>_REGISTRY_INC=...` compile definition
因此,新增一个 runtime kernel 时CMake 侧主要就是两件事:
1. 保证 Python 侧已经能生成该 kernel 的 manifest
2.`tilelang` 的 CMakeLists 里新增一条 `tilelang_register_runtime_kernel(...)`
日常新增 kernel 时,直接在 CMake 中增加一条 `tilelang_register_runtime_kernel(...)``tilelang_import_kernel_manifest(...)` 作为这条高层 helper 的实现基础,保留在底层。
## 6. 验证
推荐按下面顺序验证:
1. 先编译 TileLang kernel并查看生成的 `registry.inc`
2. 再跑完整的 wrapper 测试
常用命令:
```bash
python xllm/compiler/tilelang_launcher.py compile-kernels \
--target ascend \
--device a3 \
--output-root build/cmake.linux-aarch64-cpython-311/xllm/compiler/tilelang \
--kernels rope
python setup.py test --test-name rope_wrapper_test --device a3
```
第一条命令用于生成 `manifest.json``registry.inc` 和 object第二条命令用于验证完整接入路径。

View File

@@ -0,0 +1,33 @@
# 异步调度
## 背景
大模型推理过程可划分为3个阶段包括CPU执行调度准备模型输入阶段device计算阶段CPU处理输出阶段。
由于解码操作的序列性step-i+1 的输入需要依赖 step-i 的输出结果,
上述3个阶段需要按顺序串行执行导致在CPU执行阶段1和3的时候device侧空闲等待出现空泡资源利用不充分。
## 功能介绍
xLLM在框架层支持了异步调度功能在device执行 step-i 计算的同时提前让CPU执行 step-i+1 的调度操作device在完成 step-i 计算后可立即开始 step-i+1 的计算,从而消除空泡。
具体地CPU在发起 step-i 计算调用后不等待device计算完成为 step-i 的请求构造fake token使用fake token执行 step-i+1 的调度操作分配KV Cache等device在启动 step-i+1 的计算时,用 step-i 计算出来的true token替换fake token保证计算的正确性。CPU在另外的线程中同步处理 step-i 的结果返回给client。
整体架构如图实现中CPU侧执行阶段1和阶段3的操作分别采用了不同的线程池rpc等函数调用采用C++ future和promise非阻塞调用实现全异步runtime。![异步调度](../../assets/async_schedule_architecture.jpg)
## 使用方式
xLLM中提供了gflags参数`enable_schedule_overlap`默认false如需开启在xLLM的服务启动脚本中设置为true即可示例如下
```shell
--enable_schedule_overlap=true
```
## 性能效果
- 异步调度开启后两个step之间的device空闲时在200us左右基本类似一个kernel launch的时间。
- 在DeepSeek-R1-Distill-Qwen-1.5B模型上限制TPOT 50ms吞吐 **提升17%**
!!! warning "注意"
- 异步调度功能会在服务端额外计算一个step当使用场景中输出token数量较少或是类似embedding模型只一次性输出的场景会影响服务端吞吐所以强制关闭异步调度。
- VLM模型正在适配中暂时会强制关闭异步调度。

View File

@@ -0,0 +1,9 @@
# 基础知识
- xLLM使用一卡一进程模式多卡之间使用rpc进行函数调用模型计算过程中的数据通信使用device集合通信库。
- HCCL/LCCL是高性能集合通信提供单机多卡以及多机多卡间的数据并行、模型并行集合通信方案。

View File

@@ -0,0 +1,17 @@
# ChunkedPrefill调度器
## 功能介绍
xLLM支持chunked prefill调度策略。Chunked prefill是一种优化大语言模型推理的技术将长prompt分割成多个较小的chunk进行分批处理而不是一次性处理整个prompt。
这种方法可以有效降低显存峰值使用量提高Device利用率并且能够更好地与decode阶段的请求进行调度和混合处理。
## 使用方式
上述策略已在xLLM实现并向外暴露gflag参数控制功能的开关。
- 开启chunked prefill并设置chunked_size如果不手动设置chunked size则默认等于max_tokens_per_batch。
```bash
--enable_chunked_prefill=true
--max_tokens_per_chunk_for_prefill=20480 # optional
```
## 性能效果
开启chunked_prefill之后在Qwen3-8B模型上限制TPOT 50msTTFT时延 **下降46%**

View File

@@ -0,0 +1,7 @@
# Continuous调度器
## 功能介绍
xLLM实现了支持continuous batching的调度策略continuous_batch是一种动态批处理策略它不等待批次填满而是在有请求时就开始处理同时持续接收新请求并将其加入正在执行的批次中从而在保持高吞吐量的同时显著降低延迟。
## 使用方式
xLLM提供了continuous batching调度策略。目前`enable_chunked_prefill`默认值为true开箱即用时默认调度器是chunked prefill调度器。

View File

@@ -0,0 +1,74 @@
# PD分离
## 背景
LLM在线推理服务通常需要满足TTFT和TPOT两项性能指标而传统的Contiguous Batching调度策略将Prefill和Decode请求混合在一起调度导致P和D会互相抢占计算资源影响性能指标无法最大程度的利用计算资源。为解决上述矛盾将Prefill和Decode两阶段拆分到独立的计算资源并行执行从而同时降低TTFT和TPOT并提升吞吐量。
## 功能介绍
xLLM PD分离功能主要通过以下三个模块实现
- **etcd**: 存储实例信息等元数据
- **xLLM Service**: 调度请求和管理所有计算实例
- **xLLM**: 请求计算实例
整体架构图如下:
![xLLM PD分离架构图](../../assets/pd_architecture.jpg)
## 功能使用示例
### 使用准备
#### 安装相关依赖
- **xLLM**: 参见[安装编译](../getting_started/quick_start.md)
- **xLLM Service**: 参见[PD分离部署](../getting_started/disagg_pd.md)
### 启动PD分离服务
1. 启动etcd
```
./etcd
```
2. 启动xLLM Service
```bash
ENABLE_DECODE_RESPONSE_TO_SERVICE=true ./xllm_master_serving --etcd_addr="127.0.0.1:12389" --http_server_port 28888 --rpc_server_port 28889 --tokenizer_path=/path/to/tokenizer_config_dir/
```
3. 启动xLLM
4. 以Qwen2-7B为例
- 启动Prefill实例
```bash
/path/to/xllm --model=Qwen2-7B-Instruct \
--port=8010 \
--devices="npu:0" \
--master_node_addr="127.0.0.1:18888" \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--enable_disagg_pd=true \
--instance_role=PREFILL \
--etcd_addr=127.0.0.1:12389 \
--transfer_listen_port=26000 \
--disagg_pd_port=7777 \
--node_rank=0 \
--nnodes=1
```
- 启动Decode实例
```bash
/path/to/xllm --model=Qwen2-7B-Instruct \
--port=8020 \
--devices="npu:1" \
--master_node_addr="127.0.0.1:18898" \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--enable_disagg_pd=true \
--instance_role=DECODE \
--etcd_addr=127.0.0.1:12389 \
--transfer_listen_port=26100 \
--disagg_pd_port=7787 \
--node_rank=0 \
--nnodes=1
```
需要注意:
- PD分离需要读取`/etc/hccn.conf`文件,确保将物理机上的该文件映射到了容器中
- `etcd_addr`需与`xllm_service`的`etcd_addr`相同
!!! warning "注意事项"
PD分离目前不支持开启prefix cache及chunked prefill功能需要通过以下参数关闭
``` shell
--enable_prefix_cache=false
--enable_chunked_prefill=false
```

View File

@@ -0,0 +1,35 @@
# MoE负载均衡EPLB
## 背景介绍
MoE模型依赖动态路由分配tokens给专家但实际部署中因数据分布不均导致专家负载失衡部分过载、部分闲置。专家冗余调整如新增/删除副本需要消耗额外显存并可能因权重迁移影响推理延迟如何高效、平滑地完成是一大挑战。为此采用专家冗余策略复制热点专家结合分层和全局动态负载均衡实现了动态的MOE负载均衡。
## 功能介绍
xLLM MoE负载均衡EPLB功能主要通过以下三个模块实现
- eplb manager: 负责专家负载并收集并管理专家分布更新更新,采用逐层更新机制,根据专家负载变化情况判断是否更新该层。
- eplb excutor: 实际专家分布更新执行器。
- eplb policy: 新专家负载表生成策略。
整体架构图如下:
![xLLM eplb](../../assets/eplb_architecture.png)
## 使用方式
只需在启动 xLLM 时加上下面的 gflag 参数即可:
替换为实际的Device个数 ep_size要与device个数保持一致
- xLLM中提供了gflags参数`enable_eplb`默认false如需开启动态专家负载均衡在xLLM的服务启动脚本中设置为true即可。
- `expert_parallel_degree``ep_size`为moe相关参数`expert_parallel_degree`需要设置为`2``ep_size`要与实际NPU/GPU卡个数保持一致。参考 [moe_params](./moe_params.md)
- `eplb_update_interval`为专家分布更新时间间隔单位为妙默认值为1000.
- 专家分布更新采用根据专家负载的逐层更新机制,当某一层专家的前后两次的负载相似度小于`eplb_update_interval`时选择更新该层默认值为1取之范围为(0,1)。
```bash
--enable_eplb=true
--expert_parallel_degree=2
--ep_size=16
--eplb_update_interval=2000
--eplb_update_threshold=0.9
```
## 未来工作
* 采用更加细粒度的专家更新机制。
* 与调度层结合通过请求batch的重组实现更好的负载均衡。

View File

@@ -0,0 +1,34 @@
# 全局多级KV Cache
## 背景
大型语言模型LLM解码阶段因自回归生成需频繁访问历史KV缓存导致显存带宽成为瓶颈。随着模型规模与上下文窗口扩大如128K Token消耗超40GB显存单卡显存压力剧增。现有方案如vLLM在长上下文场景下存在明显局限预填充耗时激增、解码阶段显存带宽争抢严重为满足SLOTTFT<2s, TBT<100ms常需过量预留资源致使GPU利用率不足40%且难以利用跨服务器资源为此我们提出分布式全局多级KV缓存管理系统采用存算一体架构以突破单机资源限制
## 功能介绍
xLLM 全局KV Cache功能主要通过以下三个模块实现
- etcd: 集群服务注册负载信息同步及全局缓存状态管理
- xLLM Service: 调度请求和管理所有计算实例
- xLLM: 请求计算实例
整体架构图如下
![xLLM 全局多级KV Cache](../../assets/globalkvcache_architecture.png)
## 功能使用示例
### 使用准备
#### 安装相关依赖
- **xLLM**: 参见[快速开始](../getting_started/quick_start.md)
- **xLLM Service**: 参见[PD分离部署](../getting_started/disagg_pd.md)
### 使用方式
1. etcd启动配置
```bash
./etcd --listen-peer-urls=http://0.0.0.0:10999 --listen-client-urls=http://0.0.0.0:10998
```
2. xLLM Service启动配置
```bash
./xllm_master_serving --etcd_addr="127.0.0.1:10998" --http_server_port 28888 --rpc_server_port 28889 --tokenizer_path=/path/to/tokenizer_config_dir/
```
3. xLLM启动添加上下面的 gflag 参数即可
```bash
--enable_service_routing=true
--enable_cache_upload=true
# PD分离暂时不支持全局KVCache管理
--enable_disagg_pd=false
```

View File

@@ -0,0 +1,77 @@
# Graph Mode
## 概述
xLLM 支持 Graph Mode通过预捕获计算图并在后续执行中重放减少 CPU 开销并提高推理性能。Graph Mode 在不同硬件平台上均有对应实现。
## 功能介绍
为了优化 Host 侧调度性能,图模式通过在 CPU 一次提交大任务后,设备内部流式执行小 kernel显著降低启动时间和设备气泡。
在 xLLM 引擎中Graph Mode 实现了以下特性:
### 动态维度参数化
- 将除 num_tokens 以外的关键动态维度作为整图输入参数,包括 batch_size、kv_seq_lens、q_seq_lens、block_table_size 等,从而提高灵活性。在进行图的内存分配和内核配置时,利用这些动态参数计算实际所需值。在图启动阶段,将上述实际参数传入,以确保 kernel 能够使用正确的 stride 访问数据。
### Piecewise Graph
- 当部分算子不支持 graph 导致整图无法捕获break graph对 break 之后的各段piece分别捕获 graph。这样在无法整图捕获的情况下仍能尽可能获得 graph mode 的收益,常用于 prefill、chunked_prefill 等场景。
### 多 shape 复用的显存池
- 为了避免不同 shape 的 graph capture 分别占用独立显存,我们让不同 capture 使用不同虚拟地址空间,并共享同一组底层物理内存;同时,输入 tensor 通过持久化 buffer 与 slice 方式复用。
## 使用方式
上述功能已在 xLLM 引擎内部实现,通常通过 gflags 参数控制。
最小配置只需要开启 `enable_graph`,用于打开 decode 阶段的 Graph Mode
```shell
--enable_graph=true
```
常见的配套开关包括:
- `enable_graph`:开启 decode 阶段的 Graph Mode 基础能力
- `enable_prefill_piecewise_graph`:开启 prefill 阶段的 Piecewise Graph
- `enable_graph_mode_decode_no_padding`decode 阶段按实际 `num_tokens` 建图,而不是按 padding 后的 shape 建图
- `max_tokens_for_graph_mode`:限制 Graph Mode 覆盖的最大 token 数;`0` 表示不限制
如果希望同时开启 decode Graph 和 prefill Piecewise Graph示例如下
```shell
--enable_graph=true \
--enable_prefill_piecewise_graph=true \
--max_tokens_for_graph_mode=2048
```
如果需要在 decode 阶段启用无 padding 建图,可额外开启:
```shell
--enable_graph=true \
--enable_graph_mode_decode_no_padding=true
```
更完整的参数说明可参考 [CLI 参数说明](../cli_reference.md)。
## 性能效果
- 开启 Graph Mode 后,在 Qwen3-0.6B 和 Qwen3-1.7B 等模型上decode 阶段吞吐 **提升约 8%10%**
## 模型支持
下表列出目前各模型在 ACLGraph、CudaGraph、MLUGraph 上的支持情况。
| 模型 | ACLGraph | CudaGraph | MLUGraph |
|------|----------|-----------|----------|
| Qwen3/Qwen3-MoE | ✅ | ✅ | ✅ |
| DeepseekV3.2 | ✅ | | ✅ |
| GLM4.5/4.6/4.7 | ✅ | | |
| Qwen2.5-VL | | | ✅ |
| Qwen3-VL/Qwen3-VL-MoE | ✅ | | |
| GLM4V | ✅ | | |
| GLM4V-MoE | ✅ | | |
## 相关文档
- 更详细的 Graph Mode 设计与实现说明(含 ACL Graph / CUDA Graph 基本原理、动态维度参数化、Piecewise Graph 与多 shape 复用内存方案)见:[Graph Mode 设计文档](../design/graph_mode_design.md)

View File

@@ -0,0 +1,41 @@
# GroupGEMM算子优化
# 背景
混合专家(Mixture of Experts, MoE)架构已成为扩展大规模语言模型的重要范式其核心思想是将输入token动态路由至不同的专家子网络进行处理。在推理过程中GroupGEMM算子是MoE架构的关键计算单元负责高效执行多个专家矩阵乘法的并行计算且在整个推理耗时中占据主导地位。
## 功能介绍
结合当前GroupGEMM的性能瓶颈为I/O受限提出了一种优化方案通过索引重排替代数据拷贝取消了对token向量的多次复制改为维护专家分配的索引表。通过该行号索引直接将token映射到相应的专家计算单元并将token的分配调度与矩阵乘法融合为一个单一的kernel。
## 用户接口
### 算子直调API
```c++
aclnnStatus aclnnIndexGroupMatmulGetWorkspaceSize(
const aclTensorList *x,
const aclTensorList *weight,
const aclTensorList *scale,
const aclTensorList *perTokenScale,
const aclTensor *groupList,
const aclTensorList *out,
uint64_t *workspaceSize,
aclOpExecutor **executor);
aclnnStatus aclnnIndexGroupMatmul(
void *workspace,
uint64_t workspaceSize,
aclOpExecutor *executor,
aclrtStream stream);
```
- `x`: 输入的张量列表,包含待处理的数据。
- `weight`: 权重张量,包含模型的参数。
- `scale`: 缩放因子,用于调整输入张量的值。
- `perTokenScale`:每个token的缩放因子用于动态调整。
- `groupList`: 专家组列表,指示哪些专家参与计算。
- `out`: 输出张量列表,存储计算结果。
## 性能效果
![groupmatmul](../../assets/groupmatmul_performance.png)
* 优化后的GroupMatmul算子在计算时间上表现出明显的优势尤其是在k为128m为64情况下如图所示优化后算子计算延时 **减少50%**

View File

@@ -0,0 +1,17 @@
# EP并行
## 背景介绍
在部署DeepSeek-R1 671B参数规模模型时传统分布式部署面临、显存利用率低、通信开销大、硬件成本高昂等核心瓶颈因此需要引入ep并行。
+ 在同等资源下单张卡上的Expert越少可用于KV Cache的显存越多可Cache的token个数越多。
+ 因MLA的特性同等资源下TP Size越小冗余的KV Cache就越少可Cache的token个数越多。
+ 采用大规模ep并行部署可以将同一个expert的token计算集中到同一设备上提高硬件利用率
## 参数设置
+ dp_size设置Attention部分的dp规模大小默认值为1可设置为2的指数倍当dp_size不等于卡数时dp组内为tp并行.
+ ep_size设置MoE部分的ep规模大小默认值为1可设置为2的指数倍当ep_size不等于卡数时dp组内为tp并行.
+ expert_parallel_degree ep并行相关参数不开启ep时默认设置为0开启ep时默认为1此时为ep level1当ep_size等于卡数时可以设置为2开启ep level2.
支持 MLA 的模型会自动开启 MLA不再需要手动配置。
## 方案设计
+ 当开启ep时默认为ep level1此时attn与moe部分计算完成后通过All Gather全卡通讯将数据发送到下一阶段以64卡attn部分dp32tp2 moe部分ep32tp2为例执行流程如下
![Alt text](../../assets/moe_eplevel1.jpg)
+ 当ep_size设置为卡数时可以开启ep level2此时attn部分与moe部分之间通讯变为ALL2ALL只向需要的卡发送数据降低通讯量与通讯开销以64卡部署为例执行流程如下
![Alt text](../../assets/moe_eplevel2.jpg)

View File

@@ -0,0 +1,149 @@
# MTP投机推理
## 背景
MTP是一种创新的推理阶段加速技术专注于解决大语言模型生成过程中的效率瓶颈。MTP的本质是通过预训练阶段的特殊设计为推理阶段提供高效的草稿token预测能力从而显著提升模型的生成速度。其核心价值在于平衡推理效率与输出质量为大语言模型的长序列生成问题提供了一种高效的解决方案最终实现推理性能的优化。
## 功能介绍
MTP在推理加速方面具有以下核心功能
- **高效草稿生成**使用低成本的MTP结构快速生成草稿token这些草稿token作为主模型验证的基础大幅减少了传统自回归生成的计算开销。
- **批量验证机制**主模型能够同时批量验证多个MTP生成的草稿token而不必逐个生成和验证显著提升了推理速度。
- **高采样准确率**MTP解决了Eagle、Medusa等现有推理加速方法中的关键痛点——训练后生成的draft模块token采样率低的问题。由于MTP在预训练阶段就优化了草稿生成能力其生成的token具有更高的准确率减少了主模型的验证负担。
- **推理延迟降低**通过预先生成多个可能的后续tokenMTP有效降低了模型生成长文本时的累积延迟使用户体验更加流畅。
- **资源消耗优化**相比其他推理加速技术MTP在保持加速效果的同时对计算资源的额外需求更少适合在资源受限环境下部署。
MTP技术为大语言模型的推理阶段提供了一种全新的效率优化方案特别适合需要快速响应的实时应用场景代表了语言模型推理优化的重要发展方向。
!!! note "模型支持"
目前支持以下模型的MTP结构导出
- DeepSeek-V3 (输入 model_type: deepseek_v3, 导出 MTP model_type: deepseek_v3_mtp)
- DeepSeek-V3.2 (输入 model_type: deepseek_v3, 导出 MTP model_type: deepseek_v32_mtp)
- DeepSeek-R1 (输入 model_type: deepseek_v3, 导出 MTP model_type: deepseek_v3_mtp)
- GLM4 MoE (如 GLM-4.5-Air, 导出 MTP model_type: glm4_moe_mtp)
注意:
- DeepSeek V3 和 R1 的输入 model_type 都是 "deepseek_v3",导出的 MTP 模型 model_type 为 "deepseek_v3_mtp"
- DeepSeek V3.2 的输入 model_type 是 "deepseek_v3"(但可通过 index_head_dim 等字段自动识别),导出的 MTP 模型 model_type 为 "deepseek_v32_mtp"
## 使用示例
### 导出模型
脚本会自动检测模型类型,也可以手动指定。
#### DeepSeek-V3
```bash
python3 tools/export_mtp.py \
--input-dir /path/to/DeepSeek-V3 \
--output-dir /path/to/DeepSeek-V3-mtp
```
#### DeepSeek-V3.2
```bash
python3 tools/export_mtp.py \
--input-dir /path/to/DeepSeek-V3.2 \
--output-dir /path/to/DeepSeek-V3.2-mtp
```
#### DeepSeek-R1
```bash
python3 tools/export_mtp.py \
--input-dir /path/to/DeepSeek-R1 \
--output-dir /path/to/DeepSeek-R1-mtp
```
#### GLM4 MoE
```bash
python3 tools/export_mtp.py \
--input-dir /path/to/GLM-4.5-Air \
--output-dir /path/to/GLM-4.5-Air-mtp
```
#### 手动指定模型类型
如果自动检测失败,可以手动指定模型类型:
```bash
python3 tools/export_mtp.py \
--input-dir /path/to/model \
--output-dir /path/to/model-mtp \
--model-type deepseek_v3 # 可选: deepseek_v3 (用于V3/R1), deepseek_v32 (用于V3.2), glm4_moe
```
输入模型参考:
- [DeepSeek-V3](https://huggingface.co/deepseek-ai/DeepSeek-V3)
- [DeepSeek-V3.2](https://huggingface.co/deepseek-ai/DeepSeek-V3.2)
- [DeepSeek-R1](https://huggingface.co/deepseek-ai/DeepSeek-R1)
- [GLM-4.5-Air](https://huggingface.co/zai-org/GLM-4.5-Air)
### 启动脚本
使用MTP进行推理时需要同时指定主模型和草稿模型MTP模型
#### DeepSeek-V3/V3.2/R1 启动示例
```bash
MODEL_PATH="/models/DeepSeek-V3"
DRAFT_MODEL_PATH="/models/DeepSeek-V3-mtp"
MASTER_NODE_ADDR="127.0.0.1:42123"
START_PORT=13222
START_DEVICE=0
LOG_DIR="log"
NNODES=16
for (( i=0; i<$NNODES; i++ ))
do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i))
LOG_FILE="$LOG_DIR/node_$i.log"
nohup ./xllm \
--model $MODEL_PATH \
--devices="npu:$DEVICE" \
--port $PORT \
--master_node_addr=$MASTER_NODE_ADDR \
--nnodes=$NNODES \
--draft_model $DRAFT_MODEL_PATH \
--draft_devices="npu:$DEVICE" \
--num_speculative_tokens 1 \
--max_memory_utilization=0.90 \
--max_tokens_per_batch=10000 \
--max_seqs_per_batch=256 \
--block_size=128 \
--ep_size=1 \
--dp_size=1 \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--node_rank=$i > $LOG_FILE 2>&1 &
sleep 0.5
done
```
#### GLM4 MoE 启动示例
```bash
MODEL_PATH="/models/GLM-4.5-Air"
DRAFT_MODEL_PATH="/models/GLM-4.5-Air-mtp"
# ... 其他配置相同
```
# 性能数据
基于sharegpt数据集输入长度2500输出长度1500请求总数80。
| method | Concurrency | Mean TPOT(ms) | Mean TTFT(ms) | Output Tokens/s | Total Tokens/s |
|:---------:|:-----------:|:-------------:|:-------------:|:---------------:|:--------------:|
| baseline | 1 | 40.61 | 141.80 | 24.20 | 65.77 |
| mtp | 1 | 28.33 | 142.35 | 35.19 | 95.52 |
| baseline | 2 | 42.69 | 178.59 | 45.16 | 122.74 |
| mtp | 2 | 29.81 | 187.97 | 64.75 | 175.78 |
| baseline | 4 | 46.18 | 172.34 | 79.83 | 216.96 |
| mtp | 4 | 33.54 | 194.22 | 111.18 | 301.81 |
| baseline | 8 | 53.16 | 181.49 | 110.68 | 300.81 |
| mtp | 8 | 40.99 | 203.37 | 154.46 | 419.34 |
| baseline | 16 | 68.50 | 213.89 | 143.81 | 390.84 |
| mtp | 16 | 57.04 | 254.99 | 201.89 | 548.04 |
| baseline | 20 | 74.72 | 228.80 | 154.77 | 420.65 |
| mtp | 20 | 61.73 | 264.34 | 206.24 | 559.84 |
| baseline | 40 | 119.68 | 559.32 | 180.22 | 489.80 |
| mtp | 40 | 105.70 | 544.54 | 252.91 | 686.74 |
| baseline | 80 | 180.89 | 2996.21 | 192.09 | 522.06 |
| mtp | 80 | 152.19 | 2163.72 | 278.07 | 755.12 |

View File

@@ -0,0 +1,29 @@
# 多流并行
## 背景
大模型分布式推理场景中需要引入额外的通信操作将不同设备上的计算结果聚合在一起。以Deepseek这类大规模的MoE模型为例分布式规模通常较大通信开销也会随之变大。计算和通信都采用同一个stream的话在通信的同时device计算资源会出现浪费一直等待通信完成才能开始后面的计算。
## 功能介绍
xLLM在模型图层支持了多流并行功能将输入的batch拆分成2个micro batches一个流执行一个micro batch的计算操作另一个流执行另一个micro batch的通信操作计算和通信同时执行从而掩盖通信开销。
![异步调度](../../assets/multi_streams_architecture.jpg)
## 使用方式
xLLM中提供了gflags参数`enable_multi_stream_parallel`默认false如需开启在xLLM的服务启动脚本中设置为true即可示例如下
```shell
--enable_multi_stream_parallel=true
```
## 性能效果
prefill双流并行开启后基本可掩盖75以上的通信开销在DeepSeek-R1模型上只输出1个token的情况下
- TTFT下降 **7%**
- 吞吐 **提升7%**
!!! warning "注意"
双流并行目前只支持prefill阶段请求输入越长收益越大。
目前仅支持DeepSeek、Qwen3 dense非MoE模型。

View File

@@ -0,0 +1,19 @@
# 多模态支持
本文档主要介绍xLLM推理引擎中多模态的支持进展包括支持模型及模态类型以及离在线接口等。
## 支持模型
- Qwen2.5-VL: 包括7B/32B/72B。
- Qwen3-VL: 包括2B/4B/8B/32B。
- Qwen3-VL-MoE: 包括A3B/A22B。
- MiniCPM-V-2_6: 7B。
## 模态类型
- 图片: 支持单图、多图的输入,以及图片+Prompt组合、纯文本Promot等输入方式。
!!! warning "注意事项"
- 目前多模态后端不支持prefix cache以及chunk prefill正在支持中。
- 目前xLLM统一基于JinJa渲染ChatTemplate部署MiniCPM-V-2_6模型目录需提供ChatTemplate文件。
- 图片支持Base64输入以及图片Url。
- 目前多模态模型主要支持了图片模态,视频、音频等模态正在推进中。

View File

@@ -0,0 +1,57 @@
# 整体架构
## 背景
近年来随着百亿至万亿参数规模的大语言模型如GPT、Claude、DeepSeek、LLaMA等在自然语言处理和多模态交互领域取得突破性进展产业界对高效推理引擎与服务体系的构建提出了迫切需求。如何降低集群推理成本、提升推理效率已成为实现规模化商业落地的关键挑战。
尽管当前已涌现出一批面向大模型推理的优化引擎,但在实际部署过程中仍面临诸多技术瓶颈:
- 硬件适配性挑战:现有推理引擎对国产芯片等专用加速器的架构特性支持不足,难以充分发挥异构计算硬件的性能潜力,导致计算资源利用率低下;
- MoE架构优化难题专家并行机制中的令牌分发过程产生显著的All-to-All通信开销同时动态路由策略引发的专家负载不均衡问题严重制约了系统的可扩展性
- 长上下文管理瓶颈随着模型上下文窗口持续扩展KV缓存在内存碎片化处理、跨节点同步等方面的优化效率直接影响整体推理吞吐性能
- 混合部署效能局限现有推理集群在同时处理在线服务和离线任务时难以兼顾服务质量SLO保障与资源利用率优化。
- 动态PD适配不足当输入/输出序列长度出现剧烈波动时静态PD资源划分缺乏实时调整PD资源配置的能力既可能导致GPU资源闲置又存在SLO违约风险。
为此我们提出了xLLM——高效且易用的开源智能推理框架为模型在国产芯片上的推理提供企业级服务保障与高性能引擎计算能力。
## 功能介绍
xLLM提供智能计算能力我们实现了多种计算系统层和算法驱动层的联合推理加速
### 计算系统层
#### 多层流水线执行编排
在框架层异步化CPU调度使其与芯片推理计算形成流水线减少计算空泡在模型图层切分单个batch形成两个micro-batches之间的流水线重叠计算通信在算子内核层不同计算单元间流水重叠计算访存。
#### 动态shape的图执行优化
针对大语言模型处理动态输入如可变序列长度和批大小时面临的静态图适配问题xLLM通过参数化设计捕获输入维度实现动态适配并结合多图缓存方案减小编译开销使用受管控的显存池替代绝对地址保障安全复用最终在保持高灵活性的同时获得较高的执行效率。
#### 算子优化
xLLM实现了LLM中的关键算子在国产硬件芯片上的特定优化包括GroupMatmul、Chunked Prefill等。
#### xTensor显存管理
xTensor 显存管理框架采用 物理内存页池预分配 + 虚拟地址连续性映射 的方法通过动态按需映射物理页、复用可重用内存页Reusable及异步预映射优化调度结合 NPU 算子适配(如虚拟地址化 FlashMLA实现了高效动态内存管理取得了内存利用率提升以及延迟降低。
### 算法驱动层
#### PD分离
xLLM全面支持PD分离场景实现了高效的PD实例的管理以及PD实例之间的通信以及kv cache传输。
#### 全局调度
xLLM对请求和实例做全周期的资源调度智能管理。
##### 实例调度
我们实现了多种实例调度策略来选择如何将实例分配到更适合的实例。包括简单的Round Robin策略基于请求在各实例上的 prefix cache 命中率来选择的 prefix cache-aware 策略,基于实例上的显存空闲程度的 KV Cache-aware 策略。另外针对PD分离场景由于静态的PD比例往往无法很好应对流量以及请求输入输出长度突变的场景我们实现了一种自适应的PD动态调度器负责在线请求的全局实例分配与运行时PD动态调整。
##### 请求调度
我们实现了多种请求调度策略支持continuous batching包括chunked prefillprefill优先和decode优先等batch策略同时全面支持PD分离场景。
#### 全局kv cache管理
在全局层面采用ETCD作为元数据服务中间件实现集群服务注册、负载信息同步及全局缓存状态管理。每个计算实例维护本地多级缓存池。在调度策略方面系统采用基于 KV Cache 的动态决策机制:首先进行前缀匹配检测,计算各候选节点的 KV Cache 复用率,最终选择综合性能最优的节点进行处理,实现 KV Cache 的动态卸载与迁移。
#### 投机推理
xLLM内置优化后的投机推理算法一次生成多个tokens提升吞吐。xLLM通过投机模块下沉减少通信成本并使用调度和计算时序重叠优化、减少投机场景算子数据搬运等方式优化投机推理计算。
#### MOE负载均衡
xLLM针对MoE模型实现了基于历史专家负载统计的专家权重更新在推理时通过高效专家负责统计和双缓冲无感知的专家权重更新实现有效的动态负载均衡。
### 多模态支持
xLLM对包括Qwen2-VLMiniCPMV在内的多种多模态模型提供全面的支持。
### 相关设计文档
- [Graph Mode 设计文档](../design/graph_mode_design.md)
- [生成式推荐设计文档](../design/generative_recommendation_design.md)

View File

@@ -0,0 +1,36 @@
# PpMatmul 算子优化
## 背景
针对大模型推理中矩阵乘法占比高、耗时长的问题,优化了矩阵乘法算子的实现。
## 功能介绍
PpMatmul 算子使用 Tiling 切分策略,将矩阵乘法分解为多个小的矩阵乘法任务。然而当 tile 数量较小时任务无法被均匀分配到所有 npu 核心上,导致 tail effect 问题,影响计算效率。我们通过预取内存或重新划分任务的方式,优化 PpMatmul 算子的性能。
## 用户接口
### 算子直调 API
```cpp
aclnnStatus aclnnPpMatmulOptGetWorkspaceSize(
const aclTensor *a,
const aclTensor *b,
const aclTensor *out,
uint64_t *workspaceSize,
aclOpExecutor **executor);
aclnnStatus aclnnPpMatmulOpt(
void *workspace,
uint64_t workspaceSize,
aclOpExecutor *executor,
aclrtStream stream);
```
- `a`: 输入矩阵 A。
- `b`: 输入矩阵 B。
- `out`: 输出矩阵,存储计算结果。
## 性能效果
对于 tile 数量较小的情况(例如 M 较小,对应于 batch size 较小的情况TP=4算子较优化前有 **18%** 的性能提升。

View File

@@ -0,0 +1,20 @@
# Prefix Cache 优化
## 功能介绍
xLLM支持prefix_cache匹配。prefix_cache基于mermer_hash使用lru淘汰策略提供更极致的匹配效率同时提高prefix_cache命中率。
同时对prefix_cache进行了优化支持continuous_scheduler、chunked_scheduler和zero_evict_scheduler在prefill之后即更新
prefix_cache提高匹配时效性同时对于chunked_scheduler支持多阶段chunked_prefill匹配减少计算量并尽可能减少kv_cache占用。
## 使用方式
prefix_cache已在xLLM实现并向外暴露gflag参数控制功能的开关。
- 开启zero_evict策略并设置max_decode_token_per_sequence。
```
--enable_prefix_cache=true
```
## 性能效果
开启prefix_cache之后在Qwen3-8B模型上限制TPOT50msE2E时延 **下降10%**
!!! warning "注意"
暂不支持PD分离调度器

View File

@@ -0,0 +1,27 @@
# Topk&Topp算子优化
## 背景
在自然语言生成任务中topK和topP采样策略被广泛应用于控制生成文本的多样性和质量。然而在小模型中这两种策略的计算耗时相对较长。这主要是由于小模型的参数较少导致在处理概率分布时排序和筛选的效率降低从而影响了生成速度。因此优化小模型中topK和topP的实现可以提升其采样效率。
## 功能介绍
topKtopP算子的实现将排序、topK、softmax和topP等多个小算子融合为一个大算子从而提高了计算效率和性能。
## 用户接口
### 算子调用API
```c++
void top_k_top_p(torch::Tensor& logits,
const torch::Tensor& topK,
const torch::Tensor& topP);
```
- `logits`: 输入的logits张量包含模型的输出分数。
- `topK`: 用于选择的前K个概率的阈值张量。
- `topP`: 用于选择的累积概率的阈值张量。
## 性能效果
* 使用topKtopP融合算子后在qwen2-0.5B模型中TTOT **下降37%**,TTFT **提升10%**

View File

@@ -0,0 +1,48 @@
# xLLM Service
[:simple-github: xLLM Service](https://github.com/jd-opensource/xllm-service)
## 简介
**xLLM-service** 是一个基于 xLLM 推理引擎开发的服务层框架,为集群化部署提供高效率、高容错、高灵活性的大模型推理服务。
xLLM-service 旨在解决企业级服务场景中的关键挑战:
- 如何于在离线混合部署环境中保障在线服务的SLA提升离线任务的资源利用率。
- 如何适应实际业务中动态变化的请求负载,如输入/输出长度出现剧烈波动。
- 解决多模态模型请求的性能瓶颈。
- 保障集群计算实例的高可靠性。
#### 背景
当前百亿至万亿参数规模的大语言模型正快速部署于智能客服、实时推荐、内容生成等核心业务场景对国产计算硬件的高效支持已成为低成本推理部署的核心需求。现有推理引擎难以有效适配国产芯片等专用加速器的架构特性硬件计算单元利用率低、MoE 架构下的负载不均衡与通信开销瓶颈、kv 缓存管理困难等问题制约了请求的高效推理与系统的可扩展性。xLLM-service + xLLM推理引擎提升了全链路效率为大语言模型在实际业务中的规模化落地提供了关键技术支撑。
---
## 整体架构
xLLM-service 整体架构如图所示:
![1](../../assets/service_arch.png)
## 核心组件
### ETCD Cluster
用于元信息管理包括模型xllm实例请求等元信息的存储与管理。同时提供xllm节点注册与发现服务。
### Fault Tolerance
xLLM-service 提供容错管理,保障服务质量以及稳定性。
### Global Scheduler
实现全局感知调度,根据当前系统状态,将请求精准调度至最优实例执行,有效提升整体服务响应效率与资源利用率。
### Global KV Cache Manager
负责全局 KV Cache 管理,核心能力包括分布式 KV 缓存感知、Prefix 前缀匹配、KV Cache 动态迁移等,优化缓存资源使用效率。
### Instance Manager
聚焦实例全生命周期管理,所有 xllm 实例启动后需向本模块注册,模块基于预设策略,为实例提供调度适配、容错处理等支持。
### Event Plane
作为指标与事件中枢,接收各实例上报的 Metrics 数据,对统计指标进行统一收集与整理,为服务调度、容错、扩缩容等决策提供数据支撑。
### Planner
承担策略分析与决策职能,基于 Event Plane 上报的 Metrics 数据(含实例运行时指标、机器负载指标等),分析服务扩缩容需求、热点实例扩展必要性,输出资源调整与实例优化策略。

View File

@@ -0,0 +1,17 @@
# Zero Evict调度器
## 功能介绍
xLLM支持zero_evict调度策略。zero_evict调度策略是一种尽可能减少请求淘汰率的调度算法可以减少淘汰请求的prefill计算减少TPOT。
这种调度算法通过模拟轮次,检测请求是否调度可以被调度且不导致其它请求被淘汰。
## 使用方式
上述策略已在xLLM实现并向外暴露gflag参数控制功能的开关。
- 开启zero_evict策略并设置max_decode_token_per_sequence。
```
--use_zero_evict=true
--max_decode_token_per_sequence=256
```
## 性能效果
开启zero_evict之后在Qwen3-8B模型上限制E2E时延TPOT时延 **下降27%**

View File

@@ -0,0 +1,88 @@
# PD分离部署
`xllm`支持PD分离部署这需要与我们的另一个开源库[xllm service](https://github.com/jd-opensource/xllm-service)配套使用。
## xLLM Service依赖
首先,我们下载安装`xllm service`,与安装编译`xllm`类似:
```bash
git clone https://github.com/jd-opensource/xllm-service
cd xllm_service
git submodule init
git submodule update
```
### etcd安装
`xllm_service`依赖[etcd](https://github.com/etcd-io/etcd)使用etcd官方提供的[安装脚本](https://github.com/etcd-io/etcd/releases)进行安装,其脚本提供的默认安装路径是`/tmp/etcd-download-test/etcd`,我们可以手动修改其脚本中的安装路径,也可以运行完脚本之后手动迁移:
```bash
mv /tmp/etcd-download-test/etcd /path/to/your/etcd
```
### xLLM Service编译
先应用patch:
```bash
sh prepare.sh
```
再执行编译:
```bash
mkdir -p build
cd build
cmake ..
make -j 8
cd ..
```
!!! warning "可能的错误"
这里能会遇到关于`boost-locale``boost-interprocess`的安装错误:`vcpkg-src/packages/boost-locale_x64-linux/include: No such file or directory`,`/vcpkg-src/packages/boost-interprocess_x64-linux/include: No such file or directory`
我们使用`vcpkg`重新安装这些包:
```bash
/path/to/vcpkg remove boost-locale boost-interprocess
/path/to/vcpkg install boost-locale:x64-linux
/path/to/vcpkg install boost-interprocess:x64-linux
```
## PD分离运行
启动etcd:
```bash
./etcd-download-test/etcd --listen-peer-urls 'http://localhost:2390' --listen-client-urls 'http://localhost:2389' --advertise-client-urls 'http://localhost:2391'
```
启动xllm service:
```bash
ENABLE_DECODE_RESPONSE_TO_SERVICE=true ./xllm_master_serving --etcd_addr="127.0.0.1:12389" --http_server_port 28888 --rpc_server_port 28889 --tokenizer_path=/path/to/tokenizer_config_dir/
```
以Qwen2-7B为例
- 启动Prefill实例
```bash
/path/to/xllm --model=path/to/Qwen2-7B-Instruct \
--port=8010 \
--devices="npu:0" \
--master_node_addr="127.0.0.1:18888" \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--enable_disagg_pd=true \
--instance_role=PREFILL \
--etcd_addr=127.0.0.1:12389 \
--transfer_listen_port=26000 \
--disagg_pd_port=7777 \
--node_rank=0 \
--nnodes=1
```
- 启动Decode实例
```bash
/path/to/xllm --model=path/to/Qwen2-7B-Instruct \
--port=8020 \
--devices="npu:1" \
--master_node_addr="127.0.0.1:18898" \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--enable_disagg_pd=true \
--instance_role=DECODE \
--etcd_addr=127.0.0.1:12389 \
--transfer_listen_port=26100 \
--disagg_pd_port=7787 \
--node_rank=0 \
--nnodes=1
```
需要注意:
- PD分离需要读取`/etc/hccn.conf`文件,确保将物理机上的该文件映射到了容器中
- `etcd_addr`需与`xllm_service`的`etcd_addr`相同
测试命令和上面类似,注意`curl http://localhost:{PORT}/v1/chat/completions ...`的`PORT`选择为启动xLLM service的`http_server_port`。

View File

@@ -0,0 +1,125 @@
# 启动xllm
以Qwen3为例启动xllm的脚本如下给出的脚本适用于单机单卡和单机多卡当使用单机多卡时需要修改`NNODES`一张卡就代表一个node以及`ASCEND_RT_VISIBLE_DEVICES``CUDA_VISIBLE_DEVICES``MLU_VISIBLE_DEVICES`等环境变量。
## NPU
```bash
#!/bin/bash
set -e
rm -rf core.*
source /usr/local/Ascend/ascend-toolkit/set_env.sh
source /usr/local/Ascend/nnal/atb/set_env.sh
export ASCEND_RT_VISIBLE_DEVICES=0
export HCCL_IF_BASE_PORT=43432 # HCCL 通信基础端口
MODEL_PATH="/path/to/model/Qwen3-8B" # 模型路径
MASTER_NODE_ADDR="127.0.0.1:9748" # Master 节点地址(需全局一致)
START_PORT=18000 # 服务起始端口
START_DEVICE=0 # 起始逻辑设备号
LOG_DIR="log" # 日志目录
NNODES=1 # 节点数(当前脚本启动 1 个进程)
mkdir -p $LOG_DIR
for (( i=0; i<$NNODES; i++ ))
do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i))
LOG_FILE="$LOG_DIR/node_$i.log"
/path/to/xllm \
--model $MODEL_PATH \
--devices="npu:$DEVICE" \
--port $PORT \
--master_node_addr=$MASTER_NODE_ADDR \
--nnodes=$NNODES \
--max_memory_utilization=0.86 \
--block_size=128 \
--communication_backend="hccl" \
--enable_prefix_cache=false \
--enable_chunked_prefill=true \
--enable_schedule_overlap=true \
--enable_shm=true \
--node_rank=$i \ > $LOG_FILE 2>&1 &
done
```
## NVIDIA GPU
```bash
#!/bin/bash
set -e
rm -rf core.*
export CUDA_VISIBLE_DEVICES=0
# for debug
# export CUDA_LAUNCH_BLOCKING=1
MODEL_PATH="/path/to/model/Qwen3-8B"
MASTER_NODE_ADDR="127.0.0.1:9748"
START_PORT=18000
START_DEVICE=0
LOG_DIR="log"
NNODES=1
mkdir -p $LOG_DIR
for (( i=0; i<$NNODES; i++ ))
do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i))
LOG_FILE="$LOG_DIR/node_$i.log"
/path/to/xllm \
--model $MODEL_PATH \
--devices="cuda:$DEVICE" \
--port $PORT \
--master_node_addr=$MASTER_NODE_ADDR \
--nnodes=$NNODES \
--block_size=32 \
--max_memory_utilization=0.8 \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--enable_schedule_overlap=true \
--node_rank=$i \ > $LOG_FILE 2>&1 &
done
```
## MLU
```bash
#!/bin/bash
set -e
rm -rf core.*
export MLU_VISIBLE_DEVICES=0
MODEL_PATH="/path/to/model/Qwen3-8B"
MASTER_NODE_ADDR="127.0.0.1:9748"
START_PORT=18000
START_DEVICE=0
LOG_DIR="log"
NNODES=1
mkdir -p $LOG_DIR
for (( i=0; i<$NNODES; i++ ))
do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i))
LOG_FILE="$LOG_DIR/node_$i.log"
/path/to/xllm \
--model $MODEL_PATH \
--devices="mlu:$DEVICE" \
--port $PORT \
--nnodes=$NNODES \
--master_node_addr=$MASTER_NODE_ADDR \
--block_size=16 \
--node_rank=$i \ > $LOG_FILE 2>&1 &
done
```

View File

@@ -0,0 +1,106 @@
# 多机部署
该示例为两机32卡启动示例第一台机器服务:
```shell
bash start_deepseek_machine_1.sh
```
start_deepseek_machine_1.sh 脚本如下:
```bash
#!/bin/bash
set -e
rm -rf core.*
source /usr/local/Ascend/ascend-toolkit/set_env.sh
source /usr/local/Ascend/nnal/atb/set_env.sh
export HCCL_IF_BASE_PORT=43432 # HCCL 通信基础端口
MODEL_PATH="/path/to/your/DeepSeek-R1" # 模型路径
MASTER_NODE_ADDR="123.123.123.123:9748" # Master 节点地址(需全局一致)
LOCAL_HOST=123.123.123.123 # 本机服务启动IP
START_PORT=18000 # 服务起始端口
START_DEVICE=0 # 起始 NPU 逻辑设备号
LOG_DIR="log" # 日志目录
LOCAL_NODES=16 # 单机节点数(当前脚本启动 16 个进程)
NNODES=32 # 总卡数该示例为2机32卡
mkdir -p $LOG_DIR
for (( i=0; i<$LOCAL_NODES; i++ ))
do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i))
LOG_FILE="$LOG_DIR/node_$i.log"
/path/to/xllm \
--model $MODEL_PATH \
--host $LOCAL_HOST \
--port $PORT \
--devices="npu:$DEVICE" \
--master_node_addr=$MASTER_NODE_ADDR \
--nnodes=$NNODES \
--max_memory_utilization=0.86 \
--max_tokens_per_batch=40000 \
--max_seqs_per_batch=256 \
--block_size=128 \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--communication_backend="hccl" \
--enable_schedule_overlap=true \
--rank_tablefile=./ranktable_2s_32p.json \
--node_rank=$i \ > $LOG_FILE 2>&1 &
done
```
启动第二台机器服务:
```shell
bash start_deepseek_machine_2.sh
```
start_deepseek_machine_2.sh 脚本如下:
```bash
#!/bin/bash
set -e
rm -rf core.*
source /usr/local/Ascend/ascend-toolkit/set_env.sh
source /usr/local/Ascend/nnal/atb/set_env.sh
export HCCL_IF_BASE_PORT=43432 # HCCL 通信基础端口
MODEL_PATH="/path/to/your/DeepSeek-R1" # 模型路径
MASTER_NODE_ADDR="123.123.123.123:9748" # Master 节点地址(需全局一致)
LOCAL_HOST=456.456.456.456 # 本机服务启动IP
START_PORT=18000 # 服务起始端口
START_DEVICE=0 # 起始 NPU 逻辑设备号
LOG_DIR="log" # 日志目录
LOCAL_NODES=16 # 单机节点数(当前脚本启动 16 个进程)
NNODES=32 # 总卡数该示例为2机32卡
mkdir -p $LOG_DIR
for (( i=0; i<$LOCAL_NODES; i++ ))
do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i))
LOG_FILE="$LOG_DIR/node_$i.log"
/path/to/xllm \
--model $MODEL_PATH \
--host $LOCAL_HOST \
--port $PORT \
--devices="npu:$DEVICE" \
--master_node_addr=$MASTER_NODE_ADDR \
--nnodes=$NNODES \
--max_memory_utilization=0.86 \
--max_tokens_per_batch=40000 \
--max_seqs_per_batch=256 \
--block_size=128 \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--communication_backend="hccl" \
--enable_schedule_overlap=true \
--rank_tablefile=./ranktable_2s_32p.json \
--node_rank=$((i + LOCAL_NODES)) \ > $LOG_FILE 2>&1 &
done
```
这里使用了两台机器,可以通过 `--nnodes`设置总卡数,`--node_rank`为全局rank id。
`--rank_tablefile=./ranktable_2s_32p.json`为构建npu通信域所需文件可参考[ranktable 生成](https://gitee.com/mindspore/models/blob/master/utils/hccl_tools/README.md)生成。

View File

@@ -0,0 +1,16 @@
# 离线推理
为了方便用户快速使用xLLM进行离线推理我们提供了启动离线推理的python脚本例子
## LLM
LLM推理示例[:simple-github: https://github.com/jd-opensource/xllm/blob/main/examples/generate.py](https://github.com/jd-opensource/xllm/blob/main/examples/generate.py)
## Embedding
生成Embedding示例[:simple-github: https://github.com/jd-opensource/xllm/blob/main/examples/generate_embedding.py](https://github.com/jd-opensource/xllm/blob/main/examples/generate_embedding.py)
## VLM
VLM推理示例[:simple-github: https://github.com/jd-opensource/xllm/blob/main/examples/generate_vlm.py](https://github.com/jd-opensource/xllm/blob/main/examples/generate_vlm.py)

View File

@@ -0,0 +1,224 @@
# 在线服务
先按照[xllm启动文档](launch_xllm.md)启动xllm服务。下面给出LLM和VLM的客户端调用示例需要根据实际情况修改其中的参数。
## LLM 客户端调用
### HTTP 调用
chat模式
```bash
curl http://localhost:9977/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen2-7B-Instruct",
"max_tokens": 10,
"temperature": 0,
"stream": true,
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "hello xllm"
}
]
}'
```
completions模式
```bash
curl http://127.0.0.1:9977/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen2-7B-Instruct",
"prompt": "hello xllm",
"max_tokens": 10,
"temperature": 0,
"stream": true
}'
```
sample模式
```bash
curl http://127.0.0.1:9977/v1/sample \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen2-7B-Instruct",
"prompt": "问题:<emb_0> 是否命中。结论:<emb_0>",
"selector": {
"type": "literal",
"value": "<emb_0>"
},
"logprobs": 5,
"request_id": "sample-demo-001"
}'
```
典型响应:
```json
{
"id": "sample-demo-001",
"object": "sample_completion",
"created": 1773369600,
"model": "Qwen2-7B-Instruct",
"choices": [
{
"index": 0,
"text": "True",
"logprobs": {
"tokens": ["True", "False"],
"token_ids": [3456, 7890],
"token_logprobs": [-0.12, -2.31]
},
"finish_reason": "selector_match"
},
{
"index": 1,
"text": "",
"logprobs": {
"tokens": [],
"token_ids": [],
"token_logprobs": []
},
"finish_reason": "empty_logprobs"
}
],
"usage": {
"prompt_tokens": 20,
"completion_tokens": 2,
"total_tokens": 22
}
}
```
`/v1/sample` 使用说明:
- 仅支持 `--backend=llm`,当前不支持 VLM/DiT/Rec。
- `selector.type` 当前固定为 `literal``selector.value` 按 prompt 文本顺序全文匹配。
- `logprobs` 默认值为 `5`,允许范围为 `[1, 5]`
- `choices[i].index` 即该命中的 `sample_id`,与 prompt 中命中顺序一一对应。
- selector 无命中时返回 `200``choices=[]`;某命中位点无可用 logprobs 时返回 `finish_reason="empty_logprobs"`
- 服务日志只记录 `request_id``sample_id``match_count``model` 等摘要字段,不记录完整 prompt。
`/v1/sample` 常见错误语义:
- 缺少 `model/prompt/selector/selector.value``selector.type != literal``logprobs` 越界时返回 `INVALID_ARGUMENT`
- 模型不存在或后端不是 `llm` 时返回 `UNKNOWN`
- 并发达到上限时返回 `RESOURCE_EXHAUSTED`
- 模型处于 sleep 状态时返回 `UNAVAILABLE`
### Python调用
```python
import requests
import json
url = f"http://localhost:9977/v1/chat/completions"
messages = [
{'role': 'user', 'content': "列出三个国家和他的首都。"}
]
request_data = {
"model": "Qwen2-7B-Instruct",
"messages": messages,
"stream": False,
"temperature": 0.6,
"max_tokens": 2048,
}
response = requests.post(url, json=request_data)
if response.status_code != 200:
print(response.status_code, response.text)
else:
ans = json.loads(response.text)["choices"]
print(ans[0]['message'])
```
## VLM 客户端调用
### HTTP API
```python
import base64
import requests
api_url = "http://localhost:12345/v1/chat/completions"
image_url = ""
def encode_image(url: str) -> str:
with requests.get(url) as response:
response.raise_for_status()
result = base64.b64encode(response.content).decode("utf-8")
return result
image_base64 = encode_image(image_url)
payload = {
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "介绍下这张图片"},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image_base64}"},
},
],
}
],
"model": "Qwen2.5-VL-7B-Instruct",
"max_completion_tokens": 128,
}
response = requests.post(
api_url,
json=payload,
headers={"Content-Type": "application/json"}
)
print(response.json())
```
### OpenAI API
```python
from openai import OpenAI
import base64
import requests
openai_api_key = "EMPTY"
openai_api_base = "http://localhost:12345/v1"
image_url = ""
client = OpenAI(
api_key=openai_api_key,
base_url=openai_api_base,
)
def encode_image(url: str) -> str:
with requests.get(url) as response:
response.raise_for_status()
result = base64.b64encode(response.content).decode("utf-8")
return result
image_base64 = encode_image(image_url)
chat_completion = client.chat.completions.create(
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "介绍下这张图片"},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image_base64}"},
},
],
}
],
model="Qwen2.5-VL-7B-Instruct",
max_completion_tokens=128,
)
result = chat_completion.choices[0].message.content
print("Chat completion output:", result)
```

View File

@@ -0,0 +1,106 @@
# 快速开始
## 环境设置
所有的镜像都存放在[这里](https://quay.io/repository/jd_xllm/xllm-ai?tab=tags)下面的docker启动命令以开发镜像为例。
### NPU
下面是我们构建好的开发镜像。
```bash
# A2 x86
docker pull quay.io/jd_xllm/xllm-ai:xllm-dev-a2-x86-20260429
# A2 arm
docker pull quay.io/jd_xllm/xllm-ai:xllm-dev-a2-arm-20260429
# A3 arm
docker pull quay.io/jd_xllm/xllm-ai:xllm-dev-a3-arm-20260429
```
容器启动命令如下:
```bash
docker run -it \
--ipc=host \
-u 0 \
--name xllm-npu \
--privileged \
--network=host \
--device=/dev/davinci0 \
--device=/dev/davinci_manager \
--device=/dev/devmm_svm \
--device=/dev/hisi_hdc \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
-v /usr/local/Ascend/add-ons/:/usr/local/Ascend/add-ons/ \
-v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi \
-v /usr/local/sbin/:/usr/local/sbin/ \
-v /var/log/npu/conf/slog/slog.conf:/var/log/npu/conf/slog/slog.conf \
-v /var/log/npu/slog/:/var/log/npu/slog \
-v /var/log/npu/profiling/:/var/log/npu/profiling \
-v /var/log/npu/dump/:/var/log/npu/dump \
-v $HOME:$HOME \
-w $HOME \
<docker_image_name> \
/bin/bash
```
### NVIDIA GPU
我们提供了NVIDIA GPU使用的[Dockerfile](../../../docker/Dockerfile.cuda)可以构建自定义镜像当然也可以使用我们根据默认Dockerfile构建的开发镜像
```bash
docker pull quay.io/jd_xllm/xllm-ai:xllm-dev-cuda-x86
```
容器启动命令如下:
```bash
sudo docker run -it \
--privileged \
--shm-size '128gb' \
--ipc=host \
--net=host \
--pid=host \
--name=xllm-cuda \
-v $HOME:$HOME \
-w $HOME \
<docker_image_name> \
/bin/bash
```
### MLU
我们无法提供MLU镜像如果您已经拥有了相应的开发镜像那么可以根据下面的命令启动容器
```bash
sudo docker run -it \
--privileged \
--shm-size '128gb' \
--ipc=host \
--net=host \
--pid=host \
--name xllm-mlu \
-v $HOME:$HOME \
-w $HOME \
<docker_image_name> \
/bin/bash
```
## 编译xllm
如果下载的是release镜像即tag中带有版本号的镜像可以跳过此步因为release镜像自带编译好的xllm二进制文件路径为`/usr/local/bin/xllm`
下载xllm及依赖
```bash
git clone https://github.com/jd-opensource/xllm
cd xllm
# 第一次需要进行pre-commit安装
pip install pre-commit
pre-commit install
git submodule update --init --recursive
```
编译生成的二进制文件位于`/path/to/xllm/build/xllm/core/server/xllm`在新镜像中第一次编译xllm耗时较长因为需要编译vcpkg中的所有依赖但是后续编译会很快。
```bash
python setup.py build
```
## 启动xllm
请参考 [xllm启动方式](launch_xllm.md)。

View File

@@ -0,0 +1,583 @@
# 使用 xLLM 在 Ascend A3设备 推理 GLM-5.0-W8A8 基座模型
+ 源码地址https://github.com/jd-opensource/xllm
+ 国内可用: https://gitcode.com/xLLM-AI/xllm
+ 权重下载: [modelscope-GLM-5-W8A8](https://www.modelscope.cn/models/Eco-Tech/GLM-5-W8A8-xLLM/files)
## 1.拉取镜像环境
首先下载xLLM提供的镜像
```bash
# A2 x86
docker pull quay.io/jd_xllm/xllm-ai:xllm-dev-a2-x86-20260429
# A2 arm
docker pull quay.io/jd_xllm/xllm-ai:xllm-dev-a2-arm-20260429
# A3 arm
docker pull quay.io/jd_xllm/xllm-ai:xllm-dev-a3-arm-20260429
```
**注意**: A2 机器性能未进行压测。
然后创建对应的容器
```bash
sudo docker run -it --ipc=host -u 0 --privileged --name mydocker --network=host \
-v /var/queue_schedule:/var/queue_schedule \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
-v /usr/local/Ascend/add-ons/:/usr/local/Ascend/add-ons/ \
-v /usr/local/sbin/npu-smi:/usr/local/sbin/npu-smi \
-v /var/log/npu/conf/slog/slog.conf:/var/log/npu/conf/slog/slog.conf \
-v /var/log/npu/slog/:/var/log/npu/slog \
-v ~/.ssh:/root/.ssh \
-v /var/log/npu/profiling/:/var/log/npu/profiling \
-v /var/log/npu/dump/:/var/log/npu/dump \
-v /runtime/:/runtime/ -v /etc/hccn.conf:/etc/hccn.conf \
-v /export/home:/export/home \
-v /home/:/home/ \
-w /export/home \
quay.io/jd_xllm/xllm-ai:xllm-dev-hb-rc2-x86
```
## 2.拉取源码并编译
下载官方仓库与模块依赖:
```bash
git clone https://github.com/jd-opensource/xllm
cd xllm
git checkout preview/glm-5
git submodule init
git submodule update
```
下载安装依赖:
```bash
pip install --upgrade pre-commit
yum install numactl
```
执行编译,在`build/`下生成可执行文件`build/xllm/core/server/xllm`
```bash
python setup.py build
```
## 3.启动模型
### 若机器为重启后初次拉起服务需先执行以下脚本对device进行初始化
#若不执行且npu未初始化可能导致xllm进程拉起失败
```bash
python -c "import torch_npu
for i in range(16):torch_npu.npu.set_device(i)"
```
### 环境变量
```bash
##### 1 配置依赖路径相关环境变量
# export PYTHON_INCLUDE_PATH="$(python3 -c 'from sysconfig import get_paths; print(get_paths()["include"])')"
# export PYTHON_LIB_PATH="$(python3 -c 'from sysconfig import get_paths; print(get_paths()["include"])')"
# export PYTORCH_NPU_INSTALL_PATH=/usr/local/libtorch_npu/
# export PYTORCH_INSTALL_PATH="$(python3 -c 'import torch, os; print(os.path.dirname(os.path.abspath(torch.__file__)))')"
# export LIBTORCH_ROOT="$(python3 -c 'import torch, os; print(os.path.dirname(os.path.abspath(torch.__file__)))')"
# export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/opp/vendors/xllm/op_api/lib/:$LD_LIBRARY_PATH
# export LD_LIBRARY_PATH=/usr/local/libtorch_npu/lib:$LD_LIBRARY_PATH
export LD_PRELOAD=/usr/lib64/libjemalloc.so.2:$LD_PRELOAD
# source /usr/local/Ascend/ascend-toolkit/set_env.sh
# source /usr/local/Ascend/nnal/atb/set_env.sh
##### 2 配置日志相关环境变量
rm -rf /root/ascend/log/
rm -rf core.*
##### 3. 配置性能、通信相关环境变量
export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True
export NPU_MEMORY_FRACTION=0.96
export ATB_WORKSPACE_MEM_ALLOC_ALG_TYPE=3
export ATB_WORKSPACE_MEM_ALLOC_GLOBAL=1
export OMP_NUM_THREADS=12
export ALLOW_INTERNAL_FORMAT=1
export ATB_LAYER_INTERNAL_TENSOR_REUSE=1
export ATB_LLM_ENABLE_AUTO_TRANSPOSE=0
export ATB_CONVERT_NCHW_TO_AND=1
export ATB_LAUNCH_KERNEL_WITH_TILING=1
export ATB_OPERATION_EXECUTE_ASYNC=2
export ATB_CONTEXT_WORKSPACE_SIZE=0
export INF_NAN_MODE_ENABLE=1
export HCCL_EXEC_TIMEOUT=300
export HCCL_CONNECT_TIMEOUT=300
export HCCL_OP_EXPANSION_MODE="AIV"
export HCCL_IF_BASE_PORT=2864
```
## 启动命令 - GLM-5 W8A8权重可单机拉起
```bash
BATCH_SIZE=256
#推理最大batch数量
XLLM_PATH="./myxllm/xllm/build/xllm/core/server/xllm"
#推理入口文件路径(上一步中编译产物)
MODEL_PATH=/path/to/GLM-5-W8A8/
#模型路径此处为int8量化的Glm-5
DRAFT_MODEL_PATH=/path/to/GLM-5-W8A8/GLM-5-W8A8-MTP/
#Glm-5 导出的mtp权重
MASTER_NODE_ADDR="11.87.49.110:10015"
LOCAL_HOST="11.87.49.110"
# Service Port
START_PORT=18994
START_DEVICE=0
LOG_DIR="logs"
NNODES=16
for (( i=0; i<$NNODES; i++ ))
do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i))
LOG_FILE="$LOG_DIR/node_$i.log"
nohup numactl -C $((DEVICE*40))-$((DEVICE*40+39)) $XLLM_PATH \
--model $MODEL_PATH \
--port $PORT \
--devices="npu:$DEVICE" \
--master_node_addr=$MASTER_NODE_ADDR \
--nnodes=$NNODES \
--node_rank=$i \
--max_memory_utilization=0.85 \
--max_tokens_per_batch=8192 \
--max_seqs_per_batch=32 \
--block_size=128 \
--enable_prefix_cache=false \
--enable_chunked_prefill=true \
--communication_backend="hccl" \
--enable_schedule_overlap=true \
--enable_graph=true \
--enable_graph_mode_decode_no_padding=true \
--draft_model=$DRAFT_MODEL_PATH \
--draft_devices="npu:$DEVICE" \
--num_speculative_tokens=1 \
--ep_size=8 \
--dp_size=1 \
> $LOG_FILE 2>&1 &
done
# numactl -C xxxxx 亲和性绑核(NUMA亲和性查询命令 npu-smi info -t topo)
#--max_memory_utilization 单卡最大显存占用比例
#--max_tokens_per_batch 单batch最大token数 主要限制prefill
#--max_seqs_per_batch 单batch最大请求数 主要限制decoe
#--communication_backend 通信backend 可选(hccl / lccl) 此处建议hccl
#--enable_schedule_overlap 开启异步调度
#--enable_prefix_cache 开启prefix_cache
#--enable_chunked_prefill 开启chunked_prefill
#--enable_graph 开启aclgraph
#--draft_model mtp - mtp权重路径
#--draft_devices mtp - mtp推理设备(与主模型同一)
#--num_speculative_tokens mtp - 预测token数
```
日志出现"Brpc Server Started"表示服务成功拉起。
## 其他可选环境变量
```bash
#开启确定性计算
export LCCL_DETERMINISTIC=1
export HCCL_DETERMINISTIC=true
export ATB_MATMUL_SHUFFLE_K_ENABLE=0
# #开启动态profiling模式
# export PROFILING_MODE=dynamic
# \rm -rf ~/dynamic_profiling_socket_*
```
## 启动命令 - 双机拉起样例
### Node0 (master)
```bash
MASTER_NODE_ADDR="11.87.49.110:19990"
LOCAL_HOST="11.87.49.110"
START_PORT=15890
START_DEVICE=0
LOG_DIR="logs"
NNODES=32
LOCAL_NODES=16
export HCCL_IF_BASE_PORT=48439
unset HCCL_OP_EXPANSION_MODE
for (( i=0; i<$LOCAL_NODES; i++ ))do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i)); LOG_FILE="$LOG_DIR/node_$i.log"
nohup numactl -C $((DEVICE*40))-$((DEVICE*40+39)) $XLLM_PATH \ --model $MODEL_PATH \
--host $LOCAL_HOST \
--port $PORT \
--devices="npu:$DEVICE" \
--master_node_addr=$MASTER_NODE_ADDR \
--nnodes=$NNODES \
--node_rank=$i \
--max_memory_utilization=0.85 \
--max_tokens_per_batch=8192 \
--max_seqs_per_batch=4 \
--block_size=128 \
--enable_prefix_cache=false \
--enable_chunked_prefill=true \
--communication_backend="hccl" \
--enable_schedule_overlap=true \
--enable_graph=true \
--enable_graph_mode_decode_no_padding=true \
--ep_size=16 \
--dp_size=1 \
--rank_tablefile=/yourPath/ranktable.json \
> $LOG_FILE 2>&1 &
done
```
#### Node1 (worker)
```bash
MASTER_NODE_ADDR="11.87.49.110:19990"
LOCAL_HOST="11.87.49.111"
START_PORT=15890
START_DEVICE=0
LOG_DIR="logs"
NNODES=32
LOCAL_NODES=16
export HCCL_IF_BASE_PORT=48439
unset HCCL_OP_EXPANSION_MODE
for (( i=0; i<$LOCAL_NODES; i++ ))do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i)); LOG_FILE="$LOG_DIR/node_$i.log"
nohup numactl -C $((DEVICE*40))-$((DEVICE*40+39)) $XLLM_PATH \ --model $MODEL_PATH \
--host $LOCAL_HOST \
--port $PORT \
--devices="npu:$DEVICE" \
--master_node_addr=$MASTER_NODE_ADDR \
--nnodes=$NNODES \
--node_rank=$((i + LOCAL_NODES)) \
--max_memory_utilization=0.85 \
--max_tokens_per_batch=8192 \
--max_seqs_per_batch=4 \
--block_size=128 \
--enable_prefix_cache=false \
--enable_chunked_prefill=true \
--communication_backend="hccl" \
--enable_schedule_overlap=true \
--enable_graph=true \
--enable_graph_mode_decode_no_padding=true \
--ep_size=16 \
--dp_size=1 \
--rank_tablefile=/yourPath/ranktable.json \
> $LOG_FILE 2>&1 &
done
```
#### ranktable样例
ranktable配置指导https://www.hiascend.com/document/detail/zh/canncommercial/83RC1/hccl/hcclug/hcclug_000014.html
```json
{
"version": "1.0",
"server_count": "2",
"server_list": [
{
"server_id": "11.87.49.110",
"device": [
{
"device_id": "0",
"device_ip": "11.86.23.210",
"rank_id": "0"
},
...
{
"device_id": "7",
"device_ip": "11.86.23.217",
"rank_id": "7"
}
],
"host_nic_ip": "reserve"
},
{
"server_id": "11.87.49.111",
"device": [
{
"device_id": "0",
"device_ip": "11.87.63.202",
"rank_id": "8"
},
...
{
"device_id": "7",
"device_ip": "11.87.63.209",
"rank_id": "15"
}
],
"host_nic_ip": "reserve"
}
],
"status": "completed"
}
```
## device NUMA亲和性查看
命令:
```bash
npu-smi info -t topo
```
前述命令中
```bash
numactl -C $((DEVICE*12))-$((DEVICE*12+11))
```
表示该进程绑在对应亲和的核上可根据机器具体情况修改绑定的核id
## EX3.Glm-5 权重量化
### 安装msmodelslim
```bash
git clone https://gitcode.com/shenxiaolong/msmodelslim.git
cd msmodelslim
bash install.sh
```
### 修改tokenizer_config.json
```bash
"extra_special_tokens"
改成 "additional_special_tokens"
"tokenizer_class": "TokenizersBackend"
改成 "tokenizer_class": "PreTrainedTokenizer"
```
### 基于GLM-5-BF16 权重量化W8A8权重
```bash
### 预处理mtp相关权重
python example/GLM5/extract_mtp.py --model-dir ${model_path}
#指定transformers版本
pip install transformers==4.48.2
#量化执行(生成量化权重)
msmodelslim quant --model_path ${model_path} --save_path ${save_path} --model_type DeepSeek-V3.2 --quant_type w8a8 --trust_remote_code True
#拷贝chat_template文件
cp ${model_path}/chat_template.jinja ${save_path}
#量化mtp权重导出用于xllm推理
python example/GLM5/export_mtp.py --input-dir ${int8_save_path} --output-dir ${mtp_save_path}
```
## PD分离
### etcd\xllm-service 安装
#### PD分离部署
`xllm`支持PD分离部署这需要与另一个开源库[xllm service](https://github.com/jd-opensource/xllm-service)配套使用。
##### xLLM Service依赖
首先,我们下载安装`xllm service`,与安装编译`xllm`类似:
```bash
git clone https://github.com/jd-opensource/xllm-service
cd xllm_service
git submodule init
git submodule update
```
##### etcd安装
`xllm_service`依赖[etcd](https://github.com/etcd-io/etcd)使用etcd官方提供的[安装脚本](https://github.com/etcd-io/etcd/releases)进行安装,其脚本提供的默认安装路径是`/tmp/etcd-download-test/etcd`,我们可以手动修改其脚本中的安装路径,也可以运行完脚本之后手动迁移:
```bash
mv /tmp/etcd-download-test/etcd /path/to/your/etcd
```
##### xLLM Service编译
先应用patch:
```bash
sh prepare.sh
```
再执行编译:
```bash
mkdir -p build
cd build
cmake ..
make -j 8
cd ..
```
!!! warning "可能的错误"
这里能会遇到关于`boost-locale``boost-interprocess`的安装错误:`vcpkg-src/packages/boost-locale_x64-linux/include: No such file or directory`,`/vcpkg-src/packages/boost-interprocess_x64-linux/include: No such file or directory`
我们使用`vcpkg`重新安装这些包:
```bash
/path/to/vcpkg remove boost-locale boost-interprocess
/path/to/vcpkg install boost-locale:x64-linux
/path/to/vcpkg install boost-interprocess:x64-linux
```
### PD分离运行
启动etcd:
```bash
./etcd-download-test/etcd --listen-peer-urls 'http://localhost:2390' --listen-client-urls 'http://localhost:2389' --advertise-client-urls 'http://localhost:2391'
```
跨机配置时etcd参考如下
```bash
/tmp/etcd-download-test/etcd --listen-peer-urls 'http://0.0.0.0:3390' --listen-client-urls 'http://0.0.0.0:3389' --advertise-client-urls 'http://11.87.191.82:3389'
```
启动xllm service:
```bash
ENABLE_DECODE_RESPONSE_TO_SERVICE=true ./xllm_master_serving --etcd_addr="127.0.0.1:12389" --http_server_port 28888 --rpc_server_port 28889 --tokenizer_path=/export/home/models/GLM-5-W8A8/
```
跨机配置时启动xllm service:
```bash
ENABLE_DECODE_RESPONSE_TO_SERVICE=true ../xllm-service/build/xllm_service/xllm_master_serving --etcd_addr="11.87.191.82:3389" --http_server_port 38888 --rpc_server_port 38889 --tokenizer_path=/export/home/models/GLM-5-W8A8/
```
- 启动Prefill实例
```bash
BATCH_SIZE=256
#推理最大batch数量
XLLM_PATH="./myxllm/xllm/build/xllm/core/server/xllm"
#推理入口文件路径(上一步中编译产物)
MODEL_PATH=/export/home/models/GLM-5-w8a8/
#模型路径此处为int量化的Glm-5
DRAFT_MODEL_PATH=/export/home/models/GLM-5-MTP/
MASTER_NODE_ADDR="11.87.49.110:10015"
LOCAL_HOST="11.87.49.110"
# Service Port
START_PORT=18994
START_DEVICE=0
LOG_DIR="logs"
NNODES=16
for (( i=0; i<$NNODES; i++ ))
do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i))
LOG_FILE="$LOG_DIR/node_$i.log"
nohup numactl -C $((i*40))-$((i*40+39)) $XLLM_PATH \
--model $MODEL_PATH --model_id glmmoe \
--host $LOCAL_HOST \
--port $PORT \
--devices="npu:$DEVICE" \
--master_node_addr=$MASTER_NODE_ADDR \
--nnodes=$NNODES \
--node_rank=$i \
--max_memory_utilization=0.86 \
--max_tokens_per_batch=5000 \
--max_seqs_per_batch=$BATCH_SIZE \
--communication_backend=hccl \
--enable_schedule_overlap=true \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--enable_graph=true \
--draft_model $DRAFT_MODEL_PATH \
--draft_devices="npu:$DEVICE" \
--num_speculative_tokens 1 \
--enable_disagg_pd=true \
--instance_role=PREFILL \
--etcd_addr=$LOCAL_HOST:3389 \
--transfer_listen_port=$((36100 + i)) \
--disagg_pd_port=8877 \
> $LOG_FILE 2>&1 &
done
#--etcd_addr=$LOCAL_HOST:3389 参考etcd中advertise-client-urls的配置
#--instance_role=DECODE PD配置DECODE\PREFILL
```
- 启动Decode实例
```bash
BATCH_SIZE=256
#推理最大batch数量
XLLM_PATH="./myxllm/xllm/build/xllm/core/server/xllm"
#推理入口文件路径(上一步中编译产物)
MODEL_PATH=/export/home/models/GLM-5-w8a8/
#模型路径此处为int量化的Glm-5
DRAFT_MODEL_PATH=/export/home/models/GLM-5-MTP/
MASTER_NODE_ADDR="11.87.49.110:10015"
LOCAL_HOST="11.87.49.110"
# Service Port
START_PORT=18994
START_DEVICE=0
LOG_DIR="logs"
NNODES=16
for (( i=0; i<$NNODES; i++ ))
do
PORT=$((START_PORT + i))
DEVICE=$((START_DEVICE + i))
LOG_FILE="$LOG_DIR/node_$i.log"
nohup numactl -C $((i*40))-$((i*40+39)) $XLLM_PATH \
--model $MODEL_PATH --model_id glmmoe \
--host $LOCAL_HOST \
--port $PORT \
--devices="npu:$DEVICE" \
--master_node_addr=$MASTER_NODE_ADDR \
--nnodes=$NNODES \
--node_rank=$i \
--max_memory_utilization=0.86 \
--max_tokens_per_batch=5000 \
--max_seqs_per_batch=$BATCH_SIZE \
--communication_backend=hccl \
--enable_schedule_overlap=true \
--enable_prefix_cache=false \
--enable_chunked_prefill=false \
--enable_graph=true \
--draft_model $DRAFT_MODEL_PATH \
--draft_devices="npu:$DEVICE" \
--num_speculative_tokens 1 \
--enable_disagg_pd=true \
--instance_role=DECODE \
--etcd_addr=$LOCAL_HOST:3389 \
--transfer_listen_port=$((36100 + i)) \
--disagg_pd_port=8877 \
> $LOG_FILE 2>&1 &
done
#--etcd_addr=$LOCAL_HOST:3389 参考etcd中advertise-client-urls的配置
#--instance_role=DECODE PD配置DECODE\PREFILL
```
需要注意:
- PD分离需要读取`/etc/hccn.conf`文件,确保将物理机上的该文件映射到了容器中
- `etcd_addr`需与`xllm_service`的`etcd_addr`相同
测试命令和上面类似,注意`curl http://localhost:{PORT}/v1/chat/completions ...`的`PORT`选择为启动xLLM service的`http_server_port`。
- 多机部署P或者Q时(例如部署两个P),需要增加--rank_tablefile来完成通信。

View File

@@ -0,0 +1,66 @@
---
hide:
- navigation
---
<style>
.md-content h1:first-of-type {
display: none;
}
</style>
<div style="text-align:center">
<img src="../assets/logo_with_llm.png" alt="xLLM" style="width:50%; height:auto;">
</div>
## 简介
**xLLM** 是一个高效且易用的开源智能推理框架,为模型在国产芯片上的推理提供企业级服务保障与高性能引擎计算能力。
#### 背景
当前百亿至万亿参数规模的大语言模型正快速部署于智能客服、实时推荐、内容生成等核心业务场景对国产计算硬件的高效支持已成为低成本推理部署的核心需求。现有推理引擎难以有效适配国产芯片等专用加速器的架构特性硬件计算单元利用率低、MoE 架构下的负载不均衡与通信开销瓶颈、kv 缓存管理困难等问题制约了请求的高效推理与系统的可扩展性。xLLM 推理引擎提升了 “通信 - 计算 - 存储” 全链路的资源利用效率,为大语言模型在实际业务中的规模化落地提供了关键技术支撑。
---
## 核心特性
xLLM 提供了强大的智能计算能力,通过硬件系统的算力优化与算法驱动的决策控制,联合加速推理过程,实现高吞吐、低延迟的分布式推理服务。
### 全图化/多层流水线执行编排
- 框架调度层的异步解耦调度,减少计算空泡;
- 模型图层的计算和通信异步并行,重叠计算与通信;
- 算子内核层的异构计算单元深度流水,重叠计算与访存。
### 动态shape的图执行优化
- 基于参数化与多图缓存方法的动态尺寸适配,提升静态图灵活性;
- 受管控的显存池,保证地址安全可复用;
- 集成适配性能关键的自定义算子(如 *PageAttention*, *AllReduce*)。
### MoE算子优化
- *GroupMatmul* 优化,提升计算效率;
- *Chunked Prefill* 优化,支撑长序列输入。
### 高效显存优化
- 离散物理内存与连续虚拟内存的映射管理;
- 按需分配内存空间,减少内存碎片与浪费;
- 智能调度内存空间,增加内存页复用,减小分配延迟;
- 国产芯片相应算子适配。
### 全局多级KV Cache管理
- 多级缓存的kv智能卸载与预取
- 以kv cache为中心的分布式存储架构
- 多节点间kv的智能传输路由。
### 算法优化
- 投机推理优化,多核并行提升效率;
- MoE专家的动态负载均衡实现专家分布的高效调整。
## 设计文档
- [Graph Mode 设计文档](design/graph_mode_design.md)
- [生成式推荐设计文档](design/generative_recommendation_design.md)

View File

@@ -0,0 +1,49 @@
# 模型支持列表
## LLM
| | NPU | MLU | ILU |
| ------------------------ | :---: | :---: | :---: |
| DeepSeek-V3/R1/V3.1 | ✅ | ✅ | ❌ |
| DeepSeek-V3.2 | ✅ | ✅ | ❌ |
| DeepSeek-R1-Distill-Qwen | ✅ | ❌ | ❌ |
| Qwen2/2.5/QwQ | ✅ | ✅ | ✅ |
| Qwen3 | ✅ | ✅ | ✅ |
| Qwen3 Moe | ✅ | ✅ | ✅ |
| Kimi-k2 | ✅ | ❌ | ❌ |
| Llama2/3 | ✅ | ❌ | ✅ |
| GLM4.5 | ✅ | ❌ | ❌ |
| GLM4.6 | ✅ | ❌ | ❌ |
| GLM-4.7 | ✅ | ❌ | ❌ |
| GLM-5 | ✅ | ❌ | ❌ |
## VLM
| | NPU | MLU | ILU |
| ------------ | :---: | :---: | :---: |
| MiniCPM-V | ✅ | ❌ | ❌ |
| MiMo-VL | ✅ | ❌ | ❌ |
| Qwen2.5-VL | ✅ | ✅ | ❌ |
| Qwen3-VL | ✅ | ✅ | ❌ |
| Qwen3-VL-MoE | ✅ | ✅ | ❌ |
| GLM-4.6V | ✅ | ❌ | ❌ |
| VLM-R1 | ✅ | ❌ | ❌ |
## Rerank
| | NPU | MLU | ILU |
| -------------- | :---: | :---: | :---: |
| Qwen3-Reranker | ✅ | ❌ | ❌ |
## DiT
| | NPU | MLU | ILU |
| ---- | :---: | :---: | :---: |
| Flux | ✅ | ❌ | ❌ |
## Rec
| | NPU | MLU | ILU |
| --- | :---: | :---: | :---: |
| OneRec | ✅ | ❌ | ❌ |
| Qwen2 | ✅ | ❌ | ❌ |
| Qwen2.5 | ✅ | ❌ | ❌ |
| Qwen3 | ✅ | ❌ | ❌ |