←新闻详情

n8n Blog更新于 10月25日 22:04

如何在 NVIDIA DGX Spark 上不调用云 API 运行 n8n AI 工作流

Run your n8n AI workflows on NVIDIA DGX Spark, with no cloud API – n8n Blog

AI 导读

n8n 博客发布教程,介绍如何在一台 NVIDIA DGX Spark 上本地部署完整模型栈,让 n8n 工作流在不调用云 API 的情况下完成转录、OCR、文档问答和图像生成。整套栈通过 LiteLLM 暴露一个 OpenAI 兼容端点,主模型为 Qwen3.6-35B-A3B 的 NVFP4 版本,单用户生成速度 120 tokens/s,八用户合计 359 tokens/s。作者实测发现图像生成会让对话速度降到约三分之一,建议小团队把图像任务排队或放到非工作时间。

推荐理由

AI 评分 52

作者在单台 DGX Spark 上实测整套本地模型栈,给出了显存分配、并发吞吐和图像生成拖慢对话的具体数字。

主题

标签

正文 · AI 翻译

博客文章主视觉区块

在 NVIDIA DGX Spark 上运行你的 n8n AI 工作流,无需调用云 API

Jamie Madden

2026 年 10 月 19 日 ∙ 阅读时长 14 分钟

文章正文

大多数小企业想要的 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

图像生成 Tongyi-MAI/Z-Image-Turbo Apache 2.0

文本转语音 hexgrad/Kokoro-82M Apache 2.0

这套栈中的每个模型都允许商业使用。这条规则比任何技术规则代价都大,后面会有一节专门讲它。

第 1 步:缓存一切以供离线使用

这套栈必须能在拔掉网线的情况下运行,所以每个模型只下载一次,并记录你拿到的确切提交。下面这个循环会缓存每个模型,并写入一个包含仓库和提交的锁文件:

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 中的提交给 --revision 传参。这样 vLLM 提供的就是你测试过的权重,而不是 main 今天指向的东西,因此上游的改动无法在你不知情的情况下替换权重或许可证。

检查 vLLM 版本,而不是标签

用同样的方式固定容器,并检查容器里装了什么。我第一次尝试用的是 vLLM 的 cu130-nightly 标签。那个标签已经停止更新,它拉取的镜像已是五个月前的,里面是 vLLM 0.19。它根本加载不了 Qwen3.6。报错信息里没有提到版本。它们是一个 KeyError,指向缺失的权重缩放参数,读起来像是检查点损坏了。

在信任一个镜像之前,先打印出里面的 vLLM 版本:

docker run --rm --entrypoint python3 -c "import vllm; print(vllm.__version__)"

可用的镜像是官方 vllm/vllm-omni:v0.30.0,它附带 vLLM 0.30.0 的 arm64 构建。vLLM-Omni 是一个完整的 vLLM,额外提供图像和语音的服务模式,因此它也能服务普通 LLM。它的默认入口点不是 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= \ -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

在我的 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 选择的内核。以下是我这次运行中的几行:

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 内核可能会在后续构建中提升这些数字。

第 3 步:把其余部分装进共享内存

在 DGX Spark 上,GPU 和操作系统共享同一个 128GB 内存池。在我的机器上,Linux 报告其中 121GB 为总内存。这改变了两件你在独立 GPU 上可能视为理所当然的事。

第一,--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

嵌入模型在 vLLM 0.30.0 上还需要一个额外参数 --convert embed,并且启动较晚,排在其余模型之后。七个模型全部加载后,DGX Spark 使用了 108GB,剩余 13GB 可用。这就是一台 DGX Spark 的承载上限。要提前很久启动整套栈:表中的六个服务大约花了 12 分钟。

当 vLLM 测量可用内存时,如果有另一个进程释放了内存,vLLM 也可能在启动时失败,报错为 Error in memory profiling。在共享内存上,如果另一个模型正在加载或关闭,就会出现这种情况。等机器空闲下来后,再重新启动该模型。

第 4 步:在前面放一个端点

LiteLLM 为每个客户端提供一个 OpenAI 兼容端点。每个任务都是一条命名路由,因此你可以在不改动任何客户端的情况下,替换某条路由背后的模型。LiteLLM 的开源版本还处理虚拟密钥、团队和预算。

LiteLLM 的 config.yaml 中每条路由都指向一个后端。以下摘录展示了其中三条:

model_list: - model_name: qwen3.6 # 主模型:对话、编码、视觉、提取 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 # vLLM-Omni 上的 Z-Image-Turbo 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 # 图像路由需要此项,详见下文

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 的镜像摘要,并使用 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:// :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 不是 OpenAI 的参数,因此要放在 extra_body 中。LiteLLM 会把它透传给 vLLM-Omni。下一节会解释它为什么重要。

第 5 步:连接 n8n

n8n 的 OpenAI 凭据支持自定义 base URL,因此 n8n 可以像与 OpenAI 通信一样与 DGX Spark 通信。要让 n8n 指向 DGX Spark,请按以下步骤操作:

为拥有这些工作流的团队创建一个 LiteLLM 虚拟密钥,并将其限制在他们需要的路由上。以下命令使用 LiteLLM 主密钥创建一个虚拟密钥并打印出来:

curl -s http:// :4000/key/generate \ -H "Authorization: Bearer " -H 'Content-Type: application/json' \ -d '{"key_alias": "n8n-accounts", "models": ["qwen3.6", "ocr"]}'

在 n8n 中,创建一个 OpenAI 凭据。

在 API Key 中输入该虚拟密钥。

将 Base URL 设置为 http:// :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 提供的。

工作流:读取发票

此工作流为你提供一个网页表单。上传发票图片后,表单会显示供应商、发票号码、日期和总额,全程不调用任何云服务。获取模板。

它包含六个节点:

上传发票(n8n Form Trigger)提供一个带单个文件字段的表单。

图像转 base64(从文件提取)将上传内容转换为 base64 字符串。

在 DGX Spark 上运行 OCR(HTTP Request)将图像发送到 ocr 路由,并以文本形式取回页面内容。

用 Qwen3.6 提取字段(HTTP Request)向 qwen3.6 请求以 JSON 形式返回供应商、发票号码、日期、总额和货币。

发票字段(Edit Fields)将回复转换为一个包含这五个字段的条目。

显示字段(Form Ending)在表单的结果页面上显示这些字段。

要自动归档发票,请将表单替换为 Email Trigger (IMAP) 节点,并将结果页面替换为 Postgres 或 Google Sheets 节点。

两个 HTTP Request 节点都使用上一节中的 OpenAI 凭据,因此它们发送相同的密钥。它们使用 HTTP Request 而不是 OpenAI 节点,因为每次调用都需要一个 OpenAI 节点不会设置的字段:OCR 调用中的图像,以及提取调用中的思考开关。 这是提取请求的主体:

{ model: 'qwen3.6', max_tokens: 512, temperature: 0, chat_template_kwargs: { enable_thinking: false }, messages: [{ role: 'user', content: '从这段 OCR 文本中提取供应商名称、发票号码、发票日期、总金额和货币。只回复一个 JSON 对象,不要回复其他内容。缺失的字段用 null。\n\n' + $json.choices[0].message.content }] }

chat_template_kwargs 会在本次调用中关闭模型的思考功能,速度提升正来源于此。我在一张干净的单页示例发票上做了测试:

步骤 开启思考 关闭思考

OCR,单页 1.8s 1.8s

提取 12.1s,1,616 tokens 0.59s,76 tokens

开启思考后,模型在给出 78 个 token 的答案之前写下了 1,538 个推理 token。关闭思考后,它在不到一秒内就把全部五个字段都答对了。对于固定格式的提取任务,请关闭思考。

OCR 正确读取了每一个行项目和总计,但它从 VAT ID 中漏掉了一个零。在提交之前请先校验 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,模型提交版本已固定。你的数据会因镜像、模型或提示词不同而有所差异。每个聊天提示词都以随机标签开头,因此前缀缓存无法美化结果。

聊天速度

用户 首 token 时间(中位数) 每用户每秒 token 数 每秒总 token 数

每用户速度是每个请求在首 token 之后测得的生成速度中位数。总量是所有生成的 token 数除以测试耗时,因此也计入首 token 时间和较慢的请求。在单用户运行中,五个请求里有一个以每秒 43 个 token 运行,其余四个为每秒 117 到 129 个 token,这使总量降至 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 的某个提交,这样上游的许可证变更就无法悄无声息地生效。

在文档中注明 CC-BY-4.0 模型的署名。

这不是法律建议。在销售或为客户部署之前,请找人审查每一个已固定的许可证。

需要注意的事项

新的检查点可能需要比你固定的版本更新的 vLLM,而错误信息不会说明这一点。请先检查镜像内的版本。

faster-whisper 没有适用于 arm64 的 CUDA 构建,因此基于它构建的工具会在不告知你的情况下在 CPU 上运行。

这套栈使用 vLLM 而不是入门套件中自带的 Ollama。Ollama 适合一个人在笔记本电脑上使用。对于团队来说,vLLM 会把来自许多用户的请求批处理到每次对权重的遍历中,多用户吞吐量正是由此而来。

试一试

导入发票工作流模板。

如果你还没有 DGX Spark,可以先从自托管 AI 入门套件开始,规模更小。

如果你在自己的 DGX Spark 上运行这套方案,请在 n8n 社区论坛上分享你的数据。

与我们分享

其他入门指南

有志者,工作流已在

最新 n8n 指南

指南ITOps

12 个最佳企业工作流自动化平台

n8n 团队,Yulia Dmitrievna

AI 智能体新闻

使用 Gemini 4 Argon 构建 n8n 工作流:MCP、助手和智能体

Liam Hampton

使用 You.com、Jev 和 n8n 构建晨间 AI 新闻简报

n8n 团队

正在处理您的申请 请检查您的收件箱并点击链接以确认您的订阅 发送电子邮件时出错

来源

n8n Blog