快来看,n8n更新了!在NVIDIA DGX Spark上运行你的n8n AI工作流,无需云API

内容来源:https://blog.n8n.io/n8n-nvidia-dgx-spark/
内容总结:
许多中小企业对AI的需求其实相当集中:起草邮件、汇总通话、读取发票、检索内部文档,偶尔生成图片。但其中不少企业无法把客户数据发送到云端API来完成这些工作,原因通常是数据隐私与主权要求。诊所、律所或会计师事务所持有的记录,可能因合同、监管规定或职业义务而必须留在自己的掌控之下。数据一旦进入云端API,就存放在别人的硬件上,可能位于另一个司法辖区,且适用条款由服务商单方面变更。在自己拥有的硬件上运行模型,数据始终留在自己的场所内,也能向审计人员清楚说明数据的去向。
通过n8n工作流,用户可以在不向云端API发送任何字节的情况下完成通话转写、发票读取、基于自有文档的问答以及回复起草。本文介绍如何用一台NVIDIA DGX Spark为这些工作流提供所需的全部模型,并统一在一个OpenAI兼容端点之后,n8n的OpenAI节点可直接对接。
DGX Spark拥有128GB统一内存,由CPU和GPU共享。只要选对模型,就足以同时承载上述所有任务。本文的重点正在于此。此前的自托管AI入门套件只能让一个人在笔记本电脑上运行本地模型,而本文介绍的是下一步:在办公室放一台机器,服务整个团队,每项任务配一个模型,并附上运行代码。
本文涵盖以下内容:选择在DGX Spark内存带宽上运行较快的模型;用vLLM以NVFP4精度 serving 一个35B混合专家模型;把完整的AI栈装入一台机器的共享内存;通过一个LiteLLM端点路由所有任务,并按团队分配密钥;将n8n接入该栈并构建一个完全不调用云端API的工作流;测量有人生成图片时聊天速度受到的影响。
按带宽选模型,而非按参数规模
DGX Spark的GB10 Grace Blackwell超级芯片内存读取速度为273 GB/s。模型生成文本时,每生成一个token都要从内存读取其激活权重,因此读取的字节越少,运行越快。混合专家(MoE)模型每个token只激活少量专家。Qwen3.6-35B-A3B总参数为35B,但激活参数约3B。以NVFP4(NVIDIA的4位浮点格式)存储时,这些激活权重只占稠密模型的一小部分。在笔者的DGX Spark上,Qwen3.6-35B-A3B单用户生成速度为每秒120个token,八用户合计每秒359个token,其中包含模型作答前写出的推理token。
由此得出第一条设计原则:用一个强大的MoE模型处理所有文本类任务。摘要、分类、抽取、起草、编码、读取截图都交给同一个模型。每多一个模型,就多一个需要固定、打补丁和监控的容器,因此只有在LLM无法胜任的场景才给专用模型留位置。
模型栈
以下模型常驻于一台DGX Spark:聊天、编码、视觉、抽取使用nvidia/Qwen3.6-35B-A3B-NVFP4(Apache 2.0);嵌入使用Qwen/Qwen3-Embedding-4B(Apache 2.0);重排序使用Qwen/Qwen3-Reranker-0.6B(Apache 2.0);语音转文字使用openai/whisper-large-v3-turbo(MIT);文档OCR使用zai-org/GLM-OCR(MIT);图像生成使用Tongyi-MAI/Z-Image-Turbo(Apache 2.0);文字转语音使用hexgrad/Kokoro-82M(Apache 2.0)。栈中所有模型均允许商业使用。这条规则付出的代价比任何技术规则都大,后文有专门章节说明。
第一步:缓存全部模型以供离线使用
该栈必须能在拔掉网线的情况下运行,因此要一次性下载所有模型并记录确切的commit。以下循环会缓存每个模型,并写入包含仓库与commit的锁文件。启动模型时,通过--revision传入models.lock中的commit,vLLM便会提供经过测试的权重,而非main当前指向的版本,从而避免上游变更悄悄替换权重或许可证。
检查vLLM版本,而非标签
容器也要同样固定,并检查其内部版本。笔者第一次尝试使用vLLM的cu130-nightly标签,该标签已停止更新,拉取的镜像已是五个月前版本,内含vLLM 0.19,完全无法加载Qwen3.6。报错信息并未提及版本,而是缺少某个权重缩放参数导致的KeyError,看起来像是检查点损坏。在信任某个镜像前,先打印其中的vLLM版本。可用的是官方vllm/vllm-omni:v0.30.0镜像,内含vLLM 0.30.0并带arm64构建。vLLM-Omni是完整vLLM加上图像和语音的额外服务模式,因此也能服务普通LLM。其默认入口不是vllm serve,需包装一次。Kokoro-FastAPI镜像需要社区修复才能在arm64上构建,其Dockerfile来自NVIDIA开发者论坛上swann.schilling的帖子。
第二步:启动主干模型
该命令在vLLM中启动主模型。在笔者的DGX Spark上,docker run之后311秒模型即准备好提供服务。端口发布在172.17.0.1(Docker桥接地址)上,因此只有本机容器可访问。VLLM_API_KEY使vLLM拒绝任何不带密钥的请求。
在DGX Spark上最关键的几个参数:--gpu-memory-utilization 0.32将该模型内存上限设为约三分之一,其余模型需要剩下的部分;--kv-cache-dtype fp8将KV缓存所需内存减半,同样预算可容纳更多上下文;--moe-backend marlin为量化专家层选择内核,草稿模型的专家未量化,因此--speculative-config为其单独设置moe_backend;--speculative-config开启多token预测(MTP),模型一次向前起草三个token并一并校验;--reasoning-parser和--tool-call-parser必须与模型家族匹配,工具调用失败时先检查这些参数再归咎于模型。
启动日志中应检查vLLM选择的内核。最后那条警告对理解DGX Spark上的“NVFP4”很重要:在此vLLM构建下,权重以4位存储,但计算以16位进行。由于生成token受限于从内存读取权重,而4位权重只有四分之一大小,因此仍能获得大部分收益。针对GB10的更快的FP4内核可能在后继版本中提升这些数字。
第三步:将其余模型装入共享内存
在DGX Spark上,GPU与操作系统共享一个128GB内存池。笔者的机器上Linux报告总内存为121GB。这改变了两件在独立GPU上理所当然的事。第一,--gpu-memory-utilization是启动时占整个内存池的比例,而非占空闲内存的比例。没有上限的实例会试图占用几乎全部内存,导致下一个模型无法启动。第二,vLLM根据启动时可用内存来确定缓存大小。同时启动两个模型会争抢同一块内存。应逐个启动,等每个模型通过健康检查后再启动下一个。如果模型报CUDA error: out of memory而free显示大量可用,通常是Linux页缓存在占用,下载220GB权重后尤为常见。清理缓存后重新启动即可。
以下是各服务冷启动(先清空页缓存)所需时间及占用内存:qwen3.6为237秒、43GB;rerank为106秒、8GB;whisper为136秒、8GB;ocr为137秒、11GB;image为96秒、20GB;tts为21秒、3GB。嵌入模型在vLLM 0.30.0上还需额外参数--convert embed,并在其余模型之后启动。七个模型全部加载后,DGX Spark占用108GB,剩余13GB,这就是一台DGX Spark的容量上限。应提前启动该栈:表中六个服务约需12分钟。vLLM启动时还可能因内存分析报错而失败,原因是在其测量可用内存时有其他进程释放内存;在共享内存环境下,若另一模型正在加载或关闭就会出现。待机器空闲后重新启动即可。
第四步:前置统一端点
LiteLLM为每个客户端提供单一的OpenAI兼容端点。每项任务都是一个命名路由,因此可以在不改动任何客户端的情况下替换路由背后的模型。LiteLLM开源版还处理虚拟密钥、团队和预算。LiteLLM配置中的每个路由指向一个后端。务必保留drop_params: true。否则LiteLLM会对任何设置response_format的图像请求返回400错误,因为它不会为自托管图像模型映射该字段。开启后LiteLLM会丢弃该字段,vLLM-Omni仍会以base64返回图像。
LiteLLM也运行在Docker中。为使其能访问后端,需在其Compose服务中将host.docker.internal映射到宿主机。后端仅监听Docker桥接网络并要求共享密钥,因此LiteLLM是办公网络上唯一暴露的服务。应将LiteLLM指向桥接地址而非机器的LAN地址;若使用LAN地址,后端会拒绝连接,因为它们并未监听该地址。
LiteLLM应按镜像摘要固定,并使用1.84.0或更高版本。2026年3月有两个PyPI版本被植入后门,1.84.0修复了两个认证绕过漏洞。笔者运行的是1.99.1。至此任何OpenAI客户端都可对接DGX Spark。示例Python代码通过同一客户端调用三种不同模型。num_inference_steps不是OpenAI参数,因此放在extra_body中,LiteLLM会将其透传给vLLM-Omni,下一节说明其重要性。
第五步:接入n8n
n8n的OpenAI凭证接受自定义Base URL,因此n8n可以像对接OpenAI一样对接DGX Spark。步骤如下:首先用LiteLLM主密钥为该工作流所属团队创建一个虚拟密钥,限制其只能访问所需路由;然后在n8n中创建OpenAI凭证,在API Key中填入虚拟密钥,将Base URL设为http://
若要在AI Agent或Basic LLM Chain中使用DGX Spark,添加一个使用该凭证的OpenAI Chat Model节点,将其模型按ID设为LiteLLM路由名(如qwen3.6),然后在节点选项中关闭Use Responses API。从1.3版本起,该节点默认调用OpenAI较新的Responses API,而DGX Spark的模型通过chat completions提供服务。
工作流:读取发票
该工作流提供一个网页表单。上传发票图片后,表单显示供应商、发票号、日期和总额,全程无任何云端调用。它包含六个节点:上传发票(n8n表单触发器)提供含一个文件字段的表单;图像转base64(从文件提取)将上传内容转为base64字符串;DGX Spark上的OCR(HTTP请求)将图像发送到ocr路由并以文本形式取回页面;用Qwen3.6抽取字段(HTTP请求)要求qwen3.6以JSON返回供应商、发票号、日期、总额和币种;发票字段(编辑字段)将回复整理为包含这五个字段的单个条目;显示字段(表单结束)在表单结果页展示这些字段。若要自动归档发票,可将表单替换为Email Trigger(IMAP)节点,将结果页替换为Postgres或Google Sheets节点。
两个HTTP请求节点都使用上一节的OpenAI凭证,因此发送相同的密钥。它们使用HTTP请求而非OpenAI节点,是因为每次调用都需要一个OpenAI节点不设置的字段:OCR调用中的图像,以及抽取调用中的思考开关。抽取请求体中通过chat_template_kwargs关闭该次调用的模型思考,速度提升正来自这里。在一张干净的单页样本发票上测试:OCR一页耗时1.8秒;抽取在开启思考时耗时12.1秒、1,616个token,关闭思考时耗时0.59秒、76个token。开启思考时,模型在给出78个token的答案前写下了1,538个推理token;关闭思考时,不到一秒就正确抽取了全部五个字段。对于固定格式抽取,应关闭思考。OCR正确读取了每一行项目和总额,但漏掉了增值税号中的一位零。归档前应校验各类ID。表单接受PNG和JPEG文件,PDF发票需先转为图像,因为GLM-OCR只接受图像。扫描或拍摄的发票比这张干净页面更难,因此在依赖OCR前应用自己的文档测试。
同一栈支持的其他工作流
通话摘要:Whisper转写录音,qwen3.6生成摘要,Kokoro将摘要读回,用于语音留言或内部通报。基于自有文档的问答:embed填充向量库,rerank对匹配结果排序,由qwen3.6上的AI Agent据此作答。
一台DGX Spark的实测数据
所有数字来自2026年10月13日的一次基准测试,使用vLLM 0.30.0并固定模型commit。不同镜像、模型或提示词会得到不同结果。每个聊天提示词都以随机标签开头,以免前缀缓存美化结果。
聊天速度方面:单用户时首token中位延迟0.11秒,每用户每秒120个token,合计每秒87个token;四用户时分别为0.21秒、68、242;八用户时分别为0.29秒、47、359。每用户速度是各请求首token之后生成速度的中位数;合计为全部生成token除以测试耗时,因此也计入首token时间和较慢的请求。单用户测试中,五个请求里有一个以每秒43个token运行,其余四个为117至129,拉低了合计值。使用8,537个token的提示词时,首token耗时1.14秒。随着用户增加,每用户速度下降,但总吞吐持续上升,这正是批处理的作用:vLLM在每次遍历权重时为多个用户服务。
其他任务方面(三次中位数):文字转语音一段30秒音频耗时0.87秒;语音转文字转写该片段耗时0.58秒;图像生成一张1024x1024、8步耗时12.2秒;OCR一页耗时1.80秒;抽取(OCR文本转发票JSON,关闭思考)耗时0.59秒;嵌入32段文本耗时0.18秒;重排序32个候选耗时0.03秒。语音双向都远快于实时:30秒音频往返各不到一秒。Whisper转写准确,但末尾多加了一句音频中不存在的“Thank you.”。Whisper已知会在静音处凭空生成文本,转写前应剪掉尾部静音。图像生成的步数需自行设置。Z-Image-Turbo设计为约8步生成一张图,但vLLM-Omni默认约48步、每步1.5秒,每张图耗时72秒;设为8步后,同样的1024x1024图像耗时12.2秒。
图像生成时的聊天表现
机器上所有模型共享一块GPU。装得进内存不等于能良好并行运行。笔者未找到DGX Spark上这方面的公开基准,因此自行测量:在按服务器默认步数连续生成1024px图像的同时重跑聊天测试。单用户时每用户每秒token从122降至38,下降69%;四用户时从69降至24,下降65%。图像生成期间聊天速度约为正常的三分之一,图像本身也变慢,从73秒增至85至122秒。一台DGX Spark能容纳整个栈,但无法在不让所有人聊天变慢的情况下运行图像生成。对小团队而言,可将图像任务排队或安排在工作时间之外。一旦图像生成成为日常工作,应将媒体任务迁到第二台DGX Spark,第一台专用于文本。
许可证问题
模型质量并非难点,许可证才是。多款最好的开源文字转语音和图像模型不允许商业使用。与Z-Image-Turbo竞争的FLUX模型为非商业许可或需单独付费授权。写作时最强的开源图像模型Qwen-Image-2.1以研究许可发布。有些仓库甚至在宽松原版旁附带一个非商业双胞胎,缓存错误的仓库ID就会改变许可证。本栈遵循的规则是:只允许Apache 2.0、MIT、CC-BY-4.0、OpenMDW和NVIDIA Open Model License;将每个模型固定到Hugging Face commit,使上游许可证变更无法悄悄落地;在文档中注明CC-BY-4.0模型。这不是法律建议。在销售或为客户部署前,应请人审核每一个固定的许可证。
注意事项
新检查点可能需要比所固定版本更新的vLLM,而报错不会说明这一点,应先检查镜像内的版本。faster-whisper没有arm64的CUDA构建,基于它的工具会在不提示的情况下跑在CPU上。本栈使用vLLM而非入门套件中的Ollama。Ollama适合一个人在笔记本电脑上使用;对团队而言,vLLM会将多用户请求批处理进每次权重遍历,多用户吞吐正来源于此。
动手尝试
导入发票工作流模板。若尚无DGX Spark,可先从自托管AI入门套件小规模起步。若在自己的DGX Spark上运行,欢迎在n8n社区论坛分享实测数据。
中文翻译:
大多数小企业想要的 AI 任务其实就那么几样:起草邮件、总结通话、读取发票、检索自己的文档,偶尔生成张把图片。但其中不少企业没法把客户数据发到云端 API 去做这些事。
原因往往是数据隐私和数据主权。一家诊所、律所或会计师事务所手里的记录,可能因合同、监管规定或职业义务而必须留在自己的控制范围内。数据一旦发到云端 API,就落在别人的硬件上,可能还在另一个司法管辖区,适用的条款供应商随时可以改。把模型跑在自己的硬件上,数据就留在自己的楼里,还能向审计人员明确指出数据去了哪里。
你的 n8n 工作流可以转写通话、读取发票、基于自己的文档回答问题、起草回复,而且一个字节都不用发给云端 API。本文介绍如何用一台 NVIDIA DGX Spark 为这些工作流所需的全部模型提供服务,统一挂在一个 OpenAI 兼容端点后面——n8n 的 OpenAI 节点本来就会说这套协议。
一台 DGX Spark 有 128GB 统一内存,由 CPU 和 GPU 共享。只要选对模型,足够把上述所有任务同时常驻在内存里。本文大部分内容讲的就是这件事。
自托管 AI 入门套件能让一个人在笔记本电脑上跑起本地模型。下一步是:在办公室里放一台机器,服务整个团队,每个任务配一个模型,再加上运行它们的代码。
本文你将学到:
- 挑选在 DGX Spark 内存带宽下跑得快的模型
- 用 vLLM 以 NVFP4 精度服务一个 35B 的混合专家模型
- 把整套 AI 栈塞进一台机器的共享内存
- 通过一个 LiteLLM 端点路由所有任务,并按团队分配密钥
- 把 n8n 接到这套栈上,搭建一个从不调用云端 API 的工作流
- 测量有人生成图片时聊天速度会受到什么影响
按带宽选模型,而不是按参数量
DGX Spark 的 GB10 Grace Blackwell 超级芯片内存读取速度为 273 GB/s。模型生成文本时,每生成一个 token 都要从内存读取其激活权重,所以读的字节越少,跑得越快。
混合专家(MoE)模型每个 token 只激活少数几个专家。Qwen3.6-35B-A3B 总共有 35B 参数,但激活的只有约 3B。以 NVFP4(NVIDIA 的 4 位浮点格式)存储时,这些激活权重只占稠密模型的一小部分。
在我的 DGX Spark 上,Qwen3.6-35B-A3B 单用户生成速度为每秒 120 token,八个用户合计每秒 359 token。这些数字包含模型作答前写出的推理 token。
由此得出第一条设计原则:用一个大而强的 MoE 模型处理所有文本类任务。摘要、分类、抽取、起草、写代码、读截图,全部交给同一个模型。每多一个模型,就多一个要固定、打补丁、监控的容器,所以专用模型只有在 LLM 干不了这个活时才占一个位置。
这套栈
以下模型常驻在一台 DGX Spark 上:
| 任务 | 模型 | 许可证 |
|---|---|---|
| 聊天、编码、视觉、抽取 | nvidia/Qwen3.6-35B-A3B-NVFP4 | Apache 2.0 |
| 嵌入 | Qwen/Qwen3-Embedding-4B | Apache 2.0 |
| 重排序 | Qwen/Qwen3-Reranker-0.6B | Apache 2.0 |
| 语音转文字 | openai/whisper-large-v3-turbo | MIT |
| 文档 OCR | zai-org/GLM-OCR | MIT |
| 图像生成 | Tongyi-MAI/Z-Image-Turbo | Apache 2.0 |
| 文字转语音 | hexgrad/Kokoro-82M | Apache 2.0 |
栈里每个模型都允许商用。这条规则的代价比任何技术规则都大,后面有专门一节讲。
第 1 步:缓存一切,供离线使用
这套栈必须能在拔掉网线的情况下运行,所以每个模型只下载一次,并记录你拿到的确切 commit。下面这个循环会缓存每个模型,并写出一个包含仓库和 commit 的锁定文件:
pipx install "huggingface_hub[cli]"
hf auth login
for repo in nvidia/Qwen3.6-35B-A3B-NVFP4 Qwen/Qwen3-Embedding-4B Qwen/Qwen3-Reranker-0.6B \
openai/whisper-large-v3-turbo zai-org/GLM-OCR Tongyi-MAI/Z-Image-Turbo; do
hf download "$repo"
echo "$repo $(cat ~/.cache/huggingface/hub/models--${repo//\//--}/refs/main)" >> models.lock
done
启动模型时,把 models.lock 里的 commit 通过 --revision 传进去。这样 vLLM 服务的就是你测试过的权重,而不是 main 今天指向的东西,上游的改动就无法在你不知情的情况下换掉权重或许可证。
检查 vLLM 版本,别只看标签
容器也要同样固定,并且检查里面装的是什么。我第一次尝试用的是 vLLM 的 cu130-nightly 标签。那个标签早已停止更新,拉下来的镜像还是五个月前的,里面是 vLLM 0.19,根本加载不了 Qwen3.6。报错信息里完全没提版本,而是一个 KeyError,说缺少某个 weight-scale 参数,看起来像是 checkpoint 坏了。
信任一个镜像之前,先打印里面的 vLLM 版本:
docker run --rm --entrypoint python3 <image> -c "import vllm; print(vllm.__version__)"
能用的镜像是官方的 vllm/vllm-omni:v0.30.0,自带 vLLM 0.30.0,并有 arm64 构建。vLLM-Omni 是完整的 vLLM,额外支持图像和语音的服务模式,所以也能服务普通 LLM。它的默认 entrypoint 不是 vllm serve,所以要包一层:
printf 'FROM vllm/vllm-omni:v0.30.0\nENTRYPOINT ["vllm", "serve"]\n' | docker build -t vllm-spark:omni-0.30.0 -
Kokoro-FastAPI 镜像需要一个社区修复才能在 arm64 上构建。Dockerfile 来自 swann.schilling 在 NVIDIA 开发者论坛上的一篇帖子。
第 2 步:服务主干模型
这条命令在 vLLM 中启动主模型:
docker run -d --name qwen3.6 --gpus all --ipc=host \
-p 172.17.0.1:8001:8000 \
-e HF_HUB_OFFLINE=1 -e VLLM_API_KEY=<your-backend-key> \
-v ~/.cache/huggingface:/root/.cache/huggingface \
vllm-spark:omni-0.30.0 \
nvidia/Qwen3.6-35B-A3B-NVFP4 --revision <commit-from-models.lock> \
--served-model-name qwen3.6-35b \
--gpu-memory-utilization 0.32 --max-model-len 65536 \
--max-num-seqs 8 --max-num-batched-tokens 8192 \
--trust-remote-code --kv-cache-dtype fp8 --moe-backend marlin \
--enable-chunked-prefill --async-scheduling --enable-prefix-caching \
--speculative-config '{"method":"mtp","num_speculative_tokens":3,"moe_backend":"triton"}' \
--reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser qwen3_xml \
--host 0.0.0.0 --port 8000
在我的 DGX Spark 上,docker run 之后 311 秒,这个模型就绪可服务。
端口发布在 172.17.0.1,即 Docker 的桥接地址上,所以只有本机上的容器能访问。VLLM_API_KEY 让 vLLM 拒绝任何不带密钥的请求。
在 DGX Spark 上最要紧的几个参数:
--gpu-memory-utilization 0.32把这个模型的内存占用限制在约三分之一,剩下的留给其他模型。--kv-cache-dtype fp8把 KV 缓存所需内存减半,同样的预算能装下更多上下文。--moe-backend marlin为量化后的专家层选择 kernel。草稿模型的专家没有量化,所以--speculative-config给它单独设了moe_backend。--speculative-config开启多 token 预测(MTP)。模型一次向前草拟三个 token,再一遍校验。--reasoning-parser和--tool-call-parser必须与模型系列匹配。工具调用失败时,先检查这两个参数,再怪模型。
查看启动日志,看 vLLM 选了哪些 kernel。下面几行来自我的运行:
Using MarlinNvFp4LinearKernel for NVFP4 GEMM
Using 'MARLIN' NvFp4 MoE backend out of potential backends: ['FLASHINFER_TRTLLM', 'FLASHINFER_CUTEDSL', ...]
Your GPU does not have native support for FP4 computation but FP4 quantization is being used.
Weight-only FP4 compression will be used leveraging the Marlin kernel.
最后那条警告关系到怎么理解 DGX Spark 上的“NVFP4”。在这个 vLLM 构建里,权重以 4 位存储,但计算是 16 位。你仍能拿到大部分好处,因为生成 token 的瓶颈在于从内存读权重,而 4 位权重只有四分之一大小。后续构建中针对 GB10 的更快 FP4 kernel 可能会把这些数字再提上去。
第 3 步:把其余模型塞进共享内存
在 DGX Spark 上,GPU 和操作系统共用一个 128GB 内存池。我这台机器上 Linux 报告可用总量为 121GB。这改变了两件你在独立显卡上可能习以为常的事。
第一,--gpu-memory-utilization 是整池内存的比例,在启动时取值,而不是空闲内存的比例。不设上限的实例会试图占掉几乎全部内存,下一个模型就起不来。
第二,vLLM 根据启动时的空闲内存来确定缓存大小。同时启动两个模型,它们会抢同一块内存。要一个一个启动,等前一个通过健康检查再启动下一个:
until curl -sf http://172.17.0.1:8001/health; do sleep 5; done
如果模型报 CUDA error: out of memory,而 free 显示还有很多可用内存,那是 Linux 页缓存在占着。刚下载完 220GB 权重后这很常见。清掉缓存,再启动模型:
sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'
下面是先清掉页缓存后,每个服务冷启动所需的时间,以及占用的内存:
| 服务 | 就绪秒数 | 占用 GB |
|---|---|---|
| qwen3.6 | 237 | 43 |
| rerank | 106 | 8 |
| whisper | 136 | 8 |
| ocr | 137 | 11 |
| image | 96 | 20 |
| tts | 21 | 3 |
嵌入模型在 vLLM 0.30.0 上还需要多一个参数 --convert embed,并且它启动较晚,排在其余模型之后。七个模型全部加载后,这台 DGX Spark 用了 108GB,剩余可用 13GB。这就是一台 DGX Spark 的承载上限。要提前很久启动这套栈:表里六个服务花了大约 12 分钟。
vLLM 启动时还可能报 Error in memory profiling,原因是 vLLM 测量可用内存时另一个进程释放了内存。在共享内存的情况下,只要有别的模型在加载或关闭就会发生。等机器安静下来再启动一次。
第 4 步:在前面放一个统一端点
LiteLLM 给每个客户端一个 OpenAI 兼容端点。每个任务是一条命名路由,这样你可以在不动任何客户端的情况下换掉路由背后的模型。LiteLLM 的开源版还处理虚拟密钥、团队和预算。
LiteLLM 的 config.yaml 里每条路由指向一个后端。下面摘录其中三条:
model_list:
- model_name: qwen3.6 # main model: chat, coding, vision, extraction
litellm_params:
model: hosted_vllm/qwen3.6-35b
api_base: http://host.docker.internal:8001/v1
api_key: os.environ/BACKEND_API_KEY
- model_name: whisper
litellm_params:
model: openai/whisper-large-v3-turbo
api_base: http://host.docker.internal:8101/v1
api_key: os.environ/BACKEND_API_KEY
model_info:
mode: audio_transcription
- model_name: image # Z-Image-Turbo on vLLM-Omni
litellm_params:
model: openai/z-image-turbo
api_base: http://host.docker.internal:8103/v1
api_key: os.environ/BACKEND_API_KEY
model_info:
mode: image_generation
litellm_settings:
drop_params: true # needed for the image route, see below
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
database_url: os.environ/DATABASE_URL
务必保留 drop_params: true。没有它,LiteLLM 会以 400 错误拒绝任何设置了 response_format 的图像请求,因为它不会为自托管图像模型映射这个字段。有了它,LiteLLM 会丢掉该字段,vLLM-Omni 照样以 base64 返回图片。
LiteLLM 也跑在 Docker 里。要让它能访问后端,在其 Compose 服务里把 host.docker.internal 映射到宿主机:
extra_hosts:
- "host.docker.internal:host-gateway"
后端只监听 Docker 桥接网络,并要求共享密钥,所以 LiteLLM 是办公网络上唯一的服务。要让 LiteLLM 指向桥接地址,而不是这台机器的局域网地址。如果用局域网地址,后端会拒绝连接,因为它们没在那里监听。
LiteLLM 按镜像 digest 固定,并使用 1.84.0 或更高版本。2026 年 3 月有两个 PyPI 发布版本被植入后门,1.84.0 修复了两个认证绕过漏洞。我用的是 1.99.1。
现在任何 OpenAI 客户端都能对这台 DGX Spark 使用。下面这个 Python 示例通过同一个客户端调用三个不同的模型:
from openai import OpenAI
client = OpenAI(base_url="http://<spark-ip>:4000/v1", api_key="<your-litellm-key>")
reply = client.chat.completions.create(
model="qwen3.6",
messages=[{"role": "user", "content": "Draft a two-line reply confirming Tuesday's delivery."}],
)
with open("call.wav", "rb") as audio:
transcript = client.audio.transcriptions.create(model="whisper", file=audio)
image = client.images.generate(
model="image",
prompt="a delivery van outside a bakery at dawn",
size="1024x1024",
extra_body={"num_inference_steps": 8},
)
num_inference_steps 不是 OpenAI 的参数,所以放在 extra_body 里。LiteLLM 会把它透传给 vLLM-Omni。下一节解释为什么它重要。
第 5 步:连接 n8n
n8n 的 OpenAI 凭据接受自定义 base URL,所以 n8n 可以像对 OpenAI 说话一样对 DGX Spark 说话。要把 n8n 指向 DGX Spark,按以下步骤操作:
- 为拥有这些工作流的团队创建一个 LiteLLM 虚拟密钥,限定他们需要的路由。下面这条命令用 LiteLLM 主密钥创建一个,并把它打印出来:
curl -s http://<spark-ip>:4000/key/generate \
-H "Authorization: Bearer <your-litellm-master-key>" -H 'Content-Type: application/json' \
-d '{"key_alias": "n8n-accounts", "models": ["qwen3.6", "ocr"]}'
- 在 n8n 中创建一个 OpenAI 凭据。
- 在 API Key 中填入虚拟密钥。
- 把 Base URL 设为
http://<spark-ip>:4000/v1。
因为每个团队有自己的虚拟密钥,LiteLLM 能按团队跟踪花费和用量,尽管并不按 token 计费。
要在 AI Agent 或 Basic LLM Chain 中使用 DGX Spark,添加一个 OpenAI Chat Model 节点并使用该凭据,把其模型 By ID 设为一个 LiteLLM 路由名,比如 qwen3.6。然后关闭节点选项里的 Use Responses API。从 1.3 版起,该节点默认调用 OpenAI 较新的 Responses API,而 DGX Spark 的模型是通过 chat completions 提供的。
工作流:读取发票
这个工作流给你一个网页表单。上传发票图片,表单会显示供应商、发票号、日期和总额,全程不调用云端。获取模板。
它有六个节点:
- Upload invoice(n8n Form Trigger)提供一个带单个文件字段的表单。
- Image to base64(Extract from File)把上传的文件转成 base64 字符串。
- OCR on the DGX Spark(HTTP Request)把图片发给
ocr路由,取回文本形式的页面内容。 - Extract fields with Qwen3.6(HTTP Request)向
qwen3.6索取供应商、发票号、日期、总额和币种,要求返回 JSON。 - Invoice fields(Edit Fields)把回复整理成一个包含这五个字段的条目。
- Show the fields(Form Ending)把它们显示在表单的结果页上。
要自动归档发票,把表单换成 Email Trigger (IMAP) 节点,把结果页换成 Postgres 或 Google Sheets 节点。
两个 HTTP Request 节点都使用上一节的 OpenAI 凭据,所以发送的是同一个密钥。它们用 HTTP Request 而不是 OpenAI 节点,是因为每次调用都需要一个 OpenAI 节点不提供的字段:OCR 调用需要传图片,抽取调用需要一个思考开关。下面是抽取请求的 body:
{
model: 'qwen3.6',
max_tokens: 512,
temperature: 0,
chat_template_kwargs: { enable_thinking: false },
messages: [{ role: 'user', content: 'Extract supplier name, invoice number, invoice date, total amount and currency from this OCR text. Reply with one JSON object and nothing else. Use null for anything missing.\n\n' + $json.choices[0].message.content }]
}
chat_template_kwargs 为这次调用关掉了模型的思考,速度就来自这里。我在一张干净的单页样例发票上做了测试:
| 步骤 | 开思考 | 关思考 |
|---|---|---|
| OCR,一页 | 1.8s | 1.8s |
| 抽取 | 12.1s,1,616 token | 0.59s,76 token |
开思考时,模型在给出 78 token 的答案之前写了 1,538 个推理 token。关思考时,不到一秒就把五个字段全取对了。对固定格式的抽取,关掉思考。
OCR 把每一行明细和总计都读对了,但从一个增值税号里漏掉了一个零。归档之前要校验这些编号。表单接受 PNG 和 JPEG 文件。PDF 发票要先转成图片,因为 GLM-OCR 只吃图片。
扫描件或拍照的发票比这张干净页面难得多,所以在依赖 OCR 之前,先用自己的文档测一测。
同一套栈还能服务的其他工作流
- 通话摘要:Whisper 转写录音,
qwen3.6做摘要,Kokoro 把摘要读出来,用于语音留言或内部通报。 - 基于自己的文档作答:
embed填充向量库,rerank对匹配结果排序,跑在qwen3.6上的 AI Agent 用这些结果作答。
一台 DGX Spark 的实测数据
所有数字来自 2026 年 10 月 13 日的一次基准测试,环境是 vLLM 0.30.0,模型 commit 已固定。换用其他镜像、模型或提示词,你的数字会不同。每个聊天提示词开头都带一个随机标签,免得前缀缓存美化结果。
聊天速度
| 用户数 | 首 token 时间(中位数) | 每用户每秒 token | 每秒总 token |
|---|---|---|---|
| 1 | 0.11s | 120 | 87 |
| 4 | 0.21s | 68 | 242 |
| 8 | 0.29s | 47 | 359 |
每用户速度是各请求首 token 之后生成速度的中位数。总计是所有生成的 token 除以测试耗时,所以也计入了首 token 时间和较慢的请求。单用户那一轮,五个请求里有一个跑在每秒 43 token,其余四个在 117 到 129 之间,把总计拉低到 87。
用 8,537 token 的提示词时,首 token 耗时 1.14 秒。
用户越多,每用户速度越低,但总吞吐持续上升。这就是批处理在起作用:vLLM 每遍历一遍权重就服务多个用户。
其他各项任务
| 任务 | 内容 | 秒数(3 次中位数) |
|---|---|---|
| 文字转语音 | 一段话,30 秒音频 | 0.87 |
| 语音转文字 | 转写该片段 | 0.58 |
| 图像生成 | 一张 1024x1024 图,8 步 | 12.2 |
| OCR | 一页 | 1.80 |
| 抽取 | OCR 文本转发票 JSON,关思考 | 0.59 |
| 嵌入 | 32 段文本 | 0.18 |
| 重排序 | 32 个候选 | 0.03 |
语音两个方向都远快于实时:30 秒音频每个方向都不到一秒。Whisper 的转写很准,但在结尾加了一句音频里没有的“Thank you.”。Whisper 有在静音处编造文本的毛病,转写前先剪掉结尾的静音。
图像生成的步数要自己设。Z-Image-Turbo 设计上约 8 步出图,但 vLLM-Omni 的默认跑了约 48 步,每步 1.5 秒,一张图要 72 秒。设为 8 步后,同样一张 1024x1024 图只要 12.2 秒。
生成图片时的聊天表现
机器上所有模型共用一个 GPU。装得下不等于能一起跑得好。我没找到公开的 DGX Spark 这方面的基准,所以自己测了。我按服务器默认步数连续生成 1024px 图片,同时重跑聊天测试。
| 用户数 | 单独运行时每用户每秒 token | 生成图片时 | 变化 |
|---|---|---|---|
| 1 | 122 | 38 | -69% |
| 4 | 69 | 24 | -65% |
生成图片期间,聊天速度约为正常的三分之一,图片本身也变慢,从 73 秒变成 85 到 122 秒。
一台 DGX Spark 装得下整套栈,但没法在不拖慢所有人聊天的情况下跑图像生成。对小团队来说,把图像任务排队,或者放在非工作时间跑。一旦图像生成成为日常工作的一部分,就把媒体类任务迁到第二台 DGX Spark,第一台专用于文本。
许可证问题
难的不是模型质量,而是许可证。
几个最好的开源文字转语音和图像模型不允许商用。与 Z-Image-Turbo 竞争的 FLUX 系列模型要么非商用,要么需要单独付费的许可证。写作时最强的开源图像模型 Qwen-Image-2.1 以研究许可证发布。有些仓库甚至在宽松许可证的原版旁边放一个非商用的孪生版本,缓存错仓库 ID 就换了你的许可证。
这套栈遵循的规则:
- 只允许 Apache 2.0、MIT、CC-BY-4.0、OpenMDW 和 NVIDIA Open Model License。
- 每个模型固定到 Hugging Face 的某个 commit,这样上游的许可证变更无法悄悄生效。
- 在文档中注明 CC-BY-4.0 模型的出处。
这不是法律意见。在出售或为客户部署之前,请找人审阅每一个固定下来的许可证。
注意事项
- 新的 checkpoint 可能需要比你固定的更新的 vLLM,而报错不会这么说。先检查镜像里的版本。
faster-whisper没有 arm64 的 CUDA 构建,基于它的工具会在不告知你的情况下跑在 CPU 上。- 这套栈用的是 vLLM 而不是入门套件里带的 Ollama。Ollama 适合一个人在笔记本上用。对团队来说,vLLM 会把多用户的请求批处理进每一遍权重遍历,多用户吞吐就来自这里。
动手试试
- 导入发票工作流模板。
- 如果还没有 DGX Spark,先用自托管 AI 入门套件小规模起步。
如果你在自己的 DGX Spark 上跑了这套东西,欢迎在 n8n 社区论坛分享你的数字。
英文来源:
Most small businesses want the same handful of AI jobs. They want to draft emails, summarize calls, read invoices, search their own documents, and make the odd image. Plenty of them can't send customer data to a cloud API to do it.
Often the reason is data privacy and sovereignty. A clinic, a law firm, or an accountant may hold records that a contract, a regulator, or a professional duty says must stay under their control. Once that data goes to a cloud API, it sits on someone else's hardware, possibly in another jurisdiction, under terms the provider can change. Running the models on hardware you own keeps the data in your building, and lets you show an auditor exactly where it went.
Your n8n workflows can transcribe calls, read invoices, answer questions from your own documents, and draft replies without sending a byte to a cloud API. This post shows you how to serve every model those workflows need from one NVIDIA DGX Spark, behind one OpenAI-compatible endpoint that n8n's OpenAI nodes already speak.
A DGX Spark has 128GB of unified memory shared by the CPU and GPU. That's enough to keep every one of those jobs loaded at once, if you pick the right models. Most of this post is about that.
The self-hosted AI starter kit gets one person running local models on a laptop. This is the next step: a box in the office that serves a whole team, with a model for each job and the code to run it.
In this post, you'll:
- Choose models that run fast on the DGX Spark's memory bandwidth
- Serve a 35B mixture-of-experts model with vLLM in NVFP4
- Fit a full AI stack into one box's shared memory
- Route every job through one LiteLLM endpoint with per-team keys
- Connect n8n to the stack and build a workflow that never calls a cloud API
-
Measure what happens to chat speed when someone generates an image
Pick models for the bandwidth, not the parameter count
The DGX Spark's GB10 Grace Blackwell Superchip reads memory at 273 GB/s. When a model generates text, it reads its active weights from memory for each token, so the fewer bytes it reads, the faster it runs.
A mixture-of-experts (MoE) model only activates a few experts per token. Qwen3.6-35B-A3B
has 35B parameters in total but about 3B active. Stored in NVFP4 (NVIDIA's 4-bit floating point format), those active weights are a small fraction of a dense model's.
On my DGX Spark, Qwen3.6-35B-A3B
generated 120 tokens per second for one user, and 359 tokens per second in total across eight users. Those numbers include the reasoning tokens the model writes before it answers.
That gives the first design rule: one strong MoE model for everything text-shaped. Summarizing, classifying, extracting, drafting, coding, and reading screenshots all go to the same model. Every extra model is another container to pin, patch, and monitor, so a specialist model only gets a slot where an LLM can't do the job.
The stack
These are the models that stay loaded on one DGX Spark:Job Model License Chat, coding, vision, extraction nvidia/Qwen3.6-35B-A3B-NVFP4 Apache 2.0 Embeddings Qwen/Qwen3-Embedding-4B Apache 2.0 Reranking Qwen/Qwen3-Reranker-0.6B Apache 2.0 Speech to text openai/whisper-large-v3-turbo MIT Document OCR zai-org/GLM-OCR MIT Image generation Tongyi-MAI/Z-Image-Turbo Apache 2.0 Text to speech hexgrad/Kokoro-82M Apache 2.0 Every model in the stack allows commercial use. That rule cost more than any technical one, and there's a section on it later.
Step 1: Cache everything for offline use
The stack has to run with the network cable unplugged, so download every model once and record the exact commit you got. This loop caches each model and writes a lock file of repository and commit:
pipx install "huggingface_hub[cli]"
hf auth login
for repo in nvidia/Qwen3.6-35B-A3B-NVFP4 Qwen/Qwen3-Embedding-4B Qwen/Qwen3-Reranker-0.6B \
openai/whisper-large-v3-turbo zai-org/GLM-OCR Tongyi-MAI/Z-Image-Turbo; do
hf download "$repo"
echo "$repo $(cat ~/.cache/huggingface/hub/models--${repo//\//--}/refs/main)" >> models.lock
done
When you start a model, pass --revision
with the commit from models.lock
. vLLM then serves the weights you tested rather than whatever main
points to today, so an upstream change can't swap the weights or the license under you.
Check the vLLM version, not the tag
Pin containers the same way, and check what's inside them. My first attempt used vLLM's cu130-nightly
tag. That tag had stopped updating, and the image it pulled was five months old, with vLLM 0.19. It couldn't load Qwen3.6 at all. The errors didn't mention versions. They were a KeyError
for a missing weight-scale parameter, which reads like a broken checkpoint.
Before you trust an image, print the vLLM version inside it:
docker run --rm --entrypoint python3-c "import vllm; print(vllm.version)"
The image that worked was the official vllm/vllm-omni:v0.30.0
, which ships vLLM 0.30.0 with an arm64 build. vLLM-Omni is a full vLLM with extra serving modes for images and speech, so it also serves plain LLMs. Its default entrypoint isn't vllm serve
, so wrap it once:
printf 'FROM vllm/vllm-omni:v0.30.0\nENTRYPOINT ["vllm", "serve"]\n'docker build -t vllm-spark:omni-0.30.0 -
The Kokoro-FastAPI image needed a community fix to build on arm64. The Dockerfile comes from a post by swann.schilling on the NVIDIA Developer Forums.
Step 2: Serve the backbone
This command starts the main model in vLLM:
docker run -d --name qwen3.6 --gpus all --ipc=host \
-p 172.17.0.1:8001:8000 \
-e HF_HUB_OFFLINE=1 -e VLLM_API_KEY=\
-v ~/.cache/huggingface:/root/.cache/huggingface \
vllm-spark:omni-0.30.0 \
nvidia/Qwen3.6-35B-A3B-NVFP4 --revision\
--served-model-name qwen3.6-35b \
--gpu-memory-utilization 0.32 --max-model-len 65536 \
--max-num-seqs 8 --max-num-batched-tokens 8192 \
--trust-remote-code --kv-cache-dtype fp8 --moe-backend marlin \
--enable-chunked-prefill --async-scheduling --enable-prefix-caching \
--speculative-config '{"method":"mtp","num_speculative_tokens":3,"moe_backend":"triton"}' \
--reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser qwen3_xml \
--host 0.0.0.0 --port 8000
On my DGX Spark, this model was ready to serve 311 seconds after docker run
.
The port is published on 172.17.0.1
, Docker's bridge address, so only containers on this machine can reach it. VLLM_API_KEY
makes vLLM reject any request without the key.
The flags that matter most on a DGX Spark:
--gpu-memory-utilization 0.32
caps this model at about a third of memory. The other models need the rest.--kv-cache-dtype fp8
halves the memory the KV cache needs, so the same budget holds more context.--moe-backend marlin
picks the kernel for the quantized expert layers. The draft model's experts aren't quantized, so--speculative-config
sets its ownmoe_backend
.--speculative-config
turns on multi-token prediction (MTP). The model drafts three tokens ahead and checks them in one pass.--reasoning-parser
and--tool-call-parser
must match the model family. When tool calling fails, check these flags before you blame the model.
Check the startup log for the kernels vLLM chose. These lines are from my run:
Using MarlinNvFp4LinearKernel for NVFP4 GEMM
Using 'MARLIN' NvFp4 MoE backend out of potential backends: ['FLASHINFER_TRTLLM', 'FLASHINFER_CUTEDSL', ...]
Your GPU does not have native support for FP4 computation but FP4 quantization is being used.
Weight-only FP4 compression will be used leveraging the Marlin kernel.
That last warning matters for how you read "NVFP4" on a DGX Spark. With this vLLM build, the weights are stored in 4 bits, but the math runs in 16 bits. You still get most of the benefit, because generating tokens is limited by reading weights from memory, and 4-bit weights are a quarter of the size. Faster FP4 kernels for GB10 may raise these numbers in later builds.
Step 3: Fit the rest into shared memory
On a DGX Spark, the GPU and the operating system share one 128GB pool. Linux reported 121GB of it as total memory on my box. That changes two things you might take for granted on a discrete GPU.
First, --gpu-memory-utilization
is a fraction of the whole pool, taken at startup. It's not a fraction of what's free. An instance without a cap tries to take almost everything, and the next model fails to start.
Second, vLLM sizes its cache from the memory that's free when it starts. Start two models at once and they fight over the same memory. Start one at a time, and wait for each to answer its health check before starting the next:
until curl -sf http://172.17.0.1:8001/health; do sleep 5; done
If a model fails with CUDA error: out of memory
while free
shows plenty available, the Linux page cache is holding it. This is common straight after downloading 220GB of weights. Drop the cache, then start the model again:
sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'
This is how long each service took to load from cold, with the page cache dropped first, and how much memory it took:Service Seconds to ready GB taken qwen3.6 237 43 rerank 106 8 whisper 136 8 ocr 137 11 image 96 20 tts 21 3 The embedding model needed one more flag on vLLM 0.30.0, --convert embed
, and started later, after the rest. With all seven models loaded, the DGX Spark used 108GB and had 13GB available. That's the limit of what one DGX Spark holds. Start the stack well before anyone needs it: the six services in the table took about 12 minutes.
vLLM can also fail at startup with Error in memory profiling
, when another process frees memory while vLLM measures what's available. On shared memory, that happens if another model is loading or shutting down. Start the model again once the box is quiet.
Step 4: Put one endpoint in front
LiteLLM gives every client one OpenAI-compatible endpoint. Each job is a named route, so you can swap the model behind a route without touching any client. LiteLLM also handles virtual keys, teams, and budgets in its open-source version.
Each route in LiteLLM's config.yaml
points at a backend. This excerpt shows three of them:
model_list: - model_name: qwen3.6 # main model: chat, coding, vision, extraction
litellm_params:
model: hosted_vllm/qwen3.6-35b
api_base: http://host.docker.internal:8001/v1
api_key: os.environ/BACKEND_API_KEY - model_name: whisper
litellm_params:
model: openai/whisper-large-v3-turbo
api_base: http://host.docker.internal:8101/v1
api_key: os.environ/BACKEND_API_KEY
model_info:
mode: audio_transcription - model_name: image # Z-Image-Turbo on vLLM-Omni
litellm_params:
model: openai/z-image-turbo
api_base: http://host.docker.internal:8103/v1
api_key: os.environ/BACKEND_API_KEY
model_info:
mode: image_generation
litellm_settings:
drop_params: true # needed for the image route, see below
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
database_url: os.environ/DATABASE_URL
Keep drop_params: true
. Without it, LiteLLM rejects any image request that sets response_format
with a 400 error, because it doesn't map that field for a self-hosted image model. With it, LiteLLM drops the field, and vLLM-Omni returns the image as base64 anyway.
LiteLLM runs in Docker too. To let it reach the backends, map host.docker.internal
to the host in its Compose service:
extra_hosts: - "host.docker.internal:host-gateway"
The backends listen only on Docker's bridge network and require a shared key, so LiteLLM is the only service on the office network. Point LiteLLM at the bridge, not at the box's LAN address. If you use the LAN address, the backends refuse the connection, because they aren't listening there.
Pin LiteLLM by image digest, and use version 1.84.0 or later. Two PyPI releases were backdoored in March 2026, and 1.84.0 fixed two authentication bypasses. I ran 1.99.1.
Any OpenAI client now works against the DGX Spark. This Python example calls three different models through the same client:
from openai import OpenAI
client = OpenAI(base_url="http://:4000/v1", api_key=" ")
reply = client.chat.completions.create(
model="qwen3.6",
messages=[{"role": "user", "content": "Draft a two-line reply confirming Tuesday's delivery."}],
)
with open("call.wav", "rb") as audio:
transcript = client.audio.transcriptions.create(model="whisper", file=audio)
image = client.images.generate(
model="image",
prompt="a delivery van outside a bakery at dawn",
size="1024x1024",
extra_body={"num_inference_steps": 8},
)
num_inference_steps
isn't an OpenAI parameter, so it goes in extra_body
. LiteLLM passes it through to vLLM-Omni. The next section explains why it matters.
Step 5: Connect n8n
n8n's OpenAI credential accepts a custom base URL, so n8n can talk to the DGX Spark the same way it talks to OpenAI. To point n8n at the DGX Spark, follow these steps: - Create a LiteLLM virtual key for the team that owns the workflows, limited to the routes they need. This command creates one with the LiteLLM master key and prints it:
curl -s http://:4000/key/generate \
-H "Authorization: Bearer" -H 'Content-Type: application/json' \
-d '{"key_alias": "n8n-accounts", "models": ["qwen3.6", "ocr"]}' - In n8n, create an OpenAI credential.
- Enter the virtual key in API Key.
- Set Base URL to
http://:4000/v1
.
Because each team gets its own virtual key, LiteLLM tracks spend and usage per team, even though nothing is billed per token.
To use the DGX Spark from an AI Agent or a Basic LLM Chain, add an OpenAI Chat Model node with this credential, and set its model By ID to a LiteLLM route name, such as qwen3.6
. Then turn off Use Responses API in the node's options. From version 1.3, the node calls OpenAI's newer Responses API by default, and the DGX Spark's models are served through chat completions.
Workflow: read an invoice
This workflow gives you a web form. Upload an invoice image, and the form shows you the supplier, invoice number, date, and total, with no cloud call anywhere. Get the template.
It has six nodes: - Upload invoice (n8n Form Trigger) serves a form with one file field.
- Image to base64 (Extract from File) turns the upload into a base64 string.
- OCR on the DGX Spark (HTTP Request) sends the image to the
ocr
route and gets the page back as text. - Extract fields with Qwen3.6 (HTTP Request) asks
qwen3.6
for supplier, invoice number, date, total, and currency as JSON. - Invoice fields (Edit Fields) turns the reply into one item with those five fields. -
Show the fields (Form Ending) shows them on the form's result page.
To file invoices automatically, swap the form for an Email Trigger (IMAP) node, and the result page for a Postgres or Google Sheets node.
Both HTTP Request nodes use the OpenAI credential from the previous section, so they send the same key. They use HTTP Request rather than the OpenAI node because each call needs a field the OpenAI node doesn't set: an image in the OCR call, and a thinking switch in the extraction call. This is the body of the extraction request:
{
model: 'qwen3.6',
max_tokens: 512,
temperature: 0,
chat_template_kwargs: { enable_thinking: false },
messages: [{ role: 'user', content: 'Extract supplier name, invoice number, invoice date, total amount and currency from this OCR text. Reply with one JSON object and nothing else. Use null for anything missing.\n\n' + $json.choices[0].message.content }]
}
chat_template_kwargs
turns off the model's thinking for this call, and that's where the speed comes from. I tested it on a clean, single-page sample invoice:Step With thinking Thinking off OCR, one page 1.8s 1.8s Extraction 12.1s, 1,616 tokens 0.59s, 76 tokens With thinking on, the model wrote 1,538 reasoning tokens before a 78-token answer. With thinking off, it got all five fields right in under a second. For fixed-format extraction, turn thinking off.
OCR read every line item and total correctly, but it dropped one zero from a VAT ID. Validate IDs before you file them. The form accepts PNG and JPEG files. Convert PDF invoices to images first, because GLM-OCR takes images.
A scanned or photographed invoice is harder than this clean page, so test OCR on your own documents before you rely on it.
Other workflows the same stack serves -
Call summaries: Whisper transcribes a recording,
qwen3.6
summarizes it, and Kokoro reads the summary back for a voicemail or an internal update. - Answers from your own documents:
embed
fills a vector store,rerank
orders the matches, and an AI Agent onqwen3.6
answers with them.
What one DGX Spark measured
All numbers come from one benchmark run on October 13, 2026, on vLLM 0.30.0 with pinned model commits. Your numbers will differ with other images, models, or prompts. Every chat prompt started with a random tag, so the prefix cache couldn't flatter the results.
Chat speedUsers Time to first token (median) Tokens per second per user Tokens per second total 1 0.11s 120 87 4 0.21s 68 242 8 0.29s 47 359 Per-user speed is the median generation speed of each request, measured after its first token. Total is all tokens generated divided by the test's elapsed time, so it also counts time to first token and any slower requests. In the single-user run, one of the five requests ran at 43 tokens per second while the other four ran at 117 to 129, which pulls the total down to 87.
With an 8,537-token prompt, the first token took 1.14 seconds.
Per-user speed falls as users are added, but total throughput keeps climbing. That's batching at work: vLLM serves several users in each pass over the weights.
Every other jobJob Task Seconds (median of 3) Text to speech One paragraph, 30 seconds of audio 0.87 Speech to text Transcribe that clip 0.58 Image generation One 1024x1024 image, 8 steps 12.2 OCR One page 1.80 Extraction OCR text to invoice JSON, thinking off 0.59 Embeddings 32 passages 0.18 Reranking 32 candidates 0.03 Speech is far faster than real time in both directions: 30 seconds of audio in under a second each way. Whisper's transcript was accurate, but it added a "Thank you." at the end that isn't in the audio. Whisper is known to invent text on silence, so trim trailing silence before you transcribe.
Set the step count for image generation yourself. Z-Image-Turbo is built to make an image in about 8 steps, but vLLM-Omni's default ran about 48 at 1.5 seconds each, which took 72 seconds per image. At 8 steps, the same 1024x1024 image took 12.2 seconds.
Chat while an image generates
Every model on the box shares one GPU. Fitting in memory doesn't mean running well together. I couldn't find a published DGX Spark benchmark for this, so I measured it. I generated 1024px images back to back, at the server's default step count, while running the chat test again.Users Tokens per second per user, alone With images generating Change 1 122 38 -69% 4 69 24 -65% While images were generating, chat ran at about a third of its normal speed, and the images slowed down too, from 73 seconds to between 85 and 122.
One DGX Spark holds the whole stack, but it can't run image generation without slowing everyone's chat. For a small team, queue image jobs or run them outside working hours. Once image generation is part of the daily work, move media to a second DGX Spark and keep the first for text.
The license problem
Model quality wasn't the hard part. Licenses were.
Several of the best open text-to-speech and image models don't allow commercial use. The FLUX models that compete with Z-Image-Turbo are non-commercial or need a separately priced license. The strongest open image model at the time of writing, Qwen-Image-2.1, ships under a research license. Some repositories even have a non-commercial twin next to a permissive original, so caching the wrong repository ID changes your license.
The rules this stack follows: - Allow only Apache 2.0, MIT, CC-BY-4.0, OpenMDW, and the NVIDIA Open Model License.
- Pin every model to a Hugging Face commit, so a license change upstream can't land silently.
- Credit CC-BY-4.0 models in your documentation.
This isn't legal advice. Have someone review every pinned license before you sell or deploy this for a client.
What to watch for - New checkpoints can need a newer vLLM than the one you pinned, and the error won't say so. Check the version inside the image first.
faster-whisper
has no CUDA build for arm64, so tools built on it run on the CPU without telling you.- This stack uses vLLM rather than Ollama, which ships in the starter kit. Ollama suits one person on a laptop. For a team, vLLM batches requests from many users into each pass over the weights, which is where the multi-user throughput comes from.
Try it - Import the invoice workflow template.
- Start smaller with the self-hosted AI starter kit if you don't have a DGX Spark yet.
If you run this on your own DGX Spark, share your numbers on the n8n community forum.
文章标题:快来看,n8n更新了!在NVIDIA DGX Spark上运行你的n8n AI工作流,无需云API
文章链接:https://news.qimuai.cn/?post=5342
本站文章均为原创,未经授权请勿用于任何商业用途