MiaAI-Lab/DeepSeek-v4-Flash-DSpark-2x-DGX-Spark
GitHub: MiaAI-Lab/DeepSeek-v4-Flash-DSpark-2x-DGX-Spark
该项目提供了一套在 2x DGX Spark 集群上部署 DeepSeek-V4-Flash 大模型并启用 100 万上下文窗口的 vLLM 配置与基准测试方案。
Stars: 191 | Forks: 23
# 在 2x DGX Spark 上部署 DeepSeek V4 Flash DSpark C12 NVFP4 KV
用于服务 `DeepSeek-V4-Flash-DSpark` 的独立双节点 DGX Spark 部署方案
采用 vLLM TP=2、DSpark speculative decoding,并使用实验性的 `nvfp4_ds_mla` KV-cache 路径,实现 **1M-token** 的默认最大模型长度。
## 当前运行环境(当前检出版本)
默认 Docker 镜像是预构建的 Anemll GX10/DGX Spark 版本的 vLLM
0.25,原生支持 DSpark / NVFP4 DS-MLA / b12x MoE:
```
ghcr.io/anemll/dspark-vllm-gx10:0.1.1
```
来源:[Anemll/dspark-vllm-gx10](https://github.com/Anemll/dspark-vllm-gx10)。
在首次启动前,在**两个**节点上拉取镜像:
```
docker pull ghcr.io/anemll/dspark-vllm-gx10:0.1.1
```
`docker-compose.dspark.yml` 与该镜像布局保持一致:
- 清除了 entrypoint;command 使用 `/usr/local/bin/vllm serve`
- CUDA 位于 `/usr/local/cuda` 下(而不是 Stage-C 的 `/opt/env`)
- `--moe-backend flashinfer_b12x`
- DSpark **内置于镜像中**(无需将 Stage-C 的 `dspark_proposer.py` 通过 bind-mount 映射到 `/opt/env/...`)
- 可选的 `vllm_patch_gb10/` 挂载仍保留,用于实验性的混合 NVFP4
- HF cache 位于 `/cache/huggingface`;一旦两个节点都有了完整的本地 hub cache,首选设置 `HF_HUB_OFFLINE=1`(在线重新下载可能会占满 worker 磁盘)
备选方案:设置 `DSPARK_VLLM_IMAGE=vllm-dspark-runtime:dspark-nvfp4-stage-c` 并运行 `./build-dspark-vllm-runtime.sh` 来进行历史多阶段 Stage-C 覆盖构建。Stage-C 方案和覆盖源码保留在 `recipe/` 下。
使用 Stage-C 时,还需要合并 `docker-compose.stage-c.override.yml` 并在 `.env.dspark` 中启用 Stage-C 环境变量块(参见 [`docs/ENVS.md`](docs/ENVS.md))。
本仓库依然内置了 Keys 的 DSpark 并发补丁和 Stage-C 覆盖源码,用于本地镜像构建和文档记录。使用 Anemll 镜像时,该逻辑直接内置在镜像中,而不是作为主机的 bind-mount。
**默认的 agent 服务配置**(`.env.dspark.example` 和 README 默认值):
- 镜像:`ghcr.io/anemll/dspark-vllm-gx10:0.1.1`
- 模型:`deepseek-ai/DeepSeek-V4-Flash-DSpark`(HF hub id;当设置 `HF_HUB_OFFLINE=1` 时从缓存离线解析)
- `max_model_len=1048576`(**1M** —— 保留此作为文档化的默认值)
- `max_num_seqs=6`
- `max_num_batched_tokens=8192`
- `kv_cache_dtype=nvfp4_ds_mla`
- `gpu_memory_utilization=0.85`
- `MTP_NUM_TOKENS=3`
- API 绑定地址 `0.0.0.0:8888`
本地 `.env.dspark` 可以为特定的集群调低 `MAX_MODEL_LEN`(例如 `512000`),而无需更改方案默认值。
本仓库记录了经过验证的 1M NVFP4 agent 配置、历史的 Stage-C 检查点,以及当前的 Anemll 预构建运行环境:
- 默认 `max_model_len=1048576` (1M),`max_num_seqs=6`,`kv_cache_dtype=nvfp4_ds_mla`
- 默认镜像 `ghcr.io/anemll/dspark-vllm-gx10:0.1.1`(在此集群上约有 ~2.8M-token 的 KV pool)
- 历史 Stage-C C12 pool:`3,225,280 tokens`
- 在经过验证的 C12 门控上,单流解码速度保持在 `50 tok/s` 以上
- 确定性的直接提示词测试已完成,没有出现中文漂移或重复的垃圾内容
- 2/4/6 个并发的代码门控提示词均顺利完成 (Stage-C C12)
- DSpark 并发补丁在 `max_model_len=200000`,`max_num_seqs=16` 下得到验证
(静态 C16 `315.1` / 交错式 C16 `205.0` tok/s 聚合)
如果您之前部署了旧版本并遇到了 agent 乱码、循环、中文漂移,或提示词/工具 XML 泄露到回复中的情况,请保留 C12 NVFP4 配置,并在更改 agent 框架设置之前验证直接的 API 行为。该修复路径不会将生产环境切换到 fp8 或更小的后备模型。
## 结果
### 实时 Anemll 镜像测试通道(当前检出版本)
使用预构建的 Anemll 镜像和本仓库的 compose/启动脚本(TP=2,双节点)验证了 worker 优先启动的方式。
运行环境:
- 镜像:`ghcr.io/anemll/dspark-vllm-gx10:0.1.1`
- 模型 ID:`deepseek-ai/DeepSeek-V4-Flash-DSpark`(位于 `HF_CACHE` 下的 HF cache)
- 服务模型名称:可通过 `SERVED_MODEL_NAME` 配置(例如:`deepseek-v4-flash`)
- `kv_cache_dtype=nvfp4_ds_mla`
- 默认方案:`max_model_len=1048576`,`max_num_seqs=6`,
`max_num_batched_tokens=8192`,`gpu_memory_utilization=0.85`,`MTP_NUM_TOKENS=3`
- `--moe-backend flashinfer_b12x`
- `VLLM_USE_FLASHINFER_SAMPLER=1`,`VLLM_USE_B12X_WO_PROJECTION=1`
- 在两个节点都有完整的 hub cache 后,推荐设置 `HF_HUB_OFFLINE=1`
- fabric:显式的 `VLLM_HOST_IP` / `WORKER_VLLM_HOST_IP`,以及匹配的
`NCCL_SOCKET_IFNAME` / `TP_SOCKET_IFNAME` / `GLOO_SOCKET_IFNAME`
在此集群上的启动证据(Anemll 镜像,1M max-model-len 配置):
```
Available KV cache memory: 19.03 GiB
GPU KV cache size: 2,826,378 tokens
Maximum concurrency for 1,048,576 tokens per request: 2.70x
Application startup complete.
```
直接 API 冒烟测试:`/v1/models` 返回 HTTP 200,并且在 head 和 worker 节点上均返回了非空的兼容 OpenAI 的 chat completions 内容。
### 实际解码速度(第一个 token 之后)
在实时的 Anemll 通道上,使用 **agent / 文件写入**提示词(`max_tokens=512`,temperature 0,每个请求包含唯一的 nonce,3 次试验,按聚合取中位数)进行的流式纯解码基准测试。Prefill 和第一个 token 被**排除在外**。
| 指标 | 公式 |
| --- | --- |
| 单流解码 tok/s | `(completion_tokens − 1) / (t_last − t_first)` |
| 聚合解码 tok/s | `sum(completion_tokens − 1) / (max t_last − min t_first)` |
| 并发数 | 成功率 | 聚合解码 tok/s | 平均单流解码 tok/s | 解码窗口 (s) | 解码 tokens |
| ---: | :---: | ---: | ---: | ---: | ---: |
| 1 | 1/1 | 66.6 | 66.6 | 7.67 | 511 |
| 2 | 2/2 | 93.3 | 47.2 | 10.95 | 1022 |
| 3 | 3/3 | 92.8 | 31.9 | 16.52 | 1533 |
| 4 | 4/4 | 123.8 | 32.8 | 16.51 | 2044 |
| 5 | 5/5 | 121.1 | 25.7 | 21.11 | 2555 |
| 6 | 6/6 | 153.7 | 26.8 | 19.94 | 3066 |
各次试验聚合结果(解码 tok/s):C1 `[66.5, 69.4, 66.6]`,C2 `[92.1, 95.6, 93.3]`,
C3 `[89.4, 92.8, 93.5]`,C4 `[129.1, 123.8, 121.7]`,C5 `[125.0, 121.1, 111.6]`,
C6 `[153.7, 148.8, 157.0]`。
**聚合解码 (Agg decode)** 是生成第一个 token 后的集群总生成速度;**平均单流 (mean stream)** 是 token 开始生成后单个并发聊天感受到的速度(单独运行约 67 tok/s,C=6 时约 27 tok/s)。
在多流竞争下,当单流解码速度下降时,C3 ≈ C2 且 C5 ≈ C4(在聚合层面上)。
### 2026-07-02 Keys C12 NVFP4 检查点(历史 Stage C)
之前在 Tony 的 Stage C NVFP4 镜像上使用 Keys 的 C12 服务配置进行的高并发通道测试(保留用于对比;并非当前的默认镜像)。
运行环境:
- 测试 endpoint:`http://100.90.25.78:8888/v1`
- 服务模型:`deepseek-v4-flash-dspark`
- 镜像:`vllm-dspark-runtime:dspark-nvfp4-stage-c`
- 模型路径:`/cache/huggingface/fraserprice/DeepSeek-V4-Flash-DSpark`
- `kv_cache_dtype=nvfp4_ds_mla`
- `max_model_len=1048576`
- `max_num_seqs=6`
- `max_num_batched_tokens=8192`
- `gpu_memory_utilization=0.85`
- `MTP_NUM_TOKENS=3`
- `VLLM_USE_FLASHINFER_SAMPLER=1`
- `VLLM_USE_B12X_WO_PROJECTION=1`
- `VLLM_DSPARK_GPU_REJECTED_CONTEXT_MASK=1`
- `thinking=false`
- `--generation-config vllm`
- 无 `--override-generation-config`
启动证据:
```
GPU KV cache size: 3,225,280 tokens
Maximum concurrency for 1,000,000 tokens per request: ~3.2x
Application startup complete.
```
代码门控验证:
| 并发数 | 成功率 | 服务器生成 tok/s | 接受率 | 错误输出 |
| ---: | ---: | ---: | ---: | ---: |
| 1 | 1/1 | 52.79 | 0.585 | 0 |
| 2 | 2/2 | 79.76 | 0.600 | 0 |
| 4 | 4/4 | 134.70 | 0.602 | 0 |
| 6 | 6/6 | 127.78 | 0.615 | 0 |
| 12 | 12/12 | 230.10 | 0.602 | 0 |
此运行的上游检查点说明未导入到当前检出中;本仓库保留了运行时的更改和验证摘要,但没有保留上游的 benchmark 产出文件夹。
请勿在此 Stage C 镜像上启用 `VLLM_USE_B12X_FP8_GEMM=1`。在测试中,该标志在 DSpark drafter 预热阶段触发了 DeepGEMM 布局断言错误。
### 2026-06-30 干净的 Agent 服务检查点
在将模型重新交由 Hermes/OpenClaw 类框架处理之前,先在 Asusi/Spark4 上重现了之前保守且干净的 endpoint。
运行环境:
- 测试 endpoint:`http://100.90.25.78:8888/v1`
- 服务模型:`deepseek-v4-flash-dspark`
- 该通道上使用的镜像:`vllm-dspark-runtime:mia-raf-pr1-nvfp4-keys-c`
- 模型路径:`/cache/huggingface/fraserprice/DeepSeek-V4-Flash-DSpark`
- `kv_cache_dtype=nvfp4_ds_mla`
- `max_model_len=1048576`
- `max_num_seqs=6`
- `max_num_batched_tokens=8192`
- `gpu_memory_utilization=0.80`
- `MTP_NUM_TOKENS=5`
- `thinking=false`
- `--generation-config vllm`
- `--override-generation-config '{"temperature":0.0,"top_p":1.0}'`
- 每个节点显式指定 `VLLM_HOST_IP` 值
启动证据:
```
GPU KV cache size: 1,990,142 tokens
Maximum concurrency for 1,048,576 tokens per request: 1.90x
Application startup complete.
```
直接验证:
- `/v1/models` 报告 `"max_model_len": 1048576`
- 确定性健全性提示词返回 `NVFP4 DSPARK OK`
- 五个较长的英文提示词顺利完成,没有出现 CJK 漂移或重复的垃圾内容
- 代码门控服务器平均解码速度:`54.22 tok/s`
- 2/4/6 个并发的直接提示词均干净地成功完成
并发情况:
| 并发数 | 成功率 | 聚合 tok/s | 稳定性 |
| ---: | ---: | ---: | --- |
| 2 | 2/2 | 60.95 | 无 CJK/重复垃圾内容 |
| 4 | 4/4 | 83.21 | 无 CJK/重复垃圾内容 |
| 6 | 6/6 | 104.11 | 无 CJK/重复垃圾内容 |
此运行的上游检查点说明未导入到当前检出中。
### 1M NVFP4 配置
在 2x DGX Spark 上验证,每个节点一个 GPU,TP=2,单流。
| 用例 | 服务器 tok/s | TTFC | 接受率 | 接受/草稿 |
| --- | ---: | ---: | ---: | ---: |
| p256/g64 | 54.46 | 0.506s | 0.667 | 3.33 |
| p256/g256 | 65.38 | 0.324s | 0.718 | 3.59 |
| p512/g64 | 56.26 | 2.738s | 0.625 | 3.13 |
| p512/g256 | 54.41 | 0.422s | 0.550 | 2.75 |
| p512/g256 warmup1 | 56.73 | 0.417s | 0.585 | 2.92 |
启动日志报告:
```
GPU KV cache size: 2,044,166 tokens
Maximum concurrency for 1,048,576 tokens per request: 1.95x
```
API 报告:
```
{"max_model_len":1048576}
```
此运行的上游检查点说明未导入到当前检出中。
### DSpark 并发配置
在同样的 2x DGX Spark TP=2 部署上使用 Keys 的 DSpark 并发补丁进行验证,使用 `kv_cache_dtype=nvfp4_ds_mla`,`max_model_len=200000`,
`max_num_seqs=16`,`MTP_NUM_TOKENS=5`,以及
`VLLM_DSPARK_GPU_REJECTED_CONTEXT_MASK=1`。
补丁来源:
- [drowzeys/Keys-Concurrency-Patch-for-DSpark-DeepSeek-V4-Flash](https://github.com/drowzeys/Keys-Concurrency-Patch-for-DSpark-DeepSeek-V4-Flash)
- 测试的补丁 commit:`7e4d94bbcec95223550517c0fa9244e59f9f6483`
此处记录的实时修复保留了 `kv_cache_dtype=nvfp4_ds_mla`,并使用来自该 commit 的经过路径调整的 Patch 2b 更新,刷新了仓库中已经内置的 Keys 覆盖代码。在 Patch 2b 中,不规则的 `query_start_loc` 检测不再依赖于 `num_rejected_tokens_gpu`。只有在内置的兼容 OpenAI 的聊天冒烟请求以及 agent 客户端验证在实时服务上通过后,才应将服务视为已验证。
静态同步批次,一个 TP=2 副本:
| 并发数 | 最佳聚合 tok/s | 单流 tok/s | 接受率 |
| ---: | ---: | ---: | ---: |
| 1 | 57.6 | 57.6 | 0.635 |
| 4 | 140.8 | 35.2 | 0.619 |
| 8 | 252.6 | 31.6 | 0.635 |
| 16 | 315.1 | 19.7 | 0.609 |
交错式独立到达,一个 TP=2 副本:
| 并发数 | 成功率 | 聚合 tok/s | 接受率 |
| ---: | ---: | ---: | ---: |
| 4 | 4/4 | 109.2 | 0.544 |
| 8 | 8/8 | 147.3 | 0.534 |
| 16 | 16/16 | 205.0 | 0.567 |
正确性健全性检查:在系统扰动下,确定性的受害输出保持字节完全相同。一项中等扰动的压缩测试测得接受率为 `0.529`,在整个扰动窗口内速度达到 `99.7 tok/s`。
此运行的上游检查点说明未导入到当前检出中。
### 历史 60 tok/s DSpark 基线
早先的约 60 tok/s 数据已被复现,但那是一个单独的诊断配置,不是本仓库默认的 1M NVFP4 部署方案:
- 镜像由 `rafaelcaricio/vllm#1` commit `3519c3b88` 重新构建
- `max_model_len=262144`
- `max_num_seqs=1`
- `kv_cache_dtype=fp8`
- `MTP_NUM_TOKENS=5`
- `thinking=false`
- `temperature=0.0`,`top_p=1.0`
- 在 `code_completion` 门控上测得 `63.97 tok/s`,DSpark 接受率为 `67.9%`
使用此配置来诊断镜像/运行环境漂移。不要将其与生产环境的 1M NVFP4 路径混淆。此运行的上游检查点说明未导入到当前检出中。
### 2026-06-29 Full-1M 并发微型基准测试
上面的 200K/16 配置最大化了原始并发。对于想要**完整 1M 上下文上限同时保持并发**的 agent 集群,请使用 `max_model_len=1048576` 以及 `max_num_seqs=6` 运行。每个请求仍然可以增长到 1M,同时最多可以有 6 个会话并发运行,因为真正的限制因素是共享的 KV pool,而不是每个槽位的预留(参见 [KV cache 的工作原理](#how-the-kv-cache-works-why-1m--concurrency-is-safe))。
在 2026-06-29 的代码补全微型基准部署上进行了验证 (NVFP4,
`max_model_len=1048576`,`max_num_seqs=6`,
`VLLM_DSPARK_GPU_REJECTED_CONTEXT_MASK=1`,`VLLM_USE_B12X_WO_PROJECTION=1`):
- 启动:`GPU KV cache size: 1,901,239 tokens`,`Maximum concurrency for 1,048,576 tokens per request: 1.81x`
- 6 个并发请求:**6/6 成功**,**~182 tok/s 聚合**(每个流约 30 tok/s),无 OOM / 无抢占失败
- 在相同配置下的单流解码:~67 tok/s (代码)
当大多数会话远低于 1M 时(典型的 agent 交互),这是正确的形态,但您仍然希望保留 1M 的上限。对于 Hermes/OpenClaw 框架验证,上面提到的较新的 2026-06-30 agent 稳定性检查点是更值得引用的安全数据。
## KV cache 的工作原理(为什么 1M + 并发是安全的)
三个经常被混淆的独立参数:
| 参数 | 含义 | 当前构建版本 |
| --- | --- | --- |
| **KV cache pool** | 以 token 为单位的总共享 KV 内存,根据权重加载后的 `gpu_memory_utilization` 进行分配 | Anemll 镜像上约 2.8M tokens(当前检出);历史 Stage-C C12 上约 3.2M |
| `max_model_len` | 每个请求的**上限** —— 任何单个请求可以增长到的最大长度 | 默认 **1,048,576 (1M)** |
| `max_num_seqs` | **并发上限** —— 调度器同时运行的最大活跃序列数 | 6 |
Pool 是**共享并按需分配的**:PagedAttention 在每个请求生成 token 时为其分配 KV 块,并在完成时释放它们。`max_model_len` 和 `max_num_seqs` 是**上限,而不是预留量** —— vLLM 不会预先分配 `max_num_seqs × max_model_len` 大小的 KV。因此,真正的约束条件是:
```
sum(live tokens across all active requests) <= KV pool
```
在 1M 上限 / 6 个槽位下的计算示例:
```
6 requests x 50k tokens = 300k fits easily
6 requests x 200k tokens = 1.2M fits in the Anemll / C12 pools
6 requests x 500k tokens = 3.0M near pool capacity depending on image
3 requests x 1M tokens = 3.0M near pool capacity depending on image
6 requests x 1M tokens = 6.0M impossible — excess requests queue/preempt
```
启动日志中的 `Maximum concurrency for 1,048,576 tokens per request: ~2.7x`
(此集群上的 Anemll 镜像)仅意味着可以容纳少量*同时发起的完整 1M* 请求。Agent 交互几乎永远不会接近 1M,因此六个正常长度的会话可以共享 pool,同时为极少出现的长请求保留 1M 上限可用。这正是为什么 `1M + max_num_seqs=6` 有用的原因:您不是在预留 6×1M,而是在高上限下让多个短请求共享同一个 pool。
## 注意事项:乱码、循环、中文漂移或提示词/XML 泄露
如果模型能启动,且像 `hi` 这样的基本提示词也能正常工作,但真实的 agent 流量随机变成了重复的字符、中文漂移、泄露了工具/schema XML,或者在 Telegram 上可见的垃圾内容,请不要认为是权重损坏。
对于此部署,在责怪权重之前,需进行三项检查:
1. **运行时镜像 + DSpark 路径:**使用 Anemll 镜像时,确认两个节点运行相同的 tag(`docker image inspect $DSPARK_VLLM_IMAGE`),并且 compose 使用的是 `/usr/local/bin/vllm`(而不是 Stage-C 的 `/opt/env` 路径)。对于历史的 Stage-C 构建,还需确保 `recipe/vllm/v1/spec_decode/dspark_proposer.py` 下的 Keys proposer 路径和覆盖源码与您构建的镜像一致。
2. **两个节点上的模型缓存:**head 和 worker 上必须存在 `deepseek-ai/DeepSeek-V4-Flash-DSpark` 的完整离线 HF hub cache(完成后设置 `HF_HUB_OFFLINE=1`)。不完整的缓存或在线重新下载会占满 worker 磁盘并导致 TP=2 启动失败。
3. **解码/后备安全性:**对于较长的兼容 OpenAI 的 agent 提示词,避免不稳定的采样和隐藏的后备切换。服务器保留 `--generation-config vllm` 且不安装服务器端的 `--override-generation-config`;显式的客户端请求参数仍然具有最高优先级。
compose 启动器包含 `--generation-config vllm`,设置了 `thinking=false`,使用 DSpark speculative decoding 以及 `MTP_NUM_TOKENS=3` 和 `draft_sample_method=probabilistic`,并启用了 FlashInfer sampler。若要进行完全确定性的 curl 检查,请在请求体中发送 `temperature: 0`。
在验证期间,还要清除 agent 的后备列表。如果编排层静默发生后备切换、重启会话,或将陈旧的提示词/工具记录重播到可见的消息流中,那么在直接的 vLLM 测试中看起来已修复的模型,仍然可能看起来像中了毒。除非您有意测试该框架,否则请将 OpenClaw/Hermes 的更改与模型运行时验证分开。
实时修复后需运行的验证门控:
```
direct vLLM prompts: clean
direct concurrent vLLM prompts: clean
agent harness prompts: clean, DeepSeek, no fallback
MTP3 probabilistic draft sampling active
```
这将保留 NVFP4 KV 和 MTP3。除非您有意接受上下文和质量的折衷,否则不要仅仅为了掩盖症状而切换到 fp8 或退回到更小的后备模型。
## 重要说明
## 致谢
有关完整的归属和许可证说明,请参见 [`CREDITS.md`](CREDITS.md)。
### 特别感谢
**[drowzeys ("Keys")](https://github.com/drowzeys/)** —— 如果没有 Keys 的公开成果,本仓库将无法在真实并发下正确运行。Keys 发布了 DSpark 服务器内并发补丁、请求稳定的 主 KV slot 映射、用于混合 prefill/decode 批次的不规则 `query_start_loc` 路径,以及在 DGX Spark 上早期的 `nvfp4_ds_mla` KV-cache 连接方式。我们的覆盖代码、bind-mounted proposer 以及测量的并发数据都直接建立在这一基础之上。
### 其他贡献者
- **[drowzeys](https://github.com/drowzeys/) / Keys 并发补丁:**
[Keys-Concurrency-Patch-for-DSpark-DeepSeek-V4-Flash](https://github.com/drowzeys/Keys-Concurrency-Patch-for-DSpark-DeepSeek-V4-Flash)
- **[tonyd2wild](https://github.com/tonyd2wild/)** —— NVFP4 1M 方案脉络、
乱码修复启动器默认值,以及我们合并到运行时 proposer bind-mount 中的非均匀批次防护
- **Rafael Caricio** —— DSpark vLLM 集成和部署工作:
[vllm#1](https://github.com/rafaelcaricio/vllm/pull/1),
[spark_vllm_docker#1](https://github.com/rafaelcaricio/spark_vllm_docker/pull/1)
- **Fraser Price** —— DeepSeek V4 Flash DSpark 模型/运行时工作:
[DeepSeek-V4-Flash-DSpark](https://huggingface.co/fraserprice/DeepSeek-V4-Flash-DSpark),
[dspark-vllm](https://github.com/fraserprice/dspark-vllm)
- **MiaAI-Lab** —— 双节点 DGX Spark 封装和 优先启动操作手册:
[DeepSeek-v4-Flash-DSpark-2x-DGX-Spark](https://github.com/MiaAI-Lab/DeepSeek-v4-Flash-DSpark-2x-DGX-Spark)
- **[Anemll](https://github.com/Anemll/dspark-vllm-gx10)** —— 预构建的
`ghcr.io/anemll/dspark-vllm-gx10` vLLM 0.25 镜像,适用于带有 NVFP4 DS-MLA 和 b12x MoE 的双节点 GB10 / DGX Spark
- **上游基础** —— vLLM, FlashInfer, NVIDIA Blackwell/CUDA/NCCL
工具链,DeepSeek V4 Flash,以及 DeepSeek-AI DeepSpec / DSpark 研究
### MiaAI-Lab 贡献
MiaAI-Lab 维护着此 fork 中经过验证的 1M NVFP4-KV 方案、Stage A/B/C 运行时打包、干净的双节点启动流程、Keys 补丁集成以及 compose/启动工具。当前检出版本默认使用 Anemll 预构建镜像,同时保留了 Stage-C 构建脚本以供可选的本地重新构建。
## 许可证说明
仓库脚本和文档发布在本仓库的 `LICENSE` 下。vLLM 覆盖/运行时文件派生自 vLLM,并在存在的地方保留其 Apache-2.0 血缘关系和 SPDX 标头。基础镜像、FlashInfer/TileLang/Triton/CUDA/NCCL 以及模型权重是独立的上游产物,具有各自的许可证和使用条款。
## 文件
| 路径 | 用途 |
| --- | --- |
| `docker-compose.dspark.yml` | 双节点 vLLM/DSpark 服务(默认为 Anemll 镜像布局) |
| `.env.dspark.example` | 清理后的集群模板;默认镜像 Anemll `0.1.1`,**1M** 上下文 |
| [`docs/ENVS.md`](docs/ENVS.md) | Anemll 与 Stage-C 环境变量注册表矩阵(未知 `VLLM_*` 警告) |
| `docker-compose.stage-c.override.yml` | 可选的仅 Stage-C 环境变量注入 |
| `start-deepseek-v4-flash-dspark.sh` | worker 优先启动和冒烟测试;镜像必须在两个节点上都存在 |
| `stop-deepseek-v4-flash-dspark.sh` | 停止 head 和 worker 服务 |
| `status-deepseek-v4-flash-dspark.sh` | 显示 head/worker 容器状态 |
| `logs-deepseek-v4-flash-dspark.sh` | 滚动查看 head/worker 的 DSpark 日志 |
| `smoke-deepseek-v4-flash-dspark.sh` | 直接并发兼容 OpenAI 的冒烟测试 |
| `validate-dspark-config.sh` | 渲染并检查本地 DSpark compose/env 配置 |
| `prepare-dspark-model-cache.sh` | 下载/校验模型缓存 |
| `build-dspark-vllm-runtime.sh` | 可选的 Stage-C 本地镜像构建(Anemll 不需要) |
| `recipe/overlay/` | 用于本地镜像构建的 Stage-C DSpark vLLM 覆盖源码 |
| `recipe/vllm/v1/spec_decode/dspark_proposer.py` | Stage-C/proposer 参考;启动脚本可能会同步到 worker |
| `recipe/nvfp4/Dockerfile.stage-*` | 用于本地构建的 Stage A/B/C NVFP4 镜像层 |
| `patches/keys-concurrency.patch` | 完整的经过路径调整的 Keys 并发补丁参考 |
| `vllm_patch_gb10/` | 可选的实验性 GB10 混合 NVFP4 vLLM 插件 |
| `docs/PATCHES.md` | 通俗易懂的 Patch 1 / Patch 2 / Patch 2b 并发原理解释 |
| `scripts/verify-overlay-sources.sh` | 在构建 Stage-C 镜像前检查覆盖源码 |
## 快速开始
在 head 节点上运行。
```
cp .env.dspark.example .env.dspark
```
根据您的集群修改以下值:
- `WORKER_HOST`
- `WORKER_SCRIPT_DIR`(如果 worker 的检出/部署路径与 head 不同)
- `MASTER_ADDR`
- `NCCL_IB_HCA`
- `NCCL_SOCKET_IFNAME`(以及匹配的 `TP_SOCKET_IFNAME` / `GLOO_SOCKET_IFNAME`,或者留空让 compose 继承 NCCL IF)
- `NCCL_IB_GID_INDEX`(并不总是为 0 —— 请与您的 RoCE GID 匹配)
- `HF_CACHE`
- `WORKER_HF_CACHE`(如果 worker 的缓存路径与 head 不同)
- `VLLM_HOST_IP` 和 `WORKER_VLLM_HOST_IP`(每个节点的 fabric IP)
集群 fabric 示例值(请根据您的节点进行修改 —— f0 vs f1 和 GID index 会有所不同):
```
WORKER_HOST=10.0.0.2
MASTER_ADDR=10.0.0.1
VLLM_HOST_IP=10.0.0.1
WORKER_VLLM_HOST_IP=10.0.0.2
MASTER_PORT=25000
NCCL_IB_HCA=rocep1s0f1
NCCL_SOCKET_IFNAME=enp1s0f1np1
TP_SOCKET_IFNAME=enp1s0f1np1
GLOO_SOCKET_IFNAME=enp1s0f1np1
DSPARK_VLLM_IMAGE=ghcr.io/anemll/dspark-vllm-gx10:0.1.1
```
除非您是有意进行实验,否则请保留这些**默认的** agent 服务参数(不要将临时的本地 `MAX_MODEL_LEN` 覆盖视为方案默认值):
- `VLLM_HOST=0.0.0.0`(如果 Hermes/OpenClaw 或其他机器需要访问 API)
- `MAX_MODEL_LEN=1048576` (**1M**)
- `MAX_NUM_SEQS=6`
- `MAX_NUM_BATCHED_TOKENS=8192`
- `GPU_MEMORY_UTILIZATION=0.85`
- `MTP_NUM_TOKENS=3`
- `HF_HUB_OFFLINE=1`(在两个节点都有完整的模型缓存后启用)
- `VLLM_USE_FLASHINFER_SAMPLER=1`
- `VLLM_DSPARK_GPU_REJECTED_CONTEXT_MASK=1`
- `VLLM_USE_B12X_WO_PROJECTION=1`
- `VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=0`
在 **head 和 worker** 上拉取默认运行时镜像:
```
docker pull ghcr.io/anemll/dspark-vllm-gx10:0.1.1
```
可选:改为构建历史 Stage-C 镜像:
```
./build-dspark-vllm-runtime.sh
# 然后设置 DSPARK_VLLM_IMAGE=vllm-dspark-runtime:dspark-nvfp4-stage-c
# 以及为 prepare-dspark-model-cache.sh 设置 IMAGE_PYTHON=/opt/env/bin/python
```
在两个节点上准备模型缓存(或者通过 rsync 同步一个已校验的 hub 快照):
```
./prepare-dspark-model-cache.sh
```
即使 `.env.dspark` 中设置了 `HF_HUB_OFFLINE=1`,`prepare-dspark-model-cache.sh` 也会在下载步骤强制使用 HF 在线模式(这在缓存预热后提供服务时是正确的)。
在 Anemll 镜像上使用 `IMAGE_PYTHON=/usr/bin/python3`(默认);Stage-C 需要 `IMAGE_PYTHON=/opt/env/bin/python`。
启动服务:
```
./start-deepseek-v4-flash-dspark.sh
```
可选的实验性 GB10 混合 NVFP4 插件:
```
ENABLE_VLLM_GB10_PATCH=1 ./start-deepseek-v4-flash-dspark.sh
```
启用后,启动器会将 `vllm_patch_gb10/` 同步到 worker,将其挂载到两个容器中,使用 `pip install -e --no-deps` 安装它,设置 `VLLM_PLUGINS=gb10_hybrid_nvfp4`,并使用 `--quantization modelopt_gb10_hybrid` 启动 vLLM。默认禁用。使用 `GB10_HYBRID_NVFP4_M_THRESHOLD` 调整调度器阈值;默认为 `128`。
启动脚本会打印解析出的非机密运行时配置,将 compose/env(及相关文件)同步到 worker 路径,验证两个节点上渲染出的 Docker Compose,先启动 worker,然后启动 head,并在等待 API 就绪时跟踪启动日志。如果启动失败,它会在退出前打印最近的 head 和 worker 日志。
API 服务地址:
```
http://HEAD_NODE_IP:8888/v1
```
如果仅进行 head 节点测试,请设置 `VLLM_HOST=127.0.0.1`。如果要让 Hermes/OpenClaw 或其他机器使用该 endpoint,请保持 `VLLM_HOST=0.0.0.0` 并在网络/防火墙层控制访问权限。
## 运行时配置
### C12 Agent 服务配置(默认:1M 上下文)
核心 vLLM 标志(来自 `docker-compose.dspark.yml`):
- 镜像:`ghcr.io/anemll/dspark-vllm-gx10:0.1.1`(使用 `DSPARK_VLLM_IMAGE` 覆盖)
- `/usr/local/bin/vllm serve …`
- `--tensor-parallel-size 2`
- `--distributed-executor-backend mp`
- `--nnodes 2`
- `--kv-cache-dtype nvfp4_ds_mla`
- `--block-size 256`
- `--max-model-len 1048576`(**默认 1M**)
- `--max-num-seqs 6`
- `--max-num-batched-tokens 8192`
- `--max-cudagraph-capture-size 24` (`max_num_seqs * (MTP_NUM_TOKENS + 1)` → `6 * 4`)
- `--gpu-memory-utilization 0.85`
- `--moe-backend flashinfer_b12x`
- `--async-scheduling`
- `--enable-chunked-prefill`
- `--speculative-config '{"method":"dspark","num_speculative_tokens":${MTP_NUM_TOKENS:-3},"draft_sample_method":"probabilistic"}'`
- `--generation-config vllm`
关键运行时环境变量:
- `DSPARK_VLLM_IMAGE=ghcr.io/anemll/dspark-vllm-gx10:0.1.1`
- `HF_HUB_OFFLINE=1`(当两个节点上的 hub 缓存均完整时启用)
- `ENABLE_VLLM_GB10_PATCH=0`(默认);设置为 `1` 可加载可选的 `vllm_patch_gb10/` 插件并添加 `--quantization modelopt_gb10_hybrid`
- `GB10_HYBRID_NVFP4_M_THRESHOLD=128`
- `VLLM_USE_FLASHINFER_SAMPLER=1`
- `VLLM_USE_B12X_MOE=1`
- `VLLM_USE_B12X_WO_PROJECTION=1`
- `VLLM_DSPARK_GPU_REJECTED_CONTEXT_MASK=1`
- `VLLM_DSPARK_CONFIDENCE_SCHEDULER=off`
- `VLLM_DSPARK_LOCAL_ARGMAX=1`
- `VLLM_DSPARK_REPLICATE_MARKOV_W1=1`
- `VLLM_DSPARK_FUSED_MARKOV_ARGMAX=0`
- `VLLM_DSPARK_REFERENCE_KV_QUANT_DEQUANT=0`
- `VLLM_DSV4_B12X_COMPRESSED_MLA=0`
- `VLLM_DSV4_DSPARK_DEFER_TARGET_CAPTURE=0`
- `B12X_W4A16_TC_DECODE=0`
- `DG_JIT_NVCC_COMPILER=/usr/local/cuda/bin/nvcc`
### 200k 并发配置
对于 DSpark 并发,请使用包含的覆盖文件加上 Keys 的并发补丁,并设置:
- `MAX_MODEL_LEN=200000`
- `MAX_NUM_SEQS=16`
- `VLLM_USE_B12X_WO_PROJECTION=1`
- `VLLM_DSPARK_GPU_REJECTED_CONTEXT_MASK=1`
### 1M 单流传统配置
对于保守的单流测试,请设置 `MAX_NUM_SEQS=1` 以及 `VLLM_USE_B12X_WO_PROJECTION=0`。除非您是有意进行实验,否则请保持 `MTP_NUM_TOKENS=3`;当前本地运行时在 MTP3 下使用概率性的 DSpark draft 采样。
## 验证
启动后:
```
curl -fsS http://127.0.0.1:8888/v1/models
```
确认返回的模型条目报告如下:
```
"max_model_len": 1048576
```
然后检查日志:
```
docker compose --env-file .env.dspark -f docker-compose.dspark.yml logs vllm-dspark \
| grep -E "GPU KV cache size|Maximum concurrency"
```
在 1M max-model-len / 0.85 GPU util 下的 Anemll 镜像上,预期大致如下:
```
GPU KV cache size: approximately 2.8M tokens
Maximum concurrency for 1,048,576 tokens per request: approximately 2.7x
```
历史 Stage-C C12 启动报告了约 2–3.2M tokens 以及约 1.9–3.2x 的倍数,具体取决于镜像和利用率;请始终以您节点的实际启动日志为准。
在将 agent 框架指向该 endpoint 之前,请运行包含的冒烟测试:
```
./smoke-deepseek-v4-flash-dspark.sh
```
如果直接兼容 OpenAI 的提示词结果是干净的,但 agent 仍然出现乱码,请在责怪 DSpark 权重之前,先调查 agent 会话、后备列表或框架的提示词重播机制。
## 备注
- 旧的速度检查点是单流,而不是聚合吞吐量。
- 高并发基准测试是聚合吞吐量,并且在 `max_model_len=200000` 下进行了验证,而不是完整的 1M 上下文。
- 完整上下文和高并发会争用同一个 KV pool。C12 1M 配置适用于大多数会话远低于 1M 上限的常规 agent 流量;它并不意味着支持十二个同时发起的完整 1M 请求。
- 要将 DSpark 并发与更长的上下文结合使用,请先选择一个较低的上下文目标,然后在观察启动日志、KV 分配、接受率和请求错误的同时,缓慢提高并发数。
- 1M 作为已启动/已公布的 `max_model_len` 进行了验证,并留有 KV 余量和短提示词的速度探测。本仓库并不声称完成了完整的 1M-token 检索正确性基准测试。
- 测量的探测使用的是带有 g64/g256 的 p256/p512。如果您更改了采样、批处理、上下文长度、WO projection、compressed MLA 或 confidence scheduler,请重新进行基准测试。
- **默认的** agent 服务配置为 `MAX_MODEL_LEN=1048576` (1M),`MAX_NUM_SEQS=6`,`MAX_NUM_BATCHED_TOKENS=8192`,`GPU_MEMORY_UTILIZATION=0.85`,`MTP_NUM_TOKENS=3`,`DSPARK_VLLM_IMAGE=ghcr.io/anemll/dspark-vllm-gx10:0.1.1`,`VLLM_USE_FLASHINFER_SAMPLER=1`,`VLLM_DSPARK_GPU_REJECTED_CONTEXT_MASK=1`,`VLLM_USE_B12X_WO_PROJECTION=1`,无 generation override,以及 `VLLM_DSV4_B12X_COMPRESSED_MLA=0`。本地 `.env.dspark` 可以临时降低上下文(例如 512k),而无需更改该方案默认值。
- Worker 优先启动避免了多节点 `mp` 初始化期间的竞态条件,并在启动容器之前验证两个节点上渲染出的 compose。
- 要求两个节点上具有匹配的镜像、正确的 NCCL/RoCE 设置,以及双节点 Blackwell 级/DGX Spark 配置。
- 建议在 DGX Spark 主机上**禁用 earlyoom** (`sudo systemctl stop earlyoom && sudo systemctl disable earlyoom`)。
earlyoom 守护进程可能会在 GPU 内存压力较高时(例如,在并发处理深上下文工作负载期间)OOM-kill vLLM worker 或 head 进程,即使系统有可用的交换空间或 OOM 是暂时的。禁用它可以避免不必要的进程终止和服务中断。
- 示例模板绑定到 `0.0.0.0:8888` 以用于多主机 agent;如果仅进行 head 测试,请设置 `VLLM_HOST=127.0.0.1` 并在防火墙处控制暴露面。
- 接下来可以尝试的最大序列阶梯大约是 1.25M、1.5M,然后是 1.75M,并采用相同的启动/日志/速度门控。仅仅依靠原始的 KV 数学计算是不够的,因为 DeepSeek V4 sparse MLA 也会分配依赖于最大长度的工作区。
标签:DLL 劫持, Docker, Vectored Exception Handling, vLLM, 人工智能, 大语言模型, 安全防御评估, 模型部署, 深度学习推理, 版权保护, 用户模式Hook绕过, 英伟达硬件, 请求拦截, 逆向工具