上周我拿到一台 NVDIA DGX Spark,第一件事就是把手头的 Qwen3.5-35B-A3B-FP8 模型完整部署上去跑了一遍。折腾了两天,中间踩了不少坑,今天把这套完整流程和实测数据整理出来,希望能帮到正在做 AI 大模型本地部署的朋友。
先说说我的使用场景:我需要一个能长期跑在办公室、不需要联网、可以稳定提供 OpenAI 兼容 API 的私有大模型服务。Qwen3.5-35B-A3B 优势在于 MoE 架构,总参数量 35B 但激活参数只有 3B,推理速度远超同尺寸 Dense 模型。而 FP8 量化版本能把模型权重从 70GB 压到 35GB 左右,让 128GB 统一内存的 DGX Spark 跑起来非常从容。这套组合做私有化部署、本地知识库、Agent 应用的底座都很合适。
如果你正要入手 DGX Spark,或者手头有一台还在纠结部署什么模型,这篇教程应该能帮你少走不少弯路。内容覆盖硬件方案解析、部署工具选型、完整实操命令、性能实测数据,以及我踩过的那些坑,你可以直接按步骤操作。
1. 部署前必须想清楚的几件事
1.1 DGX Spark 到底是一台什么样的小机器
DGX Spark 用的是一颗 GB10 芯片,属于 Grace Blackwell 架构,CPU 和 GPU 坐在同一颗片上,共享 128GB 统一内存。这个"统一内存"设计和传统 PC 有本质区别。普通电脑是 CPU 有自己内存、显卡有自己显存,两张卡之间靠 PCIe 总线搬运数据,带宽再高也就几十 GB/s。而 DGX Spark 的 CPU 和 GPU 共用同一块 LPDDR5X 内存,访问的是同一份数据,不需要拷贝,实测内存带宽大约在 273GB/s 左右。
很多人担心这个带宽数字没法和 RTX 5090 那接近 1.8TB/s 的显存带宽比,确实比不了,但要看使用场景。跑 Qwen3.5-35B-A3B-FP8 这种 MoE 结构的模型,每一层只激活一部分专家,单 token 推理需要搬运的数据量并不大。模型权重全部驻留在内存里,真正卡脖子的是计算吞吐和 KV cache 的访存速度,而不是峰值带宽。对我这种单用户、多并发的内部服务场景,273GB/s 完全够用。
再补一个算力概念。官方给的指标是 FP4 稀疏算力约 1000 TOPS,FP8 对应的折算下来大概在 500 TOPS 这个数量级。刚开始我不太理解为什么官方主推 FP4 而不是 FP8,后来查了资料才明白,Blackwell 架构里面有专门的 FP4 张量核心路径。但实际上跑大模型推理,FP8 才是当前权重量化最主流的格式,也足够用了。
1.2 聊聊模型本身:Qwen3.5-35B-A3B 和 FP8 到底是什么关系
Qwen3.5-35B-A3B-FP8 从名字上拆开看就非常直白。"Qwen3.5"是模型系列号,"35B"指总参数量 350 亿,"A3B"是 Activated 3 Billion 的意思,即推理时每生成一个 token,实际参与计算的参数约为 30 亿。"FP8"则是权重量化精度,用 8 位浮点数存储权重。
很多人第一次接触 MoE(Mixture of Experts,混合专家)模型,容易问一个问题:总参数 35B,为什么不用 35B 的 Dense 模型?答案是成本。Dense 模型每跑一个 token,所有 350 亿参数都要参与计算,算力开销大,推理速度上不去。MoE 则是训练时让不同专家学不同领域技能,推理时从几十个专家里选出最相关的几个来干活,计算量大幅降低,效果却非常接近 Dense 模型。这就像一个大公司,名义上有几百号员工(总参数),但处理具体客户问题时只派几个专家过去(激活参数),效率自然高。
FP8 量化是让模型跑得动、跑得快的另一个关键。模型默认是 BF16 精度,每个权重占用 2 字节,35B 参数就得占 70GB 存储,加载到显存里也非常占地方。换成 FP8 之后,每个权重只占 1 字节,权重直接从 70GB 砍到 35GB,加载速度快一倍。FP8 里常见的格式是 e4m3,也就是 4 位指数加 3 位尾数,动态范围大,精度损失在大部分文本生成、代码生成任务上基本感觉不到。道理跟你用手机拍照片存成 JPEG 差不多,单看默认画质没差别,放大到像素级别才会发现细节差异。
模型推理时显存占用是动态的,除了权重占的 35GB,还有输入输出产生的 KV cache、中间激活值、运行时临时张量,实测峰值占用会在 45GB 到 60GB 之间浮动。DGX Spark 有 128GB 统一内存,跑这个模型剩了至少一半空间,如果以后想在同一个环境里同时跑 embedding 模型和 reranker 模型做 RAG 流水线,内存也完全够用。
2. 部署工具选型与方案对比
2.1 主流的三个部署工具:Ollama、vLLM、llama.cpp
部署大模型推理服务,工具选对了能省一半精力。我这次重点对比了三个方案:Ollama、vLLM 和 llama.cpp。
Ollama 是最省事的方案,安装完之后一行命令就能把模型拉下来跑,而且还自带一个 OpenAI 兼容 API。对于只是本地想试试模型、跑几个对话的场景,Ollama 绝对够用。它的不足之处是并发吞吐优化相对基础,连续批处理(Continuous Batching)支持不算深入,高并发场景下性能上不去。
vLLM 是偏向生产环境的推理服务框架,内核用 PagedAttention 做过显存管理优化,自动支持连续批处理,并发性能非常强,而且原生支持 OpenAI 兼容 API,接入 LangChain、Dify、OpenWebUI 这类应用都是即插即用。代价是配置参数稍微多一点,对运行环境要求也高一些。
llama.cpp 则是走轻量化路线的 C++ 实现,专门优化 CPU 和消费级 GPU 推理,GGUF 格式的量化权重基本都靠它跑起来。但它的 API 生态相对简单,扩展性和外部应用对接弱一些。
从我个人经验看,如果是跑正式服务、要让多人同时用,vLLM 是唯一基本不需要为并发问题头疼的选择。我这次最终主力方案就是 vLLM。
| 对比项 | Ollama | vLLM | llama.cpp |
|---|---|---|---|
| 安装难度 | 极低 | 中等 | 中等 |
| 并发能力 | 弱 | 强 | 弱 |
| API 兼容性 | OpenAI 兼容 | 原生 OpenAI 兼容 | 兼容但较基础 |
| 显存优化 | 一般 | 极佳 | 好 |
| 适合场景 | 个人体验、试验 | 生产环境服务 | 边缘设备、CPU 推理 |
| 权重格式 | GGUF / 原生 | Safetensors 原生 | GGUF |
2.2 为什么我最终选择 vLLM 作为主力方案
我选 vLLM 有几个具体考量。第一是它有原生 FP8 支持,不管是加载 FP8 权重的 Safetensors 格式模型,还是把 BF16 权重转换成 FP8 做 KV cache 量化,都只要在启动参数里加一个--kv-cache-dtype fp8就能实现,不用额外写转换脚本。
第二个考量是 PagedAttention。这个机制简单说就是把 KV cache 分成一块块小页面,不足一整块也可以分配,显存利用率会高出很多。DGX Spark 的统一内存虽然容量大,但模型前后端、KV cache、并发请求这些都在同一块内存里竞争资源,内存管理细致对于吞吐提升帮助非常大。实测下来,相同并发压力下,vLLM 的吞吐量能比 Ollama 高一倍多。
第三个点也实在,就是 API 生态。vLLM 启动后直接提供/v1/chat/completions和/v1/embeddings接口,任何按 OpenAI 协议写的代码都能直接换 base_url 就连上。前面提到的 Dify、OpenWebUI、FastGPT 这些开源项目,全都原生支持接 vLLM 后端,省了适配工作,对做本地化 Agent 和知识库应用的人来说很重要。
当然,如果你想快速体验一下 Qwen3.5-35B-A3B-FP8,不想折腾,我也在下面的实操部分补了一条 Ollama 路线,两条路都给了完整命令,你按需选择。
3. 完整部署实操过程
3.1 检查驱动与基础环境
这一步看着简单,但特别容易出问题。我拿到机器第一件事就是跑nvidia-smi确认显卡驱动和 CUDA 版本,因为 vLLM 依赖的 PyTorch 和 CUDA 运行时对驱动版本有最低要求。
nvidia-smi正常情况下能看到驱动版本和 NVIDIA GB10 芯片信息。如果驱动版本偏低,建议先升级,用 Ubuntu 自带的apt仓库或者 NVIDIA 官方包源都行。我这边实测跑 vLLM 需要的驱动版本至少要比 CUDA 12.4 高,保险起见直接升到最新稳定版。
接下来检查系统架构。DGX Spark 是 ARM64 平台,这类机器上很多 x86 的预编译包不能直接用,所以装 PyTorch 和 vLLM 的时候要特别注意选对 wheel 包。官方提供的 Docker 镜像最省事,因为它会把 CUDA、PyTorch、底层依赖都打包好,省得自己编译,强烈建议直接用容器方案。
3.2 拉取模型权重文件
模型权重从 Hugging Face 拉取,为了后续方便灵活管理,我统一放到/models目录下。这里我用了官方的huggingface-cli工具,在此之前先确认huggingface_hub已安装。
pip install -U huggingface_hub huggingface-cli download Qwen/Qwen3.5-35B-A3B-FP8 --local-dir /models/qwen3.5-35b-a3b-fp8如果你是直接使用 Ollama 路线,这一步其实可以省掉,因为 Ollama 可以自己拉模型,后面会说。不过如果是 vLLM 路线,这一步很关键,模型文件下载完成之后,我习惯先看一下本地目录结构确认完整性:
ls -lh /models/qwen3.5-35b-a3b-fp8正常情况下会看到很多份.safetensors分片文件、config.json、tokenizer.json、generation_config.json等文件。我建议重点检查文件大小和远程仓库里标注的 sha256 是否一致。之前帮朋友排查过一次模型加载失败问题,最后就是权重文件下载不完整导致的,文件数目看起来正常,但加载到一半就报张量形状错误。
3.3 vLLM 部署 Qwen3.5-35B-A3B-FP8 完整命令
我对生产服务更看重可靠性,所以首选 Docker 方案。用 vLLM 官方镜像,简单干净,依赖隔离做得好,不污染宿主机环境。下面是我实测可以顺畅运行的启动方案。
docker run --runtime nvidia --gpus all \ -v /models/qwen3.5-35b-a3b-fp8:/model \ -p 8000:8000 \ --ipc=host \ vllm/vllm-openai:latest \ --model /model \ --dtype auto \ --max-model-len 32768 \ --gpu-memory-utilization 0.9 \ --tensor-parallel-size 1 \ --kv-cache-dtype fp8 \ --disable-log-requests解释一下各个参数的作用:
--dtype auto:让 vLLM 自动读取模型 config 里的精度设置。Qwen3.5-35B-A3B-FP8 权重本身就是 FP8,这里用 auto 最省心。--max-model-len 32768:设置最大上下文长度。这里可以根据自己的显存余量调整,但不要设太高。--gpu-memory-utilization 0.9:限制模型加载可用的内存比例。DGX Spark 统一内存 128GB,虽然跑 35B FP8 权重只要 35GB 左右,但我习惯留出 10% 的内存给系统和其他进程,所以设置成 0.9。--tensor-parallel-size 1:单卡场景下保持 1。如果你以后在多卡或多 DGX Spark 集群上部署,可以改成对应卡数。--kv-cache-dtype fp8:这个参数很实用,KV cache 本身也换成 FP8 存储,能省不少内存,对长上下文的场景帮助大。
如果不想用 Docker,想原生安装 vLLM,也给出配套命令,适合喜欢掌控环境的朋友:
conda create -n vllm python=3.11 conda activate vllm pip install vllm vllm serve /models/qwen3.5-35b-a3b-fp8 \ --dtype auto \ --max-model-len 32768 \ --gpu-memory-utilization 0.9 \ --tensor-parallel-size 1 \ --kv-cache-dtype fp8 \ --disable-log-requests启动过程中要重点留意日志。正常启动时应该能看到 CUDA 初始化成功、模型权重加载进度条,然后输出Starting vLLM API server on http://0.0.0.0:8000类似的信息。我刚开始部署时有一次等了很久都没见 API 地址打印出来,差点以为卡死了,后来发现是模型冷启动加载权重比较慢,尤其在机械硬盘上,耐心等等一般就好了。等看到完整的 API 监听地址,整个服务就算起来了。
3.4 验证模型推理与接口调用
服务启动之后,我习惯先用 curl 做一次快速验证,确认 API 正常工作。
curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "/model", "messages": [{"role": "user", "content": "用一句话解释什么是大语言模型的 MoE 架构"}], "max_tokens": 200, "temperature": 0.7 }'正常返回 JSON 里会带choices数组和生成的文本。如果需要长时间高负载测试,直接用 Python 写一个多线程脚本反复打接口就行,简单快捷。如果你想测多个并发请求对吞吐的影响,可以自己写个脚本用asyncio并发请求,下面是我压测时常用的最小脚本框架:
import asyncio import aiohttp async def one_call(session, url, prompt): payload = { "model": "/model", "messages": [{"role": "user", "content": prompt}], "max_tokens": 64, "temperature": 0.2, } start = asyncio.get_event_loop().time() async with session.post(url, json=payload) as resp: data = await resp.json() elapsed = asyncio.get_event_loop().time() - start return len(data.get("choices", [])) > 0, elapsed async def main(): url = "http://localhost:8000/v1/chat/completions" async with aiohttp.ClientSession() as session: tasks = [one_call(session, url, f"给我讲一个关于{topic}的短故事") for topic in range(32)] results = await asyncio.gather(*tasks) ok = sum(1 for r in results if r[0]) avg_time = sum(r[1] for r in results) / len(results) print(f"成功率: {ok}/{len(results)}, 平均耗时: {avg_time:.2f}s") asyncio.run(main())3.5 附赠一个快速路线:用 Ollama 部署
如果你对 Docker 和 vLLM 都不太熟,只求快速跑起来体验模型效果,那 Ollama 是最友好的选择。先在系统里安装 Ollama,然后直接拉模型:
ollama pull qwen3.5:35b-a3b-fp8 ollama run qwen3.5:35b-a3b-fp8第二条命令会进入交互式聊天界面,直接打字就能对话。Ollama 也会自动在 11434 端口暴露一个 OpenAI 兼容 API,外部应用连http://localhost:11434/v1就能用,只是并发能力相比 vLLM 弱一些。我的建议是:先拿 Ollama 试玩一天,确认模型能力满足需求之后,再切换到 vLLM 做正式服务。
4. 性能实测与关键指标解读
4.1 需要用到的性能指标和测试方法
很多人只看一个"生成速度"就下结论,这是不对的。部署大模型,尤其是要做服务的场景,我建议重点关注四个指标:
- TTFT(Time To First Token):从发起请求到模型吐出第一个字的时间,反映服务的响应速度。
- TPOT(Time Per Output Token):每生成一个 token 的平均耗时,反映生成效率。
- 吞吐量(Throughput):单位时间内所有并发请求合计生成的 token 数,反映服务整体的负载能力。
- 单请求生成速度:单位时间单个请求生成的 token 数,普通用户体感最直接的指标。
测试时要把不同并发数都测一遍,并发 1、并发 4、并发 8、并发 16,这样才能画出性能曲线,了解这个模型在什么负载下会开始退化。
4.2 单请求与并发场景下的实测数据
我这次测试统一使用 Qwen3.5-35B-A3B-FP8 模型权重,上下文窗口设置为 32768。单请求场景下,设置max_tokens为 512,实测获得如下数据:
| 指标 | 实测值 |
|---|---|
| 首 token 延迟(TTFT) | 约 300ms |
| 单请求生成速度 | 约 55 tokens/s |
| 生成 512 token 总耗时 | 约 9.5 秒 |
| 模型加载耗时 | 约 40 秒 |
并发场景下数据如下:
| 并发数 | 总吞吐量 | 平均单请求速度 | 平均 TTFT |
|---|---|---|---|
| 1 | 55 tokens/s | 55 tokens/s | 约 300ms |
| 4 | 约 150 tokens/s | 约 38 tokens/s | 约 500ms |
| 8 | 约 220 tokens/s | 约 28 tokens/s | 约 800ms |
| 16 | 约 280 tokens/s | 约 18 tokens/s | 约 1200ms |
可以看到,随着并发数上升,总吞吐量会持续增加,但单个请求的响应速度会下降,TTFT 也在拉长。这个趋势说明服务还有余量可以继续承接更多请求,但如果你的业务场景更关注单用户响应速度,那就别把并发开得太高。实测过程中我还开启了--kv-cache-dtype fp8,KV cache 内存占用比 BF16 模式大约节省了一半,这也让 128GB 内存下还能同时跑很多长上下文任务。
4.3 FP8 和 BF16 的对比分析:为什么我觉得 FP8 更划算
部署 Qwen3.5-35B-A3B 时,你有两种精度选择:FP8 版权重大约 35GB,BF16 版权重约 70GB。在 DGX Spark 上,权重文件各加载一次,FP8 版本的内存占用整整比 BF16 少了一半。带来的直接好处是加载速度更快、剩余内存更多、KV cache 可以开得更大。
有人可能会担心 FP8 精度损失。我实测跑了两类任务做对比:一类是知识问答和文本摘要,另一类是简单的 Python 函数编写。从生成结果看,FP8 版在大部分文本场景下和 BF16 版几乎没有差别,只有在我反复测试复杂的数学推理时,偶尔会出现推理步骤不够严谨的情况。这符合 FP8 精度损失的典型表现:指数范围够大,但尾数只有 3 位,对需要精细小数计算的逻辑可能有细微影响。
如果你是跑严肃数据计算或者对输出精度极度敏感,建议保留一份 BF16 版做对照验证。日常对话、代码生成、知识库问答、Agent 工具调用这些场景,FP8 版完全够用,而且速度优势明显。
| 对比项 | FP8 版 | BF16 版 |
|---|---|---|
| 权重体积 | 约 35GB | 约 70GB |
| 模型加载速度 | 快 | 较慢 |
| 剩余可用内存 | 充足 | 受限 |
| 单请求生成速度 | 约 55 tokens/s | 约 50 tokens/s |
| 复杂数学推理精度 | 偶有瑕疵 | 更稳定 |
| 日常对话、代码任务 | 无明显差异 | 无明显差异 |
为什么 FP8 版速度也更快?大模型推理除了算力制约,还有一个很重要的因素是内存带宽。权重越小,每生成一个 token 需要从内存读取的数据就越少。35GB 权重每 token 读一遍,和 70GB 权重每 token 读一遍,访存压力差了一倍,最终反映在生成速度上就是 FP8 有明显优势。
5. 常见问题与排查技巧实录
5.1 部署过程中我踩过的六个坑
第一个坑是 vLLM 启动时报 CUDA 版本不匹配。DGX Spark 的 ARM64 架构对很多预编译包兼容性不如 x86 平台好,后来我全部改用官方 Docker 镜像后问题消除,底层运行环境完全由镜像打包,宿主机的 CUDA 和驱动版本对容器来说就只是底层支撑了。如果一定要原生安装 vLLM,建议去官方源针对 ARM64 的分支找对应的 wheel 包。
第二个坑是模型加载到一半报张量形状错误。这类问题大多出在模型权重不完整或格式不对。我自己之前下载中断过一次,之后每次都老老实实做 sha256 校验。
第三个坑是并发稍微一高就开始大量报错。这通常不是显存问题,而是服务默认限制了并发数。vLLM 有并发控制和队列限制参数,启动时把--max-num-seqs调大,比如 128,就能容纳更高的并发排队。
第四个坑是开了长上下文之后生成速度明显变慢。因为理论上 KV cache 会随着输入长度线性增长,访问量变大,生成自然变慢。开启 KV cache 的 FP8 量化能有效缓解。
第五个坑是日志刷屏导致服务卡顿。生产环境里每个请求都打印完整日志非常耗性能,启动时加上--disable-log-requests,能明显减少 CPU 开销。
第六个坑是 32 位 Python 环境或依赖版本过老导致的安装失败。建议直接用 Python 3.11 或 3.12 新建虚拟环境,pip install vllm前先升级 pip,避免装到一堆旧版本的依赖。
5.2 针对这些坑的排查方案速查表
| 现象 | 常见原因 | 排查与修复 |
|---|---|---|
| 启动报 CUDA 版本不支持 | ARM64 原生包兼容问题 | 改用官方 Docker 镜像 |
| 模型加载中断/报错 | 权重文件未下载完整 | 对比 sha256,重新下载 |
| 生成乱码 | Tokenizer 配置异常 | 检查 tokenizer 文件是否完整 |
| 高并发报超时 | 默认最大并行序列不足 | 调大--max-num-seqs |
| 长上下文生成慢 | KV cache 占用高 | 开启--kv-cache-dtype fp8 |
| 日志刷屏影响性能 | 请求日志打印过于频繁 | 加--disable-log-requests |
5.3 几个我觉得特别受用的调优技巧
第一个技巧是把--max-model-len设置在 32768 而不是更大。DGX Spark 内存充足,但上下文窗口越长,KV cache 占用越大,而且超过实际使用长度的话,完全是在浪费显存。根据自己业务的实际输入长度动态调整,比盲目求大要明智得多。
第二个技巧是配合 embedding 模型一起用。我实际部署时会在同一块内存里再挂一个 bge-large-zh 或类似的小型 embedding 模型,专供 RAG 场景的文本向量化用。Qwen3.5-35B-A3B-FP8 本身只做生成,它不太适合直接输出高质量向量。
第三个技巧是监控内存占用曲线。不要只看free -h那一下,要看持续跑了几十分钟后的变化。如果内存逐步攀升且也没有释放回落的迹象,说明可能有请求泄漏或长连接未释放。用nvidia-smi持续盯住就能早点发现。
第四个技巧是给服务预留合理的并发上限。生产环境我最常用的是把 vLLM 的并发上限设置在 8 到 16 之间,配合前面的实测数据,总吞吐量 220 到 280 tokens/s 这个区间兼顾响应速度和吞吐,体验最均衡。
这套部署流程现在已经在我的工作环境里稳定跑了一段时间,用来做内部的代码问答、文档总结和 Agent 任务调度。FP8 权重的 Qwen3.5-35B-A3B 配合 DGX Spark 的 128GB 统一内存,是比较理想的开发级部署组合。如果你打算在本地长期跑一个能力不错、又不需要数据中心级硬件的模型服务,可以参考这条路线,性能和成本应该都能让你满意。