|
1 | | -# Python 模型执行器 · 算子与交互架构 |
| 1 | +# Python 模型执行器:算子与交互架构 |
2 | 2 |
|
3 | | -## C++ ↔ Python 交互 |
| 3 | +## 当前支持范围 |
4 | 4 |
|
5 | | -xLLM 进程通过 pybind11 嵌入式解释器调用 Python 模型。交互分三个阶段: |
| 5 | +Python 模型执行器通过 `--model_impl=python`(`py` 会在配置初始化时规范化为 |
| 6 | +`python`)启用。目前只在 CUDA 构建中注册,内置 Python 模型仅包含 Qwen3 |
| 7 | +dense。本文描述的是当前实现,不表示 NPU、MUSA 或其他模型已经可用。 |
| 8 | + |
| 9 | +`model_impl`、`python_model_path` 和 `python_graph_backend` 都可以通过命令行或 |
| 10 | +JSON 配置设置,非默认生效值会写入启动配置转储。`python_model_path` 是包含 |
| 11 | +`python` package 的目录;未设置时回退到 `XLLM_PYTHON_MODEL_PATH`,再由嵌入式 |
| 12 | +解释器按原有 `sys.path` 查找。 |
| 13 | + |
| 14 | +## C++ 与 Python 交互 |
| 15 | + |
| 16 | +xLLM 服务进程通过 pybind11 嵌入 CPython,并在同一进程中调用 Python 模型。 |
| 17 | +交互分为初始化、权重加载和逐步前向三个阶段;下面将初始化阶段中的 TP 通信组 |
| 18 | +创建单独列出。 |
6 | 19 |
|
7 | 20 | ### 1. 初始化(`PyCausalLM` 构造) |
8 | 21 |
|
| 22 | +```text |
| 23 | +C++ ModelArgs/ParallelArgs -> PROPERTY 反射 -> PyDictVisitor -> py::dict config |
9 | 24 | ``` |
10 | | -C++ ModelArgs/ParallelArgs → PROPERTY 反射 → PyDictVisitor → py::dict config |
11 | | -``` |
12 | 25 |
|
13 | | -传给 Python 模型的 `config` dict 包含: |
14 | | -- 所有 `ModelArgs` 字段(`hidden_size`、`n_heads`、`n_kv_heads`、`n_layers`、`head_dim` 等) |
15 | | -- 所有 `ParallelArgs` 字段(`tp_size`、`tp_rank`) |
16 | | -- `dtype`(字符串:`"bfloat16"` / `"float16"`) |
17 | | -- `device`(字符串:`"cuda:0"` / `"npu:0"`) |
18 | | -- `python_graph_backend`、`max_seqs_per_batch`(decode graph 捕获上限) |
| 26 | +`ensure_python_interpreter()` 先保证 `torch.ops.xllm_ops.*` 的 C++ 静态注册不会被 |
| 27 | +链接器裁剪,再初始化解释器并导入 `python` package。传给 Python 模型的 |
| 28 | +`config` dict 包含: |
| 29 | + |
| 30 | +- 可反射的 `ModelArgs` 和 `ParallelArgs` 字段; |
| 31 | +- 根据实际 TP group 得到的 `tp_size`、`tp_rank`; |
| 32 | +- `dtype` 和当前 CUDA `device`; |
| 33 | +- `python_graph_backend`; |
| 34 | +- 调度器的 `max_seqs_per_batch`,作为 decode cudagraph 的最大 batch 上限。 |
| 35 | + |
| 36 | +Python 侧通过 `registry.get_model_class(model_type)` 查找模型类并用该 dict 构造。 |
| 37 | +当前 registry 只注册 `Qwen3ForCausalLM`/`qwen3`。 |
| 38 | + |
| 39 | +### 2. TP 通信组初始化 |
19 | 40 |
|
20 | | -Python 模型类通过 `registry.get_model_class(model_type)` 查找,用 `config` dict 构造。 |
| 41 | +当 `tp_size > 1` 时,Python executor 不复用或包装 C++ `ProcessGroup`。C++ 只把 |
| 42 | +当前 TP 子组对应的 rendezvous host/port、rank 和 world size 传给 |
| 43 | +`python.ops.init_tp_group()`;Python 使用 `torch.distributed.TCPStore` 和 |
| 44 | +`ProcessGroupNCCL` 创建独立的 PyTorch TP group。 |
21 | 45 |
|
22 | | -### 2. 权重加载(`load_model`) |
| 46 | +通信组按 CUDA device 缓存。TP 层通过 `torch.ops.xllm_ops.all_reduce` 和 |
| 47 | +`torch.ops.xllm_ops.all_gather` 调用 `torch.distributed`;若 TP>1 时尚未初始化 |
| 48 | +group,算子直接报错,不会静默返回局部张量。TP=1 时各层不调用通信算子。 |
23 | 49 |
|
24 | | -C++ `StateDict`(safetensors mmap)通过 `PYBIND11_EMBEDDED_MODULE(xllm_weight_loader)` 暴露为 Python 对象,接口: |
25 | | -- `state_dict.keys() → list[str]` |
26 | | -- `state_dict.has(name) → bool` |
27 | | -- `state_dict.get_tensor(name) → torch.Tensor`(零拷贝,直接引用 mmap 内存) |
| 50 | +### 3. 权重加载(`load_model`) |
28 | 51 |
|
29 | | -Python 模型的 `load_weights(state_dicts, tp_rank, tp_size)` 负责按 TP 切分加载。 |
| 52 | +C++ `StateDict` 通过 `PYBIND11_EMBEDDED_MODULE(xllm_weight_loader)` 暴露为 |
| 53 | +Python 对象,接口为: |
30 | 54 |
|
31 | | -### 3. 前向推理(每步调用) |
| 55 | +- `state_dict.keys() -> list[str]` |
| 56 | +- `state_dict.has(name) -> bool` |
| 57 | +- `state_dict.get_tensor(name) -> torch.Tensor` |
32 | 58 |
|
33 | | -C++ 每步构造参数并调用 `py_model_.forward(tokens, positions, attention_metadata, kv_caches)`: |
| 59 | +`get_tensor()` 直接返回 C++ `StateDict` 持有的 tensor,不在 pybind bridge 中 |
| 60 | +额外复制。Python 模型的 `load_weights(state_dicts, tp_rank, tp_size)` 负责权重 |
| 61 | +变换、TP 切分和复制到目标参数。 |
| 62 | + |
| 63 | +### 4. 前向推理(每步调用) |
| 64 | + |
| 65 | +C++ 每步调用 |
| 66 | +`py_model_.forward(tokens, positions, attention_metadata_dict, kv_caches)`: |
34 | 67 |
|
35 | 68 | | 参数 | 类型 | 说明 | |
36 | 69 | |------|------|------| |
37 | 70 | | `tokens` | `Tensor[int32]` | 当前步 token ids | |
38 | 71 | | `positions` | `Tensor[int32]` | 位置编码 | |
39 | | -| `attention_metadata` | `dict` | attention 元数据(见下) | |
40 | | -| `kv_caches` | `list[tuple[Tensor, Tensor]]` | 每层 (k_cache, v_cache) | |
| 72 | +| `attention_metadata_dict` | `dict` | attention 元数据 | |
| 73 | +| `kv_caches` | `list[tuple[Tensor, Tensor]]` | 每层 `(k_cache, v_cache)` | |
41 | 74 |
|
42 | | -`attention_metadata` dict 字段: |
| 75 | +`attention_metadata_dict` 包含以下字段: |
43 | 76 |
|
44 | 77 | | 字段 | 含义 | |
45 | 78 | |------|------| |
46 | 79 | | `slot_mapping` | 新 token 写入 cache 的 slot 位置 | |
47 | 80 | | `paged_kv_indptr` | paged KV 的 CSR indptr | |
48 | 81 | | `paged_kv_indices` | paged KV 的物理 block indices | |
49 | 82 | | `paged_kv_last_page_len` | 每个序列最后一页的有效 token 数 | |
50 | | -| `is_prefill` | 是否为 prefill 阶段 | |
| 83 | +| `is_prefill` | 是否为普通 prefill | |
51 | 84 | | `is_chunked_prefill` | 是否为 chunked prefill | |
52 | | -| `enable_cuda_graph` | 是否在 graph capture/replay 中 | |
53 | | -| `q_cu_seq_lens` | query 累积序列长度(prefill 时) | |
54 | | -| `kv_cu_seq_lens` | KV 累积序列长度(prefill 时) | |
55 | | -| `qo_indptr` | chunked prefill 的 query-output indptr | |
| 85 | +| `enable_cuda_graph` | C++ attention metadata 中的 graph 状态 | |
| 86 | +| `use_tensor_core` | 当前固定为 `false` | |
| 87 | +| `q_cu_seq_lens` | 普通 prefill 的 query 累积长度,可选 | |
| 88 | +| `kv_cu_seq_lens` | 普通 prefill 的 KV 累积长度,可选 | |
| 89 | +| `qo_indptr` | chunked prefill 的 query-output indptr,可选 | |
56 | 90 |
|
57 | | -返回值:`torch.Tensor`(hidden_states),C++ 侧再调 `compute_logits` 得到 logits。 |
| 91 | +Python runner 负责 attention plan、forward context 和 graph 调度。模型返回 |
| 92 | +`hidden_states`,C++ 随后调用 Python `compute_logits()`;`selected_idxes` 存在时 |
| 93 | +先选择需要计算 logits 的 token。 |
58 | 94 |
|
59 | | -## 算子分类 |
| 95 | +## Graph 执行模式 |
60 | 96 |
|
61 | | -| 类别 | 注册方式 | 位置 | |
62 | | -|------|----------|------| |
63 | | -| 计算 op (`rms_norm` / `fused_add_rms_norm` / `silu_and_mul` / `fused_qk_norm_rope` / `reshape_paged_cache`) | C++ `TORCH_LIBRARY(xllm_ops)` schema + `TORCH_LIBRARY_IMPL(CUDA)` | `core/kernels/cuda/xllm_ops_library.cpp` | |
64 | | -| 通信 op (`all_reduce` / `all_gather`) | Python `torch.library.custom_op` 调 `torch.distributed` | `python/ops/collectives.py` | |
65 | | -| Attention (`batch_prefill` / `batch_decode`) | flashinfer Python API 直接调用 | `python/layers/attention.py` | |
| 97 | +`python_graph_backend` 的行为如下: |
66 | 98 |
|
67 | | -设计约束: |
68 | | -- `models/` 和 `layers/` 只调 `ops.*` 接口,不含设备分支。 |
69 | | -- 计算 op 的 `register_fake` 设备无关,torch.compile 图中复用。 |
70 | | -- Attention 不走 `torch.ops`,由 Python 层直接管理 plan/workspace 生命周期。 |
| 99 | +| 配置值 | 行为 | |
| 100 | +|--------|------| |
| 101 | +| `off`、空字符串、`none`、`0` | 全部 eager | |
| 102 | +| `cudagraphs` | prefill/chunked prefill eager;decode 使用完整 CUDA graph | |
| 103 | +| 其他值 | 作为 `torch.compile(model, backend=...)` 的 backend | |
71 | 104 |
|
72 | | -## 风险 |
| 105 | +`cudagraphs` 按 batch bucket 捕获 decode graph:`1/2/4/8`,之后按 16 向上取整, |
| 106 | +不超过 `max_seqs_per_batch`。该上限来自调度配置,不再有独立的 |
| 107 | +`python_graph_max_batch` 参数。 |
| 108 | + |
| 109 | +每个 bucket 使用固定地址的 input、position 和 attention metadata buffer。 |
| 110 | +padding 序列使用 block manager 预留的 block 0;`paged_kv_indices` 静态 buffer |
| 111 | +在真实 KV block 数之外额外预留 `batch_pad` 个 index,避免 KV cache 接近满载时 |
| 112 | +dummy metadata 越界。FlashInfer plan 在 capture/replay 外执行,并与 graph 使用 |
| 113 | +同一条专用 CUDA stream。 |
| 114 | + |
| 115 | +## 算子分类 |
73 | 116 |
|
74 | | -- in-place op(`fused_add_rms_norm` / `fused_qk_norm_rope`)的非 CUDA 实现必须保持 `(a!)` mutating 语义。 |
75 | | -- 跨硬件不保证 bit-identical,对齐标准为贪心解码 token 序列一致。 |
| 117 | +| 类别 | 注册/调用方式 | 位置 | |
| 118 | +|------|---------------|------| |
| 119 | +| CUDA 计算 op(`rms_norm`、`fused_add_rms_norm`、`silu_and_mul`、`fused_qk_norm_rope`、`reshape_paged_cache`) | C++ `TORCH_LIBRARY(xllm_ops)` schema + `TORCH_LIBRARY_IMPL(CUDA)` | `core/kernels/cuda/cuda_ops_library.cpp` | |
| 120 | +| TP 通信 op(`all_reduce`、`all_gather`) | Python `torch.library.custom_op` + `torch.distributed` NCCL group | `python/ops/collectives.py` | |
| 121 | +| Attention(prefill/decode) | 直接调用 FlashInfer Python API | `python/layers/attention.py` | |
| 122 | + |
| 123 | +约束如下: |
| 124 | + |
| 125 | +- `models/` 和 `layers/` 通过 `python.ops` 调用计算和通信算子;设备实现由当前 |
| 126 | + CUDA 注册或 Python collective group 决定。 |
| 127 | +- C++ 注册的计算 op 在 Python 中补充 `register_fake`,供 `torch.compile` 使用。 |
| 128 | +- Attention 不注册为 `torch.ops`,由 Python 层管理 FlashInfer wrapper、plan 和 |
| 129 | + workspace 生命周期。 |
| 130 | +- in-place op(`fused_add_rms_norm`、`fused_qk_norm_rope`)必须保持 schema 中的 |
| 131 | + mutating 语义。 |
| 132 | +- TP=1/TP=2 的正确性验收以相同请求的贪心 token 序列与 native executor 一致为 |
| 133 | + 基准,不要求不同硬件间 bit-identical。 |
0 commit comments