Guide
Spark to NVL72 with Fuzzball
Build and tune on a single NVIDIA DGX Spark, then run the same containers, the same workflow definition, and the same deployment process on a GB300 NVL72 rack.
October 13, 2026A DGX Spark on a desk and a GB300 NVL72 in a data center are the two ends of the same NVIDIA Grace Blackwell family. Without a common platform, moving work between them is two projects: a container and a script that work on the Spark, then a second effort to make the same work run on the rack, with a new scheduler, new storage wiring, a new registry, and a new serving stack.
With Fuzzball, the container, the workflow definition, and the commands you use to run work are the same on both machines. What changes between a Spark and an NVL72 is the size of the resource request and, for a job that spans nodes, one multinode block. Your application stays as it is. The steps below install Fuzzball on the Spark, write one workflow, serve a model, and then take the same files to the rack.
The two ends of the ladder
| NVIDIA DGX Spark | NVIDIA GB300 NVL72 | |
|---|---|---|
| Silicon | One GB10 Grace Blackwell superchip: a 20-core Arm CPU and one Blackwell GPU | 36 Grace CPUs and 72 Blackwell Ultra GPUs in one rack |
| Memory | 128 GB unified between CPU and GPU | HBM on every GPU, one NVLink domain across all 72 |
| Architecture | linux/arm64 |
linux/arm64 |
| Where it lives | A desk, a lab bench, a small team room | A data center row, often alongside an existing HPC cluster |
| What it is for | Developing, tuning, validating, and private inference for one team | Training and serving at the scale of an organization |
Both machines in this table are Arm, so an arm64 container image built for the Spark is the image you run on the rack. Both are Blackwell, but the Spark's GB10 and the rack's Blackwell Ultra are different CUDA compute targets. Framework images such as the PyTorch and vLLM builds already ship kernels for both; GPU code you compile yourself needs both targets in the build, or PTX the driver can compile on the node. The hardware is one family. The gap between the two ends has always been the software assembled around each one.
What stays the same with Fuzzball
Fuzzball runs the same way on a single host and on a multi-node cluster, and it treats a workflow as the unit of work rather than a job on a particular machine. That is what lets the following stay fixed from the Spark to the NVL72:
| Fixed | What it means in practice |
|---|---|
| The container image | Pulled from docker:// or oras://, with the host's NVIDIA driver injected at runtime. Put CUDA and your libraries in the image, built for both GPU targets, and leave the driver out |
| The Fuzzfile | The YAML workflow definition: jobs, volumes, mounts, and resource requests. One file, validated with fuzzball workflow validate |
| The resource request | A job asks for nvidia.com/gpu by count. Fuzzball schedules it onto whatever hardware is there |
| The interfaces | The same CLI, web interface, and API on both machines, and the same MCP server for AI agents |
| The model catalog | fuzzball workflow catalog start with a validated preset or any vLLM compatible model from Hugging Face |
| Storage mounts | A job mounts a named volume at a path. The provisioner behind the volume changes, the mount line does not |
| Identity and access | One IAM, RBAC, and secrets posture, with an execution log for every action |
CIQ built Fuzzball's DGX Spark support so you can move from NVIDIA DGX Spark to NVIDIA GB300 NVL72 without changing the application, the Fuzzball orchestration model, or the Fuzzball deployment process.
Step 1: Install Fuzzball on the Spark
DGX OS ships the NVIDIA Blackwell driver and Docker Engine with the Compose plugin, which is everything the single host install needs. Fuzzball images are multi-arch, so there is nothing to build for arm64.
- Confirm the driver with
nvidia-smi. At least one GPU should be listed with a driver version. - Run the quick install script from the DGX Spark install guide, or install the
arm64CLI package by hand. When it asks about GPU support, answeryso the GPU substrate image is selected. - If you deploy by hand instead, the equivalent command is:
fuzzball cluster docker-compose deploy --gpu --update-etc-hosts --up
- Log in with the CLI and verify that the GPU is visible to the scheduler:
fuzzball node list --available-resources
The AVAILABLE DEVICES column should show nvidia.com/gpu:1. If it does not, check nvidia-smi on the host and rerun the deploy.
What you now have on one machine is an orchestrator, a web interface, authentication, persistent state, and a substrate node that schedules GPU workflows onto the Blackwell GPU. The Spark's 128 GB is unified between CPU and GPU, and the control plane uses a few gigabytes of it, so leave headroom when you size a job's memory request.
Step 2: Write the workflow once
A Fuzzfile describes what a job needs, and Fuzzball decides which machine runs it. Here is a fine tuning job that asks for one GPU and mounts a dataset volume:
version: v4
volumes:
data:
name: finetune-data
jobs:
finetune:
image:
uri: docker://registry.example.com/team/finetune:1.4
mounts:
/data:
volume: data
command:
- python3
- /opt/finetune.py
- --dataset
- /data/train.jsonl
resource:
cpu:
cores: 8
memory:
size: 64GB
devices:
nvidia.com/gpu: 1
Validate it and start it:
fuzzball workflow validate finetune.fz
fuzzball workflow start finetune.fz
A few things are worth noticing. The image is a stock arm64 container with CUDA and your code in it; Fuzzball injects the host driver when the job starts. The GPU request is a device name and a count. And the volume is referenced by name, so the storage that backs it is an administrator's decision, not something written into the workflow.
Drag the same file into the web interface if the person running it would rather not use a terminal. Or point an AI agent at Fuzzball over MCP and let it draft and run the workflow within the permissions you set.
Step 3: Serve a model on the Spark
A tuned model is useful once something can call it. Fuzzball 4.3 serves a model from the catalog in one command:
fuzzball workflow catalog start "Nemotron 3 Nano"
That preset already has the GPU count, context size, and vLLM arguments worked out. Any vLLM compatible open weight model from Hugging Face runs the same way:
fuzzball workflow catalog start vLLM --values Model=hf://openai/gpt-oss-20b
Each served model sits behind an OpenAI compatible endpoint, so a local coding agent, a notebook, or an internal application can use it without changes. Nothing leaves the Spark. For a team that cannot send data to an outside API, this is the whole development loop, privately, on one box: tune, validate, serve, iterate.
The endpoint forwards to an autoscaled pool. On a single Spark that pool is one replica, and with MinReplicas=0 it can drop to zero when no one is calling it. An authenticated request that arrives while the pool is idle gets a 503 with a retry interval and starts a replica, and the retry is served. With the default LiteLLM proxy in front of the pool, the idle request fails fast instead, and the client's normal retries wake the pool the same way.
Step 4: Take the same files to the NVL72
An NVL72 rack is a multi-node Fuzzball deployment rather than a single host. There are two ways to stand it up, and both run the Fuzzfile from Step 2 with the same image, mounts, and command. Only the resource values change, plus a multinode block.
On the rack directly. Fuzzball's Kubernetes Operator deploys the control plane across server nodes and registers each Grace Blackwell compute node as a substrate. This is the production path for a rack that Fuzzball will schedule on its own.
On top of an existing scheduler. If the rack is already part of a Slurm or PBS cluster, Fuzzball runs on top of Slurm or PBS and uses it as a resource manager, so nothing has to move on day one. Fuzzball's scheduler does the scheduling; the existing queues provide the nodes. Fuzzball is also a successor to that generation of resource manager, so moving everything onto Fuzzball later is an option rather than a requirement.
Either way, the edits to the workflow are the resource values and a multinode block. A GB300 NVL72 compute tray holds two Grace CPUs and four Blackwell Ultra GPUs, and the rack has 18 trays, so a node on this rack is a tray with four GPUs. The same fine tuning job across eight nodes with four GPUs each, 32 GPUs in all:
version: v4
volumes:
data:
name: finetune-data
jobs:
finetune:
image:
uri: docker://registry.example.com/team/finetune:1.4
mounts:
/data:
volume: data
command:
- python3
- /opt/finetune.py
- --dataset
- /data/train.jsonl
resource:
cpu:
cores: 64
memory:
size: 400GB
devices:
nvidia.com/gpu: 4
multinode:
nodes: 8
implementation: openmpi
The image line did not change: the rack is arm64 like the Spark, and the image carries kernels for both GPU targets. The mount line did not change, because the volume name is the same even though an administrator has pointed it at a parallel file system such as Lustre, GPFS, or BeeGFS instead of local disk. The command did not change. Fuzzball handles the host list, the launcher, and the environment for the multinode job. If your code uses its own launcher, set implementation: generic and Fuzzball hands you the host list and a wrapper to run commands on the other ranks.
Serving changes the same way. The catalog command is identical; the pool can now grow:
fuzzball workflow catalog start "Nemotron 3 Nano" --values Volume=models,MaxReplicas=8
A pool adds replicas as demand grows and shrinks back when traffic stops, to zero if you set MinReplicas=0, behind one endpoint whose address never changes. A model too large for one node runs as a gang scheduled group of ranks serving from rank 0, and that group is itself a replica the pool can scale. A scaling pool needs a shared volume for the model weights, which on a rack is the normal case.
What changed, and what did not
| Spark | NVL72 | |
|---|---|---|
| Container image | finetune:1.4, arm64 |
Same image |
| Fuzzfile | finetune.fz |
Same file, larger resource and a multinode block |
| GPU request | nvidia.com/gpu: 1 |
nvidia.com/gpu: 4 per node across 8 nodes |
| Volume | Local disk | Parallel file system, same volume name |
| Install | Docker Compose on one host | Kubernetes Operator, or on top of Slurm or PBS |
| Serving | One replica, scales to zero when idle | Autoscaled pool, same endpoint behavior |
| Interfaces | CLI, web, API, MCP | Same |
| Who touches infrastructure | Not touched after install | An administrator, at rack setup and when storage changes |
Beyond one rack
The same control plane extends past a single rack. Fuzzball deploys across AWS, Google Cloud, Oracle Cloud, CoreWeave, and Azure as well as on-premises, and Fuzzball Federate routes a workflow at runtime on cost, performance, and data locality. The IAM, RBAC, and secrets posture travels with the workflow rather than being rebuilt per environment. A team that started on a Spark, grew onto an NVL72, and needs burst capacity in a cloud region is still running one workflow definition.
Several Sparks can also work together under one Fuzzball environment, which gives a small team a way to add local capacity a box at a time before a rack is in the budget.
Practical notes
Image architecture. Build your images for arm64, or as multi-arch images if the same workflow also has to land on an x86 cluster. Both the Spark and a Grace based rack are arm64, which is why a single image covers this whole guide. Inside that image, compile GPU code for both the GB10 and the Blackwell Ultra targets, or ship PTX, so one build runs at both ends.
Drivers versus libraries. Fuzzball injects the host's NVIDIA driver into the container at runtime. Put CUDA and your frameworks in the image; leave the driver out.
Memory requests. The Spark's 128 GB is shared between the CPU and the GPU and the control plane uses part of it. On the rack, GPU memory is HBM per GPU and system memory is separate. A request that fit on the Spark will fit on the rack, but size the rack request for the rack rather than copying the Spark numbers upward.
Local volumes. A volume on a node's local disk pins every stage that mounts it to that node. Multinode jobs and scaling pools need a shared volume.
Validate before you move. fuzzball workflow validate runs against the file, not the cluster, so run it on the rack's copy before the first start.
Get started
- Install Fuzzball on your DGX Spark with the install guide. It takes one script and a login.
- Run one of the example workflows, then serve a model from the catalog.
- Talk to CIQ about the rack. Schedule a demo and we will run your workflow on your hardware, from the Spark up.
Built for scale. Chosen by the world’s best.
2.75M+
Rocky Linux instances
Being used world wide
90%
Of fortune 100 companies
Use CIQ supported technologies
250k
Avg. monthly downloads
Rocky Linux
9
Enterprise products
Spanning the kernel to the orchestrator